Author: jacob Date: 2009-05-11 05:10:03 -0500 (Mon, 11 May 2009) New Revision: 10738
Modified: django/trunk/django/db/models/base.py django/trunk/django/db/models/manager.py django/trunk/django/db/models/options.py django/trunk/django/db/models/sql/query.py django/trunk/tests/modeltests/proxy_models/models.py Log: Fixed #10953, #10955: proxies of proxies now work correctly, though I still don't quite understand why you'd want to do such a thing. Thanks, Armin Ronacher. Modified: django/trunk/django/db/models/base.py =================================================================== --- django/trunk/django/db/models/base.py 2009-05-11 09:57:19 UTC (rev 10737) +++ django/trunk/django/db/models/base.py 2009-05-11 10:10:03 UTC (rev 10738) @@ -116,6 +116,8 @@ new_class._meta.local_many_to_many): raise FieldError("Proxy model '%s' contains model fields." % name) + while base._meta.proxy: + base = base._meta.proxy_for_model new_class._meta.setup_proxy(base) # Do the appropriate setup for any model parents. @@ -123,6 +125,7 @@ if isinstance(f, OneToOneField)]) for base in parents: + original_base = base if not hasattr(base, '_meta'): # Things without _meta aren't functional models, so they're # uninteresting parents. @@ -167,7 +170,7 @@ # Proxy models inherit the non-abstract managers from their base, # unless they have redefined any of them. if is_proxy: - new_class.copy_managers(base._meta.concrete_managers) + new_class.copy_managers(original_base._meta.concrete_managers) # Inherit virtual fields (like GenericForeignKey) from the parent # class Modified: django/trunk/django/db/models/manager.py =================================================================== --- django/trunk/django/db/models/manager.py 2009-05-11 09:57:19 UTC (rev 10737) +++ django/trunk/django/db/models/manager.py 2009-05-11 10:10:03 UTC (rev 10738) @@ -57,7 +57,7 @@ setattr(model, name, ManagerDescriptor(self)) if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter: model._default_manager = self - if model._meta.abstract or self._inherited: + if model._meta.abstract or (self._inherited and not self.model._meta.proxy): model._meta.abstract_managers.append((self.creation_counter, name, self)) else: Modified: django/trunk/django/db/models/options.py =================================================================== --- django/trunk/django/db/models/options.py 2009-05-11 09:57:19 UTC (rev 10737) +++ django/trunk/django/db/models/options.py 2009-05-11 10:10:03 UTC (rev 10738) @@ -461,8 +461,13 @@ if ancestor in self.parents: return self.parents[ancestor] for parent in self.parents: - if parent._meta.get_ancestor_link(ancestor): - return self.parents[parent] + # Tries to get a link field from the immediate parent + parent_link = parent._meta.get_ancestor_link(ancestor) + if parent_link: + # In case of a proxied model, the first link + # of the chain to the ancestor is that parent + # links + return self.parents[parent] or parent_link def get_ordered_objects(self): "Returns a list of Options objects that are ordered with respect to this object." Modified: django/trunk/django/db/models/sql/query.py =================================================================== --- django/trunk/django/db/models/sql/query.py 2009-05-11 09:57:19 UTC (rev 10737) +++ django/trunk/django/db/models/sql/query.py 2009-05-11 10:10:03 UTC (rev 10738) @@ -778,7 +778,9 @@ qn2 = self.connection.ops.quote_name aliases = set() only_load = self.deferred_to_columns() - proxied_model = opts.proxy and opts.proxy_for_model or 0 + # Skip all proxy to the root proxied model + proxied_model = get_proxied_model(opts) + if start_alias: seen = {None: start_alias} for field, model in opts.get_fields_with_model(): @@ -1301,7 +1303,10 @@ opts = self.model._meta root_alias = self.tables[0] seen = {None: root_alias} - proxied_model = opts.proxy and opts.proxy_for_model or 0 + + # Skip all proxy to the root proxied model + proxied_model = get_proxied_model(opts) + for field, model in opts.get_fields_with_model(): if model not in seen: if model is proxied_model: @@ -1376,6 +1381,13 @@ alias = root_alias alias_chain = [] for int_model in opts.get_base_chain(model): + # Proxy model have elements in base chain + # with no parents, assign the new options + # object and skip to the next base in that + # case + if not int_opts.parents[int_model]: + int_opts = int_model._meta + continue lhs_col = int_opts.parents[int_model].column dedupe = lhs_col in opts.duplicate_targets if dedupe: @@ -1720,7 +1732,9 @@ raise MultiJoin(pos + 1) if model: # The field lives on a base class of the current model. - proxied_model = opts.proxy and opts.proxy_for_model or 0 + # Skip the chain of proxy to the concrete proxied model + proxied_model = get_proxied_model(opts) + for int_model in opts.get_base_chain(model): if int_model is proxied_model: opts = int_model._meta @@ -2423,3 +2437,11 @@ data[key].add(value) else: data[key] = set([value]) + +def get_proxied_model(opts): + int_opts = opts + proxied_model = None + while int_opts.proxy: + proxied_model = int_opts.proxy_for_model + int_opts = proxied_model._meta + return proxied_model Modified: django/trunk/tests/modeltests/proxy_models/models.py =================================================================== --- django/trunk/tests/modeltests/proxy_models/models.py 2009-05-11 09:57:19 UTC (rev 10737) +++ django/trunk/tests/modeltests/proxy_models/models.py 2009-05-11 10:10:03 UTC (rev 10738) @@ -82,6 +82,87 @@ class LowerStatusPerson(MyPersonProxy): status = models.CharField(max_length=80) +class User(models.Model): + name = models.CharField(max_length=100) + + def __unicode__(self): + return self.name + +class UserProxy(User): + class Meta: + proxy = True + +class UserProxyProxy(UserProxy): + class Meta: + proxy = True + +# We can still use `select_related()` to include related models in our querysets. +class Country(models.Model): + name = models.CharField(max_length=50) + +class State(models.Model): + name = models.CharField(max_length=50) + country = models.ForeignKey(Country) + + def __unicode__(self): + return self.name + +class StateProxy(State): + class Meta: + proxy = True + +# Proxy models still works with filters (on related fields) +# and select_related, even when mixed with model inheritance +class BaseUser(models.Model): + name = models.CharField(max_length=255) + +class TrackerUser(BaseUser): + status = models.CharField(max_length=50) + +class ProxyTrackerUser(TrackerUser): + class Meta: + proxy = True + + +class Issue(models.Model): + summary = models.CharField(max_length=255) + assignee = models.ForeignKey(TrackerUser) + + def __unicode__(self): + return ':'.join((self.__class__.__name__,self.summary,)) + +class Bug(Issue): + version = models.CharField(max_length=50) + reporter = models.ForeignKey(BaseUser) + +class ProxyBug(Bug): + """ + Proxy of an inherited class + """ + class Meta: + proxy = True + + +class ProxyProxyBug(ProxyBug): + """ + A proxy of proxy model with related field + """ + class Meta: + proxy = True + +class Improvement(Issue): + """ + A model that has relation to a proxy model + or to a proxy of proxy model + """ + version = models.CharField(max_length=50) + reporter = models.ForeignKey(ProxyTrackerUser) + associated_bug = models.ForeignKey(ProxyProxyBug) + +class ProxyImprovement(Improvement): + class Meta: + proxy = True + __test__ = {'API_TESTS' : """ # The MyPerson model should be generating the same database queries as the # Person model (when the same manager is used in each case). @@ -119,6 +200,11 @@ >>> LowerStatusPerson.objects.all() [<LowerStatusPerson: homer>] +# Correct type when querying a proxy of proxy + +>>> MyPersonProxy.objects.all() +[<MyPersonProxy: Bazza del Frob>, <MyPersonProxy: Foo McBar>, <MyPersonProxy: homer>] + # And now for some things that shouldn't work... # # All base classes must be non-abstract @@ -178,6 +264,58 @@ >>> ctype = ContentType.objects.get_for_model >>> ctype(Person) is ctype(OtherPerson) True -"""} +>>> MyPersonProxy.objects.all() +[<MyPersonProxy: barney>, <MyPersonProxy: fred>] +>>> u = User.objects.create(name='Bruce') +>>> User.objects.all() +[<User: Bruce>] +>>> UserProxy.objects.all() +[<UserProxy: Bruce>] +>>> UserProxyProxy.objects.all() +[<UserProxyProxy: Bruce>] + +# We can still use `select_related()` to include related models in our querysets. +>>> country = Country.objects.create(name='Australia') +>>> state = State.objects.create(name='New South Wales', country=country) + +>>> State.objects.select_related() +[<State: New South Wales>] +>>> StateProxy.objects.select_related() +[<StateProxy: New South Wales>] +>>> StateProxy.objects.get(name='New South Wales') +<StateProxy: New South Wales> +>>> StateProxy.objects.select_related().get(name='New South Wales') +<StateProxy: New South Wales> + +>>> contributor = TrackerUser.objects.create(name='Contributor',status='contrib') +>>> someone = BaseUser.objects.create(name='Someone') +>>> _ = Bug.objects.create(summary='fix this', version='1.1beta', +... assignee=contributor, reporter=someone) +>>> pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor', +... status='proxy') +>>> _ = Improvement.objects.create(summary='improve that', version='1.1beta', +... assignee=contributor, reporter=pcontributor, +... associated_bug=ProxyProxyBug.objects.all()[0]) + +# Related field filter on proxy +>>> ProxyBug.objects.get(version__icontains='beta') +<ProxyBug: ProxyBug:fix this> + +# Select related + filter on proxy +>>> ProxyBug.objects.select_related().get(version__icontains='beta') +<ProxyBug: ProxyBug:fix this> + +# Proxy of proxy, select_related + filter +>>> ProxyProxyBug.objects.select_related().get(version__icontains='beta') +<ProxyProxyBug: ProxyProxyBug:fix this> + +# Select related + filter on a related proxy field +>>> ProxyImprovement.objects.select_related().get(reporter__name__icontains='butor') +<ProxyImprovement: ProxyImprovement:improve that> + +# Select related + filter on a related proxy of proxy field +>>> ProxyImprovement.objects.select_related().get(associated_bug__summary__icontains='fix') +<ProxyImprovement: ProxyImprovement:improve that> +"""} --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django updates" group. To post to this group, send email to django-updates@googlegroups.com To unsubscribe from this group, send email to django-updates+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-updates?hl=en -~----------~----~----~----~------~----~------~--~---