From: Matthijs Kooijman Date: Sat, 13 Aug 2011 20:07:06 +0000 (+0200) Subject: allow dynamically adding object classes to types X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=matthijs%2Fupstream%2Fdjango-ldapdb.git allow dynamically adding object classes to types Doing so changes the type of an object and automatically registers new fields. This requires separately describing the fields in an object class. --- diff --git a/ldapdb/backends/ldap/compiler.py b/ldapdb/backends/ldap/compiler.py index 972606f..3636ede 100644 --- a/ldapdb/backends/ldap/compiler.py +++ b/ldapdb/backends/ldap/compiler.py @@ -46,7 +46,12 @@ def get_lookup_operator(lookup_type): return '=' def query_as_ldap(query): - filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes]) + # TODO: Filtering on objectClass temporarily disabled, since this + # breaks Model.save() after an objectclass was added (it queries the + # database for the old values to see what changed, but filtering on + # the new objectclasses does not return the object). + #filterstr = ''.join(['(objectClass=%s)' % cls for cls in query.model.object_classes]) + filterstr = '' sql, params = where_as_ldap(query.where) filterstr += sql return '(&%s)' % filterstr diff --git a/ldapdb/models/__init__.py b/ldapdb/models/__init__.py index 7dc12ed..ae3a4e4 100644 --- a/ldapdb/models/__init__.py +++ b/ldapdb/models/__init__.py @@ -32,4 +32,4 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -from ldapdb.models.base import Model +from ldapdb.models.base import Model, ObjectClass diff --git a/ldapdb/models/base.py b/ldapdb/models/base.py index 8cce087..8535346 100644 --- a/ldapdb/models/base.py +++ b/ldapdb/models/base.py @@ -40,6 +40,10 @@ from django.db import connections, router from django.db.models import signals import ldapdb +import fields + +# Dict containing +object_classes = {} class Model(django.db.models.base.Model): """ @@ -114,6 +118,7 @@ class Model(django.db.models.base.Model): # update an existing entry record_exists = True modlist = [] + modlist.append((ldap.MOD_REPLACE, 'objectClass', [x.encode(connection.charset) for x in self.object_classes])) orig = self.__class__.objects.get(pk=self.saved_pk) for field in self._meta.fields: if not field.db_column: @@ -143,6 +148,58 @@ class Model(django.db.models.base.Model): self.saved_pk = self.pk signals.post_save.send(sender=self.__class__, instance=self, created=(not record_exists)) + def add_object_class(self, oc): + """ + Add an extra object class to this object. The added objectclass + must be defined as a subclass of ObjectClass. This changes the + type of this object to add the fields of the new + objectclass. + + The objectclass passed can be a string naming the objectclass, + or an ObjectClass subclass. + """ + + # The new class is a subclass of Model + bases = [Model] + list(Model.__bases__) + + object_classes = self.__class__.object_classes + object_classes.append(oc) + + dict_ = { + # Copy the module from Model + '__module__': Model.__module__, + # Generate some documentation + '__doc__': 'Automatically generated class for LDAP objects with objectclasses: ' + (', '.join(object_classes)), + # Add a class variable containing the object_classes + 'object_classes': object_classes, + # Copy the base_dn from the old class + 'base_dn': self.__class__.base_dn, + } + + # Add all current fields, but leave out fields that Model + # already has (these will be added by Django later, since we + # make the new class a subclass of Model). + dict_.update([(f.name, f) for f in self._meta.fields if not f in Model._meta.fields]) + + # If the passed object class is a string, resolve it to an + # ObjectClass object. + if (isinstance(oc, basestring)): + oc = ObjectClass.object_classes[oc] + + # Copy all fields (e.g., subclasses of Field) from the new + # ObjectClass + for k,v in oc.__dict__.items(): + if isinstance(v, django.db.models.fields.Field): + dict_[k] = v + + # Generate a name for the class (gotta have something...) + name = '_'.join(object_classes) + + # And finally, generate the type + self.__class__ = type(name, tuple(bases), dict_) + + # TODO: remove_object_class + @classmethod def scoped(base_class, base_dn): """ @@ -157,3 +214,16 @@ class Model(django.db.models.base.Model): class Meta: abstract = True + +class ObjectClass(object): + """ + Superclass for LDAP objectclass descriptors. + """ + + # Mapping from objectclass name (string) to the ObjectClass subclass + # that describes it. Used when loading items from the database or + # when adding object classes. + # + # TODO: Automatically fill this list from a fancy metaclass + object_classes = {} +