Ticket #3400: 3400.3.diff

File 3400.3.diff, 27.2 KB (added by Vitek Pliska, 9 years ago)

Patch update (against 14662)

  • 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 
    1111from django.utils.translation import ugettext as _
    1212from django.utils.html import escape
    1313from django.utils.safestring import mark_safe
     14from django.contrib.admin.util import get_model_from_relation, \
     15    reverse_field_path, get_limit_choices_to_from_path
    1416import datetime
    1517
    1618class FilterSpec(object):
    1719    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):
    1922        self.field = f
    2023        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               
    2231    def register(cls, test, factory):
    2332        cls.filter_specs.append((test, factory))
    2433    register = classmethod(register)
    2534
    26     def create(cls, f, request, params, model, model_admin):
     35    def create(cls, f, request, params, model, model_admin, field_path=None):
    2736        for test, factory in cls.filter_specs:
    2837            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)
    3040    create = classmethod(create)
    3141
    3242    def has_output(self):
    class FilterSpec(object): 
    5262        return mark_safe("".join(t))
    5363
    5464class 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
    5975        else:
    60             self.lookup_title = f.verbose_name
    61         rel_name = f.rel.get_related_field().name
    62         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)
    6379        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    6480        self.lookup_choices = f.get_choices(include_blank=False)
    65 
     81       
    6682    def has_output(self):
    6783        return len(self.lookup_choices) > 1
    6884
    class RelatedFilterSpec(FilterSpec): 
    7894                   'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
    7995                   'display': val}
    8096
    81 FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
     97FilterSpec.register(lambda f: (
     98        hasattr(f, 'rel') and bool(f.rel) or
     99        isinstance(f, models.related.RelatedObject)), RelatedFilterSpec)
    82100
    83101class 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
    87108        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    88109
    89110    def choices(self, cl):
    class ChoicesFilterSpec(FilterSpec): 
    98119FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
    99120
    100121class 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)
    103127
    104         self.field_generic = '%s__' % self.field.name
     128        self.field_generic = '%s__' % self.field_path
    105129
    106130        self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)])
    107131
    class DateFieldFilterSpec(FilterSpec): 
    111135
    112136        self.links = (
    113137            (_('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)})
    122147        )
    123148
    124149    def title(self):
    class DateFieldFilterSpec(FilterSpec): 
    133158FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
    134159
    135160class 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
    140168        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    141169        self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
    142170
    FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f 
    159187# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
    160188# more appropriate, and the AllValuesFilterSpec won't get used for it.
    161189class 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)
    166206
    167207    def title(self):
    168208        return self.field.verbose_name
    169209
    170210    def choices(self, cl):
    171211        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]),
    173213               'display': _('All')}
    174214        for val in self.lookup_choices:
    175215            val = smart_unicode(val[self.field.name])
    176216            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}),
    178218                   'display': val}
    179219FilterSpec.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  
    11from django.db import models
     2from django.db.models.sql.constants import LOOKUP_SEP
    23from django.db.models.deletion import Collector
    34from django.db.models.related import RelatedObject
    45from django.forms.forms import pretty_name
    def display_for_field(value, field): 
    280281        return formats.number_format(value)
    281282    else:
    282283        return smart_unicode(value)
     284
     285
     286class NotRelationField(Exception):
     287    pass
     288
     289
     290def 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
     299def 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
     329def 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
     349def 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
     358def 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): 
    5353    # list_filter
    5454    if hasattr(cls, 'list_filter'):
    5555        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
    5857
    5958    # list_per_page = 100
    6059    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  
    11from django.contrib.admin.filterspecs import FilterSpec
    22from django.contrib.admin.options import IncorrectLookupParameters
    3 from django.contrib.admin.util import quote
     3from django.contrib.admin.util import quote, get_fields_from_path
    44from django.core.paginator import Paginator, InvalidPage
    55from django.db import models
    66from django.utils.encoding import force_unicode, smart_str
    class ChangeList(object): 
    6868    def get_filters(self, request):
    6969        filter_specs = []
    7070        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)
    7476                if spec and spec.has_output():
    7577                    filter_specs.append(spec)
    7678        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  
     1from django.utils.encoding import smart_unicode
     2from django.db.models.fields import BLANK_CHOICE_DASH
     3
    14class BoundRelatedObject(object):
    25    def __init__(self, related_object, field_mapping, original):
    36        self.relation = related_object
    class RelatedObject(object): 
    1821        self.name = '%s:%s' % (self.opts.app_label, self.opts.module_name)
    1922        self.var_name = self.opts.object_name.lower()
    2023
     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       
    2140    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
    2241        # Defer to the actual field definition for db prep
    2342        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:: 
    458458        list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
    459459        list_filter = ('is_staff', 'is_superuser')
    460460
     461In ``list_filter`` can be defined lookup separator as well::
     462
     463    class UserAdminWithLookup(UserAdmin):
     464        list_filter = ('groups__name')
     465
    461466The above code results in an admin change list page that looks like this:
    462467
    463468    .. 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) 
    3232site.register(models.Section, inlines=[models.ArticleInline])
    3333site.register(models.Thing, models.ThingAdmin)
    3434site.register(models.Fabric, models.FabricAdmin)
     35site.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): 
    9090class ChapterInline(admin.TabularInline):
    9191    model = Chapter
    9292
     93class 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
    93101class ArticleAdmin(admin.ModelAdmin):
    94102    list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
    95103    list_filter = ('date',)
    class Thing(models.Model): 
    149157        return self.title
    150158
    151159class ThingAdmin(admin.ModelAdmin):
    152     list_filter = ('color',)
     160    list_filter = ('color', 'color__warm', 'color__value')
    153161
    154162class Fabric(models.Model):
    155163    NG_CHOICES = (
    admin.site.register(CyclicTwo) 
    627635# contrib.admin.util's get_deleted_objects function.
    628636admin.site.register(Book, inlines=[ChapterInline])
    629637admin.site.register(Promo)
    630 admin.site.register(ChapterXtra1)
     638admin.site.register(ChapterXtra1, ChapterXtra1Admin)
    631639admin.site.register(Pizza, PizzaAdmin)
    632640admin.site.register(Topping)
    633641admin.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 
    1818from django.utils.cache import get_max_age
    1919from django.utils.encoding import iri_to_uri
    2020from django.utils.html import escape
     21from django.utils.http import urlencode
    2122from django.utils.translation import activate, deactivate
    2223from django.utils import unittest
    2324
    from models import Article, BarAccount, CustomArticle, EmptyModel, \ 
    2627    FooAccount, Gallery, ModelWithStringPrimaryKey, \
    2728    Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
    2829    Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
    29     Category, Post, Plot, FunkyTag
     30    Category, Post, Plot, FunkyTag, Chapter, Book, Promo
    3031
    3132
    3233class 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']
    3436
    3537    # Store the bit of the URL where the admin is registered as a class
    3638    # variable. That way we can test a second AdminSite just by subclassing
    class AdminViewBasicTest(TestCase): 
    203205        )
    204206
    205207    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        """
    207211        response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
    208212        self.failUnlessEqual(response.status_code, 200)
    209213        self.failUnless(
    class AdminViewBasicTest(TestCase): 
    215219            "Changelist filter not correctly limited by limit_choices_to."
    216220        )
    217221
     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
    218263    def testIncorrectLookupParameters(self):
    219264        """Ensure incorrect lookup parameters are handled gracefully."""
    220265        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): 
    5858        # If we specify the fields argument, fieldsets_add and fielsets_change should
    5959        # just stick the fields into a formsets structure and return it.
    6060        class BandAdmin(ModelAdmin):
    61              fields = ['name']
     61            fields = ['name']
    6262
    6363        ma = BandAdmin(Band, self.site)
    6464
    class ValidationTests(unittest.TestCase): 
    831831        )
    832832
    833833        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):
    845834            list_filter = ('is_active',)
    846835
    847836        validate(ValidationTestModelAdmin, ValidationTestModel)
Back to Top