Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 585b7ac

Browse files
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
1 parent aba5389 commit 585b7ac

File tree

20 files changed

+459
-246
lines changed

20 files changed

+459
-246
lines changed

‎django/contrib/admin/options.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ def formfield_for_manytomany(self, db_field, request=None, **kwargs):
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‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ def __init__(self, to, **kwargs):
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‎

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ def handle_noargs(self, **options):
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)
@@ -78,19 +81,6 @@ def handle_noargs(self, **options):
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‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def sql_create(app, style):
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])
@@ -40,10 +40,6 @@ def sql_create(app, style):
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())
@@ -82,7 +78,7 @@ def sql_delete(app, style):
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
@@ -97,13 +93,6 @@ def sql_delete(app, style):
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‎

Lines changed: 92 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -79,27 +79,28 @@ def get_validation_errors(outfile, app=None):
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):
@@ -117,48 +118,80 @@ def get_validation_errors(outfile, app=None):
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‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def handle_fk_field(self, obj, field):
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‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def handle_m2m_field(self, obj, field):
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())})
@@ -233,4 +233,3 @@ def getInnerText(node):
233233
else:
234234
pass
235235
return u"".join(inner_text)
236-

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /