[Python-modules-commits] [django-filter] 01/05: New upstream version 1.1.0

Antonio Terceiro terceiro at moszumanska.debian.org
Wed Jan 10 12:59:41 UTC 2018


This is an automated email from the git hooks/post-receive script.

terceiro pushed a commit to branch debian/master
in repository django-filter.

commit 7dbc6ce3ca4b4e8afc2dcda30ab2dcc601189735
Author: Antonio Terceiro <terceiro at debian.org>
Date:   Thu Nov 30 15:39:44 2017 -0200

    New upstream version 1.1.0
---
 CHANGES.rst                                    |  23 ++++
 PKG-INFO                                       |   9 +-
 README.rst                                     |   6 +-
 django_filter.egg-info/PKG-INFO                |   9 +-
 django_filter.egg-info/SOURCES.txt             |   1 +
 django_filters/__init__.py                     |  12 +-
 django_filters/conf.py                         |   1 -
 django_filters/fields.py                       | 121 +++++++++++++++++-
 django_filters/filters.py                      | 156 +++++++++++++----------
 django_filters/filterset.py                    |  88 ++++++++-----
 django_filters/locale/pl/LC_MESSAGES/django.po | 170 ++++++++++++++++++++++---
 django_filters/rest_framework/backends.py      |   8 +-
 django_filters/rest_framework/filterset.py     |   7 +-
 django_filters/utils.py                        |   6 +-
 django_filters/views.py                        |  13 +-
 django_filters/widgets.py                      |  70 ++++++++--
 docs/conf.py                                   |   4 +-
 docs/dev/tests.txt                             |  23 ++++
 docs/guide/install.txt                         |   6 +-
 docs/guide/migration.txt                       |  38 +++++-
 docs/guide/rest_framework.txt                  |   4 +-
 docs/ref/filters.txt                           |   3 +-
 docs/ref/widgets.txt                           |  25 ++++
 setup.cfg                                      |  10 +-
 setup.py                                       |   3 +-
 tests/models.py                                |   4 +-
 tests/rest_framework/test_backends.py          |  31 ++++-
 tests/rest_framework/test_filters.py           |   2 +-
 tests/rest_framework/test_filterset.py         |   6 +-
 tests/rest_framework/test_integration.py       |  17 ++-
 tests/settings.py                              |   1 -
 tests/test_conf.py                             |   5 +-
 tests/test_deprecations.py                     |  54 ++++++++
 tests/test_fields.py                           |  92 +++++++------
 tests/test_filtering.py                        | 137 +++++++++++++++-----
 tests/test_filters.py                          | 159 ++++++++++++++++-------
 tests/test_filterset.py                        |  64 +++++-----
 tests/test_forms.py                            |  14 +-
 tests/test_utils.py                            |  22 ++--
 tests/test_views.py                            |   5 +-
 tests/test_widgets.py                          |  85 +++++++++++--
 tests/urls.py                                  |   5 +-
 42 files changed, 1144 insertions(+), 375 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index eedf3b6..ec0caa5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,26 @@
