Ticket #3400: 3400.3.diff
File 3400.3.diff, 27.2 KB (added by , 14 years ago) |
---|
-
django/contrib/admin/filterspecs.py
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 6f643ee..6dd5dec 100644
a b from django.utils.encoding import smart_unicode, iri_to_uri 11 11 from django.utils.translation import ugettext as _ 12 12 from django.utils.html import escape 13 13 from django.utils.safestring import mark_safe 14 from django.contrib.admin.util import get_model_from_relation, \ 15 reverse_field_path, get_limit_choices_to_from_path 14 16 import datetime 15 17 16 18 class FilterSpec(object): 17 19 filter_specs = [] 18 def __init__(self, f, request, params, model, model_admin): 20 def __init__(self, f, request, params, model, model_admin, 21 field_path=None): 19 22 self.field = f 20 23 self.params = params 21 24 self.field_path = field_path 25 if field_path is None: 26 if isinstance(f, models.related.RelatedObject): 27 self.field_path = f.var_name 28 else: 29 self.field_path = f.name 30 22 31 def register(cls, test, factory): 23 32 cls.filter_specs.append((test, factory)) 24 33 register = classmethod(register) 25 34 26 def create(cls, f, request, params, model, model_admin ):35 def create(cls, f, request, params, model, model_admin, field_path=None): 27 36 for test, factory in cls.filter_specs: 28 37 if test(f): 29 return factory(f, request, params, model, model_admin) 38 return factory(f, request, params, model, model_admin, 39 field_path=field_path) 30 40 create = classmethod(create) 31 41 32 42 def has_output(self): … … class FilterSpec(object): 52 62 return mark_safe("".join(t)) 53 63 54 64 class RelatedFilterSpec(FilterSpec): 55 def __init__(self, f, request, params, model, model_admin): 56 super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin) 57 if isinstance(f, models.ManyToManyField): 58 self.lookup_title = f.rel.to._meta.verbose_name 65 def __init__(self, f, request, params, model, model_admin, 66 field_path=None): 67 super(RelatedFilterSpec, self).__init__( 68 f, request, params, model, model_admin, field_path=field_path) 69 70 other_model = get_model_from_relation(f) 71 if isinstance(f, (models.ManyToManyField, 72 models.related.RelatedObject)): 73 # no direct field on this model, get name from other model 74 self.lookup_title = other_model._meta.verbose_name 59 75 else: 60 self.lookup_title = f.verbose_name 61 rel_name = f.rel.get_related_field().name62 self.lookup_kwarg = '%s__%s__exact' % ( f.name, rel_name)76 self.lookup_title = f.verbose_name # use field name 77 rel_name = other_model._meta.pk.name 78 self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name) 63 79 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 64 80 self.lookup_choices = f.get_choices(include_blank=False) 65 81 66 82 def has_output(self): 67 83 return len(self.lookup_choices) > 1 68 84 … … class RelatedFilterSpec(FilterSpec): 78 94 'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), 79 95 'display': val} 80 96 81 FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec) 97 FilterSpec.register(lambda f: ( 98 hasattr(f, 'rel') and bool(f.rel) or 99 isinstance(f, models.related.RelatedObject)), RelatedFilterSpec) 82 100 83 101 class ChoicesFilterSpec(FilterSpec): 84 def __init__(self, f, request, params, model, model_admin): 85 super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin) 86 self.lookup_kwarg = '%s__exact' % f.name 102 def __init__(self, f, request, params, model, model_admin, 103 field_path=None): 104 super(ChoicesFilterSpec, self).__init__(f, request, params, model, 105 model_admin, 106 field_path=field_path) 107 self.lookup_kwarg = '%s__exact' % self.field_path 87 108 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 88 109 89 110 def choices(self, cl): … … class ChoicesFilterSpec(FilterSpec): 98 119 FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) 99 120 100 121 class DateFieldFilterSpec(FilterSpec): 101 def __init__(self, f, request, params, model, model_admin): 102 super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin) 122 def __init__(self, f, request, params, model, model_admin, 123 field_path=None): 124 super(DateFieldFilterSpec, self).__init__(f, request, params, model, 125 model_admin, 126 field_path=field_path) 103 127 104 self.field_generic = '%s__' % self.field .name128 self.field_generic = '%s__' % self.field_path 105 129 106 130 self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)]) 107 131 … … class DateFieldFilterSpec(FilterSpec): 111 135 112 136 self.links = ( 113 137 (_('Any date'), {}), 114 (_('Today'), {'%s__year' % self.field.name: str(today.year), 115 '%s__month' % self.field.name: str(today.month), 116 '%s__day' % self.field.name: str(today.day)}), 117 (_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'), 118 '%s__lte' % f.name: today_str}), 119 (_('This month'), {'%s__year' % self.field.name: str(today.year), 120 '%s__month' % f.name: str(today.month)}), 121 (_('This year'), {'%s__year' % self.field.name: str(today.year)}) 138 (_('Today'), {'%s__year' % self.field_path: str(today.year), 139 '%s__month' % self.field_path: str(today.month), 140 '%s__day' % self.field_path: str(today.day)}), 141 (_('Past 7 days'), {'%s__gte' % self.field_path: 142 one_week_ago.strftime('%Y-%m-%d'), 143 '%s__lte' % self.field_path: today_str}), 144 (_('This month'), {'%s__year' % self.field_path: str(today.year), 145 '%s__month' % self.field_path: str(today.month)}), 146 (_('This year'), {'%s__year' % self.field_path: str(today.year)}) 122 147 ) 123 148 124 149 def title(self): … … class DateFieldFilterSpec(FilterSpec): 133 158 FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) 134 159 135 160 class BooleanFieldFilterSpec(FilterSpec): 136 def __init__(self, f, request, params, model, model_admin): 137 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin) 138 self.lookup_kwarg = '%s__exact' % f.name 139 self.lookup_kwarg2 = '%s__isnull' % f.name 161 def __init__(self, f, request, params, model, model_admin, 162 field_path=None): 163 super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, 164 model_admin, 165 field_path=field_path) 166 self.lookup_kwarg = '%s__exact' % self.field_path 167 self.lookup_kwarg2 = '%s__isnull' % self.field_path 140 168 self.lookup_val = request.GET.get(self.lookup_kwarg, None) 141 169 self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) 142 170 … … FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f 159 187 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much 160 188 # more appropriate, and the AllValuesFilterSpec won't get used for it. 161 189 class AllValuesFilterSpec(FilterSpec): 162 def __init__(self, f, request, params, model, model_admin): 163 super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin) 164 self.lookup_val = request.GET.get(f.name, None) 165 self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name) 190 def __init__(self, f, request, params, model, model_admin, 191 field_path=None): 192 super(AllValuesFilterSpec, self).__init__(f, request, params, model, 193 model_admin, 194 field_path=field_path) 195 self.lookup_val = request.GET.get(self.field_path, None) 196 parent_model, reverse_path = reverse_field_path(model, field_path) 197 queryset = parent_model._default_manager.all() 198 # optional feature: limit choices base on existing relationships 199 # queryset = queryset.complex_filter( 200 # {'%s__isnull' % reverse_path: False}) 201 limit_choices_to = get_limit_choices_to_from_path(model, field_path) 202 queryset = queryset.filter(limit_choices_to) 203 204 self.lookup_choices = \ 205 queryset.distinct().order_by(f.name).values(f.name) 166 206 167 207 def title(self): 168 208 return self.field.verbose_name 169 209 170 210 def choices(self, cl): 171 211 yield {'selected': self.lookup_val is None, 172 'query_string': cl.get_query_string({}, [self.field .name]),212 'query_string': cl.get_query_string({}, [self.field_path]), 173 213 'display': _('All')} 174 214 for val in self.lookup_choices: 175 215 val = smart_unicode(val[self.field.name]) 176 216 yield {'selected': self.lookup_val == val, 177 'query_string': cl.get_query_string({self.field .name: val}),217 'query_string': cl.get_query_string({self.field_path: val}), 178 218 'display': val} 179 219 FilterSpec.register(lambda f: True, AllValuesFilterSpec) -
django/contrib/admin/util.py
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 3d076f7..c6411ae 100644
a b 1 1 from django.db import models 2 from django.db.models.sql.constants import LOOKUP_SEP 2 3 from django.db.models.deletion import Collector 3 4 from django.db.models.related import RelatedObject 4 5 from django.forms.forms import pretty_name … … def display_for_field(value, field): 280 281 return formats.number_format(value) 281 282 else: 282 283 return smart_unicode(value) 284 285 286 class NotRelationField(Exception): 287 pass 288 289 290 def get_model_from_relation(field): 291 if isinstance(field, models.related.RelatedObject): 292 return field.model 293 elif getattr(field, 'rel'): # or isinstance? 294 return field.rel.to 295 else: 296 raise NotRelationField 297 298 299 def reverse_field_path(model, path): 300 """ Create a reversed field path. 301 302 E.g. Given (Order, "user__groups"), 303 return (Group, "user__order"). 304 305 Final field must be a related model, not a data field. 306 307 """ 308 reversed_path = [] 309 parent = model 310 pieces = path.split(LOOKUP_SEP) 311 for piece in pieces: 312 field, model, direct, m2m = parent._meta.get_field_by_name(piece) 313 # skip trailing data field if extant: 314 if len(reversed_path) == len(pieces)-1: # final iteration 315 try: 316 get_model_from_relation(field) 317 except NotRelationField: 318 break 319 if direct: 320 related_name = field.related_query_name() 321 parent = field.rel.to 322 else: 323 related_name = field.field.name 324 parent = field.model 325 reversed_path.insert(0, related_name) 326 return (parent, LOOKUP_SEP.join(reversed_path)) 327 328 329 def get_fields_from_path(model, path): 330 """ Return list of Fields given path relative to model. 331 332 e.g. (ModelX, "user__groups__name") -> [ 333 <django.db.models.fields.related.ForeignKey object at 0x...>, 334 <django.db.models.fields.related.ManyToManyField object at 0x...>, 335 <django.db.models.fields.CharField object at 0x...>, 336 ] 337 """ 338 pieces = path.split(LOOKUP_SEP) 339 fields = [] 340 for piece in pieces: 341 if fields: 342 parent = get_model_from_relation(fields[-1]) 343 else: 344 parent = model 345 fields.append(parent._meta.get_field_by_name(piece)[0]) 346 return fields 347 348 349 def remove_trailing_data_field(fields): 350 """ Discard trailing non-relation field if extant. """ 351 try: 352 get_model_from_relation(fields[-1]) 353 except NotRelationField: 354 fields = fields[:-1] 355 return fields 356 357 358 def get_limit_choices_to_from_path(model, path): 359 """ Return Q object for limiting choices if applicable. 360 361 If final model in path is linked via a ForeignKey or ManyToManyField which 362 has a `limit_choices_to` attribute, return it as a Q object. 363 """ 364 365 fields = get_fields_from_path(model, path) 366 fields = remove_trailing_data_field(fields) 367 limit_choices_to = ( 368 fields and hasattr(fields[-1], 'rel') and 369 getattr(fields[-1].rel, 'limit_choices_to', None)) 370 if not limit_choices_to: 371 return models.Q() # empty Q 372 elif isinstance(limit_choices_to, models.Q): 373 return limit_choices_to # already a Q 374 else: 375 return models.Q(**limit_choices_to) # convert dict to Q -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 0434fc6..b7a01bb 100644
a b def validate(cls, model): 53 53 # list_filter 54 54 if hasattr(cls, 'list_filter'): 55 55 check_isseq(cls, 'list_filter', cls.list_filter) 56 for idx, field in enumerate(cls.list_filter): 57 get_field(cls, model, opts, 'list_filter[%d]' % idx, field) 56 # strict validation removed; same as search_fields now 58 57 59 58 # list_per_page = 100 60 59 if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): -
django/contrib/admin/views/main.py
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index e29d44a..0ebb7fd 100644
a b 1 1 from django.contrib.admin.filterspecs import FilterSpec 2 2 from django.contrib.admin.options import IncorrectLookupParameters 3 from django.contrib.admin.util import quote 3 from django.contrib.admin.util import quote, get_fields_from_path 4 4 from django.core.paginator import Paginator, InvalidPage 5 5 from django.db import models 6 6 from django.utils.encoding import force_unicode, smart_str … … class ChangeList(object): 68 68 def get_filters(self, request): 69 69 filter_specs = [] 70 70 if self.list_filter: 71 filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter] 72 for f in filter_fields: 73 spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin) 71 for filter_name in self.list_filter: 72 field = get_fields_from_path(self.model, filter_name)[-1] 73 spec = FilterSpec.create(field, request, self.params, 74 self.model, self.model_admin, 75 field_path=filter_name) 74 76 if spec and spec.has_output(): 75 77 filter_specs.append(spec) 76 78 return filter_specs, bool(filter_specs) -
django/db/models/related.py
diff --git a/django/db/models/related.py b/django/db/models/related.py index e4afd8a..7734230 100644
a b 1 from django.utils.encoding import smart_unicode 2 from django.db.models.fields import BLANK_CHOICE_DASH 3 1 4 class BoundRelatedObject(object): 2 5 def __init__(self, related_object, field_mapping, original): 3 6 self.relation = related_object … … class RelatedObject(object): 18 21 self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name) 19 22 self.var_name = self.opts.object_name.lower() 20 23 24 def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, 25 limit_to_currently_related=False): 26 """Returns choices with a default blank choices included, for use 27 as SelectField choices for this field. 28 29 Analogue of django.db.models.fields.Field.get_choices, provided 30 initially for utilisation by RelatedFilterSpec. 31 """ 32 first_choice = include_blank and blank_choice or [] 33 queryset = self.model._default_manager.all() 34 if limit_to_currently_related: 35 queryset = queryset.complex_filter( 36 {'%s__isnull' % self.parent_model._meta.module_name: False}) 37 lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset] 38 return first_choice + lst 39 21 40 def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False): 22 41 # Defer to the actual field definition for db prep 23 42 return self.field.get_db_prep_lookup(lookup_type, value, -
docs/ref/contrib/admin/index.txt
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ac517e8..f8276f4 100644
a b how both ``list_display`` and ``list_filter`` work:: 458 458 list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') 459 459 list_filter = ('is_staff', 'is_superuser') 460 460 461 In ``list_filter`` can be defined lookup separator as well:: 462 463 class UserAdminWithLookup(UserAdmin): 464 list_filter = ('groups__name') 465 461 466 The above code results in an admin change list page that looks like this: 462 467 463 468 .. image:: _images/users_changelist.png -
tests/regressiontests/admin_views/customadmin.py
diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py index 34e39ef..8cd262b 100644
a b site.register(models.Article, models.ArticleAdmin) 32 32 site.register(models.Section, inlines=[models.ArticleInline]) 33 33 site.register(models.Thing, models.ThingAdmin) 34 34 site.register(models.Fabric, models.FabricAdmin) 35 site.register(models.ChapterXtra1, models.ChapterXtra1Admin) -
new file tests/regressiontests/admin_views/fixtures/admin-views-books.xml
diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-books.xml b/tests/regressiontests/admin_views/fixtures/admin-views-books.xml new file mode 100644 index 0000000..37ded89
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <django-objects version="1.0"> 3 <object pk="1" model="admin_views.book"> 4 <field type="CharField" name="name">Book 1</field> 5 </object> 6 <object pk="2" model="admin_views.book"> 7 <field type="CharField" name="name">Book 2</field> 8 </object> 9 <object pk="1" model="admin_views.promo"> 10 <field type="CharField" name="name">Promo 1</field> 11 <field type="ForiegnKey" name="book">1</field> 12 </object> 13 <object pk="2" model="admin_views.promo"> 14 <field type="CharField" name="name">Promo 2</field> 15 <field type="ForiegnKey" name="book">2</field> 16 </object> 17 <object pk="1" model="admin_views.chapter"> 18 <field type="CharField" name="title">Chapter 1</field> 19 <field type="TextField" name="content">[ insert contents here ]</field> 20 <field type="ForiegnKey" name="book">1</field> 21 </object> 22 <object pk="2" model="admin_views.chapter"> 23 <field type="CharField" name="title">Chapter 2</field> 24 <field type="TextField" name="content">[ insert contents here ]</field> 25 <field type="ForiegnKey" name="book">1</field> 26 </object> 27 <object pk="3" model="admin_views.chapter"> 28 <field type="CharField" name="title">Chapter 1</field> 29 <field type="TextField" name="content">[ insert contents here ]</field> 30 <field type="ForiegnKey" name="book">2</field> 31 </object> 32 <object pk="4" model="admin_views.chapter"> 33 <field type="CharField" name="title">Chapter 2</field> 34 <field type="TextField" name="content">[ insert contents here ]</field> 35 <field type="ForiegnKey" name="book">2</field> 36 </object> 37 <object pk="1" model="admin_views.chapterxtra1"> 38 <field type="CharField" name="xtra">ChapterXtra1 1</field> 39 <field type="ForiegnKey" name="chap">1</field> 40 </object> 41 <object pk="2" model="admin_views.chapterxtra1"> 42 <field type="CharField" name="xtra">ChapterXtra1 2</field> 43 <field type="ForiegnKey" name="chap">3</field> 44 </object> 45 </django-objects> -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index b25a9b9..5d455a9 100644
a b class ArticleInline(admin.TabularInline): 90 90 class ChapterInline(admin.TabularInline): 91 91 model = Chapter 92 92 93 class ChapterXtra1Admin(admin.ModelAdmin): 94 list_filter = ('chap', 95 'chap__title', 96 'chap__book', 97 'chap__book__name', 98 'chap__book__promo', 99 'chap__book__promo__name',) 100 93 101 class ArticleAdmin(admin.ModelAdmin): 94 102 list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') 95 103 list_filter = ('date',) … … class Thing(models.Model): 149 157 return self.title 150 158 151 159 class ThingAdmin(admin.ModelAdmin): 152 list_filter = ('color', )160 list_filter = ('color', 'color__warm', 'color__value') 153 161 154 162 class Fabric(models.Model): 155 163 NG_CHOICES = ( … … admin.site.register(CyclicTwo) 627 635 # contrib.admin.util's get_deleted_objects function. 628 636 admin.site.register(Book, inlines=[ChapterInline]) 629 637 admin.site.register(Promo) 630 admin.site.register(ChapterXtra1 )638 admin.site.register(ChapterXtra1, ChapterXtra1Admin) 631 639 admin.site.register(Pizza, PizzaAdmin) 632 640 admin.site.register(Topping) 633 641 admin.site.register(Album) -
tests/regressiontests/admin_views/tests.py
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 4c96306..2794ca1 100644
a b from django.utils import formats 18 18 from django.utils.cache import get_max_age 19 19 from django.utils.encoding import iri_to_uri 20 20 from django.utils.html import escape 21 from django.utils.http import urlencode 21 22 from django.utils.translation import activate, deactivate 22 23 from django.utils import unittest 23 24 … … from models import Article, BarAccount, CustomArticle, EmptyModel, \ 26 27 FooAccount, Gallery, ModelWithStringPrimaryKey, \ 27 28 Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \ 28 29 Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \ 29 Category, Post, Plot, FunkyTag 30 Category, Post, Plot, FunkyTag, Chapter, Book, Promo 30 31 31 32 32 33 class AdminViewBasicTest(TestCase): 33 fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] 34 fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 35 'admin-views-fabrics.xml', 'admin-views-books.xml'] 34 36 35 37 # Store the bit of the URL where the admin is registered as a class 36 38 # variable. That way we can test a second AdminSite just by subclassing … … class AdminViewBasicTest(TestCase): 203 205 ) 204 206 205 207 def testLimitedFilter(self): 206 """Ensure admin changelist filters do not contain objects excluded via limit_choices_to.""" 208 """Ensure admin changelist filters do not contain objects excluded via limit_choices_to. 209 This also tests relation-spanning filters (e.g. 'color__value'). 210 """ 207 211 response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit) 208 212 self.failUnlessEqual(response.status_code, 200) 209 213 self.failUnless( … … class AdminViewBasicTest(TestCase): 215 219 "Changelist filter not correctly limited by limit_choices_to." 216 220 ) 217 221 222 def testRelationSpanningFilters(self): 223 response = self.client.get('/test_admin/%s/admin_views/chapterxtra1/' % 224 self.urlbit) 225 self.failUnlessEqual(response.status_code, 200) 226 self.assertContains(response, '<div id="changelist-filter">') 227 filters = { 228 'chap__id__exact': dict( 229 values=[c.id for c in Chapter.objects.all()], 230 test=lambda obj, value: obj.chap.id == value), 231 'chap__title': dict( 232 values=[c.title for c in Chapter.objects.all()], 233 test=lambda obj, value: obj.chap.title == value), 234 'chap__book__id__exact': dict( 235 values=[b.id for b in Book.objects.all()], 236 test=lambda obj, value: obj.chap.book.id == value), 237 'chap__book__name': dict( 238 values=[b.name for b in Book.objects.all()], 239 test=lambda obj, value: obj.chap.book.name == value), 240 'chap__book__promo__id__exact': dict( 241 values=[p.id for p in Promo.objects.all()], 242 test=lambda obj, value: 243 obj.chap.book.promo_set.filter(id=value).exists()), 244 'chap__book__promo__name': dict( 245 values=[p.name for p in Promo.objects.all()], 246 test=lambda obj, value: 247 obj.chap.book.promo_set.filter(name=value).exists()), 248 } 249 for filter_path, params in filters.items(): 250 for value in params['values']: 251 query_string = urlencode({filter_path: value}) 252 # ensure filter link exists 253 self.assertContains(response, '<a href="?%s">' % query_string) 254 # ensure link works 255 filtered_response = self.client.get( 256 '/test_admin/%s/admin_views/chapterxtra1/?%s' % ( 257 self.urlbit, query_string)) 258 self.failUnlessEqual(filtered_response.status_code, 200) 259 # ensure changelist contains only valid objects 260 for obj in filtered_response.context['cl'].query_set.all(): 261 self.assertTrue(params['test'](obj, value)) 262 218 263 def testIncorrectLookupParameters(self): 219 264 """Ensure incorrect lookup parameters are handled gracefully.""" 220 265 response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'}) -
tests/regressiontests/modeladmin/tests.py
diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py index 762517a..65e2862 100644
a b class ModelAdminTests(TestCase): 58 58 # If we specify the fields argument, fieldsets_add and fielsets_change should 59 59 # just stick the fields into a formsets structure and return it. 60 60 class BandAdmin(ModelAdmin): 61 61 fields = ['name'] 62 62 63 63 ma = BandAdmin(Band, self.site) 64 64 … … class ValidationTests(unittest.TestCase): 831 831 ) 832 832 833 833 class ValidationTestModelAdmin(ModelAdmin): 834 list_filter = ('non_existent_field',)835 836 self.assertRaisesRegexp(837 ImproperlyConfigured,838 "'ValidationTestModelAdmin.list_filter\[0\]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.",839 validate,840 ValidationTestModelAdmin,841 ValidationTestModel,842 )843 844 class ValidationTestModelAdmin(ModelAdmin):845 834 list_filter = ('is_active',) 846 835 847 836 validate(ValidationTestModelAdmin, ValidationTestModel)