114 self.set_attributes_from_rel() |
119 self.set_attributes_from_rel() |
115 self.related = RelatedObject(other, cls, self) |
120 self.related = RelatedObject(other, cls, self) |
116 if not cls._meta.abstract: |
121 if not cls._meta.abstract: |
117 self.contribute_to_related_class(other, self.related) |
122 self.contribute_to_related_class(other, self.related) |
118 |
123 |
119 def get_db_prep_lookup(self, lookup_type, value): |
124 def get_prep_lookup(self, lookup_type, value): |
120 # If we are doing a lookup on a Related Field, we must be |
125 if hasattr(value, 'prepare'): |
121 # comparing object instances. The value should be the PK of value, |
126 return value.prepare() |
122 # not value itself. |
127 if hasattr(value, '_prepare'): |
123 def pk_trace(value): |
128 return value._prepare() |
124 # Value may be a primary key, or an object held in a relation. |
129 # FIXME: lt and gt are explicitly allowed to make |
125 # If it is an object, then we need to get the primary key value for |
130 # get_(next/prev)_by_date work; other lookups are not allowed since that |
126 # that object. In certain conditions (especially one-to-one relations), |
131 # gets messy pretty quick. This is a good candidate for some refactoring |
127 # the primary key may itself be an object - so we need to keep drilling |
132 # in the future. |
128 # down until we hit a value that can be used for a comparison. |
133 if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
129 v, field = value, None |
134 return self._pk_trace(value, 'get_prep_lookup', lookup_type) |
130 try: |
135 if lookup_type in ('range', 'in'): |
131 while True: |
136 return [self._pk_trace(v, 'get_prep_lookup', lookup_type) for v in value] |
132 v, field = getattr(v, v._meta.pk.name), v._meta.pk |
137 elif lookup_type == 'isnull': |
133 except AttributeError: |
138 return [] |
134 pass |
139 raise TypeError("Related Field has invalid lookup: %s" % lookup_type) |
135 |
140 |
136 if field: |
141 def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): |
137 if lookup_type in ('range', 'in'): |
142 if not prepared: |
138 v = [v] |
143 value = self.get_prep_lookup(lookup_type, value) |
139 v = field.get_db_prep_lookup(lookup_type, v) |
144 if hasattr(value, 'get_compiler'): |
140 if isinstance(v, list): |
145 value = value.get_compiler(connection=connection) |
141 v = v[0] |
|
142 return v |
|
143 |
|
144 if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'): |
146 if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'): |
145 # If the value has a relabel_aliases method, it will need to |
147 # If the value has a relabel_aliases method, it will need to |
146 # be invoked before the final SQL is evaluated |
148 # be invoked before the final SQL is evaluated |
147 if hasattr(value, 'relabel_aliases'): |
149 if hasattr(value, 'relabel_aliases'): |
148 return value |
150 return value |
149 if hasattr(value, 'as_sql'): |
151 if hasattr(value, 'as_sql'): |
150 sql, params = value.as_sql() |
152 sql, params = value.as_sql() |
151 else: |
153 else: |
152 sql, params = value._as_sql() |
154 sql, params = value._as_sql(connection=connection) |
153 return QueryWrapper(('(%s)' % sql), params) |
155 return QueryWrapper(('(%s)' % sql), params) |
154 |
156 |
155 # FIXME: lt and gt are explicitally allowed to make |
157 # FIXME: lt and gt are explicitly allowed to make |
156 # get_(next/prev)_by_date work; other lookups are not allowed since that |
158 # get_(next/prev)_by_date work; other lookups are not allowed since that |
157 # gets messy pretty quick. This is a good candidate for some refactoring |
159 # gets messy pretty quick. This is a good candidate for some refactoring |
158 # in the future. |
160 # in the future. |
159 if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
161 if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: |
160 return [pk_trace(value)] |
162 return [self._pk_trace(value, 'get_db_prep_lookup', lookup_type, |
|
163 connection=connection, prepared=prepared)] |
161 if lookup_type in ('range', 'in'): |
164 if lookup_type in ('range', 'in'): |
162 return [pk_trace(v) for v in value] |
165 return [self._pk_trace(v, 'get_db_prep_lookup', lookup_type, |
|
166 connection=connection, prepared=prepared) |
|
167 for v in value] |
163 elif lookup_type == 'isnull': |
168 elif lookup_type == 'isnull': |
164 return [] |
169 return [] |
165 raise TypeError, "Related Field has invalid lookup: %s" % lookup_type |
170 raise TypeError("Related Field has invalid lookup: %s" % lookup_type) |
166 |
171 |
167 def _get_related_query_name(self, opts): |
172 def _pk_trace(self, value, prep_func, lookup_type, **kwargs): |
|
173 # Value may be a primary key, or an object held in a relation. |
|
174 # If it is an object, then we need to get the primary key value for |
|
175 # that object. In certain conditions (especially one-to-one relations), |
|
176 # the primary key may itself be an object - so we need to keep drilling |
|
177 # down until we hit a value that can be used for a comparison. |
|
178 v = value |
|
179 try: |
|
180 while True: |
|
181 v = getattr(v, v._meta.pk.name) |
|
182 except AttributeError: |
|
183 pass |
|
184 except exceptions.ObjectDoesNotExist: |
|
185 v = None |
|
186 |
|
187 field = self |
|
188 while field.rel: |
|
189 if hasattr(field.rel, 'field_name'): |
|
190 field = field.rel.to._meta.get_field(field.rel.field_name) |
|
191 else: |
|
192 field = field.rel.to._meta.pk |
|
193 |
|
194 if lookup_type in ('range', 'in'): |
|
195 v = [v] |
|
196 v = getattr(field, prep_func)(lookup_type, v, **kwargs) |
|
197 if isinstance(v, list): |
|
198 v = v[0] |
|
199 return v |
|
200 |
|
201 def related_query_name(self): |
168 # This method defines the name that can be used to identify this |
202 # This method defines the name that can be used to identify this |
169 # related object in a table-spanning query. It uses the lower-cased |
203 # related object in a table-spanning query. It uses the lower-cased |
170 # object_name by default, but this can be overridden with the |
204 # object_name by default, but this can be overridden with the |
171 # "related_name" option. |
205 # "related_name" option. |
172 return self.rel.related_name or opts.object_name.lower() |
206 return self.rel.related_name or self.opts.object_name.lower() |
173 |
207 |
174 class SingleRelatedObjectDescriptor(object): |
208 class SingleRelatedObjectDescriptor(object): |
175 # This class provides the functionality that makes the related-object |
209 # This class provides the functionality that makes the related-object |
176 # managers available as attributes on a model class, for fields that have |
210 # managers available as attributes on a model class, for fields that have |
177 # a single "remote" value, on the class pointed to by a related field. |
211 # a single "remote" value, on the class pointed to by a related field. |
178 # In the example "place.restaurant", the restaurant attribute is a |
212 # In the example "place.restaurant", the restaurant attribute is a |
179 # SingleRelatedObjectDescriptor instance. |
213 # SingleRelatedObjectDescriptor instance. |
180 def __init__(self, related): |
214 def __init__(self, related): |
181 self.related = related |
215 self.related = related |
182 self.cache_name = '_%s_cache' % related.get_accessor_name() |
216 self.cache_name = related.get_cache_name() |
183 |
217 |
184 def __get__(self, instance, instance_type=None): |
218 def __get__(self, instance, instance_type=None): |
185 if instance is None: |
219 if instance is None: |
186 return self |
220 return self |
187 try: |
221 try: |
188 return getattr(instance, self.cache_name) |
222 return getattr(instance, self.cache_name) |
189 except AttributeError: |
223 except AttributeError: |
190 params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} |
224 params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} |
191 rel_obj = self.related.model._base_manager.get(**params) |
225 db = router.db_for_read(self.related.model, instance=instance) |
|
226 rel_obj = self.related.model._base_manager.using(db).get(**params) |
192 setattr(instance, self.cache_name, rel_obj) |
227 setattr(instance, self.cache_name, rel_obj) |
193 return rel_obj |
228 return rel_obj |
194 |
229 |
195 def __set__(self, instance, value): |
230 def __set__(self, instance, value): |
196 if instance is None: |
231 if instance is None: |
197 raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name |
232 raise AttributeError("%s must be accessed via instance" % self.related.opts.object_name) |
198 |
233 |
199 # The similarity of the code below to the code in |
234 # The similarity of the code below to the code in |
200 # ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch |
235 # ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch |
201 # of small differences that would make a common base class convoluted. |
236 # of small differences that would make a common base class convoluted. |
202 |
237 |
249 params = {'%s__exact' % self.field.rel.field_name: val} |
293 params = {'%s__exact' % self.field.rel.field_name: val} |
250 |
294 |
251 # If the related manager indicates that it should be used for |
295 # If the related manager indicates that it should be used for |
252 # related fields, respect that. |
296 # related fields, respect that. |
253 rel_mgr = self.field.rel.to._default_manager |
297 rel_mgr = self.field.rel.to._default_manager |
|
298 db = router.db_for_read(self.field.rel.to, instance=instance) |
254 if getattr(rel_mgr, 'use_for_related_fields', False): |
299 if getattr(rel_mgr, 'use_for_related_fields', False): |
255 rel_obj = rel_mgr.get(**params) |
300 rel_obj = rel_mgr.using(db).get(**params) |
256 else: |
301 else: |
257 rel_obj = QuerySet(self.field.rel.to).get(**params) |
302 rel_obj = QuerySet(self.field.rel.to).using(db).get(**params) |
258 setattr(instance, cache_name, rel_obj) |
303 setattr(instance, cache_name, rel_obj) |
259 return rel_obj |
304 return rel_obj |
260 |
305 |
261 def __set__(self, instance, value): |
306 def __set__(self, instance, value): |
262 if instance is None: |
307 if instance is None: |
263 raise AttributeError, "%s must be accessed via instance" % self._field.name |
308 raise AttributeError("%s must be accessed via instance" % self._field.name) |
264 |
309 |
265 # If null=True, we can assign null here, but otherwise the value needs |
310 # If null=True, we can assign null here, but otherwise the value needs |
266 # to be an instance of the related class. |
311 # to be an instance of the related class. |
267 if value is None and self.field.null == False: |
312 if value is None and self.field.null == False: |
268 raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % |
313 raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % |
269 (instance._meta.object_name, self.field.name)) |
314 (instance._meta.object_name, self.field.name)) |
270 elif value is not None and not isinstance(value, self.field.rel.to): |
315 elif value is not None and not isinstance(value, self.field.rel.to): |
271 raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % |
316 raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % |
272 (value, instance._meta.object_name, |
317 (value, instance._meta.object_name, |
273 self.field.name, self.field.rel.to._meta.object_name)) |
318 self.field.name, self.field.rel.to._meta.object_name)) |
|
319 elif value is not None: |
|
320 if instance._state.db is None: |
|
321 instance._state.db = router.db_for_write(instance.__class__, instance=value) |
|
322 elif value._state.db is None: |
|
323 value._state.db = router.db_for_write(value.__class__, instance=instance) |
|
324 elif value._state.db is not None and instance._state.db is not None: |
|
325 if not router.allow_relation(value, instance): |
|
326 raise ValueError('Cannot assign "%r": instance is on database "%s", value is on database "%s"' % |
|
327 (value, instance._state.db, value._state.db)) |
274 |
328 |
275 # If we're setting the value of a OneToOneField to None, we need to clear |
329 # If we're setting the value of a OneToOneField to None, we need to clear |
276 # out the cache on any old related object. Otherwise, deleting the |
330 # out the cache on any old related object. Otherwise, deleting the |
277 # previously-related object will also cause this object to be deleted, |
331 # previously-related object will also cause this object to be deleted, |
278 # which is wrong. |
332 # which is wrong. |
399 getattr(instance, attname)} |
456 getattr(instance, attname)} |
400 manager.model = self.related.model |
457 manager.model = self.related.model |
401 |
458 |
402 return manager |
459 return manager |
403 |
460 |
404 def create_many_related_manager(superclass, through=False): |
461 def create_many_related_manager(superclass, rel=False): |
405 """Creates a manager that subclasses 'superclass' (which is a Manager) |
462 """Creates a manager that subclasses 'superclass' (which is a Manager) |
406 and adds behavior for many-to-many related objects.""" |
463 and adds behavior for many-to-many related objects.""" |
|
464 through = rel.through |
407 class ManyRelatedManager(superclass): |
465 class ManyRelatedManager(superclass): |
408 def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, |
466 def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, |
409 join_table=None, source_col_name=None, target_col_name=None): |
467 join_table=None, source_field_name=None, target_field_name=None, |
|
468 reverse=False): |
410 super(ManyRelatedManager, self).__init__() |
469 super(ManyRelatedManager, self).__init__() |
411 self.core_filters = core_filters |
470 self.core_filters = core_filters |
412 self.model = model |
471 self.model = model |
413 self.symmetrical = symmetrical |
472 self.symmetrical = symmetrical |
414 self.instance = instance |
473 self.instance = instance |
415 self.join_table = join_table |
474 self.source_field_name = source_field_name |
416 self.source_col_name = source_col_name |
475 self.target_field_name = target_field_name |
417 self.target_col_name = target_col_name |
|
418 self.through = through |
476 self.through = through |
419 self._pk_val = self.instance._get_pk_val() |
477 self._pk_val = self.instance.pk |
|
478 self.reverse = reverse |
420 if self._pk_val is None: |
479 if self._pk_val is None: |
421 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) |
480 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) |
422 |
481 |
423 def get_query_set(self): |
482 def get_query_set(self): |
424 return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters)) |
483 db = self._db or router.db_for_read(self.instance.__class__, instance=self.instance) |
|
484 return superclass.get_query_set(self).using(db)._next_is_sticky().filter(**(self.core_filters)) |
425 |
485 |
426 # If the ManyToMany relation has an intermediary model, |
486 # If the ManyToMany relation has an intermediary model, |
427 # the add and remove methods do not exist. |
487 # the add and remove methods do not exist. |
428 if through is None: |
488 if rel.through._meta.auto_created: |
429 def add(self, *objs): |
489 def add(self, *objs): |
430 self._add_items(self.source_col_name, self.target_col_name, *objs) |
490 self._add_items(self.source_field_name, self.target_field_name, *objs) |
431 |
491 |
432 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table |
492 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table |
433 if self.symmetrical: |
493 if self.symmetrical: |
434 self._add_items(self.target_col_name, self.source_col_name, *objs) |
494 self._add_items(self.target_field_name, self.source_field_name, *objs) |
435 add.alters_data = True |
495 add.alters_data = True |
436 |
496 |
437 def remove(self, *objs): |
497 def remove(self, *objs): |
438 self._remove_items(self.source_col_name, self.target_col_name, *objs) |
498 self._remove_items(self.source_field_name, self.target_field_name, *objs) |
439 |
499 |
440 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table |
500 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table |
441 if self.symmetrical: |
501 if self.symmetrical: |
442 self._remove_items(self.target_col_name, self.source_col_name, *objs) |
502 self._remove_items(self.target_field_name, self.source_field_name, *objs) |
443 remove.alters_data = True |
503 remove.alters_data = True |
444 |
504 |
445 def clear(self): |
505 def clear(self): |
446 self._clear_items(self.source_col_name) |
506 self._clear_items(self.source_field_name) |
447 |
507 |
448 # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table |
508 # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table |
449 if self.symmetrical: |
509 if self.symmetrical: |
450 self._clear_items(self.target_col_name) |
510 self._clear_items(self.target_field_name) |
451 clear.alters_data = True |
511 clear.alters_data = True |
452 |
512 |
453 def create(self, **kwargs): |
513 def create(self, **kwargs): |
454 # This check needs to be done here, since we can't later remove this |
514 # This check needs to be done here, since we can't later remove this |
455 # from the method lookup table, as we do with add and remove. |
515 # from the method lookup table, as we do with add and remove. |
456 if through is not None: |
516 if not rel.through._meta.auto_created: |
457 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through |
517 opts = through._meta |
458 new_obj = super(ManyRelatedManager, self).create(**kwargs) |
518 raise AttributeError("Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)) |
|
519 db = router.db_for_write(self.instance.__class__, instance=self.instance) |
|
520 new_obj = super(ManyRelatedManager, self).using(db).create(**kwargs) |
459 self.add(new_obj) |
521 self.add(new_obj) |
460 return new_obj |
522 return new_obj |
461 create.alters_data = True |
523 create.alters_data = True |
462 |
524 |
463 def get_or_create(self, **kwargs): |
525 def get_or_create(self, **kwargs): |
|
526 db = router.db_for_write(self.instance.__class__, instance=self.instance) |
464 obj, created = \ |
527 obj, created = \ |
465 super(ManyRelatedManager, self).get_or_create(**kwargs) |
528 super(ManyRelatedManager, self).using(db).get_or_create(**kwargs) |
466 # We only need to add() if created because if we got an object back |
529 # We only need to add() if created because if we got an object back |
467 # from get() then the relationship already exists. |
530 # from get() then the relationship already exists. |
468 if created: |
531 if created: |
469 self.add(obj) |
532 self.add(obj) |
470 return obj, created |
533 return obj, created |
471 get_or_create.alters_data = True |
534 get_or_create.alters_data = True |
472 |
535 |
473 def _add_items(self, source_col_name, target_col_name, *objs): |
536 def _add_items(self, source_field_name, target_field_name, *objs): |
474 # join_table: name of the m2m link table |
537 # join_table: name of the m2m link table |
475 # source_col_name: the PK colname in join_table for the source object |
538 # source_field_name: the PK fieldname in join_table for the source object |
476 # target_col_name: the PK colname in join_table for the target object |
539 # target_field_name: the PK fieldname in join_table for the target object |
477 # *objs - objects to add. Either object instances, or primary keys of object instances. |
540 # *objs - objects to add. Either object instances, or primary keys of object instances. |
478 |
541 |
479 # If there aren't any objects, there is nothing to do. |
542 # If there aren't any objects, there is nothing to do. |
|
543 from django.db.models import Model |
480 if objs: |
544 if objs: |
481 from django.db.models.base import Model |
|
482 # Check that all the objects are of the right type |
|
483 new_ids = set() |
545 new_ids = set() |
484 for obj in objs: |
546 for obj in objs: |
485 if isinstance(obj, self.model): |
547 if isinstance(obj, self.model): |
486 new_ids.add(obj._get_pk_val()) |
548 if not router.allow_relation(obj, self.instance): |
|
549 raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' % |
|
550 (obj, self.instance._state.db, obj._state.db)) |
|
551 new_ids.add(obj.pk) |
487 elif isinstance(obj, Model): |
552 elif isinstance(obj, Model): |
488 raise TypeError, "'%s' instance expected" % self.model._meta.object_name |
553 raise TypeError("'%s' instance expected" % self.model._meta.object_name) |
489 else: |
554 else: |
490 new_ids.add(obj) |
555 new_ids.add(obj) |
491 # Add the newly created or already existing objects to the join table. |
556 db = router.db_for_write(self.through.__class__, instance=self.instance) |
492 # First find out which items are already added, to avoid adding them twice |
557 vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True) |
493 cursor = connection.cursor() |
558 vals = vals.filter(**{ |
494 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ |
559 source_field_name: self._pk_val, |
495 (target_col_name, self.join_table, source_col_name, |
560 '%s__in' % target_field_name: new_ids, |
496 target_col_name, ",".join(['%s'] * len(new_ids))), |
561 }) |
497 [self._pk_val] + list(new_ids)) |
562 new_ids = new_ids - set(vals) |
498 existing_ids = set([row[0] for row in cursor.fetchall()]) |
563 |
499 |
564 if self.reverse or source_field_name == self.source_field_name: |
|
565 # Don't send the signal when we are inserting the |
|
566 # duplicate data row for symmetrical reverse entries. |
|
567 signals.m2m_changed.send(sender=rel.through, action='pre_add', |
|
568 instance=self.instance, reverse=self.reverse, |
|
569 model=self.model, pk_set=new_ids) |
500 # Add the ones that aren't there already |
570 # Add the ones that aren't there already |
501 for obj_id in (new_ids - existing_ids): |
571 for obj_id in new_ids: |
502 cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ |
572 self.through._default_manager.using(db).create(**{ |
503 (self.join_table, source_col_name, target_col_name), |
573 '%s_id' % source_field_name: self._pk_val, |
504 [self._pk_val, obj_id]) |
574 '%s_id' % target_field_name: obj_id, |
505 transaction.commit_unless_managed() |
575 }) |
506 |
576 if self.reverse or source_field_name == self.source_field_name: |
507 def _remove_items(self, source_col_name, target_col_name, *objs): |
577 # Don't send the signal when we are inserting the |
|
578 # duplicate data row for symmetrical reverse entries. |
|
579 signals.m2m_changed.send(sender=rel.through, action='post_add', |
|
580 instance=self.instance, reverse=self.reverse, |
|
581 model=self.model, pk_set=new_ids) |
|
582 |
|
583 def _remove_items(self, source_field_name, target_field_name, *objs): |
508 # source_col_name: the PK colname in join_table for the source object |
584 # source_col_name: the PK colname in join_table for the source object |
509 # target_col_name: the PK colname in join_table for the target object |
585 # target_col_name: the PK colname in join_table for the target object |
510 # *objs - objects to remove |
586 # *objs - objects to remove |
511 |
587 |
512 # If there aren't any objects, there is nothing to do. |
588 # If there aren't any objects, there is nothing to do. |
513 if objs: |
589 if objs: |
514 # Check that all the objects are of the right type |
590 # Check that all the objects are of the right type |
515 old_ids = set() |
591 old_ids = set() |
516 for obj in objs: |
592 for obj in objs: |
517 if isinstance(obj, self.model): |
593 if isinstance(obj, self.model): |
518 old_ids.add(obj._get_pk_val()) |
594 old_ids.add(obj.pk) |
519 else: |
595 else: |
520 old_ids.add(obj) |
596 old_ids.add(obj) |
|
597 if self.reverse or source_field_name == self.source_field_name: |
|
598 # Don't send the signal when we are deleting the |
|
599 # duplicate data row for symmetrical reverse entries. |
|
600 signals.m2m_changed.send(sender=rel.through, action="pre_remove", |
|
601 instance=self.instance, reverse=self.reverse, |
|
602 model=self.model, pk_set=old_ids) |
521 # Remove the specified objects from the join table |
603 # Remove the specified objects from the join table |
522 cursor = connection.cursor() |
604 db = router.db_for_write(self.through.__class__, instance=self.instance) |
523 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ |
605 self.through._default_manager.using(db).filter(**{ |
524 (self.join_table, source_col_name, |
606 source_field_name: self._pk_val, |
525 target_col_name, ",".join(['%s'] * len(old_ids))), |
607 '%s__in' % target_field_name: old_ids |
526 [self._pk_val] + list(old_ids)) |
608 }).delete() |
527 transaction.commit_unless_managed() |
609 if self.reverse or source_field_name == self.source_field_name: |
528 |
610 # Don't send the signal when we are deleting the |
529 def _clear_items(self, source_col_name): |
611 # duplicate data row for symmetrical reverse entries. |
|
612 signals.m2m_changed.send(sender=rel.through, action="post_remove", |
|
613 instance=self.instance, reverse=self.reverse, |
|
614 model=self.model, pk_set=old_ids) |
|
615 |
|
616 def _clear_items(self, source_field_name): |
530 # source_col_name: the PK colname in join_table for the source object |
617 # source_col_name: the PK colname in join_table for the source object |
531 cursor = connection.cursor() |
618 if self.reverse or source_field_name == self.source_field_name: |
532 cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ |
619 # Don't send the signal when we are clearing the |
533 (self.join_table, source_col_name), |
620 # duplicate data rows for symmetrical reverse entries. |
534 [self._pk_val]) |
621 signals.m2m_changed.send(sender=rel.through, action="pre_clear", |
535 transaction.commit_unless_managed() |
622 instance=self.instance, reverse=self.reverse, |
|
623 model=self.model, pk_set=None) |
|
624 db = router.db_for_write(self.through.__class__, instance=self.instance) |
|
625 self.through._default_manager.using(db).filter(**{ |
|
626 source_field_name: self._pk_val |
|
627 }).delete() |
|
628 if self.reverse or source_field_name == self.source_field_name: |
|
629 # Don't send the signal when we are clearing the |
|
630 # duplicate data rows for symmetrical reverse entries. |
|
631 signals.m2m_changed.send(sender=rel.through, action="post_clear", |
|
632 instance=self.instance, reverse=self.reverse, |
|
633 model=self.model, pk_set=None) |
536 |
634 |
537 return ManyRelatedManager |
635 return ManyRelatedManager |
538 |
636 |
539 class ManyRelatedObjectsDescriptor(object): |
637 class ManyRelatedObjectsDescriptor(object): |
540 # This class provides the functionality that makes the related-object |
638 # This class provides the functionality that makes the related-object |
589 # In the example "article.publications", the publications attribute is a |
687 # In the example "article.publications", the publications attribute is a |
590 # ReverseManyRelatedObjectsDescriptor instance. |
688 # ReverseManyRelatedObjectsDescriptor instance. |
591 def __init__(self, m2m_field): |
689 def __init__(self, m2m_field): |
592 self.field = m2m_field |
690 self.field = m2m_field |
593 |
691 |
|
692 def _through(self): |
|
693 # through is provided so that you have easy access to the through |
|
694 # model (Book.authors.through) for inlines, etc. This is done as |
|
695 # a property to ensure that the fully resolved value is returned. |
|
696 return self.field.rel.through |
|
697 through = property(_through) |
|
698 |
594 def __get__(self, instance, instance_type=None): |
699 def __get__(self, instance, instance_type=None): |
595 if instance is None: |
700 if instance is None: |
596 return self |
701 return self |
597 |
702 |
598 # Dynamically create a class that subclasses the related |
703 # Dynamically create a class that subclasses the related |
599 # model's default manager. |
704 # model's default manager. |
600 rel_model=self.field.rel.to |
705 rel_model=self.field.rel.to |
601 superclass = rel_model._default_manager.__class__ |
706 superclass = rel_model._default_manager.__class__ |
602 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) |
707 RelatedManager = create_many_related_manager(superclass, self.field.rel) |
603 |
708 |
604 qn = connection.ops.quote_name |
|
605 manager = RelatedManager( |
709 manager = RelatedManager( |
606 model=rel_model, |
710 model=rel_model, |
607 core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, |
711 core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, |
608 instance=instance, |
712 instance=instance, |
609 symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)), |
713 symmetrical=self.field.rel.symmetrical, |
610 join_table=qn(self.field.m2m_db_table()), |
714 source_field_name=self.field.m2m_field_name(), |
611 source_col_name=qn(self.field.m2m_column_name()), |
715 target_field_name=self.field.m2m_reverse_field_name(), |
612 target_col_name=qn(self.field.m2m_reverse_name()) |
716 reverse=False |
613 ) |
717 ) |
614 |
718 |
615 return manager |
719 return manager |
616 |
720 |
617 def __set__(self, instance, value): |
721 def __set__(self, instance, value): |
618 if instance is None: |
722 if instance is None: |
619 raise AttributeError, "Manager must be accessed via instance" |
723 raise AttributeError("Manager must be accessed via instance") |
620 |
724 |
621 through = getattr(self.field.rel, 'through', None) |
725 if not self.field.rel.through._meta.auto_created: |
622 if through is not None: |
726 opts = self.field.rel.through._meta |
623 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through |
727 raise AttributeError("Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)) |
624 |
728 |
625 manager = self.__get__(instance) |
729 manager = self.__get__(instance) |
626 manager.clear() |
730 manager.clear() |
627 manager.add(*value) |
731 manager.add(*value) |
628 |
732 |
671 self.limit_choices_to = limit_choices_to |
779 self.limit_choices_to = limit_choices_to |
672 self.symmetrical = symmetrical |
780 self.symmetrical = symmetrical |
673 self.multiple = True |
781 self.multiple = True |
674 self.through = through |
782 self.through = through |
675 |
783 |
|
784 def is_hidden(self): |
|
785 "Should the related object be hidden?" |
|
786 return self.related_name and self.related_name[-1] == '+' |
|
787 |
676 def get_related_field(self): |
788 def get_related_field(self): |
677 """ |
789 """ |
678 Returns the field in the to' object to which this relationship is tied |
790 Returns the field in the to' object to which this relationship is tied |
679 (this is always the primary key on the target model). Provided for |
791 (this is always the primary key on the target model). Provided for |
680 symmetry with ManyToOneRel. |
792 symmetry with ManyToOneRel. |
681 """ |
793 """ |
682 return self.to._meta.pk |
794 return self.to._meta.pk |
683 |
795 |
684 class ForeignKey(RelatedField, Field): |
796 class ForeignKey(RelatedField, Field): |
685 empty_strings_allowed = False |
797 empty_strings_allowed = False |
|
798 default_error_messages = { |
|
799 'invalid': _('Model %(model)s with pk %(pk)r does not exist.') |
|
800 } |
|
801 description = _("Foreign Key (type determined by related field)") |
686 def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): |
802 def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): |
687 try: |
803 try: |
688 to_name = to._meta.object_name.lower() |
804 to_name = to._meta.object_name.lower() |
689 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT |
805 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT |
690 assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) |
806 assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) |
691 else: |
807 else: |
692 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) |
808 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) |
693 to_field = to_field or to._meta.pk.name |
809 # For backwards compatibility purposes, we need to *try* and set |
|
810 # the to_field during FK construction. It won't be guaranteed to |
|
811 # be correct until contribute_to_class is called. Refs #12190. |
|
812 to_field = to_field or (to._meta.pk and to._meta.pk.name) |
694 kwargs['verbose_name'] = kwargs.get('verbose_name', None) |
813 kwargs['verbose_name'] = kwargs.get('verbose_name', None) |
695 |
814 |
696 kwargs['rel'] = rel_class(to, to_field, |
815 kwargs['rel'] = rel_class(to, to_field, |
697 related_name=kwargs.pop('related_name', None), |
816 related_name=kwargs.pop('related_name', None), |
698 limit_choices_to=kwargs.pop('limit_choices_to', None), |
817 limit_choices_to=kwargs.pop('limit_choices_to', None), |
788 def formfield(self, **kwargs): |
927 def formfield(self, **kwargs): |
789 if self.rel.parent_link: |
928 if self.rel.parent_link: |
790 return None |
929 return None |
791 return super(OneToOneField, self).formfield(**kwargs) |
930 return super(OneToOneField, self).formfield(**kwargs) |
792 |
931 |
|
932 def save_form_data(self, instance, data): |
|
933 if isinstance(data, self.rel.to): |
|
934 setattr(instance, self.name, data) |
|
935 else: |
|
936 setattr(instance, self.attname, data) |
|
937 |
|
938 def create_many_to_many_intermediary_model(field, klass): |
|
939 from django.db import models |
|
940 managed = True |
|
941 if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: |
|
942 to_model = field.rel.to |
|
943 to = to_model.split('.')[-1] |
|
944 def set_managed(field, model, cls): |
|
945 field.rel.through._meta.managed = model._meta.managed or cls._meta.managed |
|
946 add_lazy_relation(klass, field, to_model, set_managed) |
|
947 elif isinstance(field.rel.to, basestring): |
|
948 to = klass._meta.object_name |
|
949 to_model = klass |
|
950 managed = klass._meta.managed |
|
951 else: |
|
952 to = field.rel.to._meta.object_name |
|
953 to_model = field.rel.to |
|
954 managed = klass._meta.managed or to_model._meta.managed |
|
955 name = '%s_%s' % (klass._meta.object_name, field.name) |
|
956 if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: |
|
957 from_ = 'from_%s' % to.lower() |
|
958 to = 'to_%s' % to.lower() |
|
959 else: |
|
960 from_ = klass._meta.object_name.lower() |
|
961 to = to.lower() |
|
962 meta = type('Meta', (object,), { |
|
963 'db_table': field._get_m2m_db_table(klass._meta), |
|
964 'managed': managed, |
|
965 'auto_created': klass, |
|
966 'app_label': klass._meta.app_label, |
|
967 'unique_together': (from_, to), |
|
968 'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to}, |
|
969 'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to}, |
|
970 }) |
|
971 # Construct and return the new class. |
|
972 return type(name, (models.Model,), { |
|
973 'Meta': meta, |
|
974 '__module__': klass.__module__, |
|
975 from_: models.ForeignKey(klass, related_name='%s+' % name), |
|
976 to: models.ForeignKey(to_model, related_name='%s+' % name) |
|
977 }) |
|
978 |
793 class ManyToManyField(RelatedField, Field): |
979 class ManyToManyField(RelatedField, Field): |
|
980 description = _("Many-to-many relationship") |
794 def __init__(self, to, **kwargs): |
981 def __init__(self, to, **kwargs): |
795 try: |
982 try: |
796 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) |
983 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) |
797 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT |
984 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT |
798 assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) |
985 assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) |
799 |
986 |
800 kwargs['verbose_name'] = kwargs.get('verbose_name', None) |
987 kwargs['verbose_name'] = kwargs.get('verbose_name', None) |
801 kwargs['rel'] = ManyToManyRel(to, |
988 kwargs['rel'] = ManyToManyRel(to, |
802 related_name=kwargs.pop('related_name', None), |
989 related_name=kwargs.pop('related_name', None), |
803 limit_choices_to=kwargs.pop('limit_choices_to', None), |
990 limit_choices_to=kwargs.pop('limit_choices_to', None), |
804 symmetrical=kwargs.pop('symmetrical', True), |
991 symmetrical=kwargs.pop('symmetrical', to==RECURSIVE_RELATIONSHIP_CONSTANT), |
805 through=kwargs.pop('through', None)) |
992 through=kwargs.pop('through', None)) |
806 |
993 |
807 self.db_table = kwargs.pop('db_table', None) |
994 self.db_table = kwargs.pop('db_table', None) |
808 if kwargs['rel'].through is not None: |
995 if kwargs['rel'].through is not None: |
809 self.creates_table = False |
|
810 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." |
996 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." |
811 else: |
|
812 self.creates_table = True |
|
813 |
997 |
814 Field.__init__(self, **kwargs) |
998 Field.__init__(self, **kwargs) |
815 |
999 |
816 msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') |
1000 msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') |
817 self.help_text = string_concat(self.help_text, ' ', msg) |
1001 self.help_text = string_concat(self.help_text, ' ', msg) |
818 |
1002 |
819 def get_choices_default(self): |
1003 def get_choices_default(self): |
820 return Field.get_choices(self, include_blank=False) |
1004 return Field.get_choices(self, include_blank=False) |
821 |
1005 |
822 def _get_m2m_db_table(self, opts): |
1006 def _get_m2m_db_table(self, opts): |
823 "Function that can be curried to provide the m2m table name for this relation" |
1007 "Function that can be curried to provide the m2m table name for this relation" |
824 if self.rel.through is not None: |
1008 if self.rel.through is not None: |
825 return self.rel.through_model._meta.db_table |
1009 return self.rel.through._meta.db_table |
826 elif self.db_table: |
1010 elif self.db_table: |
827 return self.db_table |
1011 return self.db_table |
828 else: |
1012 else: |
829 return util.truncate_name('%s_%s' % (opts.db_table, self.name), |
1013 return util.truncate_name('%s_%s' % (opts.db_table, self.name), |
830 connection.ops.max_name_length()) |
1014 connection.ops.max_name_length()) |
831 |
1015 |
832 def _get_m2m_column_name(self, related): |
1016 def _get_m2m_attr(self, related, attr): |
833 "Function that can be curried to provide the source column name for the m2m table" |
1017 "Function that can be curried to provide the source accessor or DB column name for the m2m table" |
834 try: |
1018 cache_attr = '_m2m_%s_cache' % attr |
835 return self._m2m_column_name_cache |
1019 if hasattr(self, cache_attr): |
836 except: |
1020 return getattr(self, cache_attr) |
837 if self.rel.through is not None: |
1021 for f in self.rel.through._meta.fields: |
838 for f in self.rel.through_model._meta.fields: |
1022 if hasattr(f,'rel') and f.rel and f.rel.to == related.model: |
839 if hasattr(f,'rel') and f.rel and f.rel.to == related.model: |
1023 setattr(self, cache_attr, getattr(f, attr)) |
840 self._m2m_column_name_cache = f.column |
1024 return getattr(self, cache_attr) |
|
1025 |
|
1026 def _get_m2m_reverse_attr(self, related, attr): |
|
1027 "Function that can be curried to provide the related accessor or DB column name for the m2m table" |
|
1028 cache_attr = '_m2m_reverse_%s_cache' % attr |
|
1029 if hasattr(self, cache_attr): |
|
1030 return getattr(self, cache_attr) |
|
1031 found = False |
|
1032 for f in self.rel.through._meta.fields: |
|
1033 if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: |
|
1034 if related.model == related.parent_model: |
|
1035 # If this is an m2m-intermediate to self, |
|
1036 # the first foreign key you find will be |
|
1037 # the source column. Keep searching for |
|
1038 # the second foreign key. |
|
1039 if found: |
|
1040 setattr(self, cache_attr, getattr(f, attr)) |
841 break |
1041 break |
842 # If this is an m2m relation to self, avoid the inevitable name clash |
1042 else: |
843 elif related.model == related.parent_model: |
1043 found = True |
844 self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' |
1044 else: |
845 else: |
1045 setattr(self, cache_attr, getattr(f, attr)) |
846 self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' |
1046 break |
847 |
1047 return getattr(self, cache_attr) |
848 # Return the newly cached value |
|
849 return self._m2m_column_name_cache |
|
850 |
|
851 def _get_m2m_reverse_name(self, related): |
|
852 "Function that can be curried to provide the related column name for the m2m table" |
|
853 try: |
|
854 return self._m2m_reverse_name_cache |
|
855 except: |
|
856 if self.rel.through is not None: |
|
857 found = False |
|
858 for f in self.rel.through_model._meta.fields: |
|
859 if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: |
|
860 if related.model == related.parent_model: |
|
861 # If this is an m2m-intermediate to self, |
|
862 # the first foreign key you find will be |
|
863 # the source column. Keep searching for |
|
864 # the second foreign key. |
|
865 if found: |
|
866 self._m2m_reverse_name_cache = f.column |
|
867 break |
|
868 else: |
|
869 found = True |
|
870 else: |
|
871 self._m2m_reverse_name_cache = f.column |
|
872 break |
|
873 # If this is an m2m relation to self, avoid the inevitable name clash |
|
874 elif related.model == related.parent_model: |
|
875 self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' |
|
876 else: |
|
877 self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' |
|
878 |
|
879 # Return the newly cached value |
|
880 return self._m2m_reverse_name_cache |
|
881 |
1048 |
882 def isValidIDList(self, field_data, all_data): |
1049 def isValidIDList(self, field_data, all_data): |
883 "Validates that the value is a valid list of foreign keys" |
1050 "Validates that the value is a valid list of foreign keys" |
884 mod = self.rel.to |
1051 mod = self.rel.to |
885 try: |
1052 try: |
917 # related name on symmetrical relations for internal reasons. The |
1084 # related name on symmetrical relations for internal reasons. The |
918 # concept doesn't make a lot of sense externally ("you want me to |
1085 # concept doesn't make a lot of sense externally ("you want me to |
919 # specify *what* on my non-reversible relation?!"), so we set it up |
1086 # specify *what* on my non-reversible relation?!"), so we set it up |
920 # automatically. The funky name reduces the chance of an accidental |
1087 # automatically. The funky name reduces the chance of an accidental |
921 # clash. |
1088 # clash. |
922 if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None: |
1089 if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): |
923 self.rel.related_name = "%s_rel_+" % name |
1090 self.rel.related_name = "%s_rel_+" % name |
924 |
1091 |
925 super(ManyToManyField, self).contribute_to_class(cls, name) |
1092 super(ManyToManyField, self).contribute_to_class(cls, name) |
|
1093 |
|
1094 # The intermediate m2m model is not auto created if: |
|
1095 # 1) There is a manually specified intermediate, or |
|
1096 # 2) The class owning the m2m field is abstract. |
|
1097 if not self.rel.through and not cls._meta.abstract: |
|
1098 self.rel.through = create_many_to_many_intermediary_model(self, cls) |
|
1099 |
926 # Add the descriptor for the m2m relation |
1100 # Add the descriptor for the m2m relation |
927 setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) |
1101 setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) |
928 |
1102 |
929 # Set up the accessor for the m2m table name for the relation |
1103 # Set up the accessor for the m2m table name for the relation |
930 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) |
1104 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) |
931 |
1105 |
932 # Populate some necessary rel arguments so that cross-app relations |
1106 # Populate some necessary rel arguments so that cross-app relations |
933 # work correctly. |
1107 # work correctly. |
934 if isinstance(self.rel.through, basestring): |
1108 if isinstance(self.rel.through, basestring): |
935 def resolve_through_model(field, model, cls): |
1109 def resolve_through_model(field, model, cls): |
936 field.rel.through_model = model |
1110 field.rel.through = model |
937 add_lazy_relation(cls, self, self.rel.through, resolve_through_model) |
1111 add_lazy_relation(cls, self, self.rel.through, resolve_through_model) |
938 elif self.rel.through: |
|
939 self.rel.through_model = self.rel.through |
|
940 self.rel.through = self.rel.through._meta.object_name |
|
941 |
1112 |
942 if isinstance(self.rel.to, basestring): |
1113 if isinstance(self.rel.to, basestring): |
943 target = self.rel.to |
1114 target = self.rel.to |
944 else: |
1115 else: |
945 target = self.rel.to._meta.db_table |
1116 target = self.rel.to._meta.db_table |
946 cls._meta.duplicate_targets[self.column] = (target, "m2m") |
1117 cls._meta.duplicate_targets[self.column] = (target, "m2m") |
947 |
1118 |
948 def contribute_to_related_class(self, cls, related): |
1119 def contribute_to_related_class(self, cls, related): |
949 # m2m relations to self do not have a ManyRelatedObjectsDescriptor, |
1120 # Internal M2Ms (i.e., those with a related name ending with '+') |
950 # as it would be redundant - unless the field is non-symmetrical. |
1121 # don't get a related descriptor. |
951 if related.model != related.parent_model or not self.rel.symmetrical: |
1122 if not self.rel.is_hidden(): |
952 # Add the descriptor for the m2m relation |
|
953 setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) |
1123 setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) |
954 |
1124 |
955 # Set up the accessors for the column names on the m2m table |
1125 # Set up the accessors for the column names on the m2m table |
956 self.m2m_column_name = curry(self._get_m2m_column_name, related) |
1126 self.m2m_column_name = curry(self._get_m2m_attr, related, 'column') |
957 self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related) |
1127 self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column') |
|
1128 |
|
1129 self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') |
|
1130 self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name') |
958 |
1131 |
959 def set_attributes_from_rel(self): |
1132 def set_attributes_from_rel(self): |
960 pass |
1133 pass |
961 |
1134 |
962 def value_from_object(self, obj): |
1135 def value_from_object(self, obj): |