Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19385 again, now with real code changes

The commit of 266de5f included only
tests, this time also code changes included...
  • Loading branch information...
commit 97774429aeb54df4c09895c07cd1b09e70201f7d 1 parent 266de5f
Anssi Kääriäinen authored March 24, 2013
102  django/contrib/contenttypes/generic.py
@@ -8,10 +8,11 @@
8 8
 
9 9
 from django.core.exceptions import ObjectDoesNotExist
10 10
 from django.db import connection
11  
-from django.db.models import signals
12 11
 from django.db import models, router, DEFAULT_DB_ALIAS
13  
-from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
  12
+from django.db.models import signals
  13
+from django.db.models.fields.related import ForeignObject, ForeignObjectRel
14 14
 from django.db.models.related import PathInfo
  15
+from django.db.models.sql.where import Constraint
15 16
 from django.forms import ModelForm
16 17
 from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
17 18
 from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
@@ -149,17 +150,14 @@ def __set__(self, instance, value):
149 150
         setattr(instance, self.fk_field, fk)
150 151
         setattr(instance, self.cache_attr, value)
151 152
 
152  
-class GenericRelation(RelatedField, Field):
  153
+class GenericRelation(ForeignObject):
153 154
     """Provides an accessor to generic related objects (e.g. comments)"""
154 155
 
155 156
     def __init__(self, to, **kwargs):
156 157
         kwargs['verbose_name'] = kwargs.get('verbose_name', None)
157  
-        kwargs['rel'] = GenericRel(to,
158  
-                            related_name=kwargs.pop('related_name', None),
159  
-                            limit_choices_to=kwargs.pop('limit_choices_to', None),
160  
-                            symmetrical=kwargs.pop('symmetrical', True))
161  
-
162  
-
  158
+        kwargs['rel'] = GenericRel(
  159
+            self, to, related_name=kwargs.pop('related_name', None),
  160
+            limit_choices_to=kwargs.pop('limit_choices_to', None),)
163 161
         # Override content-type/object-id field names on the related class
164 162
         self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
165 163
         self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
@@ -167,47 +165,44 @@ def __init__(self, to, **kwargs):
167 165
         kwargs['blank'] = True
168 166
         kwargs['editable'] = False
169 167
         kwargs['serialize'] = False
170  
-        Field.__init__(self, **kwargs)
171  
-
172  
-    def get_path_info(self):
173  
-        from_field = self.model._meta.pk
  168
+        # This construct is somewhat of an abuse of ForeignObject. This field
  169
+        # represents a relation from pk to object_id field. But, this relation
  170
+        # isn't direct, the join is generated reverse along foreign key. So,
  171
+        # the from_field is object_id field, to_field is pk because of the
  172
+        # reverse join.
  173
+        super(GenericRelation, self).__init__(
  174
+            to, to_fields=[],
  175
+            from_fields=[self.object_id_field_name], **kwargs)
  176
+
  177
+    def resolve_related_fields(self):
  178
+        self.to_fields = [self.model._meta.pk.name]
  179
+        return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
  180
+                 self.model._meta.pk)]
  181
+
  182
+    def get_reverse_path_info(self):
174 183
         opts = self.rel.to._meta
175 184
         target = opts.get_field_by_name(self.object_id_field_name)[0]
176  
-        # Note that we are using different field for the join_field
177  
-        # than from_field or to_field. This is a hack, but we need the
178  
-        # GenericRelation to generate the extra SQL.
179  
-        return ([PathInfo(from_field, target, self.model._meta, opts, self, True, False)],
180  
-                opts, target, self)
  185
+        return [PathInfo(self.model._meta, opts, (target,), self.rel, True, False)]
181 186
 
182 187
     def get_choices_default(self):
183  
-        return Field.get_choices(self, include_blank=False)
  188
+        return super(GenericRelation, self).get_choices(include_blank=False)
184 189
 
185 190
     def value_to_string(self, obj):
