Django

Code

Changeset 8608

Show
Ignore:
Timestamp:
08/27/08 00:22:33 (3 months ago)
Author:
mtredinnick
Message:

Fixed #5937 -- When filtering on generic relations, restrict the target objects to those with the right content type.

This isn't a complete solution to this class of problem, but it will do for
1.0, which only has generic relations as a multicolumn type. A more general
multicolumn solution will be available after that release.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/contenttypes/generic.py

    r8279 r8608  
    166166        # same db_type as well. 
    167167        return None 
     168 
     169    def extra_filters(self, pieces, pos): 
     170        """ 
     171        Return an extra filter to the queryset so that the results are filtered 
     172        on the appropriate content type. 
     173        """ 
     174        ContentType = get_model("contenttypes", "contenttype") 
     175        content_type = ContentType.objects.get_for_model(self.model) 
     176        prefix = "__".join(pieces[:pos + 1]) 
     177        return "%s__%s" % (prefix, self.content_type_field_name), content_type 
    168178 
    169179class ReverseGenericRelatedObjectsDescriptor(object): 
  • django/trunk/django/db/models/sql/query.py

    r8559 r8608  
    648648        if not alias: 
    649649            alias = self.get_initial_alias() 
    650         field, target, opts, joins, last = self.setup_joins(pieces, opts, 
    651                 alias, False) 
     650        field, target, opts, joins, last, extra = self.setup_joins(pieces, 
     651                opts, alias, False) 
    652652        alias = joins[-1] 
    653653        col = target.column 
     
    10071007 
    10081008    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 
    1009             can_reuse=None): 
     1009            can_reuse=None, process_extras=True): 
    10101010        """ 
    10111011        Add a single filter to the query. The 'filter_expr' is a pair: 
     
    10271027        if we would otherwise force the creation of new aliases for a join 
    10281028        (needed for nested Q-filters). The set is updated by this method. 
     1029 
     1030        If 'process_extras' is set, any extra filters returned from the table 
     1031        joining process will be processed. This parameter is set to False 
     1032        during the processing of extra filters to avoid infinite recursion. 
    10291033        """ 
    10301034        arg, value = filter_expr 
     
    10541058 
    10551059        try: 
    1056             field, target, opts, join_list, last = self.setup_joins(parts, opts, 
    1057                     alias, True, allow_many, can_reuse=can_reuse) 
     1060            field, target, opts, join_list, last, extra_filters = self.setup_joins( 
     1061                    parts, opts, alias, True, allow_many, can_reuse=can_reuse) 
    10581062        except MultiJoin, e: 
    10591063            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level])) 
     
    11531157        if can_reuse is not None: 
    11541158            can_reuse.update(join_list) 
     1159        if process_extras: 
     1160            for filter in extra_filters: 
     1161                self.add_filter(filter, negate=negate, can_reuse=can_reuse, 
     1162                        process_extras=False) 
    11551163 
    11561164    def add_q(self, q_object, used_aliases=None): 
     
    12081216        dupe_set = set() 
    12091217        exclusions = set() 
     1218        extra_filters = [] 
    12101219        for pos, name in enumerate(names): 
    12111220            try: 
     
    12631272                        ())) 
    12641273 
     1274            if hasattr(field, 'extra_filters'): 
     1275                extra_filters.append(field.extra_filters(names, pos)) 
    12651276            if direct: 
    12661277                if m2m: 
     
    13661377            raise FieldError("Join on field %r not permitted." % name) 
    13671378 
    1368         return field, target, opts, joins, last 
     1379        return field, target, opts, joins, last, extra_filters 
    13691380 
    13701381    def update_dupe_avoidance(self, opts, col, alias): 
     
    14381449        try: 
    14391450            for name in field_names: 
    1440                 field, target, u2, joins, u3 = self.setup_joins( 
     1451                field, target, u2, joins, u3, u4 = self.setup_joins( 
    14411452                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 
    14421453                        True) 
     
    16021613        opts = self.model._meta 
    16031614        alias = self.get_initial_alias() 
    1604         field, col, opts, joins, last = self.setup_joins( 
     1615        field, col, opts, joins, last, extra = self.setup_joins( 
    16051616                start.split(LOOKUP_SEP), opts, alias, False) 
    16061617        alias = joins[last[-1]] 
  • django/trunk/tests/modeltests/generic_relations/models.py

    r8325 r8608  
    8383>>> bacon = Vegetable(name="Bacon", is_yucky=False) 
    8484>>> quartz = Mineral(name="Quartz", hardness=7) 
    85 >>> for o in (lion, platypus, eggplant, bacon, quartz): 
     85>>> for o in (platypus, lion, eggplant, bacon, quartz): 
    8686...     o.save() 
    8787 
     
    9696>>> lion.tags.create(tag="hairy") 
    9797<TaggedItem: hairy> 
     98>>> platypus.tags.create(tag="fatty") 
     99<TaggedItem: fatty> 
    98100 
    99101>>> lion.tags.all() 
     
    125127>>> tag1.save() 
    126128>>> platypus.tags.all() 
    127 [<TaggedItem: shiny>] 
     129[<TaggedItem: fatty>, <TaggedItem: shiny>] 
    128130>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) 
    129131[<TaggedItem: clearish>] 
     132 
     133# Queries across generic relations respect the content types. Even though there are two TaggedItems with a tag of "fatty", this query only pulls out the one with the content type related to Animals. 
     134>>> Animal.objects.filter(tags__tag='fatty') 
     135[<Animal: Platypus>] 
    130136 
    131137# If you delete an object with an explicit Generic relation, the related 
     
    133139# Original list of tags: 
    134140>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] 
    135 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'hairy', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2), (u'yellow', <ContentType: animal>, 1)] 
     141[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'hairy', <ContentType: animal>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1), (u'yellow', <ContentType: animal>, 2)] 
    136142 
    137143>>> lion.delete() 
    138144>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] 
    139 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)] 
     145[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)] 
    140146 
    141147# If Generic Relation is not explicitly defined, any related objects 
     
    143149>>> quartz.delete() 
    144150>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] 
    145 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)] 
     151[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)] 
    146152 
    147153# If you delete a tag, the objects using the tag are unaffected 
     
    152158[<TaggedItem: salty>] 
    153159>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] 
    154 [(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)] 
     160[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)] 
     161 
     162>>> TaggedItem.objects.filter(tag='fatty').delete() 
    155163 
    156164>>> ctype = ContentType.objects.get_for_model(lion) 
     
    193201[<Comparison: tiger is stronger than None>] 
    194202 
     203 
    195204# GenericInlineFormSet tests ################################################## 
    196205 
     
    208217...     print form.as_p() 
    209218<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p> 
    210 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="5" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p> 
     219<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="..." id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p> 
    211220<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p> 
    212221<p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>