Ticket #15103: ticket_15103_trunk.diff

File ticket_15103_trunk.diff, 9.0 KB (added by lukeplant, 4 years ago)

Initial patch that fixes the issue

  • django/contrib/admin/options.py

    diff -r 09ceaf49c46f django/contrib/admin/options.py
    a b  
    205205            qs = qs.order_by(*ordering)
    206206        return qs
    207207
    208     def lookup_allowed(self, lookup):
     208    def lookup_allowed(self, lookup, value):
     209        model = self.model
     210        # Check FKey lookups that are allowed, so that popups produced by
     211        # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,
     212        # are allowed to work.
     213        for l in model._meta.related_fkey_lookups:
     214            for k, v in widgets.url_params_from_lookup_dict(l).items():
     215                if k == lookup and v == value:
     216                    return True
     217
    209218        parts = lookup.split(LOOKUP_SEP)
    210219
    211220        # Last term in lookup is a query term (__exact, __startswith etc)
     
    217226        # if foo has been specificially included in the lookup list; so
    218227        # drop __id if it is the last part. However, first we need to find
    219228        # the pk attribute name.
    220         model = self.model
    221229        pk_attr_name = None
    222230        for part in parts[:-1]:
    223231            field, _, _, _ = model._meta.get_field_by_name(part)
  • django/contrib/admin/views/main.py

    diff -r 09ceaf49c46f django/contrib/admin/views/main.py
    a b  
    183183
    184184            # if key ends with __in, split parameter into separate values
    185185            if key.endswith('__in'):
    186                 lookup_params[key] = value.split(',')
     186                value = value.split(',')
     187                lookup_params[key] = value
    187188
    188189            # if key ends with __isnull, special case '' and false
    189190            if key.endswith('__isnull'):
    190191                if value.lower() in ('', 'false'):
    191                     lookup_params[key] = False
     192                    value = False
    192193                else:
    193                     lookup_params[key] = True
     194                    value = True
     195                lookup_params[key] = value
    194196
    195             if not self.model_admin.lookup_allowed(key):
     197            if not self.model_admin.lookup_allowed(key, value):
    196198                raise SuspiciousOperation(
    197199                    "Filtering by %s not allowed" % key
    198200                )
  • django/contrib/admin/widgets.py

    diff -r 09ceaf49c46f django/contrib/admin/widgets.py
    a b  
    9191    template_with_clear = (u'<span class="clearable-file-input">%s</span>'
    9292                           % forms.ClearableFileInput.template_with_clear)
    9393
     94def url_params_from_lookup_dict(lookups):
     95    """
     96    Converts the type of lookups specified in a ForeignKey limit_choices_to
     97    attribute to a dictionary of query parameters
     98    """
     99    params = {}
     100    if lookups and hasattr(lookups, 'items'):
     101        items = []
     102        for k, v in lookups.items():
     103            if isinstance(v, list):
     104                v = u','.join([str(x) for x in v])
     105            else:
     106                v = unicode(v)
     107            items.append((k, v))
     108        params.update(dict(items))
     109    return params
    94110
    95111class ForeignKeyRawIdWidget(forms.TextInput):
    96112    """
     
    108124        related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
    109125        params = self.url_parameters()
    110126        if params:
    111             url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
     127            url = u'?' + u'&amp;'.join([u'%s=%s' % (k, v) for k, v in params.items()])
    112128        else:
    113             url = ''
     129            url = u''
    114130        if "class" not in attrs:
    115131            attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
    116132        output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
    117133        # TODO: "id_" is hard-coded here. This should instead use the correct
    118134        # API to determine the ID dynamically.
    119         output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
     135        output.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
    120136            (related_url, url, name))
    121         output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
     137        output.append(u'<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
    122138        if value:
    123139            output.append(self.label_for_value(value))
    124140        return mark_safe(u''.join(output))
    125141
    126142    def base_url_parameters(self):
    127         params = {}
    128         if self.rel.limit_choices_to and hasattr(self.rel.limit_choices_to, 'items'):
    129             items = []
    130             for k, v in self.rel.limit_choices_to.items():
    131                 if isinstance(v, list):
    132                     v = ','.join([str(x) for x in v])
    133                 else:
    134                     v = str(v)
    135                 items.append((k, v))
    136             params.update(dict(items))
    137         return params
     143        return url_params_from_lookup_dict(self.rel.limit_choices_to)
    138144
    139145    def url_parameters(self):
    140146        from django.contrib.admin.views.main import TO_FIELD_VAR
  • django/db/models/fields/related.py

    diff -r 09ceaf49c46f django/db/models/fields/related.py
    a b  
    890890        # don't get a related descriptor.
    891891        if not self.rel.is_hidden():
    892892            setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
     893            if self.rel.limit_choices_to:
     894                cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to)
    893895        if self.rel.field_name is None:
    894896            self.rel.field_name = cls._meta.pk.name
    895897
  • django/db/models/options.py

    diff -r 09ceaf49c46f django/db/models/options.py
    a b  
    5555        self.abstract_managers = []
    5656        self.concrete_managers = []
    5757
     58        # List of all lookups defined in ForeignKey 'limit_choices_to' options
     59        # from *other* models. Needed for some admin checks.
     60        self.related_fkey_lookups = []
     61
    5862    def contribute_to_class(self, cls, name):
    5963        from django.db import connection
    6064        from django.db.backends.util import truncate_name
  • tests/regressiontests/admin_views/models.py

    diff -r 09ceaf49c46f tests/regressiontests/admin_views/models.py
    a b  
    178178class ThingAdmin(admin.ModelAdmin):
    179179    list_filter = ('color__warm', 'color__value')
    180180
     181class Actor(models.Model):
     182    name = models.CharField(max_length=50)
     183    age = models.IntegerField()
     184    def __unicode__(self):
     185        return self.name
     186
     187class Inquisition(models.Model):
     188    expected = models.BooleanField()
     189    leader = models.ForeignKey(Actor)
     190    def __unicode__(self):
     191        return self.expected
     192
     193class Sketch(models.Model):
     194    title = models.CharField(max_length=100)
     195    inquisition = models.ForeignKey(Inquisition, limit_choices_to={'leader__name': 'Palin',
     196                                                                   'leader__age': 27,
     197                                                                   })
     198    def __unicode__(self):
     199        return self.title
     200
    181201class Fabric(models.Model):
    182202    NG_CHOICES = (
    183203        ('Textured', (
     
    632652admin.site.register(ModelWithStringPrimaryKey)
    633653admin.site.register(Color)
    634654admin.site.register(Thing, ThingAdmin)
     655admin.site.register(Actor)
     656admin.site.register(Inquisition)
     657admin.site.register(Sketch)
    635658admin.site.register(Person, PersonAdmin)
    636659admin.site.register(Persona, PersonaAdmin)
    637660admin.site.register(Subscriber, SubscriberAdmin)
  • tests/regressiontests/admin_views/tests.py

    diff -r 09ceaf49c46f tests/regressiontests/admin_views/tests.py
    a b  
    392392        response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
    393393        self.assertEqual(response.status_code, 200)
    394394
     395    def test_allowed_filtering_15103(self):
     396        """
     397        Regressions test for ticket 15103 - filtering on fields defined in a
     398        ForeignKey 'limit_choices_to' should be allowed, otherwise raw_id_fields
     399        can break.
     400        """
     401        try:
     402            self.client.get("/test_admin/admin/admin_views/inquisition/?leader__name=Palin&leader__age=27")
     403        except SuspiciousOperation:
     404            self.fail("Filters should be allowed if they are defined on a ForeignKey pointing to this model")
     405
    395406class SaveAsTests(TestCase):
    396407    fixtures = ['admin-views-users.xml','admin-views-person.xml']
    397408
Back to Top