186 191
         qs = getattr(obj, self.name).all()
187 192
         return smart_text([instance._get_pk_val() for instance in qs])
188 193
 
189  
-    def m2m_db_table(self):
190  
-        return self.rel.to._meta.db_table
191  
-
192  
-    def m2m_column_name(self):
193  
-        return self.object_id_field_name
194  
-
195  
-    def m2m_reverse_name(self):
196  
-        return self.rel.to._meta.pk.column
197  
-
198  
-    def m2m_target_field_name(self):
199  
-        return self.model._meta.pk.name
200  
-
201  
-    def m2m_reverse_target_field_name(self):
202  
-        return self.rel.to._meta.pk.name
  194
+    def get_joining_columns(self, reverse_join=False):
  195
+        if not reverse_join:
  196
+            # This error message is meant for the user, and from user
  197
+            # perspective this is a reverse join along the GenericRelation.
  198
+            raise ValueError('Joining in reverse direction not allowed.')
  199
+        return super(GenericRelation, self).get_joining_columns(reverse_join)
203 200
 
204 201
     def contribute_to_class(self, cls, name):
205  
-        super(GenericRelation, self).contribute_to_class(cls, name)
206  
-
  202
+        super(GenericRelation, self).contribute_to_class(cls, name, virtual_only=True)
207 203
         # Save a reference to which model this class is on for future use
208 204
         self.model = cls
209  
-
210  
-        # Add the descriptor for the m2m relation
  205
+        # Add the descriptor for the relation
211 206
         setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
212 207
 
213 208
     def contribute_to_related_class(self, cls, related):
@@ -219,21 +214,18 @@ def set_attributes_from_rel(self):
219 214
     def get_internal_type(self):
220 215
         return "ManyToManyField"
221 216
 
222  
-    def db_type(self, connection):
223  
-        # Since we're simulating a ManyToManyField, in effect, best return the
224  
-        # same db_type as well.
225  
-        return None
226  
-
227 217
     def get_content_type(self):
228 218
         """
229 219
         Returns the content type associated with this field's model.
230 220
         """
231 221
         return ContentType.objects.get_for_model(self.model)
232 222
 
233  
-    def get_extra_join_sql(self, connection, qn, lhs_alias, rhs_alias):
234  
-        extra_col = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0].column
235  
-        contenttype = self.get_content_type().pk
236  
-        return " AND %s.%s = %%s" % (qn(rhs_alias), qn(extra_col)), [contenttype]
  223
+    def get_extra_restriction(self, where_class, alias, remote_alias):
  224
+        field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
  225
+        contenttype_pk = self.get_content_type().pk
  226
+        cond = where_class()
  227
+        cond.add((Constraint(remote_alias, field.column, field), 'exact', contenttype_pk), 'AND')
  228
+        return cond
237 229
 
238 230
     def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
239 231
         """
@@ -273,12 +265,12 @@ def __get__(self, instance, instance_type=None):
273 265
         qn = connection.ops.quote_name
274 266
         content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(instance)
275 267
 
  268
+        join_cols = self.field.get_joining_columns(reverse_join=True)[0]
276 269
         manager = RelatedManager(
277 270
             model = rel_model,
278 271
             instance = instance,
279  
-            symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model),
280  
-            source_col_name = qn(self.field.m2m_column_name()),
281  
-            target_col_name = qn(self.field.m2m_reverse_name()),
  272
+            source_col_name = qn(join_cols[0]),
  273
+            target_col_name = qn(join_cols[1]),
282 274
             content_type = content_type,
283 275
             content_type_field_name = self.field.content_type_field_name,
284 276
             object_id_field_name = self.field.object_id_field_name,
@@ -378,14 +370,10 @@ def create(self, **kwargs):
378 370
 
379 371
     return GenericRelatedObjectManager
380 372
 
381  
-class GenericRel(ManyToManyRel):
382  
-    def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True):
383  
-        self.to = to
384  
-        self.related_name = related_name
385  
-        self.limit_choices_to = limit_choices_to or {}
386  
-        self.symmetrical = symmetrical
387  
-        self.multiple = True
388  
-        self.through = None
  373
+class GenericRel(ForeignObjectRel):
  374
+
  375
+    def __init__(self, field, to, related_name=None, limit_choices_to=None):
  376
+        super(GenericRel, self).__init__(field, to, related_name, limit_choices_to)
389 377
 
390 378
 class BaseGenericInlineFormSet(BaseModelFormSet):
391 379
     """
