Django

Code

Changeset 7096

Show
Ignore:
Timestamp:
02/08/08 03:49:17 (10 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Fixed up OneToOneFields? (mostly).

They now share as much code as possible with ForeignKeys?, but behave more or
less as they did before (the backwards incompatible change is that they are no
longer automatically primary keys -- so more than one per model is permitted).

The documentation still uses an example that is better suited to model
inheritance, but that will change in due course. Also, the admin interface
still shows them as read-only fields, which is probably wrong now, but that can
change on newforms-admin after this branch is merged into trunk.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/fields/related.py

    r6730 r7096  
    460460        manager.add(*value) 
    461461 
    462 class ForeignKey(RelatedField, Field): 
    463     empty_strings_allowed = False 
    464     def __init__(self, to, to_field=None, **kwargs): 
    465         try: 
    466             to_name = to._meta.object_name.lower() 
    467         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 
    468             assert isinstance(to, basestring), "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) 
    469         else: 
    470             to_field = to_field or to._meta.pk.name 
    471         kwargs['verbose_name'] = kwargs.get('verbose_name', '') 
    472  
    473         if 'edit_inline_type' in kwargs: 
    474             import warnings 
    475             warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.") 
    476             kwargs['edit_inline'] = kwargs.pop('edit_inline_type') 
    477  
    478         kwargs['rel'] = ManyToOneRel(to, to_field, 
    479             num_in_admin=kwargs.pop('num_in_admin', 3), 
    480             min_num_in_admin=kwargs.pop('min_num_in_admin', None), 
    481             max_num_in_admin=kwargs.pop('max_num_in_admin', None), 
    482             num_extra_on_change=kwargs.pop('num_extra_on_change', 1), 
    483             edit_inline=kwargs.pop('edit_inline', False), 
    484             related_name=kwargs.pop('related_name', None), 
    485             limit_choices_to=kwargs.pop('limit_choices_to', None), 
    486             lookup_overrides=kwargs.pop('lookup_overrides', None), 
    487             raw_id_admin=kwargs.pop('raw_id_admin', False)) 
    488         Field.__init__(self, **kwargs) 
    489  
    490         self.db_index = True 
    491  
    492     def get_attname(self): 
    493         return '%s_id' % self.name 
    494  
    495     def get_validator_unique_lookup_type(self): 
    496         return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) 
    497  
    498     def prepare_field_objs_and_params(self, manipulator, name_prefix): 
    499         params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} 
    500         if self.rel.raw_id_admin: 
    501             field_objs = self.get_manipulator_field_objs() 
    502             params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) 
    503         else: 
    504             if self.radio_admin: 
    505                 field_objs = [oldforms.RadioSelectField] 
    506                 params['ul_class'] = get_ul_class(self.radio_admin) 
    507             else: 
    508                 if self.null: 
    509                     field_objs = [oldforms.NullSelectField] 
    510                 else: 
    511                     field_objs = [oldforms.SelectField] 
    512             params['choices'] = self.get_choices_default() 
    513         return field_objs, params 
    514  
    515     def get_manipulator_field_objs(self): 
    516         rel_field = self.rel.get_related_field() 
    517         if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): 
    518             return rel_field.get_manipulator_field_objs() 
    519         else: 
    520             return [oldforms.IntegerField] 
    521  
    522     def get_db_prep_save(self, value): 
    523         if value == '' or value == None: 
    524             return None 
    525         else: 
    526             return self.rel.get_related_field().get_db_prep_save(value) 
    527  
    528     def flatten_data(self, follow, obj=None): 
    529         if not obj: 
    530             # In required many-to-one fields with only one available choice, 
    531             # select that one available choice. Note: For SelectFields 
    532             # (radio_admin=False), we have to check that the length of choices 
    533             # is *2*, not 1, because SelectFields always have an initial 
    534             # "blank" value. Otherwise (radio_admin=True), we check that the 
    535             # length is 1. 
    536             if not self.blank and (not self.rel.raw_id_admin or self.choices): 
    537                 choice_list = self.get_choices_default() 
    538                 if self.radio_admin and len(choice_list) == 1: 
    539                     return {self.attname: choice_list[0][0]} 
    540                 if not self.radio_admin and len(choice_list) == 2: 
    541                     return {self.attname: choice_list[1][0]} 
    542         return Field.flatten_data(self, follow, obj) 
    543  
    544     def contribute_to_class(self, cls, name): 
    545         super(ForeignKey, self).contribute_to_class(cls, name) 
    546         setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) 
    547  
    548     def contribute_to_related_class(self, cls, related): 
    549         setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 
    550  
    551     def formfield(self, **kwargs): 
    552         defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()} 
    553         defaults.update(kwargs) 
    554         return super(ForeignKey, self).formfield(**defaults) 
    555  
    556     def db_type(self): 
    557         # The database column type of a ForeignKey is the column type 
    558         # of the field to which it points. An exception is if the ForeignKey 
    559         # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, 
    560         # in which case the column type is simply that of an IntegerField. 
    561         rel_field = self.rel.get_related_field() 
    562         if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)): 
    563             return IntegerField().db_type() 
    564         return rel_field.db_type() 
    565  
    566 class OneToOneField(RelatedField, IntegerField): 
    567     def __init__(self, to, to_field=None, **kwargs): 
    568         try: 
    569             to_name = to._meta.object_name.lower() 
    570         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 
    571             assert isinstance(to, basestring), "OneToOneField(%r) is invalid. First parameter to OneToOneField must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) 
    572         else: 
    573             to_field = to_field or to._meta.pk.name 
    574         kwargs['verbose_name'] = kwargs.get('verbose_name', '') 
    575  
    576         if 'edit_inline_type' in kwargs: 
    577             import warnings 
    578             warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.") 
    579             kwargs['edit_inline'] = kwargs.pop('edit_inline_type') 
    580  
    581         kwargs['rel'] = OneToOneRel(to, to_field, 
    582             num_in_admin=kwargs.pop('num_in_admin', 0), 
    583             edit_inline=kwargs.pop('edit_inline', False), 
    584             related_name=kwargs.pop('related_name', None), 
    585             limit_choices_to=kwargs.pop('limit_choices_to', None), 
    586             lookup_overrides=kwargs.pop('lookup_overrides', None), 
    587             raw_id_admin=kwargs.pop('raw_id_admin', False)) 
    588         kwargs['primary_key'] = True 
    589         IntegerField.__init__(self, **kwargs) 
    590  
    591         self.db_index = True 
    592  
    593     def get_attname(self): 
    594         return '%s_id' % self.name 
    595  
    596     def get_validator_unique_lookup_type(self): 
    597         return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) 
    598  
    599     # TODO: Copied from ForeignKey... putting this in RelatedField adversely affects 
    600     # ManyToManyField. This works for now. 
    601     def prepare_field_objs_and_params(self, manipulator, name_prefix): 
    602         params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} 
    603         if self.rel.raw_id_admin: 
    604             field_objs = self.get_manipulator_field_objs() 
    605             params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) 
    606         else: 
    607             if self.radio_admin: 
    608                 field_objs = [oldforms.RadioSelectField] 
    609                 params['ul_class'] = get_ul_class(self.radio_admin) 
    610             else: 
    611                 if self.null: 
    612                     field_objs = [oldforms.NullSelectField] 
    613                 else: 
    614                     field_objs = [oldforms.SelectField] 
    615             params['choices'] = self.get_choices_default() 
    616         return field_objs, params 
    617  
    618     def contribute_to_class(self, cls, name): 
    619         super(OneToOneField, self).contribute_to_class(cls, name) 
    620         setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) 
    621  
    622     def contribute_to_related_class(self, cls, related): 
    623         setattr(cls, related.get_accessor_name(), SingleRelatedObjectDescriptor(related)) 
    624         if not cls._meta.one_to_one_field: 
    625             cls._meta.one_to_one_field = self 
    626  
    627     def formfield(self, **kwargs): 
    628         defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()} 
    629         defaults.update(kwargs) 
    630         return super(OneToOneField, self).formfield(**defaults) 
    631  
    632     def db_type(self): 
    633         # The database column type of a OneToOneField is the column type 
    634         # of the field to which it points. An exception is if the OneToOneField 
    635         # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, 
    636         # in which case the column type is simply that of an IntegerField. 
    637         rel_field = self.rel.get_related_field() 
    638         if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)): 
    639             return IntegerField().db_type() 
    640         return rel_field.db_type() 
    641  
    642 class ManyToManyField(RelatedField, Field): 
    643     def __init__(self, to, **kwargs): 
    644         kwargs['verbose_name'] = kwargs.get('verbose_name', None) 
    645         kwargs['rel'] = ManyToManyRel(to, 
    646             num_in_admin=kwargs.pop('num_in_admin', 0), 
    647             related_name=kwargs.pop('related_name', None), 
    648             filter_interface=kwargs.pop('filter_interface', None), 
    649             limit_choices_to=kwargs.pop('limit_choices_to', None), 
    650             raw_id_admin=kwargs.pop('raw_id_admin', False), 
    651             symmetrical=kwargs.pop('symmetrical', True)) 
    652         self.db_table = kwargs.pop('db_table', None) 
    653         if kwargs["rel"].raw_id_admin: 
    654             kwargs.setdefault("validator_list", []).append(self.isValidIDList) 
    655         Field.__init__(self, **kwargs) 
    656  
    657         if self.rel.raw_id_admin: 
    658             msg = ugettext_lazy('Separate multiple IDs with commas.') 
    659         else: 
    660             msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') 
    661         self.help_text = string_concat(self.help_text, ' ', msg) 
    662  
    663     def get_manipulator_field_objs(self): 
    664         if self.rel.raw_id_admin: 
    665             return [oldforms.RawIdAdminField] 
    666         else: 
    667             choices = self.get_choices_default() 
    668             return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] 
    669  
    670     def get_choices_default(self): 
    671         return Field.get_choices(self, include_blank=False) 
    672  
    673     def _get_m2m_db_table(self, opts): 
    674         "Function that can be curried to provide the m2m table name for this relation" 
    675         if self.db_table: 
    676             return self.db_table 
    677         else: 
    678             return '%s_%s' % (opts.db_table, self.name) 
    679  
    680     def _get_m2m_column_name(self, related): 
    681         "Function that can be curried to provide the source column name for the m2m table" 
    682         # If this is an m2m relation to self, avoid the inevitable name clash 
    683         if related.model == related.parent_model: 
    684             return 'from_' + related.model._meta.object_name.lower() + '_id' 
    685         else: 
    686             return related.model._meta.object_name.lower() + '_id' 
    687  
    688     def _get_m2m_reverse_name(self, related): 
    689         "Function that can be curried to provide the related column name for the m2m table" 
    690         # If this is an m2m relation to self, avoid the inevitable name clash 
    691         if related.model == related.parent_model: 
    692             return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    693         else: 
    694             return related.parent_model._meta.object_name.lower() + '_id' 
    695  
    696     def isValidIDList(self, field_data, all_data): 
    697         "Validates that the value is a valid list of foreign keys" 
    698         mod = self.rel.to 
    699         try: 
    700             pks = map(int, field_data.split(',')) 
    701         except ValueError: 
    702             # the CommaSeparatedIntegerField validator will catch this error 
    703             return 
    704         objects = mod._default_manager.in_bulk(pks) 
    705         if len(objects) != len(pks): 
    706             badkeys = [k for k in pks if k not in objects] 
    707             raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
    708                     "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % { 
    709                 'self': self.verbose_name, 
    710                 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), 
    711             } 
    712  
    713     def flatten_data(self, follow, obj = None): 
    714         new_data = {} 
    715         if obj: 
    716             instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] 
    717             if self.rel.raw_id_admin: 
    718                 new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids]) 
    719             else: 
    720                 new_data[self.name] = instance_ids 
    721         else: 
    722             # In required many-to-many fields with only one available choice, 
    723             # select that one available choice. 
    724             if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin: 
    725                 choices_list = self.get_choices_default() 
    726                 if len(choices_list) == 1: 
    727                     new_data[self.name] = [choices_list[0][0]] 
    728         return new_data 
    729  
    730     def contribute_to_class(self, cls, name): 
    731         super(ManyToManyField, self).contribute_to_class(cls, name) 
    732         # Add the descriptor for the m2m relation 
    733         setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 
    734  
    735         # Set up the accessor for the m2m table name for the relation 
    736         self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 
    737  
    738     def contribute_to_related_class(self, cls, related): 
    739         # m2m relations to self do not have a ManyRelatedObjectsDescriptor, 
    740         # as it would be redundant - unless the field is non-symmetrical. 
    741         if related.model != related.parent_model or not self.rel.symmetrical: 
    742             # Add the descriptor for the m2m relation 
    743             setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) 
    744  
    745         # Set up the accessors for the column names on the m2m table 
    746         self.m2m_column_name = curry(self._get_m2m_column_name, related) 
    747         self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related) 
    748  
    749     def set_attributes_from_rel(self): 
    750         pass 
    751  
    752     def value_from_object(self, obj): 
    753         "Returns the value of this field in the given model instance." 
    754         return getattr(obj, self.attname).all() 
    755  
    756     def save_form_data(self, instance, data): 
    757         setattr(instance, self.attname, data) 
    758          
    759     def formfield(self, **kwargs): 
    760         defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} 
    761         defaults.update(kwargs) 
    762         # If initial is passed in, it's a list of related objects, but the 
    763         # MultipleChoiceField takes a list of IDs. 
    764         if defaults.get('initial') is not None: 
    765             defaults['initial'] = [i._get_pk_val() for i in defaults['initial']] 
    766         return super(ManyToManyField, self).formfield(**defaults) 
    767  
    768     def db_type(self): 
    769         # A ManyToManyField is not represented by a single column, 
    770         # so return None. 
    771         return None 
    772  
    773462class ManyToOneRel(object): 
    774463    def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, 
     
    798487 
    799488class OneToOneRel(ManyToOneRel): 
    800     def __init__(self, to, field_name, num_in_admin=0, edit_inline=False, 
    801         related_name=None, limit_choices_to=None, lookup_overrides=None, 
    802         raw_id_admin=False): 
    803         self.to, self.field_name = to, field_name 
    804         self.num_in_admin, self.edit_inline = num_in_admin, edit_inline 
    805         self.related_name = related_name 
    806         if limit_choices_to is None: 
    807             limit_choices_to = {} 
    808         self.limit_choices_to = limit_choices_to 
    809         self.lookup_overrides = lookup_overrides or {} 
    810         self.raw_id_admin = raw_id_admin 
     489    def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None, 
     490            max_num_in_admin=None, num_extra_on_change=None, edit_inline=False, 
     491            related_name=None, limit_choices_to=None, lookup_overrides=None, 
     492            raw_id_admin=False): 
     493        # NOTE: *_num_in_admin and num_extra_on_change are intentionally 
     494        # ignored here. We accept them as parameters only to match the calling 
     495        # signature of ManyToOneRel.__init__(). 
     496        super(OneToOneRel, self).__init__(to, field_name, num_in_admin, 
     497                edit_inline, related_name, limit_choices_to, lookup_overrides, 
     498                raw_id_admin) 
    811499        self.multiple = False 
    812500 
     
    827515 
    828516        assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" 
     517 
     518class ForeignKey(RelatedField, Field): 
     519    empty_strings_allowed = False 
     520    def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): 
     521        try: 
     522            to_name = to._meta.object_name.lower() 
     523        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 
     524            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) 
     525        else: 
     526            to_field = to_field or to._meta.pk.name 
     527        kwargs['verbose_name'] = kwargs.get('verbose_name', '') 
     528 
     529        if 'edit_inline_type' in kwargs: 
     530            import warnings 
     531            warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.") 
     532            kwargs['edit_inline'] = kwargs.pop('edit_inline_type') 
     533 
     534        kwargs['rel'] = rel_class(to, to_field, 
     535            num_in_admin=kwargs.pop('num_in_admin', 3), 
     536            min_num_in_admin=kwargs.pop('min_num_in_admin', None), 
     537            max_num_in_admin=kwargs.pop('max_num_in_admin', None), 
     538            num_extra_on_change=kwargs.pop('num_extra_on_change', 1), 
     539            edit_inline=kwargs.pop('edit_inline', False), 
     540            related_name=kwargs.pop('related_name', None), 
     541            limit_choices_to=kwargs.pop('limit_choices_to', None), 
     542            lookup_overrides=kwargs.pop('lookup_overrides', None), 
     543            raw_id_admin=kwargs.pop('raw_id_admin', False)) 
     544        Field.__init__(self, **kwargs) 
     545 
     546        self.db_index = True 
     547 
     548    def get_attname(self): 
     549        return '%s_id' % self.name 
     550 
     551    def get_validator_unique_lookup_type(self): 
     552        return '%s__%s__exact' % (self.name, self.rel.get_related_field().name) 
     553 
     554    def prepare_field_objs_and_params(self, manipulator, name_prefix): 
     555        params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname} 
     556        if self.rel.raw_id_admin: 
     557            field_objs = self.get_manipulator_field_objs() 
     558            params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator)) 
     559        else: 
     560            if self.radio_admin: 
     561                field_objs = [oldforms.RadioSelectField] 
     562                params['ul_class'] = get_ul_class(self.radio_admin) 
     563            else: 
     564                if self.null: 
     565                    field_objs = [oldforms.NullSelectField] 
     566                else: 
     567                    field_objs = [oldforms.SelectField] 
     568            params['choices'] = self.get_choices_default() 
     569        return field_objs, params 
     570 
     571    def get_manipulator_field_objs(self): 
     572        rel_field = self.rel.get_related_field() 
     573        if self.rel.raw_id_admin and not isinstance(rel_field, AutoField): 
     574            return rel_field.get_manipulator_field_objs() 
     575        else: 
     576            return [oldforms.IntegerField] 
     577 
     578    def get_db_prep_save(self, value): 
     579        if value == '' or value == None: 
     580            return None 
     581        else: 
     582            return self.rel.get_related_field().get_db_prep_save(value) 
     583 
     584    def flatten_data(self, follow, obj=None): 
     585        if not obj: 
     586            # In required many-to-one fields with only one available choice, 
     587            # select that one available choice. Note: For SelectFields 
     588            # (radio_admin=False), we have to check that the length of choices 
     589            # is *2*, not 1, because SelectFields always have an initial 
     590            # "blank" value. Otherwise (radio_admin=True), we check that the 
     591            # length is 1. 
     592            if not self.blank and (not self.rel.raw_id_admin or self.choices): 
     593                choice_list = self.get_choices_default() 
     594                if self.radio_admin and len(choice_list) == 1: 
     595                    return {self.attname: choice_list[0][0]} 
     596                if not self.radio_admin and len(choice_list) == 2: 
     597                    return {self.attname: choice_list[1][0]} 
     598        return Field.flatten_data(self, follow, obj) 
     599 
     600    def contribute_to_class(self, cls, name): 
     601        super(ForeignKey, self).contribute_to_class(cls, name) 
     602        setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self)) 
     603 
     604    def contribute_to_related_class(self, cls, related): 
     605        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 
     606 
     607    def formfield(self, **kwargs): 
     608        defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()} 
     609        defaults.update(kwargs) 
     610        return super(ForeignKey, self).formfield(**defaults) 
     611 
     612    def db_type(self): 
     613        # The database column type of a ForeignKey is the column type 
     614        # of the field to which it points. An exception is if the ForeignKey 
     615        # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, 
     616        # in which case the column type is simply that of an IntegerField. 
     617        rel_field = self.rel.get_related_field() 
     618        if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)): 
     619            return IntegerField().db_type() 
     620        return rel_field.db_type() 
     621 
     622class OneToOneField(ForeignKey): 
     623    """ 
     624    A OneToOneField is essentially the same as a ForeignKey, with the exception 
     625    that always carries a "unique" constraint with it and the reverse relation 
     626    always returns the object pointed to (since there will only ever be one), 
     627    rather than returning a list. 
     628    """ 
     629    def __init__(self, to, to_field=None, **kwargs): 
     630        kwargs['unique'] = True 
     631        if 'num_in_admin' not in kwargs: 
     632            kwargs['num_in_admin'] = 0 
     633        super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) 
     634 
     635    def contribute_to_related_class(self, cls, related): 
     636        setattr(cls, related.get_accessor_name(), 
     637                SingleRelatedObjectDescriptor(related)) 
     638        if not cls._meta.one_to_one_field: 
     639            cls._meta.one_to_one_field = self 
     640 
     641class ManyToManyField(RelatedField, Field): 
     642    def __init__(self, to, **kwargs): 
     643        kwargs['verbose_name'] = kwargs.get('verbose_name', None) 
     644        kwargs['rel'] = ManyToManyRel(to, 
     645            num_in_admin=kwargs.pop('num_in_admin', 0), 
     646            related_name=kwargs.pop('related_name', None), 
     647            filter_interface=kwargs.pop('filter_interface', None), 
     648            limit_choices_to=kwargs.pop('limit_choices_to', None), 
     649            raw_id_admin=kwargs.pop('raw_id_admin', False), 
     650            symmetrical=kwargs.pop('symmetrical', True)) 
     651        self.db_table = kwargs.pop('db_table', None) 
     652        if kwargs["rel"].raw_id_admin: 
     653            kwargs.setdefault("validator_list", []).append(self.isValidIDList) 
     654        Field.__init__(self, **kwargs) 
     655 
     656        if self.rel.raw_id_admin: 
     657            msg = ugettext_lazy('Separate multiple IDs with commas.') 
     658        else: 
     659            msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') 
     660        self.help_text = string_concat(self.help_text, ' ', msg) 
     661 
     662    def get_manipulator_field_objs(self): 
     663        if self.rel.raw_id_admin: 
     664            return [oldforms.RawIdAdminField] 
     665        else: 
     666            choices = self.get_choices_default() 
     667            return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] 
     668 
     669    def get_choices_default(self): 
     670        return Field.get_choices(self, include_blank=False) 
     671 
     672    def _get_m2m_db_table(self, opts): 
     673        "Function that can be curried to provide the m2m table name for this relation" 
     674        if self.db_table: 
     675            return self.db_table 
     676        else: 
     677            return '%s_%s' % (opts.db_table, self.name) 
     678 
     679    def _get_m2m_column_name(self, related): 
     680        "Function that can be curried to provide the source column name for the m2m table" 
     681        # If this is an m2m relation to self, avoid the inevitable name clash 
     682        if related.model == related.parent_model: 
     683            return 'from_' + related.model._meta.object_name.lower() + '_id' 
     684        else: 
     685            return related.model._meta.object_name.lower() + '_id' 
     686 
     687    def _get_m2m_reverse_name(self, related): 
     688        "Function that can be curried to provide the related column name for the m2m table" 
     689        # If this is an m2m relation to self, avoid the inevitable name clash 
     690        if related.model == related.parent_model: 
     691            return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
     692        else: 
     693            return related.parent_model._meta.object_name.lower() + '_id' 
     694 
     695    def isValidIDList(self, field_data, all_data): 
     696        "Validates that the value is a valid list of foreign keys" 
     697        mod = self.rel.to 
     698        try: 
     699            pks = map(int, field_data.split(',')) 
     700        except ValueError: 
     701            # the CommaSeparatedIntegerField validator will catch this error 
     702            return 
     703        objects = mod._default_manager.in_bulk(pks) 
     704        if len(objects) != len(pks): 
     705            badkeys = [k for k in pks if k not in objects] 
     706            raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.", 
     707                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % { 
     708                'self': self.verbose_name, 
     709                'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys), 
     710            } 
     711 
     712    def flatten_data(self, follow, obj = None): 
     713        new_data = {} 
     714        if obj: 
     715            instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] 
     716            if self.rel.raw_id_admin: 
     717                new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids]) 
     718            else: 
     719                new_data[self.name] = instance_ids 
     720        else: 
     721            # In required many-to-many fields with only one available choice, 
     722            # select that one available choice. 
     723            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin: 
     724                choices_list = self.get_choices_default() 
     725                if len(choices_list) == 1: 
     726                    new_data[self.name] = [choices_list[0][0]] 
     727        return new_data 
     728 
     729    def contribute_to_class(self, cls, name): 
     730        super(ManyToManyField, self).contribute_to_class(cls, name) 
     731        # Add the descriptor for the m2m relation 
     732        setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 
     733 
     734        # Set up the accessor for the m2m table name for the relation 
     735        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 
     736 
     737    def contribute_to_related_class(self, cls, related): 
     738        # m2m relations to self do not have a ManyRelatedObjectsDescriptor, 
     739        # as it would be redundant - unless the field is non-symmetrical. 
     740        if related.model != related.parent_model or not self.rel.symmetrical: 
     741            # Add the descriptor for the m2m relation 
     742            setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) 
     743 
     744        # Set up the accessors for the column names on the m2m table 
     745        self.m2m_column_name = curry(self._get_m2m_column_name, related) 
     746        self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related) 
     747 
     748    def set_attributes_from_rel(self): 
     749        pass 
     750 
     751    def value_from_object(self, obj): 
     752        "Returns the value of this field in the given model instance." 
     753        return getattr(obj, self.attname).all() 
     754 
     755    def save_form_data(self, instance, data): 
     756        setattr(instance, self.attname, data) 
     757         
     758    def formfield(self, **kwargs): 
     759        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} 
     760        defaults.update(kwargs) 
     761        # If initial is passed in, it's a list of related objects, but the 
     762        # MultipleChoiceField takes a list of IDs. 
     763        if defaults.get('initial') is not None: 
     764            defaults['initial'] = [i._get_pk_val() for i in defaults['initial']] 
     765        return super(ManyToManyField, self).formfield(**defaults) 
     766 
     767    def db_type(self): 
     768        # A ManyToManyField is not represented by a single column, 
     769        # so return None. 
     770        return None 
     771 
  • django/branches/queryset-refactor/docs/model-api.txt

    r6954 r7096  
    980980~~~~~~~~~~~~~~~~~~~~~~~~ 
    981981 
    982 The semantics of one-to-one relationships will be changing soon, so we don't 
    983 recommend you use them. If that doesn't scare you away, keep reading. 
    984  
    985982To define a one-to-one relationship, use ``OneToOneField``. You use it just 
    986983like any other ``Field`` type: by including it as a class attribute of your 
     
    10041001models can be made by using a string containing the model name. 
    10051002 
    1006 This ``OneToOneField`` will actually replace the primary key ``id`` field 
    1007 (since one-to-one relations share the same primary key), and will be displayed 
    1008 as a read-only field when you edit an object in the admin interface: 
     1003**New in Django development version:** ``OneToOneField`` classes used to 
     1004automatically become the primary key on a model. This is no longer true, 
     1005although you can manually pass in the ``primary_key`` attribute if you like. 
     1006Thus, it's now possible to have multilpe fields of type ``OneToOneField`` on a 
     1007single model. 
    10091008 
    10101009See the `One-to-one relationship model example`_ for a full example. 
  • django/branches/queryset-refactor/tests/modeltests/one_to_one/models.py

    r5876 r7096  
    1717 
    1818class Restaurant(models.Model): 
    19     place = models.OneToOneField(Place
     19    place = models.OneToOneField(Place, primary_key=True
    2020    serves_hot_dogs = models.BooleanField() 
    2121    serves_pizza = models.BooleanField() 
     
    3838    link = models.OneToOneField(ManualPrimaryKey) 
    3939    name = models.CharField(max_length = 50) 
     40 
     41class MultiModel(models.Model): 
     42    link1 = models.OneToOneField(Place) 
     43    link2 = models.OneToOneField(ManualPrimaryKey) 
     44    name = models.CharField(max_length=50) 
     45 
     46    def __unicode__(self): 
     47        return u"Multimodel %s" % self.name 
    4048 
    4149__test__ = {'API_TESTS':""" 
     
    6472DoesNotExist: Restaurant matching query does not exist. 
    6573 
    66 # Set the place using assignment notation. Because place is the primary key on Restaurant, 
    67 # the save will create a new restaurant 
     74# Set the place using assignment notation. Because place is the primary key on 
     75# Restaurant, the save will create a new restaurant 
    6876>>> r.place = p2 
    6977>>> r.save() 
     
    7381<Place: Ace Hardware the place> 
    7482 
    75 # Set the place back again, using assignment in the reverse direction 
    76 # Need to reget restaurant object first, because the reverse set 
    77 # can't update the existing restaurant instance 
     83# Set the place back again, using assignment in the reverse direction. 
     84# Need to reget restaurant object first, because the reverse set can't update 
     85# the existing restaurant instance 
    7886>>> p1.restaurant = r 
    7987>>> r.save() 
     
    8795# Restaurant.objects.all() just returns the Restaurants, not the Places. 
    8896# Note that there are two restaurants - Ace Hardware the Restaurant was created 
    89 # in the call to r.place = p2. This means there are multiple restaurants referencing 
    90 # a single place... 
     97# in the call to r.place = p2. 
    9198>>> Restaurant.objects.all() 
    9299[<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ace Hardware the restaurant>] 
     
    166173>>> o2 = RelatedModel(link=o1, name="secondary") 
    167174>>> o2.save() 
     175 
     176# You can have multiple one-to-one fields on a model, too. 
     177>>> x1 = MultiModel(link1=p1, link2=o1, name="x1") 
     178>>> x1.save() 
     179>>> o1.multimodel 
     180<MultiModel: Multimodel x1> 
     181 
     182# This will fail because each one-to-one field must be unique (and link2=o1 was 
     183# used for x1, above). 
     184>>> MultiModel(link1=p2, link2=o1, name="x1").save() 
     185Traceback (most recent call last): 
     186    ... 
     187IntegrityError: ... 
    168188"""} 
  • django/branches/queryset-refactor/tests/modeltests/serializers/models.py

    r6954 r7096  
    2323    class Meta: 
    2424        ordering = ('name',) 
    25      
     25 
    2626    def __unicode__(self): 
    2727        return self.name 
     
    4040 
    4141class AuthorProfile(models.Model): 
    42     author = models.OneToOneField(Author
     42    author = models.OneToOneField(Author, primary_key=True
    4343    date_of_birth = models.DateField() 
    44      
     44 
    4545    def __unicode__(self): 
    4646        return u"Profile of %s" % self.author 
    47          
     47 
    4848class Actor(models.Model): 
    4949    name = models.CharField(max_length=20, primary_key=True) 
     
    5151    class Meta: 
    5252        ordering = ('name',) 
    53      
     53 
    5454    def __unicode__(self): 
    5555        return self.name 
    56      
     56 
    5757class Movie(models.Model): 
    5858    actor = models.ForeignKey(Actor) 
     
    6464    def __unicode__(self): 
    6565        return self.title 
    66          
     66 
    6767class Score(models.Model): 
    6868    score = models.FloatField() 
     
    101101 
    102102# Deserializing has a similar interface, except that special DeserializedObject 
    103 # instances are returned.  This is because data might have changed in the  
     103# instances are returned.  This is because data might have changed in the 
    104104# database since the data was serialized (we'll simulate that below). 
    105105>>> for obj in serializers.deserialize("xml", xml): 
     
    149149[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>] 
    150150 
    151 # If you use your own primary key field (such as a OneToOneField),  
     151# If you use your own primary key field (such as a OneToOneField), 
    152152# it doesn't appear in the serialized field list - it replaces the 
    153153# pk identifier. 
     
    187187[{"pk": 1, "model": "serializers.article", "fields": {"headline": "Just kidding; I love TV poker", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 2, "model": "serializers.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:11"}}, {"pk": 3, "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00"}}] 
    188188 
    189 # Every string is serialized as a unicode object, also primary key  
     189# Every string is serialized as a unicode object, also primary key 
    190190# which is 'varchar' 
    191191>>> ac = Actor(name="Zażółć") 
     
    248248<BLANKLINE> 
    249249 
    250 >>> obs = list(serializers.deserialize("yaml", serialized))  
    251 >>> for i in obs:  
     250>>> obs = list(serializers.deserialize("yaml", serialized)) 
     251>>> for i in obs: 
    252252...     print i 
    253253<DeserializedObject: Just kidding; I love TV poker> 
     
    256256""" 
    257257except ImportError: pass 
    258      
     258 
  • django/branches/queryset-refactor/tests/regressiontests/serializers_regress/models.py

    r5876 r7096  
    7878class XMLData(models.Model): 
    7979    data = models.XMLField(null=True) 
    80      
     80 
    8181class Tag(models.Model): 
    8282    """A tag on an item.""" 
     
    9494 
    9595    tags = generic.GenericRelation(Tag) 
    96      
     96 
    9797# The following test classes are all for validation 
    9898# of related objects; in particular, forward, backward, 
    9999# and self references. 
    100      
     100 
    101101class Anchor(models.Model): 
    102     """This is a model that can be used as  
     102    """This is a model that can be used as 
    103103    something for other models to point at""" 
    104      
     104 
    105105    data = models.CharField(max_length=30) 
    106106 
    107107class UniqueAnchor(models.Model): 
    108     """This is a model that can be used as  
     108    """This is a model that can be used as 
    109109    something for other models to point at""" 
    110110 
    111111    data = models.CharField(unique=True, max_length=30) 
    112      
     112 
    113113class FKData(models.Model): 
    114114    data = models.ForeignKey(Anchor, null=True) 
    115      
     115 
    116116class M2MData(models.Model): 
    117117    data = models.ManyToManyField(Anchor, null=True) 
    118      
     118 
    119119class O2OData(models.Model): 
    120     # One to one field can't be null, since it is a PK. 
    121     data = models.OneToOneField(Anchor
     120    # One to one field can't be null here, since it is a PK. 
     121    data = models.OneToOneField(Anchor, primary_key=True
    122122 
    123123class FKSelfData(models.Model): 
    124124    data = models.ForeignKey('self', null=True) 
    125      
     125 
    126126class M2MSelfData(models.Model): 
    127127    data = models.ManyToManyField('self', null=True, symmetrical=False) 
     
    143143class BooleanPKData(models.Model): 
    144144    data = models.BooleanField(primary_key=True) 
    145      
     145 
    146146class CharPKData(models.Model): 
    147147    data = models.CharField(max_length=30, primary_key=True)