Ticket #7270: 121_reverse_r7831.patch

File 121_reverse_r7831.patch, 21.1 KB (added by George Vilches, 16 years ago)

Updated patch again r7831, friendlier towards FK(unique=True)

  • django/db/models/fields/related.py

    diff -r c273e74671a6 django/db/models/fields/related.py
    a b  
     1import types
    12from django.db import connection, transaction
    23from django.db.models import signals, get_model
    34from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class, FieldDoesNotExist
     
    168169    # SingleRelatedObjectDescriptor instance.
    169170    def __init__(self, related):
    170171        self.related = related
    171         self.cache_name = '_%s_cache' % related.get_accessor_name()
     172        cache_name = '_%s_cache' % related.field.related_query_name()
     173        # Contribute to the parent model for later lookup.
     174        related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name
     175        self.cache_name = cache_name
    172176
    173177    def __get__(self, instance, instance_type=None):
    174178        if instance is None:
     
    274278    # attribute is a ForeignRelatedObjectsDescriptor instance.
    275279    def __init__(self, related):
    276280        self.related = related   # RelatedObject instance
     281        if related.field.unique:
     282            cache_name = '_%s_cache' % related.field.related_query_name()
     283            # Contribute to the parent model for later lookup.
     284            related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name
     285            self.cache_name = cache_name
    277286
    278287    def __get__(self, instance, instance_type=None):
    279288        if instance is None:
     
    281290
    282291        rel_field = self.related.field
    283292        rel_model = self.related.model
     293        if rel_field.unique:
     294            cache_name = self.cache_name
    284295
    285296        # Dynamically create a class that subclasses the related
    286297        # model's default manager.
     
    320331                        setattr(obj, rel_field.name, None)
    321332                        obj.save()
    322333                clear.alters_data = True
     334           
     335            if rel_field.unique:
     336                def all(self):
     337                    try:
     338                        result = getattr(instance, cache_name)
     339                        if isinstance(result, (types.TupleType, types.ListType)):
     340                            return result
     341                        else:
     342                            return [result]
     343                    except AttributeError, ae:
     344                        return superclass.get_query_set(self)
    323345
    324346        manager = RelatedManager()
    325347        attname = rel_field.rel.get_related_field().name
     
    339361        if self.related.field.null:
    340362            manager.clear()
    341363        manager.add(*value)
     364        # Cache the value specially if from a unique set.
     365        if self.related.field.unique:
     366            self.cache_name = value
    342367
    343368def create_many_related_manager(superclass):
    344369    """Creates a manager that subclasses 'superclass' (which is a Manager)
  • django/db/models/options.py

    diff -r c273e74671a6 django/db/models/options.py
    a b  
    4545        self.abstract = False
    4646        self.parents = SortedDict()
    4747        self.duplicate_targets = {}
     48        self.reverse_field_cache = {}
    4849
    4950    def contribute_to_class(self, cls, name):
    5051        from django.db import connection
  • django/db/models/query.py

    diff -r c273e74671a6 django/db/models/query.py
    a b  
    772772        if cached_row:
    773773            rel_obj, index_end = cached_row
    774774            setattr(obj, f.get_cache_name(), rel_obj)
     775
     776            # Do the reverse cache if the field is a unique relation.
     777            if f.unique:
     778                cache_var_name = rel_obj._meta.reverse_field_cache[f.related_query_name()]
     779                setattr(rel_obj, cache_var_name, obj)
     780
     781    if restricted:
     782        related_fields = [(x.field, x.model) for x in klass._meta.get_all_related_objects() if x.field.unique]
     783        for f, model in related_fields:
     784            if f.related_query_name() not in requested:
     785                continue
     786
     787            next = requested.get(f.related_query_name(), {})
     788            cached_row = get_cached_row(model, row, index_end, max_depth,
     789                    cur_depth+1, next)
     790            if cached_row:
     791                rel_obj, index_end = cached_row
     792                setattr(rel_obj, f.get_cache_name(), obj)
     793
     794                # Now do the reverse cache.
     795                cache_var_name = obj._meta.reverse_field_cache[f.related_query_name()]
     796                setattr(obj, cache_var_name, rel_obj)
     797
    775798    return obj, index_end
    776799
    777800def delete_objects(seen_objs):
  • django/db/models/sql/query.py

    diff -r c273e74671a6 django/db/models/sql/query.py
    a b  
    977977                next = requested.get(f.name, {})
    978978            else:
    979979                next = False
    980             if f.null is not None:
     980            if nullable is not None:
     981                new_nullable = nullable
     982            else:
    981983                new_nullable = f.null
    982             else:
    983                 new_nullable = None
    984984            for dupe_opts, dupe_col in dupe_set:
    985985                self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
    986986            self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
    987987                    used, next, restricted, new_nullable, dupe_set)
     988           
     989        # Do reverse columns, but only the ones in the requested list.
     990        if restricted:
     991            related_fields = [(x.field, x.model) for x in opts.get_all_related_objects() if x.field.unique]
     992            for f, model in related_fields:
     993                if f.rel.parent_link or f.related_query_name() not in requested:
     994                    continue
     995                table = model._meta.db_table
     996                if nullable or f.null:
     997                    promote = True
     998                else:
     999                    promote = False
     1000                alias = self.join((root_alias, table, f.rel.get_related_field().column,
     1001                        f.column), exclusions=used,
     1002                        promote=promote, reuse=used)
     1003                used.add(alias)
     1004
     1005                self.related_select_cols.extend([(alias, f2.column)
     1006                        for f2 in model._meta.fields])
     1007                self.related_select_fields.extend(model._meta.fields)
     1008
     1009                next = requested.get(f.related_query_name(), {})
     1010                if nullable is not None:
     1011                    new_nullable = nullable
     1012                else:
     1013                    new_nullable = f.null
     1014                self.fill_related_selections(model._meta, table, cur_depth + 1,
     1015                        used, next, restricted, new_nullable)
     1016
    9881017
    9891018    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
    9901019            can_reuse=None):
  • new file tests/modeltests/select_related_reverse/models.py

    diff -r c273e74671a6 tests/modeltests/select_related_reverse/models.py
    - +  
     1"""
     2``select_related()`` follows all forward relationships, but should also follow
     3reverse relationships where it is appropriate (currently for OneToOneFields
     4only).
     5"""
     6
     7from django.db import models
     8
     9# OneToOneField tests only.
     10
     11class User(models.Model):
     12    alias = models.CharField(max_length=20)
     13   
     14    def __unicode__(self):
     15        return 'User, alias = %s' % (self.alias,)
     16
     17
     18class UserInfo(models.Model):
     19    user = models.OneToOneField(User, primary_key=True)
     20    name = models.CharField(max_length=32)
     21
     22    def __unicode__(self):
     23        return 'UserInfo, name = %s' % (self.name,)
     24   
     25
     26class UserStatResults(models.Model):
     27    results = models.CharField(max_length=50)
     28
     29    def __unicode__(self):
     30        return 'UserStatResults, results = %s' % (self.results,)
     31   
     32
     33class UserStat(models.Model):
     34    user = models.OneToOneField(User, primary_key=True)
     35    posts = models.IntegerField()
     36    results = models.ForeignKey(UserStatResults)
     37
     38    def __unicode__(self):
     39        return 'UserStat, posts = %s' % (self.posts,)
     40
     41
     42class StatDetails(models.Model):
     43    base_stats = models.OneToOneField(UserStat, primary_key=True)
     44    comments = models.IntegerField()
     45
     46    def __unicode__(self):
     47        return 'StatDetails, comments = %s' % (self.comments,)
     48
     49
     50__test__ = {'API_TESTS':"""
     51
     52# Set up.
     53# The test runner sets settings.DEBUG to False, but we want to gather queries
     54# so we'll set it to True here and reset it at the end of the test suite.
     55>>> from django.conf import settings
     56>>> settings.DEBUG = True
     57>>> from django import db
     58
     59>>> usr = UserStatResults.objects.create(results='first results')
     60>>> u = User.objects.create(alias='tom')
     61>>> ui = UserInfo.objects.create(user=u, name='Tom Jones')
     62>>> stat = UserStat.objects.create(user=u, posts=150, results=usr)
     63>>> sd = StatDetails.objects.create(base_stats=stat, comments=259)
     64>>> u = User.objects.create(alias='john')
     65>>> ui = UserInfo.objects.create(user=u, name='John Smith')
     66>>> stat = UserStat.objects.create(user=u, posts=33, results=usr)
     67>>> sd = StatDetails = StatDetails.objects.create(base_stats=stat, comments=104)
     68
     69# Specifying models in the select_related(...) works as expected.
     70>>> db.reset_queries()
     71>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom')
     72>>> u.userstat
     73<UserStat: UserStat, posts = 150>
     74>>> len(db.connection.queries)
     751
     76>>> u.userstat.statdetails
     77<StatDetails: StatDetails, comments = 259>
     78>>> len(db.connection.queries)
     792
     80>>> u.userinfo
     81<UserInfo: UserInfo, name = Tom Jones>
     82>>> len(db.connection.queries)
     832
     84>>> db.reset_queries()
     85>>> u = User.objects.select_related('userstat', 'userstat__statdetails', 'userstat__results').get(alias='tom')
     86>>> u.userstat
     87<UserStat: UserStat, posts = 150>
     88>>> len(db.connection.queries)
     891
     90>>> u.userstat.statdetails
     91<StatDetails: StatDetails, comments = 259>
     92>>> len(db.connection.queries)
     931
     94>>> u.userstat.results
     95<UserStatResults: UserStatResults, results = first results>
     96>>> len(db.connection.queries)
     971
     98>>> u.userinfo
     99<UserInfo: UserInfo, name = Tom Jones>
     100>>> len(db.connection.queries)
     1012
     102
     103>>> db.reset_queries()
     104>>> us = UserStat.objects.select_related('user__userinfo').get(user__alias='john')
     105>>> us
     106<UserStat: UserStat, posts = 33>
     107>>> len(db.connection.queries)
     1081
     109>>> us.user
     110<User: User, alias = john>
     111>>> len(db.connection.queries)
     1121
     113>>> us.user.userinfo
     114<UserInfo: UserInfo, name = John Smith>
     115>>> len(db.connection.queries)
     1161
     117>>> us.user.userstat
     118<UserStat: UserStat, posts = 33>
     119>>> len(db.connection.queries)
     1201
     121
     122"""}
     123   
     124 No newline at end of file
  • new file tests/modeltests/select_related_reverse2/models.py

    diff -r c273e74671a6 tests/modeltests/select_related_reverse2/models.py
    - +  
     1"""
     2``select_related()`` follows all forward relationships, but should also follow
     3reverse relationships where it is appropriate (currently for OneToOneFields
     4only).
     5"""
     6
     7from django.db import models
     8
     9# OneToOneField tests only.
     10
     11class User(models.Model):
     12    alias = models.CharField(max_length=20)
     13   
     14    def __unicode__(self):
     15        return 'User, alias = %s' % (self.alias,)
     16
     17
     18class UserInfo(models.Model):
     19    user = models.OneToOneField(User, primary_key=True)
     20    name = models.CharField(max_length=32)
     21
     22    def __unicode__(self):
     23        return 'UserInfo, name = %s' % (self.name,)
     24   
     25
     26class UserStatResults(models.Model):
     27    results = models.CharField(max_length=50)
     28
     29    def __unicode__(self):
     30        return 'UserStatResults, results = %s' % (self.results,)
     31   
     32
     33class UserStat(models.Model):
     34    user = models.ForeignKey(User, primary_key=True, unique=True, related_name='userstat')
     35    posts = models.IntegerField()
     36    results = models.ForeignKey(UserStatResults)
     37
     38    def __unicode__(self):
     39        return 'UserStat, posts = %s' % (self.posts,)
     40
     41
     42class StatDetails(models.Model):
     43    base_stats = models.OneToOneField(UserStat, primary_key=True)
     44    comments = models.IntegerField()
     45
     46    def __unicode__(self):
     47        return 'StatDetails, comments = %s' % (self.comments,)
     48
     49
     50__test__ = {'API_TESTS':"""
     51
     52# Set up.
     53# The test runner sets settings.DEBUG to False, but we want to gather queries
     54# so we'll set it to True here and reset it at the end of the test suite.
     55>>> from django.conf import settings
     56>>> settings.DEBUG = True
     57>>> from django import db
     58
     59>>> usr = UserStatResults.objects.create(results='first results')
     60>>> u = User.objects.create(alias='tom')
     61>>> ui = UserInfo.objects.create(user=u, name='Tom Jones')
     62>>> stat = UserStat.objects.create(user=u, posts=150, results=usr)
     63>>> sd = StatDetails.objects.create(base_stats=stat, comments=259)
     64>>> u = User.objects.create(alias='john')
     65>>> ui = UserInfo.objects.create(user=u, name='John Smith')
     66>>> stat = UserStat.objects.create(user=u, posts=33, results=usr)
     67>>> sd = StatDetails = StatDetails.objects.create(base_stats=stat, comments=104)
     68
     69# Specifying models in the select_related(...) works as expected.
     70>>> db.reset_queries()
     71>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom')
     72>>> u.userstat.all()
     73[<UserStat: UserStat, posts = 150>]
     74>>> len(db.connection.queries)
     751
     76>>> u.userstat.all()[0].statdetails
     77<StatDetails: StatDetails, comments = 259>
     78>>> len(db.connection.queries)
     792
     80>>> u.userinfo
     81<UserInfo: UserInfo, name = Tom Jones>
     82>>> len(db.connection.queries)
     832
     84>>> db.reset_queries()
     85>>> u = User.objects.select_related('userstat', 'userstat__statdetails', 'userstat__results').get(alias='tom')
     86>>> u.userstat.all()
     87[<UserStat: UserStat, posts = 150>]
     88>>> len(db.connection.queries)
     891
     90>>> u.userstat.all()[0].statdetails
     91<StatDetails: StatDetails, comments = 259>
     92>>> len(db.connection.queries)
     931
     94>>> u.userstat.all()[0].results
     95<UserStatResults: UserStatResults, results = first results>
     96>>> len(db.connection.queries)
     971
     98>>> u.userinfo
     99<UserInfo: UserInfo, name = Tom Jones>
     100>>> len(db.connection.queries)
     1012
     102
     103>>> db.reset_queries()
     104>>> us = UserStat.objects.select_related('user__userinfo').get(user__alias='john')
     105>>> us
     106<UserStat: UserStat, posts = 33>
     107>>> len(db.connection.queries)
     1081
     109>>> us.user
     110<User: User, alias = john>
     111>>> len(db.connection.queries)
     1121
     113>>> us.user.userinfo
     114<UserInfo: UserInfo, name = John Smith>
     115>>> len(db.connection.queries)
     1161
     117>>> us.user.userstat.all()
     118[<UserStat: UserStat, posts = 33>]
     119>>> len(db.connection.queries)
     1201
     121
     122"""}
     123   
     124 No newline at end of file
  • new file tests/modeltests/select_related_reverse3/models.py

    diff -r c273e74671a6 tests/modeltests/select_related_reverse3/models.py
    - +  
     1"""
     2``select_related()`` follows all forward relationships, but should also follow
     3reverse relationships where it is appropriate (currently for OneToOneFields
     4only).
     5"""
     6
     7from django.db import models
     8
     9# OneToOneField tests only.
     10
     11class User(models.Model):
     12    alias = models.CharField(max_length=20)
     13   
     14    def __unicode__(self):
     15        return 'User, alias = %s' % (self.alias,)
     16
     17
     18class UserInfo(models.Model):
     19    user = models.OneToOneField(User, primary_key=True)
     20    name = models.CharField(max_length=32)
     21
     22    def __unicode__(self):
     23        return 'UserInfo, name = %s' % (self.name,)
     24   
     25
     26class UserStatResults(models.Model):
     27    results = models.CharField(max_length=50)
     28
     29    def __unicode__(self):
     30        return 'UserStatResults, results = %s' % (self.results,)
     31   
     32
     33class UserStat(models.Model):
     34    user = models.OneToOneField(User, primary_key=True)
     35    posts = models.IntegerField()
     36    results = models.ForeignKey(UserStatResults)
     37    watcher = models.OneToOneField(User, related_name='stat_watcher')
     38
     39    def __unicode__(self):
     40        return 'UserStat, posts = %s' % (self.posts,)
     41
     42
     43class StatDetails(models.Model):
     44    base_stats = models.OneToOneField(UserStat, primary_key=True)
     45    comments = models.IntegerField()
     46
     47    def __unicode__(self):
     48        return 'StatDetails, comments = %s' % (self.comments,)
     49
     50
     51__test__ = {'API_TESTS':"""
     52
     53# Set up.
     54# The test runner sets settings.DEBUG to False, but we want to gather queries
     55# so we'll set it to True here and reset it at the end of the test suite.
     56>>> from django.conf import settings
     57>>> settings.DEBUG = True
     58>>> from django import db
     59
     60>>> usr = UserStatResults.objects.create(results='first results')
     61>>> u1 = User.objects.create(alias='tom')
     62>>> ui = UserInfo.objects.create(user=u1, name='Tom Jones')
     63>>> u2 = User.objects.create(alias='john')
     64>>> ui = UserInfo.objects.create(user=u2, name='John Smith')
     65>>> u3 = User.objects.create(alias='bob')
     66>>> ui = UserInfo.objects.create(user=u3, name='Bob Rogers')
     67>>> stat = UserStat.objects.create(user=u1, posts=150, results=usr, watcher=u3)
     68>>> sd = StatDetails.objects.create(base_stats=stat, comments=259)
     69>>> stat = UserStat.objects.create(user=u2, posts=33, results=usr, watcher=u2)
     70>>> sd = StatDetails = StatDetails.objects.create(base_stats=stat, comments=104)
     71
     72# Specifying models in the select_related(...) works as expected.
     73>>> db.reset_queries()
     74>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom')
     75>>> u.userstat
     76<UserStat: UserStat, posts = 150>
     77>>> len(db.connection.queries)
     781
     79>>> u.userstat.statdetails
     80<StatDetails: StatDetails, comments = 259>
     81>>> len(db.connection.queries)
     822
     83>>> u.userinfo
     84<UserInfo: UserInfo, name = Tom Jones>
     85>>> len(db.connection.queries)
     862
     87>>> db.reset_queries()
     88>>> u = User.objects.select_related('userstat', 'userstat__statdetails', 'userstat__results').get(alias='tom')
     89>>> u.userstat
     90<UserStat: UserStat, posts = 150>
     91>>> len(db.connection.queries)
     921
     93>>> u.userstat.statdetails
     94<StatDetails: StatDetails, comments = 259>
     95>>> len(db.connection.queries)
     961
     97>>> u.userstat.results
     98<UserStatResults: UserStatResults, results = first results>
     99>>> len(db.connection.queries)
     1001
     101>>> u.userinfo
     102<UserInfo: UserInfo, name = Tom Jones>
     103>>> len(db.connection.queries)
     1042
     105
     106>>> db.reset_queries()
     107>>> us = UserStat.objects.select_related('user__userinfo').get(user__alias='john')
     108>>> us
     109<UserStat: UserStat, posts = 33>
     110>>> len(db.connection.queries)
     1111
     112>>> us.user
     113<User: User, alias = john>
     114>>> len(db.connection.queries)
     1151
     116>>> us.user.userinfo
     117<UserInfo: UserInfo, name = John Smith>
     118>>> len(db.connection.queries)
     1191
     120>>> us.user.userstat
     121<UserStat: UserStat, posts = 33>
     122>>> len(db.connection.queries)
     1231
     124
     125# !!!!!!!!!!!!!!
     126# Everything past this point is testing multiple fields pointing to the same model.
     127# !!!!!!!!!!!!!!
     128>>> us.user.userstat.watcher
     129<User: User, alias = john>
     130>>> len(db.connection.queries)
     1312
     132
     133>>> db.reset_queries()
     134>>> us = UserStat.objects.select_related('user__userinfo', 'watcher').get(user__alias='john')
     135>>> us
     136<UserStat: UserStat, posts = 33>
     137>>> len(db.connection.queries)
     1381
     139>>> us.user
     140<User: User, alias = john>
     141>>> len(db.connection.queries)
     1421
     143>>> us.user.userinfo
     144<UserInfo: UserInfo, name = John Smith>
     145>>> len(db.connection.queries)
     1461
     147>>> us.user.userstat
     148<UserStat: UserStat, posts = 33>
     149>>> len(db.connection.queries)
     1501
     151
     152>>> us.user.userstat.watcher
     153<User: User, alias = john>
     154>>> len(db.connection.queries)
     1551
     156
     157# Show that the # of queries increases as expected when used reverse.
     158>>> db.reset_queries()
     159>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom')
     160>>> u.userstat
     161<UserStat: UserStat, posts = 150>
     162>>> len(db.connection.queries)
     1631
     164>>> u.userstat.statdetails
     165<StatDetails: StatDetails, comments = 259>
     166>>> len(db.connection.queries)
     1672
     168>>> u.userstat.watcher
     169<User: User, alias = bob>
     170>>> len(db.connection.queries)
     1713
     172>>> u.userinfo
     173<UserInfo: UserInfo, name = Tom Jones>
     174>>> len(db.connection.queries)
     1753
     176
     177# Show that we can follow the watcher too.
     178>>> db.reset_queries()
     179>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='john')
     180>>> u.userstat
     181<UserStat: UserStat, posts = 33>
     182>>> len(db.connection.queries)
     1831
     184>>> u.userstat.watcher
     185<User: User, alias = john>
     186>>> len(db.connection.queries)
     1872
     188>>> u.stat_watcher
     189<UserStat: UserStat, posts = 33>
     190>>> len(db.connection.queries)
     1913
     192
     193# Show that we can follow the watcher via the select_related(...) bits.
     194>>> db.reset_queries()
     195>>> u = User.objects.select_related('userstat', 'stat_watcher').get(alias='john')
     196>>> u.userstat
     197<UserStat: UserStat, posts = 33>
     198>>> len(db.connection.queries)
     1991
     200>>> u.userstat.watcher
     201<User: User, alias = john>
     202>>> len(db.connection.queries)
     2032
     204>>> u.stat_watcher
     205<UserStat: UserStat, posts = 33>
     206>>> len(db.connection.queries)
     2072
     208
     209# Show that we can follow the watcher via the select_related(...) bits that are nested.
     210>>> db.reset_queries()
     211>>> u = User.objects.select_related('userstat', 'userstat__watcher').get(alias='tom')
     212>>> u.userstat
     213<UserStat: UserStat, posts = 150>
     214>>> len(db.connection.queries)
     2151
     216>>> u.userstat.watcher
     217<User: User, alias = bob>
     218>>> len(db.connection.queries)
     2191
     220
     221"""}
     222   
     223 No newline at end of file
Back to Top