12  django/core/management/validation.py
@@ -153,8 +153,16 @@ def get_validation_errors(outfile, app=None):
153 153
                     continue
154 154
 
155 155
                 # Make sure the related field specified by a ForeignKey is unique
156  
-                if not f.rel.to._meta.get_field(f.rel.field_name).unique:
157  
-                    e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
  156
+                if f.requires_unique_target:
  157
+                    if len(f.foreign_related_fields) > 1:
  158
+                        has_unique_field = False
  159
+                        for rel_field in f.foreign_related_fields:
  160
+                            has_unique_field = has_unique_field or rel_field.unique
  161
+                        if not has_unique_field:
  162
+                            e.add(opts, "Field combination '%s' under model '%s' must have a unique=True constraint" % (','.join([rel_field.name for rel_field in f.foreign_related_fields]), f.rel.to.__name__))
  163
+                    else:
  164
+                        if not f.foreign_related_fields[0].unique:
  165
+                            e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.foreign_related_fields[0].name, f.rel.to.__name__))
158 166
 
159 167
                 rel_opts = f.rel.to._meta
160 168
                 rel_name = f.related.get_accessor_name()
6  django/db/backends/mysql/compiler.py
@@ -17,6 +17,12 @@ def resolve_columns(self, row, fields=()):
17 17
             values.append(value)
18 18
         return row[:index_extra_select] + tuple(values)
19 19
 
  20
+    def as_subquery_condition(self, alias, columns):
  21
+        qn = self.quote_name_unless_alias
  22
+        qn2 = self.connection.ops.quote_name
  23
+        sql, params = self.as_sql()
  24
+        return '(%s) IN (%s)' % (', '.join(['%s.%s' % (qn(alias), qn2(column)) for column in columns]), sql), params
  25
+
20 26
 class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler):
21 27
     pass
22 28
 
2  django/db/models/__init__.py
@@ -8,7 +8,7 @@
8 8
 from django.db.models.fields import *
9 9
 from django.db.models.fields.subclassing import SubfieldBase
10 10
 from django.db.models.fields.files import FileField, ImageField
11  
-from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
  11
+from django.db.models.fields.related import ForeignKey, ForeignObject, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
12 12
 from django.db.models.deletion import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAULT, DO_NOTHING, ProtectedError
13 13
 from django.db.models import signals
14 14
 from django.utils.decorators import wraps
