diff -r 57b4279d8c4e -r 03e267d67478 app/django/db/models/fields/related.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/django/db/models/fields/related.py Fri Jul 18 18:22:23 2008 +0000 @@ -0,0 +1,835 @@ +from django.db import connection, transaction +from django.db.models import signals, get_model +from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class, FieldDoesNotExist +from django.db.models.related import RelatedObject +from django.db.models.query_utils import QueryWrapper +from django.utils.text import capfirst +from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ +from django.utils.functional import curry +from django.utils.encoding import smart_unicode +from django.core import validators +from django import oldforms +from django import newforms as forms +from django.dispatch import dispatcher + +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + +# Values for Relation.edit_inline. +TABULAR, STACKED = 1, 2 + +RECURSIVE_RELATIONSHIP_CONSTANT = 'self' + +pending_lookups = {} + +def add_lazy_relation(cls, field, relation): + """ + 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. + """ + # 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: + field.rel.to = model + field.do_related_class(model, cls) + else: + key = (app_label, model_name) + value = (cls, field) + pending_lookups.setdefault(key, []).append(value) + +def do_pending_lookups(sender): + """ + Handle any pending relations to the sending model. Sent from class_prepared. + """ + key = (sender._meta.app_label, sender.__name__) + for cls, field in pending_lookups.pop(key, []): + field.rel.to = sender + field.do_related_class(sender, cls) + +dispatcher.connect(do_pending_lookups, signal=signals.class_prepared) + +def manipulator_valid_rel_key(f, self, field_data, all_data): + "Validates that the value is a valid foreign key" + klass = f.rel.to + try: + klass._default_manager.get(**{f.rel.field_name: field_data}) + except klass.DoesNotExist: + raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name + +#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) + other = self.rel.to + if isinstance(other, basestring): + add_lazy_relation(cls, self, other) + else: + self.do_related_class(other, cls) + if not cls._meta.abstract and self.rel.related_name: + self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()} + + def set_attributes_from_rel(self): + self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) + self.verbose_name = self.verbose_name or 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() + related = RelatedObject(other, cls, self) + self.contribute_to_related_class(other, 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 = value + try: + while True: + v = getattr(v, v._meta.pk.name) + except AttributeError: + pass + return v + + if hasattr(value, 'as_sql'): + sql, params = value.as_sql() + return QueryWrapper(('(%s)' % sql), params) + if lookup_type == 'exact': + return [pk_trace(value)] + if lookup_type == '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: + raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name + + 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._default_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 + # Set the value of the related field + setattr(value, self.related.field.rel.get_related_field().attname, instance) + + # Clear the cache, if it exists + try: + delattr(value, self.related.field.get_cache_name()) + except AttributeError: + pass + +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: + raise AttributeError, "%s must be accessed via instance" % self.field.name + 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} + rel_obj = self.field.rel.to._default_manager.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 + # 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) + + # Clear the cache, if it exists + try: + delattr(instance, self.field.get_cache_name()) + except AttributeError: + pass + +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: + raise AttributeError, "Manager must be accessed via instance" + + rel_field = self.related.field + rel_model = self.related.model + + # Dynamically create a class that subclasses the related + # model's default manager. + superclass = self.related.model._default_manager.__class__ + + 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: + setattr(obj, rel_field.name, instance) + obj.save() + add.alters_data = True + + def create(self, **kwargs): + new_obj = self.model(**kwargs) + self.add(new_obj) + return new_obj + 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() + manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)} + manager.model = self.related.model + + return manager + + 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 create_many_related_manager(superclass): + """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._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." % model) + + def get_query_set(self): + return superclass.get_query_set(self).filter(**(self.core_filters)) + + 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): + new_obj = self.model(**kwargs) + new_obj.save() + self.add(new_obj) + return new_obj + 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: + # 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()) + 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: + raise AttributeError, "Manager must be accessed via instance" + + # 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) + + 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" + + 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: + raise AttributeError, "Manager must be accessed via instance" + + # 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) + + 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 instance.__class__ == 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" + + manager = self.__get__(instance) + manager.clear() + manager.add(*value) + +class ManyToOneRel(object): + def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, + max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, + related_name=None, limit_choices_to=None, lookup_overrides=None, + raw_id_admin=False, 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.num_in_admin, self.edit_inline = num_in_admin, edit_inline + self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin + self.num_extra_on_change, self.related_name = num_extra_on_change, 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.raw_id_admin = raw_id_admin + 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, num_in_admin=0, min_num_in_admin=None, + max_num_in_admin=None, num_extra_on_change=None, edit_inline=False, + related_name=None, limit_choices_to=None, lookup_overrides=None, + raw_id_admin=False, parent_link=False): + # NOTE: *_num_in_admin and num_extra_on_change are intentionally + # ignored here. We accept them as parameters only to match the calling + # signature of ManyToOneRel.__init__(). + super(OneToOneRel, self).__init__(to, field_name, num_in_admin, + edit_inline=edit_inline, related_name=related_name, + limit_choices_to=limit_choices_to, + lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin, + parent_link=parent_link) + self.multiple = False + +class ManyToManyRel(object): + def __init__(self, to, num_in_admin=0, related_name=None, + filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True): + self.to = to + self.num_in_admin = num_in_admin + self.related_name = related_name + self.filter_interface = filter_interface + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to + self.edit_inline = False + self.raw_id_admin = raw_id_admin + self.symmetrical = symmetrical + self.multiple = True + + assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" + +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: + to_field = to_field or to._meta.pk.name + kwargs['verbose_name'] = kwargs.get('verbose_name', '') + + if 'edit_inline_type' in kwargs: + import warnings + warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.", DeprecationWarning) + kwargs['edit_inline'] = kwargs.pop('edit_inline_type') + + kwargs['rel'] = rel_class(to, to_field, + num_in_admin=kwargs.pop('num_in_admin', 3), + min_num_in_admin=kwargs.pop('min_num_in_admin', None), + max_num_in_admin=kwargs.pop('max_num_in_admin', None), + num_extra_on_change=kwargs.pop('num_extra_on_change', 1), + edit_inline=kwargs.pop('edit_inline', False), + related_name=kwargs.pop('related_name', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + lookup_overrides=kwargs.pop('lookup_overrides', None), + raw_id_admin=kwargs.pop('raw_id_admin', False), + 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 prepare_field_objs_and_params(self, manipulator, name_prefix): + params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} + if self.rel.raw_id_admin: + field_objs = self.get_manipulator_field_objs() + params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) + else: + if self.radio_admin: + field_objs = [oldforms.RadioSelectField] + params['ul_class'] = get_ul_class(self.radio_admin) + else: + if self.null: + field_objs = [oldforms.NullSelectField] + else: + field_objs = [oldforms.SelectField] + params['choices'] = self.get_choices_default() + return field_objs, params + + 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_manipulator_field_objs(self): + rel_field = self.rel.get_related_field() + if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): + return rel_field.get_manipulator_field_objs() + else: + return [oldforms.IntegerField] + + 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 flatten_data(self, follow, obj=None): + if not obj: + # In required many-to-one fields with only one available choice, + # select that one available choice. Note: For SelectFields + # (radio_admin=False), we have to check that the length of choices + # is *2*, not 1, because SelectFields always have an initial + # "blank" value. Otherwise (radio_admin=True), we check that the + # length is 1. + if not self.blank and (not self.rel.raw_id_admin or self.choices): + choice_list = self.get_choices_default() + if self.radio_admin and len(choice_list) == 1: + return {self.attname: choice_list[0][0]} + if not self.radio_admin and len(choice_list) == 2: + return {self.attname: choice_list[1][0]} + return Field.flatten_data(self, follow, obj) + + def contribute_to_class(self, cls, name): + super(ForeignKey, self).contribute_to_class(cls, name) + setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) + + 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.all()} + 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. + rel_field = self.rel.get_related_field() + if isinstance(rel_field, (AutoField, 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 + if 'num_in_admin' not in kwargs: + kwargs['num_in_admin'] = 0 + 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)) + if not cls._meta.one_to_one_field: + cls._meta.one_to_one_field = self + +class ManyToManyField(RelatedField, Field): + def __init__(self, to, **kwargs): + kwargs['verbose_name'] = kwargs.get('verbose_name', None) + kwargs['rel'] = ManyToManyRel(to, + num_in_admin=kwargs.pop('num_in_admin', 0), + related_name=kwargs.pop('related_name', None), + filter_interface=kwargs.pop('filter_interface', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + raw_id_admin=kwargs.pop('raw_id_admin', False), + symmetrical=kwargs.pop('symmetrical', True)) + self.db_table = kwargs.pop('db_table', None) + if kwargs["rel"].raw_id_admin: + kwargs.setdefault("validator_list", []).append(self.isValidIDList) + Field.__init__(self, **kwargs) + + if self.rel.raw_id_admin: + msg = ugettext_lazy('Separate multiple IDs with commas.') + else: + 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_manipulator_field_objs(self): + if self.rel.raw_id_admin: + return [oldforms.RawIdAdminField] + else: + choices = self.get_choices_default() + return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] + + 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.db_table: + return self.db_table + else: + return '%s_%s' % (opts.db_table, self.name) + + def _get_m2m_column_name(self, related): + "Function that can be curried to provide the source column name for the m2m table" + # If this is an m2m relation to self, avoid the inevitable name clash + if related.model == related.parent_model: + return 'from_' + related.model._meta.object_name.lower() + '_id' + else: + return related.model._meta.object_name.lower() + '_id' + + def _get_m2m_reverse_name(self, related): + "Function that can be curried to provide the related column name for the m2m table" + # If this is an m2m relation to self, avoid the inevitable name clash + if related.model == related.parent_model: + return 'to_' + related.parent_model._meta.object_name.lower() + '_id' + else: + return related.parent_model._meta.object_name.lower() + '_id' + + 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 validators.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 flatten_data(self, follow, obj = None): + new_data = {} + if obj: + instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] + if self.rel.raw_id_admin: + new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids]) + else: + new_data[self.name] = instance_ids + else: + # In required many-to-many fields with only one available choice, + # select that one available choice. + if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin: + choices_list = self.get_choices_default() + if len(choices_list) == 1: + new_data[self.name] = [choices_list[0][0]] + return new_data + + def contribute_to_class(self, cls, 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) + + 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.all()} + 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: + defaults['initial'] = [i._get_pk_val() for i in defaults['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 +