diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0356801 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dd_example +*.pyc +local.db diff --git a/README.rst b/README.rst index bb714a5..c7aaca9 100644 --- a/README.rst +++ b/README.rst @@ -35,3 +35,7 @@ using any attribute with any instance at the database level but schema can be used to constrain user interface when editing or creating an instance. Django directory is especially adapted for identity management solutions. + +It should be simple to create connector allowing to synchronize a django +directory and an external directory or to provide a virtual view of an external +directory. diff --git a/django_directory/admin.py b/django_directory/admin.py index 6612c34..f037bd6 100644 --- a/django_directory/admin.py +++ b/django_directory/admin.py @@ -1,11 +1,34 @@ from django.contrib.admin import AdminSite, ModelAdmin, site, TabularInline -from django.db import models +from django.utils.translation import gettext_lazy as _ from .schema import get_schemas, schemas_update_subscribe -from models import (Schema, View, ViewMember, Attribute, Principal, +from .models import (Schema, View, ViewMember, Attribute, Principal, Predicate, Relation, AttributeValue, PrincipalUser) +class AttributeValueInlineAdmin(TabularInline): + model = AttributeValue + +class ChildRelationsInlineAdmin(TabularInline): + model = Relation + fk_name = 'object' + verbose_name_plural = _('Child relations') + +class ParentRelationsInlineAdmin(TabularInline): + model = Relation + fk_name = 'value' + verbose_name_plural = _('Parent relations') + +class PrincipalModelAdmin(ModelAdmin): + inlines = [ AttributeValueInlineAdmin, ChildRelationsInlineAdmin, + ParentRelationsInlineAdmin ] + +class ViewMemberInlineAdmin(TabularInline): + model = ViewMember + +class ViewModelAdmin(ModelAdmin): + inlines = [ ViewMemberInlineAdmin] + class DirectoryAdmin(AdminSite): def __init__(self, *args, **kwargs): super(DirectoryAdmin, self).__init__(*args, **kwargs) @@ -40,7 +63,7 @@ class DirectoryAdmin(AdminSite): qs = qs.filter(schema=schema) qs.prefetch_related('attributes', 'child_relations', 'parent_relations') return qs - model_admin = type(model_admin_name, (ModelAdmin,), dict(queryset=queryset)) + model_admin = type(model_admin_name, (PrincipalModelAdmin,), dict(queryset=queryset)) return model_admin def regenerate_proxy_models(self): @@ -73,21 +96,10 @@ class DirectoryAdmin(AdminSite): site = DirectoryAdmin() -class AttributeValueInlineAdmin(TabularInline): - model = AttributeValue -class PrincipalModelAdmin(ModelAdmin): - inlines = [ AttributeValueInlineAdmin ] - -class ViewMemberInlineAdmin(TabularInline): - model = ViewMember - -class ViewModelAdmin(ModelAdmin): - inlines = [ ViewMemberInlineAdmin] +site.register(Principal, PrincipalModelAdmin) +site.register(View, ViewModelAdmin) for model in (Schema, Attribute, Predicate, Relation, AttributeValue, PrincipalUser): site.register(model) - -site.register(Principal, PrincipalModelAdmin) -site.register(View, ViewModelAdmin) diff --git a/django_directory/models.py b/django_directory/models.py index 595c645..3db31f6 100644 --- a/django_directory/models.py +++ b/django_directory/models.py @@ -276,16 +276,55 @@ class DirectoryUser(User): class Predicate(models.Model): name = models.CharField(max_length=128, unique=True) display_name = models.CharField(max_length=256) + symmetric = models.BooleanField(blank=True) + transitive = models.BooleanField(blank=True) def __unicode__(self): return self.display_name or self.name +class RelationManager(models.Manager): + def closure(self, filter_field=None, filter_value=None, predicate=None, + field=None, seen=None): + '''Compute transitive closure of relations''' + kwargs = { filter_field: filter_value } + qs = self.filter(predicate=predicate, **kwargs).values_list(field, flat=True) + traverse = set(qs) - seen + seen |= traverse + if predicate.transitive: + for value in traverse: + self.closure(filter_field=filter_field, filter_value=value, predicate=predicate, + field=field, seen=seen) + return seen + + def values1(self, instance, predicate): + return self.closure(filter_field='object', filter_value=instance, + predicate=predicate, field='value', seen=set()) + + def objects1(self, instance, predicate): + return self.closure(filter_field='value', filter_value=instance, + predicate=predicate, field='object', seen=set()) + + def values(self, instance, predicate): + seen = self.values1(instance, predicate) + if predicate.symmetric: + seen |= self.objects1(instance, predicate) + return seen + + def objects(self, instance, predicate): + seen = self.objects1(instance, predicate) + if predicate.symmetric: + seen |= self.values1(instance, predicate) + return seen + class Relation(models.Model): '''State a relation between two principals''' object = models.ForeignKey(Principal, related_name='child_relations') predicate = models.ForeignKey(Predicate, related_name='relations') value = models.ForeignKey(Principal, related_name='parent_relations') + class Meta: + unique_together = (('object', 'predicate', 'value'),) + def update_schema(sender, **kwargs): from . import schema print 'update schema', kwargs @@ -297,3 +336,4 @@ for model in (Schema, View, ViewMember, Attribute, Predicate, Relation): post_delete.connect(update_schema, sender=model, dispatch_uid='schema-rebuild') +import signals diff --git a/django_directory/signals.py b/django_directory/signals.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/django_directory/signals.py @@ -0,0 +1 @@ +