Loading django/db/models/base.py +2 −1 Original line number Diff line number Diff line Loading @@ -272,7 +272,8 @@ class ModelBase(type): else: # .. and abstract ones. for field in parent_fields: new_class.add_to_class(field.name, copy.deepcopy(field)) new_field = copy.deepcopy(field) new_class.add_to_class(field.name, new_field) # Pass any non-abstract parent classes onto child. new_class._meta.parents.update(base._meta.parents) Loading django/db/models/options.py +85 −92 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ from django.utils.lru_cache import lru_cache from django.utils.text import camel_case_to_spaces from django.utils.translation import override, string_concat PROXY_PARENTS = object() EMPTY_RELATION_TREE = tuple() IMMUTABLE_WARNING = ( Loading Loading @@ -473,8 +475,6 @@ class Options(object): @cached_property def _forward_fields_map(self): res = {} # call get_fields() with export_ordered_set=True in order to have a # field_instance -> names map fields = self._get_fields(reverse=False) for field in fields: res[field.name] = field Loading Loading @@ -566,6 +566,10 @@ class Options(object): # included in the results. if field.is_relation and field.many_to_one and field.related_model is None: continue # Relations to child proxy models should not be included. if (field.model != self.model and field.model._meta.concrete_model == self.concrete_model): continue names.add(field.name) if hasattr(field, 'attname'): Loading @@ -576,14 +580,13 @@ class Options(object): def get_all_related_objects(self, local_only=False, include_hidden=False, include_proxy_eq=False): include_parents = local_only is False include_parents = True if local_only is False else PROXY_PARENTS fields = self._get_fields( forward=False, reverse=True, include_parents=include_parents, include_hidden=include_hidden, ) fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField)) if include_proxy_eq: children = chain.from_iterable(c._relation_tree for c in self.concrete_model._meta.proxied_children Loading @@ -606,9 +609,10 @@ class Options(object): @raise_deprecation(suggested_alternative="get_fields()") def get_all_related_many_to_many_objects(self, local_only=False): include_parents = True if local_only is not True else PROXY_PARENTS fields = self._get_fields( forward=False, reverse=True, include_parents=local_only is not True, include_hidden=True include_parents=include_parents, include_hidden=True ) return [obj for obj in fields if isinstance(obj.field, ManyToManyField)] Loading Loading @@ -676,19 +680,16 @@ class Options(object): all_models = self.apps.get_models(include_auto_created=True) for model in all_models: # Abstract model's fields are copied to child models, hence we will # see the fields from the child models. if model._meta.abstract: continue fields_with_relations = ( f for f in model._meta._get_fields(reverse=False) f for f in model._meta._get_fields(reverse=False, include_parents=False) if f.is_relation and f.related_model is not None ) if model._meta.auto_created: fields_with_relations = ( f for f in fields_with_relations if not f.many_to_many ) for f in fields_with_relations: if not isinstance(f.rel.to, six.string_types): # Set options_instance -> field related_objects_graph[f.rel.to._meta].append(f) for model in all_models: Loading @@ -698,24 +699,14 @@ class Options(object): # @cached_property). This means that the _meta._relation_tree is # only called if related_objects is not in __dict__. related_objects = related_objects_graph[model._meta] # If related_objects are empty, it makes sense to set # EMPTY_RELATION_TREE. This will avoid allocating multiple empty # relation trees. relation_tree = EMPTY_RELATION_TREE if related_objects: relation_tree = related_objects model._meta.__dict__['_relation_tree'] = relation_tree model._meta.__dict__['_relation_tree'] = related_objects # It seems it is possible that self is not in all_models, so guard # against that with default for get(). return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE) @cached_property def _relation_tree(self): # If cache is not present, populate the cache self._populate_directed_relation_graph() # It may happen, often when the registry is not ready, that a not yet # registered model is queried. In this very rare case we simply return # an EMPTY_RELATION_TREE. When the registry will be ready, cache will # be flushed and this model will be computed properly. return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE) return self._populate_directed_relation_graph() def _expire_cache(self, forward=True, reverse=True): # This method is usually called by apps.cache_clear(), when the Loading Loading @@ -744,16 +735,40 @@ class Options(object): - include_hidden: include fields that have a related_name that starts with a "+" """ if include_parents is False: include_parents = PROXY_PARENTS return self._get_fields(include_parents=include_parents, include_hidden=include_hidden) def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False, export_ordered_set=False): seen_models=None): """ Internal helper function to return fields of the model. * If forward=True, then fields defined on this model are returned. * If reverse=True, then relations pointing to this model are returned. * If include_hidden=True, then fields with is_hidden=True are returned. * The include_parents argument toggles if fields from parent models should be included. It has three values: True, False, and PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all fields defined for the current model or any of its parents in the parent chain to the model's concrete model. """ if include_parents not in (True, False, PROXY_PARENTS): raise TypeError("Invalid argument for include_parents: %s" % (include_parents,)) # This helper function is used to allow recursion in ``get_fields()`` # implementation and to provide a fast way for Django's internals to # access specific subsets of fields. # We must keep track of which models we have already seen. Otherwise we # could include the same field multiple times from different models. topmost_call = False if seen_models is None: seen_models = set() topmost_call = True seen_models.add(self.model) # Creates a cache key composed of all arguments cache_key = (forward, reverse, include_parents, include_hidden, export_ordered_set) cache_key = (forward, reverse, include_parents, include_hidden, topmost_call) try: # In order to avoid list manipulation. Always return a shallow copy # of the results. Loading @@ -761,76 +776,54 @@ class Options(object): except KeyError: pass # Using an OrderedDict preserves the order of insertion. This is # important when displaying a ModelForm or the contrib.admin panel # and no specific ordering is provided. fields = OrderedDict() options = { 'include_parents': include_parents, 'include_hidden': include_hidden, 'export_ordered_set': True, } # Abstract models cannot hold reverse fields. if reverse and not self.abstract: if include_parents: parent_list = self.get_parent_list() fields = [] # Recursively call _get_fields() on each parent, with the same # options provided in this call. if include_parents is not False: for parent in self.parents: for obj, _ in six.iteritems(parent._meta._get_fields(forward=False, **options)): if obj.many_to_many: # In order for a reverse ManyToManyRel object to be # valid, its creation counter must be > 0 and must # be in the parent list. if not (obj.field.creation_counter < 0 and obj.related_model not in parent_list): fields[obj] = True elif not ((obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.related_model not in parent_list): fields[obj] = True # In diamond inheritance it is possible that we see the same # model from two different routes. In that case, avoid adding # fields from the same parent again. if parent in seen_models: continue if (parent._meta.concrete_model != self.concrete_model and include_parents == PROXY_PARENTS): continue for obj in parent._meta._get_fields( forward=forward, reverse=reverse, include_parents=include_parents, include_hidden=include_hidden, seen_models=seen_models): if hasattr(obj, 'parent_link') and obj.parent_link: continue fields.append(obj) if reverse: # Tree is computed once and cached until the app cache is expired. # It is composed of a list of fields pointing to the current model # from other models. If the model is a proxy model, then we also # add the concrete model. all_fields = ( self._relation_tree if not self.proxy else chain(self._relation_tree, self.concrete_model._meta._relation_tree) ) # Pull out all related objects from forward fields for field in (f.rel for f in all_fields): # from other models. all_fields = self._relation_tree for field in all_fields: # If hidden fields should be included or the relation is not # intentionally hidden, add to the fields dict. if include_hidden or not field.hidden: fields[field] = True if include_hidden or not field.rel.hidden: fields.append(field.rel) if forward: if include_parents: for parent in self.parents: # Add the forward fields of each parent. fields.update(parent._meta._get_fields(reverse=False, **options)) fields.update( (field, True,) for field in chain(self.local_fields, self.local_many_to_many) fields.extend( field for field in chain(self.local_fields, self.local_many_to_many) ) # Virtual fields are recopied to each child model, and they get a # different model as field.model in each child. Hence we have to # add the virtual fields separately from the topmost call. If we # did this recursively similar to local_fields, we would get field # instances with field.model != self.model. if topmost_call: fields.extend( f for f in self.virtual_fields ) if not export_ordered_set: # By default, fields contains field instances as keys and all # possible names if the field instance as values. When # _get_fields() is called, we only want to return field instances, # so we just preserve the keys. fields = list(fields.keys()) # Virtual fields are not inheritable, therefore they are inserted # only when the recursive _get_fields() call comes to an end. if forward: fields.extend(self.virtual_fields) # In order to avoid list manipulation. Always # return a shallow copy of the results fields = make_immutable_fields_list("get_fields()", fields) # Store result into cache for later access self._get_fields_cache[cache_key] = fields # In order to avoid list manipulation. Always # return a shallow copy of the results return fields tests/model_meta/tests.py +5 −8 Original line number Diff line number Diff line Loading @@ -228,11 +228,9 @@ class RelationTreeTests(TestCase): sorted([field.related_query_name() for field in Relation._meta._relation_tree if not field.rel.field.rel.is_hidden()]), sorted([ 'fk_abstract_rel', 'fk_abstract_rel', 'fk_abstract_rel', 'fk_base_rel', 'fk_base_rel', 'fk_base_rel', 'fk_concrete_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_abstract_rel', 'fo_abstract_rel', 'fo_base_rel', 'fo_base_rel', 'fo_base_rel', 'fo_concrete_rel', 'fo_concrete_rel', 'm2m_abstract_rel', 'm2m_abstract_rel', 'm2m_abstract_rel', 'm2m_base_rel', 'm2m_base_rel', 'm2m_base_rel', 'm2m_concrete_rel', 'm2m_concrete_rel', 'fk_abstract_rel', 'fk_base_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_base_rel', 'fo_concrete_rel', 'm2m_abstract_rel', 'm2m_base_rel', 'm2m_concrete_rel' ]) ) # Testing hidden related objects Loading @@ -243,9 +241,8 @@ class RelationTreeTests(TestCase): 'BasePerson_following_base+', 'BasePerson_following_base+', 'BasePerson_friends_abstract+', 'BasePerson_friends_abstract+', 'BasePerson_friends_base+', 'BasePerson_friends_base+', 'BasePerson_m2m_abstract+', 'BasePerson_m2m_base+', 'Relating_basepeople+', 'Relating_basepeople_hidden+', 'followers_abstract', 'followers_abstract', 'followers_abstract', 'followers_base', 'followers_base', 'followers_base', 'friends_abstract_rel_+', 'friends_abstract_rel_+', 'friends_abstract_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'person', 'Relating_basepeople_hidden+', 'followers_abstract', 'followers_base', 'friends_abstract_rel_+', 'friends_base_rel_+', 'person', 'relating_basepeople', 'relating_baseperson', ]) ) Loading tests/proxy_models/models.py +1 −1 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ class ProxyTrackerUser(TrackerUser): @python_2_unicode_compatible class Issue(models.Model): summary = models.CharField(max_length=255) assignee = models.ForeignKey(TrackerUser) assignee = models.ForeignKey(ProxyTrackerUser) def __str__(self): return ':'.join((self.__class__.__name__, self.summary,)) Loading tests/proxy_models/tests.py +12 −2 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.apps import apps from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core import checks, management from django.core import checks, exceptions, management from django.db import DEFAULT_DB_ALIAS, models from django.db.models import signals from django.test import TestCase, override_settings Loading Loading @@ -327,8 +327,18 @@ class ProxyModelTests(TestCase): resp = StateProxy.objects.select_related().get(name='New South Wales') self.assertEqual(resp.name, 'New South Wales') def test_filter_proxy_relation_reverse(self): tu = TrackerUser.objects.create( name='Contributor', status='contrib') with self.assertRaises(exceptions.FieldError): TrackerUser.objects.filter(issue=None), self.assertQuerysetEqual( ProxyTrackerUser.objects.filter(issue=None), [tu], lambda x: x ) def test_proxy_bug(self): contributor = TrackerUser.objects.create(name='Contributor', contributor = ProxyTrackerUser.objects.create(name='Contributor', status='contrib') someone = BaseUser.objects.create(name='Someone') Bug.objects.create(summary='fix this', version='1.1beta', Loading Loading
django/db/models/base.py +2 −1 Original line number Diff line number Diff line Loading @@ -272,7 +272,8 @@ class ModelBase(type): else: # .. and abstract ones. for field in parent_fields: new_class.add_to_class(field.name, copy.deepcopy(field)) new_field = copy.deepcopy(field) new_class.add_to_class(field.name, new_field) # Pass any non-abstract parent classes onto child. new_class._meta.parents.update(base._meta.parents) Loading
django/db/models/options.py +85 −92 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ from django.utils.lru_cache import lru_cache from django.utils.text import camel_case_to_spaces from django.utils.translation import override, string_concat PROXY_PARENTS = object() EMPTY_RELATION_TREE = tuple() IMMUTABLE_WARNING = ( Loading Loading @@ -473,8 +475,6 @@ class Options(object): @cached_property def _forward_fields_map(self): res = {} # call get_fields() with export_ordered_set=True in order to have a # field_instance -> names map fields = self._get_fields(reverse=False) for field in fields: res[field.name] = field Loading Loading @@ -566,6 +566,10 @@ class Options(object): # included in the results. if field.is_relation and field.many_to_one and field.related_model is None: continue # Relations to child proxy models should not be included. if (field.model != self.model and field.model._meta.concrete_model == self.concrete_model): continue names.add(field.name) if hasattr(field, 'attname'): Loading @@ -576,14 +580,13 @@ class Options(object): def get_all_related_objects(self, local_only=False, include_hidden=False, include_proxy_eq=False): include_parents = local_only is False include_parents = True if local_only is False else PROXY_PARENTS fields = self._get_fields( forward=False, reverse=True, include_parents=include_parents, include_hidden=include_hidden, ) fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField)) if include_proxy_eq: children = chain.from_iterable(c._relation_tree for c in self.concrete_model._meta.proxied_children Loading @@ -606,9 +609,10 @@ class Options(object): @raise_deprecation(suggested_alternative="get_fields()") def get_all_related_many_to_many_objects(self, local_only=False): include_parents = True if local_only is not True else PROXY_PARENTS fields = self._get_fields( forward=False, reverse=True, include_parents=local_only is not True, include_hidden=True include_parents=include_parents, include_hidden=True ) return [obj for obj in fields if isinstance(obj.field, ManyToManyField)] Loading Loading @@ -676,19 +680,16 @@ class Options(object): all_models = self.apps.get_models(include_auto_created=True) for model in all_models: # Abstract model's fields are copied to child models, hence we will # see the fields from the child models. if model._meta.abstract: continue fields_with_relations = ( f for f in model._meta._get_fields(reverse=False) f for f in model._meta._get_fields(reverse=False, include_parents=False) if f.is_relation and f.related_model is not None ) if model._meta.auto_created: fields_with_relations = ( f for f in fields_with_relations if not f.many_to_many ) for f in fields_with_relations: if not isinstance(f.rel.to, six.string_types): # Set options_instance -> field related_objects_graph[f.rel.to._meta].append(f) for model in all_models: Loading @@ -698,24 +699,14 @@ class Options(object): # @cached_property). This means that the _meta._relation_tree is # only called if related_objects is not in __dict__. related_objects = related_objects_graph[model._meta] # If related_objects are empty, it makes sense to set # EMPTY_RELATION_TREE. This will avoid allocating multiple empty # relation trees. relation_tree = EMPTY_RELATION_TREE if related_objects: relation_tree = related_objects model._meta.__dict__['_relation_tree'] = relation_tree model._meta.__dict__['_relation_tree'] = related_objects # It seems it is possible that self is not in all_models, so guard # against that with default for get(). return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE) @cached_property def _relation_tree(self): # If cache is not present, populate the cache self._populate_directed_relation_graph() # It may happen, often when the registry is not ready, that a not yet # registered model is queried. In this very rare case we simply return # an EMPTY_RELATION_TREE. When the registry will be ready, cache will # be flushed and this model will be computed properly. return self.__dict__.get('_relation_tree', EMPTY_RELATION_TREE) return self._populate_directed_relation_graph() def _expire_cache(self, forward=True, reverse=True): # This method is usually called by apps.cache_clear(), when the Loading Loading @@ -744,16 +735,40 @@ class Options(object): - include_hidden: include fields that have a related_name that starts with a "+" """ if include_parents is False: include_parents = PROXY_PARENTS return self._get_fields(include_parents=include_parents, include_hidden=include_hidden) def _get_fields(self, forward=True, reverse=True, include_parents=True, include_hidden=False, export_ordered_set=False): seen_models=None): """ Internal helper function to return fields of the model. * If forward=True, then fields defined on this model are returned. * If reverse=True, then relations pointing to this model are returned. * If include_hidden=True, then fields with is_hidden=True are returned. * The include_parents argument toggles if fields from parent models should be included. It has three values: True, False, and PROXY_PARENTS. When set to PROXY_PARENTS, the call will return all fields defined for the current model or any of its parents in the parent chain to the model's concrete model. """ if include_parents not in (True, False, PROXY_PARENTS): raise TypeError("Invalid argument for include_parents: %s" % (include_parents,)) # This helper function is used to allow recursion in ``get_fields()`` # implementation and to provide a fast way for Django's internals to # access specific subsets of fields. # We must keep track of which models we have already seen. Otherwise we # could include the same field multiple times from different models. topmost_call = False if seen_models is None: seen_models = set() topmost_call = True seen_models.add(self.model) # Creates a cache key composed of all arguments cache_key = (forward, reverse, include_parents, include_hidden, export_ordered_set) cache_key = (forward, reverse, include_parents, include_hidden, topmost_call) try: # In order to avoid list manipulation. Always return a shallow copy # of the results. Loading @@ -761,76 +776,54 @@ class Options(object): except KeyError: pass # Using an OrderedDict preserves the order of insertion. This is # important when displaying a ModelForm or the contrib.admin panel # and no specific ordering is provided. fields = OrderedDict() options = { 'include_parents': include_parents, 'include_hidden': include_hidden, 'export_ordered_set': True, } # Abstract models cannot hold reverse fields. if reverse and not self.abstract: if include_parents: parent_list = self.get_parent_list() fields = [] # Recursively call _get_fields() on each parent, with the same # options provided in this call. if include_parents is not False: for parent in self.parents: for obj, _ in six.iteritems(parent._meta._get_fields(forward=False, **options)): if obj.many_to_many: # In order for a reverse ManyToManyRel object to be # valid, its creation counter must be > 0 and must # be in the parent list. if not (obj.field.creation_counter < 0 and obj.related_model not in parent_list): fields[obj] = True elif not ((obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.related_model not in parent_list): fields[obj] = True # In diamond inheritance it is possible that we see the same # model from two different routes. In that case, avoid adding # fields from the same parent again. if parent in seen_models: continue if (parent._meta.concrete_model != self.concrete_model and include_parents == PROXY_PARENTS): continue for obj in parent._meta._get_fields( forward=forward, reverse=reverse, include_parents=include_parents, include_hidden=include_hidden, seen_models=seen_models): if hasattr(obj, 'parent_link') and obj.parent_link: continue fields.append(obj) if reverse: # Tree is computed once and cached until the app cache is expired. # It is composed of a list of fields pointing to the current model # from other models. If the model is a proxy model, then we also # add the concrete model. all_fields = ( self._relation_tree if not self.proxy else chain(self._relation_tree, self.concrete_model._meta._relation_tree) ) # Pull out all related objects from forward fields for field in (f.rel for f in all_fields): # from other models. all_fields = self._relation_tree for field in all_fields: # If hidden fields should be included or the relation is not # intentionally hidden, add to the fields dict. if include_hidden or not field.hidden: fields[field] = True if include_hidden or not field.rel.hidden: fields.append(field.rel) if forward: if include_parents: for parent in self.parents: # Add the forward fields of each parent. fields.update(parent._meta._get_fields(reverse=False, **options)) fields.update( (field, True,) for field in chain(self.local_fields, self.local_many_to_many) fields.extend( field for field in chain(self.local_fields, self.local_many_to_many) ) # Virtual fields are recopied to each child model, and they get a # different model as field.model in each child. Hence we have to # add the virtual fields separately from the topmost call. If we # did this recursively similar to local_fields, we would get field # instances with field.model != self.model. if topmost_call: fields.extend( f for f in self.virtual_fields ) if not export_ordered_set: # By default, fields contains field instances as keys and all # possible names if the field instance as values. When # _get_fields() is called, we only want to return field instances, # so we just preserve the keys. fields = list(fields.keys()) # Virtual fields are not inheritable, therefore they are inserted # only when the recursive _get_fields() call comes to an end. if forward: fields.extend(self.virtual_fields) # In order to avoid list manipulation. Always # return a shallow copy of the results fields = make_immutable_fields_list("get_fields()", fields) # Store result into cache for later access self._get_fields_cache[cache_key] = fields # In order to avoid list manipulation. Always # return a shallow copy of the results return fields
tests/model_meta/tests.py +5 −8 Original line number Diff line number Diff line Loading @@ -228,11 +228,9 @@ class RelationTreeTests(TestCase): sorted([field.related_query_name() for field in Relation._meta._relation_tree if not field.rel.field.rel.is_hidden()]), sorted([ 'fk_abstract_rel', 'fk_abstract_rel', 'fk_abstract_rel', 'fk_base_rel', 'fk_base_rel', 'fk_base_rel', 'fk_concrete_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_abstract_rel', 'fo_abstract_rel', 'fo_base_rel', 'fo_base_rel', 'fo_base_rel', 'fo_concrete_rel', 'fo_concrete_rel', 'm2m_abstract_rel', 'm2m_abstract_rel', 'm2m_abstract_rel', 'm2m_base_rel', 'm2m_base_rel', 'm2m_base_rel', 'm2m_concrete_rel', 'm2m_concrete_rel', 'fk_abstract_rel', 'fk_base_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_base_rel', 'fo_concrete_rel', 'm2m_abstract_rel', 'm2m_base_rel', 'm2m_concrete_rel' ]) ) # Testing hidden related objects Loading @@ -243,9 +241,8 @@ class RelationTreeTests(TestCase): 'BasePerson_following_base+', 'BasePerson_following_base+', 'BasePerson_friends_abstract+', 'BasePerson_friends_abstract+', 'BasePerson_friends_base+', 'BasePerson_friends_base+', 'BasePerson_m2m_abstract+', 'BasePerson_m2m_base+', 'Relating_basepeople+', 'Relating_basepeople_hidden+', 'followers_abstract', 'followers_abstract', 'followers_abstract', 'followers_base', 'followers_base', 'followers_base', 'friends_abstract_rel_+', 'friends_abstract_rel_+', 'friends_abstract_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'friends_base_rel_+', 'person', 'Relating_basepeople_hidden+', 'followers_abstract', 'followers_base', 'friends_abstract_rel_+', 'friends_base_rel_+', 'person', 'relating_basepeople', 'relating_baseperson', ]) ) Loading
tests/proxy_models/models.py +1 −1 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ class ProxyTrackerUser(TrackerUser): @python_2_unicode_compatible class Issue(models.Model): summary = models.CharField(max_length=255) assignee = models.ForeignKey(TrackerUser) assignee = models.ForeignKey(ProxyTrackerUser) def __str__(self): return ':'.join((self.__class__.__name__, self.summary,)) Loading
tests/proxy_models/tests.py +12 −2 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.apps import apps from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.core import checks, management from django.core import checks, exceptions, management from django.db import DEFAULT_DB_ALIAS, models from django.db.models import signals from django.test import TestCase, override_settings Loading Loading @@ -327,8 +327,18 @@ class ProxyModelTests(TestCase): resp = StateProxy.objects.select_related().get(name='New South Wales') self.assertEqual(resp.name, 'New South Wales') def test_filter_proxy_relation_reverse(self): tu = TrackerUser.objects.create( name='Contributor', status='contrib') with self.assertRaises(exceptions.FieldError): TrackerUser.objects.filter(issue=None), self.assertQuerysetEqual( ProxyTrackerUser.objects.filter(issue=None), [tu], lambda x: x ) def test_proxy_bug(self): contributor = TrackerUser.objects.create(name='Contributor', contributor = ProxyTrackerUser.objects.create(name='Contributor', status='contrib') someone = BaseUser.objects.create(name='Someone') Bug.objects.create(summary='fix this', version='1.1beta', Loading