[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