| Trees | Indices | Help |
|
|---|
|
|
1 '''
2 @author: jnaous
3 '''
4 import logging
5 from django.db import models
6 from django.db.models.base import ModelBase
7 from django.utils.importlib import import_module
8 from expedient.common.tests.utils import drop_to_shell
9
10 logger = logging.getLogger("extendable.models")
11
13 '''
14 Metaclass for all Extendable models.
15 '''
16
18 '''Add the fields in 'extended_fields to all
19 grandchildren of Extendable. Also connect the post_save signal
20 to save the content_object.'''
21
22 # get the inner Extend class
23 class Extend: pass
24 extend = attrs.setdefault('Extend', Extend)
25
26 # Only do this for subclasses of Extendable
27 if (name != "Extendable" or
28 attrs['__module__'] != ExtendableMeta.__module__):
29
30 # get the extended fields of the parents
31 extended_fields = {}
32 repl_kw = set()
33 for base in bases:
34 if isinstance(base, ExtendableMeta):
35 base_extend = getattr(base, "Extend", Extend)
36 # check for clashes in field names and replacement keywords
37 # and add to dict
38 for fname, fval in \
39 getattr(base_extend, "fields", {}).items():
40 field_cls, args, kwargs, args_repl, kwargs_repl = fval
41 if fname in extended_fields:
42 raise Exception(
43 "Extended field '%s' from %s " % (fname, base)
44 +"already defined in another parent of "
45 +"%s.%s" % (attrs['__module__'], name))
46 else:
47 clashes = set(args_repl).intersection(repl_kw)
48 clashes = clashes.union(set(kwargs_repl.values())\
49 .intersection(repl_kw))
50 if clashes:
51 raise Exception(
52 "Replacement keywords '%s' " % clashes
53 +"from %s already " % attrs['__module__']
54 +"defined in another parent of %s.%s" % (
55 name, base))
56 else:
57 extended_fields[fname] = fval
58
59 # get the delegations, check that they are all in extended_fields
60 delegations = getattr(extend, "redelegate", [])
61 for d in delegations:
62 if d not in extended_fields:
63 raise Exception(
64 "Redelegated field '%s' is not an extended" % d
65 +" field in any of %s.%s's parents." %(
66 attrs['__module__'], name))
67 else:
68 # remove the field from the list to add to this class
69 # and put it in this class's inner Extend
70 extend.fields = getattr(extend, "fields", {})
71 extend.fields[d] = extended_fields[d]
72 del extended_fields[d]
73
74 if extended_fields:
75 # get replacements
76 repl = getattr(extend, 'replacements', {})
77
78 # create each field in the child, replacing when necessary
79 for fname, fval in extended_fields.items():
80 # expand the information about the field
81 field_cls, args, kwargs, args_repl, kwargs_repl = fval
82
83 # check if there are any replacement for arguments
84 if args_repl:
85 if len(args_repl) != len(args):
86 raise Exception("Arguments list must be of the "
87 + "same length as its replacement "
88 + "keywords.")
89 z = zip(args, args_repl)
90 args = [repl[t[1]] \
91 if t[1] != None and t[1] in repl \
92 else t[0] for t in z]
93
94 # check for keyword argument replacements
95 kwargs = kwargs.copy()
96 if kwargs_repl:
97 for repl_arg, repl_key in kwargs_repl.items():
98 if repl_key in repl:
99 kwargs[repl_arg] = repl[repl_key]
100
101 # check for clashes
102 if fname in attrs:
103 raise Exception(
104 "Field '%s' clashes with extended field " % fname
105 +"in class %s.%s" % (attrs['__module__'], name))
106
107 # now create the field in this class
108 attrs[fname] = field_cls(*args, **kwargs)
109
110 new_cls = super(ExtendableMeta, cls).__new__(cls, name, bases, attrs)
111
112 return new_cls
113
114
116 """
117 A manager for Extendable objects.
118 """
119
121 """
122 Return a filtered QuerySet that only has instances whose
123 leaf class is C{klass}.
124
125 @param klass: The leaf model class of instances we are looking for.
126 @type klass: a class
127 """
128 return self.filter(leaf_name=klass.__name__.lower())
129
131 """
132 Return a filtered QuerySet that only has instances whose
133 leaf class are in the list C{klasses}.
134
135 @param klasses: List of leaf model classes of instances we are
136 looking for.
137 @type klasses: list of classes
138 """
139 cls_names = [c.__name__.lower() for c in klasses]
140 return self.filter(leaf_name__in=cls_names)
141
143 '''
144 Extendable object.
145
146 An extendable object that enables an instance to be obtained as its
147 class's farthest descendant.
148
149 Additionally, fields can be defined that would be added only
150 to a direct subclass. This can be useful to automatically add relationships
151 to subclasses so that relationships exist in the subclasses but not in the
152 parents, allowing the same field name to be used for different types of
153 relationships.
154
155 Further, the class that inherits from C{Extendable} may specify which
156 field initialization arguments
157 subclasses may override. Subclasses can override the arguments by adding
158 values to a L{dict} in an inner class.
159
160 Adding fields is done by adding an C{Extend} inner class. Three fields can
161 be used in the C{Extend} inner class:
162 1. C{fields}: This is a L{dict} that describes what the fields to be added
163 in the subclass are. The format is as follows ::
164
165 {field_name : (field_class, args, kwargs, repl_args, repl_kwargs)}
166
167 - C{field_name}: name of the field to add to subclass
168 - C{field_class}: class of the field e.g. ManyToManyField
169 - C{args}: a list of pos args with which to init field_class
170 - C{kwargs}: a list of keyword args with which to init field_class
171 - C{repl_args}: either None or a list with the same length as
172 C{args} that specifies which arguments can be overridden, and
173 what keyword to use to replace that argument.
174 - C{repl_kwargs}: either None or a dict that specifies which kwargs
175 can be overridden by subclasses and what keyword to use for the
176 override.
177
178 2. C{mandatory}: C{iterable} of keywords that subclasses must replace.
179
180 3. C{replacements}: L{dict} that specifies the replacements to use in the
181 subclass using the keywords defined in the C{repl_args} and
182 C{repl_kwargs} in a parent's C{Extend.fields} values.
183
184 4. C{redelegate}: C{iterable} of field names from C{fields} in a
185 parent's C{Extend} inner class that specifies which fields should not
186 be added in this class but its subclass.
187
188 Using the C{mandatory} field in a child class of C{Exendable} forces
189 grandchildren of to specify replacements for particular keywords.
190
191 Grandchildren may further delegate adding fields to their own children i.e.
192 the great grandchildren by specifying the field names in their C{redelegate}
193 field of their inner C{Extend} class.
194
195 For example: ::
196
197 from django.db import models
198
199 class OtherModel(models.Model): pass
200
201 class OtherModelRel(models.Model):
202 parent = models.ForeignKey("TestParent")
203 other = models.ForeignKey(OtherModel)
204
205 class YetAnotherModel(models.Model): pass
206
207 class YetAnotherModelRel(models.Model):
208 child = models.ForeignKey("TestChild")
209 other = models.ForeignKey(YetAnotherModel)
210
211 class TestParent(Extendable):
212 class Extend:
213 fields = {
214 'other': (models.ManyToManyField,
215 [OtherModel,],
216 {'through': OtherModelRel},
217 ['other_model',],
218 {'through': 'other_through'},
219 ),
220 }
221 mandatory = [
222 'other_through',
223 ]
224
225 class TestChild(TestParent):
226 class Extend:
227 replacements = {
228 'other_model': YetAnotherModel,
229 'other_through': YetAnotherModelRel,
230 }
231 '''
232
233 objects = ExtendableManager()
234
235 leaf_name = models.CharField(max_length=100, blank=True, editable=False)
236 module_name = models.CharField(max_length=100, blank=True, editable=False)
237
238 __metaclass__ = ExtendableMeta
239
241 abstract = True
242
244 kwargs.setdefault("leaf_name", self.__class__.__name__)
245 kwargs.setdefault("module_name", self.__class__.__module__)
246 super(Extendable, self).__init__(*args, **kwargs)
247
249 '''Return this instance as the farthest descendant of its class'''
250 if self.is_instance_of(self.__class__):
251 return self
252 else:
253 try:
254 mod = import_module(self.module_name)
255 except ImportError:
256 logger.debug("init import error.")
257 drop_to_shell(local=locals())
258 raise
259 klass = getattr(mod, self.leaf_name)
260 return klass.objects.get(pk=self.pk)
261
263 """Is the object an instance of the passed class C{klass}?"""
264
265 return self.leaf_name == klass.__name__ and self.module_name == klass.__module__
266
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Feb 18 13:10:10 2011 | http://epydoc.sourceforge.net |