add missing imports
[matthijs/upstream/django-ldapdb.git] / ldapdb / models / base.py
1 # -*- coding: utf-8 -*-
2
3 # django-ldapdb
4 # Copyright (c) 2009-2010, BollorĂ© telecom
5 # All rights reserved.
6
7 # See AUTHORS file for a full list of contributors.
8
9 # Redistribution and use in source and binary forms, with or without modification,
10 # are permitted provided that the following conditions are met:
11
12 #     1. Redistributions of source code must retain the above copyright notice, 
13 #        this list of conditions and the following disclaimer.
14 #     
15 #     2. Redistributions in binary form must reproduce the above copyright 
16 #        notice, this list of conditions and the following disclaimer in the
17 #        documentation and/or other materials provided with the distribution.
18
19 #     3. Neither the name of BollorĂ© telecom nor the names of its contributors
20 #        may be used to endorse or promote products derived from this software
21 #        without specific prior written permission.
22
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
27 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
30 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #
34
35 import ldap
36 import logging
37
38 import django.db.models
39 from django.db import connections, router
40 from django.db.models import signals
41
42 import ldapdb
43 from ldapdb.models.query import QuerySet
44
45 class ModelBase(django.db.models.base.ModelBase):
46     """
47     Metaclass for all LDAP models.
48     """
49     def __new__(cls, name, bases, attrs):
50         super_new = super(ModelBase, cls).__new__
51         new_class = super_new(cls, name, bases, attrs)
52
53         # patch manager to use our own QuerySet class
54         if not new_class._meta.abstract:
55             def get_query_set():
56                 return QuerySet(new_class)
57             new_class.objects.get_query_set = get_query_set
58             new_class._default_manager.get_query_set = get_query_set
59
60         return new_class
61
62 class Model(django.db.models.base.Model):
63     """
64     Base class for all LDAP models.
65     """
66     __metaclass__ = ModelBase
67
68     dn = django.db.models.fields.CharField(max_length=200)
69
70     # meta-data
71     base_dn = None
72     search_scope = ldap.SCOPE_SUBTREE
73     object_classes = ['top']
74
75     def __init__(self, *args, **kwargs):
76         super(Model, self).__init__(*args, **kwargs)
77         self.saved_pk = self.pk
78
79     def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
80         """
81         This private API seems to be called by the admin interface in django 1.2
82         """
83         pk_val = self._get_pk_val()
84         seen_objs.add(self.__class__, pk_val, self,
85                       type(parent), parent, nullable)
86
87     def build_rdn(self):
88         """
89         Build the Relative Distinguished Name for this entry.
90         """
91         bits = []
92         for field in self._meta.fields:
93             if field.db_column and field.primary_key:
94                 bits.append("%s=%s" % (field.db_column, getattr(self, field.name)))
95         if not len(bits):
96             raise Exception("Could not build Distinguished Name")
97         return '+'.join(bits)
98
99     def build_dn(self):
100         """
101         Build the Distinguished Name for this entry.
102         """
103         return "%s,%s" % (self.build_rdn(), self.base_dn)
104         raise Exception("Could not build Distinguished Name")
105
106     def delete(self, using=None):
107         """
108         Delete this entry.
109         """
110         using = using or router.db_for_write(self.__class__, instance=self)
111         connection = connections[using]
112         logging.debug("Deleting LDAP entry %s" % self.dn)
113         connection.delete_s(self.dn)
114         signals.post_delete.send(sender=self.__class__, instance=self)
115
116     def save(self, using=None):
117         """
118         Saves the current instance.
119         """
120         using = using or router.db_for_write(self.__class__, instance=self)
121         connection = connections[using]
122         if not self.dn:
123             # create a new entry
124             record_exists = False 
125             entry = [('objectClass', self.object_classes)]
126             new_dn = self.build_dn()
127
128             for field in self._meta.fields:
129                 if not field.db_column:
130                     continue
131                 value = getattr(self, field.name)
132                 if value:
133                     entry.append((field.db_column, field.get_db_prep_save(value, connection=connection)))
134
135             logging.debug("Creating new LDAP entry %s" % new_dn)
136             connection.add_s(new_dn, entry)
137
138             # update object
139             self.dn = new_dn
140
141         else:
142             # update an existing entry
143             record_exists = True
144             modlist = []
145             orig = self.__class__.objects.get(pk=self.saved_pk)
146             for field in self._meta.fields:
147                 if not field.db_column:
148                     continue
149                 old_value = getattr(orig, field.name, None)
150                 new_value = getattr(self, field.name, None)
151                 if old_value != new_value:
152                     if new_value:
153                         modlist.append((ldap.MOD_REPLACE, field.db_column, field.get_db_prep_save(new_value, connection=connection)))
154                     elif old_value:
155                         modlist.append((ldap.MOD_DELETE, field.db_column, None))
156
157             if len(modlist):
158                 # handle renaming
159                 new_dn = self.build_dn()
160                 if new_dn != self.dn:
161                     logging.debug("Renaming LDAP entry %s to %s" % (self.dn, new_dn))
162                     connection.rename_s(self.dn, self.build_rdn())
163                     self.dn = new_dn
164             
165                 logging.debug("Modifying existing LDAP entry %s" % self.dn)
166                 connection.modify_s(self.dn, modlist)
167             else:
168                 logging.debug("No changes to be saved to LDAP entry %s" % self.dn)
169
170         # done
171         self.saved_pk = self.pk
172         signals.post_save.send(sender=self.__class__, instance=self, created=(not record_exists))
173
174     @classmethod
175     def scoped(base_class, base_dn):
176         """
177         Returns a copy of the current class with a different base_dn.
178         """
179         import new
180         import re
181         suffix = re.sub('[=,]', '_', base_dn)
182         name = "%s_%s" % (base_class.__name__, str(suffix))
183         new_class = new.classobj(name, (base_class,), {'base_dn': base_dn, '__module__': base_class.__module__})
184         return new_class
185