19  django/db/models/base.py
@@ -10,7 +10,7 @@
10 10
 from django.core.exceptions import (ObjectDoesNotExist,
11 11
     MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
12 12
 from django.db.models.fields import AutoField, FieldDoesNotExist
13  
-from django.db.models.fields.related import (ManyToOneRel,
  13
+from django.db.models.fields.related import (ForeignObjectRel, ManyToOneRel,
14 14
     OneToOneField, add_lazy_relation)
15 15
 from django.db import (router, transaction, DatabaseError,
16 16
     DEFAULT_DB_ALIAS)
@@ -333,12 +333,12 @@ def __init__(self, *args, **kwargs):
333 333
         # The reason for the kwargs check is that standard iterator passes in by
334 334
         # args, and instantiation for iteration is 33% faster.
335 335
         args_len = len(args)
336  
-        if args_len > len(self._meta.fields):
  336
+        if args_len > len(self._meta.concrete_fields):
337 337
             # Daft, but matches old exception sans the err msg.
338 338
             raise IndexError("Number of args exceeds number of fields")
339 339
 
340  
-        fields_iter = iter(self._meta.fields)
341 340
         if not kwargs:
  341
+            fields_iter = iter(self._meta.concrete_fields)
342 342
             # The ordering of the zip calls matter - zip throws StopIteration
343 343
             # when an iter throws it. So if the first iter throws it, the second
344 344
             # is *not* consumed. We rely on this, so don't change the order
@@ -347,6 +347,7 @@ def __init__(self, *args, **kwargs):
347 347
                 setattr(self, field.attname, val)
348 348
         else:
349 349
             # Slower, kwargs-ready version.
  350
+            fields_iter = iter(self._meta.fields)
350 351
             for val, field in zip(args, fields_iter):
351 352
                 setattr(self, field.attname, val)
352 353
                 kwargs.pop(field.name, None)
@@ -363,11 +364,12 @@ def __init__(self, *args, **kwargs):
363 364
             # data-descriptor object (DeferredAttribute) without triggering its
364 365
             # __get__ method.
365 366
             if (field.attname not in kwargs and
366  
-                    isinstance(self.__class__.__dict__.get(field.attname), DeferredAttribute)):
  367
+                    (isinstance(self.__class__.__dict__.get(field.attname), DeferredAttribute)
  368
+                     or field.column is None)):
367 369
                 # This field will be populated on request.
368 370
                 continue
369 371
             if kwargs:
370  
-                if isinstance(field.rel, ManyToOneRel):
  372
+                if isinstance(field.rel, ForeignObjectRel):
371 373
                     try:
372 374
                         # Assume object instance was passed in.
373 375
                         rel_obj = kwargs.pop(field.name)
@@ -394,6 +396,7 @@ def __init__(self, *args, **kwargs):
394 396
                         val = field.get_default()
395 397
             else:
396 398
                 val = field.get_default()
  399
+
397 400
             if is_related_object:
398 401
                 # If we are passed a related instance, set it using the
399 402
                 # field.name instead of field.attname (e.g. "user" instead of
@@ -528,7 +531,7 @@ def save(self, force_insert=False, force_update=False, using=None,
528 531
         # automatically do a "update_fields" save on the loaded fields.
529 532
         elif not force_insert and self._deferred and using == self._state.db:
530 533
             field_names = set()
531  
-            for field in self._meta.fields:
  534
+            for field in self._meta.concrete_fields:
532 535
                 if not field.primary_key and not hasattr(field, 'through'):
533 536
                     field_names.add(field.attname)
534 537
             deferred_fields = [
@@ -614,7 +617,7 @@ def _save_table(self, raw=False, cls=None, force_insert=False,
614 617
         for a single table.
615 618
         """
616 619
         meta = cls._meta
617  
-        non_pks = [f for f in meta.local_fields if not f.primary_key]
  620
+        non_pks = [f for f in meta.local_concrete_fields if not f.primary_key]
618 621
 
619 622
         if update_fields:
620 623
             non_pks = [f for f in non_pks
@@ -652,7 +655,7 @@ def _save_table(self, raw=False, cls=None, force_insert=False,
652 655
                     **{field.name: getattr(self, field.attname)}).count()
653 656
                 self._order = order_value
654 657
 
655  
-            fields = meta.local_fields
  658
+            fields = meta.local_concrete_fields
656 659
             if not pk_set:
657 660
                 fields = [f for f in fields if not isinstance(f, AutoField)]
658 661
 
15  django/db/models/deletion.py
... ...
@@ -1,4 +1,3 @@
1  
-from functools import wraps
2 1
 from operator import attrgetter
3 2
 
4 3
 from django.db import connections, transaction, IntegrityError
@@ -196,17 +195,13 @@ def collect(self, objs, source=None, nullable=False, collect_related=True,
196 195
                     self.fast_deletes.append(sub_objs)
197 196
                 elif sub_objs:
198 197
                     field.rel.on_delete(self, field, sub_objs, self.using)
199  
-
200  
-            # TODO This entire block is only needed as a special case to
201  
-            # support cascade-deletes for GenericRelation. It should be
202  
-            # removed/fixed when the ORM gains a proper abstraction for virtual
203  
-            # or composite fields, and GFKs are reworked to fit into that.
204  
-            for relation in model._meta.many_to_many:
205  
-                if not relation.rel.through:
206  
-                    sub_objs = relation.bulk_related_objects(new_objs, self.using)
  198
+            for field in model._meta.virtual_fields:
  199
+                if hasattr(field, 'bulk_related_objects'):
  200
+                    # Its something like generic foreign key.
  201
+                    sub_objs = field.bulk_related_objects(new_objs, self.using)
207 202
                     self.collect(sub_objs,
208 203
                                  source=model,
209  
-                                 source_attr=relation.rel.related_name,
  204
+                                 source_attr=field.rel.related_name,
210 205
                                  nullable=True)
211 206
 
212 207
     def related_objects(self, related, objs):
7  django/db/models/fields/__init__.py
@@ -292,10 +292,13 @@ def set_attributes_from_name(self, name):
292 292
         if self.verbose_name is None and self.name:
293 293
             self.verbose_name = self.name.replace('_', ' ')
294 294
 
295  
-    def contribute_to_class(self, cls, name):
  295
+    def contribute_to_class(self, cls, name, virtual_only=False):
296 296
         self.set_attributes_from_name(name)
297 297
         self.model = cls
298  
-        cls._meta.add_field(self)
  298
+        if virtual_only:
  299
+            cls._meta.add_virtual_field(self)
  300
+        else:
  301
+            cls._meta.add_field(self)
299 302
         if self.choices:
300 303
             setattr(cls, 'get_%s_display' % self.name,
301 304
                     curry(cls._get_FIELD_display, field=self))
532  django/db/models/fields/related.py
@@ -7,7 +7,6 @@
@@ -93,22 +92,27 @@ def do_pending_lookups(sender, **kwargs):
@@ -122,7 +126,6 @@ def set_attributes_from_rel(self):
@@ -130,94 +133,6 @@ def do_related_class(self, other, cls):
@@ -254,8 +169,8 @@ def get_prefetch_queryset(self, instances):
@@ -274,7 +189,9 @@ def __get__(self, instance, instance_type=None):
@@ -314,13 +231,14 @@ def __set__(self, instance, value):
@@ -352,16 +270,12 @@ def get_queryset(self, **db_hints):
@@ -377,16 +291,14 @@ def __get__(self, instance, instance_type=None):
@@ -440,11 +352,11 @@ def __set__(self, instance, value):
@@ -487,15 +399,12 @@ def related_manager_cls(self):
@@ -504,20 +413,22 @@ def get_queryset(self):
@@ -550,10 +461,10 @@ def get_or_create(self, **kwargs):
@@ -577,16 +488,26 @@ def __init__(self, model=None, query_field_name=None, instance=None, symmetrical
@@ -620,11 +541,9 @@ def get_queryset(self):
@@ -634,16 +553,14 @@ def get_prefetch_queryset(self, instances):
@@ -795,7 +712,7 @@ def _clear_items(self, source_field_name):
@@ -918,19 +835,18 @@ def __set__(self, instance, value):
@@ -939,6 +855,20 @@ def is_hidden(self):
@@ -952,9 +882,9 @@ def get_related_field(self):
@@ -963,7 +893,7 @@ def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
@@ -989,7 +919,199 @@ def get_related_field(self):
@@ -999,7 +1121,7 @@ class ForeignKey(RelatedField, Field):
@@ -1008,44 +1130,33 @@ def __init__(self, to, to_field=None, rel_class=ManyToOneRel,
@@ -1066,21 +1177,26 @@ def validate(self, value, model_instance):