Skip to content

Commit

Permalink
Merge branch 'develop' into nkryptic-issue-84
Browse files Browse the repository at this point in the history
  • Loading branch information
apollo13 committed Aug 7, 2013
2 parents 0669cf9 + ba17954 commit 1d885d9
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 78 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ python:

env:
- DJANGO=https://github.com/django/django/archive/master.tar.gz
- DJANGO=https://github.com/django/django/archive/stable/1.6.x.tar.gz
- DJANGO=django==1.5 --use-mirrors
- DJANGO=django==1.4.5 --use-mirrors

Expand All @@ -28,3 +29,6 @@ matrix:
env: DJANGO=django==1.4.5 --use-mirrors
- python: "3.3"
env: DJANGO=django==1.4.5 --use-mirrors
- python: "2.6"
env: DJANGO=https://github.com/django/django/archive/master.tar.gz

1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Maximillian Dornseif
Marc Fargas
Vladimir Sidorenko
Tom Christie
Remco Wendt
14 changes: 8 additions & 6 deletions django_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Filter(object):
field_class = forms.Field

def __init__(self, name=None, label=None, widget=None, action=None,
lookup_type='exact', required=False, **kwargs):
lookup_type='exact', required=False, distinct=False, **kwargs):
self.name = name
self.label = label
if action:
Expand All @@ -39,6 +39,7 @@ def __init__(self, name=None, label=None, widget=None, action=None,
self.widget = widget
self.required = required
self.extra = kwargs
self.distinct = distinct

self.creation_counter = Filter.creation_counter
Filter.creation_counter += 1
Expand All @@ -62,17 +63,18 @@ def field(self):
return self._field

def filter(self, qs, value):
if not value:
return qs
if isinstance(value, (list, tuple)):
lookup = six.text_type(value[1])
if not lookup:
lookup = 'exact' # fallback to exact if lookup is not provided
value = value[0]
else:
lookup = self.lookup_type
if value:
return qs.filter(**{'%s__%s' % (self.name, lookup): value})
if value in ([], (), {}, None, ''):
return qs
qs = qs.filter(**{'%s__%s' % (self.name, lookup): value})
if self.distinct:
qs = qs.distinct()
return qs


Expand Down Expand Up @@ -148,7 +150,7 @@ def filter(self, qs, value):

class DateRangeFilter(ChoiceFilter):
options = {
'': (_('Any Date'), lambda qs, name: qs.all()),
'': (_('Any date'), lambda qs, name: qs.all()),
1: (_('Today'), lambda qs, name: qs.filter(**{
'%s__year' % name: now().year,
'%s__month' % name: now().month,
Expand Down
6 changes: 5 additions & 1 deletion django_filters/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def filters_for_model(model, fields=None, exclude=None, filter_for_field=None,
field_dict = SortedDict()
opts = model._meta
if fields is None:
fields = [f.name for f in sorted(opts.fields + opts.many_to_many)]
fields = [f.name for f in sorted(opts.fields + opts.many_to_many)
if not isinstance(f, models.AutoField)]
for f in fields:
if exclude is not None and f in exclude:
continue
Expand Down Expand Up @@ -135,6 +136,9 @@ def __new__(cls, name, bases, attrs):


FILTER_FOR_DBFIELD_DEFAULTS = {
models.AutoField: {
'filter_class': NumberFilter
},
models.CharField: {
'filter_class': CharFilter
},
Expand Down
7 changes: 4 additions & 3 deletions docs/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
django-filter
=============

Django-filter is a generic, reusable application to alleviate some of the more
mundane bits of view code. Specifically allowing the users to filter down a
queryset based on a model's fields and displaying the form to let them do this.
Django-filter is a generic, reusable application to alleviate writing some of
the more mundane bits of view code. Specifically, it allows users to filter
down a queryset based on a model's fields, displaying the form to let them
do this.

Contents:

Expand Down
2 changes: 1 addition & 1 deletion docs/install.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Installing django-filter
------------------------

To install, simply place the ``django_filters`` directory somehwere on your
To install, simply place the ``django_filters`` directory somewhere on your
``PYTHONPATH``, and then add ``'django_filters'`` to your ``INSTALLED_APPS``.
6 changes: 6 additions & 0 deletions docs/ref/filters.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ then the user can select the lookup type from all the ones available in the Djan
ORM. If a ``list`` or ``tuple`` is provided, then the user can select from those
options.

``distinct``
~~~~~~~~~~~~~~~

A boolean value that specifies whether the Filter will use distinct on the
queryset. This option can be used to eliminate duplicate results when using filters that span related models. Defaults to ``False``.

``**kwargs``
~~~~~~~~~~~~

Expand Down
48 changes: 34 additions & 14 deletions docs/usage.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
Using django-filter
-------------------
===================

Django-filter provides a simple way to filter down a queryset based on
parameters a user provides. Say we have a ``Product`` model and we want to let
our users filter which products they see on a list page. Let's start with our
model::
our users filter which products they see on a list page.

The model
--------

Let's start with our model::

from django.db import models

Expand All @@ -15,6 +19,9 @@ model::
release_date = models.DateField()
manufacturer = models.ForeignKey(Manufacturer)

The filter
----------

We have a number of fields and we want to let our users filter based on the
price or the release_date. We create a ``FilterSet`` for this::

Expand All @@ -27,7 +34,7 @@ price or the release_date. We create a ``FilterSet`` for this::


As you can see this uses a very similar API to Django's ``ModelForm``. Just
like with a ``ModelForm`` we can also overide filters, or add new ones using a
like with a ``ModelForm`` we can also override filters, or add new ones using a
declarative syntax::

import django_filters
Expand All @@ -43,8 +50,8 @@ use with Django's ORM. So here when a user entered a price it would show all
Products with a price less than that.

Filters also take any arbitrary keyword arguments which get passed onto the
``django.forms.Field`` constructor. These extra keyword arguments get stored
in ``Filter.extra``, so it's possible to overide the constructor of a
``django.forms.Field`` initializer. These extra keyword arguments get stored
in ``Filter.extra``, so it's possible to override the initializer of a
``FilterSet`` to add extra ones::

class ProductFilterSet(django_filters.FilterSet):
Expand All @@ -58,6 +65,9 @@ in ``Filter.extra``, so it's possible to overide the constructor of a
{'empty_label': 'All Manufacturers'})


The view
--------

Now we need to write a view::

def product_list(request):
Expand All @@ -67,6 +77,16 @@ Now we need to write a view::
If a queryset argument isn't provided then all the items in the default manager
of the model will be used.

The URL conf
------------

We need a URL pattern to call the view::

url(r'^list$', views.product_list)

The template
------------

And lastly we need a template::

{% extends "base.html" %}
Expand Down Expand Up @@ -112,12 +132,12 @@ Items in the ``fields`` sequence in the ``Meta`` class may include
"relationship paths" using Django's ``__`` syntax to filter on fields on a
related model.

If you want to use a custom widget, or in any other way overide the ordering
field you can overide the ``get_ordering_field()`` method on a ``FilterSet``.
If you want to use a custom widget, or in any other way override the ordering
field you can override the ``get_ordering_field()`` method on a ``FilterSet``.
This method just needs to return a Form Field.

Generic View
============
------------

In addition to the above usage there is also a class-based generic view
included in django-filter, which lives at ``django_filters.views.FilterView``.
Expand All @@ -130,15 +150,15 @@ You must provide either a ``model`` or ``filter_class`` argument, similar to
from myapp.models import Product

urlpatterns = patterns('',
(r'^list/', FilterView.as_view(model=Product)),
(r'^list/$', FilterView.as_view(model=Product)),
)

You must provide a template at ``<app>/<model>_filter.html`` which gets the
context parameter ``filter``. Additionally, the context will contain
``object_list`` which holds the filtered queryset.

A legacy functional generic view is still include in django-filter, although
it's use is deprecated. It can be found at
A legacy functional generic view is still included in django-filter, although
its use is deprecated. It can be found at
``django_filters.views.object_filter``. You must provide the same arguments
to it as the class based view::

Expand All @@ -147,8 +167,8 @@ to it as the class based view::
from myapp.models import Product

urlpatterns = patterns('',
(r'^list/', 'django_filters.views.object_filter', {'model': Product}),
(r'^list/$', 'django_filters.views.object_filter', {'model': Product}),
)

The needed template and it's context variables will also be the same as the
The needed template and its context variables will also be the same as the
class-based view above.
9 changes: 7 additions & 2 deletions runtests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#!/usr/bin/env python
import sys
from django import VERSION
from django.conf import settings
from django.core.management import execute_from_command_line

if not settings.configured:
test_runners_args = {}
if VERSION[1] < 6:
test_runners_args = {
'TEST_RUNNER': 'discover_runner.DiscoverRunner',
}
settings.configure(
DATABASES={
'default': {
Expand All @@ -18,8 +24,7 @@
ROOT_URLCONF=None,
USE_TZ=True,
SECRET_KEY='foobar',
TEST_RUNNER='discover_runner.DiscoverRunner',
TEST_DISCOVER_PATTERN="*_tests.py"
**test_runners_args
)


Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
license-file = LICENSE

[wheel]
universal=1 # use py2.py3 tag for pure-python dist
universal=1
10 changes: 5 additions & 5 deletions tests/field_tests.py → tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ def to_d(float_value):


class RangeFieldTests(TestCase):

def test_field(self):
f = RangeField()
self.assertEqual(len(f.fields), 2)

def test_clean(self):
w = RangeWidget()
f = RangeField(widget=w)

self.assertEqual(
f.clean(['12.34', '55']),
slice(to_d(12.34), to_d(55)))


class LookupTypeFieldTests(TestCase):

def test_field(self):
inner = forms.DecimalField()
f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')])
Expand Down Expand Up @@ -70,13 +70,13 @@ def test_render_used_html5(self):
inner = forms.DecimalField()
f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')])
self.assertHTMLEqual(f.widget.render('price', ''), """
<input type="number" name="price_0" />
<input type="number" step="any" name="price_0" />
<select name="price_1">
<option value="gt">gt</option>
<option value="lt">lt</option>
</select>""")
self.assertHTMLEqual(f.widget.render('price', ['abc', 'lt']), """
<input type="number" name="price_0" value="abc" />
<input type="number" step="any" name="price_0" value="abc" />
<select name="price_1">
<option value="gt">gt</option>
<option selected="selected" value="lt">lt</option>
Expand Down
Loading

0 comments on commit 1d885d9

Please sign in to comment.