+Version 1.1 (2017-10-19)
+------------------------
+
+* Add Deprecations for 2.0 (#792)
+* Improve IsoDateTimeField test clarity (#790)
+* Fix form attr references in tests (#789)
+* Simplify tox config, drop python 3.3 & django 1.8 (#787)
+* Make get_filter_name a classmethod, allowing it to be overriden for each FilterClass (#775)
+* Support active timezone (#750)
+* Docs Typo: django_filters -> filters in docs (#773)
+* Add Polish translations for some messages (#771)
+* Remove support for Django 1.9 (EOL) (#752)
+* Use required attribute from field when getting schema fields (#766)
+* Prevent circular ImportError hiding for rest_framework sub-package (#741)
+* Deprecate 'extra' field attrs on Filter (#734)
+* Add SuffixedMultiWidget (#681)
+* Fix null filtering for *Choice filters (#680)
+* Use isort on imports (#761)
+* Use urlencode from django.utils.http (#760)
+* Remove OrderingFilter.help_text (#757)
+* Update DRF test dependency to 3.6 (#747)
+
+
 Version 1.0.4 (2017-05-19)
 --------------------------
 
diff --git a/PKG-INFO b/PKG-INFO
index 24f976a..5e84fae 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-filter
-Version: 1.0.4
+Version: 1.1.0
 Summary: Django-filter is a reusable Django application for allowing users to filter querysets dynamically.
 Home-page: https://github.com/carltongibson/django-filter/tree/master
 Author: Carlton Gibson
@@ -27,9 +27,9 @@ Description: Django Filter
         Requirements
         ------------
         
-        * **Python**: 2.7, 3.3, 3.4, 3.5
-        * **Django**: 1.8, 1.9, 1.10, 1.11
-        * **DRF**: 3.5
+        * **Python**: 2.7, 3.4, 3.5, 3.6
+        * **Django**: 1.8, 1.10, 1.11
+        * **DRF**: 3.7
         
         Installation
         ------------
@@ -117,7 +117,6 @@ Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Framework :: Django
 Classifier: Framework :: Django :: 1.8
-Classifier: Framework :: Django :: 1.9
 Classifier: Framework :: Django :: 1.10
 Classifier: Framework :: Django :: 1.11
 Classifier: Programming Language :: Python
diff --git a/README.rst b/README.rst
index f1be14c..772babe 100644
--- a/README.rst
+++ b/README.rst
@@ -19,9 +19,9 @@ Full documentation on `read the docs`_.
 Requirements
 ------------
 
-* **Python**: 2.7, 3.3, 3.4, 3.5
-* **Django**: 1.8, 1.9, 1.10, 1.11
-* **DRF**: 3.5
+* **Python**: 2.7, 3.4, 3.5, 3.6
+* **Django**: 1.8, 1.10, 1.11
+* **DRF**: 3.7
 
 Installation
 ------------
diff --git a/django_filter.egg-info/PKG-INFO b/django_filter.egg-info/PKG-INFO
index 24f976a..5e84fae 100644
--- a/django_filter.egg-info/PKG-INFO
+++ b/django_filter.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-filter
-Version: 1.0.4
+Version: 1.1.0
 Summary: Django-filter is a reusable Django application for allowing users to filter querysets dynamically.
 Home-page: https://github.com/carltongibson/django-filter/tree/master
 Author: Carlton Gibson
@@ -27,9 +27,9 @@ Description: Django Filter
         Requirements
         ------------
         
-        * **Python**: 2.7, 3.3, 3.4, 3.5
-        * **Django**: 1.8, 1.9, 1.10, 1.11
-        * **DRF**: 3.5
+        * **Python**: 2.7, 3.4, 3.5, 3.6
+        * **Django**: 1.8, 1.10, 1.11
+        * **DRF**: 3.7
         
         Installation
         ------------
@@ -117,7 +117,6 @@ Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Framework :: Django
 Classifier: Framework :: Django :: 1.8
-Classifier: Framework :: Django :: 1.9
 Classifier: Framework :: Django :: 1.10
 Classifier: Framework :: Django :: 1.11
 Classifier: Programming Language :: Python
diff --git a/django_filter.egg-info/SOURCES.txt b/django_filter.egg-info/SOURCES.txt
index 0796015..b661b58 100644
--- a/django_filter.egg-info/SOURCES.txt
+++ b/django_filter.egg-info/SOURCES.txt
@@ -69,6 +69,7 @@ tests/models.py
 tests/settings.py
 tests/tags
 tests/test_conf.py
+tests/test_deprecations.py
 tests/test_fields.py
 tests/test_filtering.py
 tests/test_filters.py
diff --git a/django_filters/__init__.py b/django_filters/__init__.py
index 9d57b0f..77f9119 100644
--- a/django_filters/__init__.py
+++ b/django_filters/__init__.py
@@ -1,17 +1,19 @@
 # flake8: noqa
 from __future__ import absolute_import
+
+import pkgutil
+
 from .constants import STRICTNESS
 from .filterset import FilterSet
 from .filters import *
 
 # We make the `rest_framework` module available without an additional import.
-#   If DRF is not installed we simply set None.
-try:
+#   If DRF is not installed, no-op.
+if pkgutil.find_loader('rest_framework') is not None:
     from . import rest_framework
-except ImportError:
-    rest_framework = None
+del pkgutil
 
-__version__ = '1.0.4'
+__version__ = '1.1.0'
 
 
 def parse_version(version):
diff --git a/django_filters/conf.py b/django_filters/conf.py
index c672c30..b0a1027 100644
--- a/django_filters/conf.py
+++ b/django_filters/conf.py
@@ -8,7 +8,6 @@ from django.utils.translation import ugettext_lazy as _
 from .constants import STRICTNESS
 from .utils import deprecate
 
-
 DEFAULTS = {
     'DISABLE_HELP_TEXT': False,
     'HELP_TEXT_FILTER': True,
diff --git a/django_filters/fields.py b/django_filters/fields.py
index 160f419..99a1076 100644
--- a/django_filters/fields.py
+++ b/django_filters/fields.py
@@ -1,17 +1,17 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
-from datetime import datetime, time
 from collections import namedtuple
+from datetime import datetime, time
 
+import django
 from django import forms
 from django.utils.dateparse import parse_datetime
-
 from django.utils.encoding import force_str
 from django.utils.translation import ugettext_lazy as _
 
+from .conf import settings
 from .utils import handle_timezone
-from .widgets import RangeWidget, LookupTypeWidget, CSVWidget, BaseCSVWidget
+from .widgets import BaseCSVWidget, CSVWidget, LookupTypeWidget, RangeWidget
 
 
 class RangeField(forms.MultiValueField):
@@ -181,3 +181,114 @@ class BaseRangeField(BaseCSVField):
                 code='invalid_values')
 
         return value
+
+
+class ChoiceIterator(object):
+    # Emulates the behavior of ModelChoiceIterator, but instead wraps
+    # the field's _choices iterable.
+
+    def __init__(self, field, choices):
+        self.field = field
+        self.choices = choices
+
+    def __iter__(self):
+        if self.field.empty_label is not None:
+            yield ("", self.field.empty_label)
+        if self.field.null_label is not None:
+            yield (self.field.null_value, self.field.null_label)
+
+        # Python 2 lacks 'yield from'
+        for choice in self.choices:
+            yield choice
+
+    def __len__(self):
+        add = 1 if self.field.empty_label is not None else 0
+        add += 1 if self.field.null_label is not None else 0
+        return len(self.choices) + add
+
+
+class ModelChoiceIterator(forms.models.ModelChoiceIterator):
+    # Extends the base ModelChoiceIterator to add in 'null' choice handling.
+    # This is a bit verbose since we have to insert the null choice after the
+    # empty choice, but before the remainder of the choices.
+
+    def __iter__(self):
+        iterable = super(ModelChoiceIterator, self).__iter__()
+
+        if self.field.empty_label is not None:
+            yield next(iterable)
+        if self.field.null_label is not None:
+            yield (self.field.null_value, self.field.null_label)
+
+        # Python 2 lacks 'yield from'
+        for value in iterable:
+            yield value
+
+    def __len__(self):
+        add = 1 if self.field.null_label is not None else 0
+        return super(ModelChoiceIterator, self).__len__() + add
+
+
+class ChoiceIteratorMixin(object):
+    def __init__(self, *args, **kwargs):
+        self.null_label = kwargs.pop('null_label', settings.NULL_CHOICE_LABEL)
+        self.null_value = kwargs.pop('null_value', settings.NULL_CHOICE_VALUE)
+
+        super(ChoiceIteratorMixin, self).__init__(*args, **kwargs)
+
+    def _get_choices(self):
+        if django.VERSION >= (1, 11):
+            return super(ChoiceIteratorMixin, self)._get_choices()
+
+        # HACK: Django < 1.11 does not allow a custom iterator to be provided.
+        # This code only executes for Model*ChoiceFields.
+        if hasattr(self, '_choices'):
+            return self._choices
+        return self.iterator(self)
+
+    def _set_choices(self, value):
+        super(ChoiceIteratorMixin, self)._set_choices(value)
+        value = self.iterator(self, self._choices)
+
+        self._choices = self.widget.choices = value
+    choices = property(_get_choices, _set_choices)
+
+
+# Unlike their Model* counterparts, forms.ChoiceField and forms.MultipleChoiceField do not set empty_label
+class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField):
+    iterator = ChoiceIterator
+
+    def __init__(self, *args, **kwargs):
+        self.empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL)
+        super(ChoiceField, self).__init__(*args, **kwargs)
+
+
+class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField):
+    iterator = ChoiceIterator
+
+    def __init__(self, *args, **kwargs):
+        self.empty_label = None
+        super(MultipleChoiceField, self).__init__(*args, **kwargs)
+
+
+class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField):
+    iterator = ModelChoiceIterator
+
+    def to_python(self, value):
+        # bypass the queryset value check
+        if self.null_label is not None and value == self.null_value:
+            return value
+        return super(ModelChoiceField, self).to_python(value)
+
+
+class ModelMultipleChoiceField(ChoiceIteratorMixin, forms.ModelMultipleChoiceField):
+    iterator = ModelChoiceIterator
+
+    def _check_values(self, value):
+        null = self.null_label is not None and value and self.null_value in value
+        if null:  # remove the null value and any potential duplicates
+            value = [v for v in value if v != self.null_value]
+
+        result = list(super(ModelMultipleChoiceField, self)._check_values(value))
+        result += [self.null_value] if null else []
+        return result
diff --git a/django_filters/filters.py b/django_filters/filters.py
index ec16d4c..e336bdd 100644
--- a/django_filters/filters.py
+++ b/django_filters/filters.py
@@ -1,13 +1,12 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 from collections import OrderedDict
 from datetime import timedelta
 
 from django import forms
 from django.db.models import Q
-from django.db.models.sql.constants import QUERY_TERMS
 from django.db.models.constants import LOOKUP_SEP
+from django.db.models.sql.constants import QUERY_TERMS
 from django.utils import six
 from django.utils.itercompat import is_iterable
 from django.utils.timezone import now
@@ -17,11 +16,21 @@ from .compat import pretty_name
 from .conf import settings
 from .constants import EMPTY_VALUES
 from .fields import (
-    Lookup, LookupTypeField, BaseCSVField, BaseRangeField, RangeField,
-    DateRangeField, DateTimeRangeField, TimeRangeField, IsoDateTimeField
+    BaseCSVField,
+    BaseRangeField,
+    ChoiceField,
+    DateRangeField,
+    DateTimeRangeField,
+    IsoDateTimeField,
+    Lookup,
+    LookupTypeField,
+    ModelChoiceField,
+    ModelMultipleChoiceField,
+    MultipleChoiceField,
+    RangeField,
+    TimeRangeField
 )
-from .utils import label_for_filter
-
+from .utils import deprecate, label_for_filter
 
 __all__ = [
     'AllValuesFilter',
@@ -58,23 +67,40 @@ __all__ = [
 LOOKUP_TYPES = sorted(QUERY_TERMS)
 
 
+def _extra_attr(attr):
+    fmt = ("The `.%s` attribute has been deprecated in favor of making it accessible "
+           "alongside the other field kwargs. You should now access it as `.extra['%s']`.")
+
+    def fget(self):
+        deprecate(fmt % (attr, attr))
+        return self.extra.get(attr)
+
+    def fset(self, value):
+        deprecate(fmt % (attr, attr))
+        self.extra[attr] = value
+
+    return {'fget': fget, 'fset': fset}
+
+
 class Filter(object):
     creation_counter = 0
     field_class = forms.Field
 
-    def __init__(self, name=None, label=None, widget=None, method=None, lookup_expr='exact',
-                 required=False, distinct=False, exclude=False, **kwargs):
-        self.name = name
+    def __init__(self, field_name=None, label=None, method=None, lookup_expr='exact',
+                 distinct=False, exclude=False, **kwargs):
+        self.field_name = field_name
+        if field_name is None and 'name' in kwargs:
+            deprecate("`Filter.name` has been renamed to `Filter.field_name`.")
+            self.field_name = kwargs.pop('name')
         self.label = label
         self.method = method
         self.lookup_expr = lookup_expr
-
-        self.widget = widget
-        self.required = required
-        self.extra = kwargs
         self.distinct = distinct
         self.exclude = exclude
 
+        self.extra = kwargs
+        self.extra.setdefault('required', False)
+
         self.creation_counter = Filter.creation_counter
         Filter.creation_counter += 1
 
@@ -106,12 +132,24 @@ class Filter(object):
         return locals()
     method = property(**method())
 
+    def name():
+        def fget(self):
+            deprecate("`Filter.name` has been renamed to `Filter.field_name`.")
+            return self.field_name
+
+        def fset(self, value):
+            deprecate("`Filter.name` has been renamed to `Filter.field_name`.")
+            self.field_name = value
+
+        return locals()
+    name = property(**name())
+
     def label():
         def fget(self):
             if self._label is None and hasattr(self, 'parent'):
                 model = self.parent._meta.model
                 self._label = label_for_filter(
-                    model, self.name, self.lookup_expr, self.exclude
+                    model, self.field_name, self.lookup_expr, self.exclude
                 )
             return self._label
 
@@ -121,6 +159,10 @@ class Filter(object):
         return locals()
     label = property(**label())
 
+    # deprecated field props
+    widget = property(**_extra_attr('widget'))
+    required = property(**_extra_attr('required'))
+
     @property
     def field(self):
         if not hasattr(self, '_field'):
@@ -150,13 +192,11 @@ class Filter(object):
                             if x in self.lookup_expr:
                                 lookup.append(choice)
 
-                self._field = LookupTypeField(self.field_class(
-                    required=self.required, widget=self.widget, **field_kwargs),
-                    lookup, required=self.required, label=self.label)
+                self._field = LookupTypeField(
+                    self.field_class(**field_kwargs), lookup,
+                    required=field_kwargs['required'], label=self.label)
             else:
-                self._field = self.field_class(required=self.required,
-                                               label=self.label, widget=self.widget,
-                                               **field_kwargs)
+                self._field = self.field_class(label=self.label, **field_kwargs)
         return self._field
 
     def filter(self, qs, value):
@@ -169,7 +209,7 @@ class Filter(object):
             return qs
         if self.distinct:
             qs = qs.distinct()
-        qs = self.get_method(qs)(**{'%s__%s' % (self.name, lookup): value})
+        qs = self.get_method(qs)(**{'%s__%s' % (self.field_name, lookup): value})
         return qs
 
 
@@ -182,39 +222,17 @@ class BooleanFilter(Filter):
 
 
 class ChoiceFilter(Filter):
-    field_class = forms.ChoiceField
+    field_class = ChoiceField
 
     def __init__(self, *args, **kwargs):
-        empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL)
-        null_label = kwargs.pop('null_label', settings.NULL_CHOICE_LABEL)
-        null_value = kwargs.pop('null_value', settings.NULL_CHOICE_VALUE)
-
-        self.null_value = null_value
-
-        if 'choices' in kwargs:
-            choices = kwargs.get('choices')
-
-            # coerce choices to list
-            if callable(choices):
-                choices = choices()
-            choices = list(choices)
-
-            # create the empty/null choices that prepend the original choices
-            prepend = []
-            if empty_label is not None:
-                prepend.append(('', empty_label))
-            if null_label is not None:
-                prepend.append((null_value, null_label))
-
-            kwargs['choices'] = prepend + choices
-
+        self.null_value = kwargs.get('null_value', settings.NULL_CHOICE_VALUE)
         super(ChoiceFilter, self).__init__(*args, **kwargs)
 
     def filter(self, qs, value):
         if value != self.null_value:
             return super(ChoiceFilter, self).filter(qs, value)
 
-        qs = self.get_method(qs)(**{'%s__%s' % (self.name, self.lookup_expr): None})
+        qs = self.get_method(qs)(**{'%s__%s' % (self.field_name, self.lookup_expr): None})
         return qs.distinct() if self.distinct else qs
 
 
@@ -249,13 +267,14 @@ class MultipleChoiceFilter(Filter):
     `distinct` defaults to `True` as to-many relationships will generally
     require this.
     """
-    field_class = forms.MultipleChoiceField
+    field_class = MultipleChoiceField
 
     always_filter = True
 
     def __init__(self, *args, **kwargs):
         kwargs.setdefault('distinct', True)
         self.conjoined = kwargs.pop('conjoined', False)
+        self.null_value = kwargs.get('null_value', settings.NULL_CHOICE_VALUE)
         super(MultipleChoiceFilter, self).__init__(*args, **kwargs)
 
     def is_noop(self, qs, value):
@@ -267,7 +286,7 @@ class MultipleChoiceFilter(Filter):
             return False
 
         # A reasonable default for being a noop...
-        if self.required and len(value) == len(self.field.choices):
+        if self.extra.get('required') and len(value) == len(self.field.choices):
             return True
 
         return False
@@ -283,6 +302,8 @@ class MultipleChoiceFilter(Filter):
         if not self.conjoined:
             q = Q()
         for v in set(value):
+            if v == self.null_value:
+                v = None
             predicate = self.get_filter_predicate(v)
             if self.conjoined:
                 qs = self.get_method(qs)(**predicate)
@@ -296,9 +317,9 @@ class MultipleChoiceFilter(Filter):
 
     def get_filter_predicate(self, v):
         try:
-            return {self.name: getattr(v, self.field.to_field_name)}
+            return {self.field_name: getattr(v, self.field.to_field_name)}
         except (AttributeError, TypeError):
-            return {self.name: v}
+            return {self.field_name: v}
 
 
 class TypedMultipleChoiceFilter(MultipleChoiceFilter):
@@ -385,12 +406,16 @@ class QuerySetRequestMixin(object):
         return super(QuerySetRequestMixin, self).field
 
 
-class ModelChoiceFilter(QuerySetRequestMixin, Filter):
-    field_class = forms.ModelChoiceField
+class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter):
+    field_class = ModelChoiceField
+
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault('empty_label', settings.EMPTY_CHOICE_LABEL)
+        super(ModelChoiceFilter, self).__init__(*args, **kwargs)
 
 
 class ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter):
-    field_class = forms.ModelMultipleChoiceField
+    field_class = ModelMultipleChoiceField
 
 
 class NumberFilter(Filter):
@@ -403,13 +428,13 @@ class NumericRangeFilter(Filter):
     def filter(self, qs, value):
         if value:
             if value.start is not None and value.stop is not None:
-                lookup = '%s__%s' % (self.name, self.lookup_expr)
+                lookup = '%s__%s' % (self.field_name, self.lookup_expr)
                 return self.get_method(qs)(**{lookup: (value.start, value.stop)})
             else:
                 if value.start is not None:
-                    qs = self.get_method(qs)(**{'%s__startswith' % self.name: value.start})
+                    qs = self.get_method(qs)(**{'%s__startswith' % self.field_name: value.start})
                 if value.stop is not None:
-                    qs = self.get_method(qs)(**{'%s__endswith' % self.name: value.stop})
+                    qs = self.get_method(qs)(**{'%s__endswith' % self.field_name: value.stop})
             if self.distinct:
                 qs = qs.distinct()
         return qs
@@ -421,13 +446,13 @@ class RangeFilter(Filter):
     def filter(self, qs, value):
         if value:
             if value.start is not None and value.stop is not None:
-                lookup = '%s__range' % self.name
+                lookup = '%s__range' % self.field_name
                 return self.get_method(qs)(**{lookup: (value.start, value.stop)})
             else:
                 if value.start is not None:
-                    qs = self.get_method(qs)(**{'%s__gte' % self.name: value.start})
+                    qs = self.get_method(qs)(**{'%s__gte' % self.field_name: value.start})
                 if value.stop is not None:
-                    qs = self.get_method(qs)(**{'%s__lte' % self.name: value.stop})
+                    qs = self.get_method(qs)(**{'%s__lte' % self.field_name: value.stop})
             if self.distinct:
                 qs = qs.distinct()
         return qs
@@ -479,7 +504,7 @@ class DateRangeFilter(ChoiceFilter):
             value = ''
 
         assert value in self.options
-        qs = self.options[value][1](qs, self.name)
+        qs = self.options[value][1](qs, self.field_name)
         if self.distinct:
             qs = qs.distinct()
         return qs
@@ -501,7 +526,7 @@ class AllValuesFilter(ChoiceFilter):
     @property
     def field(self):
         qs = self.model._default_manager.distinct()
-        qs = qs.order_by(self.name).values_list(self.name, flat=True)
+        qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
         self.extra['choices'] = [(o, o) for o in qs]
         return super(AllValuesFilter, self).field
 
@@ -510,7 +535,7 @@ class AllValuesMultipleFilter(MultipleChoiceFilter):
     @property
     def field(self):
         qs = self.model._default_manager.distinct()
-        qs = qs.order_by(self.name).values_list(self.name, flat=True)
+        qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
         self.extra['choices'] = [(o, o) for o in qs]
         return super(AllValuesMultipleFilter, self).field
 
@@ -618,6 +643,7 @@ class OrderingFilter(BaseCSVFilter, ChoiceFilter):
             kwargs['choices'] = self.build_choices(fields, field_labels)
 
         kwargs.setdefault('label', _('Ordering'))
+        kwargs.setdefault('help_text', '')
         kwargs.setdefault('null_label', None)
         super(OrderingFilter, self).__init__(*args, **kwargs)
 
@@ -684,7 +710,7 @@ class FilterMethod(object):
         if value in EMPTY_VALUES:
             return qs
 
-        return self.method(qs, self.f.name, value)
+        return self.method(qs, self.f.field_name, value)
 
     @property
     def method(self):
@@ -700,7 +726,7 @@ class FilterMethod(object):
         # otherwise, method is the name of a method on the parent FilterSet.
         assert hasattr(instance, 'parent'), \
             "Filter '%s' must have a parent FilterSet to find '.%s()'" %  \
-            (instance.name, instance.method)
+            (instance.field_name, instance.method)
 
         parent = instance.parent
         method = getattr(parent, instance.method, None)
diff --git a/django_filters/filterset.py b/django_filters/filterset.py
index 22b40c3..49f8f52 100644
--- a/django_filters/filterset.py
+++ b/django_filters/filterset.py
@@ -1,5 +1,4 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
 
 import copy
 from collections import OrderedDict
@@ -10,29 +9,32 @@ from django.db.models.constants import LOOKUP_SEP
 from django.db.models.fields.related import ForeignObjectRel
 from django.utils import six
 
-from .conf import settings
 from .compat import remote_field, remote_queryset
-from .constants import ALL_FIELDS, STRICTNESS, EMPTY_VALUES
-from .filters import (Filter, CharFilter, BooleanFilter, BaseInFilter, BaseRangeFilter,
-                      ChoiceFilter, DateFilter, DateTimeFilter, TimeFilter, ModelChoiceFilter,
-                      ModelMultipleChoiceFilter, NumberFilter, UUIDFilter, DurationFilter)
-from .utils import try_dbfield, get_all_model_fields, get_model_field, resolve_field
-
-
-def get_filter_name(field_name, lookup_expr):
-    """
-    Combine a field name and lookup expression into a usable filter name.
-    Exact lookups are the implicit default, so "exact" is stripped from the
-    end of the filter name.
-    """
-    filter_name = LOOKUP_SEP.join([field_name, lookup_expr])
-
-    # This also works with transformed exact lookups, such as 'date__exact'
-    _exact = LOOKUP_SEP + 'exact'
-    if filter_name.endswith(_exact):
-        filter_name = filter_name[:-len(_exact)]
-
-    return filter_name
+from .conf import settings
+from .constants import ALL_FIELDS, EMPTY_VALUES, STRICTNESS
+from .filters import (
+    BaseInFilter,
+    BaseRangeFilter,
+    BooleanFilter,
+    CharFilter,
+    ChoiceFilter,
+    DateFilter,
+    DateTimeFilter,
+    DurationFilter,
+    Filter,
+    ModelChoiceFilter,
+    ModelMultipleChoiceFilter,
+    NumberFilter,
+    TimeFilter,
+    UUIDFilter
+)
+from .utils import (
+    deprecate,
+    get_all_model_fields,
+    get_model_field,
+    resolve_field,
+    try_dbfield
+)
 
 
 def _together_valid(form, fieldset):
@@ -74,6 +76,8 @@ class FilterSetOptions(object):
 
         self.form = getattr(options, 'form', forms.Form)
 
+        if hasattr(options, 'together'):
+            deprecate('The `Meta.together` option has been deprecated in favor of overriding `Form.clean`.', 1)
         self.together = getattr(options, 'together', None)
 
 
@@ -95,10 +99,10 @@ class FilterSetMetaclass(type):
             if isinstance(obj, Filter)
         ]
 
-        # Default the `filter.name` to the attribute name on the filterset
+        # Default the `filter.field_name` to the attribute name on the filterset
         for filter_name, f in filters:
-            if getattr(f, 'name', None) is None:
-                f.name = filter_name
+            if getattr(f, 'field_name', None) is None:
+                f.field_name = filter_name
 
         filters.sort(key=lambda x: x[1].creation_counter)
 
@@ -142,6 +146,7 @@ FILTER_FOR_DBFIELD_DEFAULTS = {
         'extra': lambda f: {
             'queryset': remote_queryset(f),
             'to_field_name': remote_field(f).field_name,
+            'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
         }
     },
     models.ForeignKey: {
@@ -149,6 +154,7 @@ FILTER_FOR_DBFIELD_DEFAULTS = {
         'extra': lambda f: {
             'queryset': remote_queryset(f),
             'to_field_name': remote_field(f).field_name,
+            'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
         }
     },
     models.ManyToManyField: {
@@ -267,6 +273,22 @@ class BaseFilterSet(object):
         return OrderedDict(fields)
 
     @classmethod
+    def get_filter_name(cls, field_name, lookup_expr):
+        """
+        Combine a field name and lookup expression into a usable filter name.
+        Exact lookups are the implicit default, so "exact" is stripped from the
+        end of the filter name.
+        """
+        filter_name = LOOKUP_SEP.join([field_name, lookup_expr])
+
+        # This also works with transformed exact lookups, such as 'date__exact'
+        _exact = LOOKUP_SEP + 'exact'
+        if filter_name.endswith(_exact):
+            filter_name = filter_name[:-len(_exact)]
+
+        return filter_name
+
+    @classmethod
     def get_filters(cls):
         """
         Get all filters for the filterset. This is the combination of declared and
@@ -295,7 +317,7 @@ class BaseFilterSet(object):
                 continue
 
             for lookup_expr in lookups:
-                filter_name = get_filter_name(field_name, lookup_expr)
+                filter_name = cls.get_filter_name(field_name, lookup_expr)
 
                 # If the filter is explicitly declared on the class, skip generation
                 if filter_name in cls.declared_filters:
@@ -319,11 +341,11 @@ class BaseFilterSet(object):
         return filters
 
     @classmethod
-    def filter_for_field(cls, f, name, lookup_expr='exact'):
+    def filter_for_field(cls, f, field_name, lookup_expr='exact'):
         f, lookup_type = resolve_field(f, lookup_expr)
 
         default = {
-            'name': name,
+            'field_name': field_name,
             'lookup_expr': lookup_expr,
         }
 
@@ -334,16 +356,16 @@ class BaseFilterSet(object):
             "%s resolved field '%s' with '%s' lookup to an unrecognized field "
             "type %s. Try adding an override to 'Meta.filter_overrides'. See: "
             "https://django-filter.readthedocs.io/en/develop/ref/filterset.html#customise-filter-generation-with-filter-overrides"
-        ) % (cls.__name__, name, lookup_expr, f.__class__.__name__)
+        ) % (cls.__name__, field_name, lookup_expr, f.__class__.__name__)
 
         return filter_class(**default)
 
     @classmethod
-    def filter_for_reverse_field(cls, f, name):
+    def filter_for_reverse_field(cls, f, field_name):
         rel = remote_field(f.field)
         queryset = f.field.model._default_manager.all()
         default = {
-            'name': name,
+            'field_name': field_name,
             'queryset': queryset,
         }
         if rel.multiple:
diff --git a/django_filters/locale/pl/LC_MESSAGES/django.po b/django_filters/locale/pl/LC_MESSAGES/django.po
index 5c78bdd..9ef204d 100644
--- a/django_filters/locale/pl/LC_MESSAGES/django.po
+++ b/django_filters/locale/pl/LC_MESSAGES/django.po
@@ -3,63 +3,199 @@
 # This file is distributed under the same license as the PACKAGE package.
 # FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
 #
+#: conf.py:35 conf.py:36 conf.py:49
 msgid ""
 msgstr ""
 "Project-Id-Version: django_filters 0.0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-07-25 01:24+0200\n"
+"POT-Creation-Date: 2017-09-01 17:21+0000\n"
 "PO-Revision-Date: 2015-07-25 01:27+0100\n"
 "Last-Translator: Adam Dobrawy <naczelnik at jawnosc.tk>\n"
 "Language-Team: Adam Dobrawy <naczelnik at jawnosc.tk>\n"
+"Language: pl_PL\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
 "|| n%100>=20) ? 1 : 2);\n"
 "X-Generator: Poedit 1.5.4\n"
-"Language: pl_PL\n"
 
-#: filters.py:56
-msgid "This is an exclusion filter"
-msgstr "Jest to filtr wykluczający"
+#: conf.py:25
+#, fuzzy
+#| msgid "Any date"
+msgid "date"
+msgstr "Dowolna data"
+
+#: conf.py:26
+#, fuzzy
+#| msgid "This year"
+msgid "year"
+msgstr "Ten rok"
 
-#: filters.py:56
-msgid "Filter"
-msgstr "Filter"
+#: conf.py:27
+#, fuzzy
+#| msgid "This month"
+msgid "month"
+msgstr "Ten miesiąc"
+
+#: conf.py:28
+#, fuzzy
+#| msgid "Today"
+msgid "day"
+msgstr "Dziś"
+
+#: conf.py:29
+msgid "week day"
+msgstr "dzień tygodnia"
+
+#: conf.py:30
+msgid "hour"
+msgstr "godzina"
+
+#: conf.py:31
+msgid "minute"
+msgstr "minuta"
+
+#: conf.py:32
+msgid "second"
+msgstr ""
+
+#: conf.py:37 conf.py:38
+msgid "contains"
+msgstr "zawiera"
+
+#: conf.py:39
+msgid "is in"
+msgstr "zawiera się w"
+
+#: conf.py:40
+msgid "is greater than"
+msgstr "powyżej"
+
+#: conf.py:41
+msgid "is greater than or equal to"
+msgstr "powyżej lub równe"
+
+#: conf.py:42
+msgid "is less than"
+msgstr "poniżej"
+
+#: conf.py:43
+msgid "is less than or equal to"
+msgstr "poniżej lub równe"
+
+#: conf.py:44 conf.py:45
+msgid "starts with"
+msgstr "zaczyna się od"
+
+#: conf.py:46 conf.py:47
+msgid "ends with"
+msgstr "kończy się na"
+
+#: conf.py:48
+msgid "is in range"
+msgstr "zawiera się w zakresie"
+
+#: conf.py:50 conf.py:51
+msgid "matches regex"
+msgstr "pasuje do wyrażenia regularnego"
 
-#: filters.py:226
+#: conf.py:52 conf.py:60
+msgid "search"
... 1730 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-filter.git



More information about the Python-modules-commits mailing list