From cc17d0a6d40bbf8db7ca85497333aae080f7738c Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 16 Apr 2012 09:51:37 +0200 Subject: [PATCH] First usable version.. The object model supports schema, views (aggregation of attributes for view/edition purpose), attributes and relations. The addition of relation make it a bit more powerful than the classical LDAP object model. To map an LDAP schema, you can use oid URN for attribute names. --- .gitignore | 3 +++ README.rst | 4 ++++ django_directory/admin.py | 44 +++++++++++++++++++++++-------------- django_directory/models.py | 40 +++++++++++++++++++++++++++++++++ django_directory/signals.py | 1 + 5 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 .gitignore create mode 100644 django_directory/signals.py 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 @@ +