diff -r 000000000000 -r 0d40e90630ef 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 Jan 20 00:34:04 2010 +0100 @@ -0,0 +1,984 @@ +from django.db import connection, 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, string_concat, ungettext, ugettext as _ +from django.utils.functional import curry +from django.core import exceptions +from django import forms + +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +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 + + # 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) + + # Add an accessor to allow easy determination of the related query path for this field + self.related_query_name = curry(self._get_related_query_name, 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()} + + other = self.rel.to + if isinstance(other, basestring): + 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_db_prep_lookup(self, lookup_type, value): + # If we are doing a lookup on a Related Field, we must be + # comparing object instances. The value should be the PK of value, + # not value itself. + def pk_trace(value): + # 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, field = value, None + try: + while True: + v, field = getattr(v, v._meta.pk.name), v._meta.pk + except AttributeError: + pass + + if field: + if lookup_type in ('range', 'in'): + v = [v] + v = field.get_db_prep_lookup(lookup_type, v) + if isinstance(v, list): + v = v[0] + return v + + 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() + return QueryWrapper(('(%s)' % sql), params) + + # FIXME: lt and gt are explicitally 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 [pk_trace(value)] + if lookup_type in ('range', 'in'): + return [pk_trace(v) for v in value] + elif lookup_type == 'isnull': + return [] + raise TypeError, "Related Field has invalid lookup: %s" % lookup_type + + def _get_related_query_name(self, opts): + # 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 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 = '_%s_cache' % related.get_accessor_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()} + rel_obj = self.related.model._base_manager.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)) + + # 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 + if getattr(rel_mgr, 'use_for_related_fields', False): + rel_obj = rel_mgr.get(**params) + else: + rel_obj = QuerySet(self.field.rel.to).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)) + + # 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 = '_%s_cache' % self.field.related.get_accessor_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): + return superclass.get_query_set(self).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}) + return super(RelatedManager, self).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}) + return super(RelatedManager, self).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, through=False): + """Creates a manager that subclasses 'superclass' (which is a Manager) + and adds behavior for many-to-many related objects.""" + class ManyRelatedManager(superclass): + def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, + join_table=None, source_col_name=None, target_col_name=None): + super(ManyRelatedManager, self).__init__() + self.core_filters = core_filters + self.model = model + self.symmetrical = symmetrical + self.instance = instance + self.join_table = join_table + self.source_col_name = source_col_name + self.target_col_name = target_col_name + self.through = through + self._pk_val = self.instance._get_pk_val() + 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): + return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters)) + + # If the ManyToMany relation has an intermediary model, + # the add and remove methods do not exist. + if through is None: + def add(self, *objs): + self._add_items(self.source_col_name, self.target_col_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_col_name, self.source_col_name, *objs) + add.alters_data = True + + def remove(self, *objs): + self._remove_items(self.source_col_name, self.target_col_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_col_name, self.source_col_name, *objs) + remove.alters_data = True + + def clear(self): + self._clear_items(self.source_col_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_col_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 through is not None: + raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through + new_obj = super(ManyRelatedManager, self).create(**kwargs) + self.add(new_obj) + return new_obj + create.alters_data = True + + def get_or_create(self, **kwargs): + obj, created = \ + super(ManyRelatedManager, self).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_col_name, target_col_name, *objs): + # join_table: name of the m2m link table + # 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 add. Either object instances, or primary keys of object instances. + + # If there aren't any objects, there is nothing to do. + if objs: + from django.db.models.base import Model + # Check that all the objects are of the right type + new_ids = set() + for obj in objs: + if isinstance(obj, self.model): + new_ids.add(obj._get_pk_val()) + elif isinstance(obj, Model): + raise TypeError, "'%s' instance expected" % self.model._meta.object_name + else: + new_ids.add(obj) + # Add the newly created or already existing objects to the join table. + # First find out which items are already added, to avoid adding them twice + cursor = connection.cursor() + cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (target_col_name, self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(new_ids))), + [self._pk_val] + list(new_ids)) + existing_ids = set([row[0] for row in cursor.fetchall()]) + + # Add the ones that aren't there already + for obj_id in (new_ids - existing_ids): + cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (self.join_table, source_col_name, target_col_name), + [self._pk_val, obj_id]) + transaction.commit_unless_managed() + + def _remove_items(self, source_col_name, target_col_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._get_pk_val()) + else: + old_ids.add(obj) + # Remove the specified objects from the join table + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ + (self.join_table, source_col_name, + target_col_name, ",".join(['%s'] * len(old_ids))), + [self._pk_val] + list(old_ids)) + transaction.commit_unless_managed() + + def _clear_items(self, source_col_name): + # source_col_name: the PK colname in join_table for the source object + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ + (self.join_table, source_col_name), + [self._pk_val]) + transaction.commit_unless_managed() + + 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.through) + + qn = connection.ops.quote_name + manager = RelatedManager( + model=rel_model, + core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, + instance=instance, + symmetrical=False, + join_table=qn(self.related.field.m2m_db_table()), + source_col_name=qn(self.related.field.m2m_reverse_name()), + target_col_name=qn(self.related.field.m2m_column_name()) + ) + + return manager + + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + through = getattr(self.related.field.rel, 'through', None) + if through is not None: + raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through + + 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 __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.through) + + qn = connection.ops.quote_name + 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 and isinstance(instance, rel_model)), + join_table=qn(self.field.m2m_db_table()), + source_col_name=qn(self.field.m2m_column_name()), + target_col_name=qn(self.field.m2m_reverse_name()) + ) + + return manager + + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + through = getattr(self.field.rel, 'through', None) + if through is not None: + raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through + + 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 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 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 + 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) + to_field = to_field or 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 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): + if value == '' or value == None: + return None + else: + return self.rel.get_related_field().get_db_prep_save(value) + + 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): + setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) + + def formfield(self, **kwargs): + defaults = { + 'form_class': forms.ModelChoiceField, + 'queryset': self.rel.to._default_manager.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): + # 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() + return rel_field.db_type() + +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. + """ + 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) + +class ManyToManyField(RelatedField, Field): + 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', True), + through=kwargs.pop('through', None)) + + self.db_table = kwargs.pop('db_table', None) + if kwargs['rel'].through is not None: + self.creates_table = False + assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." + else: + self.creates_table = True + + Field.__init__(self, **kwargs) + + msg = ugettext_lazy('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_model._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_column_name(self, related): + "Function that can be curried to provide the source column name for the m2m table" + try: + return self._m2m_column_name_cache + except: + if self.rel.through is not None: + for f in self.rel.through_model._meta.fields: + if hasattr(f,'rel') and f.rel and f.rel.to == related.model: + self._m2m_column_name_cache = f.column + break + # If this is an m2m relation to self, avoid the inevitable name clash + elif related.model == related.parent_model: + self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' + else: + self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' + + # Return the newly cached value + return self._m2m_column_name_cache + + def _get_m2m_reverse_name(self, related): + "Function that can be curried to provide the related column name for the m2m table" + try: + return self._m2m_reverse_name_cache + except: + if self.rel.through is not None: + found = False + for f in self.rel.through_model._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: + self._m2m_reverse_name_cache = f.column + break + else: + found = True + else: + self._m2m_reverse_name_cache = f.column + break + # If this is an m2m relation to self, avoid the inevitable name clash + elif related.model == related.parent_model: + self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' + else: + self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' + + # Return the newly cached value + return self._m2m_reverse_name_cache + + 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" and self.rel.related_name is None: + self.rel.related_name = "%s_rel_+" % name + + super(ManyToManyField, self).contribute_to_class(cls, name) + # 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 = model + add_lazy_relation(cls, self, self.rel.through, resolve_through_model) + elif self.rel.through: + self.rel.through_model = self.rel.through + self.rel.through = self.rel.through._meta.object_name + + 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): + # m2m relations to self do not have a ManyRelatedObjectsDescriptor, + # as it would be redundant - unless the field is non-symmetrical. + if related.model != related.parent_model or not self.rel.symmetrical: + # Add the descriptor for the m2m relation + 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_column_name, related) + self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related) + + 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): + defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.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): + # A ManyToManyField is not represented by a single column, + # so return None. + return None