mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	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.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@10738 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -116,6 +116,8 @@ class ModelBase(type): | ||||
|                     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 @@ class ModelBase(type): | ||||
|                 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 @@ class ModelBase(type): | ||||
|             # 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 | ||||
|   | ||||
| @@ -57,7 +57,7 @@ class Manager(object): | ||||
|         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: | ||||
|   | ||||
| @@ -461,8 +461,13 @@ class Options(object): | ||||
|         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." | ||||
|   | ||||
| @@ -778,7 +778,9 @@ class BaseQuery(object): | ||||
|         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 @@ class BaseQuery(object): | ||||
|         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 @@ class BaseQuery(object): | ||||
|                 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 @@ class BaseQuery(object): | ||||
|                 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 @@ def add_to_dict(data, key, value): | ||||
|         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 | ||||
|   | ||||
| @@ -82,6 +82,87 @@ class MyPersonProxy(MyPerson): | ||||
| 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 @@ False | ||||
| >>> 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 @@ FieldError: Proxy model 'NoNewFields' contains model fields. | ||||
| >>> 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> | ||||
| """} | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user