Ticket #9475: 9475-r11858.diff

File 9475-r11858.diff, 17.8 KB (added by Travis Cline <travis.cline@…>, 5 years ago)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index c055f4e..07c23b1 100644
    a b class BaseModelAdmin(object): 
    154154        """
    155155        Get a form Field for a ManyToManyField.
    156156        """
    157         # If it uses an intermediary model that isn't auto created, don't show
    158         # a field in admin.
    159         if not db_field.rel.through._meta.auto_created:
     157        # If it uses an intermediary model that isn't insertable with just the
     158        # related models, don't show a field in admin.
     159        if not db_field.rel.through._meta.insertable_with_only_relationships:
    160160            return None
    161161
    162162        if db_field.name in self.raw_id_fields:
  • django/contrib/admin/validation.py

    diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
    index 726da65..937fcfd 100644
    a b def validate_base(cls, model): 
    197197        for field in cls.fields:
    198198            check_formfield(cls, model, opts, 'fields', field)
    199199            f = get_field(cls, model, opts, 'fields', field)
    200             if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
     200            if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.insertable_with_only_relationships:
    201201                raise ImproperlyConfigured("'%s.fields' can't include the ManyToManyField "
    202                     "field '%s' because '%s' manually specifies "
    203                     "a 'through' model." % (cls.__name__, field, field))
     202                    "field '%s' because '%s' has a 'through' model that is not marked "
     203                    "as safe to insert." % (cls.__name__, field, field))
    204204        if cls.fieldsets:
    205205            raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
    206206        if len(cls.fields) > len(set(cls.fields)):
    def validate_base(cls, model): 
    228228                    check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
    229229                    try:
    230230                        f = opts.get_field(field)
    231                         if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
     231                        if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.insertable_with_only_relationships:
    232232                            raise ImproperlyConfigured("'%s.fieldsets[%d][1]['fields']' "
    233233                                "can't include the ManyToManyField field '%s' because "
    234                                 "'%s' manually specifies a 'through' model." % (
    235                                     cls.__name__, idx, field, field))
     234                                "'%s' has a 'through' model that is not marked as safe "
     235                                "to insert." % (cls.__name__, idx, field, field))
    236236                    except models.FieldDoesNotExist:
    237237                        # If we can't find a field on the model that matches,
    238238                        # it could be an extra field on the form.
  • django/core/management/validation.py

    diff --git a/django/core/management/validation.py b/django/core/management/validation.py
    index 97164d7..bc0c029 100644
    a b def get_validation_errors(outfile, app=None): 
    120120
    121121            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
    122122                from_model, to_model = cls, f.rel.to
    123                 if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
    124                     e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
     123                if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.insertable_with_only_relationships:
     124                    e.add(opts, "Many-to-many fields with 'through' models that are not marked "
     125                          "as safe to insert cannot be symmetrical.")
    125126                seen_from, seen_to, seen_self = False, False, 0
    126127                for inter_field in f.rel.through._meta.fields:
    127128                    rel_to = getattr(inter_field.rel, 'to', None)
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index fcdda22..e4724d3 100644
    a b def create_many_related_manager(superclass, rel=False): 
    427427        def get_query_set(self):
    428428            return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters))
    429429
    430         # If the ManyToMany relation has an intermediary model,
    431         # the add and remove methods do not exist.
    432         if rel.through._meta.auto_created:
     430        # Check that the through model can be created without additional fields
     431        # before attaching teh add and remove methods
     432        if through._meta.insertable_with_only_relationships:
    433433            def add(self, *objs):
    434434                self._add_items(self.source_field_name, self.target_field_name, *objs)
    435435
    def create_many_related_manager(superclass, rel=False): 
    457457        def create(self, **kwargs):
    458458            # This check needs to be done here, since we can't later remove this
    459459            # from the method lookup table, as we do with add and remove.
    460             if not rel.through._meta.auto_created:
    461                 opts = through._meta
    462                 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
     460            opts = through._meta
     461            if not opts.insertable_with_only_relationships:
     462                raise AttributeError, "Cannot use create() on this ManyToManyField. Use %s.%s's Manager or set insertable_with_only_relationships on its Meta if appropriate." % (opts.app_label, opts.object_name)
    463463            new_obj = super(ManyRelatedManager, self).create(**kwargs)
    464464            self.add(new_obj)
    465465            return new_obj
    class ManyRelatedObjectsDescriptor(object): 
    569569        if instance is None:
    570570            raise AttributeError, "Manager must be accessed via instance"
    571571
    572         if not self.related.field.rel.through._meta.auto_created:
    573             opts = self.related.field.rel.through._meta
    574             raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
     572        opts = self.related.field.rel.through._meta
     573        if not opts.insertable_with_only_relationships:
     574            raise AttributeError, "Cannot set values on this ManyToManyField. Use %s.%s's Manager or set insertable_with_only_relationships on its Meta if appropriate." % (opts.app_label, opts.object_name)
    575575
    576576        manager = self.__get__(instance)
    577577        manager.clear()
    class ReverseManyRelatedObjectsDescriptor(object): 
    619619        if instance is None:
    620620            raise AttributeError, "Manager must be accessed via instance"
    621621
    622         if not self.field.rel.through._meta.auto_created:
    623             opts = self.field.rel.through._meta
    624             raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
     622        opts = self.field.rel.through._meta
     623        if not opts.insertable_with_only_relationships:
     624            raise AttributeError, "Cannot set values on this ManyToManyField. Use %s.%s's Manager or set insertable_with_only_relationships on its Meta if appropriate." % (opts.app_label, opts.object_name)
    625625
    626626        manager = self.__get__(instance)
    627627        manager.clear()
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 05ff54a..f58a8f3 100644
    a b from django.utils.datastructures import SortedDict 
    1818# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
    1919get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
    2020
    21 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    22                  'unique_together', 'permissions', 'get_latest_by',
    23                  'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract', 'managed', 'proxy', 'auto_created')
     21DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 'unique_together',
     22                 'permissions', 'get_latest_by', 'order_with_respect_to',
     23                 'app_label', 'db_tablespace', 'abstract', 'managed', 'proxy',
     24                 'auto_created', 'insertable_with_only_relationships')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
    class Options(object): 
    4848        self.parents = SortedDict()
    4949        self.duplicate_targets = {}
    5050        self.auto_created = False
     51        self.insertable_with_only_relationships = None
    5152
    5253        # To handle various inheritance situations, we need to track where
    5354        # managers came from (concrete or abstract base classes).
    class Options(object): 
    104105            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    105106            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
    106107
     108        if self.insertable_with_only_relationships == None:
     109            self.insertable_with_only_relationships = self.auto_created
    107110
    108111    def _prepare(self, model):
    109112        if self.order_with_respect_to:
  • docs/ref/models/options.txt

    diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
    index d74f835..8b65989 100644
    a b Example:: 
    8686
    8787See the docs for :meth:`~django.db.models.QuerySet.latest` for more.
    8888
     89``insertable_with_only_relationships``
     90-----------------------
     91
     92.. attribute:: Options.insertable_with_only_relationships
     93
     94.. versionadded:: 1.2
     95
     96By default if you specify an explicit model for :attr:`ManyToManyField.through`
     97you lose the convience methods of .add and .remove on field as Django can't be
     98sure it is safe to create new records with only the two model relationships. If
     99you want to retain this behavior you can set this to True.
     100
    89101``managed``
    90102-----------------------
    91103
  • tests/modeltests/invalid_models/models.py

    diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
    index af19963..10657d2 100644
    a b invalid_models.grouptwo: 'secondary' has a manually-defined m2m relation through 
    273273invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed
    274274invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead.
    275275invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted.
    276 invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical.
     276invalid_models.personselfrefm2m: Many-to-many fields with 'through' models that are not marked as safe to insert cannot be symmetrical.
    277277invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted.
    278 invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical.
     278invalid_models.personselfrefm2mexplicit: Many-to-many fields with 'through' models that are not marked as safe to insert cannot be symmetrical.
    279279invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract.
    280280invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract.
    281281invalid_models.uniquem2m: ManyToManyFields cannot be unique.  Remove the unique argument on 'unique_people'.
  • tests/modeltests/m2m_through/models.py

    diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py
    index 16f303d..ad35fca 100644
    a b AttributeError: 'ManyRelatedManager' object has no attribute 'add' 
    133133>>> rock.members.create(name='Anne')
    134134Traceback (most recent call last):
    135135...
    136 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
     136AttributeError: Cannot use create() on this ManyToManyField. Use m2m_through.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    137137
    138138# Remove has similar complications, and is not provided either.
    139139>>> rock.members.remove(jim)
    AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 
    160160>>> rock.members = backup
    161161Traceback (most recent call last):
    162162...
    163 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
     163AttributeError: Cannot set values on this ManyToManyField. Use m2m_through.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    164164
    165165# Let's re-save those instances that we've cleared.
    166166>>> m1.save()
    AttributeError: 'ManyRelatedManager' object has no attribute 'add' 
    184184>>> bob.group_set.create(name='Funk')
    185185Traceback (most recent call last):
    186186...
    187 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
     187AttributeError: Cannot use create() on this ManyToManyField. Use m2m_through.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    188188
    189189# Remove has similar complications, and is not provided either.
    190190>>> jim.group_set.remove(rock)
    AttributeError: 'ManyRelatedManager' object has no attribute 'remove' 
    209209>>> jim.group_set = backup
    210210Traceback (most recent call last):
    211211...
    212 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
     212AttributeError: Cannot set values on this ManyToManyField. Use m2m_through.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    213213
    214214# Let's re-save those instances that we've cleared.
    215215>>> m1.save()
  • tests/regressiontests/admin_validation/models.py

    diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py
    index eb53a9d..a69bb2f 100644
    a b Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha 
    120120>>> validate(BookAdmin, Book)
    121121Traceback (most recent call last):
    122122    ...
    123 ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
     123ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' has a 'through' model that is not marked as safe to insert.
    124124
    125125>>> class FieldsetBookAdmin(admin.ModelAdmin):
    126126...     fieldsets = (
    ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 
    131131>>> validate(FieldsetBookAdmin, Book)
    132132Traceback (most recent call last):
    133133   ...
    134 ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
     134ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' has a 'through' model that is not marked as safe to insert.
    135135
    136136>>> class NestedFieldsetAdmin(admin.ModelAdmin):
    137137...    fieldsets = (
  • tests/regressiontests/m2m_through_regress/models.py

    diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py
    index 56aecd6..9c5b50d 100644
    a b __test__ = {'API_TESTS':""" 
    8484>>> bob.group_set = []
    8585Traceback (most recent call last):
    8686...
    87 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
     87AttributeError: Cannot set values on this ManyToManyField. Use m2m_through_regress.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    8888
    8989>>> roll.members = []
    9090Traceback (most recent call last):
    9191...
    92 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
     92AttributeError: Cannot set values on this ManyToManyField. Use m2m_through_regress.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    9393
    9494>>> rock.members.create(name='Anne')
    9595Traceback (most recent call last):
    9696...
    97 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
     97AttributeError: Cannot use create() on this ManyToManyField. Use m2m_through_regress.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    9898
    9999>>> bob.group_set.create(name='Funk')
    100100Traceback (most recent call last):
    101101...
    102 AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
     102AttributeError: Cannot use create() on this ManyToManyField. Use m2m_through_regress.Membership's Manager or set insertable_with_only_relationships on its Meta if appropriate.
    103103
    104104# Now test that the intermediate with a relationship outside
    105105# the current app (i.e., UserMembership) workds
Back to Top