diff -r 09ceaf49c46f django/contrib/admin/options.py
a
|
b
|
|
205 | 205 | qs = qs.order_by(*ordering) |
206 | 206 | return qs |
207 | 207 | |
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 | |
209 | 218 | parts = lookup.split(LOOKUP_SEP) |
210 | 219 | |
211 | 220 | # Last term in lookup is a query term (__exact, __startswith etc) |
… |
… |
|
217 | 226 | # if foo has been specificially included in the lookup list; so |
218 | 227 | # drop __id if it is the last part. However, first we need to find |
219 | 228 | # the pk attribute name. |
220 | | model = self.model |
221 | 229 | pk_attr_name = None |
222 | 230 | for part in parts[:-1]: |
223 | 231 | field, _, _, _ = model._meta.get_field_by_name(part) |
diff -r 09ceaf49c46f django/contrib/admin/views/main.py
a
|
b
|
|
183 | 183 | |
184 | 184 | # if key ends with __in, split parameter into separate values |
185 | 185 | if key.endswith('__in'): |
186 | | lookup_params[key] = value.split(',') |
| 186 | value = value.split(',') |
| 187 | lookup_params[key] = value |
187 | 188 | |
188 | 189 | # if key ends with __isnull, special case '' and false |
189 | 190 | if key.endswith('__isnull'): |
190 | 191 | if value.lower() in ('', 'false'): |
191 | | lookup_params[key] = False |
| 192 | value = False |
192 | 193 | else: |
193 | | lookup_params[key] = True |
| 194 | value = True |
| 195 | lookup_params[key] = value |
194 | 196 | |
195 | | if not self.model_admin.lookup_allowed(key): |
| 197 | if not self.model_admin.lookup_allowed(key, value): |
196 | 198 | raise SuspiciousOperation( |
197 | 199 | "Filtering by %s not allowed" % key |
198 | 200 | ) |
diff -r 09ceaf49c46f django/contrib/admin/widgets.py
a
|
b
|
|
91 | 91 | template_with_clear = (u'<span class="clearable-file-input">%s</span>' |
92 | 92 | % forms.ClearableFileInput.template_with_clear) |
93 | 93 | |
| 94 | def 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 |
94 | 110 | |
95 | 111 | class ForeignKeyRawIdWidget(forms.TextInput): |
96 | 112 | """ |
… |
… |
|
108 | 124 | related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) |
109 | 125 | params = self.url_parameters() |
110 | 126 | if params: |
111 | | url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) |
| 127 | url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()]) |
112 | 128 | else: |
113 | | url = '' |
| 129 | url = u'' |
114 | 130 | if "class" not in attrs: |
115 | 131 | attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook. |
116 | 132 | output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] |
117 | 133 | # TODO: "id_" is hard-coded here. This should instead use the correct |
118 | 134 | # 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);"> ' % \ |
120 | 136 | (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'))) |
122 | 138 | if value: |
123 | 139 | output.append(self.label_for_value(value)) |
124 | 140 | return mark_safe(u''.join(output)) |
125 | 141 | |
126 | 142 | 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) |
138 | 144 | |
139 | 145 | def url_parameters(self): |
140 | 146 | from django.contrib.admin.views.main import TO_FIELD_VAR |
diff -r 09ceaf49c46f django/db/models/fields/related.py
a
|
b
|
|
890 | 890 | # don't get a related descriptor. |
891 | 891 | if not self.rel.is_hidden(): |
892 | 892 | 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) |
893 | 895 | if self.rel.field_name is None: |
894 | 896 | self.rel.field_name = cls._meta.pk.name |
895 | 897 | |
diff -r 09ceaf49c46f django/db/models/options.py
a
|
b
|
|
55 | 55 | self.abstract_managers = [] |
56 | 56 | self.concrete_managers = [] |
57 | 57 | |
| 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 | |
58 | 62 | def contribute_to_class(self, cls, name): |
59 | 63 | from django.db import connection |
60 | 64 | from django.db.backends.util import truncate_name |
diff -r 09ceaf49c46f tests/regressiontests/admin_views/models.py
a
|
b
|
|
178 | 178 | class ThingAdmin(admin.ModelAdmin): |
179 | 179 | list_filter = ('color__warm', 'color__value') |
180 | 180 | |
| 181 | class Actor(models.Model): |
| 182 | name = models.CharField(max_length=50) |
| 183 | age = models.IntegerField() |
| 184 | def __unicode__(self): |
| 185 | return self.name |
| 186 | |
| 187 | class Inquisition(models.Model): |
| 188 | expected = models.BooleanField() |
| 189 | leader = models.ForeignKey(Actor) |
| 190 | def __unicode__(self): |
| 191 | return self.expected |
| 192 | |
| 193 | class 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 | |
181 | 201 | class Fabric(models.Model): |
182 | 202 | NG_CHOICES = ( |
183 | 203 | ('Textured', ( |
… |
… |
|
632 | 652 | admin.site.register(ModelWithStringPrimaryKey) |
633 | 653 | admin.site.register(Color) |
634 | 654 | admin.site.register(Thing, ThingAdmin) |
| 655 | admin.site.register(Actor) |
| 656 | admin.site.register(Inquisition) |
| 657 | admin.site.register(Sketch) |
635 | 658 | admin.site.register(Person, PersonAdmin) |
636 | 659 | admin.site.register(Persona, PersonaAdmin) |
637 | 660 | admin.site.register(Subscriber, SubscriberAdmin) |
diff -r 09ceaf49c46f tests/regressiontests/admin_views/tests.py
a
|
b
|
|
392 | 392 | response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk) |
393 | 393 | self.assertEqual(response.status_code, 200) |
394 | 394 | |
| 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 | |
395 | 406 | class SaveAsTests(TestCase): |
396 | 407 | fixtures = ['admin-views-users.xml','admin-views-person.xml'] |
397 | 408 | |