Django

Code

root/django/trunk/django/db/models/fields/related.py

Revision 8884, 41.5 kB (checked in by mtredinnick, 5 days ago)

Fixed #8669 -- Use a consistent version of create() across the board for
model/field instance creation. Based on a patch from Richard Davies.

  • Property svn:eol-style set to native
Line 
1 from django.db import connection, transaction
2 from django.db.models import signals, get_model
3 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
4 from django.db.models.related import RelatedObject
5 from django.db.models.query import QuerySet
6 from django.db.models.query_utils import QueryWrapper
7 from django.utils.encoding import smart_unicode
8 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
9 from django.utils.functional import curry
10 from django.core import exceptions
11 from django import forms
12
13 try:
14     set
15 except NameError:
16     from sets import Set as set   # Python 2.3 fallback
17
18 RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
19
20 pending_lookups = {}
21
22 def add_lazy_relation(cls, field, relation, operation):
23     """
24     Adds a lookup on ``cls`` when a related field is defined using a string,
25     i.e.::
26
27         class MyModel(Model):
28             fk = ForeignKey("AnotherModel")
29
30     This string can be:
31
32         * RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive
33           relation.
34
35         * The name of a model (i.e "AnotherModel") to indicate another model in
36           the same app.
37
38         * An app-label and model name (i.e. "someapp.AnotherModel") to indicate
39           another model in a different app.
40
41     If the other model hasn't yet been loaded -- almost a given if you're using
42     lazy relationships -- then the relation won't be set up until the
43     class_prepared signal fires at the end of model initialization.
44
45     operation is the work that must be performed once the relation can be resolved.
46     """
47     # Check for recursive relations
48     if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
49         app_label = cls._meta.app_label
50         model_name = cls.__name__
51
52     else:
53         # Look for an "app.Model" relation
54         try:
55             app_label, model_name = relation.split(".")
56         except ValueError:
57             # If we can't split, assume a model in current app
58             app_label = cls._meta.app_label
59             model_name = relation
60
61     # Try to look up the related model, and if it's already loaded resolve the
62     # string right away. If get_model returns None, it means that the related
63     # model isn't loaded yet, so we need to pend the relation until the class
64     # is prepared.
65     model = get_model(app_label, model_name, False)
66     if model:
67         operation(field, model, cls)
68     else:
69         key = (app_label, model_name)
70         value = (cls, field, operation)
71         pending_lookups.setdefault(key, []).append(value)
72
73 def do_pending_lookups(sender, **kwargs):
74     """
75     Handle any pending relations to the sending model. Sent from class_prepared.
76     """
77     key = (sender._meta.app_label, sender.__name__)
78     for cls, field, operation in pending_lookups.pop(key, []):
79         operation(field, sender, cls)
80
81 signals.class_prepared.connect(do_pending_lookups)
82
83 #HACK
84 class RelatedField(object):
85     def contribute_to_class(self, cls, name):
86         sup = super(RelatedField, self)
87
88         # Add an accessor to allow easy determination of the related query path for this field
89         self.related_query_name = curry(self._get_related_query_name, cls._meta)
90
91         if hasattr(sup, 'contribute_to_class'):
92             sup.contribute_to_class(cls, name)
93
94         if not cls._meta.abstract and self.rel.related_name:
95             self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
96
97         other = self.rel.to
98         if isinstance(other, basestring):
99             def resolve_related_class(field, model, cls):
100                 field.rel.to = model
101                 field.do_related_class(model, cls)
102             add_lazy_relation(cls, self, other, resolve_related_class)
103         else:
104             self.do_related_class(other, cls)
105
106     def set_attributes_from_rel(self):
107         self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
108         if self.verbose_name is None:
109             self.verbose_name = self.rel.to._meta.verbose_name
110         self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
111
112     def do_related_class(self, other, cls):
113         self.set_attributes_from_rel()
114         related = RelatedObject(other, cls, self)
115         if not cls._meta.abstract:
116             self.contribute_to_related_class(other, related)
117
118     def get_db_prep_lookup(self, lookup_type, value):
119         # If we are doing a lookup on a Related Field, we must be
120         # comparing object instances. The value should be the PK of value,
121         # not value itself.
122         def pk_trace(value):
123             # Value may be a primary key, or an object held in a relation.
124             # If it is an object, then we need to get the primary key value for
125             # that object. In certain conditions (especially one-to-one relations),
126             # the primary key may itself be an object - so we need to keep drilling
127             # down until we hit a value that can be used for a comparison.
128             v, field = value, None
129             try:
130                 while True:
131                     v, field = getattr(v, v._meta.pk.name), v._meta.pk
132             except AttributeError:
133                 pass
134             if field:
135                 if lookup_type in ('range', 'in'):
136                     v = [v]
137                 v = field.get_db_prep_lookup(lookup_type, v)
138                 if isinstance(v, list):
139                     v = v[0]
140             return v
141
142         if hasattr(value, 'as_sql'):
143             sql, params = value.as_sql()
144             return QueryWrapper(('(%s)' % sql), params)
145
146         # FIXME: lt and gt are explicitally allowed to make
147         # get_(next/prev)_by_date work; other lookups are not allowed since that
148         # gets messy pretty quick. This is a good candidate for some refactoring
149         # in the future.
150         if lookup_type in ['exact', 'gt', 'lt']:
151             return [pk_trace(value)]
152         if lookup_type in ('range', 'in'):
153             return [pk_trace(v) for v in value]
154         elif lookup_type == 'isnull':
155             return []
156         raise TypeError, "Related Field has invalid lookup: %s" % lookup_type
157
158     def _get_related_query_name(self, opts):
159         # This method defines the name that can be used to identify this
160         # related object in a table-spanning query. It uses the lower-cased
161         # object_name by default, but this can be overridden with the
162         # "related_name" option.
163         return self.rel.related_name or opts.object_name.lower()
164
165 class SingleRelatedObjectDescriptor(object):
166     # This class provides the functionality that makes the related-object
167     # managers available as attributes on a model class, for fields that have
168     # a single "remote" value, on the class pointed to by a related field.
169     # In the example "place.restaurant", the restaurant attribute is a
170     # SingleRelatedObjectDescriptor instance.
171     def __init__(self, related):
172         self.related = related
173         self.cache_name = '_%s_cache' % related.get_accessor_name()
174
175     def __get__(self, instance, instance_type=None):
176         if instance is None:
177             raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
178
179         try:
180             return getattr(instance, self.cache_name)
181         except AttributeError:
182             params = {'%s__pk' % self.related.field.name: instance._get_pk_val()}
183             rel_obj = self.related.model._default_manager.get(**params)
184             setattr(instance, self.cache_name, rel_obj)
185             return rel_obj
186
187     def __set__(self, instance, value):
188         if instance is None:
189             raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
190
191         # The similarity of the code below to the code in
192         # ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
193         # of small differences that would make a common base class convoluted.
194
195         # If null=True, we can assign null here, but otherwise the value needs
196         # to be an instance of the related class.
197         if value is None and self.related.field.null == False:
198             raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
199                                 (instance._meta.object_name, self.related.get_accessor_name()))
200         elif value is not None and not isinstance(value, self.related.model):
201             raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
202                                 (value, instance._meta.object_name,
203                                  self.related.get_accessor_name(), self.related.opts.object_name))
204
205         # Set the value of the related field
206         setattr(value, self.related.field.rel.get_related_field().attname, instance)
207
208         # Since we already know what the related object is, seed the related
209         # object caches now, too. This avoids another db hit if you get the
210         # object you just set.
211         setattr(instance, self.cache_name, value)
212         setattr(value, self.related.field.get_cache_name(), instance)
213
214 class ReverseSingleRelatedObjectDescriptor(object):
215     # This class provides the functionality that makes the related-object
216     # managers available as attributes on a model class, for fields that have
217     # a single "remote" value, on the class that defines the related field.
218     # In the example "choice.poll", the poll attribute is a
219     # ReverseSingleRelatedObjectDescriptor instance.
220     def __init__(self, field_with_rel):
221         self.field = field_with_rel
222
223     def __get__(self, instance, instance_type=None):
224         if instance is None:
225             raise AttributeError, "%s must be accessed via instance" % self.field.name
226         cache_name = self.field.get_cache_name()
227         try:
228             return getattr(instance, cache_name)
229         except AttributeError:
230             val = getattr(instance, self.field.attname)
231             if val is None:
232                 # If NULL is an allowed value, return it.
233                 if self.field.null:
234                     return None
235                 raise self.field.rel.to.DoesNotExist
236             other_field = self.field.rel.get_related_field()
237             if other_field.rel:
238                 params = {'%s__pk' % self.field.rel.field_name: val}
239             else:
240                 params = {'%s__exact' % self.field.rel.field_name: val}
241
242             # If the related manager indicates that it should be used for
243             # related fields, respect that.
244             rel_mgr = self.field.rel.to._default_manager
245             if getattr(rel_mgr, 'use_for_related_fields', False):
246                 rel_obj = rel_mgr.get(**params)
247             else:
248                 rel_obj = QuerySet(self.field.rel.to).get(**params)
249             setattr(instance, cache_name, rel_obj)
250             return rel_obj
251
252     def __set__(self, instance, value):
253         if instance is None:
254             raise AttributeError, "%s must be accessed via instance" % self._field.name
255
256         # If null=True, we can assign null here, but otherwise the value needs
257         # to be an instance of the related class.
258         if value is None and self.field.null == False:
259             raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
260                                 (instance._meta.object_name, self.field.name))
261         elif value is not None and not isinstance(value, self.field.rel.to):
262             raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
263                                 (value, instance._meta.object_name,
264                                  self.field.name, self.field.rel.to._meta.object_name))
265
266         # Set the value of the related field
267         try:
268             val = getattr(value, self.field.rel.get_related_field().attname)
269         except AttributeError:
270             val = None
271         setattr(instance, self.field.attname, val)
272
273         # Since we already know what the related object is, seed the related
274         # object cache now, too. This avoids another db hit if you get the
275         # object you just set.
276         setattr(instance, self.field.get_cache_name(), value)
277
278 class ForeignRelatedObjectsDescriptor(object):
279     # This class provides the functionality that makes the related-object
280     # managers available as attributes on a model class, for fields that have
281     # multiple "remote" values and have a ForeignKey pointed at them by
282     # some other model. In the example "poll.choice_set", the choice_set
283     # attribute is a ForeignRelatedObjectsDescriptor instance.
284     def __init__(self, related):
285         self.related = related   # RelatedObject instance
286
287     def __get__(self, instance, instance_type=None):
288         if instance is None:
289             raise AttributeError, "Manager must be accessed via instance"
290
291         rel_field = self.related.field
292         rel_model = self.related.model
293
294         # Dynamically create a class that subclasses the related
295         # model's default manager.
296         superclass = self.related.model._default_manager.__class__
297
298         class RelatedManager(superclass):
299             def get_query_set(self):
300                 return superclass.get_query_set(self).filter(**(self.core_filters))
301
302             def add(self, *objs):
303                 for obj in objs:
304                     setattr(obj, rel_field.name, instance)
305                     obj.save()
306             add.alters_data = True
307
308             def create(self, **kwargs):
309                 kwargs.update({rel_field.name: instance})
310                 return super(RelatedManager, self).create(**kwargs)
311             create.alters_data = True
312
313             def get_or_create(self, **kwargs):
314                 # Update kwargs with the related object that this
315                 # ForeignRelatedObjectsDescriptor knows about.
316                 kwargs.update({rel_field.name: instance})
317                 return super(RelatedManager, self).get_or_create(**kwargs)
318             get_or_create.alters_data = True
319
320             # remove() and clear() are only provided if the ForeignKey can have a value of null.
321             if rel_field.null:
322                 def remove(self, *objs):
323                     val = getattr(instance, rel_field.rel.get_related_field().attname)
324                     for obj in objs:
325                         # Is obj actually part of this descriptor set?
326                         if getattr(obj, rel_field.attname) == val:
327                             setattr(obj, rel_field.name, None)
328                             obj.save()
329                         else:
330                             raise rel_field.rel.to.DoesNotExist, "%r is not related to %r." % (obj, instance)
331                 remove.alters_data = True
332
333                 def clear(self):
334                     for obj in self.all():
335                         setattr(obj, rel_field.name, None)
336                         obj.save()
337                 clear.alters_data = True
338
339         manager = RelatedManager()
340         attname = rel_field.rel.get_related_field().name
341         manager.core_filters = {'%s__%s' % (rel_field.name, attname):
342                 getattr(instance, attname)}
343         manager.model = self.related.model
344
345         return manager
346
347     def __set__(self, instance, value):
348         if instance is None:
349             raise AttributeError, "Manager must be accessed via instance"
350
351         manager = self.__get__(instance)
352         # If the foreign key can support nulls, then completely clear the related set.
353         # Otherwise, just move the named objects into the set.
354         if self.related.field.null:
355             manager.clear()
356         manager.add(*value)
357
358 def create_many_related_manager(superclass, through=False):
359     """Creates a manager that subclasses 'superclass' (which is a Manager)
360     and adds behavior for many-to-many related objects."""
361     class ManyRelatedManager(superclass):
362         def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
363                 join_table=None, source_col_name=None, target_col_name=None):
364             super(ManyRelatedManager, self).__init__()
365             self.core_filters = core_filters
366             self.model = model
367             self.symmetrical = symmetrical
368             self.instance = instance
369             self.join_table = join_table
370             self.source_col_name = source_col_name
371             self.target_col_name = target_col_name
372             self.through = through
373             self._pk_val = self.instance._get_pk_val()
374             if self._pk_val is None:
375                 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
376
377         def get_query_set(self):
378             return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters))
379
380         # If the ManyToMany relation has an intermediary model,
381         # the add and remove methods do not exist.
382         if through is None:
383             def add(self, *objs):
384                 self._add_items(self.source_col_name, self.target_col_name, *objs)
385
386                 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
387                 if self.symmetrical:
388                     self._add_items(self.target_col_name, self.source_col_name, *objs)
389             add.alters_data = True
390
391             def remove(self, *objs):
392                 self._remove_items(self.source_col_name, self.target_col_name, *objs)
393
394                 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
395                 if self.symmetrical:
396                     self._remove_items(self.target_col_name, self.source_col_name, *objs)
397             remove.alters_data = True
398
399         def clear(self):
400             self._clear_items(self.source_col_name)
401
402             # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table
403             if self.symmetrical:
404                 self._clear_items(self.target_col_name)
405         clear.alters_data = True
406
407         def create(self, **kwargs):
408             # This check needs to be done here, since we can't later remove this
409             # from the method lookup table, as we do with add and remove.
410             if through is not None:
411                 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
412             new_obj = super(ManyRelatedManager, self).create(**kwargs)
413             self.add(new_obj)
414             return new_obj
415         create.alters_data = True
416
417         def get_or_create(self, **kwargs):
418             obj, created = \
419                     super(ManyRelatedManager, self).get_or_create(**kwargs)
420             # We only need to add() if created because if we got an object back
421             # from get() then the relationship already exists.
422             if created:
423                 self.add(obj)
424             return obj, created
425         get_or_create.alters_data = True
426
427         def _add_items(self, source_col_name, target_col_name, *objs):
428             # join_table: name of the m2m link table
429             # source_col_name: the PK colname in join_table for the source object
430             # target_col_name: the PK colname in join_table for the target object
431             # *objs - objects to add. Either object instances, or primary keys of object instances.
432
433             # If there aren't any objects, there is nothing to do.
434             if objs:
435                 # Check that all the objects are of the right type
436                 new_ids = set()
437                 for obj in objs:
438                     if isinstance(obj, self.model):
439                         new_ids.add(obj._get_pk_val())
440                     else:
441                         new_ids.add(obj)
442                 # Add the newly created or already existing objects to the join table.
443                 # First find out which items are already added, to avoid adding them twice
444                 cursor = connection.cursor()
445                 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
446                     (target_col_name, self.join_table, source_col_name,
447                     target_col_name, ",".join(['%s'] * len(new_ids))),
448                     [self._pk_val] + list(new_ids))
449                 existing_ids = set([row[0] for row in cursor.fetchall()])
450
451                 # Add the ones that aren't there already
452                 for obj_id in (new_ids - existing_ids):
453                     cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
454                         (self.join_table, source_col_name, target_col_name),
455                         [self._pk_val, obj_id])
456                 transaction.commit_unless_managed()
457
458         def _remove_items(self, source_col_name, target_col_name, *objs):
459             # source_col_name: the PK colname in join_table for the source object
460             # target_col_name: the PK colname in join_table for the target object
461             # *objs - objects to remove
462
463             # If there aren't any objects, there is nothing to do.
464             if objs:
465                 # Check that all the objects are of the right type
466                 old_ids = set()
467                 for obj in objs:
468                     if isinstance(obj, self.model):
469                         old_ids.add(obj._get_pk_val())
470                     else:
471                         old_ids.add(obj)
472                 # Remove the specified objects from the join table
473                 cursor = connection.cursor()
474                 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
475                     (self.join_table, source_col_name,
476                     target_col_name, ",".join(['%s'] * len(old_ids))),
477                     [self