1 | from django.conf import settings
|
---|
2 | from django.core import formfields, validators
|
---|
3 | from django.core import db
|
---|
4 | from django.core.exceptions import ObjectDoesNotExist
|
---|
5 | from django.core.meta.fields import *
|
---|
6 | from django.utils.functional import curry
|
---|
7 | from django.utils.text import capfirst
|
---|
8 | import copy, datetime, os, re, sys, types
|
---|
9 |
|
---|
10 | # Admin stages.
|
---|
11 | ADD, CHANGE, BOTH = 1, 2, 3
|
---|
12 |
|
---|
13 | # Size of each "chunk" for get_iterator calls.
|
---|
14 | # Larger values are slightly faster at the expense of more storage space.
|
---|
15 | GET_ITERATOR_CHUNK_SIZE = 100
|
---|
16 |
|
---|
17 | # Prefix (in Python path style) to location of models.
|
---|
18 | MODEL_PREFIX = 'django.models'
|
---|
19 |
|
---|
20 | # Methods on models with the following prefix will be removed and
|
---|
21 | # converted to module-level functions.
|
---|
22 | MODEL_FUNCTIONS_PREFIX = '_module_'
|
---|
23 |
|
---|
24 | # Methods on models with the following prefix will be removed and
|
---|
25 | # converted to manipulator methods.
|
---|
26 | MANIPULATOR_FUNCTIONS_PREFIX = '_manipulator_'
|
---|
27 |
|
---|
28 | LOOKUP_SEPARATOR = '__'
|
---|
29 |
|
---|
30 | ####################
|
---|
31 | # HELPER FUNCTIONS #
|
---|
32 | ####################
|
---|
33 |
|
---|
34 | # Django currently supports two forms of ordering.
|
---|
35 | # Form 1 (deprecated) example:
|
---|
36 | # order_by=(('pub_date', 'DESC'), ('headline', 'ASC'), (None, 'RANDOM'))
|
---|
37 | # Form 2 (new-style) example:
|
---|
38 | # order_by=('-pub_date', 'headline', '?')
|
---|
39 | # Form 1 is deprecated and will no longer be supported for Django's first
|
---|
40 | # official release. The following code converts from Form 1 to Form 2.
|
---|
41 |
|
---|
42 | LEGACY_ORDERING_MAPPING = {'ASC': '_', 'DESC': '-_', 'RANDOM': '?'}
|
---|
43 |
|
---|
44 | def handle_legacy_orderlist(order_list):
|
---|
45 | if not order_list or isinstance(order_list[0], basestring):
|
---|
46 | return order_list
|
---|
47 | else:
|
---|
48 | import warnings
|
---|
49 | new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', str(i)) for i, j in order_list]
|
---|
50 | warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning)
|
---|
51 | return new_order_list
|
---|
52 |
|
---|
53 | def orderlist2sql(order_list, prefix=''):
|
---|
54 | output = []
|
---|
55 | for f in handle_legacy_orderlist(order_list):
|
---|
56 | if f.startswith('-'):
|
---|
57 | output.append('%s%s DESC' % (prefix, f[1:]))
|
---|
58 | elif f == '?':
|
---|
59 | output.append('RANDOM()')
|
---|
60 | else:
|
---|
61 | output.append('%s%s ASC' % (prefix, f))
|
---|
62 | return ', '.join(output)
|
---|
63 |
|
---|
64 | def get_module(app_label, module_name):
|
---|
65 | return __import__('%s.%s.%s' % (MODEL_PREFIX, app_label, module_name), '', '', [''])
|
---|
66 |
|
---|
67 | def get_app(app_label):
|
---|
68 | return __import__('%s.%s' % (MODEL_PREFIX, app_label), '', '', [''])
|
---|
69 |
|
---|
70 | _installed_models_cache = None
|
---|
71 | def get_installed_models():
|
---|
72 | """
|
---|
73 | Returns a list of installed "models" packages, such as foo.models,
|
---|
74 | ellington.news.models, etc. This does NOT include django.models.
|
---|
75 | """
|
---|
76 | global _installed_models_cache
|
---|
77 | if _installed_models_cache is not None:
|
---|
78 | return _installed_models_cache
|
---|
79 | _installed_models_cache = []
|
---|
80 | for a in settings.INSTALLED_APPS:
|
---|
81 | try:
|
---|
82 | _installed_models_cache.append(__import__(a + '.models', '', '', ['']))
|
---|
83 | except ImportError:
|
---|
84 | pass
|
---|
85 | return _installed_models_cache
|
---|
86 |
|
---|
87 | _installed_modules_cache = None
|
---|
88 | def get_installed_model_modules(core_models=None):
|
---|
89 | """
|
---|
90 | Returns a list of installed models, such as django.models.core,
|
---|
91 | ellington.news.models.news, foo.models.bar, etc.
|
---|
92 | """
|
---|
93 | global _installed_modules_cache
|
---|
94 | if _installed_modules_cache is not None:
|
---|
95 | return _installed_modules_cache
|
---|
96 | _installed_modules_cache = []
|
---|
97 |
|
---|
98 | # django.models is a special case.
|
---|
99 | for submodule in (core_models or []):
|
---|
100 | _installed_modules_cache.append(__import__('django.models.%s' % submodule, '', '', ['']))
|
---|
101 | for m in get_installed_models():
|
---|
102 | for submodule in getattr(m, '__all__', []):
|
---|
103 | mod = __import__('django.models.%s' % submodule, '', '', [''])
|
---|
104 | try:
|
---|
105 | mod._MODELS
|
---|
106 | except AttributeError:
|
---|
107 | pass # Skip model modules that don't actually have models in them.
|
---|
108 | else:
|
---|
109 | _installed_modules_cache.append(mod)
|
---|
110 | return _installed_modules_cache
|
---|
111 |
|
---|
112 | class LazyDate:
|
---|
113 | """
|
---|
114 | Use in limit_choices_to to compare the field to dates calculated at run time
|
---|
115 | instead of when the model is loaded. For example::
|
---|
116 |
|
---|
117 | ... limit_choices_to = {'date__gt' : meta.LazyDate(days=-3)} ...
|
---|
118 |
|
---|
119 | which will limit the choices to dates greater than three days ago.
|
---|
120 | """
|
---|
121 | def __init__(self, **kwargs):
|
---|
122 | self.delta = datetime.timedelta(**kwargs)
|
---|
123 |
|
---|
124 | def __str__(self):
|
---|
125 | return str(self.__get_value__())
|
---|
126 |
|
---|
127 | def __repr__(self):
|
---|
128 | return "<LazyDate: %s>" % self.delta
|
---|
129 |
|
---|
130 | def __get_value__(self):
|
---|
131 | return datetime.datetime.now() + self.delta
|
---|
132 |
|
---|
133 | ################
|
---|
134 | # MAIN CLASSES #
|
---|
135 | ################
|
---|
136 |
|
---|
137 | class FieldDoesNotExist(Exception):
|
---|
138 | pass
|
---|
139 |
|
---|
140 | class BadKeywordArguments(Exception):
|
---|
141 | pass
|
---|
142 |
|
---|
143 | class Options:
|
---|
144 | def __init__(self, module_name='', verbose_name='', verbose_name_plural='', db_table='',
|
---|
145 | fields=None, ordering=None, unique_together=None, admin=None, has_related_links=False,
|
---|
146 | where_constraints=None, object_name=None, app_label=None,
|
---|
147 | exceptions=None, permissions=None, get_latest_by=None,
|
---|
148 | order_with_respect_to=None, module_constants=None):
|
---|
149 |
|
---|
150 | # Save the original function args, for use by copy(). Note that we're
|
---|
151 | # NOT using copy.deepcopy(), because that would create a new copy of
|
---|
152 | # everything in memory, and it's better to conserve memory. Of course,
|
---|
153 | # this comes with the important gotcha that changing any attribute of
|
---|
154 | # this object will change its value in self._orig_init_args, so we
|
---|
155 | # need to be careful not to do that. In practice, we can pull this off
|
---|
156 | # because Options are generally read-only objects, and __init__() is
|
---|
157 | # the only place where its attributes are manipulated.
|
---|
158 |
|
---|
159 | # locals() is used purely for convenience, so we don't have to do
|
---|
160 | # something verbose like this:
|
---|
161 | # self._orig_init_args = {
|
---|
162 | # 'module_name': module_name,
|
---|
163 | # 'verbose_name': verbose_name,
|
---|
164 | # ...
|
---|
165 | # }
|
---|
166 | self._orig_init_args = locals()
|
---|
167 | del self._orig_init_args['self'] # because we don't care about it.
|
---|
168 |
|
---|
169 | # Move many-to-many related fields from self.fields into self.many_to_many.
|
---|
170 | self.fields, self.many_to_many = [], []
|
---|
171 | for field in (fields or []):
|
---|
172 | if field.rel and isinstance(field.rel, ManyToMany):
|
---|
173 | self.many_to_many.append(field)
|
---|
174 | else:
|
---|
175 | self.fields.append(field)
|
---|
176 | self.module_name, self.verbose_name = module_name, verbose_name
|
---|
177 | self.verbose_name_plural = verbose_name_plural or verbose_name + 's'
|
---|
178 | self.db_table, self.has_related_links = db_table, has_related_links
|
---|
179 | self.ordering = ordering or []
|
---|
180 | self.unique_together = unique_together or []
|
---|
181 | self.where_constraints = where_constraints or []
|
---|
182 | self.exceptions = exceptions or []
|
---|
183 | self.permissions = permissions or []
|
---|
184 | self.object_name, self.app_label = object_name, app_label
|
---|
185 | self.get_latest_by = get_latest_by
|
---|
186 | if order_with_respect_to:
|
---|
187 | self.order_with_respect_to = self.get_field(order_with_respect_to)
|
---|
188 | self.ordering = ('_order',)
|
---|
189 | else:
|
---|
190 | self.order_with_respect_to = None
|
---|
191 | self.module_constants = module_constants or {}
|
---|
192 | self.admin = admin
|
---|
193 |
|
---|
194 | # Calculate one_to_one_field.
|
---|
195 | self.one_to_one_field = None
|
---|
196 | for f in self.fields:
|
---|
197 | if isinstance(f.rel, OneToOne):
|
---|
198 | self.one_to_one_field = f
|
---|
199 | break
|
---|
200 | # Cache the primary-key field.
|
---|
201 | self.pk = None
|
---|
202 | for f in self.fields:
|
---|
203 | if f.primary_key:
|
---|
204 | self.pk = f
|
---|
205 | break
|
---|
206 | # If a primary_key field hasn't been specified, add an
|
---|
207 | # auto-incrementing primary-key ID field automatically.
|
---|
208 | if self.pk is None:
|
---|
209 | self.fields.insert(0, AutoField('id', 'ID', primary_key=True))
|
---|
210 | self.pk = self.fields[0]
|
---|
211 | # Cache whether this has an AutoField.
|
---|
212 | self.has_auto_field = False
|
---|
213 | for f in self.fields:
|
---|
214 | is_auto = isinstance(f, AutoField)
|
---|
215 | if is_auto and self.has_auto_field:
|
---|
216 | raise AssertionError, "A model can't have more than one AutoField."
|
---|
217 | elif is_auto:
|
---|
218 | self.has_auto_field = True
|
---|
219 |
|
---|
220 | def __repr__(self):
|
---|
221 | return '<Options for %s>' % self.module_name
|
---|
222 |
|
---|
223 | def copy(self, **kwargs):
|
---|
224 | args = self._orig_init_args.copy()
|
---|
225 | args.update(kwargs)
|
---|
226 | return self.__class__(**args)
|
---|
227 |
|
---|
228 | def get_model_module(self):
|
---|
229 | return get_module(self.app_label, self.module_name)
|
---|
230 |
|
---|
231 | def get_content_type_id(self):
|
---|
232 | "Returns the content-type ID for this object type."
|
---|
233 | if not hasattr(self, '_content_type_id'):
|
---|
234 | mod = get_module('core', 'contenttypes')
|
---|
235 | self._content_type_id = mod.get_object(python_module_name__exact=self.module_name, package__label__exact=self.app_label).id
|
---|
236 | return self._content_type_id
|
---|
237 |
|
---|
238 | def get_field(self, name, many_to_many=True):
|
---|
239 | """
|
---|
240 | Returns the requested field by name. Raises FieldDoesNotExist on error.
|
---|
241 | """
|
---|
242 | to_search = many_to_many and (self.fields + self.many_to_many) or self.fields
|
---|
243 | for f in to_search:
|
---|
244 | if f.name == name:
|
---|
245 | return f
|
---|
246 | raise FieldDoesNotExist, "name=%s" % name
|
---|
247 |
|
---|
248 | def get_order_sql(self, table_prefix=''):
|
---|
249 | "Returns the full 'ORDER BY' clause for this object, according to self.ordering."
|
---|
250 | if not self.ordering: return ''
|
---|
251 | pre = table_prefix and (table_prefix + '.') or ''
|
---|
252 | return 'ORDER BY ' + orderlist2sql(self.ordering, pre)
|
---|
253 |
|
---|
254 | def get_add_permission(self):
|
---|
255 | return 'add_%s' % self.object_name.lower()
|
---|
256 |
|
---|
257 | def get_change_permission(self):
|
---|
258 | return 'change_%s' % self.object_name.lower()
|
---|
259 |
|
---|
260 | def get_delete_permission(self):
|
---|
261 | return 'delete_%s' % self.object_name.lower()
|
---|
262 |
|
---|
263 | def get_rel_object_method_name(self, rel_opts, rel_field):
|
---|
264 | # This method encapsulates the logic that decides what name to give a
|
---|
265 | # method that retrieves related many-to-one objects. Usually it just
|
---|
266 | # uses the lower-cased object_name, but if the related object is in
|
---|
267 | # another app, its app_label is appended.
|
---|
268 | #
|
---|
269 | # Examples:
|
---|
270 | #
|
---|
271 | # # Normal case -- a related object in the same app.
|
---|
272 | # # This method returns "choice".
|
---|
273 | # Poll.get_choice_list()
|
---|
274 | #
|
---|
275 | # # A related object in a different app.
|
---|
276 | # # This method returns "lcom_bestofaward".
|
---|
277 | # Place.get_lcom_bestofaward_list() # "lcom_bestofaward"
|
---|
278 | rel_obj_name = rel_field.rel.related_name or rel_opts.object_name.lower()
|
---|
279 | if self.app_label != rel_opts.app_label:
|
---|
280 | rel_obj_name = '%s_%s' % (rel_opts.app_label, rel_obj_name)
|
---|
281 | return rel_obj_name
|
---|
282 |
|
---|
283 | def get_all_related_objects(self):
|
---|
284 | try: # Try the cache first.
|
---|
285 | return self._all_related_objects
|
---|
286 | except AttributeError:
|
---|
287 | module_list = get_installed_model_modules()
|
---|
288 | rel_objs = []
|
---|
289 | for mod in module_list:
|
---|
290 | for klass in mod._MODELS:
|
---|
291 | for f in klass._meta.fields:
|
---|
292 | if f.rel and self == f.rel.to:
|
---|
293 | rel_objs.append((klass._meta, f))
|
---|
294 | if self.has_related_links:
|
---|
295 | # Manually add RelatedLink objects, which are a special case.
|
---|
296 | core = get_module('relatedlinks', 'relatedlinks')
|
---|
297 | # Note that the copy() is very important -- otherwise any
|
---|
298 | # subsequently loaded object with related links will override this
|
---|
299 | # relationship we're adding.
|
---|
300 | link_field = copy.copy(core.RelatedLink._meta.get_field('object_id'))
|
---|
301 | link_field.rel = ManyToOne(self.get_model_module().Klass, 'related_links', 'id',
|
---|
302 | num_in_admin=3, min_num_in_admin=3, edit_inline=TABULAR,
|
---|
303 | lookup_overrides={
|
---|
304 | 'content_type__package__label__exact': self.app_label,
|
---|
305 | 'content_type__python_module_name__exact': self.module_name
|
---|
306 | })
|
---|
307 | rel_objs.append((core.RelatedLink._meta, link_field))
|
---|
308 | self._all_related_objects = rel_objs
|
---|
309 | return rel_objs
|
---|
310 |
|
---|
311 | def get_inline_related_objects(self):
|
---|
312 | return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline]
|
---|
313 |
|
---|
314 | def get_all_related_many_to_many_objects(self):
|
---|
315 | module_list = get_installed_model_modules()
|
---|
316 | rel_objs = []
|
---|
317 | for mod in module_list:
|
---|
318 | for klass in mod._MODELS:
|
---|
319 | try:
|
---|
320 | for f in klass._meta.many_to_many:
|
---|
321 | if f.rel and self == f.rel.to:
|
---|
322 | rel_objs.append((klass._meta, f))
|
---|
323 | raise StopIteration
|
---|
324 | except StopIteration:
|
---|
325 | continue
|
---|
326 | return rel_objs
|
---|
327 |
|
---|
328 | def get_ordered_objects(self):
|
---|
329 | "Returns a list of Options objects that are ordered with respect to this object."
|
---|
330 | if not hasattr(self, '_ordered_objects'):
|
---|
331 | objects = []
|
---|
332 | for klass in get_app(self.app_label)._MODELS:
|
---|
333 | opts = klass._meta
|
---|
334 | if opts.order_with_respect_to and opts.order_with_respect_to.rel \
|
---|
335 | and self == opts.order_with_respect_to.rel.to:
|
---|
336 | objects.append(opts)
|
---|
337 | self._ordered_objects = objects
|
---|
338 | return self._ordered_objects
|
---|
339 |
|
---|
340 | def has_field_type(self, field_type):
|
---|
341 | """
|
---|
342 | Returns True if this object's admin form has at least one of the given
|
---|
343 | field_type (e.g. FileField).
|
---|
344 | """
|
---|
345 | if not hasattr(self, '_field_types'):
|
---|
346 | self._field_types = {}
|
---|
347 | if not self._field_types.has_key(field_type):
|
---|
348 | try:
|
---|
349 | # First check self.fields.
|
---|
350 | for f in self.fields:
|
---|
351 | if isinstance(f, field_type):
|
---|
352 | raise StopIteration
|
---|
353 | # Failing that, check related fields.
|
---|
354 | for rel_obj, rel_field in self.get_inline_related_objects():
|
---|
355 | for f in rel_obj.fields:
|
---|
356 | if isinstance(f, field_type):
|
---|
357 | raise StopIteration
|
---|
358 | except StopIteration:
|
---|
359 | self._field_types[field_type] = True
|
---|
360 | else:
|
---|
361 | self._field_types[field_type] = False
|
---|
362 | return self._field_types[field_type]
|
---|
363 |
|
---|
364 | def _reassign_globals(function_dict, extra_globals, namespace):
|
---|
365 | new_functions = {}
|
---|
366 | for k, v in function_dict.items():
|
---|
367 | # Get the code object.
|
---|
368 | code = v.func_code
|
---|
369 | # Recreate the function, but give it access to extra_globals and the
|
---|
370 | # given namespace's globals, too.
|
---|
371 | new_globals = {'__builtins__': __builtins__, 'db': db.db, 'datetime': datetime}
|
---|
372 | new_globals.update(extra_globals.__dict__)
|
---|
373 | func = types.FunctionType(code, globals=new_globals, name=k, argdefs=v.func_defaults)
|
---|
374 | func.__dict__.update(v.__dict__)
|
---|
375 | setattr(namespace, k, func)
|
---|
376 | # For all of the custom functions that have been added so far, give
|
---|
377 | # them access to the new function we've just created.
|
---|
378 | for new_k, new_v in new_functions.items():
|
---|
379 | new_v.func_globals[k] = func
|
---|
380 | new_functions[k] = func
|
---|
381 |
|
---|
382 | class ModelBase(type):
|
---|
383 | "Metaclass for all models"
|
---|
384 | def __new__(cls, name, bases, attrs):
|
---|
385 | # If this isn't a subclass of Model, don't do anything special.
|
---|
386 | if not bases:
|
---|
387 | return type.__new__(cls, name, bases, attrs)
|
---|
388 |
|
---|
389 | # If this model is a subclass of another Model, create an Options
|
---|
390 | # object by first copying the base class's _meta and then updating it
|
---|
391 | # with the overrides from this class.
|
---|
392 | replaces_module = None
|
---|
393 | if bases[0] != Model:
|
---|
394 | if not attrs.has_key('fields'):
|
---|
395 | attrs['fields'] = list(bases[0]._meta._orig_init_args['fields'][:])
|
---|
396 | if attrs.has_key('ignore_fields'):
|
---|
397 | ignore_fields = attrs.pop('ignore_fields')
|
---|
398 | new_fields = []
|
---|
399 | for i, f in enumerate(attrs['fields']):
|
---|
400 | if f.name not in ignore_fields:
|
---|
401 | new_fields.append(f)
|
---|
402 | attrs['fields'] = new_fields
|
---|
403 | if attrs.has_key('add_fields'):
|
---|
404 | attrs['fields'].extend(attrs.pop('add_fields'))
|
---|
405 | if attrs.has_key('replaces_module'):
|
---|
406 | # Set the replaces_module variable for now. We can't actually
|
---|
407 | # do anything with it yet, because the module hasn't yet been
|
---|
408 | # created.
|
---|
409 | replaces_module = attrs.pop('replaces_module').split('.')
|
---|
410 | # Pass any Options overrides to the base's Options instance, and
|
---|
411 | # simultaneously remove them from attrs. When this is done, attrs
|
---|
412 | # will be a dictionary of custom methods, plus __module__.
|
---|
413 | meta_overrides = {}
|
---|
414 | for k, v in attrs.items():
|
---|
415 | if not callable(v) and k != '__module__':
|
---|
416 | meta_overrides[k] = attrs.pop(k)
|
---|
417 | opts = bases[0]._meta.copy(**meta_overrides)
|
---|
418 | opts.object_name = name
|
---|
419 | del meta_overrides
|
---|
420 | else:
|
---|
421 | opts = Options(
|
---|
422 | # If the module_name wasn't given, use the class name
|
---|
423 | # in lowercase, plus a trailing "s" -- a poor-man's
|
---|
424 | # pluralization.
|
---|
425 | module_name = attrs.pop('module_name', name.lower() + 's'),
|
---|
426 | # If the verbose_name wasn't given, use the class name,
|
---|
427 | # converted from InitialCaps to "lowercase with spaces".
|
---|
428 | verbose_name = attrs.pop('verbose_name',
|
---|
429 | re.sub('([A-Z])', ' \\1', name).lower().strip()),
|
---|
430 | verbose_name_plural = attrs.pop('verbose_name_plural', ''),
|
---|
431 | db_table = attrs.pop('db_table', ''),
|
---|
432 | fields = attrs.pop('fields'),
|
---|
433 | ordering = attrs.pop('ordering', None),
|
---|
434 | unique_together = attrs.pop('unique_together', None),
|
---|
435 | admin = attrs.pop('admin', None),
|
---|
436 | has_related_links = attrs.pop('has_related_links', False),
|
---|
437 | where_constraints = attrs.pop('where_constraints', None),
|
---|
438 | object_name = name,
|
---|
439 | app_label = attrs.pop('app_label', None),
|
---|
440 | exceptions = attrs.pop('exceptions', None),
|
---|
441 | permissions = attrs.pop('permissions', None),
|
---|
442 | get_latest_by = attrs.pop('get_latest_by', None),
|
---|
443 | order_with_respect_to = attrs.pop('order_with_respect_to', None),
|
---|
444 | module_constants = attrs.pop('module_constants', None),
|
---|
445 | )
|
---|
446 |
|
---|
447 | # Dynamically create the module that will contain this class and its
|
---|
448 | # associated helper functions.
|
---|
449 | if replaces_module is not None:
|
---|
450 | new_mod = get_module(*replaces_module)
|
---|
451 | else:
|
---|
452 | new_mod = types.ModuleType(opts.module_name)
|
---|
453 |
|
---|
454 | # Collect any/all custom class methods and module functions, and move
|
---|
455 | # them to a temporary holding variable. We'll deal with them later.
|
---|
456 | if replaces_module is not None:
|
---|
457 | # Initialize these values to the base class' custom_methods and
|
---|
458 | # custom_functions.
|
---|
459 | custom_methods = dict([(k, v) for k, v in new_mod.Klass.__dict__.items() if hasattr(v, 'custom')])
|
---|
460 | custom_functions = dict([(k, v) for k, v in new_mod.__dict__.items() if hasattr(v, 'custom')])
|
---|
461 | else:
|
---|
462 | custom_methods, custom_functions = {}, {}
|
---|
463 | manipulator_methods = {}
|
---|
464 | for k, v in attrs.items():
|
---|
465 | if k in ('__module__', '__init__', '_overrides', '__doc__'):
|
---|
466 | continue # Skip the important stuff.
|
---|
467 | # Give the function a function attribute "custom" to designate that
|
---|
468 | # it's a custom function/method.
|
---|
469 | v.custom = True
|
---|
470 | if k.startswith(MODEL_FUNCTIONS_PREFIX):
|
---|
471 | custom_functions[k[len(MODEL_FUNCTIONS_PREFIX):]] = v
|
---|
472 | elif k.startswith(MANIPULATOR_FUNCTIONS_PREFIX):
|
---|
473 | manipulator_methods[k[len(MANIPULATOR_FUNCTIONS_PREFIX):]] = v
|
---|
474 | else:
|
---|
475 | custom_methods[k] = v
|
---|
476 | del attrs[k]
|
---|
477 |
|
---|
478 | # Create the module-level ObjectDoesNotExist exception.
|
---|
479 | dne_exc_name = '%sDoesNotExist' % name
|
---|
480 | does_not_exist_exception = types.ClassType(dne_exc_name, (ObjectDoesNotExist,), {})
|
---|
481 | # Explicitly set its __module__ because it will initially (incorrectly)
|
---|
482 | # be set to the module the code is being executed in.
|
---|
483 | does_not_exist_exception.__module__ = MODEL_PREFIX + '.' + opts.module_name
|
---|
484 | setattr(new_mod, dne_exc_name, does_not_exist_exception)
|
---|
485 |
|
---|
486 | # Create other exceptions.
|
---|
487 | for exception_name in opts.exceptions:
|
---|
488 | exc = types.ClassType(exception_name, (Exception,), {})
|
---|
489 | exc.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
|
---|
490 | setattr(new_mod, exception_name, exc)
|
---|
491 |
|
---|
492 | # Create any module-level constants, if applicable.
|
---|
493 | for k, v in opts.module_constants.items():
|
---|
494 | setattr(new_mod, k, v)
|
---|
495 |
|
---|
496 | # Create the default class methods.
|
---|
497 | attrs['__init__'] = curry(method_init, opts)
|
---|
498 | attrs['__eq__'] = curry(method_eq, opts)
|
---|
499 | attrs['save'] = curry(method_save, opts)
|
---|
500 | attrs['save'].alters_data = True
|
---|
501 | attrs['delete'] = curry(method_delete, opts)
|
---|
502 | attrs['delete'].alters_data = True
|
---|
503 |
|
---|
504 | if opts.order_with_respect_to:
|
---|
505 | attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
|
---|
506 | attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
|
---|
507 |
|
---|
508 | for f in opts.fields:
|
---|
509 | # If the object has a relationship to itself, as designated by
|
---|
510 | # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
|
---|
511 | if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
|
---|
512 | f.rel.to = opts
|
---|
513 | f.name = f.name or ((f.rel.name or f.rel.to.object_name.lower()) + '_' + f.rel.to.pk.name)
|
---|
514 | f.verbose_name = f.verbose_name or f.rel.to.verbose_name
|
---|
515 | f.rel.field_name = f.rel.field_name or f.rel.to.pk.name
|
---|
516 | # Add "get_thingie" methods for many-to-one related objects.
|
---|
517 | # EXAMPLES: Choice.get_poll(), Story.get_dateline()
|
---|
518 | if isinstance(f.rel, ManyToOne):
|
---|
519 | func = curry(method_get_many_to_one, f)
|
---|
520 | func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name)
|
---|
521 | attrs['get_%s' % f.rel.name] = func
|
---|
522 |
|
---|
523 | for f in opts.many_to_many:
|
---|
524 | # Add "get_thingie" methods for many-to-many related objects.
|
---|
525 | # EXAMPLES: Poll.get_site_list(), Story.get_byline_list()
|
---|
526 | func = curry(method_get_many_to_many, f)
|
---|
527 | func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name)
|
---|
528 | attrs['get_%s_list' % f.rel.name] = func
|
---|
529 | # Add "set_thingie" methods for many-to-many related objects.
|
---|
530 | # EXAMPLES: Poll.set_sites(), Story.set_bylines()
|
---|
531 | func = curry(method_set_many_to_many, f)
|
---|
532 | func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs. Note that it doesn't check whether the given IDs are valid." % (f.rel.to.app_label, f.rel.to.module_name)
|
---|
533 | func.alters_data = True
|
---|
534 | attrs['set_%s' % f.name] = func
|
---|
535 |
|
---|
536 | # Create the class, because we need it to use in currying.
|
---|
537 | new_class = type.__new__(cls, name, bases, attrs)
|
---|
538 |
|
---|
539 | # Give the class a docstring -- its definition.
|
---|
540 | new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
|
---|
541 |
|
---|
542 | # Create the standard, module-level API helper functions such
|
---|
543 | # as get_object() and get_list().
|
---|
544 | new_mod.get_object = curry(function_get_object, opts, new_class, does_not_exist_exception)
|
---|
545 | new_mod.get_object.__doc__ = "Returns the %s object matching the given parameters." % name
|
---|
546 |
|
---|
547 | new_mod.get_list = curry(function_get_list, opts, new_class)
|
---|
548 | new_mod.get_list.__doc__ = "Returns a list of %s objects matching the given parameters." % name
|
---|
549 |
|
---|
550 | new_mod.get_iterator = curry(function_get_iterator, opts, new_class)
|
---|
551 | new_mod.get_iterator.__doc__ = "Returns an iterator of %s objects matching the given parameters." % name
|
---|
552 |
|
---|
553 | new_mod.get_values = curry(function_get_values, opts, new_class)
|
---|
554 | new_mod.get_values.__doc__ = "Returns a list of dictionaries matching the given parameters."
|
---|
555 |
|
---|
556 | new_mod.get_values_iterator = curry(function_get_values_iterator, opts, new_class)
|
---|
557 | new_mod.get_values_iterator.__doc__ = "Returns an iterator of dictionaries matching the given parameters."
|
---|
558 |
|
---|
559 | new_mod.get_count = curry(function_get_count, opts)
|
---|
560 | new_mod.get_count.__doc__ = "Returns the number of %s objects matching the given parameters." % name
|
---|
561 |
|
---|
562 | new_mod._get_sql_clause = curry(function_get_sql_clause, opts)
|
---|
563 |
|
---|
564 | new_mod.get_in_bulk = curry(function_get_in_bulk, opts, new_class)
|
---|
565 | new_mod.get_in_bulk.__doc__ = "Returns a dictionary of ID -> %s for the %s objects with IDs in the given id_list." % (name, name)
|
---|
566 |
|
---|
567 | if opts.get_latest_by:
|
---|
568 | new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
|
---|
569 |
|
---|
570 | for f in opts.fields:
|
---|
571 | if isinstance(f, DateField) or isinstance(f, DateTimeField):
|
---|
572 | # Add "get_next_by_thingie" and "get_previous_by_thingie" methods
|
---|
573 | # for all DateFields and DateTimeFields that cannot be null.
|
---|
574 | # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
|
---|
575 | if not f.null:
|
---|
576 | setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, f, True))
|
---|
577 | setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, f, False))
|
---|
578 | # Add "get_thingie_list" for all DateFields and DateTimeFields.
|
---|
579 | # EXAMPLE: polls.get_pub_date_list()
|
---|
580 | func = curry(function_get_date_list, opts, f)
|
---|
581 | func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects) in which %s objects are available. The first parameter ('kind') must be one of 'year', 'month' or 'day'." % name
|
---|
582 | setattr(new_mod, 'get_%s_list' % f.name, func)
|
---|
583 |
|
---|
584 | elif isinstance(f, FileField):
|
---|
585 | setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f))
|
---|
586 | setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f))
|
---|
587 | setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f))
|
---|
588 | func = curry(method_save_file, f)
|
---|
589 | func.alters_data = True
|
---|
590 | setattr(new_class, 'save_%s_file' % f.name, func)
|
---|
591 | if isinstance(f, ImageField):
|
---|
592 | # Add get_BLAH_width and get_BLAH_height methods, but only
|
---|
593 | # if the image field doesn't have width and height cache
|
---|
594 | # fields.
|
---|
595 | if not f.width_field:
|
---|
596 | setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
|
---|
597 | if not f.height_field:
|
---|
598 | setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
|
---|
599 |
|
---|
600 | # Add the class itself to the new module we've created.
|
---|
601 | new_mod.__dict__[name] = new_class
|
---|
602 |
|
---|
603 | # Add "Klass" -- a shortcut reference to the class.
|
---|
604 | new_mod.__dict__['Klass'] = new_class
|
---|
605 |
|
---|
606 | # Add the Manipulators.
|
---|
607 | new_mod.__dict__['AddManipulator'] = get_manipulator(opts, new_class, manipulator_methods, add=True)
|
---|
608 | new_mod.__dict__['ChangeManipulator'] = get_manipulator(opts, new_class, manipulator_methods, change=True)
|
---|
609 |
|
---|
610 | # Now that we have references to new_mod and new_class, we can add
|
---|
611 | # any/all extra class methods to the new class. Note that we could
|
---|
612 | # have just left the extra methods in attrs (above), but that would
|
---|
613 | # have meant that any code within the extra methods would *not* have
|
---|
614 | # access to module-level globals, such as get_list(), db, etc.
|
---|
615 | # In order to give these methods access to those globals, we have to
|
---|
616 | # deconstruct the method getting its raw "code" object, then recreating
|
---|
617 | # the function with a new "globals" dictionary.
|
---|
618 | #
|
---|
619 | # To complicate matters more, because each method is manually assigned
|
---|
620 | # a "globals" value, that "globals" value does NOT include the methods
|
---|
621 | # that haven't been created yet. For instance, if there are two custom
|
---|
622 | # methods, foo() and bar(), and foo() is created first, it won't have
|
---|
623 | # bar() within its globals(). This is a problem because sometimes
|
---|
624 | # custom methods/functions refer to other custom methods/functions. To
|
---|
625 | # solve this problem, we keep track of the new functions created (in
|
---|
626 | # the new_functions variable) and manually append each new function to
|
---|
627 | # the func_globals() of all previously-created functions. So, by the
|
---|
628 | # end of the loop, all functions will "know" about all the other
|
---|
629 | # functions.
|
---|
630 | _reassign_globals(custom_methods, new_mod, new_class)
|
---|
631 | _reassign_globals(custom_functions, new_mod, new_mod)
|
---|
632 | _reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['AddManipulator'])
|
---|
633 | _reassign_globals(manipulator_methods, new_mod, new_mod.__dict__['ChangeManipulator'])
|
---|
634 |
|
---|
635 | if hasattr(new_class, 'get_absolute_url'):
|
---|
636 | new_class.get_absolute_url = curry(get_absolute_url, opts, new_class.get_absolute_url)
|
---|
637 |
|
---|
638 | # Get a reference to the module the class is in, and dynamically add
|
---|
639 | # the new module to it.
|
---|
640 | app_package = sys.modules.get(new_class.__module__)
|
---|
641 | if replaces_module is not None:
|
---|
642 | app_label = replaces_module[0]
|
---|
643 | else:
|
---|
644 | app_package.__dict__[opts.module_name] = new_mod
|
---|
645 | app_label = app_package.__name__[app_package.__name__.rfind('.')+1:]
|
---|
646 |
|
---|
647 | # Populate the _MODELS member on the module the class is in.
|
---|
648 | # Example: django.models.polls will have a _MODELS member that will
|
---|
649 | # contain this list:
|
---|
650 | # [<class 'django.models.polls.Poll'>, <class 'django.models.polls.Choice'>]
|
---|
651 | # Don't do this if replaces_module is set.
|
---|
652 | app_package.__dict__.setdefault('_MODELS', []).append(new_class)
|
---|
653 |
|
---|
654 | # Cache the app label.
|
---|
655 | opts.app_label = app_label
|
---|
656 |
|
---|
657 | # If the db_table wasn't provided, use the app_label + module_name.
|
---|
658 | if not opts.db_table:
|
---|
659 | opts.db_table = "%s_%s" % (app_label, opts.module_name)
|
---|
660 | new_class._meta = opts
|
---|
661 |
|
---|
662 | # Set the __file__ attribute to the __file__ attribute of its package,
|
---|
663 | # because they're technically from the same file. Note: if we didn't
|
---|
664 | # set this, sys.modules would think this module was built-in.
|
---|
665 | try:
|
---|
666 | new_mod.__file__ = app_package.__file__
|
---|
667 | except AttributeError:
|
---|
668 | # 'module' object has no attribute '__file__', which means the
|
---|
669 | # class was probably being entered via the interactive interpreter.
|
---|
670 | pass
|
---|
671 |
|
---|
672 | # Add the module's entry to sys.modules -- for instance,
|
---|
673 | # "django.models.polls.polls". Note that "django.models.polls" has already
|
---|
674 | # been added automatically.
|
---|
675 | sys.modules.setdefault('%s.%s.%s' % (MODEL_PREFIX, app_label, opts.module_name), new_mod)
|
---|
676 |
|
---|
677 | # If this module replaces another one, get a reference to the other
|
---|
678 | # module's parent, and replace the other module with the one we've just
|
---|
679 | # created.
|
---|
680 | if replaces_module is not None:
|
---|
681 | old_app = get_app(replaces_module[0])
|
---|
682 | setattr(old_app, replaces_module[1], new_mod)
|
---|
683 | for i, model in enumerate(old_app._MODELS):
|
---|
684 | if model._meta.module_name == replaces_module[1]:
|
---|
685 | # Replace the appropriate member of the old app's _MODELS
|
---|
686 | # data structure.
|
---|
687 | old_app._MODELS[i] = new_class
|
---|
688 | # Replace all relationships to the old class with
|
---|
689 | # relationships to the new one.
|
---|
690 | for rel_opts, rel_field in model._meta.get_all_related_objects():
|
---|
691 | rel_field.rel.to = opts
|
---|
692 | for rel_opts, rel_field in model._meta.get_all_related_many_to_many_objects():
|
---|
693 | rel_field.rel.to = opts
|
---|
694 | break
|
---|
695 |
|
---|
696 | return new_class
|
---|
697 |
|
---|
698 | class Model:
|
---|
699 | __metaclass__ = ModelBase
|
---|
700 |
|
---|
701 | def __repr__(self):
|
---|
702 | return '<%s object>' % self.__class__.__name__
|
---|
703 |
|
---|
704 | ############################################
|
---|
705 | # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
|
---|
706 | ############################################
|
---|
707 |
|
---|
708 | # CORE METHODS #############################
|
---|
709 |
|
---|
710 | def method_init(opts, self, *args, **kwargs):
|
---|
711 | if kwargs:
|
---|
712 | for f in opts.fields:
|
---|
713 | setattr(self, f.name, kwargs.pop(f.name, f.get_default()))
|
---|
714 | if kwargs:
|
---|
715 | raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
|
---|
716 | for i, arg in enumerate(args):
|
---|
717 | setattr(self, opts.fields[i].name, arg)
|
---|
718 |
|
---|
719 | def method_eq(opts, self, other):
|
---|
720 | return isinstance(other, self.__class__) and getattr(self, opts.pk.name) == getattr(other, opts.pk.name)
|
---|
721 |
|
---|
722 | def method_save(opts, self):
|
---|
723 | # Run any pre-save hooks.
|
---|
724 | if hasattr(self, '_pre_save'):
|
---|
725 | self._pre_save()
|
---|
726 | non_pks = [f for f in opts.fields if not f.primary_key]
|
---|
727 | cursor = db.db.cursor()
|
---|
728 |
|
---|
729 | # First, try an UPDATE. If that doesn't update anything, do an INSERT.
|
---|
730 | pk_set = bool(getattr(self, opts.pk.name))
|
---|
731 | if pk_set:
|
---|
732 | db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), False)) for f in non_pks]
|
---|
733 | cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
|
---|
734 | ','.join(['%s=%%s' % f.name for f in non_pks]), opts.pk.name),
|
---|
735 | db_values + [getattr(self, opts.pk.name)])
|
---|
736 | if not pk_set or cursor.rowcount == 0:
|
---|
737 | field_names = [f.name for f in opts.fields if not isinstance(f, AutoField)]
|
---|
738 | placeholders = ['%s'] * len(field_names)
|
---|
739 | db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.name), True)) for f in opts.fields if not isinstance(f, AutoField)]
|
---|
740 | if opts.order_with_respect_to:
|
---|
741 | field_names.append('_order')
|
---|
742 | # TODO: This assumes the database supports subqueries.
|
---|
743 | placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
|
---|
744 | (opts.db_table, opts.order_with_respect_to.name))
|
---|
745 | db_values.append(getattr(self, opts.order_with_respect_to.name))
|
---|
746 | cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table,
|
---|
747 | ','.join(field_names), ','.join(placeholders)), db_values)
|
---|
748 | if opts.has_auto_field:
|
---|
749 | setattr(self, opts.pk.name, db.get_last_insert_id(cursor, opts.db_table, opts.pk.name))
|
---|
750 | db.db.commit()
|
---|
751 | # Run any post-save hooks.
|
---|
752 | if hasattr(self, '_post_save'):
|
---|
753 | self._post_save()
|
---|
754 |
|
---|
755 | def method_delete(opts, self):
|
---|
756 | assert getattr(self, opts.pk.name) is not None, "%r can't be deleted because it doesn't have an ID."
|
---|
757 | # Run any pre-delete hooks.
|
---|
758 | if hasattr(self, '_pre_delete'):
|
---|
759 | self._pre_delete()
|
---|
760 | cursor = db.db.cursor()
|
---|
761 | for rel_opts, rel_field in opts.get_all_related_objects():
|
---|
762 | rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
|
---|
763 | if isinstance(rel_field.rel, OneToOne):
|
---|
764 | try:
|
---|
765 | sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
|
---|
766 | except ObjectDoesNotExist:
|
---|
767 | pass
|
---|
768 | else:
|
---|
769 | sub_obj.delete()
|
---|
770 | else:
|
---|
771 | for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
|
---|
772 | sub_obj.delete()
|
---|
773 | for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
|
---|
774 | cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts),
|
---|
775 | self._meta.object_name.lower()), [getattr(self, opts.pk.name)])
|
---|
776 | for f in opts.many_to_many:
|
---|
777 | cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()),
|
---|
778 | [getattr(self, opts.pk.name)])
|
---|
779 | cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.name), [getattr(self, opts.pk.name)])
|
---|
780 | db.db.commit()
|
---|
781 | setattr(self, opts.pk.name, None)
|
---|
782 | for f in opts.fields:
|
---|
783 | if isinstance(f, FileField) and getattr(self, f.name):
|
---|
784 | file_name = getattr(self, 'get_%s_filename' % f.name)()
|
---|
785 | # If the file exists and no other object of this type references it,
|
---|
786 | # delete it from the filesystem.
|
---|
787 | if os.path.exists(file_name) and not opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
|
---|
788 | os.remove(file_name)
|
---|
789 | # Run any post-delete hooks.
|
---|
790 | if hasattr(self, '_post_delete'):
|
---|
791 | self._post_delete()
|
---|
792 |
|
---|
793 | def method_get_next_in_order(opts, order_field, self):
|
---|
794 | if not hasattr(self, '_next_in_order_cache'):
|
---|
795 | self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
|
---|
796 | where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name),
|
---|
797 | '%s=%%s' % order_field.name], limit=1,
|
---|
798 | params=[getattr(self, opts.pk.name), getattr(self, order_field.name)])
|
---|
799 | return self._next_in_order_cache
|
---|
800 |
|
---|
801 | def method_get_previous_in_order(opts, order_field, self):
|
---|
802 | if not hasattr(self, '_previous_in_order_cache'):
|
---|
803 | self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
|
---|
804 | where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.name),
|
---|
805 | '%s=%%s' % order_field.name], limit=1,
|
---|
806 | params=[getattr(self, opts.pk.name), getattr(self, order_field.name)])
|
---|
807 | return self._previous_in_order_cache
|
---|
808 |
|
---|
809 | # RELATIONSHIP METHODS #####################
|
---|
810 |
|
---|
811 | # Example: Story.get_dateline()
|
---|
812 | def method_get_many_to_one(field_with_rel, self):
|
---|
813 | cache_var = field_with_rel.rel.get_cache_name()
|
---|
814 | if not hasattr(self, cache_var):
|
---|
815 | val = getattr(self, field_with_rel.name)
|
---|
816 | mod = field_with_rel.rel.to.get_model_module()
|
---|
817 | if val is None:
|
---|
818 | raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name)
|
---|
819 | retrieved_obj = mod.get_object(**{'%s__exact' % field_with_rel.rel.field_name: val})
|
---|
820 | setattr(self, cache_var, retrieved_obj)
|
---|
821 | return getattr(self, cache_var)
|
---|
822 |
|
---|
823 | # Handles getting many-to-many related objects.
|
---|
824 | # Example: Poll.get_site_list()
|
---|
825 | def method_get_many_to_many(field_with_rel, self):
|
---|
826 | rel = field_with_rel.rel.to
|
---|
827 | cache_var = '_%s_cache' % field_with_rel.name
|
---|
828 | if not hasattr(self, cache_var):
|
---|
829 | mod = rel.get_model_module()
|
---|
830 | sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \
|
---|
831 | (','.join(['a.%s' % f.name for f in rel.fields]), rel.db_table,
|
---|
832 | field_with_rel.get_m2m_db_table(self._meta), rel.pk.name,
|
---|
833 | rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a'))
|
---|
834 | cursor = db.db.cursor()
|
---|
835 | cursor.execute(sql, [getattr(self, self._meta.pk.name)])
|
---|
836 | setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
|
---|
837 | return getattr(self, cache_var)
|
---|
838 |
|
---|
839 | # Handles setting many-to-many relationships.
|
---|
840 | # Example: Poll.set_sites()
|
---|
841 | def method_set_many_to_many(rel_field, self, id_list):
|
---|
842 | id_list = map(int, id_list) # normalize to integers
|
---|
843 | current_ids = [obj.id for obj in method_get_many_to_many(rel_field, self)]
|
---|
844 | ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []
|
---|
845 | for current_id in current_ids:
|
---|
846 | if current_id in id_list:
|
---|
847 | del ids_to_add[current_id]
|
---|
848 | else:
|
---|
849 | ids_to_delete.append(current_id)
|
---|
850 | ids_to_add = ids_to_add.keys()
|
---|
851 | # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.
|
---|
852 | if not ids_to_delete and not ids_to_add:
|
---|
853 | return False # No change
|
---|
854 | rel = rel_field.rel.to
|
---|
855 | m2m_table = rel_field.get_m2m_db_table(self._meta)
|
---|
856 | cursor = db.db.cursor()
|
---|
857 | this_id = getattr(self, self._meta.pk.name)
|
---|
858 | if ids_to_delete:
|
---|
859 | sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete)))
|
---|
860 | cursor.execute(sql, [this_id])
|
---|
861 | if ids_to_add:
|
---|
862 | sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower())
|
---|
863 | cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
|
---|
864 | db.db.commit()
|
---|
865 | try:
|
---|
866 | delattr(self, '_%s_cache' % rel_field.name) # clear cache, if it exists
|
---|
867 | except AttributeError:
|
---|
868 | pass
|
---|
869 | return True
|
---|
870 |
|
---|
871 | # Handles related-object retrieval.
|
---|
872 | # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()
|
---|
873 | def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):
|
---|
874 | kwargs['%s__exact' % rel_field.name] = getattr(self, rel_field.rel.field_name)
|
---|
875 | kwargs.update(rel_field.rel.lookup_overrides)
|
---|
876 | return getattr(rel_mod, method_name)(**kwargs)
|
---|
877 |
|
---|
878 | # Handles adding related objects.
|
---|
879 | # Example: Poll.add_choice()
|
---|
880 | def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):
|
---|
881 | init_kwargs = dict(zip([f.name for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args))
|
---|
882 | init_kwargs.update(kwargs)
|
---|
883 | for f in rel_obj.fields:
|
---|
884 | if isinstance(f, AutoField):
|
---|
885 | init_kwargs[f.name] = None
|
---|
886 | init_kwargs[rel_field.name] = getattr(self, rel_field.rel.field_name)
|
---|
887 | obj = rel_mod.Klass(**init_kwargs)
|
---|
888 | obj.save()
|
---|
889 | return obj
|
---|
890 |
|
---|
891 | # Handles related many-to-many object retrieval.
|
---|
892 | # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()
|
---|
893 | def method_get_related_many_to_many(method_name, rel_mod, rel_field, self, **kwargs):
|
---|
894 | kwargs['%s__id__exact' % rel_field.name] = self.id
|
---|
895 | return getattr(rel_mod, method_name)(**kwargs)
|
---|
896 |
|
---|
897 | # Handles setting many-to-many related objects.
|
---|
898 | # Example: Album.set_songs()
|
---|
899 | def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
|
---|
900 | id_list = map(int, id_list) # normalize to integers
|
---|
901 | rel = rel_field.rel.to
|
---|
902 | m2m_table = rel_field.get_m2m_db_table(rel_opts)
|
---|
903 | this_id = getattr(self, self._meta.pk.name)
|
---|
904 | cursor = db.db.cursor()
|
---|
905 | cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id])
|
---|
906 | sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower())
|
---|
907 | cursor.executemany(sql, [(this_id, i) for i in id_list])
|
---|
908 | db.db.commit()
|
---|
909 |
|
---|
910 | # ORDERING METHODS #########################
|
---|
911 |
|
---|
912 | def method_set_order(ordered_obj, self, id_list):
|
---|
913 | cursor = db.db.cursor()
|
---|
914 | # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
|
---|
915 | sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.name, ordered_obj.pk.name)
|
---|
916 | rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
|
---|
917 | cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
|
---|
918 | db.db.commit()
|
---|
919 |
|
---|
920 | def method_get_order(ordered_obj, self):
|
---|
921 | cursor = db.db.cursor()
|
---|
922 | # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
|
---|
923 | sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.name, ordered_obj.db_table, ordered_obj.order_with_respect_to.name)
|
---|
924 | rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
|
---|
925 | cursor.execute(sql, [rel_val])
|
---|
926 | return [r[0] for r in cursor.fetchall()]
|
---|
927 |
|
---|
928 | # DATE-RELATED METHODS #####################
|
---|
929 |
|
---|
930 | def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs):
|
---|
931 | kwargs.setdefault('where', []).append('%s %s %%s' % (field.name, (is_next and '>' or '<')))
|
---|
932 | kwargs.setdefault('params', []).append(str(getattr(self, field.name)))
|
---|
933 | kwargs['order_by'] = [(not is_next and '-' or '') + field.name]
|
---|
934 | kwargs['limit'] = 1
|
---|
935 | return get_object_func(**kwargs)
|
---|
936 |
|
---|
937 | # FILE-RELATED METHODS #####################
|
---|
938 |
|
---|
939 | def method_get_file_filename(field, self):
|
---|
940 | return os.path.join(settings.MEDIA_ROOT, getattr(self, field.name))
|
---|
941 |
|
---|
942 | def method_get_file_url(field, self):
|
---|
943 | if getattr(self, field.name): # value is not blank
|
---|
944 | import urlparse
|
---|
945 | return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.name))
|
---|
946 | return ''
|
---|
947 |
|
---|
948 | def method_get_file_size(field, self):
|
---|
949 | return os.path.getsize(method_get_file_filename(field, self))
|
---|
950 |
|
---|
951 | def method_save_file(field, self, filename, raw_contents):
|
---|
952 | directory = field.get_directory_name()
|
---|
953 | try: # Create the date-based directory if it doesn't exist.
|
---|
954 | os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
|
---|
955 | except OSError: # Directory probably already exists.
|
---|
956 | pass
|
---|
957 | filename = field.get_filename(filename)
|
---|
958 |
|
---|
959 | # If the filename already exists, keep adding an underscore to the name of
|
---|
960 | # the file until the filename doesn't exist.
|
---|
961 | while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
|
---|
962 | try:
|
---|
963 | dot_index = filename.rindex('.')
|
---|
964 | except ValueError: # filename has no dot
|
---|
965 | filename += '_'
|
---|
966 | else:
|
---|
967 | filename = filename[:dot_index] + '_' + filename[dot_index:]
|
---|
968 |
|
---|
969 | # Write the file to disk.
|
---|
970 | setattr(self, field.name, filename)
|
---|
971 | fp = open(getattr(self, 'get_%s_filename' % field.name)(), 'wb')
|
---|
972 | fp.write(raw_contents)
|
---|
973 | fp.close()
|
---|
974 |
|
---|
975 | # Save the width and/or height, if applicable.
|
---|
976 | if isinstance(field, ImageField) and (field.width_field or field.height_field):
|
---|
977 | from django.utils.images import get_image_dimensions
|
---|
978 | width, height = get_image_dimensions(getattr(self, 'get_%s_filename' % field.name)())
|
---|
979 | if field.width_field:
|
---|
980 | setattr(self, field.width_field, width)
|
---|
981 | if field.height_field:
|
---|
982 | setattr(self, field.height_field, height)
|
---|
983 |
|
---|
984 | # Save the object, because it has changed.
|
---|
985 | self.save()
|
---|
986 |
|
---|
987 | # IMAGE FIELD METHODS ######################
|
---|
988 |
|
---|
989 | def method_get_image_width(field, self):
|
---|
990 | return _get_image_dimensions(field, self)[0]
|
---|
991 |
|
---|
992 | def method_get_image_height(field, self):
|
---|
993 | return _get_image_dimensions(field, self)[1]
|
---|
994 |
|
---|
995 | def _get_image_dimensions(field, self):
|
---|
996 | cachename = "__%s_dimensions_cache" % field.name
|
---|
997 | if not hasattr(self, cachename):
|
---|
998 | from django.utils.images import get_image_dimensions
|
---|
999 | fname = getattr(self, "get_%s_filename" % field.name)()
|
---|
1000 | setattr(self, cachename, get_image_dimensions(fname))
|
---|
1001 | return getattr(self, cachename)
|
---|
1002 |
|
---|
1003 | ##############################################
|
---|
1004 | # HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
|
---|
1005 | ##############################################
|
---|
1006 |
|
---|
1007 | def get_absolute_url(opts, func, self):
|
---|
1008 | return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self)
|
---|
1009 |
|
---|
1010 | def _get_where_clause(lookup_type, table_prefix, field_name, value):
|
---|
1011 | try:
|
---|
1012 | return '%s%s %s %%s' % (table_prefix, field_name, db.OPERATOR_MAPPING[lookup_type])
|
---|
1013 | except KeyError:
|
---|
1014 | pass
|
---|
1015 | if lookup_type == 'in':
|
---|
1016 | return '%s%s IN (%s)' % (table_prefix, field_name, ','.join(['%s' for v in value]))
|
---|
1017 | elif lookup_type in ('range', 'year'):
|
---|
1018 | return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name)
|
---|
1019 | elif lookup_type in ('month', 'day'):
|
---|
1020 | return "%s = %%s" % db.get_date_extract_sql(lookup_type, table_prefix + field_name)
|
---|
1021 | elif lookup_type == 'isnull':
|
---|
1022 | return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
---|
1023 | raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
---|
1024 |
|
---|
1025 | def function_get_object(opts, klass, does_not_exist_exception, **kwargs):
|
---|
1026 | obj_list = function_get_list(opts, klass, **kwargs)
|
---|
1027 | if len(obj_list) < 1:
|
---|
1028 | raise does_not_exist_exception, "%s does not exist for %s" % (opts.object_name, kwargs)
|
---|
1029 | assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (opts.object_name, len(obj_list), kwargs)
|
---|
1030 | return obj_list[0]
|
---|
1031 |
|
---|
1032 | def _get_cached_row(opts, row, index_start):
|
---|
1033 | "Helper function that recursively returns an object with cache filled"
|
---|
1034 | index_end = index_start + len(opts.fields)
|
---|
1035 | obj = opts.get_model_module().Klass(*row[index_start:index_end])
|
---|
1036 | for f in opts.fields:
|
---|
1037 | if f.rel and not f.null:
|
---|
1038 | rel_obj, index_end = _get_cached_row(f.rel.to, row, index_end)
|
---|
1039 | setattr(obj, f.rel.get_cache_name(), rel_obj)
|
---|
1040 | return obj, index_end
|
---|
1041 |
|
---|
1042 | def function_get_iterator(opts, klass, **kwargs):
|
---|
1043 | # kwargs['select'] is a dictionary, and dictionaries' key order is
|
---|
1044 | # undefined, so we convert it to a list of tuples internally.
|
---|
1045 | kwargs['select'] = kwargs.get('select', {}).items()
|
---|
1046 |
|
---|
1047 | cursor = db.db.cursor()
|
---|
1048 | select, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
|
---|
1049 | cursor.execute(full_query, params)
|
---|
1050 | fill_cache = kwargs.get('select_related')
|
---|
1051 | index_end = len(opts.fields)
|
---|
1052 | while 1:
|
---|
1053 | rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
|
---|
1054 | if not rows:
|
---|
1055 | raise StopIteration
|
---|
1056 | for row in rows:
|
---|
1057 | if fill_cache:
|
---|
1058 | obj, index_end = _get_cached_row(opts, row, 0)
|
---|
1059 | else:
|
---|
1060 | obj = klass(*row[:index_end])
|
---|
1061 | for i, k in enumerate(kwargs['select']):
|
---|
1062 | setattr(obj, k[0], row[index_end+i])
|
---|
1063 | yield obj
|
---|
1064 |
|
---|
1065 | def function_get_list(opts, klass, **kwargs):
|
---|
1066 | return list(function_get_iterator(opts, klass, **kwargs))
|
---|
1067 |
|
---|
1068 | def function_get_count(opts, **kwargs):
|
---|
1069 | kwargs['order_by'] = []
|
---|
1070 | kwargs['offset'] = None
|
---|
1071 | kwargs['limit'] = None
|
---|
1072 | kwargs['select_related'] = False
|
---|
1073 | _, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
|
---|
1074 | cursor = db.db.cursor()
|
---|
1075 | cursor.execute("SELECT COUNT(*)" + sql, params)
|
---|
1076 | return cursor.fetchone()[0]
|
---|
1077 |
|
---|
1078 | def function_get_values_iterator(opts, klass, **kwargs):
|
---|
1079 | # select_related and select aren't supported in get_values().
|
---|
1080 | kwargs['select_related'] = False
|
---|
1081 | kwargs['select'] = {}
|
---|
1082 |
|
---|
1083 | # 'fields' is a list of field names to fetch.
|
---|
1084 | try:
|
---|
1085 | fields = kwargs.pop('fields')
|
---|
1086 | except KeyError: # Default to all fields.
|
---|
1087 | fields = [f.name for f in opts.fields]
|
---|
1088 |
|
---|
1089 | cursor = db.db.cursor()
|
---|
1090 | _, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
|
---|
1091 | select = ['%s.%s' % (opts.db_table, f) for f in fields]
|
---|
1092 | cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
|
---|
1093 | while 1:
|
---|
1094 | rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
|
---|
1095 | if not rows:
|
---|
1096 | raise StopIteration
|
---|
1097 | for row in rows:
|
---|
1098 | yield dict(zip(fields, row))
|
---|
1099 |
|
---|
1100 | def function_get_values(opts, klass, **kwargs):
|
---|
1101 | return list(function_get_values_iterator(opts, klass, **kwargs))
|
---|
1102 |
|
---|
1103 | def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen):
|
---|
1104 | """
|
---|
1105 | Helper function that recursively populates the select, tables and where (in
|
---|
1106 | place) for fill-cache queries.
|
---|
1107 | """
|
---|
1108 | for f in opts.fields:
|
---|
1109 | if f.rel and not f.null:
|
---|
1110 | db_table = f.rel.to.db_table
|
---|
1111 | if db_table not in cache_tables_seen:
|
---|
1112 | tables.append(db_table)
|
---|
1113 | else: # The table was already seen, so give it a table alias.
|
---|
1114 | new_prefix = '%s%s' % (db_table, len(cache_tables_seen))
|
---|
1115 | tables.append('%s %s' % (db_table, new_prefix))
|
---|
1116 | db_table = new_prefix
|
---|
1117 | cache_tables_seen.append(db_table)
|
---|
1118 | where.append('%s.%s = %s.%s' % (old_prefix, f.name, db_table, f.rel.field_name))
|
---|
1119 | select.extend(['%s.%s as "%s.%s"' % (db_table, f2.name, db_table, f2.name) for f2 in f.rel.to.fields])
|
---|
1120 | _fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
|
---|
1121 |
|
---|
1122 | def _throw_bad_kwarg_error(kwarg):
|
---|
1123 | # Helper function to remove redundancy.
|
---|
1124 | raise TypeError, "got unexpected keyword argument '%s'" % kwarg
|
---|
1125 |
|
---|
1126 | def _parse_lookup(kwarg_items, opts, table_count=0):
|
---|
1127 | # Helper function that handles converting API kwargs (e.g.
|
---|
1128 | # "name__exact": "tom") to SQL.
|
---|
1129 |
|
---|
1130 | # Note that there is a distinction between where and join_where. The latter
|
---|
1131 | # is specifically a list of where clauses to use for JOINs. This
|
---|
1132 | # distinction is necessary because of support for "_or".
|
---|
1133 |
|
---|
1134 | # table_count is used to ensure table aliases are unique.
|
---|
1135 | tables, join_where, where, params = [], [], [], []
|
---|
1136 | for kwarg, kwarg_value in kwarg_items:
|
---|
1137 | if kwarg in ('order_by', 'limit', 'offset', 'select_related', 'distinct', 'select', 'tables', 'where', 'params'):
|
---|
1138 | continue
|
---|
1139 | if kwarg_value is None:
|
---|
1140 | continue
|
---|
1141 | if kwarg == '_or':
|
---|
1142 | for val in kwarg_value:
|
---|
1143 | tables2, join_where2, where2, params2, table_count = _parse_lookup(val, opts, table_count)
|
---|
1144 | tables.extend(tables2)
|
---|
1145 | join_where.extend(join_where2)
|
---|
1146 | where.append('(%s)' % ' OR '.join(where2))
|
---|
1147 | params.extend(params2)
|
---|
1148 | continue
|
---|
1149 | lookup_list = kwarg.split(LOOKUP_SEPARATOR)
|
---|
1150 | # pk="value" is shorthand for (primary key)__exact="value"
|
---|
1151 | if lookup_list[-1] == 'pk':
|
---|
1152 | lookup_list = lookup_list[:-1] + [opts.pk.name, 'exact']
|
---|
1153 | if len(lookup_list) == 1:
|
---|
1154 | _throw_bad_kwarg_error(kwarg)
|
---|
1155 | lookup_type = lookup_list.pop()
|
---|
1156 | current_opts = opts # We'll be overwriting this, so keep a reference to the original opts.
|
---|
1157 | current_table_alias = current_opts.db_table
|
---|
1158 | param_required = False
|
---|
1159 | while lookup_list or param_required:
|
---|
1160 | table_count += 1
|
---|
1161 | try:
|
---|
1162 | # "current" is a piece of the lookup list. For example, in
|
---|
1163 | # choices.get_list(poll__sites__id__exact=5), lookup_list is
|
---|
1164 | # ["polls", "sites", "id"], and the first current is "polls".
|
---|
1165 | try:
|
---|
1166 | current = lookup_list.pop(0)
|
---|
1167 | except IndexError:
|
---|
1168 | # If we're here, lookup_list is empty but param_required
|
---|
1169 | # is set to True, which means the kwarg was bad.
|
---|
1170 | # Example: choices.get_list(poll__exact='foo')
|
---|
1171 | _throw_bad_kwarg_error(kwarg)
|
---|
1172 | # Try many-to-many relationships first...
|
---|
1173 | for f in current_opts.many_to_many:
|
---|
1174 | if f.name == current:
|
---|
1175 | rel_table_alias = 't%s' % table_count
|
---|
1176 | table_count += 1
|
---|
1177 | tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias))
|
---|
1178 | join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.name,
|
---|
1179 | rel_table_alias, current_opts.object_name.lower()))
|
---|
1180 | # Optimization: In the case of primary-key lookups, we
|
---|
1181 | # don't have to do an extra join.
|
---|
1182 | if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
|
---|
1183 | where.append(_get_where_clause(lookup_type, rel_table_alias+'.',
|
---|
1184 | f.rel.to.object_name.lower()+'_id', kwarg_value))
|
---|
1185 | params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
|
---|
1186 | lookup_list.pop()
|
---|
1187 | param_required = False
|
---|
1188 | else:
|
---|
1189 | new_table_alias = 't%s' % table_count
|
---|
1190 | tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
|
---|
1191 | join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(),
|
---|
1192 | new_table_alias, f.rel.to.pk.name))
|
---|
1193 | current_table_alias = new_table_alias
|
---|
1194 | param_required = True
|
---|
1195 | current_opts = f.rel.to
|
---|
1196 | raise StopIteration
|
---|
1197 | for f in current_opts.fields:
|
---|
1198 | # Try many-to-one relationships...
|
---|
1199 | if f.rel and f.rel.name == current:
|
---|
1200 | # Optimization: In the case of primary-key lookups, we
|
---|
1201 | # don't have to do an extra join.
|
---|
1202 | if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
|
---|
1203 | where.append(_get_where_clause(lookup_type, current_table_alias+'.', f.name, kwarg_value))
|
---|
1204 | params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
|
---|
1205 | lookup_list.pop()
|
---|
1206 | param_required = False
|
---|
1207 | else:
|
---|
1208 | new_table_alias = 't%s' % table_count
|
---|
1209 | tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
|
---|
1210 | join_where.append('%s.%s = %s.%s' % (current_table_alias, f.name, new_table_alias, f.rel.to.pk.name))
|
---|
1211 | current_table_alias = new_table_alias
|
---|
1212 | param_required = True
|
---|
1213 | current_opts = f.rel.to
|
---|
1214 | raise StopIteration
|
---|
1215 | # Try direct field-name lookups...
|
---|
1216 | if f.name == current:
|
---|
1217 | where.append(_get_where_clause(lookup_type, current_table_alias+'.', current, kwarg_value))
|
---|
1218 | params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
|
---|
1219 | param_required = False
|
---|
1220 | raise StopIteration
|
---|
1221 | # If we haven't hit StopIteration at this point, "current" must be
|
---|
1222 | # an invalid lookup, so raise an exception.
|
---|
1223 | _throw_bad_kwarg_error(kwarg)
|
---|
1224 | except StopIteration:
|
---|
1225 | continue
|
---|
1226 | return tables, join_where, where, params, table_count
|
---|
1227 |
|
---|
1228 | def function_get_sql_clause(opts, **kwargs):
|
---|
1229 | select = ["%s.%s" % (opts.db_table, f.name) for f in opts.fields]
|
---|
1230 | tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
|
---|
1231 | where = kwargs.get('where') and kwargs['where'][:] or []
|
---|
1232 | params = kwargs.get('params') and kwargs['params'][:] or []
|
---|
1233 |
|
---|
1234 | # Convert the kwargs into SQL.
|
---|
1235 | tables2, join_where2, where2, params2, _ = _parse_lookup(kwargs.items(), opts)
|
---|
1236 | tables.extend(tables2)
|
---|
1237 | where.extend(join_where2 + where2)
|
---|
1238 | params.extend(params2)
|
---|
1239 |
|
---|
1240 | # Add any additional constraints from the "where_constraints" parameter.
|
---|
1241 | where.extend(opts.where_constraints)
|
---|
1242 |
|
---|
1243 | # Add additional tables and WHERE clauses based on select_related.
|
---|
1244 | if kwargs.get('select_related') is True:
|
---|
1245 | _fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
|
---|
1246 |
|
---|
1247 | # Add any additional SELECTs passed in via kwargs.
|
---|
1248 | if kwargs.get('select'):
|
---|
1249 | select.extend(['(%s) AS %s' % (s[1], s[0]) for s in kwargs['select']])
|
---|
1250 |
|
---|
1251 | # ORDER BY clause
|
---|
1252 | order_by = []
|
---|
1253 | for f in handle_legacy_orderlist(kwargs.get('order_by', opts.ordering)):
|
---|
1254 | if f == '?': # Special case.
|
---|
1255 | order_by.append('RANDOM()')
|
---|
1256 | else:
|
---|
1257 | # Use the database table as a column prefix if it wasn't given,
|
---|
1258 | # and if the requested column isn't a custom SELECT.
|
---|
1259 | if "." not in f and f not in [k[0] for k in kwargs.get('select', [])]:
|
---|
1260 | table_prefix = opts.db_table + '.'
|
---|
1261 | else:
|
---|
1262 | table_prefix = ''
|
---|
1263 | if f.startswith('-'):
|
---|
1264 | order_by.append('%s%s DESC' % (table_prefix, f[1:]))
|
---|
1265 | else:
|
---|
1266 | order_by.append('%s%s ASC' % (table_prefix, f))
|
---|
1267 | order_by = ", ".join(order_by)
|
---|
1268 |
|
---|
1269 |
|
---|
1270 | sql = " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "")
|
---|
1271 |
|
---|
1272 | if (db.DATABASE_ENGINE != 'oracle'):
|
---|
1273 | # LIMIT and OFFSET clauses
|
---|
1274 | if kwargs.get('limit') is not None:
|
---|
1275 | limit_sql = " LIMIT %s " % kwargs['limit']
|
---|
1276 | if kwargs.get('offset') is not None and kwargs['offset'] != 0:
|
---|
1277 | limit_sql += "OFFSET %s " % kwargs['offset']
|
---|
1278 | else:
|
---|
1279 | limit_sql = ""
|
---|
1280 |
|
---|
1281 | full_query = "SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql + limit_sql
|
---|
1282 | return select, sql + limit_sql, params, full_query
|
---|
1283 | else:
|
---|
1284 | # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query.
|
---|
1285 |
|
---|
1286 | select_clause = ",".join(select)
|
---|
1287 | distinct = (kwargs.get('distinct') and "DISTINCT " or "")
|
---|
1288 | from_clause = ",".join(tables)
|
---|
1289 | where_clause = (where and " WHERE " + " AND ".join(where) or "")
|
---|
1290 |
|
---|
1291 | if order_by:
|
---|
1292 | order_by_clause = " OVER (ORDER BY %s )" % (order_by)
|
---|
1293 | else:
|
---|
1294 | #Oracle's row_number() function always requires an order-by clause.
|
---|
1295 | #So we need to define a default order-by, since none was provided.
|
---|
1296 | order_by_clause = " OVER (ORDER BY %s.%s)" % (opts.db_table, opts.fields[0].name)
|
---|
1297 |
|
---|
1298 | # limit_and_offset_clause
|
---|
1299 | limit = kwargs.get('limit',0)
|
---|
1300 | offset = kwargs.get('offset',0)
|
---|
1301 |
|
---|
1302 | limit_and_offset_clause = ''
|
---|
1303 | if limit:
|
---|
1304 | limit = int(limit)
|
---|
1305 | offset = int(offset)
|
---|
1306 | limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset)
|
---|
1307 | else:
|
---|
1308 | limit_and_offset_clause = "WHERE rn > %s" % (offset)
|
---|
1309 |
|
---|
1310 | full_query = """SELECT * FROM
|
---|
1311 | (SELECT %s
|
---|
1312 | %s,
|
---|
1313 | ROW_NUMBER() %s AS rn
|
---|
1314 | FROM %s
|
---|
1315 | %s
|
---|
1316 | )
|
---|
1317 | %s
|
---|
1318 | """ % (distinct, select_clause, order_by_clause, from_clause, where_clause, limit_and_offset_clause)
|
---|
1319 | return select, sql, params, full_query
|
---|
1320 |
|
---|
1321 | def function_get_in_bulk(opts, klass, *args, **kwargs):
|
---|
1322 | id_list = args and args[0] or kwargs['id_list']
|
---|
1323 | assert id_list != [], "get_in_bulk() cannot be passed an empty list."
|
---|
1324 | kwargs['where'] = ["%s.id IN (%s)" % (opts.db_table, ",".join(map(str, id_list)))]
|
---|
1325 | obj_list = function_get_list(opts, klass, **kwargs)
|
---|
1326 | return dict([(o.id, o) for o in obj_list])
|
---|
1327 |
|
---|
1328 | def function_get_latest(opts, klass, does_not_exist_exception, **kwargs):
|
---|
1329 | kwargs['order_by'] = ('-' + opts.get_latest_by,)
|
---|
1330 | kwargs['limit'] = 1
|
---|
1331 | return function_get_object(opts, klass, does_not_exist_exception, **kwargs)
|
---|
1332 |
|
---|
1333 | def function_get_date_list(opts, field, *args, **kwargs):
|
---|
1334 | from django.core.db.typecasts import typecast_timestamp
|
---|
1335 | kind = args and args[0] or kwargs['kind']
|
---|
1336 | assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
|
---|
1337 | order = 'ASC'
|
---|
1338 | if kwargs.has_key('_order'):
|
---|
1339 | order = kwargs['_order']
|
---|
1340 | del kwargs['_order']
|
---|
1341 | assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
|
---|
1342 | kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
|
---|
1343 | if field.null:
|
---|
1344 | kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.name))
|
---|
1345 | select, sql, params, full_query = function_get_sql_clause(opts, **kwargs)
|
---|
1346 | sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.name)), sql)
|
---|
1347 | cursor = db.db.cursor()
|
---|
1348 | cursor.execute(sql, params)
|
---|
1349 | # We have to manually run typecast_timestamp(str()) on the results, because
|
---|
1350 | # MySQL doesn't automatically cast the result of date functions as datetime
|
---|
1351 | # objects -- MySQL returns the values as strings, instead.
|
---|
1352 | return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
|
---|
1353 |
|
---|
1354 | ###################################
|
---|
1355 | # HELPER FUNCTIONS (MANIPULATORS) #
|
---|
1356 | ###################################
|
---|
1357 |
|
---|
1358 | def get_manipulator(opts, klass, extra_methods, add=False, change=False):
|
---|
1359 | "Returns the custom Manipulator (either add or change) for the given opts."
|
---|
1360 | assert (add == False or change == False) and add != change, "get_manipulator() can be passed add=True or change=True, but not both"
|
---|
1361 | man = types.ClassType('%sManipulator%s' % (opts.object_name, add and 'Add' or 'Change'), (formfields.Manipulator,), {})
|
---|
1362 | man.__module__ = MODEL_PREFIX + '.' + opts.module_name # Set this explicitly, as above.
|
---|
1363 | man.__init__ = curry(manipulator_init, opts, add, change)
|
---|
1364 | man.save = curry(manipulator_save, opts, klass, add, change)
|
---|
1365 | for field_name_list in opts.unique_together:
|
---|
1366 | setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
|
---|
1367 | for f in opts.fields:
|
---|
1368 | if f.unique_for_date:
|
---|
1369 | setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_date), opts, 'date'))
|
---|
1370 | if f.unique_for_month:
|
---|
1371 | setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_month), opts, 'month'))
|
---|
1372 | if f.unique_for_year:
|
---|
1373 | setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_year), opts, 'year'))
|
---|
1374 | for k, v in extra_methods.items():
|
---|
1375 | setattr(man, k, v)
|
---|
1376 | return man
|
---|
1377 |
|
---|
1378 | def manipulator_init(opts, add, change, self, obj_key=None):
|
---|
1379 | if change:
|
---|
1380 | assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
|
---|
1381 | self.obj_key = obj_key
|
---|
1382 | try:
|
---|
1383 | self.original_object = opts.get_model_module().get_object(pk=obj_key)
|
---|
1384 | except ObjectDoesNotExist:
|
---|
1385 | # If the object doesn't exist, this might be a manipulator for a
|
---|
1386 | # one-to-one related object that hasn't created its subobject yet.
|
---|
1387 | # For example, this might be a Restaurant for a Place that doesn't
|
---|
1388 | # yet have restaurant information.
|
---|
1389 | if opts.one_to_one_field:
|
---|
1390 | # Sanity check -- Make sure the "parent" object exists.
|
---|
1391 | # For example, make sure the Place exists for the Restaurant.
|
---|
1392 | # Let the ObjectDoesNotExist exception propogate up.
|
---|
1393 | lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
|
---|
1394 | lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
|
---|
1395 | _ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs)
|
---|
1396 | params = dict([(f.name, f.get_default()) for f in opts.fields])
|
---|
1397 | params[opts.pk.name] = obj_key
|
---|
1398 | self.original_object = opts.get_model_module().Klass(**params)
|
---|
1399 | else:
|
---|
1400 | raise
|
---|
1401 | self.fields = []
|
---|
1402 | for f in opts.fields + opts.many_to_many:
|
---|
1403 | if f.editable and not (f.primary_key and change) and (not f.rel or not f.rel.edit_inline):
|
---|
1404 | self.fields.extend(f.get_manipulator_fields(opts, self, change))
|
---|
1405 |
|
---|
1406 | # Add fields for related objects.
|
---|
1407 | for rel_opts, rel_field in opts.get_inline_related_objects():
|
---|
1408 | if change:
|
---|
1409 | count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))()
|
---|
1410 | count += rel_field.rel.num_extra_on_change
|
---|
1411 | if rel_field.rel.min_num_in_admin:
|
---|
1412 | count = max(count, rel_field.rel.min_num_in_admin)
|
---|
1413 | if rel_field.rel.max_num_in_admin:
|
---|
1414 | count = min(count, rel_field.rel.max_num_in_admin)
|
---|
1415 | else:
|
---|
1416 | count = rel_field.rel.num_in_admin
|
---|
1417 | for f in rel_opts.fields + rel_opts.many_to_many:
|
---|
1418 | if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change)):
|
---|
1419 | for i in range(count):
|
---|
1420 | self.fields.extend(f.get_manipulator_fields(rel_opts, self, change, name_prefix='%s.%d.' % (rel_opts.object_name.lower(), i), rel=True))
|
---|
1421 |
|
---|
1422 | # Add field for ordering.
|
---|
1423 | if change and opts.get_ordered_objects():
|
---|
1424 | self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
|
---|
1425 |
|
---|
1426 | def manipulator_save(opts, klass, add, change, self, new_data):
|
---|
1427 | from django.utils.datastructures import DotExpandedDict
|
---|
1428 | params = {}
|
---|
1429 | for f in opts.fields:
|
---|
1430 | # Fields with auto_now_add are another special case; they should keep
|
---|
1431 | # their original value in the change stage.
|
---|
1432 | if change and getattr(f, 'auto_now_add', False):
|
---|
1433 | params[f.name] = getattr(self.original_object, f.name)
|
---|
1434 | else:
|
---|
1435 | params[f.name] = f.get_manipulator_new_data(new_data)
|
---|
1436 |
|
---|
1437 | if change:
|
---|
1438 | params[opts.pk.name] = self.obj_key
|
---|
1439 |
|
---|
1440 | # First, save the basic object itself.
|
---|
1441 | new_object = klass(**params)
|
---|
1442 | new_object.save()
|
---|
1443 |
|
---|
1444 | # Now that the object's been saved, save any uploaded files.
|
---|
1445 | for f in opts.fields:
|
---|
1446 | if isinstance(f, FileField):
|
---|
1447 | f.save_file(new_data, new_object, change and self.original_object or None, change, rel=False)
|
---|
1448 |
|
---|
1449 | # Calculate which primary fields have changed.
|
---|
1450 | if change:
|
---|
1451 | self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
|
---|
1452 | for f in opts.fields:
|
---|
1453 | if not f.primary_key and str(getattr(self.original_object, f.name)) != str(getattr(new_object, f.name)):
|
---|
1454 | self.fields_changed.append(f.verbose_name)
|
---|
1455 |
|
---|
1456 | # Save many-to-many objects. Example: Poll.set_sites()
|
---|
1457 | for f in opts.many_to_many:
|
---|
1458 | if not f.rel.edit_inline:
|
---|
1459 | was_changed = getattr(new_object, 'set_%s' % f.name)(new_data.getlist(f.name))
|
---|
1460 | if change and was_changed:
|
---|
1461 | self.fields_changed.append(f.verbose_name)
|
---|
1462 |
|
---|
1463 | # Save many-to-one objects. Example: Add the Choice objects for a Poll.
|
---|
1464 | for rel_opts, rel_field in opts.get_inline_related_objects():
|
---|
1465 | # Create obj_list, which is a DotExpandedDict such as this:
|
---|
1466 | # [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
|
---|
1467 | # ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
|
---|
1468 | # ('2', {'id': [''], 'choice': ['']})]
|
---|
1469 | obj_list = DotExpandedDict(new_data.data)[rel_opts.object_name.lower()].items()
|
---|
1470 | obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
|
---|
1471 | params = {}
|
---|
1472 |
|
---|
1473 | # For each related item...
|
---|
1474 | for _, rel_new_data in obj_list:
|
---|
1475 |
|
---|
1476 | # Keep track of which core=True fields were provided.
|
---|
1477 | # If all core fields were given, the related object will be saved.
|
---|
1478 | # If none of the core fields were given, the object will be deleted.
|
---|
1479 | # If some, but not all, of the fields were given, the validator would
|
---|
1480 | # have caught that.
|
---|
1481 | all_cores_given, all_cores_blank = True, True
|
---|
1482 |
|
---|
1483 | # Get a reference to the old object. We'll use it to compare the
|
---|
1484 | # old to the new, to see which fields have changed.
|
---|
1485 | if change:
|
---|
1486 | old_rel_obj = None
|
---|
1487 | if rel_new_data[rel_opts.pk.name][0]:
|
---|
1488 | try:
|
---|
1489 | old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(rel_opts, rel_field))(**{'%s__exact' % rel_opts.pk.name: rel_new_data[rel_opts.pk.name][0]})
|
---|
1490 | except ObjectDoesNotExist:
|
---|
1491 | pass
|
---|
1492 |
|
---|
1493 | for f in rel_opts.fields:
|
---|
1494 | if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
|
---|
1495 | all_cores_given = False
|
---|
1496 | elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
|
---|
1497 | all_cores_blank = False
|
---|
1498 | # If this field isn't editable, give it the same value it had
|
---|
1499 | # previously, according to the given ID. If the ID wasn't
|
---|
1500 | # given, use a default value. FileFields are also a special
|
---|
1501 | # case, because they'll be dealt with later.
|
---|
1502 | if change and (isinstance(f, FileField) or not f.editable):
|
---|
1503 | if rel_new_data.get(rel_opts.pk.name, False) and rel_new_data[rel_opts.pk.name][0]:
|
---|
1504 | params[f.name] = getattr(old_rel_obj, f.name)
|
---|
1505 | else:
|
---|
1506 | params[f.name] = f.get_default()
|
---|
1507 | elif f == rel_field:
|
---|
1508 | params[f.name] = getattr(new_object, rel_field.rel.field_name)
|
---|
1509 | elif add and isinstance(f, AutoField):
|
---|
1510 | params[f.name] = None
|
---|
1511 | else:
|
---|
1512 | params[f.name] = f.get_manipulator_new_data(rel_new_data, rel=True)
|
---|
1513 | # Related links are a special case, because we have to
|
---|
1514 | # manually set the "content_type_id" field.
|
---|
1515 | if opts.has_related_links and rel_opts.module_name == 'relatedlinks':
|
---|
1516 | contenttypes_mod = get_module('core', 'contenttypes')
|
---|
1517 | params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=opts.app_label, python_module_name__exact=opts.module_name).id
|
---|
1518 | params['object_id'] = new_object.id
|
---|
1519 |
|
---|
1520 | # Create the related item.
|
---|
1521 | new_rel_obj = rel_opts.get_model_module().Klass(**params)
|
---|
1522 |
|
---|
1523 | # If all the core fields were provided (non-empty), save the item.
|
---|
1524 | if all_cores_given:
|
---|
1525 | new_rel_obj.save()
|
---|
1526 |
|
---|
1527 | # Save any uploaded files.
|
---|
1528 | for f in rel_opts.fields:
|
---|
1529 | if isinstance(f, FileField) and rel_new_data.get(f.name, False):
|
---|
1530 | f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, change, rel=True)
|
---|
1531 |
|
---|
1532 | # Calculate whether any fields have changed.
|
---|
1533 | if change:
|
---|
1534 | if not old_rel_obj: # This object didn't exist before.
|
---|
1535 | self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj))
|
---|
1536 | else:
|
---|
1537 | for f in rel_opts.fields:
|
---|
1538 | if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.name)) != str(getattr(new_rel_obj, f.name)):
|
---|
1539 | self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj))
|
---|
1540 |
|
---|
1541 | # Save many-to-many objects.
|
---|
1542 | for f in rel_opts.many_to_many:
|
---|
1543 | if not f.rel.edit_inline:
|
---|
1544 | was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.name])
|
---|
1545 | if change and was_changed:
|
---|
1546 | self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj))
|
---|
1547 |
|
---|
1548 | # If, in the change stage, all of the core fields were blank and
|
---|
1549 | # the primary key (ID) was provided, delete the item.
|
---|
1550 | if change and all_cores_blank and rel_new_data.has_key(rel_opts.pk.name) and rel_new_data[rel_opts.pk.name][0]:
|
---|
1551 | new_rel_obj.delete()
|
---|
1552 | self.fields_deleted.append('%s "%r"' % (rel_opts.verbose_name, old_rel_obj))
|
---|
1553 |
|
---|
1554 | # Save the order, if applicable.
|
---|
1555 | if change and opts.get_ordered_objects():
|
---|
1556 | order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
|
---|
1557 | for rel_opts in opts.get_ordered_objects():
|
---|
1558 | getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
|
---|
1559 | return new_object
|
---|
1560 |
|
---|
1561 | def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):
|
---|
1562 | from django.utils.text import get_text_list
|
---|
1563 | field_list = [opts.get_field(field_name) for field_name in field_name_list]
|
---|
1564 | kwargs = {'%s__iexact' % field_name_list[0]: field_data}
|
---|
1565 | for f in field_list[1:]:
|
---|
1566 | field_val = all_data.get(f.name, None)
|
---|
1567 | if field_val is None:
|
---|
1568 | # This will be caught by another validator, assuming the field
|
---|
1569 | # doesn't have blank=True.
|
---|
1570 | return
|
---|
1571 | kwargs['%s__iexact' % f.name] = field_val
|
---|
1572 | mod = opts.get_model_module()
|
---|
1573 | try:
|
---|
1574 | old_obj = mod.get_object(**kwargs)
|
---|
1575 | except ObjectDoesNotExist:
|
---|
1576 | return
|
---|
1577 | if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name):
|
---|
1578 | pass
|
---|
1579 | else:
|
---|
1580 | raise validators.ValidationError, "%s with this %s already exists for the given %s." % \
|
---|
1581 | (capfirst(opts.verbose_name), field_list[0].verbose_name, get_text_list(field_name_list[1:], 'and'))
|
---|
1582 |
|
---|
1583 | def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data):
|
---|
1584 | date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None)
|
---|
1585 | mod = opts.get_model_module()
|
---|
1586 | date_val = formfields.DateField.html2python(date_str)
|
---|
1587 | if date_val is None:
|
---|
1588 | return # Date was invalid. This will be caught by another validator.
|
---|
1589 | lookup_kwargs = {'%s__iexact' % from_field.name: field_data, '%s__year' % date_field.name: date_val.year}
|
---|
1590 | if lookup_type in ('month', 'date'):
|
---|
1591 | lookup_kwargs['%s__month' % date_field.name] = date_val.month
|
---|
1592 | if lookup_type == 'date':
|
---|
1593 | lookup_kwargs['%s__day' % date_field.name] = date_val.day
|
---|
1594 | try:
|
---|
1595 | old_obj = mod.get_object(**lookup_kwargs)
|
---|
1596 | except ObjectDoesNotExist:
|
---|
1597 | return
|
---|
1598 | else:
|
---|
1599 | if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.name) == getattr(old_obj, opts.pk.name):
|
---|
1600 | pass
|
---|
1601 | else:
|
---|
1602 | format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'
|
---|
1603 | raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \
|
---|
1604 | (from_field.verbose_name, date_val.strftime(format_string))
|
---|