Ticket #5833: 5833.patch

File 5833.patch, 14.6 KB (added by Samuel Cormier-Iijima, 9 years ago)

New patch against 1.1/SVN

  • django/contrib/admin/__init__.py

    diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
    index 8105976..a80444e 100644
    a b from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 
    22from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
    33from django.contrib.admin.options import StackedInline, TabularInline
    44from django.contrib.admin.sites import AdminSite, site
     5from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
    56from django.utils.importlib import import_module
    67
    78# A flag to tell us if autodiscover is running.  autodiscover will set this to
  • django/contrib/admin/filterspecs.py

    diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
    index 6f643ee..10cef73 100644
    a b from django.utils.safestring import mark_safe 
    1414import datetime
    1515
    1616class FilterSpec(object):
    17     filter_specs = []
    18     def __init__(self, f, request, params, model, model_admin):
    19         self.field = f
     17    def __init__(self, request, params, model, model_admin):
    2018        self.params = params
    2119
    22     def register(cls, test, factory):
    23         cls.filter_specs.append((test, factory))
    24     register = classmethod(register)
    25 
    26     def create(cls, f, request, params, model, model_admin):
    27         for test, factory in cls.filter_specs:
    28             if test(f):
    29                 return factory(f, request, params, model, model_admin)
    30     create = classmethod(create)
    31 
    3220    def has_output(self):
    3321        return True
    3422
    3523    def choices(self, cl):
    3624        raise NotImplementedError()
    37 
     25       
    3826    def title(self):
    39         return self.field.verbose_name
     27        raise NotImplementedError()
     28       
     29    def get_query_set(self, cl, qs):
     30        return False
     31   
     32    def consumed_params(self):
     33        """
     34        Return a list of parameters to consume from the change list querystring.
     35       
     36        Override this for non-field based FilterSpecs subclasses in order
     37        to consume custom GET parameters, as any GET parameters that are not
     38        consumed and are not a field name raises an exception.
     39        """
     40        return []
    4041
    4142    def output(self, cl):
    4243        t = []
    class FilterSpec(object): 
    5051                     choice['display']))
    5152            t.append('</ul>\n\n')
    5253        return mark_safe("".join(t))
     54       
     55class FieldFilterSpec(FilterSpec):
     56    field_filter_specs = []
     57    _high_priority_index = 0
     58   
     59    def __init__(self, request, params, model, model_admin, f):
     60        super(FieldFilterSpec, self).__init__(request, params, model, model_admin)
     61        self.field = f
     62       
     63    def title(self):
     64        return self.field.verbose_name
     65   
     66    def register(cls, test, factory, high_priority=True):
     67        if high_priority:
     68            cls.field_filter_specs.insert(cls._high_priority_index, (test, factory))
     69            cls._high_priority_index += 1
     70        else:
     71            cls.field_filter_specs.append((test, factory))
     72    register = classmethod(register)
     73   
     74    def create(cls, request, params, model, model_admin, f):
     75        for test, factory in cls.field_filter_specs:
     76            if test(f):
     77                return factory(request, params, model, model_admin, f)
     78    create = classmethod(create)
    5379
    54 class RelatedFilterSpec(FilterSpec):
    55     def __init__(self, f, request, params, model, model_admin):
    56         super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin)
     80class RelatedFilterSpec(FieldFilterSpec):
     81    def __init__(self, request, params, model, model_admin, f):
     82        super(RelatedFilterSpec, self).__init__(request, params, model, model_admin, f)
    5783        if isinstance(f, models.ManyToManyField):
    5884            self.lookup_title = f.rel.to._meta.verbose_name
    5985        else:
    class RelatedFilterSpec(FilterSpec): 
    78104                   'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
    79105                   'display': val}
    80106
    81 FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec)
     107FieldFilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec, False)
    82108
    83 class ChoicesFilterSpec(FilterSpec):
    84     def __init__(self, f, request, params, model, model_admin):
    85         super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin)
     109class ChoicesFilterSpec(FieldFilterSpec):
     110    def __init__(self, request, params, model, model_admin, f):
     111        super(ChoicesFilterSpec, self).__init__(request, params, model, model_admin, f)
    86112        self.lookup_kwarg = '%s__exact' % f.name
    87113        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    88114
    class ChoicesFilterSpec(FilterSpec): 
    95121                    'query_string': cl.get_query_string({self.lookup_kwarg: k}),
    96122                    'display': v}
    97123
    98 FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
     124FieldFilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec, False)
    99125
    100 class DateFieldFilterSpec(FilterSpec):
    101     def __init__(self, f, request, params, model, model_admin):
    102         super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
     126class DateFieldFilterSpec(FieldFilterSpec):
     127    def __init__(self, request, params, model, model_admin, f):
     128        super(DateFieldFilterSpec, self).__init__(request, params, model, model_admin, f)
    103129
    104130        self.field_generic = '%s__' % self.field.name
    105131
    class DateFieldFilterSpec(FilterSpec): 
    121147            (_('This year'), {'%s__year' % self.field.name: str(today.year)})
    122148        )
    123149
    124     def title(self):
    125         return self.field.verbose_name
    126 
    127150    def choices(self, cl):
    128151        for title, param_dict in self.links:
    129152            yield {'selected': self.date_params == param_dict,
    130153                   'query_string': cl.get_query_string(param_dict, [self.field_generic]),
    131154                   'display': title}
    132155
    133 FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
     156FieldFilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec, False)
    134157
    135 class BooleanFieldFilterSpec(FilterSpec):
    136     def __init__(self, f, request, params, model, model_admin):
    137         super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin)
     158class BooleanFieldFilterSpec(FieldFilterSpec):
     159    def __init__(self, request, params, model, model_admin, f):
     160        super(BooleanFieldFilterSpec, self).__init__(request, params, model, model_admin, f)
    138161        self.lookup_kwarg = '%s__exact' % f.name
    139162        self.lookup_kwarg2 = '%s__isnull' % f.name
    140163        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    141164        self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
    142165
    143     def title(self):
    144         return self.field.verbose_name
    145 
    146166    def choices(self, cl):
    147167        for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):
    148168            yield {'selected': self.lookup_val == v and not self.lookup_val2,
    class BooleanFieldFilterSpec(FilterSpec): 
    153173                   'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
    154174                   'display': _('Unknown')}
    155175
    156 FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)
     176FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec, False)
    157177
    158178# This should be registered last, because it's a last resort. For example,
    159179# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
    160180# more appropriate, and the AllValuesFilterSpec won't get used for it.
    161 class AllValuesFilterSpec(FilterSpec):
    162     def __init__(self, f, request, params, model, model_admin):
    163         super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin)
     181class AllValuesFilterSpec(FieldFilterSpec):
     182    def __init__(self, request, params, model, model_admin, f):
     183        super(AllValuesFilterSpec, self).__init__(request, params, model, model_admin, f)
    164184        self.lookup_val = request.GET.get(f.name, None)
    165185        self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name)
    166186
    167     def title(self):
    168         return self.field.verbose_name
    169 
    170187    def choices(self, cl):
    171188        yield {'selected': self.lookup_val is None,
    172189               'query_string': cl.get_query_string({}, [self.field.name]),
    class AllValuesFilterSpec(FilterSpec): 
    176193            yield {'selected': self.lookup_val == val,
    177194                   'query_string': cl.get_query_string({self.field.name: val}),
    178195                   'display': val}
    179 FilterSpec.register(lambda f: True, AllValuesFilterSpec)
     196FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False)
  • django/contrib/admin/validation.py

    diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
    index 50e4143..c72515e 100644
    a b from django.db import models 
    88from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model, _get_foreign_key
    99from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
    1010from django.contrib.admin.options import HORIZONTAL, VERTICAL
     11from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
    1112
    1213__all__ = ['validate']
    1314
    def validate(cls, model): 
    5051            fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field)
    5152            if field not in cls.list_display:
    5253                raise ImproperlyConfigured("'%s.list_display_links[%d]'"
    53                         "refers to '%s' which is not defined in 'list_display'."
     54                        " refers to '%s' which is not defined in 'list_display'."
    5455                        % (cls.__name__, idx, field))
    5556
    5657    # list_filter
    5758    if hasattr(cls, 'list_filter'):
    5859        check_isseq(cls, 'list_filter', cls.list_filter)
    59         for idx, field in enumerate(cls.list_filter):
    60             get_field(cls, model, opts, 'list_filter[%d]' % idx, field)
     60        for idx, item in enumerate(cls.list_filter):
     61            if callable(item) and not isinstance(item, models.Field):
     62                # Make sure the item is not FieldFilterSpec or a subclass thereof
     63                # since it has a different __init__ signature, which leads to
     64                # strange exceptions if not caught here
     65                if not issubclass(item, FilterSpec) or issubclass(item, FieldFilterSpec):
     66                    raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s' which is not of type FilterSpec."
     67                        % (cls.__name__, idx, item))
     68                else:
     69                    try:
     70                        field, factory = item
     71                    except (TypeError, ValueError):
     72                        field = item
     73                    else:
     74                        if not issubclass(factory, FieldFilterSpec):
     75                            raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
     76                                " refers to '%s' which is not of type FieldFilterSpec."
     77                                % (cls.__name__, idx, factory.__name__))
     78                    # Validate field
     79                    if not isinstance(field, models.Field):
     80                        get_field(cls, model, opts, 'list_filter[%d]' % idx, field)
     81            elif not isinstance(item, models.Field):
     82                get_field(cls, model, opts, 'list_filter[%d]' % idx, item)
    6183
    6284    # list_per_page = 100
    6385    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 df0fd9f..da01a82 100644
    a b  
    1 from django.contrib.admin.filterspecs import FilterSpec
     1from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
    22from django.contrib.admin.options import IncorrectLookupParameters
    33from django.contrib.admin.util import quote
    44from django.core.paginator import Paginator, InvalidPage
    class ChangeList(object): 
    6363        if ERROR_FLAG in self.params:
    6464            del self.params[ERROR_FLAG]
    6565
     66        self.filter_specs, self.has_filters = self.get_filters(request)
    6667        self.order_field, self.order_type = self.get_ordering()
    6768        self.query = request.GET.get(SEARCH_VAR, '')
    6869        self.query_set = self.get_query_set()
    6970        self.get_results(request)
    7071        self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
    71         self.filter_specs, self.has_filters = self.get_filters(request)
    7272        self.pk_attname = self.lookup_opts.pk.attname
    7373
    7474    def get_filters(self, request):
    7575        filter_specs = []
    7676        if self.list_filter:
    77             filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
    78             for f in filter_fields:
    79                 spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
     77            for item in self.list_filter:
     78                if callable(item):
     79                    spec = item(request, self.params, self.model, self.model_admin)
     80                else:
     81                    try:
     82                        field, factory = item
     83                    except (TypeError, ValueError):
     84                        field, factory = item, FieldFilterSpec.create
     85                    if not isinstance(field, models.Field):
     86                        field = self.lookup_opts.get_field(field)
     87                    spec = factory(request, self.params, self.model,
     88                                   self.model_admin, field)
    8089                if spec and spec.has_output():
    8190                    filter_specs.append(spec)
    8291        return filter_specs, bool(filter_specs)
    class ChangeList(object): 
    184193            # if key ends with __in, split parameter into separate values
    185194            if key.endswith('__in'):
    186195                lookup_params[key] = value.split(',')
     196               
     197        # Let every filter spec modify the qs and params to its liking
     198        for filter_spec in self.filter_specs:
     199            new_qs = filter_spec.get_query_set(self, qs)
     200            if new_qs:
     201                qs = new_qs
     202                # Only consume params if we got a new queryset
     203                for param in filter_spec.consumed_params():
     204                    try:
     205                        del lookup_params[param]
     206                    except KeyError:
     207                        pass
    187208
    188209        # Apply lookup parameters from the query string.
    189210        try:
Back to Top