Commit 585b7aca authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #10109 -- Removed the use of raw SQL in many-to-many fields by...

Fixed #10109 -- Removed the use of raw SQL in many-to-many fields by introducing an autogenerated through model.

This is the first part of Alex Gaynor's GSoC project to add Multi-db support to Django.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11710 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent aba53893
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -153,8 +153,9 @@ class BaseModelAdmin(object):
        """
        Get a form Field for a ManyToManyField.
        """
        # If it uses an intermediary model, don't show field in admin.
        if db_field.rel.through is not None:
        # If it uses an intermediary model that isn't auto created, don't show
        # a field in admin.
        if not db_field.rel.through._meta.auto_created:
            return None

        if db_field.name in self.raw_id_fields:
+0 −2
Original line number Diff line number Diff line
@@ -105,8 +105,6 @@ class GenericRelation(RelatedField, Field):
                            limit_choices_to=kwargs.pop('limit_choices_to', None),
                            symmetrical=kwargs.pop('symmetrical', True))

        # By its very nature, a GenericRelation doesn't create a table.
        self.creates_table = False

        # Override content-type/object-id field names on the related class
        self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
+5 −15
Original line number Diff line number Diff line
@@ -57,12 +57,15 @@ class Command(NoArgsCommand):
        # Create the tables for each model
        for app in models.get_apps():
            app_name = app.__name__.split('.')[-2]
            model_list = models.get_models(app)
            model_list = models.get_models(app, include_auto_created=True)
            for model in model_list:
                # Create the model's database table, if it doesn't already exist.
                if verbosity >= 2:
                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
                if connection.introspection.table_name_converter(model._meta.db_table) in tables:
                opts = model._meta
                if (connection.introspection.table_name_converter(opts.db_table) in tables or
                    (opts.auto_created and
                    connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
                    continue
                sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
                seen_models.add(model)
@@ -78,19 +81,6 @@ class Command(NoArgsCommand):
                    cursor.execute(statement)
                tables.append(connection.introspection.table_name_converter(model._meta.db_table))

        # Create the m2m tables. This must be done after all tables have been created
        # to ensure that all referred tables will exist.
        for app in models.get_apps():
            app_name = app.__name__.split('.')[-2]
            model_list = models.get_models(app)
            for model in model_list:
                if model in created_models:
                    sql = connection.creation.sql_for_many_to_many(model, self.style)
                    if sql:
                        if verbosity >= 2:
                            print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
                        for statement in sql:
                            cursor.execute(statement)

        transaction.commit_unless_managed()

+2 −13
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ def sql_create(app, style):
    # We trim models from the current app so that the sqlreset command does not
    # generate invalid SQL (leaving models out of known_models is harmless, so
    # we can be conservative).
    app_models = models.get_models(app)
    app_models = models.get_models(app, include_auto_created=True)
    final_output = []
    tables = connection.introspection.table_names()
    known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
@@ -40,10 +40,6 @@ def sql_create(app, style):
        # Keep track of the fact that we've created the table for this model.
        known_models.add(model)

    # Create the many-to-many join tables.
    for model in app_models:
        final_output.extend(connection.creation.sql_for_many_to_many(model, style))

    # Handle references to tables that are from other apps
    # but don't exist physically.
    not_installed_models = set(pending_references.keys())
@@ -82,7 +78,7 @@ def sql_delete(app, style):
    to_delete = set()

    references_to_delete = {}
    app_models = models.get_models(app)
    app_models = models.get_models(app, include_auto_created=True)
    for model in app_models:
        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
            # The table exists, so it needs to be dropped
@@ -97,13 +93,6 @@ def sql_delete(app, style):
        if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))

    # Output DROP TABLE statements for many-to-many tables.
    for model in app_models:
        opts = model._meta
        for f in opts.local_many_to_many:
            if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))

    # Close database connection explicitly, in case this output is being piped
    # directly into a database client, to avoid locking issues.
    if cursor:
+92 −59
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ def get_validation_errors(outfile, app=None):
                rel_opts = f.rel.to._meta
                rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
                rel_query_name = f.related_query_name()
                if not f.rel.is_hidden():
                    for r in rel_opts.fields:
                        if r.name == rel_name:
                            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))
@@ -117,48 +118,80 @@ def get_validation_errors(outfile, app=None):
            if f.unique:
                e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)

            if getattr(f.rel, 'through', None) is not None:
                if hasattr(f.rel, 'through_model'):
            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
                from_model, to_model = cls, f.rel.to
                    if from_model == to_model and f.rel.symmetrical:
                if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
                    e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
                seen_from, seen_to, seen_self = False, False, 0
                    for inter_field in f.rel.through_model._meta.fields:
                for inter_field in f.rel.through._meta.fields:
                    rel_to = getattr(inter_field.rel, 'to', None)
                    if from_model == to_model: # relation to self
                        if rel_to == from_model:
                            seen_self += 1
                        if seen_self > 2:
                                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))
                            e.add(opts, "Intermediary model %s has more than "
                                "two foreign keys to %s, which is ambiguous "
                                "and is not permitted." % (
                                    f.rel.through._meta.object_name,
                                    from_model._meta.object_name
                                )
                            )
                    else:
                        if rel_to == from_model:
                            if seen_from:
                                    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))
                                e.add(opts, "Intermediary model %s has more "
                                    "than one foreign key to %s, which is "
                                    "ambiguous and is not permitted." % (
                                        f.rel.through._meta.object_name,
                                         from_model._meta.object_name
                                     )
                                 )
                            else:
                                seen_from = True
                        elif rel_to == to_model:
                            if seen_to:
                                    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))
                                e.add(opts, "Intermediary model %s has more "
                                    "than one foreign key to %s, which is "
                                    "ambiguous and is not permitted." % (
                                        f.rel.through._meta.object_name,
                                        rel_to._meta.object_name
                                    )
                                )
                            else:
                                seen_to = True
                    if f.rel.through_model not in models.get_models():
                        e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
                    signature = (f.rel.to, cls, f.rel.through_model)
                if f.rel.through not in models.get_models(include_auto_created=True):
                    e.add(opts, "'%s' specifies an m2m relation through model "
                        "%s, which has not been installed." % (f.name, f.rel.through)
                    )
                signature = (f.rel.to, cls, f.rel.through)
                if signature in seen_intermediary_signatures:
                        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))
                    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._meta.object_name
                        )
                    )
                else:
                    seen_intermediary_signatures.append(signature)
                seen_related_fk, seen_this_fk = False, False
                    for field in f.rel.through_model._meta.fields:
                for field in f.rel.through._meta.fields:
                    if field.rel:
                        if not seen_related_fk and field.rel.to == f.rel.to:
                            seen_related_fk = True
                        elif field.rel.to == cls:
                            seen_this_fk = True
                if not seen_related_fk or not seen_this_fk:
                        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))
                else:
                    e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
                    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._meta.object_name,
                            f.rel.to._meta.object_name, cls._meta.object_name)
                    )
            elif isinstance(f.rel.through, basestring):
                e.add(opts, "'%s' specifies an m2m relation through model %s, "
                    "which has not been installed" % (f.name, f.rel.through)
                )

            rel_opts = f.rel.to._meta
            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
Loading