├── MANIFEST.in ├── adminfilters ├── __init__.py └── admin.py ├── .gitignore ├── setup.py ├── LICENSE └── README /MANIFEST.in: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /adminfilters/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.2' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .settings 3 | .*project 4 | dist 5 | *.egg-info 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = __import__('adminfilters').__version__ 4 | 5 | setup( 6 | name = 'django-admin-filters', 7 | version = version, 8 | description = 'Django Admin Filters', 9 | author = 'Jonas Obrist', 10 | author_email = 'jonas.obrist@divio.ch', 11 | url = 'http://github.com/ojii/django-admin-filters', 12 | packages = find_packages(), 13 | zip_safe=False, 14 | ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Jonas Obrist 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Jonas Obrist nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL JONAS OBRIST BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /adminfilters/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.views.main import ChangeList 2 | from django.contrib.admin.options import ModelAdmin 3 | from django.contrib.admin.filterspecs import FilterSpec 4 | 5 | 6 | class GenericFilterSpec(FilterSpec): 7 | def __init__(self, data, request, title): 8 | self.data = data 9 | self.request = request 10 | self._title = title 11 | 12 | def title(self): 13 | return self._title 14 | 15 | def has_output(self): 16 | return True 17 | 18 | def choices(self, changelist): 19 | if callable(self.data): 20 | choices = list(self.data()) 21 | else: 22 | choices = list(self.data) 23 | for choice in [dict(zip(['selected', 'query_string', 'display'], x)) for x in choices]: 24 | yield choice 25 | 26 | 27 | class GenericFilterChangeList(ChangeList): 28 | def __init__(self, request, *args, **kwargs): 29 | self.request = request 30 | super(GenericFilterChangeList, self).__init__(request, *args, **kwargs) 31 | 32 | @property 33 | def generic_filters(self): 34 | return getattr(self.model_admin, 'generic_filters', None) 35 | 36 | def build_filter_spec(self, choices, title): 37 | return GenericFilterSpec(choices, self.request, title) 38 | 39 | def get_filters(self, request): 40 | """ 41 | Extend ChangeList.get_filters to include generic_filters. 42 | """ 43 | filter_specs = super(GenericFilterChangeList, self).get_filters(request)[0] 44 | if self.generic_filters: 45 | for fname in self.generic_filters: 46 | func = getattr(self.model_admin, fname) 47 | spec = func(request, self) 48 | if spec and spec.has_output(): 49 | filter_specs.append(spec) 50 | return filter_specs, bool(filter_specs) 51 | 52 | 53 | class GenericFilterAdmin(ModelAdmin): 54 | def get_changelist(self, request, **kwargs): 55 | return GenericFilterChangeList 56 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ==================== 2 | Django Admin Filters 3 | ==================== 4 | 5 | Allows you to use generic filters for the admin changelist view. 6 | 7 | Quickstart 8 | ---------- 9 | 10 | Example:: 11 | 12 | from adminfilters.admin import GenericFilterAdmin 13 | import string 14 | 15 | class MyAdmin(GenericFilterAdmin): 16 | generic_filters = ('alphabetic_filter',) 17 | 18 | def alphabetic_filter(self, request, cl): 19 | """ 20 | Creates an alphabetic filter for the 'name' field on the model 21 | registered to this admin 22 | """ 23 | if self.model.objects.all().count(): 24 | selected = request.GET.get('name__istartswith', None) 25 | choices = [(selected is None, 26 | cl.get_query_string({}, ['name__istartswith']), 27 | 'All')] 28 | for letter in string.ascii_lowercase: 29 | if self.model.objects.filter(name__istartswith=letter).count(): 30 | choices.append((selected == letter, 31 | cl.get_query_string({'name__istartswith': letter}), 32 | letter.upper())) 33 | return cl.build_filter_spec(choices, 'alphabetic') 34 | return False 35 | 36 | 37 | Generic Filters Property 38 | ------------------------ 39 | 40 | The ``generic_filters`` property on a ``GenericFilterAdmin`` subclass defines a 41 | sequence of filter methods on the same class. 42 | 43 | 44 | Filter Methods 45 | -------------- 46 | 47 | A filter method takes a ``HttpRequest`` object and a ``GenericFilterChangeList`` 48 | object as arguments. It returns either False if this filter has no output for 49 | the given request and/or environment or a ``FilterSpec`` object. 50 | 51 | GenericFilterChangeList.build_filter_spec 52 | ----------------------------------------- 53 | 54 | This method takes two arguments. A sequence or callable defining the available 55 | choices and a title to be used for the filter spec. It returns a ``FilterSpec`` 56 | object. This is a helper method to quickly build ``FilterSpec`` objects without 57 | actually writing a ``FilterSpec`` subclass. 58 | 59 | If a callable is given as first argument, it should return a sequence when 60 | called. 61 | 62 | The sequence should contain 3-item sequences like this: 63 | 64 | * A boolean object whether this option is currently selected or not. 65 | * A query string to be used to activate this filter 66 | * A string to be displayed as the choices' label. 67 | 68 | GenericFilterChangeList.get_query_string 69 | ---------------------------------------- 70 | 71 | This method takes two arguments. A dictionary of key-value pairs to be *added* 72 | to the query string and optionally a sequence of keys to be *removed* from the 73 | query string. It returns a query string. --------------------------------------------------------------------------------