diff -r 8d941af65caf -r 77b6da96e6f1 web/lib/django/db/models/fields/related.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django/db/models/fields/related.py Wed Jun 02 18:57:35 2010 +0200 @@ -0,0 +1,1161 @@ +from django.conf import settings +from django.db import connection, router, transaction +from django.db.backends import util +from django.db.models import signals, get_model +from django.db.models.fields import (AutoField, Field, IntegerField, + PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist) +from django.db.models.related import RelatedObject +from django.db.models.query import QuerySet +from django.db.models.query_utils import QueryWrapper +from django.utils.encoding import smart_unicode +from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext +from django.utils.functional import curry +from django.core import exceptions +from django import forms + + +RECURSIVE_RELATIONSHIP_CONSTANT = 'self' + +pending_lookups = {} + +def add_lazy_relation(cls, field, relation, operation): + """ + Adds a lookup on ``cls`` when a related field is defined using a string, + i.e.:: + + class MyModel(Model): + fk = ForeignKey("AnotherModel") + + This string can be: + + * RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive + relation. + + * The name of a model (i.e "AnotherModel") to indicate another model in + the same app. + + * An app-label and model name (i.e. "someapp.AnotherModel") to indicate + another model in a different app. + + If the other model hasn't yet been loaded -- almost a given if you're using + lazy relationships -- then the relation won't be set up until the + class_prepared signal fires at the end of model initialization. + + operation is the work that must be performed once the relation can be resolved. + """ + # Check for recursive relations + if relation == RECURSIVE_RELATIONSHIP_CONSTANT: + app_label = cls._meta.app_label + model_name = cls.__name__ + + else: + # Look for an "app.Model" relation + try: + app_label, model_name = relation.split(".") + except ValueError: + # If we can't split, assume a model in current app + app_label = cls._meta.app_label + model_name = relation + except AttributeError: + # If it doesn't have a split it's actually a model class + app_label = relation._meta.app_label + model_name = relation._meta.object_name + + # Try to look up the related model, and if it's already loaded resolve the + # string right away. If get_model returns None, it means that the related + # model isn't loaded yet, so we need to pend the relation until the class + # is prepared. + model = get_model(app_label, model_name, False) + if model: + operation(field, model, cls) + else: + key = (app_label, model_name) + value = (cls, field, operation) + pending_lookups.setdefault(key, []).append(value) + +def do_pending_lookups(sender, **kwargs): + """ + Handle any pending relations to the sending model. Sent from class_prepared. + """ + key = (sender._meta.app_label, sender.__name__) + for cls, field, operation in pending_lookups.pop(key, []): + operation(field, sender, cls) + +signals.class_prepared.connect(do_pending_lookups) + +#HACK +class RelatedField(object): + def contribute_to_class(self, cls, name): + sup = super(RelatedField, self) + + # Store the opts for related_query_name() + self.opts = cls._meta + + if hasattr(sup, 'contribute_to_class'): + sup.contribute_to_class(cls, name) + + if not cls._meta.abstract and self.rel.related_name: + self.rel.related_name = self.rel.related_name % { + 'class': cls.__name__.lower(), + 'app_label': cls._meta.app_label.lower(), + } + + other = self.rel.to + if isinstance(other, basestring) or other._meta.pk is None: + def resolve_related_class(field, model, cls): + field.rel.to = model + field.do_related_class(model, cls) + add_lazy_relation(cls, self, other, resolve_related_class) + else: + self.do_related_class(other, cls) + + def set_attributes_from_rel(self): + self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) + if self.verbose_name is None: + self.verbose_name = self.rel.to._meta.verbose_name + self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name + + def do_related_class(self, other, cls): + self.set_attributes_from_rel() + self.related = RelatedObject(other, cls, self) + if not cls._meta.abstract: + self.contribute_to_related_class(other, self.related) + + def get_prep_lookup(self, lookup_type, value): + if hasattr(value, 'prepare'): + return value.prepare() + if hasattr(value, '_prepare'): + return value._prepare() + # FIXME: lt and gt are explicitly allowed to make + # get_(next/prev)_by_date work; other lookups are not allowed since that + # gets messy pretty quick. This is a good candidate for some refactoring + # in the future. + if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: + return self._pk_trace(value, 'get_prep_lookup', lookup_type) + if lookup_type in ('range', 'in'): + return [self._pk_trace(v, 'get_prep_lookup', lookup_type) for v in value] + elif lookup_type == 'isnull': + return [] + raise TypeError("Related Field has invalid lookup: %s" % lookup_type) + + def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): + if not prepared: + value = self.get_prep_lookup(lookup_type, value) + if hasattr(value, 'get_compiler'): + value = value.get_compiler(connection=connection) + if hasattr(value, 'as_sql') or hasattr(value, '_as_sql'): + # If the value has a relabel_aliases method, it will need to + # be invoked before the final SQL is evaluated + if hasattr(value, 'relabel_aliases'): + return value + if hasattr(value, 'as_sql'): + sql, params = value.as_sql() + else: + sql, params = value._as_sql(connection=connection) + return QueryWrapper(('(%s)' % sql), params) + + # FIXME: lt and gt are explicitly allowed to make + # get_(next/prev)_by_date work; other lookups are not allowed since that + # gets messy pretty quick. This is a good candidate for some refactoring + # in the future. + if lookup_type in ['exact', 'gt', 'lt', 'gte', 'lte']: + return [self._pk_trace(value, 'get_db_prep_lookup', lookup_type, + connection=connection, prepared=prepared)] + if lookup_type in ('range', 'in'): + return [self._pk_trace(v, 'get_db_prep_lookup', lookup_type, + connection=connection, prepared=prepared) + for v in value] + elif lookup_type == 'isnull': + return [] + raise TypeError("Related Field has invalid lookup: %s" % lookup_type) + + def _pk_trace(self, value, prep_func, lookup_type, **kwargs): + # Value may be a primary key, or an object held in a relation. + # If it is an object, then we need to get the primary key value for + # that object. In certain conditions (especially one-to-one relations), + # the primary key may itself be an object - so we need to keep drilling + # down until we hit a value that can be used for a comparison. + v = value + try: + while True: + v = getattr(v, v._meta.pk.name) + except AttributeError: + pass + except exceptions.ObjectDoesNotExist: + v = None + + field = self + while field.rel: + if hasattr(field.rel, 'field_name'): + field = field.rel.to._meta.get_field(field.rel.field_name) + else: + field = field.rel.to._meta.pk + + if lookup_type in ('range', 'in'): + v = [v] + v = getattr(field, prep_func)(lookup_type, v, **kwargs) + if isinstance(v, list): + v = v[0] + return v + + def related_query_name(self): + # This method defines the name that can be used to identify this + # related object in a table-spanning query. It uses the lower-cased + # object_name by default, but this can be overridden with the + # "related_name" option. + return self.rel.related_name or self.opts.object_name.lower() + +class SingleRelatedObjectDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class, for fields that have + # a single "remote" value, on the class pointed to by a related field. + # In the example "place.restaurant", the restaurant attribute is a + # SingleRelatedObjectDescriptor instance. + def __init__(self, related): + self.related = related + self.cache_name = related.get_cache_name() + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + try: + return getattr(instance, self.cache_name) + except AttributeError: + params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} + db = router.db_for_read(self.related.model, instance=instance) + rel_obj = self.related.model._base_manager.using(db).get(**params) + setattr(instance, self.cache_name, rel_obj) + return rel_obj + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("%s must be accessed via instance" % self.related.opts.object_name) + + # The similarity of the code below to the code in + # ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch + # of small differences that would make a common base class convoluted. + + # If null=True, we can assign null here, but otherwise the value needs + # to be an instance of the related class. + if value is None and self.related.field.null == False: + raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % + (instance._meta.object_name, self.related.get_accessor_name())) + elif value is not None and not isinstance(value, self.related.model): + raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % + (value, instance._meta.object_name, + self.related.get_accessor_name(), self.related.opts.object_name)) + elif value is not None: + if instance._state.db is None: + instance._state.db = router.db_for_write(instance.__class__, instance=value) + elif value._state.db is None: + value._state.db = router.db_for_write(value.__class__, instance=instance) + elif value._state.db is not None and instance._state.db is not None: + if not router.allow_relation(value, instance): + raise ValueError('Cannot assign "%r": instance is on database "%s", value is on database "%s"' % + (value, instance._state.db, value._state.db)) + + # Set the value of the related field to the value of the related object's related field + setattr(value, self.related.field.attname, getattr(instance, self.related.field.rel.get_related_field().attname)) + + # Since we already know what the related object is, seed the related + # object caches now, too. This avoids another db hit if you get the + # object you just set. + setattr(instance, self.cache_name, value) + setattr(value, self.related.field.get_cache_name(), instance) + +class ReverseSingleRelatedObjectDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class, for fields that have + # a single "remote" value, on the class that defines the related field. + # In the example "choice.poll", the poll attribute is a + # ReverseSingleRelatedObjectDescriptor instance. + def __init__(self, field_with_rel): + self.field = field_with_rel + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + + cache_name = self.field.get_cache_name() + try: + return getattr(instance, cache_name) + except AttributeError: + val = getattr(instance, self.field.attname) + if val is None: + # If NULL is an allowed value, return it. + if self.field.null: + return None + raise self.field.rel.to.DoesNotExist + other_field = self.field.rel.get_related_field() + if other_field.rel: + params = {'%s__pk' % self.field.rel.field_name: val} + else: + params = {'%s__exact' % self.field.rel.field_name: val} + + # If the related manager indicates that it should be used for + # related fields, respect that. + rel_mgr = self.field.rel.to._default_manager + db = router.db_for_read(self.field.rel.to, instance=instance) + if getattr(rel_mgr, 'use_for_related_fields', False): + rel_obj = rel_mgr.using(db).get(**params) + else: + rel_obj = QuerySet(self.field.rel.to).using(db).get(**params) + setattr(instance, cache_name, rel_obj) + return rel_obj + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("%s must be accessed via instance" % self._field.name) + + # If null=True, we can assign null here, but otherwise the value needs + # to be an instance of the related class. + if value is None and self.field.null == False: + raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % + (instance._meta.object_name, self.field.name)) + elif value is not None and not isinstance(value, self.field.rel.to): + raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % + (value, instance._meta.object_name, + self.field.name, self.field.rel.to._meta.object_name)) + elif value is not None: + if instance._state.db is None: + instance._state.db = router.db_for_write(instance.__class__, instance=value) + elif value._state.db is None: + value._state.db = router.db_for_write(value.__class__, instance=instance) + elif value._state.db is not None and instance._state.db is not None: + if not router.allow_relation(value, instance): + raise ValueError('Cannot assign "%r": instance is on database "%s", value is on database "%s"' % + (value, instance._state.db, value._state.db)) + + # If we're setting the value of a OneToOneField to None, we need to clear + # out the cache on any old related object. Otherwise, deleting the + # previously-related object will also cause this object to be deleted, + # which is wrong. + if value is None: + # Look up the previously-related object, which may still be available + # since we've not yet cleared out the related field. + # Use the cache directly, instead of the accessor; if we haven't + # populated the cache, then we don't care - we're only accessing + # the object to invalidate the accessor cache, so there's no + # need to populate the cache just to expire it again. + related = getattr(instance, self.field.get_cache_name(), None) + + # If we've got an old related object, we need to clear out its + # cache. This cache also might not exist if the related object + # hasn't been accessed yet. + if related: + cache_name = self.field.related.get_cache_name() + try: + delattr(related, cache_name) + except AttributeError: + pass + + # Set the value of the related field + try: + val = getattr(value, self.field.rel.get_related_field().attname) + except AttributeError: + val = None + setattr(instance, self.field.attname, val) + + # Since we already know what the related object is, seed the related + # object cache now, too. This avoids another db hit if you get the + # object you just set. + setattr(instance, self.field.get_cache_name(), value) + +class ForeignRelatedObjectsDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class, for fields that have + # multiple "remote" values and have a ForeignKey pointed at them by + # some other model. In the example "poll.choice_set", the choice_set + # attribute is a ForeignRelatedObjectsDescriptor instance. + def __init__(self, related): + self.related = related # RelatedObject instance + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + + return self.create_manager(instance, + self.related.model._default_manager.__class__) + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("Manager must be accessed via instance") + + manager = self.__get__(instance) + # If the foreign key can support nulls, then completely clear the related set. + # Otherwise, just move the named objects into the set. + if self.related.field.null: + manager.clear() + manager.add(*value) + + def delete_manager(self, instance): + """ + Returns a queryset based on the related model's base manager (rather + than the default manager, as returned by __get__). Used by + Model.delete(). + """ + return self.create_manager(instance, + self.related.model._base_manager.__class__) + + def create_manager(self, instance, superclass): + """ + Creates the managers used by other methods (__get__() and delete()). + """ + rel_field = self.related.field + rel_model = self.related.model + + class RelatedManager(superclass): + def get_query_set(self): + db = self._db or router.db_for_read(rel_model, instance=instance) + return superclass.get_query_set(self).using(db).filter(**(self.core_filters)) + + def add(self, *objs): + for obj in objs: + if not isinstance(obj, self.model): + raise TypeError("'%s' instance expected" % self.model._meta.object_name) + setattr(obj, rel_field.name, instance) + obj.save() + add.alters_data = True + + def create(self, **kwargs): + kwargs.update({rel_field.name: instance}) + db = router.db_for_write(rel_model, instance=instance) + return super(RelatedManager, self).using(db).create(**kwargs) + create.alters_data = True + + def get_or_create(self, **kwargs): + # Update kwargs with the related object that this + # ForeignRelatedObjectsDescriptor knows about. + kwargs.update({rel_field.name: instance}) + db = router.db_for_write(rel_model, instance=instance) + return super(RelatedManager, self).using(db).get_or_create(**kwargs) + get_or_create.alters_data = True + + # remove() and clear() are only provided if the ForeignKey can have a value of null. + if rel_field.null: + def remove(self, *objs): + val = getattr(instance, rel_field.rel.get_related_field().attname) + for obj in objs: + # Is obj actually part of this descriptor set? + if getattr(obj, rel_field.attname) == val: + setattr(obj, rel_field.name, None) + obj.save() + else: + raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, instance)) + remove.alters_data = True + + def clear(self): + for obj in self.all(): + setattr(obj, rel_field.name, None) + obj.save() + clear.alters_data = True + + manager = RelatedManager() + attname = rel_field.rel.get_related_field().name + manager.core_filters = {'%s__%s' % (rel_field.name, attname): + getattr(instance, attname)} + manager.model = self.related.model + + return manager + +def create_many_related_manager(superclass, rel=False): + """Creates a manager that subclasses 'superclass' (which is a Manager) + and adds behavior for many-to-many related objects.""" + through = rel.through + class ManyRelatedManager(superclass): + def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, + join_table=None, source_field_name=None, target_field_name=None, + reverse=False): + super(ManyRelatedManager, self).__init__() + self.core_filters = core_filters + self.model = model + self.symmetrical = symmetrical + self.instance = instance + self.source_field_name = source_field_name + self.target_field_name = target_field_name + self.through = through + self._pk_val = self.instance.pk + self.reverse = reverse + if self._pk_val is None: + raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) + + def get_query_set(self): + db = self._db or router.db_for_read(self.instance.__class__, instance=self.instance) + return superclass.get_query_set(self).using(db)._next_is_sticky().filter(**(self.core_filters)) + + # If the ManyToMany relation has an intermediary model, + # the add and remove methods do not exist. + if rel.through._meta.auto_created: + def add(self, *objs): + self._add_items(self.source_field_name, self.target_field_name, *objs) + + # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table + if self.symmetrical: + self._add_items(self.target_field_name, self.source_field_name, *objs) + add.alters_data = True + + def remove(self, *objs): + self._remove_items(self.source_field_name, self.target_field_name, *objs) + + # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table + if self.symmetrical: + self._remove_items(self.target_field_name, self.source_field_name, *objs) + remove.alters_data = True + + def clear(self): + self._clear_items(self.source_field_name) + + # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table + if self.symmetrical: + self._clear_items(self.target_field_name) + clear.alters_data = True + + def create(self, **kwargs): + # This check needs to be done here, since we can't later remove this + # from the method lookup table, as we do with add and remove. + if not rel.through._meta.auto_created: + opts = through._meta + 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)) + db = router.db_for_write(self.instance.__class__, instance=self.instance) + new_obj = super(ManyRelatedManager, self).using(db).create(**kwargs) + self.add(new_obj) + return new_obj + create.alters_data = True + + def get_or_create(self, **kwargs): + db = router.db_for_write(self.instance.__class__, instance=self.instance) + obj, created = \ + super(ManyRelatedManager, self).using(db).get_or_create(**kwargs) + # We only need to add() if created because if we got an object back + # from get() then the relationship already exists. + if created: + self.add(obj) + return obj, created + get_or_create.alters_data = True + + def _add_items(self, source_field_name, target_field_name, *objs): + # join_table: name of the m2m link table + # source_field_name: the PK fieldname in join_table for the source object + # target_field_name: the PK fieldname in join_table for the target object + # *objs - objects to add. Either object instances, or primary keys of object instances. + + # If there aren't any objects, there is nothing to do. + from django.db.models import Model + if objs: + new_ids = set() + for obj in objs: + if isinstance(obj, self.model): + if not router.allow_relation(obj, self.instance): + raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' % + (obj, self.instance._state.db, obj._state.db)) + new_ids.add(obj.pk) + elif isinstance(obj, Model): + raise TypeError("'%s' instance expected" % self.model._meta.object_name) + else: + new_ids.add(obj) + db = router.db_for_write(self.through.__class__, instance=self.instance) + vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True) + vals = vals.filter(**{ + source_field_name: self._pk_val, + '%s__in' % target_field_name: new_ids, + }) + new_ids = new_ids - set(vals) + + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are inserting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action='pre_add', + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=new_ids) + # Add the ones that aren't there already + for obj_id in new_ids: + self.through._default_manager.using(db).create(**{ + '%s_id' % source_field_name: self._pk_val, + '%s_id' % target_field_name: obj_id, + }) + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are inserting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action='post_add', + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=new_ids) + + def _remove_items(self, source_field_name, target_field_name, *objs): + # source_col_name: the PK colname in join_table for the source object + # target_col_name: the PK colname in join_table for the target object + # *objs - objects to remove + + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + old_ids = set() + for obj in objs: + if isinstance(obj, self.model): + old_ids.add(obj.pk) + else: + old_ids.add(obj) + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are deleting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="pre_remove", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=old_ids) + # Remove the specified objects from the join table + db = router.db_for_write(self.through.__class__, instance=self.instance) + self.through._default_manager.using(db).filter(**{ + source_field_name: self._pk_val, + '%s__in' % target_field_name: old_ids + }).delete() + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are deleting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="post_remove", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=old_ids) + + def _clear_items(self, source_field_name): + # source_col_name: the PK colname in join_table for the source object + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are clearing the + # duplicate data rows for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="pre_clear", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=None) + db = router.db_for_write(self.through.__class__, instance=self.instance) + self.through._default_manager.using(db).filter(**{ + source_field_name: self._pk_val + }).delete() + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are clearing the + # duplicate data rows for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="post_clear", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=None) + + return ManyRelatedManager + +class ManyRelatedObjectsDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class, for fields that have + # multiple "remote" values and have a ManyToManyField pointed at them by + # some other model (rather than having a ManyToManyField themselves). + # In the example "publication.article_set", the article_set attribute is a + # ManyRelatedObjectsDescriptor instance. + def __init__(self, related): + self.related = related # RelatedObject instance + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + + # Dynamically create a class that subclasses the related + # model's default manager. + rel_model = self.related.model + superclass = rel_model._default_manager.__class__ + RelatedManager = create_many_related_manager(superclass, self.related.field.rel) + + manager = RelatedManager( + model=rel_model, + core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, + instance=instance, + symmetrical=False, + source_field_name=self.related.field.m2m_reverse_field_name(), + target_field_name=self.related.field.m2m_field_name(), + reverse=True + ) + + return manager + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("Manager must be accessed via instance") + + if not self.related.field.rel.through._meta.auto_created: + opts = self.related.field.rel.through._meta + 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)) + + manager = self.__get__(instance) + manager.clear() + manager.add(*value) + + +class ReverseManyRelatedObjectsDescriptor(object): + # This class provides the functionality that makes the related-object + # managers available as attributes on a model class, for fields that have + # multiple "remote" values and have a ManyToManyField defined in their + # model (rather than having another model pointed *at* them). + # In the example "article.publications", the publications attribute is a + # ReverseManyRelatedObjectsDescriptor instance. + def __init__(self, m2m_field): + self.field = m2m_field + + def _through(self): + # through is provided so that you have easy access to the through + # model (Book.authors.through) for inlines, etc. This is done as + # a property to ensure that the fully resolved value is returned. + return self.field.rel.through + through = property(_through) + + def __get__(self, instance, instance_type=None): + if instance is None: + return self + + # Dynamically create a class that subclasses the related + # model's default manager. + rel_model=self.field.rel.to + superclass = rel_model._default_manager.__class__ + RelatedManager = create_many_related_manager(superclass, self.field.rel) + + manager = RelatedManager( + model=rel_model, + core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, + instance=instance, + symmetrical=self.field.rel.symmetrical, + source_field_name=self.field.m2m_field_name(), + target_field_name=self.field.m2m_reverse_field_name(), + reverse=False + ) + + return manager + + def __set__(self, instance, value): + if instance is None: + raise AttributeError("Manager must be accessed via instance") + + if not self.field.rel.through._meta.auto_created: + opts = self.field.rel.through._meta + 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)) + + manager = self.__get__(instance) + manager.clear() + manager.add(*value) + +class ManyToOneRel(object): + def __init__(self, to, field_name, related_name=None, + limit_choices_to=None, lookup_overrides=None, parent_link=False): + try: + to._meta + except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT + assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT + self.to, self.field_name = to, field_name + self.related_name = related_name + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to + self.lookup_overrides = lookup_overrides or {} + self.multiple = True + self.parent_link = parent_link + + def is_hidden(self): + "Should the related object be hidden?" + return self.related_name and self.related_name[-1] == '+' + + def get_related_field(self): + """ + Returns the Field in the 'to' object to which this relationship is + tied. + """ + data = self.to._meta.get_field_by_name(self.field_name) + if not data[2]: + raise FieldDoesNotExist("No related field named '%s'" % + self.field_name) + return data[0] + +class OneToOneRel(ManyToOneRel): + def __init__(self, to, field_name, related_name=None, + limit_choices_to=None, lookup_overrides=None, parent_link=False): + super(OneToOneRel, self).__init__(to, field_name, + related_name=related_name, limit_choices_to=limit_choices_to, + lookup_overrides=lookup_overrides, parent_link=parent_link) + self.multiple = False + +class ManyToManyRel(object): + def __init__(self, to, related_name=None, limit_choices_to=None, + symmetrical=True, through=None): + self.to = to + self.related_name = related_name + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to + self.symmetrical = symmetrical + self.multiple = True + self.through = through + + def is_hidden(self): + "Should the related object be hidden?" + return self.related_name and self.related_name[-1] == '+' + + def get_related_field(self): + """ + Returns the field in the to' object to which this relationship is tied + (this is always the primary key on the target model). Provided for + symmetry with ManyToOneRel. + """ + return self.to._meta.pk + +class ForeignKey(RelatedField, Field): + empty_strings_allowed = False + default_error_messages = { + 'invalid': _('Model %(model)s with pk %(pk)r does not exist.') + } + description = _("Foreign Key (type determined by related field)") + def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): + try: + to_name = to._meta.object_name.lower() + except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT + 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) + else: + assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) + # For backwards compatibility purposes, we need to *try* and set + # the to_field during FK construction. It won't be guaranteed to + # be correct until contribute_to_class is called. Refs #12190. + to_field = to_field or (to._meta.pk and to._meta.pk.name) + kwargs['verbose_name'] = kwargs.get('verbose_name', None) + + kwargs['rel'] = rel_class(to, to_field, + related_name=kwargs.pop('related_name', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + lookup_overrides=kwargs.pop('lookup_overrides', None), + parent_link=kwargs.pop('parent_link', False)) + Field.__init__(self, **kwargs) + + self.db_index = True + + def validate(self, value, model_instance): + if self.rel.parent_link: + return + super(ForeignKey, self).validate(value, model_instance) + if value is None: + return + + qs = self.rel.to._default_manager.filter(**{self.rel.field_name:value}) + qs = qs.complex_filter(self.rel.limit_choices_to) + if not qs.exists(): + raise exceptions.ValidationError(self.error_messages['invalid'] % { + 'model': self.rel.to._meta.verbose_name, 'pk': value}) + + def get_attname(self): + return '%s_id' % self.name + + def get_validator_unique_lookup_type(self): + return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) + + def get_default(self): + "Here we check if the default value is an object and return the to_field if so." + field_default = super(ForeignKey, self).get_default() + if isinstance(field_default, self.rel.to): + return getattr(field_default, self.rel.get_related_field().attname) + return field_default + + def get_db_prep_save(self, value, connection): + if value == '' or value == None: + return None + else: + return self.rel.get_related_field().get_db_prep_save(value, + connection=connection) + + def value_to_string(self, obj): + if not obj: + # In required many-to-one fields with only one available choice, + # select that one available choice. Note: For SelectFields + # we have to check that the length of choices is *2*, not 1, + # because SelectFields always have an initial "blank" value. + if not self.blank and self.choices: + choice_list = self.get_choices_default() + if len(choice_list) == 2: + return smart_unicode(choice_list[1][0]) + return Field.value_to_string(self, obj) + + def contribute_to_class(self, cls, name): + super(ForeignKey, self).contribute_to_class(cls, name) + setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) + if isinstance(self.rel.to, basestring): + target = self.rel.to + else: + target = self.rel.to._meta.db_table + cls._meta.duplicate_targets[self.column] = (target, "o2m") + + def contribute_to_related_class(self, cls, related): + # Internal FK's - i.e., those with a related name ending with '+' - + # don't get a related descriptor. + if not self.rel.is_hidden(): + setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) + if self.rel.field_name is None: + self.rel.field_name = cls._meta.pk.name + + def formfield(self, **kwargs): + db = kwargs.pop('using', None) + defaults = { + 'form_class': forms.ModelChoiceField, + 'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to), + 'to_field_name': self.rel.field_name, + } + defaults.update(kwargs) + return super(ForeignKey, self).formfield(**defaults) + + def db_type(self, connection): + # The database column type of a ForeignKey is the column type + # of the field to which it points. An exception is if the ForeignKey + # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, + # in which case the column type is simply that of an IntegerField. + # If the database needs similar types for key fields however, the only + # thing we can do is making AutoField an IntegerField. + rel_field = self.rel.get_related_field() + if (isinstance(rel_field, AutoField) or + (not connection.features.related_fields_match_type and + isinstance(rel_field, (PositiveIntegerField, + PositiveSmallIntegerField)))): + return IntegerField().db_type(connection=connection) + return rel_field.db_type(connection=connection) + +class OneToOneField(ForeignKey): + """ + A OneToOneField is essentially the same as a ForeignKey, with the exception + that always carries a "unique" constraint with it and the reverse relation + always returns the object pointed to (since there will only ever be one), + rather than returning a list. + """ + description = _("One-to-one relationship") + def __init__(self, to, to_field=None, **kwargs): + kwargs['unique'] = True + super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) + + def contribute_to_related_class(self, cls, related): + setattr(cls, related.get_accessor_name(), + SingleRelatedObjectDescriptor(related)) + + def formfield(self, **kwargs): + if self.rel.parent_link: + return None + return super(OneToOneField, self).formfield(**kwargs) + + def save_form_data(self, instance, data): + if isinstance(data, self.rel.to): + setattr(instance, self.name, data) + else: + setattr(instance, self.attname, data) + +def create_many_to_many_intermediary_model(field, klass): + from django.db import models + managed = True + if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: + to_model = field.rel.to + to = to_model.split('.')[-1] + def set_managed(field, model, cls): + field.rel.through._meta.managed = model._meta.managed or cls._meta.managed + add_lazy_relation(klass, field, to_model, set_managed) + elif isinstance(field.rel.to, basestring): + to = klass._meta.object_name + to_model = klass + managed = klass._meta.managed + else: + to = field.rel.to._meta.object_name + to_model = field.rel.to + managed = klass._meta.managed or to_model._meta.managed + name = '%s_%s' % (klass._meta.object_name, field.name) + if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: + from_ = 'from_%s' % to.lower() + to = 'to_%s' % to.lower() + else: + from_ = klass._meta.object_name.lower() + to = to.lower() + meta = type('Meta', (object,), { + 'db_table': field._get_m2m_db_table(klass._meta), + 'managed': managed, + 'auto_created': klass, + 'app_label': klass._meta.app_label, + 'unique_together': (from_, to), + 'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to}, + 'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to}, + }) + # Construct and return the new class. + return type(name, (models.Model,), { + 'Meta': meta, + '__module__': klass.__module__, + from_: models.ForeignKey(klass, related_name='%s+' % name), + to: models.ForeignKey(to_model, related_name='%s+' % name) + }) + +class ManyToManyField(RelatedField, Field): + description = _("Many-to-many relationship") + def __init__(self, to, **kwargs): + try: + assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) + except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT + 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) + + kwargs['verbose_name'] = kwargs.get('verbose_name', None) + kwargs['rel'] = ManyToManyRel(to, + related_name=kwargs.pop('related_name', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + symmetrical=kwargs.pop('symmetrical', to==RECURSIVE_RELATIONSHIP_CONSTANT), + through=kwargs.pop('through', None)) + + self.db_table = kwargs.pop('db_table', None) + if kwargs['rel'].through is not None: + assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." + + Field.__init__(self, **kwargs) + + msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') + self.help_text = string_concat(self.help_text, ' ', msg) + + def get_choices_default(self): + return Field.get_choices(self, include_blank=False) + + def _get_m2m_db_table(self, opts): + "Function that can be curried to provide the m2m table name for this relation" + if self.rel.through is not None: + return self.rel.through._meta.db_table + elif self.db_table: + return self.db_table + else: + return util.truncate_name('%s_%s' % (opts.db_table, self.name), + connection.ops.max_name_length()) + + def _get_m2m_attr(self, related, attr): + "Function that can be curried to provide the source accessor or DB column name for the m2m table" + cache_attr = '_m2m_%s_cache' % attr + if hasattr(self, cache_attr): + return getattr(self, cache_attr) + for f in self.rel.through._meta.fields: + if hasattr(f,'rel') and f.rel and f.rel.to == related.model: + setattr(self, cache_attr, getattr(f, attr)) + return getattr(self, cache_attr) + + def _get_m2m_reverse_attr(self, related, attr): + "Function that can be curried to provide the related accessor or DB column name for the m2m table" + cache_attr = '_m2m_reverse_%s_cache' % attr + if hasattr(self, cache_attr): + return getattr(self, cache_attr) + found = False + for f in self.rel.through._meta.fields: + if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: + if related.model == related.parent_model: + # If this is an m2m-intermediate to self, + # the first foreign key you find will be + # the source column. Keep searching for + # the second foreign key. + if found: + setattr(self, cache_attr, getattr(f, attr)) + break + else: + found = True + else: + setattr(self, cache_attr, getattr(f, attr)) + break + return getattr(self, cache_attr) + + def isValidIDList(self, field_data, all_data): + "Validates that the value is a valid list of foreign keys" + mod = self.rel.to + try: + pks = map(int, field_data.split(',')) + except ValueError: + # the CommaSeparatedIntegerField validator will catch this error + return + objects = mod._default_manager.in_bulk(pks) + if len(objects) != len(pks): + badkeys = [k for k in pks if k not in objects] + raise exceptions.ValidationError( + ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", + "Please enter valid %(self)s IDs. The values %(value)r are invalid.", + len(badkeys)) % { + 'self': self.verbose_name, + 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), + }) + + def value_to_string(self, obj): + data = '' + if obj: + qs = getattr(obj, self.name).all() + data = [instance._get_pk_val() for instance in qs] + else: + # In required many-to-many fields with only one available choice, + # select that one available choice. + if not self.blank: + choices_list = self.get_choices_default() + if len(choices_list) == 1: + data = [choices_list[0][0]] + return smart_unicode(data) + + def contribute_to_class(self, cls, name): + # To support multiple relations to self, it's useful to have a non-None + # related name on symmetrical relations for internal reasons. The + # concept doesn't make a lot of sense externally ("you want me to + # specify *what* on my non-reversible relation?!"), so we set it up + # automatically. The funky name reduces the chance of an accidental + # clash. + if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): + self.rel.related_name = "%s_rel_+" % name + + super(ManyToManyField, self).contribute_to_class(cls, name) + + # The intermediate m2m model is not auto created if: + # 1) There is a manually specified intermediate, or + # 2) The class owning the m2m field is abstract. + if not self.rel.through and not cls._meta.abstract: + self.rel.through = create_many_to_many_intermediary_model(self, cls) + + # Add the descriptor for the m2m relation + setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) + + # Set up the accessor for the m2m table name for the relation + self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) + + # Populate some necessary rel arguments so that cross-app relations + # work correctly. + if isinstance(self.rel.through, basestring): + def resolve_through_model(field, model, cls): + field.rel.through = model + add_lazy_relation(cls, self, self.rel.through, resolve_through_model) + + if isinstance(self.rel.to, basestring): + target = self.rel.to + else: + target = self.rel.to._meta.db_table + cls._meta.duplicate_targets[self.column] = (target, "m2m") + + def contribute_to_related_class(self, cls, related): + # Internal M2Ms (i.e., those with a related name ending with '+') + # don't get a related descriptor. + if not self.rel.is_hidden(): + setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) + + # Set up the accessors for the column names on the m2m table + self.m2m_column_name = curry(self._get_m2m_attr, related, 'column') + self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column') + + self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') + self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name') + + def set_attributes_from_rel(self): + pass + + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname).all() + + def save_form_data(self, instance, data): + setattr(instance, self.attname, data) + + def formfield(self, **kwargs): + db = kwargs.pop('using', None) + defaults = { + 'form_class': forms.ModelMultipleChoiceField, + 'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to) + } + defaults.update(kwargs) + # If initial is passed in, it's a list of related objects, but the + # MultipleChoiceField takes a list of IDs. + if defaults.get('initial') is not None: + initial = defaults['initial'] + if callable(initial): + initial = initial() + defaults['initial'] = [i._get_pk_val() for i in initial] + return super(ManyToManyField, self).formfield(**defaults) + + def db_type(self, connection): + # A ManyToManyField is not represented by a single column, + # so return None. + return None