| 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))
|
|---|