Ticket #10109: m2m-refactor.r11683.diff

File m2m-refactor.r11683.diff, 58.8 KB (added by Russell Keith-Magee, 14 years ago)

RC1 of the m2m refactor stemming from Alex's GSoC 2009 project

  • django/contrib/admin/options.py

    diff -r eff0b46ff6fa django/contrib/admin/options.py
    a b  
    153153        """
    154154        Get a form Field for a ManyToManyField.
    155155        """
    156         # If it uses an intermediary model, don't show field in admin.
    157         if db_field.rel.through is not None:
     156        # If it uses an intermediary model that isn't auto created, don't show
     157        # a field in admin.
     158        if not db_field.rel.through._meta.auto_created:
    158159            return None
    159160
    160161        if db_field.name in self.raw_id_fields:
  • django/contrib/contenttypes/generic.py

    diff -r eff0b46ff6fa django/contrib/contenttypes/generic.py
    a b  
    105105                            limit_choices_to=kwargs.pop('limit_choices_to', None),
    106106                            symmetrical=kwargs.pop('symmetrical', True))
    107107
    108         # By its very nature, a GenericRelation doesn't create a table.
    109         self.creates_table = False
    110108
    111109        # Override content-type/object-id field names on the related class
    112110        self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
  • django/core/management/commands/syncdb.py

    diff -r eff0b46ff6fa django/core/management/commands/syncdb.py
    a b  
    5757        # Create the tables for each model
    5858        for app in models.get_apps():
    5959            app_name = app.__name__.split('.')[-2]
    60             model_list = models.get_models(app)
     60            model_list = models.get_models(app, include_auto_created=True)
    6161            for model in model_list:
    6262                # Create the model's database table, if it doesn't already exist.
    6363                if verbosity >= 2:
    6464                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
    65                 if connection.introspection.table_name_converter(model._meta.db_table) in tables:
     65                opts = model._meta
     66                if (connection.introspection.table_name_converter(opts.db_table) in tables or
     67                    (opts.auto_created and
     68                    connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
    6669                    continue
    6770                sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
    6871                seen_models.add(model)
     
    7881                    cursor.execute(statement)
    7982                tables.append(connection.introspection.table_name_converter(model._meta.db_table))
    8083
    81         # Create the m2m tables. This must be done after all tables have been created
    82         # to ensure that all referred tables will exist.
    83         for app in models.get_apps():
    84             app_name = app.__name__.split('.')[-2]
    85             model_list = models.get_models(app)
    86             for model in model_list:
    87                 if model in created_models:
    88                     sql = connection.creation.sql_for_many_to_many(model, self.style)
    89                     if sql:
    90                         if verbosity >= 2:
    91                             print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
    92                         for statement in sql:
    93                             cursor.execute(statement)
    9484
    9585        transaction.commit_unless_managed()
    9686
  • django/core/management/sql.py

    diff -r eff0b46ff6fa django/core/management/sql.py
    a b  
    2323    # We trim models from the current app so that the sqlreset command does not
    2424    # generate invalid SQL (leaving models out of known_models is harmless, so
    2525    # we can be conservative).
    26     app_models = models.get_models(app)
     26    app_models = models.get_models(app, include_auto_created=True)
    2727    final_output = []
    2828    tables = connection.introspection.table_names()
    2929    known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
     
    4040        # Keep track of the fact that we've created the table for this model.
    4141        known_models.add(model)
    4242
    43     # Create the many-to-many join tables.
    44     for model in app_models:
    45         final_output.extend(connection.creation.sql_for_many_to_many(model, style))
    46 
    4743    # Handle references to tables that are from other apps
    4844    # but don't exist physically.
    4945    not_installed_models = set(pending_references.keys())
     
    8278    to_delete = set()
    8379
    8480    references_to_delete = {}
    85     app_models = models.get_models(app)
     81    app_models = models.get_models(app, include_auto_created=True)
    8682    for model in app_models:
    8783        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
    8884            # The table exists, so it needs to be dropped
     
    9793        if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
    9894            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
    9995
    100     # Output DROP TABLE statements for many-to-many tables.
    101     for model in app_models:
    102         opts = model._meta
    103         for f in opts.local_many_to_many:
    104             if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
    105                 output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
    106 
    10796    # Close database connection explicitly, in case this output is being piped
    10897    # directly into a database client, to avoid locking issues.
    10998    if cursor:
  • django/core/management/validation.py

    diff -r eff0b46ff6fa django/core/management/validation.py
    a b  
    7979                rel_opts = f.rel.to._meta
    8080                rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
    8181                rel_query_name = f.related_query_name()
    82                 for r in rel_opts.fields:
    83                     if r.name == rel_name:
    84                         e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
    85                     if r.name == rel_query_name:
    86                         e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
    87                 for r in rel_opts.local_many_to_many:
    88                     if r.name == rel_name:
    89                         e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
    90                     if r.name == rel_query_name:
    91                         e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
    92                 for r in rel_opts.get_all_related_many_to_many_objects():
    93                     if r.get_accessor_name() == rel_name:
    94                         e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
    95                     if r.get_accessor_name() == rel_query_name:
    96                         e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
    97                 for r in rel_opts.get_all_related_objects():
    98                     if r.field is not f:
     82                if not f.rel.is_hidden():
     83                    for r in rel_opts.fields:
     84                        if r.name == rel_name:
     85                            e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
     86                        if r.name == rel_query_name:
     87                            e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
     88                    for r in rel_opts.local_many_to_many:
     89                        if r.name == rel_name:
     90                            e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
     91                        if r.name == rel_query_name:
     92                            e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
     93                    for r in rel_opts.get_all_related_many_to_many_objects():
    9994                        if r.get_accessor_name() == rel_name:
    100                             e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
     95                            e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
    10196                        if r.get_accessor_name() == rel_query_name:
    102                             e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
     97                            e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
     98                    for r in rel_opts.get_all_related_objects():
     99                        if r.field is not f:
     100                            if r.get_accessor_name() == rel_name:
     101                                e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
     102                            if r.get_accessor_name() == rel_query_name:
     103                                e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
    103104
    104105        seen_intermediary_signatures = []
    105106        for i, f in enumerate(opts.local_many_to_many):
     
    117118            if f.unique:
    118119                e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
    119120
    120             if getattr(f.rel, 'through', None) is not None:
    121                 if hasattr(f.rel, 'through_model'):
    122                     from_model, to_model = cls, f.rel.to
    123                     if from_model == to_model and f.rel.symmetrical:
    124                         e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
    125                     seen_from, seen_to, seen_self = False, False, 0
    126                     for inter_field in f.rel.through_model._meta.fields:
    127                         rel_to = getattr(inter_field.rel, 'to', None)
    128                         if from_model == to_model: # relation to self
    129                             if rel_to == from_model:
    130                                 seen_self += 1
    131                             if seen_self > 2:
    132                                 e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
    133                         else:
    134                             if rel_to == from_model:
    135                                 if seen_from:
    136                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
    137                                 else:
    138                                     seen_from = True
    139                             elif rel_to == to_model:
    140                                 if seen_to:
    141                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
    142                                 else:
    143                                     seen_to = True
    144                     if f.rel.through_model not in models.get_models():
    145                         e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
    146                     signature = (f.rel.to, cls, f.rel.through_model)
    147                     if signature in seen_intermediary_signatures:
    148                         e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
     121            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
     122                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.")
     125                seen_from, seen_to, seen_self = False, False, 0
     126                for inter_field in f.rel.through._meta.fields:
     127                    rel_to = getattr(inter_field.rel, 'to', None)
     128                    if from_model == to_model: # relation to self
     129                        if rel_to == from_model:
     130                            seen_self += 1
     131                        if seen_self > 2:
     132                            e.add(opts, "Intermediary model %s has more than "
     133                                "two foreign keys to %s, which is ambiguous "
     134                                "and is not permitted." % (
     135                                    f.rel.through._meta.object_name,
     136                                    from_model._meta.object_name
     137                                )
     138                            )
    149139                    else:
    150                         seen_intermediary_signatures.append(signature)
    151                     seen_related_fk, seen_this_fk = False, False
    152                     for field in f.rel.through_model._meta.fields:
    153                         if field.rel:
    154                             if not seen_related_fk and field.rel.to == f.rel.to:
    155                                 seen_related_fk = True
    156                             elif field.rel.to == cls:
    157                                 seen_this_fk = True
    158                     if not seen_related_fk or not seen_this_fk:
    159                         e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
     140                        if rel_to == from_model:
     141                            if seen_from:
     142                                e.add(opts, "Intermediary model %s has more "
     143                                    "than one foreign key to %s, which is "
     144                                    "ambiguous and is not permitted." % (
     145                                        f.rel.through._meta.object_name,
     146                                         from_model._meta.object_name
     147                                     )
     148                                 )
     149                            else:
     150                                seen_from = True
     151                        elif rel_to == to_model:
     152                            if seen_to:
     153                                e.add(opts, "Intermediary model %s has more "
     154                                    "than one foreign key to %s, which is "
     155                                    "ambiguous and is not permitted." % (
     156                                        f.rel.through._meta.object_name,
     157                                        rel_to._meta.object_name
     158                                    )
     159                                )
     160                            else:
     161                                seen_to = True
     162                if f.rel.through not in models.get_models(include_auto_created=True):
     163                    e.add(opts, "'%s' specifies an m2m relation through model "
     164                        "%s, which has not been installed." % (f.name, f.rel.through)
     165                    )
     166                signature = (f.rel.to, cls, f.rel.through)
     167                if signature in seen_intermediary_signatures:
     168                    e.add(opts, "The model %s has two manually-defined m2m "
     169                        "relations through the model %s, which is not "
     170                        "permitted. Please consider using an extra field on "
     171                        "your intermediary model instead." % (
     172                            cls._meta.object_name,
     173                            f.rel.through._meta.object_name
     174                        )
     175                    )
    160176                else:
    161                     e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
     177                    seen_intermediary_signatures.append(signature)
     178                seen_related_fk, seen_this_fk = False, False
     179                for field in f.rel.through._meta.fields:
     180                    if field.rel:
     181                        if not seen_related_fk and field.rel.to == f.rel.to:
     182                            seen_related_fk = True
     183                        elif field.rel.to == cls:
     184                            seen_this_fk = True
     185                if not seen_related_fk or not seen_this_fk:
     186                    e.add(opts, "'%s' has a manually-defined m2m relation "
     187                        "through model %s, which does not have foreign keys "
     188                        "to %s and %s" % (f.name, f.rel.through._meta.object_name,
     189                            f.rel.to._meta.object_name, cls._meta.object_name)
     190                    )
     191            elif isinstance(f.rel.through, basestring):
     192                e.add(opts, "'%s' specifies an m2m relation through model %s, "
     193                    "which has not been installed" % (f.name, f.rel.through)
     194                )
    162195
    163196            rel_opts = f.rel.to._meta
    164197            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
  • django/core/serializers/python.py

    diff -r eff0b46ff6fa django/core/serializers/python.py
    a b  
    5656        self._current[field.name] = smart_unicode(related, strings_only=True)
    5757
    5858    def handle_m2m_field(self, obj, field):
    59         if field.creates_table:
     59        if field.rel.through._meta.auto_created:
    6060            self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
    6161                               for related in getattr(obj, field.name).iterator()]
    6262
  • django/core/serializers/xml_serializer.py

    diff -r eff0b46ff6fa django/core/serializers/xml_serializer.py
    a b  
    9898        serialized as references to the object's PK (i.e. the related *data*
    9999        is not dumped, just the relation).
    100100        """
    101         if field.creates_table:
     101        if field.rel.through._meta.auto_created:
    102102            self._start_relational_field(field)
    103103            for relobj in getattr(obj, field.name).iterator():
    104104                self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
     
    233233        else:
    234234           pass
    235235    return u"".join(inner_text)
    236 
  • django/db/models/base.py

    diff -r eff0b46ff6fa django/db/models/base.py
    a b  
    429429        else:
    430430            meta = cls._meta
    431431
    432         if origin:
     432        if origin and not meta.auto_created:
    433433            signals.pre_save.send(sender=origin, instance=self, raw=raw)
    434434
    435435        # If we are in a raw save, save the object exactly as presented.
     
    502502                    setattr(self, meta.pk.attname, result)
    503503            transaction.commit_unless_managed()
    504504
    505         if origin:
     505        if origin and not meta.auto_created:
    506506            signals.post_save.send(sender=origin, instance=self,
    507507                created=(not record_exists), raw=raw)
    508508
     
    539539                        rel_descriptor = cls.__dict__[rel_opts_name]
    540540                        break
    541541                else:
    542                     raise AssertionError("Should never get here.")
     542                    # in the case of a hidden fkey just skip it, it'll get
     543                    # processed as an m2m
     544                    if not related.field.rel.is_hidden():
     545                        raise AssertionError("Should never get here.")
     546                    else:
     547                        continue
    543548                delete_qs = rel_descriptor.delete_manager(self).all()
    544549                for sub_obj in delete_qs:
    545550                    sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
  • django/db/models/fields/related.py

    diff -r eff0b46ff6fa django/db/models/fields/related.py
    a b  
    5858            # If we can't split, assume a model in current app
    5959            app_label = cls._meta.app_label
    6060            model_name = relation
     61        except AttributeError:
     62            # If it doesn't have a split it's actually a model class
     63            app_label = relation._meta.app_label
     64            model_name = relation._meta.object_name
    6165
    6266    # Try to look up the related model, and if it's already loaded resolve the
    6367    # string right away. If get_model returns None, it means that the related
     
    96100            self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
    97101
    98102        other = self.rel.to
    99         if isinstance(other, basestring):
     103        if isinstance(other, basestring) or other._meta.pk is None:
    100104            def resolve_related_class(field, model, cls):
    101105                field.rel.to = model
    102106                field.do_related_class(model, cls)
     
    401405
    402406        return manager
    403407
    404 def create_many_related_manager(superclass, through=False):
     408def create_many_related_manager(superclass, rel=False):
    405409    """Creates a manager that subclasses 'superclass' (which is a Manager)
    406410    and adds behavior for many-to-many related objects."""
     411    through = rel.through
    407412    class ManyRelatedManager(superclass):
    408413        def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
    409                 join_table=None, source_col_name=None, target_col_name=None):
     414                join_table=None, source_field_name=None, target_field_name=None):
    410415            super(ManyRelatedManager, self).__init__()
    411416            self.core_filters = core_filters
    412417            self.model = model
    413418            self.symmetrical = symmetrical
    414419            self.instance = instance
    415             self.join_table = join_table
    416             self.source_col_name = source_col_name
    417             self.target_col_name = target_col_name
     420            self.source_field_name = source_field_name
     421            self.target_field_name = target_field_name
    418422            self.through = through
    419             self._pk_val = self.instance._get_pk_val()
     423            self._pk_val = self.instance.pk
    420424            if self._pk_val is None:
    421425                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
    422426
     
    425429
    426430        # If the ManyToMany relation has an intermediary model,
    427431        # the add and remove methods do not exist.
    428         if through is None:
     432        if rel.through._meta.auto_created:
    429433            def add(self, *objs):
    430                 self._add_items(self.source_col_name, self.target_col_name, *objs)
     434                self._add_items(self.source_field_name, self.target_field_name, *objs)
    431435
    432436                # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
    433437                if self.symmetrical:
    434                     self._add_items(self.target_col_name, self.source_col_name, *objs)
     438                    self._add_items(self.target_field_name, self.source_field_name, *objs)
    435439            add.alters_data = True
    436440
    437441            def remove(self, *objs):
    438                 self._remove_items(self.source_col_name, self.target_col_name, *objs)
     442                self._remove_items(self.source_field_name, self.target_field_name, *objs)
    439443
    440444                # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
    441445                if self.symmetrical:
    442                     self._remove_items(self.target_col_name, self.source_col_name, *objs)
     446                    self._remove_items(self.target_field_name, self.source_field_name, *objs)
    443447            remove.alters_data = True
    444448
    445449        def clear(self):
    446             self._clear_items(self.source_col_name)
     450            self._clear_items(self.source_field_name)
    447451
    448452            # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table
    449453            if self.symmetrical:
    450                 self._clear_items(self.target_col_name)
     454                self._clear_items(self.target_field_name)
    451455        clear.alters_data = True
    452456
    453457        def create(self, **kwargs):
    454458            # This check needs to be done here, since we can't later remove this
    455459            # from the method lookup table, as we do with add and remove.
    456             if through is not None:
    457                 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
     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)
    458463            new_obj = super(ManyRelatedManager, self).create(**kwargs)
    459464            self.add(new_obj)
    460465            return new_obj
     
    470475            return obj, created
    471476        get_or_create.alters_data = True
    472477
    473         def _add_items(self, source_col_name, target_col_name, *objs):
     478        def _add_items(self, source_field_name, target_field_name, *objs):
    474479            # join_table: name of the m2m link table
    475             # source_col_name: the PK colname in join_table for the source object
    476             # target_col_name: the PK colname in join_table for the target object
     480            # source_field_name: the PK fieldname in join_table for the source object
     481            # target_col_name: the PK fielname in join_table for the target object
    477482            # *objs - objects to add. Either object instances, or primary keys of object instances.
    478483
    479484            # If there aren't any objects, there is nothing to do.
     485            from django.db.models import Model
    480486            if objs:
    481                 from django.db.models.base import Model
    482                 # Check that all the objects are of the right type
    483487                new_ids = set()
    484488                for obj in objs:
    485489                    if isinstance(obj, self.model):
    486                         new_ids.add(obj._get_pk_val())
     490                        new_ids.add(obj.pk)
    487491                    elif isinstance(obj, Model):
    488492                        raise TypeError, "'%s' instance expected" % self.model._meta.object_name
    489493                    else:
    490494                        new_ids.add(obj)
    491                 # Add the newly created or already existing objects to the join table.
    492                 # First find out which items are already added, to avoid adding them twice
    493                 cursor = connection.cursor()
    494                 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
    495                     (target_col_name, self.join_table, source_col_name,
    496                     target_col_name, ",".join(['%s'] * len(new_ids))),
    497                     [self._pk_val] + list(new_ids))
    498                 existing_ids = set([row[0] for row in cursor.fetchall()])
     495                vals = self.through._default_manager.values_list(target_field_name, flat=True)
     496                vals = vals.filter(**{
     497                    source_field_name: self._pk_val,
     498                    '%s__in' % target_field_name: new_ids,
     499                })
     500                vals = set(vals)
    499501
    500502                # Add the ones that aren't there already
    501                 for obj_id in (new_ids - existing_ids):
    502                     cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
    503                         (self.join_table, source_col_name, target_col_name),
    504                         [self._pk_val, obj_id])
    505                 transaction.commit_unless_managed()
     503                for obj_id in (new_ids - vals):
     504                    self.through._default_manager.create(**{
     505                        '%s_id' % source_field_name: self._pk_val,
     506                        '%s_id' % target_field_name: obj_id,
     507                    })
    506508
    507         def _remove_items(self, source_col_name, target_col_name, *objs):
     509        def _remove_items(self, source_field_name, target_field_name, *objs):
    508510            # source_col_name: the PK colname in join_table for the source object
    509511            # target_col_name: the PK colname in join_table for the target object
    510512            # *objs - objects to remove
     
    515517                old_ids = set()
    516518                for obj in objs:
    517519                    if isinstance(obj, self.model):
    518                         old_ids.add(obj._get_pk_val())
     520                        old_ids.add(obj.pk)
    519521                    else:
    520522                        old_ids.add(obj)
    521523                # Remove the specified objects from the join table
    522                 cursor = connection.cursor()
    523                 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
    524                     (self.join_table, source_col_name,
    525                     target_col_name, ",".join(['%s'] * len(old_ids))),
    526                     [self._pk_val] + list(old_ids))
    527                 transaction.commit_unless_managed()
     524                self.through._default_manager.filter(**{
     525                    source_field_name: self._pk_val,
     526                    '%s__in' % target_field_name: old_ids
     527                }).delete()
    528528
    529         def _clear_items(self, source_col_name):
     529        def _clear_items(self, source_field_name):
    530530            # source_col_name: the PK colname in join_table for the source object
    531             cursor = connection.cursor()
    532             cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
    533                 (self.join_table, source_col_name),
    534                 [self._pk_val])
    535             transaction.commit_unless_managed()
     531            self.through._default_manager.filter(**{
     532                source_field_name: self._pk_val
     533            }).delete()
    536534
    537535    return ManyRelatedManager
    538536
     
    554552        # model's default manager.
    555553        rel_model = self.related.model
    556554        superclass = rel_model._default_manager.__class__
    557         RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
     555        RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
    558556
    559         qn = connection.ops.quote_name
    560557        manager = RelatedManager(
    561558            model=rel_model,
    562559            core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
    563560            instance=instance,
    564561            symmetrical=False,
    565             join_table=qn(self.related.field.m2m_db_table()),
    566             source_col_name=qn(self.related.field.m2m_reverse_name()),
    567             target_col_name=qn(self.related.field.m2m_column_name())
     562            source_field_name=self.related.field.m2m_reverse_field_name(),
     563            target_field_name=self.related.field.m2m_field_name()
    568564        )
    569565
    570566        return manager
     
    573569        if instance is None:
    574570            raise AttributeError, "Manager must be accessed via instance"
    575571
    576         through = getattr(self.related.field.rel, 'through', None)
    577         if through is not None:
    578             raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
     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)
    579575
    580576        manager = self.__get__(instance)
    581577        manager.clear()
     
    599595        # model's default manager.
    600596        rel_model=self.field.rel.to
    601597        superclass = rel_model._default_manager.__class__
    602         RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
     598        RelatedManager = create_many_related_manager(superclass, self.field.rel)
    603599
    604         qn = connection.ops.quote_name
    605600        manager = RelatedManager(
    606601            model=rel_model,
    607602            core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
    608603            instance=instance,
    609604            symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)),
    610             join_table=qn(self.field.m2m_db_table()),
    611             source_col_name=qn(self.field.m2m_column_name()),
    612             target_col_name=qn(self.field.m2m_reverse_name())
     605            source_field_name=self.field.m2m_field_name(),
     606            target_field_name=self.field.m2m_reverse_field_name()
    613607        )
    614608
    615609        return manager
     
    618612        if instance is None:
    619613            raise AttributeError, "Manager must be accessed via instance"
    620614
    621         through = getattr(self.field.rel, 'through', None)
    622         if through is not None:
    623             raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s's Manager instead." % through
     615        if not self.field.rel.through._meta.auto_created:
     616            opts = self.field.rel.through._meta
     617            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)
    624618
    625619        manager = self.__get__(instance)
    626620        manager.clear()
     
    642636        self.multiple = True
    643637        self.parent_link = parent_link
    644638
     639    def is_hidden(self):
     640        "Should the related object be hidden?"
     641        return self.related_name and self.related_name[-1] == '+'
     642
    645643    def get_related_field(self):
    646644        """
    647645        Returns the Field in the 'to' object to which this relationship is
     
    673671        self.multiple = True
    674672        self.through = through
    675673
     674    def is_hidden(self):
     675        "Should the related object be hidden?"
     676        return self.related_name and self.related_name[-1] == '+'
     677
    676678    def get_related_field(self):
    677679        """
    678680        Returns the field in the to' object to which this relationship is tied
     
    690692            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)
    691693        else:
    692694            assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
    693             to_field = to_field or to._meta.pk.name
    694695        kwargs['verbose_name'] = kwargs.get('verbose_name', None)
    695696
    696697        kwargs['rel'] = rel_class(to, to_field,
     
    743744        cls._meta.duplicate_targets[self.column] = (target, "o2m")
    744745
    745746    def contribute_to_related_class(self, cls, related):
    746         setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
     747        # Internal FK's - i.e., those with a related name ending with '+' -
     748        # don't get a related descriptor.
     749        if not self.rel.is_hidden():
     750            setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
     751        if self.rel.field_name is None:
     752            self.rel.field_name = cls._meta.pk.name
    747753
    748754    def formfield(self, **kwargs):
    749755        defaults = {
     
    790796            return None
    791797        return super(OneToOneField, self).formfield(**kwargs)
    792798
     799def create_many_to_many_intermediary_model(field, klass):
     800    from django.db import models
     801    managed = True
     802    if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
     803        to = field.rel.to
     804        to_model = field.rel.to
     805        def set_managed(field, model, cls):
     806            field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
     807        add_lazy_relation(klass, field, to_model, set_managed)
     808    elif isinstance(field.rel.to, basestring):
     809        to = klass._meta.object_name
     810        to_model = klass
     811        managed = klass._meta.managed
     812    else:
     813        to = field.rel.to._meta.object_name
     814        to_model = field.rel.to
     815        managed = klass._meta.managed or to_model._meta.managed
     816    meta = type('Meta', (object,), {
     817        'db_table': field._get_m2m_db_table(klass._meta),
     818        'managed': managed,
     819        'auto_created': klass,
     820    })
     821    name = '%s_%s' % (klass._meta.object_name, field.name)
     822    if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name:
     823        from_ = 'from_%s' % to.lower()
     824        to = to.lower()
     825    else:
     826        from_ = klass._meta.object_name.lower()
     827        to = to.lower()
     828    return type(name, (models.Model,), {
     829        'Meta': meta,
     830        '__module__': klass.__module__,
     831        from_: models.ForeignKey(klass, related_name='%s+' % name),
     832        to: models.ForeignKey(to_model, related_name='%s+' % name)
     833    })
     834
    793835class ManyToManyField(RelatedField, Field):
    794836    def __init__(self, to, **kwargs):
    795837        try:
     
    806848
    807849        self.db_table = kwargs.pop('db_table', None)
    808850        if kwargs['rel'].through is not None:
    809             self.creates_table = False
    810851            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    811         else:
    812             self.creates_table = True
    813852
    814853        Field.__init__(self, **kwargs)
    815854
     
    822861    def _get_m2m_db_table(self, opts):
    823862        "Function that can be curried to provide the m2m table name for this relation"
    824863        if self.rel.through is not None:
    825             return self.rel.through_model._meta.db_table
     864            return self.rel.through._meta.db_table
    826865        elif self.db_table:
    827866            return self.db_table
    828867        else:
    829868            return util.truncate_name('%s_%s' % (opts.db_table, self.name),
    830869                                      connection.ops.max_name_length())
    831870
    832     def _get_m2m_column_name(self, related):
     871    def _get_m2m_attr(self, related, attr):
    833872        "Function that can be curried to provide the source column name for the m2m table"
    834         try:
    835             return self._m2m_column_name_cache
    836         except:
    837             if self.rel.through is not None:
    838                 for f in self.rel.through_model._meta.fields:
    839                     if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
    840                         self._m2m_column_name_cache = f.column
     873        cache_attr = '_m2m_%s_cache' % attr
     874        if hasattr(self, cache_attr):
     875            return getattr(self, cache_attr)
     876        for f in self.rel.through._meta.fields:
     877            if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
     878                setattr(self, cache_attr, getattr(f, attr))
     879                return getattr(self, cache_attr)
     880
     881    def _get_m2m_reverse_attr(self, related, attr):
     882        "Function that can be curried to provide the related column name for the m2m table"
     883        cache_attr = '_m2m_reverse_%s_cache' % attr
     884        if hasattr(self, cache_attr):
     885            return getattr(self, cache_attr)
     886        found = False
     887        for f in self.rel.through._meta.fields:
     888            if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
     889                if related.model == related.parent_model:
     890                    # If this is an m2m-intermediate to self,
     891                    # the first foreign key you find will be
     892                    # the source column. Keep searching for
     893                    # the second foreign key.
     894                    if found:
     895                        setattr(self, cache_attr, getattr(f, attr))
    841896                        break
    842             # If this is an m2m relation to self, avoid the inevitable name clash
    843             elif related.model == related.parent_model:
    844                 self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id'
    845             else:
    846                 self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id'
    847 
    848             # Return the newly cached value
    849             return self._m2m_column_name_cache
    850 
    851     def _get_m2m_reverse_name(self, related):
    852         "Function that can be curried to provide the related column name for the m2m table"
    853         try:
    854             return self._m2m_reverse_name_cache
    855         except:
    856             if self.rel.through is not None:
    857                 found = False
    858                 for f in self.rel.through_model._meta.fields:
    859                     if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
    860                         if related.model == related.parent_model:
    861                             # If this is an m2m-intermediate to self,
    862                             # the first foreign key you find will be
    863                             # the source column. Keep searching for
    864                             # the second foreign key.
    865                             if found:
    866                                 self._m2m_reverse_name_cache = f.column
    867                                 break
    868                             else:
    869                                 found = True
    870                         else:
    871                             self._m2m_reverse_name_cache = f.column
    872                             break
    873             # If this is an m2m relation to self, avoid the inevitable name clash
    874             elif related.model == related.parent_model:
    875                 self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id'
    876             else:
    877                 self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id'
    878 
    879             # Return the newly cached value
    880             return self._m2m_reverse_name_cache
     897                    else:
     898                        found = True
     899                else:
     900                    setattr(self, cache_attr, getattr(f, attr))
     901                    break
     902        return getattr(self, cache_attr)
    881903
    882904    def isValidIDList(self, field_data, all_data):
    883905        "Validates that the value is a valid list of foreign keys"
     
    919941        # specify *what* on my non-reversible relation?!"), so we set it up
    920942        # automatically. The funky name reduces the chance of an accidental
    921943        # clash.
    922         if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None:
     944        if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
    923945            self.rel.related_name = "%s_rel_+" % name
    924946
    925947        super(ManyToManyField, self).contribute_to_class(cls, name)
     948
     949        if not self.rel.through:
     950            self.rel.through = create_many_to_many_intermediary_model(self, cls)
     951
    926952        # Add the descriptor for the m2m relation
    927953        setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
    928954
     
    933959        # work correctly.
    934960        if isinstance(self.rel.through, basestring):
    935961            def resolve_through_model(field, model, cls):
    936                 field.rel.through_model = model
     962                field.rel.through = model
    937963            add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
    938         elif self.rel.through:
    939             self.rel.through_model = self.rel.through
    940             self.rel.through = self.rel.through._meta.object_name
    941964
    942965        if isinstance(self.rel.to, basestring):
    943966            target = self.rel.to
     
    946969        cls._meta.duplicate_targets[self.column] = (target, "m2m")
    947970
    948971    def contribute_to_related_class(self, cls, related):
    949         # m2m relations to self do not have a ManyRelatedObjectsDescriptor,
    950         # as it would be redundant - unless the field is non-symmetrical.
    951         if related.model != related.parent_model or not self.rel.symmetrical:
    952             # Add the descriptor for the m2m relation
     972        # Internal M2Ms (i.e., those with a related name ending with '+')
     973        # don't get a related descriptor.
     974        if not self.rel.is_hidden():
    953975            setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
    954976
    955977        # Set up the accessors for the column names on the m2m table
    956         self.m2m_column_name = curry(self._get_m2m_column_name, related)
    957         self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related)
     978        self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')
     979        self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
     980
     981        self.m2m_field_name = curry(self._get_m2m_attr, related, 'name')
     982        self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name')
    958983
    959984    def set_attributes_from_rel(self):
    960985        pass
  • django/db/models/loading.py

    diff -r eff0b46ff6fa django/db/models/loading.py
    a b  
    131131        self._populate()
    132132        return self.app_errors
    133133
    134     def get_models(self, app_mod=None):
     134    def get_models(self, app_mod=None, include_auto_created=False):
    135135        """
    136136        Given a module containing models, returns a list of the models.
    137137        Otherwise returns a list of all installed models.
     138
     139        By default, auto-created models (i.e., m2m models without an
     140        explicit intermediate table) are not included. However, if you
     141        specify include_auto_created=True, they will be.
    138142        """
    139143        self._populate()
    140144        if app_mod:
    141             return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
     145            model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
    142146        else:
    143147            model_list = []
    144148            for app_entry in self.app_models.itervalues():
    145149                model_list.extend(app_entry.values())
    146             return model_list
     150        if not include_auto_created:
     151            return filter(lambda o: not o._meta.auto_created, model_list)
     152        return model_list
    147153
    148154    def get_model(self, app_label, model_name, seed_cache=True):
    149155        """
  • django/db/models/options.py

    diff -r eff0b46ff6fa django/db/models/options.py
    a b  
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract', 'managed', 'proxy')
     24                 'abstract', 'managed', 'proxy', 'auto_created')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    4747        self.proxy_for_model = None
    4848        self.parents = SortedDict()
    4949        self.duplicate_targets = {}
     50        self.auto_created = False
    5051
    5152        # To handle various inheritance situations, we need to track where
    5253        # managers came from (concrete or abstract base classes).
     
    487488        Returns the index of the primary key field in the self.fields list.
    488489        """
    489490        return self.fields.index(self.pk)
    490 
  • django/db/models/query.py

    diff -r eff0b46ff6fa django/db/models/query.py
    a b  
    10281028
    10291029            # Pre-notify all instances to be deleted.
    10301030            for pk_val, instance in items:
    1031                 signals.pre_delete.send(sender=cls, instance=instance)
     1031                if not cls._meta.auto_created:
     1032                    signals.pre_delete.send(sender=cls, instance=instance)
    10321033
    10331034            pk_list = [pk for pk,instance in items]
    10341035            del_query = sql.DeleteQuery(cls, connection)
     
    10621063                    if field.rel and field.null and field.rel.to in seen_objs:
    10631064                        setattr(instance, field.attname, None)
    10641065
    1065                 signals.post_delete.send(sender=cls, instance=instance)
     1066                if not cls._meta.auto_created:
     1067                    signals.post_delete.send(sender=cls, instance=instance)
    10661068                setattr(instance, cls._meta.pk.attname, None)
    10671069
    10681070        if forced_managed:
  • docs/internals/deprecation.txt

    diff -r eff0b46ff6fa docs/internals/deprecation.txt
    a b  
    2222        * The old imports for CSRF functionality (``django.contrib.csrf.*``),
    2323          which moved to core in 1.2, will be removed.
    2424
     25    * 1.4
     26        * The many to many SQL generation functions on the database backends
     27          will be removed.  These have been deprecated since the 1.2 release.
     28
    2529    * 2.0
    2630        * ``django.views.defaults.shortcut()``. This function has been moved
    2731          to ``django.contrib.contenttypes.views.shortcut()`` as part of the
  • tests/modeltests/invalid_models/models.py

    diff -r eff0b46ff6fa tests/modeltests/invalid_models/models.py
    a b  
    182182    """ Model to test for unique ManyToManyFields, which are invalid. """
    183183    unique_people = models.ManyToManyField( Person, unique=True )
    184184
     185
    185186model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
    186187invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
    187188invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
  • tests/modeltests/m2m_through/models.py

    diff -r eff0b46ff6fa tests/modeltests/m2m_through/models.py
    a b  
    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 Membership's Manager instead.
     136AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
    137137
    138138# Remove has similar complications, and is not provided either.
    139139>>> rock.members.remove(jim)
     
    160160>>> rock.members = backup
    161161Traceback (most recent call last):
    162162...
    163 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
     163AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
    164164
    165165# Let's re-save those instances that we've cleared.
    166166>>> m1.save()
     
    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 Membership's Manager instead.
     187AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
    188188
    189189# Remove has similar complications, and is not provided either.
    190190>>> jim.group_set.remove(rock)
     
    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 Membership's Manager instead.
     212AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
    213213
    214214# Let's re-save those instances that we've cleared.
    215215>>> m1.save()
     
    334334# QuerySet's distinct() method can correct this problem.
    335335>>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct()
    336336[<Person: Jane>, <Person: Jim>]
    337 """}
    338  No newline at end of file
     337"""}
  • tests/regressiontests/m2m_through_regress/models.py

    diff -r eff0b46ff6fa tests/regressiontests/m2m_through_regress/models.py
    a b  
    8484>>> bob.group_set = []
    8585Traceback (most recent call last):
    8686...
    87 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
     87AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
    8888
    8989>>> roll.members = []
    9090Traceback (most recent call last):
    9191...
    92 AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
     92AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
    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 Membership's Manager instead.
     97AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
    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 Membership's Manager instead.
     102AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
    103103
    104104# Now test that the intermediate with a relationship outside
    105105# the current app (i.e., UserMembership) workds
  • tests/regressiontests/serializers_regress/models.py

    diff -r eff0b46ff6fa tests/regressiontests/serializers_regress/models.py
    a b  
    105105
    106106    data = models.CharField(max_length=30)
    107107
     108    class Meta:
     109        ordering = ('id',)
     110
    108111class UniqueAnchor(models.Model):
    109112    """This is a model that can be used as
    110113    something for other models to point at"""
     
    135138
    136139class M2MIntermediateData(models.Model):
    137140    data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
    138    
     141
    139142class Intermediate(models.Model):
    140143    left = models.ForeignKey(M2MIntermediateData)
    141144    right = models.ForeignKey(Anchor)
     
    242245
    243246class InheritAbstractModel(AbstractBaseModel):
    244247    child_data = models.IntegerField()
    245    
     248
    246249class BaseModel(models.Model):
    247250    parent_data = models.IntegerField()
    248251
     
    252255class ExplicitInheritBaseModel(BaseModel):
    253256    parent = models.OneToOneField(BaseModel)
    254257    child_data = models.IntegerField()
    255    
    256  No newline at end of file
  • new file tests/regressiontests/signals_regress/models.py

    diff -r eff0b46ff6fa tests/regressiontests/signals_regress/models.py
    - +  
     1"""
     2Testing signals before/after saving and deleting.
     3"""
     4
     5from django.db import models
     6
     7class Author(models.Model):
     8    name = models.CharField(max_length=20)
     9
     10    def __unicode__(self):
     11        return self.name
     12
     13class Book(models.Model):
     14    name = models.CharField(max_length=20)
     15    authors = models.ManyToManyField(Author)
     16
     17    def __unicode__(self):
     18        return self.name
     19
     20def pre_save_test(signal, sender, instance, **kwargs):
     21    print 'pre_save signal,', instance
     22    if kwargs.get('raw'):
     23        print 'Is raw'
     24
     25def post_save_test(signal, sender, instance, **kwargs):
     26    print 'post_save signal,', instance
     27    if 'created' in kwargs:
     28        if kwargs['created']:
     29            print 'Is created'
     30        else:
     31            print 'Is updated'
     32    if kwargs.get('raw'):
     33        print 'Is raw'
     34
     35def pre_delete_test(signal, sender, instance, **kwargs):
     36    print 'pre_delete signal,', instance
     37    print 'instance.id is not None: %s' % (instance.id != None)
     38
     39def post_delete_test(signal, sender, instance, **kwargs):
     40    print 'post_delete signal,', instance
     41    print 'instance.id is not None: %s' % (instance.id != None)
     42
     43__test__ = {'API_TESTS':"""
     44
     45# Save up the number of connected signals so that we can check at the end
     46# that all the signals we register get properly unregistered (#9989)
     47>>> pre_signals = (len(models.signals.pre_save.receivers),
     48...                len(models.signals.post_save.receivers),
     49...                len(models.signals.pre_delete.receivers),
     50...                len(models.signals.post_delete.receivers))
     51
     52>>> models.signals.pre_save.connect(pre_save_test)
     53>>> models.signals.post_save.connect(post_save_test)
     54>>> models.signals.pre_delete.connect(pre_delete_test)
     55>>> models.signals.post_delete.connect(post_delete_test)
     56
     57>>> a1 = Author(name='Neal Stephenson')
     58>>> a1.save()
     59pre_save signal, Neal Stephenson
     60post_save signal, Neal Stephenson
     61Is created
     62
     63>>> b1 = Book(name='Snow Crash')
     64>>> b1.save()
     65pre_save signal, Snow Crash
     66post_save signal, Snow Crash
     67Is created
     68
     69# Assigning to m2m shouldn't generate an m2m signal
     70>>> b1.authors = [a1]
     71
     72# Removing an author from an m2m shouldn't generate an m2m signal
     73>>> b1.authors = []
     74
     75>>> models.signals.post_delete.disconnect(post_delete_test)
     76>>> models.signals.pre_delete.disconnect(pre_delete_test)
     77>>> models.signals.post_save.disconnect(post_save_test)
     78>>> models.signals.pre_save.disconnect(pre_save_test)
     79
     80# Check that all our signals got disconnected properly.
     81>>> post_signals = (len(models.signals.pre_save.receivers),
     82...                 len(models.signals.post_save.receivers),
     83...                 len(models.signals.pre_delete.receivers),
     84...                 len(models.signals.post_delete.receivers))
     85
     86>>> pre_signals == post_signals
     87True
     88
     89"""}
Back to Top