django-filter-1.1.0/0000755000076500000240000000000013172070045015022 5ustar carltonstaff00000000000000django-filter-1.1.0/AUTHORS0000644000076500000240000000051112524657704016104 0ustar carltonstaff00000000000000Authors ======= Thanks to the following people for contributing to django-filter. Ben Firshman Alex Gaynor Jannis Leidel Martin Mahner Brian Rosner Adam Vandenberg Florian Apolloner Andrew Ball Tino de Bruijn Maximillian Dornseif Marc Fargas Vladimir Sidorenko Tom Christie Remco Wendt Axel Haustant Brad Erickson Diogo Laginhadjango-filter-1.1.0/CHANGES.rst0000644000076500000240000001764013172067725016647 0ustar carltonstaff00000000000000Version 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) -------------------------- Quick fix for verbose_field_name issue from 1.0.3 (#722) Version 1.0.3 (2017-05-16) -------------------------- Improves compatibility with Django REST Framework schema generation. See the `1.0.3 Milestone`__ for full details. __ https://github.com/carltongibson/django-filter/milestone/13?closed=1 Version 1.0.2 (2017-03-20) -------------------------- Updates for compatibility with Django 1.11 and Django REST Framework 3.6. Adds CI testing against Python 3.6 See the `1.0.2 Milestone`__ for full details. __ https://github.com/carltongibson/django-filter/milestone/12?closed=1 Version 1.0.1 (2016-11-28) -------------------------- Small release to ease compatibility with DRF: * #568 Adds ``rest_framework`` to the ``django_filters`` namespace to allow single ``import django_filters` usage. * A number of small updates to the docs Version 1.0 (2016-11-17) ------------------------ This release removes all the deprecated code from 0.14 and 0.15 for 1.0 #480. Please see the `Migration Notes`__ for details of how to migrate. Stick with 0.15.3 if you're not ready to update. __ https://github.com/carltongibson/django-filter/blob/1.0.0/docs/guide/migration.txt The release includes a number of small fixes and documentation updates. See the `1.0 Milestone`__ for full details. __ https://github.com/carltongibson/django-filter/milestone/8?closed=1 Version 0.15.3 (2016-10-17) --------------------------- Adds compatibility for DRF (3.5+) get_schema_fields filter backend introspection. * #492 Port get_schema_fields from DRF Version 0.15.2 (2016-09-29) --------------------------- * #507 Fix compatibility issue when not using the DTL Version 0.15.1 (2016-09-28) --------------------------- A couple of quick bug fixes: * #496 OrderingFilter not working with Select widget * #498 DRF Backend Templates not loading Version 0.15.0 (2016-09-20) --------------------------- This is a preparatory release for a 1.0. Lots of clean-up, lots of changes, mostly backwards compatible. Special thanks to Ryan P Kilby (@rpkilby) for lots of hard work. Most changes should raise a Deprecation Warning. **Note**: if you're doing *Clever Things™* with the various filter options — ``filter_overrides`` etc — you may run into an `AttributeError` since these are now defined on the metaclass and not on the filter itself. (See the discussion on #459) Summary: Highly Recommended, but take a moment to ensure everything still works. * Added the DRF backend. #481 * Deprecated `MethodFilter` in favour of `Filter.method` #382 * Move filter options to metaclass #459 * Added `get_filter_predicate` hook. (Allows e.g. filtering on annotated fields) #469 * Rework Ordering options into a filter #472 * Hardened all deprecations for 1.0. Please do see the `Migration Notes`__ __ https://github.com/carltongibson/django-filter/blob/1.0.0/docs/guide/migration.txt Version 0.14.0 (2016-08-14) --------------------------- * Confirmed support for Django 1.10. * Add support for filtering on DurationField (new in Django 1.8). * Fix UUIDFilter import issue * Improve FieldLookupError message * Add filters_for_model to improve extensibility * Fix limit_choices_to behavior with callables * Fix distinct behavior for range filters * Various Minor Clean up issues. Version 0.13.0 (2016-03-11) --------------------------- * Add support for filtering by CSV #363 * Add DateTimeFromToRangeFilter #376 * Add Chinese translation #359 * Lots of fixes. Version 0.12.0 (2016-01-07) --------------------------- * Raised minimum Django version to 1.8.x * FEATURE: Add support for custom ORM lookup types #221 * FEATURE: Add JavaScript friendly BooleanWidget #270 * FIXED: (More) Compatability with Django 1.8 and Django 1.9+ * BREAKING CHANGE: custom filter names are now also be used for ordering #230 If you use ordering on a field you defined as custom filter with custom name, you should now use the filter name as ordering key as well. Eg. For a filter like : class F(FilterSet): account = CharFilter(name='username') class Meta: model = User fields = ['account', 'status'] order_by = True Before, ordering was like `?o=username`. Since 0.12.0 it's `o=account`. Version 0.11.0 (2015-08-14) --------------------------- * FEATURE: Added default filter method lookup for MethodFilter #222 * FEATURE: Added support for yesterday in daterangefilter #234 * FEATURE: Created Filter for NumericRange. #236 * FEATURE: Added Date/time range filters #215 * FEATURE: Added option to raise with `strict` #255 * FEATURE: Added Form Field and Filter to parse ISO-8601 timestamps Version 0.10.0 (2015-05-13) --------------------- * FEATURE: Added ``conjoined`` parameter to ``MultipleChoiceFilter`` * FEATURE: Added ``together`` meta option to validate fields as a group * FIXED: Added testing on Django 1.8 * FIXED: ``get_model_field`` on Django 1.8 Version 0.9.2 (2015-01-23) -------------------------- * FIXED: Compatibility with Django v1.8a1 Version 0.9.1 (2014-12-03) -------------------------- * FIXED: Compatibility with Debug Toolbar's versions panel Version 0.9 (2014-11-28) ------------------------ * FEATURE: Allow Min/Max-Only use of RangeFilter * FEATURE: Added TypedChoiceFilter * FIXED: Correct logic for short circuit on MultipleChoiceFilter Added `always_filter` attribute and `is_noop()` test to apply short-circuiting. Set `always_filter` to `False` on init to apply default `is_noop()` test. Override `is_noop()` for more complex cases. * MISC: Version bumping with ``bumpversion`` Version 0.8 (2014-09-29) ------------------------ * FEATURE: Added exclusion filters support * FEATURE: Added `fields` dictionary shorthand syntax * FEATURE: Added `MethodFilter`. * FIXED: #115 "filters.Filter.filter() fails if it receives [] or () as value" * MISC: Various Documentation and Testing improvements Version 0.7 (2013-08-10) ------------------------ * FEATURE: Added support for AutoField. * FEATURE: There is a "distinct" flag to ensure that only unique rows are returned. * FEATURE: Support descending ordering (slighty backwards incompatible). * FEATURE: Support "strict" querysets, ie wrong filter data returns no results. * FIXED: Some translation strings were changed to be in line with admin. * FIXED: Support for Django 1.7. Version 0.6 (2013-03-25) ------------------------ * raised minimum Django version to 1.4.x * added Python 3.2 and Python 3.3 support * added Django 1.5 support and initial 1.6 compatability * FEATURE: recognition of custom model field subclasses * FEATURE: allow optional display names for order_by values * FEATURE: addition of class-based FilterView * FEATURE: addition of count() method on FilterSet to prevent pagination from loading entire queryset * FIXED: attempts to filter on reverse side of m2m, o2o or fk would raise an error Version 0.5.4 (2012-11-16) -------------------------- * project brought back to life django-filter-1.1.0/django_filter.egg-info/0000755000076500000240000000000013172070045021323 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filter.egg-info/dependency_links.txt0000644000076500000240000000000113172070045025371 0ustar carltonstaff00000000000000 django-filter-1.1.0/django_filter.egg-info/not-zip-safe0000644000076500000240000000000113172070045023551 0ustar carltonstaff00000000000000 django-filter-1.1.0/django_filter.egg-info/PKG-INFO0000644000076500000240000001065713172070045022431 0ustar carltonstaff00000000000000Metadata-Version: 1.1 Name: django-filter 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 Author-email: carlton.gibson@noumenal.es License: BSD Description: Django Filter ============= Django-filter is a reusable Django application allowing users to declaratively add dynamic ``QuerySet`` filtering from URL parameters. Full documentation on `read the docs`_. .. image:: https://travis-ci.org/carltongibson/django-filter.svg?branch=master :target: https://travis-ci.org/carltongibson/django-filter .. image:: https://codecov.io/gh/carltongibson/django-filter/branch/develop/graph/badge.svg :target: https://codecov.io/gh/carltongibson/django-filter .. image:: https://badge.fury.io/py/django-filter.svg :target: http://badge.fury.io/py/django-filter Requirements ------------ * **Python**: 2.7, 3.4, 3.5, 3.6 * **Django**: 1.8, 1.10, 1.11 * **DRF**: 3.7 Installation ------------ Install using pip: .. code-block:: sh pip install django-filter Then add ``'django_filters'`` to your ``INSTALLED_APPS``. .. code-block:: python INSTALLED_APPS = [ ... 'django_filters', ] Usage ----- Django-filter can be used for generating interfaces similar to the Django admin's ``list_filter`` interface. It has an API very similar to Django's ``ModelForms``. For example, if you had a Product model you could have a filterset for it with the code: .. code-block:: python import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['name', 'price', 'manufacturer'] And then in your view you could do: .. code-block:: python def product_list(request): filter = ProductFilter(request.GET, queryset=Product.objects.all()) return render(request, 'my_app/template.html', {'filter': filter}) Usage with Django REST Framework -------------------------------- Django-filter provides a custom ``FilterSet`` and filter backend for use with Django REST Framework. To use this adjust your import to use ``django_filters.rest_framework.FilterSet``. .. code-block:: python from django_filters import rest_framework as filters class ProductFilter(filters.FilterSet): class Meta: model = Product fields = ('category', 'in_stock') For more details see the `DRF integration docs`_. Support ------- If you have questions about usage or development you can join the `mailing list`_. .. _`read the docs`: https://django-filter.readthedocs.io/en/develop/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/develop/guide/rest_framework.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django Classifier: Framework :: Django :: 1.8 Classifier: Framework :: Django :: 1.10 Classifier: Framework :: Django :: 1.11 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Framework :: Django django-filter-1.1.0/django_filter.egg-info/SOURCES.txt0000644000076500000240000000510613172070045023211 0ustar carltonstaff00000000000000AUTHORS CHANGES.rst LICENSE MANIFEST.in README.rst runshell.py runtests.py setup.cfg setup.py django_filter.egg-info/PKG-INFO django_filter.egg-info/SOURCES.txt django_filter.egg-info/dependency_links.txt django_filter.egg-info/not-zip-safe django_filter.egg-info/top_level.txt django_filters/__init__.py django_filters/compat.py django_filters/conf.py django_filters/constants.py django_filters/exceptions.py django_filters/fields.py django_filters/filters.py django_filters/filterset.py django_filters/models.py django_filters/utils.py django_filters/views.py django_filters/widgets.py django_filters/locale/de/LC_MESSAGES/django.mo django_filters/locale/de/LC_MESSAGES/django.po django_filters/locale/es_AR/LC_MESSAGES/django.mo django_filters/locale/es_AR/LC_MESSAGES/django.po django_filters/locale/es_ES/LC_MESSAGES/django.mo django_filters/locale/es_ES/LC_MESSAGES/django.po django_filters/locale/fr/LC_MESSAGES/django.mo django_filters/locale/fr/LC_MESSAGES/django.po django_filters/locale/pl/LC_MESSAGES/django.mo django_filters/locale/pl/LC_MESSAGES/django.po django_filters/locale/ru/LC_MESSAGES/django.mo django_filters/locale/ru/LC_MESSAGES/django.po django_filters/locale/zh_CN/LC_MESSAGES/django.po django_filters/rest_framework/__init__.py django_filters/rest_framework/backends.py django_filters/rest_framework/filters.py django_filters/rest_framework/filterset.py django_filters/templates/django_filters/rest_framework/crispy_form.html django_filters/templates/django_filters/rest_framework/form.html django_filters/templates/django_filters/widgets/multiwidget.html docs/.DS_Store docs/Makefile docs/conf.py docs/index.txt docs/make.bat docs/assets/form.png docs/dev/tests.txt docs/guide/install.txt docs/guide/migration.txt docs/guide/rest_framework.txt docs/guide/tips.txt docs/guide/usage.txt docs/ref/fields.txt docs/ref/filters.txt docs/ref/filterset.txt docs/ref/settings.txt docs/ref/widgets.txt requirements/maintainer.txt requirements/test-ci.txt requirements/test.txt tests/__init__.py 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 tests/test_filterset.py tests/test_forms.py tests/test_utils.py tests/test_views.py tests/test_widgets.py tests/urls.py tests/rest_framework/__init__.py tests/rest_framework/apps.py tests/rest_framework/models.py tests/rest_framework/test_backends.py tests/rest_framework/test_filters.py tests/rest_framework/test_filterset.py tests/rest_framework/test_integration.py tests/rest_framework/templates/filter_template.html tests/templates/tests/book_filter.htmldjango-filter-1.1.0/django_filter.egg-info/top_level.txt0000644000076500000240000000001713172070045024053 0ustar carltonstaff00000000000000django_filters django-filter-1.1.0/django_filters/0000755000076500000240000000000013172070045020014 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/__init__.py0000644000076500000240000000135613172067725022145 0ustar carltonstaff00000000000000# 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, no-op. if pkgutil.find_loader('rest_framework') is not None: from . import rest_framework del pkgutil __version__ = '1.1.0' def parse_version(version): ''' '0.1.2-dev' -> (0, 1, 2, 'dev') '0.1.2' -> (0, 1, 2) ''' v = version.split('.') v = v[:-1] + v[-1].split('-') ret = [] for p in v: if p.isdigit(): ret.append(int(p)) else: ret.append(p) return tuple(ret) VERSION = parse_version(__version__) django-filter-1.1.0/django_filters/compat.py0000644000076500000240000000323713107513130021651 0ustar carltonstaff00000000000000 from __future__ import absolute_import import django from django.conf import settings from django.utils.timezone import make_aware as make_aware_orig try: from django.forms.utils import pretty_name except ImportError: # Django 1.8 from django.forms.forms import pretty_name # django-crispy-forms is optional try: import crispy_forms except ImportError: crispy_forms = None def is_crispy(): return 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms # coreapi is optional (Note that uritemplate is a dependency of coreapi) # Fixes #525 - cannot simply import from rest_framework.compat, due to # import issues w/ django-guardian. try: import coreapi except ImportError: coreapi = None try: import coreschema except ImportError: coreschema = None def remote_field(field): """ https://docs.djangoproject.com/en/1.9/releases/1.9/#field-rel-changes """ if django.VERSION >= (1, 9): return field.remote_field return field.rel def remote_model(field): if django.VERSION >= (1, 9): return remote_field(field).model return remote_field(field).to def remote_queryset(field): model = remote_model(field) limit_choices_to = field.get_limit_choices_to() return model._default_manager.complex_filter(limit_choices_to) def format_value(widget, value): if django.VERSION >= (1, 10): return widget.format_value(value) return widget._format_value(value) def make_aware(value, timezone, is_dst): """is_dst was added for 1.9""" if django.VERSION >= (1, 9): return make_aware_orig(value, timezone, is_dst) else: return make_aware_orig(value, timezone) django-filter-1.1.0/django_filters/conf.py0000644000076500000240000000627513172067725021340 0ustar carltonstaff00000000000000 from __future__ import absolute_import from django.conf import settings as dj_settings from django.core.signals import setting_changed 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, 'HELP_TEXT_EXCLUDE': True, # empty/null choices 'EMPTY_CHOICE_LABEL': '---------', 'NULL_CHOICE_LABEL': None, 'NULL_CHOICE_VALUE': 'null', 'STRICTNESS': STRICTNESS.RETURN_NO_RESULTS, 'VERBOSE_LOOKUPS': { # transforms don't need to be verbose, since their expressions are chained 'date': _('date'), 'year': _('year'), 'month': _('month'), 'day': _('day'), 'week_day': _('week day'), 'hour': _('hour'), 'minute': _('minute'), 'second': _('second'), # standard lookups 'exact': _(''), 'iexact': _(''), 'contains': _('contains'), 'icontains': _('contains'), 'in': _('is in'), 'gt': _('is greater than'), 'gte': _('is greater than or equal to'), 'lt': _('is less than'), 'lte': _('is less than or equal to'), 'startswith': _('starts with'), 'istartswith': _('starts with'), 'endswith': _('ends with'), 'iendswith': _('ends with'), 'range': _('is in range'), 'isnull': _(''), 'regex': _('matches regex'), 'iregex': _('matches regex'), 'search': _('search'), # postgres lookups 'contained_by': _('is contained by'), 'overlap': _('overlaps'), 'has_key': _('has key'), 'has_keys': _('has keys'), 'has_any_keys': _('has any keys'), 'trigram_similar': _('search'), }, } DEPRECATED_SETTINGS = [ 'HELP_TEXT_FILTER', 'HELP_TEXT_EXCLUDE', ] def is_callable(value): # check for callables, except types return callable(value) and not isinstance(value, type) class Settings(object): def __getattr__(self, name): if name not in DEFAULTS: msg = "'%s' object has no attribute '%s'" raise AttributeError(msg % (self.__class__.__name__, name)) value = self.get_setting(name) if is_callable(value): value = value() # Cache the result setattr(self, name, value) return value def get_setting(self, setting): django_setting = 'FILTERS_%s' % setting if setting in DEPRECATED_SETTINGS and hasattr(dj_settings, django_setting): deprecate("The '%s' setting has been deprecated." % django_setting) return getattr(dj_settings, django_setting, DEFAULTS[setting]) def change_setting(self, setting, value, enter, **kwargs): if not setting.startswith('FILTERS_'): return setting = setting[8:] # strip 'FILTERS_' # ensure a valid app setting is being overridden if setting not in DEFAULTS: return # if exiting, delete value to repopulate if enter: setattr(self, setting, value) else: delattr(self, setting) settings = Settings() setting_changed.connect(settings.change_setting) django-filter-1.1.0/django_filters/constants.py0000644000076500000240000000073513065267357022426 0ustar carltonstaff00000000000000 ALL_FIELDS = '__all__' EMPTY_VALUES = ([], (), {}, '', None) class STRICTNESS(object): class IGNORE(object): pass class RETURN_NO_RESULTS(object): pass class RAISE_VALIDATION_ERROR(object): pass # Values of False & True chosen for backward compatability reasons. # Originally, these were the only options. _LEGACY = { False: IGNORE, True: RETURN_NO_RESULTS, "RAISE": RAISE_VALIDATION_ERROR, } django-filter-1.1.0/django_filters/exceptions.py0000644000076500000240000000042412754141601022551 0ustar carltonstaff00000000000000 from django.core.exceptions import FieldError class FieldLookupError(FieldError): def __init__(self, model_field, lookup_expr): super(FieldLookupError, self).__init__( "Unsupported lookup '%s' for field '%s'." % (lookup_expr, model_field) ) django-filter-1.1.0/django_filters/fields.py0000644000076500000240000002263413172067725021656 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals 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 BaseCSVWidget, CSVWidget, LookupTypeWidget, RangeWidget class RangeField(forms.MultiValueField): widget = RangeWidget def __init__(self, fields=None, *args, **kwargs): if fields is None: fields = ( forms.DecimalField(), forms.DecimalField()) super(RangeField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: return slice(*data_list) return None class DateRangeField(RangeField): def __init__(self, *args, **kwargs): fields = ( forms.DateField(), forms.DateField()) super(DateRangeField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: start_date, stop_date = data_list if start_date: start_date = handle_timezone( datetime.combine(start_date, time.min), False ) if stop_date: stop_date = handle_timezone( datetime.combine(stop_date, time.max), False ) return slice(start_date, stop_date) return None class DateTimeRangeField(RangeField): def __init__(self, *args, **kwargs): fields = ( forms.DateTimeField(), forms.DateTimeField()) super(DateTimeRangeField, self).__init__(fields, *args, **kwargs) class TimeRangeField(RangeField): def __init__(self, *args, **kwargs): fields = ( forms.TimeField(), forms.TimeField()) super(TimeRangeField, self).__init__(fields, *args, **kwargs) class Lookup(namedtuple('Lookup', ('value', 'lookup_type'))): # python nature is test __len__ on tuple types for boolean check def __len__(self): if not self.value: return 0 return 2 class LookupTypeField(forms.MultiValueField): def __init__(self, field, lookup_choices, *args, **kwargs): fields = ( field, forms.ChoiceField(choices=lookup_choices) ) defaults = { 'widgets': [f.widget for f in fields], } widget = LookupTypeWidget(**defaults) kwargs['widget'] = widget kwargs['help_text'] = field.help_text super(LookupTypeField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): if len(data_list) == 2: return Lookup(value=data_list[0], lookup_type=data_list[1] or 'exact') return Lookup(value=None, lookup_type='exact') class IsoDateTimeField(forms.DateTimeField): """ Supports 'iso-8601' date format too which is out the scope of the ``datetime.strptime`` standard library # ISO 8601: ``http://www.w3.org/TR/NOTE-datetime`` Based on Gist example by David Medina https://gist.github.com/copitux/5773821 """ ISO_8601 = 'iso-8601' input_formats = [ISO_8601] def strptime(self, value, format): value = force_str(value) if format == self.ISO_8601: parsed = parse_datetime(value) if parsed is None: # Continue with other formats if doesn't match raise ValueError return handle_timezone(parsed) return super(IsoDateTimeField, self).strptime(value, format) class BaseCSVField(forms.Field): """ Base field for validating CSV types. Value validation is performed by secondary base classes. ex:: class IntegerCSVField(BaseCSVField, filters.IntegerField): pass """ base_widget_class = BaseCSVWidget def __init__(self, *args, **kwargs): widget = kwargs.get('widget') or self.widget kwargs['widget'] = self._get_widget_class(widget) super(BaseCSVField, self).__init__(*args, **kwargs) def _get_widget_class(self, widget): # passthrough, allows for override if isinstance(widget, BaseCSVWidget) or ( isinstance(widget, type) and issubclass(widget, BaseCSVWidget)): return widget # complain since we are unable to reconstruct widget instances assert isinstance(widget, type), \ "'%s.widget' must be a widget class, not %s." \ % (self.__class__.__name__, repr(widget)) bases = (self.base_widget_class, widget, ) return type(str('CSV%s' % widget.__name__), bases, {}) def clean(self, value): if value is None: return None return [super(BaseCSVField, self).clean(v) for v in value] class BaseRangeField(BaseCSVField): # Force use of text input, as range must always have two inputs. A date # input would only allow a user to input one value and would always fail. widget = CSVWidget default_error_messages = { 'invalid_values': _('Range query expects two values.') } def clean(self, value): value = super(BaseRangeField, self).clean(value) if value is not None and len(value) != 2: raise forms.ValidationError( self.error_messages['invalid_values'], 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 django-filter-1.1.0/django_filters/filters.py0000644000076500000240000005536113172067725022063 0ustar carltonstaff00000000000000from __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.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 from django.utils.translation import ugettext_lazy as _ from .compat import pretty_name from .conf import settings from .constants import EMPTY_VALUES from .fields import ( BaseCSVField, BaseRangeField, ChoiceField, DateRangeField, DateTimeRangeField, IsoDateTimeField, Lookup, LookupTypeField, ModelChoiceField, ModelMultipleChoiceField, MultipleChoiceField, RangeField, TimeRangeField ) from .utils import deprecate, label_for_filter __all__ = [ 'AllValuesFilter', 'AllValuesMultipleFilter', 'BaseCSVFilter', 'BaseInFilter', 'BaseRangeFilter', 'BooleanFilter', 'CharFilter', 'ChoiceFilter', 'DateFilter', 'DateFromToRangeFilter', 'DateRangeFilter', 'DateTimeFilter', 'DateTimeFromToRangeFilter', 'DurationFilter', 'Filter', 'IsoDateTimeFilter', 'ModelChoiceFilter', 'ModelMultipleChoiceFilter', 'MultipleChoiceFilter', 'NumberFilter', 'NumericRangeFilter', 'OrderingFilter', 'RangeFilter', 'TimeFilter', 'TimeRangeFilter', 'TypedChoiceFilter', 'TypedMultipleChoiceFilter', 'UUIDFilter', ] 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, 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.distinct = distinct self.exclude = exclude self.extra = kwargs self.extra.setdefault('required', False) self.creation_counter = Filter.creation_counter Filter.creation_counter += 1 def get_method(self, qs): """Return filter method based on whether we're excluding or simply filtering. """ return qs.exclude if self.exclude else qs.filter def method(): """ Filter method needs to be lazily resolved, as it may be dependent on the 'parent' FilterSet. """ def fget(self): return self._method def fset(self, value): self._method = value # clear existing FilterMethod if isinstance(self.filter, FilterMethod): del self.filter # override filter w/ FilterMethod. if value is not None: self.filter = FilterMethod(self) 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.field_name, self.lookup_expr, self.exclude ) return self._label def fset(self, value): self._label = value 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'): field_kwargs = self.extra.copy() if settings.DISABLE_HELP_TEXT: field_kwargs.pop('help_text', None) if (self.lookup_expr is None or isinstance(self.lookup_expr, (list, tuple))): lookup = [] for x in LOOKUP_TYPES: if isinstance(x, (list, tuple)) and len(x) == 2: choice = (x[0], x[1]) else: choice = (x, x) if self.lookup_expr is None: lookup.append(choice) else: if isinstance(x, (list, tuple)) and len(x) == 2: if x[0] in self.lookup_expr: lookup.append(choice) else: if x in self.lookup_expr: lookup.append(choice) self._field = LookupTypeField( self.field_class(**field_kwargs), lookup, required=field_kwargs['required'], label=self.label) else: self._field = self.field_class(label=self.label, **field_kwargs) return self._field def filter(self, qs, value): if isinstance(value, Lookup): lookup = six.text_type(value.lookup_type) value = value.value else: lookup = self.lookup_expr if value in EMPTY_VALUES: return qs if self.distinct: qs = qs.distinct() qs = self.get_method(qs)(**{'%s__%s' % (self.field_name, lookup): value}) return qs class CharFilter(Filter): field_class = forms.CharField class BooleanFilter(Filter): field_class = forms.NullBooleanField class ChoiceFilter(Filter): field_class = ChoiceField def __init__(self, *args, **kwargs): 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.field_name, self.lookup_expr): None}) return qs.distinct() if self.distinct else qs class TypedChoiceFilter(Filter): field_class = forms.TypedChoiceField class UUIDFilter(Filter): field_class = forms.UUIDField class MultipleChoiceFilter(Filter): """ This filter performs OR(by default) or AND(using conjoined=True) query on the selected options. Advanced usage -------------- Depending on your application logic, when all or no choices are selected, filtering may be a no-operation. In this case you may wish to avoid the filtering overhead, particularly if using a `distinct` call. You can override `get_filter_predicate` to use a custom filter. By default it will use the filter's name for the key, and the value will be the model object - or in case of passing in `to_field_name` the value of that attribute on the model. Set `always_filter` to `False` after instantiation to enable the default `is_noop` test. You can override `is_noop` if you need a different test for your application. `distinct` defaults to `True` as to-many relationships will generally require this. """ 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): """ Return `True` to short-circuit unnecessary and potentially slow filtering. """ if self.always_filter: return False # A reasonable default for being a noop... if self.extra.get('required') and len(value) == len(self.field.choices): return True return False def filter(self, qs, value): if not value: # Even though not a noop, no point filtering if empty. return qs if self.is_noop(qs, value): return qs 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) else: q |= Q(**predicate) if not self.conjoined: qs = self.get_method(qs)(q) return qs.distinct() if self.distinct else qs def get_filter_predicate(self, v): try: return {self.field_name: getattr(v, self.field.to_field_name)} except (AttributeError, TypeError): return {self.field_name: v} class TypedMultipleChoiceFilter(MultipleChoiceFilter): field_class = forms.TypedMultipleChoiceField class DateFilter(Filter): field_class = forms.DateField class DateTimeFilter(Filter): field_class = forms.DateTimeField class IsoDateTimeFilter(DateTimeFilter): """ Uses IsoDateTimeField to support filtering on ISO 8601 formated datetimes. For context see: * https://code.djangoproject.com/ticket/23448 * https://github.com/tomchristie/django-rest-framework/issues/1338 * https://github.com/alex/django-filter/pull/264 """ field_class = IsoDateTimeField class TimeFilter(Filter): field_class = forms.TimeField class DurationFilter(Filter): field_class = forms.DurationField class QuerySetRequestMixin(object): """ Add callable functionality to filters that support the ``queryset`` argument. If the ``queryset`` is callable, then it **must** accept the ``request`` object as a single argument. This is useful for filtering querysets by properties on the ``request`` object, such as the user. Example:: def departments(request): company = request.user.company return company.department_set.all() class EmployeeFilter(filters.FilterSet): department = filters.ModelChoiceFilter(queryset=departments) ... The above example restricts the set of departments to those in the logged-in user's associated company. """ def __init__(self, *args, **kwargs): self.queryset = kwargs.get('queryset') super(QuerySetRequestMixin, self).__init__(*args, **kwargs) def get_request(self): try: return self.parent.request except AttributeError: return None def get_queryset(self, request): queryset = self.queryset if callable(queryset): return queryset(request) return queryset @property def field(self): request = self.get_request() queryset = self.get_queryset(request) if queryset is not None: self.extra['queryset'] = queryset return super(QuerySetRequestMixin, self).field 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 = ModelMultipleChoiceField class NumberFilter(Filter): field_class = forms.DecimalField class NumericRangeFilter(Filter): field_class = RangeField def filter(self, qs, value): if value: if value.start is not None and value.stop is not None: 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.field_name: value.start}) if value.stop is not None: qs = self.get_method(qs)(**{'%s__endswith' % self.field_name: value.stop}) if self.distinct: qs = qs.distinct() return qs class RangeFilter(Filter): field_class = RangeField def filter(self, qs, value): if value: if value.start is not None and value.stop is not None: 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.field_name: value.start}) if value.stop is not None: qs = self.get_method(qs)(**{'%s__lte' % self.field_name: value.stop}) if self.distinct: qs = qs.distinct() return qs def _truncate(dt): return dt.date() class DateRangeFilter(ChoiceFilter): options = { '': (_('Any date'), lambda qs, name: qs), 1: (_('Today'), lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, '%s__month' % name: now().month, '%s__day' % name: now().day })), 2: (_('Past 7 days'), lambda qs, name: qs.filter(**{ '%s__gte' % name: _truncate(now() - timedelta(days=7)), '%s__lt' % name: _truncate(now() + timedelta(days=1)), })), 3: (_('This month'), lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, '%s__month' % name: now().month })), 4: (_('This year'), lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, })), 5: (_('Yesterday'), lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, '%s__month' % name: now().month, '%s__day' % name: (now() - timedelta(days=1)).day, })), } def __init__(self, *args, **kwargs): kwargs['choices'] = [ (key, value[0]) for key, value in six.iteritems(self.options)] # empty/null choices not relevant kwargs.setdefault('empty_label', None) kwargs.setdefault('null_label', None) super(DateRangeFilter, self).__init__(*args, **kwargs) def filter(self, qs, value): try: value = int(value) except (ValueError, TypeError): value = '' assert value in self.options qs = self.options[value][1](qs, self.field_name) if self.distinct: qs = qs.distinct() return qs class DateFromToRangeFilter(RangeFilter): field_class = DateRangeField class DateTimeFromToRangeFilter(RangeFilter): field_class = DateTimeRangeField class TimeRangeFilter(RangeFilter): field_class = TimeRangeField class AllValuesFilter(ChoiceFilter): @property def field(self): qs = self.model._default_manager.distinct() 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 class AllValuesMultipleFilter(MultipleChoiceFilter): @property def field(self): qs = self.model._default_manager.distinct() 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 class BaseCSVFilter(Filter): """ Base class for CSV type filters, such as IN and RANGE. """ base_field_class = BaseCSVField def __init__(self, *args, **kwargs): kwargs.setdefault('help_text', _('Multiple values may be separated by commas.')) super(BaseCSVFilter, self).__init__(*args, **kwargs) class ConcreteCSVField(self.base_field_class, self.field_class): pass ConcreteCSVField.__name__ = self._field_class_name( self.field_class, self.lookup_expr ) self.field_class = ConcreteCSVField @classmethod def _field_class_name(cls, field_class, lookup_expr): """ Generate a suitable class name for the concrete field class. This is not completely reliable, as not all field class names are of the format Field. ex:: BaseCSVFilter._field_class_name(DateTimeField, 'year__in') returns 'DateTimeYearInField' """ # DateTimeField => DateTime type_name = field_class.__name__ if type_name.endswith('Field'): type_name = type_name[:-5] # year__in => YearIn parts = lookup_expr.split(LOOKUP_SEP) expression_name = ''.join(p.capitalize() for p in parts) # DateTimeYearInField return str('%s%sField' % (type_name, expression_name)) class BaseInFilter(BaseCSVFilter): def __init__(self, *args, **kwargs): kwargs.setdefault('lookup_expr', 'in') super(BaseInFilter, self).__init__(*args, **kwargs) class BaseRangeFilter(BaseCSVFilter): base_field_class = BaseRangeField def __init__(self, *args, **kwargs): kwargs.setdefault('lookup_expr', 'range') super(BaseRangeFilter, self).__init__(*args, **kwargs) class OrderingFilter(BaseCSVFilter, ChoiceFilter): """ Enable queryset ordering. As an extension of ``ChoiceFilter`` it accepts two additional arguments that are used to build the ordering choices. * ``fields`` is a mapping of {model field name: parameter name}. The parameter names are exposed in the choices and mask/alias the field names used in the ``order_by()`` call. Similar to field ``choices``, ``fields`` accepts the 'list of two-tuples' syntax that retains order. ``fields`` may also just be an iterable of strings. In this case, the field names simply double as the exposed parameter names. * ``field_labels`` is an optional argument that allows you to customize the display label for the corresponding parameter. It accepts a mapping of {field name: human readable label}. Keep in mind that the key is the field name, and not the exposed parameter name. Additionally, you can just provide your own ``choices`` if you require explicit control over the exposed options. For example, when you might want to disable descending sort options. This filter is also CSV-based, and accepts multiple ordering params. The default select widget does not enable the use of this, but it is useful for APIs. """ descending_fmt = _('%s (descending)') def __init__(self, *args, **kwargs): """ ``fields`` may be either a mapping or an iterable. ``field_labels`` must be a map of field names to display labels """ fields = kwargs.pop('fields', {}) fields = self.normalize_fields(fields) field_labels = kwargs.pop('field_labels', {}) self.param_map = {v: k for k, v in fields.items()} if 'choices' not in kwargs: 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) def get_ordering_value(self, param): descending = param.startswith('-') param = param[1:] if descending else param field_name = self.param_map.get(param, param) return "-%s" % field_name if descending else field_name def filter(self, qs, value): if value in EMPTY_VALUES: return qs ordering = [self.get_ordering_value(param) for param in value] return qs.order_by(*ordering) @classmethod def normalize_fields(cls, fields): """ Normalize the fields into an ordered map of {field name: param name} """ # fields is a mapping, copy into new OrderedDict if isinstance(fields, dict): return OrderedDict(fields) # convert iterable of values => iterable of pairs (field name, param name) assert is_iterable(fields), \ "'fields' must be an iterable (e.g., a list, tuple, or mapping)." # fields is an iterable of field names assert all(isinstance(field, six.string_types) or is_iterable(field) and len(field) == 2 # may need to be wrapped in parens for field in fields), \ "'fields' must contain strings or (field name, param name) pairs." return OrderedDict([ (f, f) if isinstance(f, six.string_types) else f for f in fields ]) def build_choices(self, fields, labels): ascending = [ (param, labels.get(field, _(pretty_name(param)))) for field, param in fields.items() ] descending = [ ('-%s' % param, labels.get('-%s' % param, self.descending_fmt % label)) for param, label in ascending ] # interleave the ascending and descending choices return [val for pair in zip(ascending, descending) for val in pair] class FilterMethod(object): """ This helper is used to override Filter.filter() when a 'method' argument is passed. It proxies the call to the actual method on the filter's parent. """ def __init__(self, filter_instance): self.f = filter_instance def __call__(self, qs, value): if value in EMPTY_VALUES: return qs return self.method(qs, self.f.field_name, value) @property def method(self): """ Resolve the method on the parent filterset. """ instance = self.f # noop if 'method' is a function if callable(instance.method): return instance.method # 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.field_name, instance.method) parent = instance.parent method = getattr(parent, instance.method, None) assert callable(method), \ "Expected parent FilterSet '%s.%s' to have a '.%s()' method." % \ (parent.__class__.__module__, parent.__class__.__name__, instance.method) return method django-filter-1.1.0/django_filters/filterset.py0000644000076500000240000003725313172067725022414 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals import copy from collections import OrderedDict from django import forms from django.db import models from django.db.models.constants import LOOKUP_SEP from django.db.models.fields.related import ForeignObjectRel from django.utils import six from .compat import remote_field, remote_queryset 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): field_presence = [ form.cleaned_data.get(field) not in EMPTY_VALUES for field in fieldset ] if any(field_presence): return all(field_presence) return True def get_full_clean_override(together): # coerce together to list of pairs if isinstance(together[0], (six.string_types)): together = [together] def full_clean(form): super(form.__class__, form).full_clean() message = 'Following fields must be together: %s' for each in together: if not _together_valid(form, each): return form.add_error(None, message % ','.join(each)) return full_clean class FilterSetOptions(object): def __init__(self, options=None): self.model = getattr(options, 'model', None) self.fields = getattr(options, 'fields', None) self.exclude = getattr(options, 'exclude', None) self.filter_overrides = getattr(options, 'filter_overrides', {}) self.strict = getattr(options, 'strict', None) 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) class FilterSetMetaclass(type): def __new__(cls, name, bases, attrs): attrs['declared_filters'] = cls.get_declared_filters(bases, attrs) new_class = super(FilterSetMetaclass, cls).__new__(cls, name, bases, attrs) new_class._meta = FilterSetOptions(getattr(new_class, 'Meta', None)) new_class.base_filters = new_class.get_filters() return new_class @classmethod def get_declared_filters(cls, bases, attrs): filters = [ (filter_name, attrs.pop(filter_name)) for filter_name, obj in list(attrs.items()) if isinstance(obj, Filter) ] # Default the `filter.field_name` to the attribute name on the filterset for filter_name, f in filters: if getattr(f, 'field_name', None) is None: f.field_name = filter_name filters.sort(key=lambda x: x[1].creation_counter) # merge declared filters from base classes for base in reversed(bases): if hasattr(base, 'declared_filters'): filters = [ (name, f) for name, f in base.declared_filters.items() if name not in attrs ] + filters return OrderedDict(filters) FILTER_FOR_DBFIELD_DEFAULTS = { models.AutoField: {'filter_class': NumberFilter}, models.CharField: {'filter_class': CharFilter}, models.TextField: {'filter_class': CharFilter}, models.BooleanField: {'filter_class': BooleanFilter}, models.DateField: {'filter_class': DateFilter}, models.DateTimeField: {'filter_class': DateTimeFilter}, models.TimeField: {'filter_class': TimeFilter}, models.DurationField: {'filter_class': DurationFilter}, models.DecimalField: {'filter_class': NumberFilter}, models.SmallIntegerField: {'filter_class': NumberFilter}, models.IntegerField: {'filter_class': NumberFilter}, models.PositiveIntegerField: {'filter_class': NumberFilter}, models.PositiveSmallIntegerField: {'filter_class': NumberFilter}, models.FloatField: {'filter_class': NumberFilter}, models.NullBooleanField: {'filter_class': BooleanFilter}, models.SlugField: {'filter_class': CharFilter}, models.EmailField: {'filter_class': CharFilter}, models.FilePathField: {'filter_class': CharFilter}, models.URLField: {'filter_class': CharFilter}, models.GenericIPAddressField: {'filter_class': CharFilter}, models.CommaSeparatedIntegerField: {'filter_class': CharFilter}, models.UUIDField: {'filter_class': UUIDFilter}, models.OneToOneField: { 'filter_class': ModelChoiceFilter, '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: { 'filter_class': ModelChoiceFilter, '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: { 'filter_class': ModelMultipleChoiceFilter, 'extra': lambda f: { 'queryset': remote_queryset(f), } }, } class BaseFilterSet(object): FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS def __init__(self, data=None, queryset=None, prefix=None, strict=None, request=None): self.is_bound = data is not None self.data = data or {} if queryset is None: queryset = self._meta.model._default_manager.all() self.queryset = queryset self.form_prefix = prefix # What to do on on validation errors # Fallback to meta, then settings strictness if strict is None: strict = self._meta.strict if strict is None: strict = settings.STRICTNESS # transform legacy values self.strict = STRICTNESS._LEGACY.get(strict, strict) self.request = request self.filters = copy.deepcopy(self.base_filters) for filter_ in self.filters.values(): # propagate the model and filterset to the filters filter_.model = self._meta.model filter_.parent = self @property def qs(self): if not hasattr(self, '_qs'): if not self.is_bound: self._qs = self.queryset.all() return self._qs if not self.form.is_valid(): if self.strict == STRICTNESS.RAISE_VALIDATION_ERROR: raise forms.ValidationError(self.form.errors) elif self.strict == STRICTNESS.RETURN_NO_RESULTS: self._qs = self.queryset.none() return self._qs # else STRICTNESS.IGNORE... ignoring # start with all the results and filter from there qs = self.queryset.all() for name, filter_ in six.iteritems(self.filters): value = self.form.cleaned_data.get(name) if value is not None: # valid & clean data qs = filter_.filter(qs, value) self._qs = qs return self._qs @property def form(self): if not hasattr(self, '_form'): fields = OrderedDict([ (name, filter_.field) for name, filter_ in six.iteritems(self.filters)]) Form = type(str('%sForm' % self.__class__.__name__), (self._meta.form,), fields) if self._meta.together: Form.full_clean = get_full_clean_override(self._meta.together) if self.is_bound: self._form = Form(self.data, prefix=self.form_prefix) else: self._form = Form(prefix=self.form_prefix) return self._form @classmethod def get_fields(cls): """ Resolve the 'fields' argument that should be used for generating filters on the filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'. """ model = cls._meta.model fields = cls._meta.fields exclude = cls._meta.exclude assert not (fields is None and exclude is None), \ "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " \ "has been deprecated since 0.15.0 and is now disallowed. Add an explicit " \ "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__ # Setting exclude with no fields implies all other fields. if exclude is not None and fields is None: fields = ALL_FIELDS # Resolve ALL_FIELDS into all fields for the filterset's model. if fields == ALL_FIELDS: fields = get_all_model_fields(model) # Remove excluded fields exclude = exclude or [] if not isinstance(fields, dict): fields = [(f, ['exact']) for f in fields if f not in exclude] else: fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude] 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 generated filters. """ # No model specified - skip filter generation if not cls._meta.model: return cls.declared_filters.copy() # Determine the filters that should be included on the filterset. filters = OrderedDict() fields = cls.get_fields() undefined = [] for field_name, lookups in fields.items(): field = get_model_field(cls._meta.model, field_name) # warn if the field doesn't exist. if field is None: undefined.append(field_name) # ForeignObjectRel does not support non-exact lookups if isinstance(field, ForeignObjectRel): filters[field_name] = cls.filter_for_reverse_field(field, field_name) continue for lookup_expr in lookups: 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: filters[filter_name] = cls.declared_filters[filter_name] continue if field is not None: filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr) # filter out declared filters undefined = [f for f in undefined if f not in cls.declared_filters] if undefined: raise TypeError( "'Meta.fields' contains fields that are not defined on this FilterSet: " "%s" % ', '.join(undefined) ) # Add in declared filters. This is necessary since we don't enforce adding # declared filters to the 'Meta.fields' option filters.update(cls.declared_filters) return filters @classmethod def filter_for_field(cls, f, field_name, lookup_expr='exact'): f, lookup_type = resolve_field(f, lookup_expr) default = { 'field_name': field_name, 'lookup_expr': lookup_expr, } filter_class, params = cls.filter_for_lookup(f, lookup_type) default.update(params) assert filter_class is not None, ( "%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__, field_name, lookup_expr, f.__class__.__name__) return filter_class(**default) @classmethod def filter_for_reverse_field(cls, f, field_name): rel = remote_field(f.field) queryset = f.field.model._default_manager.all() default = { 'field_name': field_name, 'queryset': queryset, } if rel.multiple: return ModelMultipleChoiceFilter(**default) else: return ModelChoiceFilter(**default) @classmethod def filter_for_lookup(cls, f, lookup_type): DEFAULTS = dict(cls.FILTER_DEFAULTS) if hasattr(cls, '_meta'): DEFAULTS.update(cls._meta.filter_overrides) data = try_dbfield(DEFAULTS.get, f.__class__) or {} filter_class = data.get('filter_class') params = data.get('extra', lambda f: {})(f) # if there is no filter class, exit early if not filter_class: return None, {} # perform lookup specific checks if lookup_type == 'exact' and f.choices: return ChoiceFilter, {'choices': f.choices} if lookup_type == 'isnull': data = try_dbfield(DEFAULTS.get, models.BooleanField) filter_class = data.get('filter_class') params = data.get('extra', lambda f: {})(f) return filter_class, params if lookup_type == 'in': class ConcreteInFilter(BaseInFilter, filter_class): pass ConcreteInFilter.__name__ = cls._csv_filter_class_name( filter_class, lookup_type ) return ConcreteInFilter, params if lookup_type == 'range': class ConcreteRangeFilter(BaseRangeFilter, filter_class): pass ConcreteRangeFilter.__name__ = cls._csv_filter_class_name( filter_class, lookup_type ) return ConcreteRangeFilter, params return filter_class, params @classmethod def _csv_filter_class_name(cls, filter_class, lookup_type): """ Generate a suitable class name for a concrete filter class. This is not completely reliable, as not all filter class names are of the format Filter. ex:: FilterSet._csv_filter_class_name(DateTimeFilter, 'in') returns 'DateTimeInFilter' """ # DateTimeFilter => DateTime type_name = filter_class.__name__ if type_name.endswith('Filter'): type_name = type_name[:-6] # in => In lookup_name = lookup_type.capitalize() # DateTimeInFilter return str('%s%sFilter' % (type_name, lookup_name)) class FilterSet(six.with_metaclass(FilterSetMetaclass, BaseFilterSet)): pass def filterset_factory(model, fields=ALL_FIELDS): meta = type(str('Meta'), (object,), {'model': model, 'fields': fields}) filterset = type(str('%sFilterSet' % model._meta.object_name), (FilterSet,), {'Meta': meta}) return filterset django-filter-1.1.0/django_filters/locale/0000755000076500000240000000000013172070045021253 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/de/0000755000076500000240000000000013172070045021643 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/de/LC_MESSAGES/0000755000076500000240000000000013172070045023430 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/de/LC_MESSAGES/django.mo0000644000076500000240000000133312410601557025231 0ustar carltonstaff00000000000000\       %s (descending)AllAny datePast 7 daysThis monthThis yearTodayProject-Id-Version: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2013-08-10 05:34-0500 PO-Revision-Date: 2013-08-10 12:29+0100 Last-Translator: Florian Apolloner Language-Team: Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.4 %s (absteigend)AlleAlle DatenLetzte 7 TageDiesen MonatDieses JahrHeutedjango-filter-1.1.0/django_filters/locale/de/LC_MESSAGES/django.po0000644000076500000240000000206312410601557025235 0ustar carltonstaff00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-08-10 05:34-0500\n" "PO-Revision-Date: 2013-08-10 12:29+0100\n" "Last-Translator: Florian Apolloner \n" "Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.5.4\n" #: filters.py:153 msgid "Any date" msgstr "Alle Daten" #: filters.py:154 msgid "Today" msgstr "Heute" #: filters.py:159 msgid "Past 7 days" msgstr "Letzte 7 Tage" #: filters.py:163 msgid "This month" msgstr "Diesen Monat" #: filters.py:167 msgid "This year" msgstr "Dieses Jahr" #: filterset.py:332 filterset.py:341 #, python-format msgid "%s (descending)" msgstr "%s (absteigend)" #: widgets.py:63 msgid "All" msgstr "Alle" django-filter-1.1.0/django_filters/locale/es_AR/0000755000076500000240000000000013172070045022244 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/es_AR/LC_MESSAGES/0000755000076500000240000000000013172070045024031 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/es_AR/LC_MESSAGES/django.mo0000644000076500000240000000135012643540653025640 0ustar carltonstaff00000000000000\   p AllAny datePast 7 daysThis is an exclusion filterThis monthThis yearTodayProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2013-07-05 19:24+0200 PO-Revision-Date: 2015-10-11 20:53-0300 Last-Translator: Gonzalo Bustos Language-Team: Spanish (Argentina) Language: es_AR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.10 TodosCualquier fechaÚltimos 7 díasEste es un filtro de exclusiónEste mesEste añoHoydjango-filter-1.1.0/django_filters/locale/es_AR/LC_MESSAGES/django.po0000644000076500000240000000176712643540653025657 0ustar carltonstaff00000000000000# Django Filter translation. # Copyright (C) 2013 # This file is distributed under the same license as the django_filter package. # Gonzalo Bustos, 2015. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-07-05 19:24+0200\n" "PO-Revision-Date: 2015-10-11 20:53-0300\n" "Last-Translator: Gonzalo Bustos\n" "Language-Team: Spanish (Argentina)\n" "Language: es_AR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: filters.py:51 msgid "This is an exclusion filter" msgstr "Este es un filtro de exclusión" #: filters.py:158 msgid "Any date" msgstr "Cualquier fecha" #: filters.py:159 msgid "Today" msgstr "Hoy" #: filters.py:164 msgid "Past 7 days" msgstr "Últimos 7 días" #: filters.py:168 msgid "This month" msgstr "Este mes" #: filters.py:172 msgid "This year" msgstr "Este año" #: widgets.py:63 msgid "All" msgstr "Todos" django-filter-1.1.0/django_filters/locale/es_ES/0000755000076500000240000000000013172070045022251 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/es_ES/LC_MESSAGES/0000755000076500000240000000000013172070045024036 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/es_ES/LC_MESSAGES/django.mo0000644000076500000240000000430713065267357025660 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]ir@w'#'4\c lv z    ) 5 AK"]  %# "  )(& * !$'%s (descending)AllAny dateField filtersMultiple values may be separated by commas.NoOrderingPast 7 daysRange query expects two values.SubmitThis monthThis yearTodayUnknownYesYesterdaycontainsdatedayends withexcludehas any keyshas keyhas keyshouris contained byis greater thanis greater than or equal tois inis in rangeis less thanis less than or equal tomatches regexminutemonthoverlapssearchsecondstarts withweek dayyearProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2017-01-26 20:32+0100 PO-Revision-Date: 2017-01-26 20:52+0100 Last-Translator: Carlos Goce Language-Team: Spanish (España) Language: es_ES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 1.8.11 %s (descendente)TodoCualquier fechaFiltros de campoMúltiples valores separados por comas.NoOrdenadoÚltimos 7 díasConsultar un rango requiere dos valoresEnviarEste mesEste añoHoyDesconocidoSíAyercontienefechadíatermina porexcluyecontiene alguna de las clavescontiene la clavecontiene las claveshoracontenido enmayor quemayor o igual quepresente enen el rangomenor quemenor o igual quecoincide con la expresión regularminutomessolapadobuscarsegundocomienza pordía de la semanaañodjango-filter-1.1.0/django_filters/locale/es_ES/LC_MESSAGES/django.po0000644000076500000240000000610713065267357025663 0ustar carltonstaff00000000000000# Django Filter translation. # Copyright (C) 2013 # This file is distributed under the same license as the django_filter package. # Carlos Goce, 2017. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-26 20:32+0100\n" "PO-Revision-Date: 2017-01-26 20:52+0100\n" "Last-Translator: Carlos Goce\n" "Language-Team: Spanish (España)\n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.8.11\n" #: conf.py:26 msgid "date" msgstr "fecha" #: conf.py:27 msgid "year" msgstr "año" #: conf.py:28 msgid "month" msgstr "mes" #: conf.py:29 msgid "day" msgstr "día" #: conf.py:30 msgid "week day" msgstr "día de la semana" #: conf.py:31 msgid "hour" msgstr "hora" #: conf.py:32 msgid "minute" msgstr "minuto" #: conf.py:33 msgid "second" msgstr "segundo" #: conf.py:38 conf.py:39 msgid "contains" msgstr "contiene" #: conf.py:40 msgid "is in" msgstr "presente en" #: conf.py:41 msgid "is greater than" msgstr "mayor que" #: conf.py:42 msgid "is greater than or equal to" msgstr "mayor o igual que" #: conf.py:43 msgid "is less than" msgstr "menor que" #: conf.py:44 msgid "is less than or equal to" msgstr "menor o igual que" #: conf.py:45 conf.py:46 msgid "starts with" msgstr "comienza por" #: conf.py:47 conf.py:48 msgid "ends with" msgstr "termina por" #: conf.py:49 msgid "is in range" msgstr "en el rango" #: conf.py:51 conf.py:52 msgid "matches regex" msgstr "coincide con la expresión regular" #: conf.py:53 conf.py:61 msgid "search" msgstr "buscar" #: conf.py:56 msgid "is contained by" msgstr "contenido en" #: conf.py:57 msgid "overlaps" msgstr "solapado" #: conf.py:58 msgid "has key" msgstr "contiene la clave" #: conf.py:59 msgid "has keys" msgstr "contiene las claves" #: conf.py:60 msgid "has any keys" msgstr "contiene alguna de las claves" #: fields.py:167 msgid "Range query expects two values." msgstr "Consultar un rango requiere dos valores" #: filters.py:443 msgid "Any date" msgstr "Cualquier fecha" #: filters.py:444 msgid "Today" msgstr "Hoy" #: filters.py:449 msgid "Past 7 days" msgstr "Últimos 7 días" #: filters.py:453 msgid "This month" msgstr "Este mes" #: filters.py:457 msgid "This year" msgstr "Este año" #: filters.py:460 msgid "Yesterday" msgstr "Ayer" #: filters.py:526 msgid "Multiple values may be separated by commas." msgstr "Múltiples valores separados por comas." #: filters.py:605 #, python-format msgid "%s (descending)" msgstr "%s (descendente)" #: filters.py:621 msgid "Ordering" msgstr "Ordenado" #: utils.py:220 msgid "exclude" msgstr "excluye" #: widgets.py:71 msgid "All" msgstr "Todo" #: widgets.py:119 msgid "Unknown" msgstr "Desconocido" #: widgets.py:120 msgid "Yes" msgstr "Sí" #: widgets.py:121 msgid "No" msgstr "No" #: rest_framework/filterset.py:31 #: templates/django_filters/rest_framework/form.html:5 msgid "Submit" msgstr "Enviar" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 msgid "Field filters" msgstr "Filtros de campo" django-filter-1.1.0/django_filters/locale/fr/0000755000076500000240000000000013172070045021662 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/fr/LC_MESSAGES/0000755000076500000240000000000013172070045023447 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/fr/LC_MESSAGES/django.mo0000644000076500000240000000140012411012542025232 0ustar carltonstaff00000000000000\   |   AllAny datePast 7 daysThis is an exclusion filterThis monthThis yearTodayProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2013-07-05 19:24+0200 PO-Revision-Date: 2013-07-05 19:24+0200 Last-Translator: Axel Haustant Language-Team: LANGUAGE Language: French MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); TousToutes les dates7 derniers joursCeci est un filtre d'exclusionCe mois-ciCette annéeAujourd'huidjango-filter-1.1.0/django_filters/locale/fr/LC_MESSAGES/django.po0000644000076500000240000000205312411012542025242 0ustar carltonstaff00000000000000# Django Filter translation. # Copyright (C) 2013 # This file is distributed under the same license as the django_filter package. # Axel Haustant , 2013. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-07-05 19:24+0200\n" "PO-Revision-Date: 2013-07-05 19:24+0200\n" "Last-Translator: Axel Haustant \n" "Language-Team: LANGUAGE \n" "Language: French\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: filters.py:51 msgid "This is an exclusion filter" msgstr "Ceci est un filtre d'exclusion" #: filters.py:158 msgid "Any date" msgstr "Toutes les dates" #: filters.py:159 msgid "Today" msgstr "Aujourd'hui" #: filters.py:164 msgid "Past 7 days" msgstr "7 derniers jours" #: filters.py:168 msgid "This month" msgstr "Ce mois-ci" #: filters.py:172 msgid "This year" msgstr "Cette année" #: widgets.py:63 msgid "All" msgstr "Tous" django-filter-1.1.0/django_filters/locale/pl/0000755000076500000240000000000013172070045021666 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/pl/LC_MESSAGES/0000755000076500000240000000000013172070045023453 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/pl/LC_MESSAGES/django.mo0000644000076500000240000000200312643540653025256 0ustar carltonstaff00000000000000 | !15>E NZ v  ~    %s (descending)AllAny dateFilterOrderingPast 7 daysThis is an exclusion filterThis monthThis yearTodayYesterdayProject-Id-Version: django_filters 0.0.1 Report-Msgid-Bugs-To: POT-Creation-Date: 2015-07-25 01:24+0200 PO-Revision-Date: 2015-07-25 01:27+0100 Last-Translator: Adam Dobrawy Language-Team: Adam Dobrawy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); X-Generator: Poedit 1.5.4 Language: pl_PL %s (malejąco)WszystkoDowolna dataFilterSortowanieOstatnie 7 dniJest to filtr wykluczającyTen miesiącTen rokDziśWczorajdjango-filter-1.1.0/django_filters/locale/pl/LC_MESSAGES/django.po0000644000076500000240000000667413172067725025305 0ustar carltonstaff00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , 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: 2017-09-01 17:21+0000\n" "PO-Revision-Date: 2015-07-25 01:27+0100\n" "Last-Translator: Adam Dobrawy \n" "Language-Team: Adam Dobrawy \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" #: conf.py:25 #, fuzzy #| msgid "Any date" msgid "date" msgstr "Dowolna data" #: conf.py:26 #, fuzzy #| msgid "This year" msgid "year" msgstr "Ten rok" #: 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" #: conf.py:52 conf.py:60 msgid "search" msgstr "szukaj" #: conf.py:55 msgid "is contained by" msgstr "zawiera się w" #: conf.py:56 msgid "overlaps" msgstr "" #: conf.py:57 msgid "has key" msgstr "" #: conf.py:58 msgid "has keys" msgstr "" #: conf.py:59 msgid "has any keys" msgstr "" #: fields.py:172 msgid "Range query expects two values." msgstr "" #: filters.py:452 msgid "Any date" msgstr "Dowolna data" #: filters.py:453 msgid "Today" msgstr "Dziś" #: filters.py:458 msgid "Past 7 days" msgstr "Ostatnie 7 dni" #: filters.py:462 msgid "This month" msgstr "Ten miesiąc" #: filters.py:466 msgid "This year" msgstr "Ten rok" #: filters.py:469 msgid "Yesterday" msgstr "Wczoraj" #: filters.py:535 msgid "Multiple values may be separated by commas." msgstr "Wiele wartości można rozdzielić przecinkami" #: filters.py:614 #, python-format msgid "%s (descending)" msgstr "%s (malejąco)" #: filters.py:630 msgid "Ordering" msgstr "Sortowanie" #: rest_framework/filterset.py:34 #: templates/django_filters/rest_framework/form.html:5 msgid "Submit" msgstr "" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 #, fuzzy #| msgid "Filter" msgid "Field filters" msgstr "Filter" #: utils.py:225 msgid "exclude" msgstr "" #: widgets.py:66 msgid "All" msgstr "Wszystko" #: widgets.py:173 msgid "Unknown" msgstr "" #: widgets.py:174 msgid "Yes" msgstr "Tak" #: widgets.py:175 msgid "No" msgstr "Nie" #~ msgid "This is an exclusion filter" #~ msgstr "Jest to filtr wykluczający" django-filter-1.1.0/django_filters/locale/ru/0000755000076500000240000000000013172070045021701 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/ru/LC_MESSAGES/0000755000076500000240000000000013172070045023466 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/ru/LC_MESSAGES/django.mo0000644000076500000240000000154013013341253025261 0ustar carltonstaff00000000000000\   #<Q%s (descending)AllAny datePast 7 daysThis monthThis yearTodayProject-Id-Version: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2013-08-10 05:34-0500 PO-Revision-Date: 2016-09-29 11:47+0300 Last-Translator: Mikhail Mitrofanov Language-Team: Language: ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); X-Generator: Poedit 1.8.9 %s (по убыванию)ВсеЛюбая датаПрошедшие 7 днейЗа этот месяцВ этом годуСегодняdjango-filter-1.1.0/django_filters/locale/ru/LC_MESSAGES/django.po0000644000076500000240000000227013013341253025265 0ustar carltonstaff00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-08-10 05:34-0500\n" "PO-Revision-Date: 2016-09-29 11:47+0300\n" "Last-Translator: Mikhail Mitrofanov \n" "Language-Team: \n" "Language: ru\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%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 1.8.9\n" #: filters.py:153 msgid "Any date" msgstr "Любая дата" #: filters.py:154 msgid "Today" msgstr "Сегодня" #: filters.py:159 msgid "Past 7 days" msgstr "Прошедшие 7 дней" #: filters.py:163 msgid "This month" msgstr "За этот месяц" #: filters.py:167 msgid "This year" msgstr "В этом году" #: filterset.py:332 filterset.py:341 #, python-format msgid "%s (descending)" msgstr "%s (по убыванию)" #: widgets.py:63 msgid "All" msgstr "Все" django-filter-1.1.0/django_filters/locale/zh_CN/0000755000076500000240000000000013172070045022254 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/zh_CN/LC_MESSAGES/0000755000076500000240000000000013172070045024041 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/locale/zh_CN/LC_MESSAGES/django.po0000644000076500000240000000243112670617671025661 0ustar carltonstaff00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Kane Blueriver , 2016. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-01-30 17:39+0800\n" "PO-Revision-Date: 2016-01-30 17:50+0800\n" "Last-Translator: Kane Blueriver \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: filters.py:62 msgid "This is an exclusion filter" msgstr "未启用该过滤器" #: filters.py:62 msgid "Filter" msgstr "过滤器" #: filters.py:264 msgid "Any date" msgstr "任何时刻" #: filters.py:265 msgid "Today" msgstr "今日" #: filters.py:270 msgid "Past 7 days" msgstr "过去 7 日" #: filters.py:274 msgid "This month" msgstr "本月" #: filters.py:278 msgid "This year" msgstr "今年" #: filters.py:281 msgid "Yesterday" msgstr "昨日" #: filterset.py:398 filterset.py:409 #, python-format msgid "%s (descending)" msgstr "%s(降序)" #: filterset.py:411 msgid "Ordering" msgstr "排序" #: widgets.py:60 msgid "All" msgstr "全部" django-filter-1.1.0/django_filters/models.py0000644000076500000240000000000012410601557021641 0ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/rest_framework/0000755000076500000240000000000013172070045023046 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/rest_framework/__init__.py0000644000076500000240000000023012770314705025161 0ustar carltonstaff00000000000000# flake8: noqa from __future__ import absolute_import from .backends import DjangoFilterBackend from .filterset import FilterSet from .filters import * django-filter-1.1.0/django_filters/rest_framework/backends.py0000644000076500000240000000700313172067725025205 0ustar carltonstaff00000000000000 from __future__ import absolute_import import warnings from django.template import loader from django.utils import six from . import filters, filterset from .. import compat class DjangoFilterBackend(object): default_filter_set = filterset.FilterSet @property def template(self): if compat.is_crispy(): return 'django_filters/rest_framework/crispy_form.html' return 'django_filters/rest_framework/form.html' def get_filter_class(self, view, queryset=None): """ Return the django-filters `FilterSet` used to filter the queryset. """ filter_class = getattr(view, 'filter_class', None) filter_fields = getattr(view, 'filter_fields', None) if filter_class: filter_model = filter_class.Meta.model assert issubclass(queryset.model, filter_model), \ 'FilterSet model %s does not match queryset model %s' % \ (filter_model, queryset.model) return filter_class if filter_fields: MetaBase = getattr(self.default_filter_set, 'Meta', object) class AutoFilterSet(self.default_filter_set): class Meta(MetaBase): model = queryset.model fields = filter_fields return AutoFilterSet return None def filter_queryset(self, request, queryset, view): filter_class = self.get_filter_class(view, queryset) if filter_class: return filter_class(request.query_params, queryset=queryset, request=request).qs return queryset def to_html(self, request, queryset, view): filter_class = self.get_filter_class(view, queryset) if not filter_class: return None filter_instance = filter_class(request.query_params, queryset=queryset, request=request) template = loader.get_template(self.template) context = { 'filter': filter_instance } return template.render(context, request) def get_coreschema_field(self, field): if isinstance(field, filters.NumberFilter): field_cls = compat.coreschema.Number else: field_cls = compat.coreschema.String return field_cls( description=six.text_type(field.extra.get('help_text', '')) ) def get_schema_fields(self, view): # This is not compatible with widgets where the query param differs from the # filter's attribute name. Notably, this includes `MultiWidget`, where query # params will be of the format `_0`, `_1`, etc... assert compat.coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' assert compat.coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' filter_class = getattr(view, 'filter_class', None) if filter_class is None: try: filter_class = self.get_filter_class(view, view.get_queryset()) except Exception: warnings.warn( "{} is not compatible with schema generation".format(view.__class__) ) filter_class = None return [] if not filter_class else [ compat.coreapi.Field( name=field_name, required=field.extra['required'], location='query', schema=self.get_coreschema_field(field) ) for field_name, field in filter_class.base_filters.items() ] django-filter-1.1.0/django_filters/rest_framework/filters.py0000644000076500000240000000037512770314705025104 0ustar carltonstaff00000000000000 from ..filters import * from ..widgets import BooleanWidget class BooleanFilter(BooleanFilter): def __init__(self, *args, **kwargs): kwargs.setdefault('widget', BooleanWidget) super(BooleanFilter, self).__init__(*args, **kwargs) django-filter-1.1.0/django_filters/rest_framework/filterset.py0000644000076500000240000000271313172067725025437 0ustar carltonstaff00000000000000 from __future__ import absolute_import from copy import deepcopy from django import forms from django.db import models from django.utils.translation import ugettext_lazy as _ from django_filters import filterset from .. import compat, utils from .filters import BooleanFilter, IsoDateTimeFilter FILTER_FOR_DBFIELD_DEFAULTS = deepcopy(filterset.FILTER_FOR_DBFIELD_DEFAULTS) FILTER_FOR_DBFIELD_DEFAULTS.update({ models.DateTimeField: {'filter_class': IsoDateTimeFilter}, models.BooleanField: {'filter_class': BooleanFilter}, }) class FilterSet(filterset.FilterSet): FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS @property def form(self): form = super(FilterSet, self).form if compat.is_crispy(): from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Submit layout_components = list(form.fields.keys()) + [ Submit('', _('Submit'), css_class='btn-default'), ] helper = FormHelper() helper.form_method = 'GET' helper.template_pack = 'bootstrap3' helper.layout = Layout(*layout_components) form.helper = helper return form @property def qs(self): from rest_framework.exceptions import ValidationError try: return super(FilterSet, self).qs except forms.ValidationError as e: raise ValidationError(utils.raw_validation(e)) django-filter-1.1.0/django_filters/templates/0000755000076500000240000000000013172070045022012 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/templates/django_filters/0000755000076500000240000000000013172070045025004 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/templates/django_filters/rest_framework/0000755000076500000240000000000013172070045030036 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/templates/django_filters/rest_framework/crispy_form.html0000644000076500000240000000015413065267357033277 0ustar carltonstaff00000000000000{% load crispy_forms_tags %} {% load i18n %}

{% trans "Field filters" %}

{% crispy filter.form %} django-filter-1.1.0/django_filters/templates/django_filters/rest_framework/form.html0000644000076500000240000000032313065267357031704 0ustar carltonstaff00000000000000{% load i18n %}

{% trans "Field filters" %}

{{ filter.form.as_p }}
django-filter-1.1.0/django_filters/templates/django_filters/widgets/0000755000076500000240000000000013172070045026452 5ustar carltonstaff00000000000000django-filter-1.1.0/django_filters/templates/django_filters/widgets/multiwidget.html0000644000076500000240000000016613065267357031720 0ustar carltonstaff00000000000000{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %} django-filter-1.1.0/django_filters/utils.py0000644000076500000240000001676313172067725021556 0ustar carltonstaff00000000000000import warnings import django from django.conf import settings from django.core.exceptions import FieldError from django.db import models from django.db.models.constants import LOOKUP_SEP from django.db.models.expressions import Expression from django.db.models.fields import FieldDoesNotExist from django.db.models.fields.related import ForeignObjectRel, RelatedField from django.forms import ValidationError from django.utils import six, timezone from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext as _ from .compat import make_aware, remote_field, remote_model from .exceptions import FieldLookupError def deprecate(msg, level_modifier=0): warnings.warn( "%s See: https://django-filter.readthedocs.io/en/develop/migration.html" % msg, DeprecationWarning, stacklevel=3 + level_modifier) def try_dbfield(fn, field_class): """ Try ``fn`` with the DB ``field_class`` by walking its MRO until a result is found. ex:: _try_dbfield(field_dict.get, models.CharField) """ # walk the mro, as field_class could be a derived model field. for cls in field_class.mro(): # skip if cls is models.Field if cls is models.Field: continue data = fn(cls) if data: return data def get_all_model_fields(model): opts = model._meta return [ f.name for f in sorted(opts.fields + opts.many_to_many) if not isinstance(f, models.AutoField) and not (getattr(remote_field(f), 'parent_link', False)) ] def get_model_field(model, field_name): """ Get a ``model`` field, traversing relationships in the ``field_name``. ex:: f = get_model_field(Book, 'author__first_name') """ fields = get_field_parts(model, field_name) return fields[-1] if fields else None def get_field_parts(model, field_name): """ Get the field parts that represent the traversable relationships from the base ``model`` to the final field, described by ``field_name``. ex:: >>> parts = get_field_parts(Book, 'author__first_name') >>> [p.verbose_name for p in parts] ['author', 'first name'] """ parts = field_name.split(LOOKUP_SEP) opts = model._meta fields = [] # walk relationships for name in parts: try: field = opts.get_field(name) except FieldDoesNotExist: return None fields.append(field) if isinstance(field, RelatedField): opts = remote_model(field)._meta elif isinstance(field, ForeignObjectRel): opts = field.related_model._meta return fields def resolve_field(model_field, lookup_expr): """ Resolves a ``lookup_expr`` into its final output field, given the initial ``model_field``. The lookup expression should only contain transforms and lookups, not intermediary model field parts. Note: This method is based on django.db.models.sql.query.Query.build_lookup For more info on the lookup API: https://docs.djangoproject.com/en/1.9/ref/models/lookups/ """ query = model_field.model._default_manager.all().query lhs = Expression(model_field) lookups = lookup_expr.split(LOOKUP_SEP) assert len(lookups) > 0 try: while lookups: name = lookups[0] args = (lhs, name) if django.VERSION < (2, 0): # rest_of_lookups was removed in Django 2.0 args += (lookups,) # If there is just one part left, try first get_lookup() so # that if the lhs supports both transform and lookup for the # name, then lookup will be picked. if len(lookups) == 1: final_lookup = lhs.get_lookup(name) if not final_lookup: # We didn't find a lookup. We are going to interpret # the name as transform, and do an Exact lookup against # it. lhs = query.try_transform(*args) final_lookup = lhs.get_lookup('exact') return lhs.output_field, final_lookup.lookup_name lhs = query.try_transform(*args) lookups = lookups[1:] except FieldError as e: six.raise_from(FieldLookupError(model_field, lookup_expr), e) def handle_timezone(value, is_dst=None): if settings.USE_TZ and timezone.is_naive(value): return make_aware(value, timezone.get_current_timezone(), is_dst) elif not settings.USE_TZ and timezone.is_aware(value): return timezone.make_naive(value, timezone.utc) return value def verbose_field_name(model, field_name): """ Get the verbose name for a given ``field_name``. The ``field_name`` will be traversed across relationships. Returns '[invalid name]' for any field name that cannot be traversed. ex:: >>> verbose_field_name(Article, 'author__name') 'author name' """ if field_name is None: return '[invalid name]' parts = get_field_parts(model, field_name) if not parts: return '[invalid name]' names = [] for part in parts: if isinstance(part, ForeignObjectRel): if part.related_name: names.append(part.related_name.replace('_', ' ')) else: return '[invalid name]' else: names.append(force_text(part.verbose_name)) return ' '.join(names) def verbose_lookup_expr(lookup_expr): """ Get a verbose, more humanized expression for a given ``lookup_expr``. Each part in the expression is looked up in the ``FILTERS_VERBOSE_LOOKUPS`` dictionary. Missing keys will simply default to itself. ex:: >>> verbose_lookup_expr('year__lt') 'year is less than' # with `FILTERS_VERBOSE_LOOKUPS = {}` >>> verbose_lookup_expr('year__lt') 'year lt' """ from .conf import settings as app_settings VERBOSE_LOOKUPS = app_settings.VERBOSE_LOOKUPS or {} lookups = [ force_text(VERBOSE_LOOKUPS.get(lookup, _(lookup))) for lookup in lookup_expr.split(LOOKUP_SEP) ] return ' '.join(lookups) def label_for_filter(model, field_name, lookup_expr, exclude=False): """ Create a generic label suitable for a filter. ex:: >>> label_for_filter(Article, 'author__name', 'in') 'auther name is in' """ name = verbose_field_name(model, field_name) verbose_expression = [_('exclude'), name] if exclude else [name] # iterable lookups indicate a LookupTypeField, which should not be verbose if isinstance(lookup_expr, six.string_types): verbose_expression += [verbose_lookup_expr(lookup_expr)] verbose_expression = [force_text(part) for part in verbose_expression if part] verbose_expression = capfirst(' '.join(verbose_expression)) return verbose_expression def raw_validation(error): """ Deconstruct a django.forms.ValidationError into a primitive structure eg, plain dicts and lists. """ if isinstance(error, ValidationError): if hasattr(error, 'error_dict'): error = error.error_dict elif not hasattr(error, 'message'): error = error.error_list else: error = error.message if isinstance(error, dict): return {key: raw_validation(value) for key, value in error.items()} elif isinstance(error, list): return [raw_validation(value) for value in error] else: return error django-filter-1.1.0/django_filters/views.py0000644000076500000240000000724113172067725021542 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django.core.exceptions import ImproperlyConfigured from django.views.generic import View from django.views.generic.list import ( MultipleObjectMixin, MultipleObjectTemplateResponseMixin ) from .constants import ALL_FIELDS from .filterset import filterset_factory class FilterMixin(object): """ A mixin that provides a way to show and handle a FilterSet in a request. """ filterset_class = None filter_fields = ALL_FIELDS def get_filterset_class(self): """ Returns the filterset class to use in this view """ if self.filterset_class: return self.filterset_class elif self.model: return filterset_factory(model=self.model, fields=self.filter_fields) else: msg = "'%s' must define 'filterset_class' or 'model'" raise ImproperlyConfigured(msg % self.__class__.__name__) def get_filterset(self, filterset_class): """ Returns an instance of the filterset to be used in this view. """ kwargs = self.get_filterset_kwargs(filterset_class) return filterset_class(**kwargs) def get_filterset_kwargs(self, filterset_class): """ Returns the keyword arguments for instanciating the filterset. """ kwargs = { 'data': self.request.GET or None, 'request': self.request, } try: kwargs.update({ 'queryset': self.get_queryset(), }) except ImproperlyConfigured: # ignore the error here if the filterset has a model defined # to acquire a queryset from if filterset_class._meta.model is None: msg = ("'%s' does not define a 'model' and the view '%s' does " "not return a valid queryset from 'get_queryset'. You " "must fix one of them.") args = (filterset_class.__name__, self.__class__.__name__) raise ImproperlyConfigured(msg % args) return kwargs class BaseFilterView(FilterMixin, MultipleObjectMixin, View): def get(self, request, *args, **kwargs): filterset_class = self.get_filterset_class() self.filterset = self.get_filterset(filterset_class) self.object_list = self.filterset.qs context = self.get_context_data(filter=self.filterset, object_list=self.object_list) return self.render_to_response(context) class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView): """ Render some list of objects with filter, set by `self.model` or `self.queryset`. `self.queryset` can actually be any iterable of items, not just a queryset. """ template_name_suffix = '_filter' def object_filter(request, model=None, queryset=None, template_name=None, extra_context=None, context_processors=None, filter_class=None): class ECFilterView(FilterView): """Handle the extra_context from the functional object_filter view""" def get_context_data(self, **kwargs): context = super(ECFilterView, self).get_context_data(**kwargs) extra_context = self.kwargs.get('extra_context') or {} for k, v in extra_context.items(): if callable(v): v = v() context[k] = v return context kwargs = dict(model=model, queryset=queryset, template_name=template_name, filterset_class=filter_class) view = ECFilterView.as_view(**kwargs) return view(request, extra_context=extra_context) django-filter-1.1.0/django_filters/widgets.py0000644000076500000240000002201713172067725022051 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from collections import Iterable from itertools import chain from re import search, sub import django from django import forms from django.db.models.fields import BLANK_CHOICE_DASH from django.forms.utils import flatatt from django.utils.datastructures import MultiValueDict from django.utils.encoding import force_text from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.utils.six import string_types from django.utils.translation import ugettext as _ from .compat import format_value class LinkWidget(forms.Widget): def __init__(self, attrs=None, choices=()): super(LinkWidget, self).__init__(attrs) self.choices = choices def value_from_datadict(self, data, files, name): value = super(LinkWidget, self).value_from_datadict(data, files, name) self.data = data return value def render(self, name, value, attrs=None, choices=()): if not hasattr(self, 'data'): self.data = {} if value is None: value = '' if django.VERSION < (1, 11): final_attrs = self.build_attrs(attrs) else: final_attrs = self.build_attrs(self.attrs, extra_attrs=attrs) output = ['' % flatatt(final_attrs)] options = self.render_options(choices, [value], name) if options: output.append(options) output.append('') return mark_safe('\n'.join(output)) def render_options(self, choices, selected_choices, name): selected_choices = set(force_text(v) for v in selected_choices) output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): for option in option_label: output.append( self.render_option(name, selected_choices, *option)) else: output.append( self.render_option(name, selected_choices, option_value, option_label)) return '\n'.join(output) def render_option(self, name, selected_choices, option_value, option_label): option_value = force_text(option_value) if option_label == BLANK_CHOICE_DASH[0][1]: option_label = _("All") data = self.data.copy() data[name] = option_value selected = data == self.data or option_value in selected_choices try: url = data.urlencode() except AttributeError: url = urlencode(data) return self.option_string() % { 'attrs': selected and ' class="selected"' or '', 'query_string': url, 'label': force_text(option_label) } def option_string(self): return '
  • %(label)s
  • ' class SuffixedMultiWidget(forms.MultiWidget): """ A MultiWidget that allows users to provide custom suffixes instead of indexes. - Suffixes must be unique. - There must be the same number of suffixes as fields. """ suffixes = [] def __init__(self, *args, **kwargs): super(SuffixedMultiWidget, self).__init__(*args, **kwargs) assert len(self.widgets) == len(self.suffixes) assert len(self.suffixes) == len(set(self.suffixes)) def suffixed(self, name, suffix): return '_'.join([name, suffix]) if suffix else name def get_context(self, name, value, attrs): context = super(SuffixedMultiWidget, self).get_context(name, value, attrs) for subcontext, suffix in zip(context['widget']['subwidgets'], self.suffixes): subcontext['name'] = self.suffixed(name, suffix) return context def value_from_datadict(self, data, files, name): return [ widget.value_from_datadict(data, files, self.suffixed(name, suffix)) for widget, suffix in zip(self.widgets, self.suffixes) ] def value_omitted_from_data(self, data, files, name): return all( widget.value_omitted_from_data(data, files, self.suffixed(name, suffix)) for widget, suffix in zip(self.widgets, self.suffixes) ) # Django < 1.11 compat def format_output(self, rendered_widgets): rendered_widgets = [ self.replace_name(output, i) for i, output in enumerate(rendered_widgets) ] return '\n'.join(rendered_widgets) def replace_name(self, output, index): result = search(r'name="(?P.*)_%d"' % index, output) name = result.group('name') name = self.suffixed(name, self.suffixes[index]) name = 'name="%s"' % name return sub(r'name=".*_%d"' % index, name, output) def decompress(self, value): if value is None: return [None, None] return value class RangeWidget(forms.MultiWidget): template_name = 'django_filters/widgets/multiwidget.html' def __init__(self, attrs=None): widgets = (forms.TextInput, forms.TextInput) super(RangeWidget, self).__init__(widgets, attrs) def format_output(self, rendered_widgets): # Method was removed in Django 1.11. return '-'.join(rendered_widgets) def decompress(self, value): if value: return [value.start, value.stop] return [None, None] class LookupTypeWidget(forms.MultiWidget): def decompress(self, value): if value is None: return [None, None] return value class BooleanWidget(forms.Select): """Convert true/false values into the internal Python True/False. This can be used for AJAX queries that pass true/false from JavaScript's internal types through. """ def __init__(self, attrs=None): choices = (('', _('Unknown')), ('true', _('Yes')), ('false', _('No'))) super(BooleanWidget, self).__init__(attrs, choices) def render(self, name, value, attrs=None): try: value = { True: 'true', False: 'false', '1': 'true', '0': 'false' }[value] except KeyError: value = '' return super(BooleanWidget, self).render(name, value, attrs) def value_from_datadict(self, data, files, name): value = data.get(name, None) if isinstance(value, string_types): value = value.lower() return { '1': True, '0': False, 'true': True, 'false': False, True: True, False: False, }.get(value, None) class BaseCSVWidget(forms.Widget): def _isiterable(self, value): return isinstance(value, Iterable) and not isinstance(value, string_types) def value_from_datadict(self, data, files, name): value = super(BaseCSVWidget, self).value_from_datadict(data, files, name) if value is not None: if value == '': # empty value should parse as an empty list return [] return value.split(',') return None def render(self, name, value, attrs=None): if not self._isiterable(value): value = [value] if len(value) <= 1: # delegate to main widget (Select, etc...) if not multiple values value = value[0] if value else '' return super(BaseCSVWidget, self).render(name, value, attrs) # if we have multiple values, we need to force render as a text input # (otherwise, the additional values are lost) surrogate = forms.TextInput() value = [force_text(format_value(surrogate, v)) for v in value] value = ','.join(list(value)) return surrogate.render(name, value, attrs) class CSVWidget(BaseCSVWidget, forms.TextInput): pass class QueryArrayWidget(BaseCSVWidget, forms.TextInput): """ Enables request query array notation that might be consumed by MultipleChoiceFilter 1. Values can be provided as csv string: ?foo=bar,baz 2. Values can be provided as query array: ?foo[]=bar&foo[]=baz 3. Values can be provided as query array: ?foo=bar&foo=baz Note: Duplicate and empty values are skipped from results """ def value_from_datadict(self, data, files, name): if not isinstance(data, MultiValueDict): for key, value in data.items(): # treat value as csv string: ?foo=1,2 if isinstance(value, string_types): data[key] = [x.strip() for x in value.rstrip(',').split(',') if x] data = MultiValueDict(data) values_list = data.getlist(name, data.getlist('%s[]' % name)) or [] # apparently its an array, so no need to process it's values as csv # ?foo=1&foo=2 -> data.getlist(foo) -> foo = [1, 2] # ?foo[]=1&foo[]=2 -> data.getlist(foo[]) -> foo = [1, 2] if len(values_list) > 0: ret = [x for x in values_list if x] else: ret = [] return list(set(ret)) django-filter-1.1.0/docs/0000755000076500000240000000000013172070045015752 5ustar carltonstaff00000000000000django-filter-1.1.0/docs/.DS_Store0000644000076500000240000001400412435162061017435 0ustar carltonstaff00000000000000Bud1ldvSrnlong_buildvSrnlong  @ @ @ @ EDSDB ` @ @ @django-filter-1.1.0/docs/assets/0000755000076500000240000000000013172070045017254 5ustar carltonstaff00000000000000django-filter-1.1.0/docs/assets/form.png0000644000076500000240000003255613013341253020734 0ustar carltonstaff00000000000000PNG  IHDRS iCCPICC ProfileHTLzBzG: lH(1B#+ ]QpQ!6 (bo;|s;s aRi`o7zdT4 y@ AAw}׿ƥrrR>DG(JCii.DX^iN;g#<%@AtNC#l.3v\36IIY5G6'yJ< >ˌTa2;\d= D>HD֬:iϝ?iq h9azgsJ@h`Tg + (pk& v0'@hep tp<` Q * i@1d 1 g򇂡((JZ m b :@@g z @[ ɰ< āPx9\x\ WF2 ߃W H(JebQhT냍&b` Vl/v;q8'\ K.np$ 91Em0~ C%8 \BpBE"LeD'b(1XJ#ߑH$-=i OH*%$]' >Fdw2|J~H~GP(hJerI*e&ŒJm*j-Z +͔^!-]"}J AFO]-^\L̄,UB6P6EP 9W.WA*MurG!yQ@|BBB~GcђiE}E5EbV:ۊ(*)++SLWTNRޡܤTbD%CeJ 8 4,x Q=ڥ:&T۫vEmLKUYKKIOү5U5}4Ś45'´r굞jڻ۴u4tt<%2ty{t;t?EmkWgg?164k5d&31lxFFac[c>^Ҥϔl4M7505^0zᎅ ۘ'1l!gkcbȒcYny׊beꍵu~6T-6m6llEuv:v1vv} yFqcfg[4?M9,_ȢA'-'!~gsA~MKsWmWk0Ӑ<|f&r;}{#ߣS3̳󙗖WW׸V>êaG +{o/o |v Ztn vIВ%/-wPCV Z8 L.,&cGDqDuQ*Qh\txtURϥ-YroPYJ앧b011bٕXVlE8ǝs+w/IpJؙ0s2D&M%G$קSbR IWe V^=.UBSKl A<^)# 595 230 niP(IDATx Uq5$rD.%nF ejJ^3%Ťh)j* 5jHPInK99>ϱαg^ǾiӦ_&L;v0ҥK{M H 0C=d+V^fXDLTΝUVtʖ-{ʧk.F} D_ TÆ ]ʩrѢEn?Tvu3]wevq={۷o_^ɓmyfShPs3' @@ S ÇwWeFiSr RZn@AaW^֥KSb;vJ= +V]Un:?~`={<6UYOMJڵ]Zt{OWeزeRڶm[j[0}g=æ|L{I6lIWN8f͚ u>]j.,z߃ך5k O<;ԃ  q"a*3㧟~&L`UVAGs_bC qkU)d [ouzmy7ey饗,%%?֨Q#ׯ;,&Jq-),*D⋮ $=q.PÇU}ݦnjrW{M6Bmzs)o֦MSշo_0` m@8re\RzI5j_vuYʕ\6 R/_>MRJc=fgu.̤[lsݮzԋN])j nJ .:{HFazL_ 9– ..4[ok6xlѢ ZjW=T?x:@@ mz=BZ<1c?w?')og, mwХ=ФR;xn|}')} >^^V-) / A c郔iNJfkV%  osRR%?MάhM/7TY.#w:zhȪ QSLqՋYQ(bgBDuԩ}i%MϨx}~i/g3f̰7|3ͼ @L d0hHTB&ku[ I =D:r^x;%72ۿI&n?`~(o(k'P> ĺ@ڸqk? 6YMU2sOVe0+s-\_sB =;ygܟթSK/4d .# +.Lݻjҵ#+UhQ‚{]SHEV:ά8\D&ڧ/EIV &ޛ7o[[NBWtMf8@3X ׅ)WHs䁟T_^de"w_4YsvrJt4MLtm@ȶ 歃 'KCMdd^VڊfDMP~E=wrnmWK_4_{nR]^Fk@bQ ׅ)~hMWz"tK /o>+޶G c?{PJyp]=<^;S+m۶[˷*(Dlev4[AS@'\4GkUqe]h=*tW[dVY |h̊&kTzFE̟?i䌪doiF4JW@ϯɛvy @, 0%\(EnQ]0\!+_}TZ3= [U Lj_N-4WF=C]|\Iۤ̚Yfnu2{) L}&Mrhy @, g_7ӧ[GJJj+Jaҽ}QYwХ{f]VoڴWae…GJ1zʷhWHLLt$$sx G ^QҍlBנA+\)pjbŋ={zfQ!tܹ"w-*o}G.4*kHV7F20+I@$\u=L6*/<޺߿owof͚K]*[l9daJâ0رcݽ {"{n ᜏn`c'5%EA@ =~ @iիv5\H]ݦ[D d` OkeJ<ݼt! ]CG{ɎaڵB5'/-ޙ}si@,pF ȱ! @| ߵn=  S@@S+  @7 D @]@@@@ Tx   "cW@@0o@@0" )~  @@ L@@" LEǮ  a  a*}?k~ d6muK,U=[gճ{ʗ/oƍ?NlܹVJ8pcڡCSN6.={Zڵ3fؔ)S /ۅG@aLR3guѮz[`=vQۺu 4ͳZjٯjC7|5!ĥKs=g'N@|rNJJ8pK1bo֬Y֯_?pwa77`]vQ]{]v^{J*evrgϞO>cQ}w.L\m_jMĞz)SGW\a_|<  QS :u… ]Xƍ,Y?#OsGiڵjҤIkRY{ܻw{l׮~S]Ϙ6;vmQhRPɓmR8RٿmذT/^f} TZj A@;L.\Pނ/(Q=GI 7ķl2{wuAE4TTp㕊+zO<.]ڽVhS&O0b)nF7oyRo,@5le˖uEujCKd4)nV볼6] Cj*  ķ@&@@ N\. ;  a*L8vC@$@w D @]@@@@ Tx   "cW@@ ̟?=@# fTSPMM@d:QB{ a   +@%  Z0چ-  aʗ   @hTh  )_"*  Sm؂  |  La   +@%  Z0چ-  aʗ   @hTh  )_"*  Sm؂  |  La   +@%  Z0چ-  aʗ   @hTh  )_"*  Sm؂  $Ȥ3&@@ J&g DGa* ĉa*NhN@#@+" @4@@ :* ĉa*NhN@#@+" @4@@ :* ĉa*NhN@#@+" @4@@ :* ĉa*NhN@#v:|5l0;#>|QF;u^oܸ1[>oĈ_=d{rnݺu؀ !y5Xb4UZ4E&M@V^j2M;ǏO:Y~ '-a{VjՓtsOZ D6vXKLLxʕ+gCUVYo߾VdI6/_>{1+_]f̘]IRݯ^ySW\ag}v&;O8쮻Jgyƚ6mjeʔqu>k׮K_z7nl>-Z駟oa6l… [G ڑ#G^S޽{yqֱcG۾} 2ĵSO94 @8>䓓BH.],99ٖ.]jk֬q!AAeŊO[su/4 f͚eҥKʕ+m̙.` 6̶nܶm/_[ B 0 L5˗𫯾jS[:u_VsB7엒ڝ3gժU:d c TTxTm㏻SKf͚6a0`s9.DN<}OuҐM yڱc}.5j+R+{@J(\`t]ZBW7tգzt>s G]ueU)  {WRyWsH{^ҖNHXyYOY|[ +PD]\OK&h- ?[*3/@@ re9>@85~F@  "cW@@0o@@0" )~  @@ L@@"v2Ϗc@4 i|Z70|G| 9.@'J诀a6lA@|SDT@@B B۰@ LQ@-@ m@@W0KD@@ a* [@@_”/@@6lA@|SDT@@B B۰@ LQ@-@ m@@W0KD@@ a* [@@_”/@@6lA@|SDT@@B B۰@ LQ@-@ m@@W0KD@@ a* [@@_$T?~&[ل ľ@@ @@0WZE@8 Li" @tSqU@T|ќ& DG0WZE@8 Li" @tSqU@T|ќ& DG0WZE@8v2v'狷ڴE[lՖ}qBvi_]Z*k8  @ };-;ZshB^KJJE;1ݑ'%%W{6rgۅ  #a󍜲yu@AR r  @]HN.@ԁC)q#g*P@@ ~SCę" Sm؂  |  La   +@%  Z0چ-  @Dm= gg% G2&@@ 0U`Z–|Οh'_vjUSz^[7+%{5Zdw$[s١q-̊u@@7:̗`"~nՅvc۾VY|i{SvZ`<`n1g;x/6yuoyU ]mٺ5Zܐu6.Kʓ`5=Vm=`vfU(b)G[@UQKR?'  @Fgjw ĨWk=-*!y9SӗjG!&1O;Lr yrv%l56tjZjnد7+Il=׽~tCx0<Ol7y#  RL(~nlXήQ,j^/7@UV ‘]?{kPoYm.ofw|͗ .ꩺ]GnXls@X mȸN1\n#k6: r)?K׃AJ;lڙTj+kf<\U:xD@NIആ kI=@]7y/緖aJn ~'0\4&ϛǏOF  a 0U`}ue+'\[Ṽyz%P .,N'М2:[w%W9  u$eÆn>Rլd`NM4WoUbLsLqW,Q)k> Oi6h{}9RnQgP@@?:@>X,x7JKh& \I]lF-h{KÅ>)ЫP޼2 9/0\#yD@2H% +;͊-aYyH$?ms-zdvt,xߜ|o^ֿeN QL ~{33 R^;<" 8sy" a*'L@T| a*'L@T| a*'L@TIU1D!  ď@aQ?RY8Syȅ #v޺%?bG*HAr  @};m۝l/jmU[ŏZ3=\kYUJ/n+/@@ " S ù! Y{/+S@uTÜ DU0U^G@X L7! @TSQq@uTÜ DU0U^G@X L7! @TSQq@uTÜ DU0U^G@X L7! @TSQq@uTÜ DU0U^G@X L7! @TcIENDB`django-filter-1.1.0/docs/conf.py0000644000076500000240000002001213172067725017257 0ustar carltonstaff00000000000000# -*- coding: utf-8 -*- # # django-filter documentation build configuration file, created by # sphinx-quickstart on Mon Sep 17 11:25:20 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'django-filter' copyright = u'2013, Alex Gaynor and others.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.1.0' # The full version, including alpha/beta/rc tags. release = '1.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'django-filterdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'django-filter.tex', u'django-filter Documentation', u'Alex Gaynor and others.', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'django-filter', u'django-filter Documentation', [u'Alex Gaynor and others.'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'django-filter', u'django-filter Documentation', u'Alex Gaynor and others.', 'django-filter', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # see: # https://github.com/snide/sphinx_rtd_theme#using-this-theme-locally-then-building-on-read-the-docs on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # only import and set the theme if we're building docs locally if not on_rtd: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] django-filter-1.1.0/docs/dev/0000755000076500000240000000000013172070045016530 5ustar carltonstaff00000000000000django-filter-1.1.0/docs/dev/tests.txt0000644000076500000240000000364113172067725020452 0ustar carltonstaff00000000000000====================== Running the Test Suite ====================== The easiest way to run the django-filter tests is to check out the source code and create a virtualenv where you can install the test dependencies. Django-filter uses a custom test runner to configure the environment, so a wrapper script is available to set up and run the test suite. .. note:: The following assumes you have `virtualenv`__ and `git`__ installed. __ https://virtualenv.pypa.io/en/stable/ __ https://git-scm.com Clone the repository -------------------- Get the source code using the following command: .. code-block:: bash $ git clone https://github.com/carltongibson/django-filter.git Switch to the django-filter directory: .. code-block:: bash $ cd django-filter Set up the virtualenv --------------------- Create a new virtualenv to run the test suite in: .. code-block:: bash $ virtualenv venv Then activate the virtualenv and install the test requirements: .. code-block:: bash $ source venv/bin/activate $ pip install -r requirements/test.txt Execute the test runner ----------------------- Run the tests with the runner script: .. code-block:: bash $ python runtests.py Test all supported versions --------------------------- You can also use the excellent tox testing tool to run the tests against all supported versions of Python and Django. Install tox, and then simply run: .. code-block:: bash $ pip install tox $ tox Housekeeping ------------ The ``isort`` utility is used to maintain module imports. You can either test the module imports with the appropriate `tox` env, or with `isort` directly. .. code-block:: bash $ pip install tox $ tox -e isort # or $ pip install isort $ isort --check-only --recursive django_filters tests To sort the imports, simply remove the ``--check-only`` option. .. code-block:: bash $ isort --recursive django_filters tests django-filter-1.1.0/docs/guide/0000755000076500000240000000000013172070045017047 5ustar carltonstaff00000000000000django-filter-1.1.0/docs/guide/install.txt0000644000076500000240000000121213172067725021265 0ustar carltonstaff00000000000000============ Installation ============ Django-filter can be installed from PyPI with tools like ``pip``: .. code-block:: bash $ pip install django-filter Then add ``'django_filters'`` to your ``INSTALLED_APPS``. .. code-block:: python INSTALLED_APPS = [ ... 'django_filters', ] Requirements ------------ Django-filter is tested against all supported versions of Python and `Django`__, as well as the latest version of Django REST Framework (`DRF`__). __ https://www.djangoproject.com/download/ __ http://www.django-rest-framework.org/ * **Python**: 2.7, 3.4, 3.5, 3.6 * **Django**: 1.10, 1.11 * **DRF**: 3.7 django-filter-1.1.0/docs/guide/migration.txt0000644000076500000240000001712513172067725021622 0ustar carltonstaff00000000000000=============== Migration Guide =============== ---------------- Migrating to 2.0 ---------------- Removal of the ``Meta.together`` option --------------------------------------- The ``Meta.together`` has been deprecated in favor of userland implementations that override the ``clean`` method of the ``Meta.form`` class. An example will be provided in a "recipes" secion in future docs. ``Filter.name`` renamed to ``Filter.field_name`` ------------------------------------------------ The filter ``name`` has been renamed to ``field_name`` as a way to disambiguate the filter's attribute name on its FilterSet class from the ``field_name`` used for filtering purposes. FilterSet strictness has been removed ------------------------------------- Strictness handling has been removed from the ``FilterSet`` and added to the view layer. As a result, the ``FILTERS_STRICTNESS`` setting, ``Meta.strict`` option, and ``strict`` argument for the ``FilterSet`` initializer have all been removed. To alter strictness behavior, the appropriate view code should be overridden. More details will be provided in future docs. ---------------- Migrating to 1.0 ---------------- The 1.0 release of django-filter introduces several API changes and refinements that break forwards compatibility. Below is a list of deprecations and instructions on how to migrate to the 1.0 release. A forwards-compatible 0.15 release has also been created to help with migration. It is compatible with both the existing and new APIs and will raise warnings for deprecated behavior. Enabling warnings ----------------- To view the deprecations, you may need to enable warnings within python. This can be achieved with either the ``-W`` `flag`__, or with ``PYTHONWARNINGS`` `environment variable`__. For example, you could run your test suite like so: .. code-block:: bash $ python -W once manage.py test The above would print all warnings once when they first occur. This is useful to know what violations exist in your code (or occasionally in third party code). However, it only prints the last line of the stack trace. You can use the following to raise the full exception instead: .. code-block:: bash $ python -W error manage.py test __ https://docs.python.org/3.6/using/cmdline.html#cmdoption-W __ https://docs.python.org/3.6/using/cmdline.html#envvar-PYTHONWARNINGS MethodFilter and Filter.action replaced by Filter.method -------------------------------------------------------- Details: https://github.com/carltongibson/django-filter/pull/382 The functionality of ``MethodFilter`` and ``Filter.action`` has been merged together and replaced by the ``Filter.method`` parameter. The ``method`` parameter takes either a callable or the name of a ``FilterSet`` method. The signature now takes an additional ``name`` argument that is the name of the model field to be filtered on. Since ``method`` is now a parameter of all filters, inputs are validated and cleaned by its ``field_class``. The function will receive the cleaned value instead of the raw value. .. code-block:: python # 0.x class UserFilter(FilterSet): last_login = filters.MethodFilter() def filter_last_login(self, qs, value): # try to convert value to datetime, which may fail. if value and looks_like_a_date(value): value = datetime(value) return qs.filter(last_login=value}) # 1.0 class UserFilter(FilterSet): last_login = filters.CharFilter(method='filter_last_login') def filter_last_login(self, qs, name, value): return qs.filter(**{name: value}) QuerySet methods are no longer proxied -------------------------------------- Details: https://github.com/carltongibson/django-filter/pull/440 The ``__iter__()``, ``__len__()``, ``__getitem__()``, ``count()`` methods are no longer proxied from the queryset. To fix this, call the methods on the ``.qs`` property itself. .. code-block:: python f = UserFilter(request.GET, queryset=User.objects.all()) # 0.x for obj in f: ... # 1.0 for obj in f.qs: ... Filters no longer autogenerated when Meta.fields is not specified ----------------------------------------------------------------- Details: https://github.com/carltongibson/django-filter/pull/450 FilterSets had an undocumented behavior of autogenerating filters for all model fields when either ``Meta.fields`` was not specified or when set to ``None``. This can lead to potentially unsafe data or schema exposure and has been deprecated in favor of explicitly setting ``Meta.fields`` to the ``'__all__'`` special value. You may also blacklist fields by setting the ``Meta.exclude`` attribute. .. code-block:: python class UserFilter(FilterSet): class Meta: model = User fields = '__all__' # or class UserFilter(FilterSet): class Meta: model = User exclude = ['password'] Move FilterSet options to Meta class ------------------------------------ Details: https://github.com/carltongibson/django-filter/issues/430 Several ``FilterSet`` options have been moved to the ``Meta`` class to prevent potential conflicts with declared filter names. This includes: * ``filter_overrides`` * ``strict`` * ``order_by_field`` .. code-block:: python # 0.x class UserFilter(FilterSet): filter_overrides = {} strict = STRICTNESS.RAISE_VALIDATION_ERROR order_by_field = 'order' ... # 1.0 class UserFilter(FilterSet): ... class Meta: filter_overrides = {} strict = STRICTNESS.RAISE_VALIDATION_ERROR order_by_field = 'order' FilterSet ordering replaced by OrderingFilter --------------------------------------------- Details: https://github.com/carltongibson/django-filter/pull/472 The FilterSet ordering options and methods have been deprecated and replaced by :ref:`OrderingFilter `. Deprecated options include: * ``Meta.order_by`` * ``Meta.order_by_field`` These options retain backwards compatibility with the following caveats: * ``order_by`` asserts that ``Meta.fields`` is not using the dict syntax. This previously was undefined behavior, however the migration code is unable to support it. * Prior, if no ordering was specified in the request, the FilterSet implicitly filtered by the first param in the ``order_by`` option. This behavior cannot be easily emulated but can be fixed by ensuring that the passed in queryset explicitly calls ``.order_by()``. .. code-block:: python filterset = MyFilterSet(queryset=MyModel.objects.order_by('field')) The following methods are deprecated and will raise an assertion if present on the FilterSet: * ``.get_order_by()`` * ``.get_ordering_field()`` To fix this, simply remove the methods from your class. You can subclass ``OrderingFilter`` to migrate any custom logic. Deprecated ``FILTERS_HELP_TEXT_FILTER`` and ``FILTERS_HELP_TEXT_EXCLUDE`` ------------------------------------------------------------------------- Details: https://github.com/carltongibson/django-filter/pull/437 Generated filter labels in 1.0 will be more descriptive, including humanized text about the lookup being performed and if the filter is an exclusion filter. These settings will no longer have an effect and will be removed in the 1.0 release. DRF filter backend raises ``TemplateDoesNotExist`` exception ------------------------------------------------------------ Templates are now provided by django-filter. If you are receiving this error, you may need to add ``'django_filters'`` to your ``INSTALLED_APPS`` setting. Alternatively, you could provide your own templates. django-filter-1.1.0/docs/guide/rest_framework.txt0000644000076500000240000001424513172067725022663 0ustar carltonstaff00000000000000==================== Integration with DRF ==================== Integration with `Django Rest Framework`__ is provided through a DRF-specific ``FilterSet`` and a `filter backend`__. These may be found in the ``rest_framework`` sub-package. __ http://www.django-rest-framework.org/ __ http://www.django-rest-framework.org/api-guide/filtering/ Quickstart ---------- Using the new ``FilterSet`` simply requires changing the import path. Instead of importing from ``django_filters``, import from the ``rest_framework`` sub-package. .. code-block:: python from django_filters import rest_framework as filters class ProductFilter(filters.FilterSet): ... Your view class will also need to add ``DjangoFilterBackend`` to the ``filter_backends``. .. code-block:: python from django_filters import rest_framework as filters class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = (filters.DjangoFilterBackend,) filter_fields = ('category', 'in_stock') If you want to use the django-filter backend by default, add it to the ``DEFAULT_FILTER_BACKENDS`` setting. .. code-block:: python # settings.py INSTALLED_APPS = [ ... 'rest_framework', 'django_filters', ] REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ( 'django_filters.rest_framework.DjangoFilterBackend', ... ), } Adding a FilterSet with ``filter_class`` ---------------------------------------- To enable filtering with a ``FilterSet``, add it to the ``filter_class`` parameter on your view class. .. code-block:: python from rest_framework import generics from django_filters import rest_framework as filters from myapp import Product class ProductFilter(filters.FilterSet): min_price = filters.NumberFilter(name="price", lookup_expr='gte') max_price = filters.NumberFilter(name="price", lookup_expr='lte') class Meta: model = Product fields = ['category', 'in_stock', 'min_price', 'max_price'] class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = (filters.DjangoFilterBackend,) filter_class = ProductFilter Using the ``filter_fields`` shortcut ------------------------------------ You may bypass creating a ``FilterSet`` by instead adding ``filter_fields`` to your view class. This is equivalent to creating a FilterSet with just :ref:`Meta.fields `. .. code-block:: python from rest_framework import generics from django_filters import rest_framework as filters from myapp import Product class ProductList(generics.ListAPIView): queryset = Product.objects.all() filter_backends = (filters.DjangoFilterBackend,) filter_fields = ('category', 'in_stock') # Equivalent FilterSet: class ProductFilter(filters.FilterSet): class Meta: model = Product fields = ('category', 'in_stock') Schema Generation with Core API ------------------------------- The backend class integrates with DRF's schema generation by implementing ``get_schema_fields()``. This is automatically enabled when Core API is installed. Schema generation usually functions seamlessly, however the implementation does expect to invoke the view's ``get_queryset()`` method. There is a caveat in that views are artificially constructed during schema generation, so the ``args`` and ``kwargs`` attributes will be empty. If you depend on arguments parsed from the URL, you will need to handle their absence in ``get_queryset()``. For example, your get queryset method may look like this: .. code-block:: python class IssueViewSet(views.ModelViewSet): queryset = models.Issue.objects.all() def get_project(self): return models.Project.objects.get(pk=self.kwargs['project_id']) def get_queryset(self): project = self.get_project() return self.queryset \ .filter(project=project) \ .filter(author=self.request.user) This could be rewritten like so: .. code-block:: python class IssueViewSet(views.ModelViewSet): queryset = models.Issue.objects.all() def get_project(self): try: return models.Project.objects.get(pk=self.kwargs['project_id']) except models.Project.DoesNotExist: return None def get_queryset(self): project = self.get_project() if project is None: return self.queryset.none() return self.queryset \ .filter(project=project) \ .filter(author=self.request.user) Or more simply as: .. code-block:: python class IssueViewSet(views.ModelViewSet): queryset = models.Issue.objects.all() def get_queryset(self): # project_id may be None return self.queryset \ .filter(project_id=self.kwargs.get('project_id')) \ .filter(author=self.request.user) Crispy Forms ------------ If you are using DRF's browsable API or admin API you may also want to install ``django-crispy-forms``, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML. Note that this isn't actively supported, although pull requests for bug fixes are welcome. .. code-block:: bash pip install django-crispy-forms With crispy forms installed and added to Django's ``INSTALLED_APPS``, the browsable API will present a filtering control for ``DjangoFilterBackend``, like so: .. image:: ../assets/form.png Additional ``FilterSet`` Features --------------------------------- The following features are specific to the rest framework FilterSet: - ``BooleanFilter``'s use the API-friendly ``BooleanWidget``, which accepts lowercase ``true``/``false``. - Filter generation uses ``IsoDateTimeFilter`` for datetime model fields. - Raised ``ValidationError``'s are reraised as their DRF equivalent. This behavior is useful when setting FilterSet strictness to ``STRICTNESS.RAISE_VALIDATION_ERROR``. django-filter-1.1.0/docs/guide/tips.txt0000644000076500000240000002070213107513130020563 0ustar carltonstaff00000000000000================== Tips and Solutions ================== Common problems for declared filters ------------------------------------ Below are some of the common problem that occur when declaring filters. It is recommended that you read this as it provides a more complete understanding of how filters work. Filter ``name`` and ``lookup_expr`` not configured ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While ``name`` and ``lookup_expr`` are optional, it is recommended that you specify them. By default, if ``name`` is not specified, the filter's name on the filterset class will be used. Additionally, ``lookup_expr`` defaults to ``exact``. The following is an example of a misconfigured price filter: .. code-block:: python class ProductFilter(django_filters.FilterSet): price__gt = django_filters.NumberFilter() The filter instance will have a field name of ``price__gt`` and an ``exact`` lookup type. Under the hood, this will incorrectly be resolved as: .. code-block:: python Produce.objects.filter(price__gt__exact=value) The above will most likely generate a ``FieldError``. The correct configuration would be: .. code-block:: python class ProductFilter(django_filters.FilterSet): price__gt = django_filters.NumberFilter(name='price', lookup_expr='gt') Missing ``lookup_expr`` for text search filters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's quite common to forget to set the lookup expression for :code:`CharField` and :code:`TextField` and wonder why a search for "foo" does not return results for "foobar". This is because the default lookup type is ``exact``, but you probably want to perform an ``icontains`` lookup. Filter and lookup expression mismatch (in, range, isnull) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's not always appropriate to directly match a filter to its model field's type, as some lookups expect different types of values. This is a commonly found issue with ``in``, ``range``, and ``isnull`` lookups. Let's look at the following product model: .. code-block:: python class Product(models.Model): category = models.ForeignKey(Category, null=True) Given that ``category`` is optional, it's reasonable to want to enable a search for uncategorized products. The following is an incorrectly configured ``isnull`` filter: .. code-block:: python class ProductFilter(django_filters.FilterSet): uncategorized = django_filters.NumberFilter(name='category', lookup_expr='isnull') So what's the issue? While the underlying column type for ``category`` is an integer, ``isnull`` lookups expect a boolean value. A ``NumberFilter`` however only validates numbers. Filters are not `'expression aware'` and won't change behavior based on their ``lookup_expr``. You should use filters that match the data type of the lookup expression `instead` of the data type underlying the model field. The following would correctly allow you to search for both uncategorized products and products for a set of categories: .. code-block:: python class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter): pass class ProductFilter(django_filters.FilterSet): categories = NumberInFilter(name='category', lookup_expr='in') uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull') More info on constructing ``in`` and ``range`` csv :ref:`filters `. Filtering by empty values ------------------------- There are a number of cases where you may need to filter by empty or null values. The following are some common solutions to these problems: Filtering by null values ~~~~~~~~~~~~~~~~~~~~~~~~ As explained in the above "Filter and lookup expression mismatch" section, a common problem is how to correctly filter by null values on a field. Solution 1: Using a ``BooleanFilter`` with ``isnull`` """"""""""""""""""""""""""""""""""""""""""""""""""""" Using ``BooleanFilter`` with an ``isnull`` lookup is a builtin solution used by the FilterSet's automatic filter generation. To do this manually, simply add: .. code-block:: python class ProductFilter(django_filters.FilterSet): uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull') .. note:: Remember that the filter class is validating the input value. The underlying type of the mode field is not relevant here. You may also reverse the logic with the ``exclude`` parameter. .. code-block:: python class ProductFilter(django_filters.FilterSet): has_category = django_filters.BooleanFilter(name='category', lookup_expr='isnull', exclude=True) Solution 2: Using ``ChoiceFilter``'s null choice """""""""""""""""""""""""""""""""""""""""""""""" If you're using a ChoiceFilter, you may also filter by null values by enabling the ``null_label`` parameter. More details in the ``ChoiceFilter`` reference :ref:`docs `. .. code-block:: python class ProductFilter(django_filters.FilterSet): category = django_filters.ModelChoiceFilter( name='category', lookup_expr='isnull', null_label='Uncategorized', queryset=Category.objects.all(), ) Solution 3: Combining fields w/ ``MultiValueField`` """"""""""""""""""""""""""""""""""""""""""""""""""" An alternative approach is to use Django's ``MultiValueField`` to manually add in a ``BooleanField`` to handle null values. Proof of concept: https://github.com/carltongibson/django-filter/issues/446 Filtering by an empty string ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's not currently possible to filter by an empty string, since empty values are interpreted as a skipped filter. GET http://localhost/api/my-model?myfield= Solution 1: Magic values """""""""""""""""""""""" You can override the ``filter()`` method of a filter class to specifically check for magic values. This is similar to the ``ChoiceFilter``'s null value handling. GET http://localhost/api/my-model?myfield=EMPTY .. code-block:: python class MyCharFilter(filters.CharFilter): empty_value = 'EMPTY' def filter(self, qs, value): if value != self.empty_value: return super(MyCharFilter, self).filter(qs, value) qs = self.get_method(qs)(**{'%s__%s' % (self.name, self.lookup_expr): ""}) return qs.distinct() if self.distinct else qs Solution 2: Empty string filter """"""""""""""""""""""""""""""" It would also be possible to create an empty value filter that exhibits the same behavior as an ``isnull`` filter. GET http://localhost/api/my-model?myfield__isempty=false .. code-block:: python from django.core.validators import EMPTY_VALUES class EmptyStringFilter(filters.BooleanFilter): def filter(self, qs, value): if value in EMPTY_VALUES: return qs exclude = self.exclude ^ (value is False) method = qs.exclude if exclude else qs.filter return method(**{self.name: ""}) class MyFilterSet(filters.FilterSet): myfield__isempty = EmptyStringFilter(name='myfield') class Meta: model = MyModel Using ``initial`` values as defaults ------------------------------------ In pre-1.0 versions of django-filter, a filter field's ``initial`` value was used as a default when no value was submitted. This behavior was not officially supported and has since been removed. .. warning:: It is recommended that you do **NOT** implement the below as it adversely affects usability. Django forms don't provide this behavior for a reason. - Using initial values as defaults is inconsistent with the behavior of Django forms. - Default values prevent users from filtering by empty values. - Default values prevent users from skipping that filter. If defaults are necessary though, the following should mimic the pre-1.0 behavior: .. code-block:: python class BaseFilterSet(FilterSet): def __init__(self, data=None, *args, **kwargs): # if filterset is bound, use initial values as defaults if data is not None: # get a mutable copy of the QueryDict data = data.copy() for name, f in self.base_filters.items(): initial = f.extra.get('initial') # filter param is either missing or empty, use initial as default if not data.get(name) and initial: data[name] = initial super(BaseFilterSet, self).__init__(data, *args, **kwargs) django-filter-1.1.0/docs/guide/usage.txt0000644000076500000240000002552213107513130020715 0ustar carltonstaff00000000000000=============== Getting Started =============== 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. .. note:: If you're using django-filter with Django Rest Framework, it's recommended that you read the integration docs after this guide. The model --------- Let's start with our model:: from django.db import models class Product(models.Model): name = models.CharField(max_length=255) price = models.DecimalField() description = models.TextField() 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 name, the price or the release_date. We create a ``FilterSet`` for this:: import django_filters class ProductFilter(django_filters.FilterSet): name = django_filters.CharFilter(lookup_expr='iexact') class Meta: model = Product fields = ['price', 'release_date'] As you can see this uses a very similar API to Django's ``ModelForm``. Just like with a ``ModelForm`` we can also override filters, or add new ones using a declarative syntax. Declaring filters ~~~~~~~~~~~~~~~~~ The declarative syntax provides you with the most flexibility when creating filters, however it is fairly verbose. We'll use the below example to outline the :ref:`core filter arguments ` on a ``FilterSet``:: class ProductFilter(django_filters.FilterSet): price = django_filters.NumberFilter() price__gt = django_filters.NumberFilter(name='price', lookup_expr='gt') price__lt = django_filters.NumberFilter(name='price', lookup_expr='lt') release_year = django_filters.NumberFilter(name='release_date', lookup_expr='year') release_year__gt = django_filters.NumberFilter(name='release_date', lookup_expr='year__gt') release_year__lt = django_filters.NumberFilter(name='release_date', lookup_expr='year__lt') manufacturer__name = django_filters.CharFilter(lookup_expr='icontains') class Meta: model = Product There are two main arguments for filters: - ``name``: The name of the model field to filter on. You can traverse "relationship paths" using Django's ``__`` syntax to filter fields on a related model. ex, ``manufacturer__name``. - ``lookup_expr``: The `field lookup`_ to use when filtering. Django's ``__`` syntax can again be used in order to support lookup transforms. ex, ``year__gte``. .. _`field lookup`: https://docs.djangoproject.com/en/dev/ref/models/querysets/#field-lookups Together, the field ``name`` and ``lookup_expr`` represent a complete Django lookup expression. A detailed explanation of lookup expressions is provided in Django's `lookup reference`_. django-filter supports expressions containing both transforms and a final lookup for version 1.9 of Django and above. For Django version 1.8, transformed expressions are not supported. .. _`lookup reference`: https://docs.djangoproject.com/en/dev/ref/models/lookups/#module-django.db.models.lookups Generating filters with Meta.fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The FilterSet Meta class provides a ``fields`` attribute that can be used for easily specifying multiple filters without significant code duplication. The base syntax supports a list of multiple field names:: import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['price', 'release_date'] The above generates 'exact' lookups for both the 'price' and 'release_date' fields. Additionally, a dictionary can be used to specify multiple lookup expressions for each field:: import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = { 'price': ['lt', 'gt'], 'release_date': ['exact', 'year__gt'], } The above would generate 'price__lt', 'price__gt', 'release_date', and 'release_date__year__gt' filters. .. note:: The filter lookup type 'exact' is an implicit default and therefore never added to a filter name. In the above example, the release date's exact filter is 'release_date', not 'release_date__exact'. 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:: class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['manufacturer__country'] Overriding default filters """""""""""""""""""""""""" Like ``django.contrib.admin.ModelAdmin``, it is possible to override default filters for all the models fields of the same kind using ``filter_overrides`` on the ``Meta`` class:: class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = { 'name': ['exact'], 'release_date': ['isnull'], } filter_overrides = { models.CharField: { 'filter_class': django_filters.CharFilter, 'extra': lambda f: { 'lookup_expr': 'icontains', }, }, models.BooleanField: { 'filter_class': django_filters.BooleanFilter, 'extra': lambda f: { 'widget': forms.CheckboxInput, }, }, } Request-based filtering ~~~~~~~~~~~~~~~~~~~~~~~ The ``FilterSet`` may be initialized with an optional ``request`` argument. If a request object is passed, then you may access the request during filtering. This allows you to filter by properties on the request, such as the currently logged-in user or the ``Accepts-Languages`` header. .. note:: It is not guaranteed that a `request` will be provied to the `FilterSet` instance. Any code depending on a request should handle the `None` case. Filtering the primary ``.qs`` """"""""""""""""""""""""""""" To filter the primary queryset by the ``request`` object, simply override the ``FilterSet.qs`` property. For example, you could filter blog articles to only those that are published and those that are owned by the logged-in user (presumably the author's draft articles). .. code-block:: python class ArticleFilter(django_filters.FilterSet): class Meta: model = Article fields = [...] @property def qs(self): parent = super(ArticleFilter, self).qs author = getattr(self.request, 'user', None) return parent.filter(is_published=True) \ | parent.filter(author=author) Filtering the related queryset for ``ModelChoiceFilter`` """""""""""""""""""""""""""""""""""""""""""""""""""""""" The ``queryset`` argument for ``ModelChoiceFilter`` and ``ModelMultipleChoiceFilter`` supports callable behavior. If a callable is passed, it will be invoked with the ``request`` as its only argument. This allows you to perform the same kinds of request-based filtering without resorting to overriding ``FilterSet.__init__``. .. code-block:: python def departments(request): if request is None: return Department.objects.none() company = request.user.company return company.department_set.all() class EmployeeFilter(filters.FilterSet): department = filters.ModelChoiceFilter(queryset=departments) ... Customize filtering with ``Filter.method`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can control the behavior of a filter by specifying a ``method`` to perform filtering. View more information in the :ref:`method reference `. Note that you may access the filterset's properties, such as the ``request``. .. code-block:: python class F(django_filters.FilterSet): username = CharFilter(method='my_custom_filter') class Meta: model = User fields = ['username'] def my_custom_filter(self, queryset, name, value): return queryset.filter(**{ name: value, }) The view -------- Now we need to write a view:: def product_list(request): f = ProductFilter(request.GET, queryset=Product.objects.all()) return render(request, 'my_app/template.html', {'filter': f}) If a queryset argument isn't provided then all the items in the default manager of the model will be used. If you want to access the filtered objects in your views, for example if you want to paginate them, you can do that. They are in f.qs 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" %} {% block content %}
    {{ filter.form.as_p }}
    {% for obj in filter.qs %} {{ obj.name }} - ${{ obj.price }}
    {% endfor %} {% endblock %} And that's all there is to it! The ``form`` attribute contains a normal Django form, and when we iterate over the ``FilterSet.qs`` we get the objects in the resulting queryset. Generic view & configuration ----------------------------- 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``. You must provide either a ``model`` or ``filterset_class`` argument, similar to ``ListView`` in Django itself:: # urls.py from django.conf.urls import url from django_filters.views import FilterView from myapp.models import Product urlpatterns = [ url(r'^list/$', FilterView.as_view(model=Product)), ] If you provide a ``model`` optionally you can set ``filter_fields`` to specify a list or a tuple of the fields that you want to include for the automatic construction of the filterset class. You must provide a template at ``/_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 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:: # urls.py from django.conf.urls import url from django_filters.views import object_filter from myapp.models import Product urlpatterns = [ url(r'^list/$', object_filter, {'model': Product}), ] The needed template and its context variables will also be the same as the class-based view above. django-filter-1.1.0/docs/index.txt0000644000076500000240000000123413013341253017616 0ustar carltonstaff00000000000000============= django-filter ============= 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. .. toctree:: :maxdepth: 2 :caption: User Guide guide/install guide/usage guide/rest_framework guide/tips guide/migration .. toctree:: :maxdepth: 1 :caption: Reference Documentation ref/filterset ref/filters ref/fields ref/widgets ref/settings .. toctree:: :maxdepth: 1 :caption: Developer Documentation dev/tests django-filter-1.1.0/docs/make.bat0000644000076500000240000001176612410601557017374 0ustar carltonstaff00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-filter.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-filter.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end django-filter-1.1.0/docs/Makefile0000644000076500000240000001303612563340337017424 0ustar carltonstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-filter.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-filter.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-filter" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-filter" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." livehtml: sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html django-filter-1.1.0/docs/ref/0000755000076500000240000000000013172070045016526 5ustar carltonstaff00000000000000django-filter-1.1.0/docs/ref/fields.txt0000644000076500000240000000151713013341253020535 0ustar carltonstaff00000000000000=============== Field Reference =============== ``IsoDateTimeField`` ~~~~~~~~~~~~~~~~~~~~ Extends ``django.forms.DateTimeField`` to allow parsing ISO 8601 formated dates, in addition to existing formats Defines a class level attribute ``ISO_8601`` as constant for the format. Sets ``input_formats = [ISO_8601]`` — this means that by default ``IsoDateTimeField`` will **only** parse ISO 8601 formated dates. You may set ``input_formats`` to your list of required formats as per the `DateTimeField Docs`_, using the ``ISO_8601`` class level attribute to specify the ISO 8601 format. .. code-block:: python f = IsoDateTimeField() f.input_formats = [IsoDateTimeField.ISO_8601] + DateTimeField.input_formats .. _`DateTimeField Docs`: https://docs.djangoproject.com/en/1.8/ref/forms/fields/#django.forms.DateTimeField.input_formats django-filter-1.1.0/docs/ref/filters.txt0000644000076500000240000006214713172067725020764 0ustar carltonstaff00000000000000================ Filter Reference ================ This is a reference document with a list of the filters and their arguments. .. _core-arguments: Core Arguments -------------- The following are the core arguments that apply to all filters. ``name`` ~~~~~~~~ The name of the field this filter is supposed to filter on, if this is not provided it automatically becomes the filter's name on the ``FilterSet``. You can traverse "relationship paths" using Django's ``__`` syntax to filter fields on a related model. eg, ``manufacturer__name``. ``label`` ~~~~~~~~~ The label as it will apear in the HTML, analogous to a form field's label argument. If a label is not provided, a verbose label will be generated based on the field ``name`` and the parts of the ``lookup_expr``. (See: :ref:`verbose-lookups-setting`). ``widget`` ~~~~~~~~~~ The django.form Widget class which will represent the ``Filter``. In addition to the widgets that are included with Django that you can use there are additional ones that django-filter provides which may be useful: * :ref:`LinkWidget ` -- this displays the options in a manner similar to the way the Django Admin does, as a series of links. The link for the selected option will have ``class="selected"``. * :ref:`BooleanWidget ` -- this widget converts its input into Python's True/False values. It will convert all case variations of ``True`` and ``False`` into the internal Python values. * :ref:`CSVWidget ` -- this widget expects a comma separated value and converts it into a list of string values. It is expected that the field class handle a list of values as well as type conversion. * :ref:`RangeWidget ` -- this widget is used with ``RangeFilter`` to generate two form input elements using a single field. .. _filter-method: ``method`` ~~~~~~~~~~ An optional argument that tells the filter how to handle the queryset. It can accept either a callable or the name of a method on the ``FilterSet``. The method receives a ``QuerySet``, the name of the model field to filter on, and the value to filter with. It should return a ``Queryset`` that is filtered appropriately. The passed in value is validated and cleaned by the filter's ``field_class``, so raw value transformation and empty value checking should be unnecessary. .. code-block:: python class F(FilterSet): """Filter for Books by if books are published or not""" published = BooleanFilter(name='published_on', method='filter_published') def filter_published(self, queryset, name, value): # construct the full lookup expression. lookup = '__'.join([name, 'isnull']) return queryset.filter(**{lookup: False}) # alternatively, it may not be necessary to construct the lookup. return queryset.filter(published_on__isnull=False) class Meta: model = Book fields = ['published'] # Callables may also be defined out of the class scope. def filter_not_empty(queryset, name, value): lookup = '__'.join([name, 'isnull']) return queryset.filter(**{lookup: False}) class F(FilterSet): """Filter for Books by if books are published or not""" published = BooleanFilter(name='published_on', method=filter_not_empty) class Meta: model = Book fields = ['published'] ``lookup_expr`` ~~~~~~~~~~~~~~~ The lookup expression that should be performed using `Django's ORM`_. .. _`Django's ORM`: https://docs.djangoproject.com/en/dev/ref/models/querysets/#field-lookups A ``list`` or ``tuple`` of lookup types is also accepted, allowing the user to select the lookup from a dropdown. The list of lookup types are filtered against ``filters.LOOKUP_TYPES``. If `lookup_expr=None` is passed, then a list of all lookup types will be generated:: class ProductFilter(django_filters.FilterSet): name = django_filters.CharFilter(lookup_expr=['exact', 'iexact']) You can enable custom lookups by adding them to ``LOOKUP_TYPES``:: from django_filters import filters filters.LOOKUP_TYPES = ['gt', 'gte', 'lt', 'lte', 'custom_lookup_type'] Additionally, you can provide human-friendly help text by overriding ``LOOKUP_TYPES``:: # filters.py from django_filters import filters filters.LOOKUP_TYPES = [ ('', '---------'), ('exact', 'Is equal to'), ('not_exact', 'Is not equal to'), ('lt', 'Lesser than'), ('gt', 'Greater than'), ('gte', 'Greater than or equal to'), ('lte', 'Lesser than or equal to'), ('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('not_contains', 'Does not contain'), ] ``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``. ``exclude`` ~~~~~~~~~~~ A boolean value that specifies whether the Filter should use ``filter`` or ``exclude`` on the queryset. Defaults to ``False``. ``**kwargs`` ~~~~~~~~~~~~ Any additional keyword arguments are stored as the ``extra`` parameter on the filter. They are provided to the accompanying form Field and can be used to provide arguments like ``choices``. ModelChoiceFilter and ModelMultipleChoiceFilter arguments --------------------------------------------------------- These arguments apply specifically to ModelChoiceFilter and ModelMultipleChoiceFilter only. ``queryset`` ~~~~~~~~~~~~ ``ModelChoiceFilter`` and ``ModelMultipleChoiceFilter`` require a queryset to operate on which must be passed as a kwarg. ``to_field_name`` ~~~~~~~~~~~~~~~~~ If you pass in ``to_field_name`` (which gets forwarded to the Django field), it will be used also in the default ``get_filter_predicate`` implementation as the model's attribute. Filters ------- ``CharFilter`` ~~~~~~~~~~~~~~ This filter does simple character matches, used with ``CharField`` and ``TextField`` by default. ``UUIDFilter`` ~~~~~~~~~~~~~~ This filter matches UUID values, used with ``models.UUIDField`` by default. ``BooleanFilter`` ~~~~~~~~~~~~~~~~~ This filter matches a boolean, either ``True`` or ``False``, used with ``BooleanField`` and ``NullBooleanField`` by default. .. _choice-filter: ``ChoiceFilter`` ~~~~~~~~~~~~~~~~ This filter matches values in its ``choices`` argument. The ``choices`` must be explicitly passed when the filter is declared on the ``FilterSet``. For example, .. code-block:: python class User(models.Model): username = models.CharField(max_length=255) first_name = SubCharField(max_length=100) last_name = SubSubCharField(max_length=100) status = models.IntegerField(choices=STATUS_CHOICES, default=0) STATUS_CHOICES = ( (0, 'Regular'), (1, 'Manager'), (2, 'Admin'), ) class F(FilterSet): status = ChoiceFilter(choices=STATUS_CHOICES) class Meta: model = User fields = ['status'] ``ChoiceFilter`` also has arguments that enable a choice for not filtering, as well as a choice for filtering by ``None`` values. Each of the arguments have a corresponding global setting (:doc:`/ref/settings`). * ``empty_label``: The display label to use for the select choice to not filter. The choice may be disabled by setting this argument to ``None``. Defaults to ``FILTERS_EMPTY_CHOICE_LABEL``. * ``null_label``: The display label to use for the choice to filter by ``None`` values. The choice may be disabled by setting this argument to ``None``. Defaults to ``FILTERS_NULL_CHOICE_LABEL``. * ``null_value``: The special value to match to enable filtering by ``None`` values. This value defaults ``FILTERS_NULL_CHOICE_VALUE`` and needs to be a non-empty value (``''``, ``None``, ``[]``, ``()``, ``{}``). ``TypedChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~ The same as ``ChoiceFilter`` with the added possibility to convert value to match against. This could be done by using `coerce` parameter. An example use-case is limiting boolean choices to match against so only some predefined strings could be used as input of a boolean filter:: import django_filters from distutils.util import strtobool BOOLEAN_CHOICES = (('false', 'False'), ('true', 'True'),) class YourFilterSet(django_filters.FilterSet): ... flag = django_filters.TypedChoiceFilter(choices=BOOLEAN_CHOICES, coerce=strtobool) ``MultipleChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~ The same as ``ChoiceFilter`` except the user can select multiple choices and the filter will form the OR of these choices by default to match items. The filter will form the AND of the selected choices when the ``conjoined=True`` argument is passed to this class. Multiple choices are represented in the query string by reusing the same key with different values (e.g. ''?status=Regular&status=Admin''). ``distinct`` defaults to ``True`` as to-many relationships will generally require this. Advanced Use: Depending on your application logic, when all or no choices are selected, filtering may be a noop. In this case you may wish to avoid the filtering overhead, particularly of the `distinct` call. Set `always_filter` to False after instantiation to enable the default `is_noop` test. Override `is_noop` if you require a different test for your application. ``TypedMultipleChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like ``MultipleChoiceFilter``, but in addition accepts the ``coerce`` parameter, as in ``TypedChoiceFilter``. ``DateFilter`` ~~~~~~~~~~~~~~ Matches on a date. Used with ``DateField`` by default. ``TimeFilter`` ~~~~~~~~~~~~~~ Matches on a time. Used with ``TimeField`` by default. ``DateTimeFilter`` ~~~~~~~~~~~~~~~~~~ Matches on a date and time. Used with ``DateTimeField`` by default. ``IsoDateTimeFilter`` ~~~~~~~~~~~~~~~~~~~~~ Uses ``IsoDateTimeField`` to support filtering on ISO 8601 formatted dates, as are often used in APIs, and are employed by default by Django REST Framework. Example:: class F(FilterSet): """Filter for Books by date published, using ISO 8601 formatted dates""" published = IsoDateTimeFilter() class Meta: model = Book fields = ['published'] ``DurationFilter`` ~~~~~~~~~~~~~~~~~~ Matches on a duration. Used with ``DurationField`` by default. Supports both Django ('%d %H:%M:%S.%f') and ISO 8601 formatted durations (but only the sections that are accepted by Python's timedelta, so no year, month, and week designators, e.g. 'P3DT10H22M'). ``ModelChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~ Similar to a ``ChoiceFilter`` except it works with related models, used for ``ForeignKey`` by default. If automatically instantiated, ``ModelChoiceFilter`` will use the default ``QuerySet`` for the related field. If manually instantiated you **must** provide the ``queryset`` kwarg. Example:: class F(FilterSet): """Filter for books by author""" author = ModelChoiceFilter(queryset=Author.objects.all()) class Meta: model = Book fields = ['author'] The ``queryset`` argument also supports callable behavior. If a callable is passed, it will be invoked with ``Filterset.request`` as its only argument. This allows you to easily filter by properties on the request object without having to override the ``FilterSet.__init__``. .. note:: You should expect that the `request` object may be `None`. .. code-block:: python def departments(request): if request is None: return Department.objects.none() company = request.user.company return company.department_set.all() class EmployeeFilter(filters.FilterSet): department = filters.ModelChoiceFilter(queryset=departments) ... ``ModelMultipleChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to a ``MultipleChoiceFilter`` except it works with related models, used for ``ManyToManyField`` by default. As with ``ModelChoiceFilter``, if automatically instantiated, ``ModelMultipleChoiceFilter`` will use the default ``QuerySet`` for the related field. If manually instantiated you **must** provide the ``queryset`` kwarg. Like ``ModelChoiceFilter``, the ``queryset`` argument has callable behavior. To use a custom field name for the lookup, you can use ``to_field_name``:: class FooFilter(BaseFilterSet): foo = django_filters.filters.ModelMultipleChoiceFilter( name='attr__uuid', to_field_name='uuid', queryset=Foo.objects.all(), ) If you want to use a custom queryset, e.g. to add annotated fields, this can be done as follows:: class MyMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter): def get_filter_predicate(self, v): return {'annotated_field': v.annotated_field} def filter(self, qs, value): if value: qs = qs.annotate_with_custom_field() qs = super().filter(qs, value) return qs foo = MyMultipleChoiceFilter( to_field_name='annotated_field', queryset=Model.objects.annotate_with_custom_field(), ) The ``annotate_with_custom_field`` method would be defined through a custom QuerySet, which then gets used as the model's manager:: class CustomQuerySet(models.QuerySet): def annotate_with_custom_field(self): return self.annotate( custom_field=Case( When(foo__isnull=False, then=F('foo__uuid')), When(bar__isnull=False, then=F('bar__uuid')), default=None, ), ) class MyModel(models.Model): objects = CustomQuerySet.as_manager() ``NumberFilter`` ~~~~~~~~~~~~~~~~ Filters based on a numerical value, used with ``IntegerField``, ``FloatField``, and ``DecimalField`` by default. ``NumericRangeFilter`` ~~~~~~~~~~~~~~~~~~~~~~ Filters where a value is between two numerical values, or greater than a minimum or less than a maximum where only one limit value is provided. This filter is designed to work with the Postgres Numerical Range Fields, including ``IntegerRangeField``, ``BigIntegerRangeField`` and ``FloatRangeField`` (available since Django 1.8). The default widget used is the ``RangeField``. Regular field lookups are available in addition to several containment lookups, including ``overlap``, ``contains``, and ``contained_by``. More details in the Django `docs`__. __ https://docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields/#querying-range-fields If the lower limit value is provided, the filter automatically defaults to ``startswith`` as the lookup and ``endswith`` if only the upper limit value is provided. ``RangeFilter`` ~~~~~~~~~~~~~~~ Filters where a value is between two numerical values, or greater than a minimum or less than a maximum where only one limit value is provided. :: class F(FilterSet): """Filter for Books by Price""" price = RangeFilter() class Meta: model = Book fields = ['price'] qs = Book.objects.all().order_by('title') # Range: Books between 5€ and 15€ f = F({'price_0': '5', 'price_1': '15'}, queryset=qs) # Min-Only: Books costing more the 11€ f = F({'price_0': '11'}, queryset=qs) # Max-Only: Books costing less than 19€ f = F({'price_1': '19'}, queryset=qs) ``DateRangeFilter`` ~~~~~~~~~~~~~~~~~~~ Filter similar to the admin changelist date one, it has a number of common selections for working with date fields. ``DateFromToRangeFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to a ``RangeFilter`` except it uses dates instead of numerical values. It can be used with ``DateField``. It also works with ``DateTimeField``, but takes into consideration only the date. Example of using the ``DateField`` field:: class Comment(models.Model): date = models.DateField() time = models.TimeField() class F(FilterSet): date = DateFromToRangeFilter() class Meta: model = Comment fields = ['date'] # Range: Comments added between 2016-01-01 and 2016-02-01 f = F({'date_0': '2016-01-01', 'date_1': '2016-02-01'}) # Min-Only: Comments added after 2016-01-01 f = F({'date_0': '2016-01-01'}) # Max-Only: Comments added before 2016-02-01 f = F({'date_1': '2016-02-01'}) .. note:: When filtering ranges that occurs on DST transition dates ``DateFromToRangeFilter`` will use the first valid hour of the day for start datetime and the last valid hour of the day for end datetime. This is OK for most applications, but if you want to customize this behavior you must extend ``DateFromToRangeFilter`` and make a custom field for it. .. warning:: If you're using Django prior to 1.9 you may hit ``AmbiguousTimeError`` or ``NonExistentTimeError`` when start/end date matches DST start/end respectively. This occurs because versions before 1.9 don't allow to change the DST behavior for making a datetime aware. Example of using the ``DateTimeField`` field:: class Article(models.Model): published = models.DateTimeField() class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] Article.objects.create(published='2016-01-01 8:00') Article.objects.create(published='2016-01-20 10:00') Article.objects.create(published='2016-02-10 12:00') # Range: Articles published between 2016-01-01 and 2016-02-01 f = F({'published_0': '2016-01-01', 'published_1': '2016-02-01'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 f = F({'published_0': '2016-01-01'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-02-01 f = F({'published_1': '2016-02-01'}) assert len(f.qs) == 2 ``DateTimeFromToRangeFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to a ``RangeFilter`` except it uses datetime format values instead of numerical values. It can be used with ``DateTimeField``. Example:: class Article(models.Model): published = models.DateTimeField() class F(FilterSet): published = DateTimeFromToRangeFilter() class Meta: model = Article fields = ['published'] Article.objects.create(published='2016-01-01 8:00') Article.objects.create(published='2016-01-01 9:30') Article.objects.create(published='2016-01-02 8:00') # Range: Articles published 2016-01-01 between 8:00 and 10:00 f = F({'published_0': '2016-01-01 8:00', 'published_1': '2016-01-01 10:00'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 8:00 f = F({'published_0': '2016-01-01 8:00'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-01-01 10:00 f = F({'published_1': '2016-01-01 10:00'}) assert len(f.qs) == 2 ``TimeRangeFilter`` ~~~~~~~~~~~~~~~~~~~ Similar to a ``RangeFilter`` except it uses time format values instead of numerical values. It can be used with ``TimeField``. Example:: class Comment(models.Model): date = models.DateField() time = models.TimeField() class F(FilterSet): time = TimeRangeFilter() class Meta: model = Comment fields = ['time'] # Range: Comments added between 8:00 and 10:00 f = F({'time_0': '8:00', 'time_1': '10:00'}) # Min-Only: Comments added after 8:00 f = F({'time_0': '8:00'}) # Max-Only: Comments added before 10:00 f = F({'time_1': '10:00'}) ``AllValuesFilter`` ~~~~~~~~~~~~~~~~~~~ This is a ``ChoiceFilter`` whose choices are the current values in the database. So if in the DB for the given field you have values of 5, 7, and 9 each of those is present as an option. This is similar to the default behavior of the admin. ``AllValuesMultipleFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is a ``MultipleChoiceFilter`` whose choices are the current values in the database. So if in the DB for the given field you have values of 5, 7, and 9 each of those is present as an option. This is similar to the default behavior of the admin. .. _base-in-filter: ``BaseInFilter`` ~~~~~~~~~~~~~~~~ This is a base class used for creating IN lookup filters. It is expected that this filter class is used in conjunction with another filter class, as this class **only** validates that the incoming value is comma-separated. The secondary filter is then used to validate the individual values. Example:: class NumberInFilter(BaseInFilter, NumberFilter): pass class F(FilterSet): id__in = NumberInFilter(name='id', lookup_expr='in') class Meta: model = User User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') User.objects.create(username='carl') # In: User with IDs 1 and 3. f = F({'id__in': '1,3'}) assert len(f.qs) == 2 ``BaseRangeFilter`` ~~~~~~~~~~~~~~~~~~~ This is a base class used for creating RANGE lookup filters. It behaves identically to ``BaseInFilter`` with the exception that it expects only two comma-separated values. Example:: class NumberRangeFilter(BaseInFilter, NumberFilter): pass class F(FilterSet): id__range = NumberRangeFilter(name='id', lookup_expr='range') class Meta: model = User User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') User.objects.create(username='carl') # Range: User with IDs between 1 and 3. f = F({'id__range': '1,3'}) assert len(f.qs) == 3 .. _ordering-filter: ``OrderingFilter`` ~~~~~~~~~~~~~~~~~~ Enable queryset ordering. As an extension of ``ChoiceFilter`` it accepts two additional arguments that are used to build the ordering choices. * ``fields`` is a mapping of {model field name: parameter name}. The parameter names are exposed in the choices and mask/alias the field names used in the ``order_by()`` call. Similar to field ``choices``, ``fields`` accepts the 'list of two-tuples' syntax that retains order. ``fields`` may also just be an iterable of strings. In this case, the field names simply double as the exposed parameter names. * ``field_labels`` is an optional argument that allows you to customize the display label for the corresponding parameter. It accepts a mapping of {field name: human readable label}. Keep in mind that the key is the field name, and not the exposed parameter name. .. code-block:: python class UserFilter(FilterSet): account = CharFilter(name='username') status = NumberFilter(name='status') o = OrderingFilter( # tuple-mapping retains order fields=( ('username', 'account'), ('first_name', 'first_name'), ('last_name', 'last_name'), ), # labels do not need to retain order field_labels={ 'username': 'User account', } ) class Meta: model = User fields = ['first_name', 'last_name'] >>> UserFilter().filters['o'].field.choices [ ('account', 'User account'), ('-account', 'User account (descending)'), ('first_name', 'First name'), ('-first_name', 'First name (descending)'), ('last_name', 'Last name'), ('-last_name', 'Last name (descending)'), ] Additionally, you can just provide your own ``choices`` if you require explicit control over the exposed options. For example, when you might want to disable descending sort options. .. code-block:: python class UserFilter(FilterSet): account = CharFilter(name='username') status = NumberFilter(name='status') o = OrderingFilter( choices=( ('account', 'Account'), ), fields={ 'username': 'account', }, ) This filter is also CSV-based, and accepts multiple ordering params. The default select widget does not enable the use of this, but it is useful for APIs. ``SelectMultiple`` widgets are not compatible, given that they are not able to retain selection order. Adding Custom filter choices """""""""""""""""""""""""""" If you wish to sort by non-model fields, you'll need to add custom handling to an ``OrderingFilter`` subclass. For example, if you want to sort by a computed 'relevance' factor, you would need to do something like the following: .. code-block:: python class CustomOrderingFilter(django_filters.OrderingFilter): def __init__(self, *args, **kwargs): super(CustomOrderingFilter, self).__init__(*args, **kwargs) self.extra['choices'] += [ ('relevance', 'Relevance'), ('-relevance', 'Relevance (descending)'), ] def filter(self, qs, value): # OrderingFilter is CSV-based, so `value` is a list if any(v in ['relevance', '-relevance'] for v in value): # sort queryset by relevance return ... return super(CustomOrderingFilter, self).filter(qs, value) django-filter-1.1.0/docs/ref/filterset.txt0000644000076500000240000001546313107513130021274 0ustar carltonstaff00000000000000================= FilterSet Options ================= This document provides a guide on using additional FilterSet features. Meta options ------------ - :ref:`model ` - :ref:`fields ` - :ref:`exclude ` - :ref:`form
    ` - :ref:`together ` - :ref:`filter_overrides ` - :ref:`strict ` .. _model: Automatic filter generation with ``model`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``FilterSet`` is capable of automatically generating filters for a given ``model``'s fields. Similar to Django's ``ModelForm``, filters are created based on the underlying model field's type. This option must be combined with either the ``fields`` or ``exclude`` option, which is the same requirement for Django's ``ModelForm`` class, detailed `here`__. __ https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#selecting-the-fields-to-use .. code-block:: python class UserFilter(django_filters.FilterSet): class Meta: model = User fields = ['username', 'last_login'] .. _fields: Declaring filterable ``fields`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``fields`` option is combined with ``model`` to automatically generate filters. Note that generated filters will not overwrite filters declared on the ``FilterSet``. The ``fields`` option accepts two syntaxes: * a list of field names * a dictionary of field names mapped to a list of lookups .. code-block:: python class UserFilter(django_filters.FilterSet): class Meta: model = User fields = ['username', 'last_login'] # or class UserFilter(django_filters.FilterSet): class Meta: model = User fields = { 'username': ['exact', 'contains'], 'last_login': ['exact', 'year__gt'], } The list syntax will create an ``exact`` lookup filter for each field included in ``fields``. The dictionary syntax will create a filter for each lookup expression declared for its corresponding model field. These expressions may include both transforms and lookups, as detailed in the `lookup reference`__. __ https://docs.djangoproject.com/en/dev/ref/models/lookups/#module-django.db.models.lookups .. _exclude: Disable filter fields with ``exclude`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``exclude`` option accepts a blacklist of field names to exclude from automatic filter generation. Note that this option will not disable filters declared directly on the ``FilterSet``. .. code-block:: python class UserFilter(django_filters.FilterSet): class Meta: model = User exclude = ['password'] .. _form: Custom Forms using ``form`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The inner ``Meta`` class also takes an optional ``form`` argument. This is a form class from which ``FilterSet.form`` will subclass. This works similar to the ``form`` option on a ``ModelAdmin.`` .. _together: Group fields with ``together`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The inner ``Meta`` class also takes an optional ``together`` argument. This is a list of lists, each containing field names. For convenience can be a single list/tuple when dealing with a single set of fields. Fields within a field set must either be all or none present in the request for ``FilterSet.form`` to be valid:: import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['price', 'release_date', 'rating'] together = ['rating', 'price'] .. _filter_overrides: Customise filter generation with ``filter_overrides`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The inner ``Meta`` class also takes an optional ``filter_overrides`` argument. This is a map of model fields to filter classes with options:: class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['name', 'release_date'] filter_overrides = { models.CharField: { 'filter_class': django_filters.CharFilter, 'extra': lambda f: { 'lookup_expr': 'icontains', }, }, models.BooleanField: { 'filter_class': django_filters.BooleanFilter, 'extra': lambda f: { 'widget': forms.CheckboxInput, }, }, } .. _strict: Handling validation errors with ``strict`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``strict`` option determines the filterset's behavior when filters fail to validate. Example use: .. code-block:: python from django_filters import FilterSet, STRICTNESS class ProductFilter(FilterSet): class Meta: model = Product fields = ['name', 'release_date'] strict = STRICTNESS.RETURN_NO_RESULTS Currently, there are three different behaviors: - ``STRICTNESS.RETURN_NO_RESULTS`` (default) This returns an empty queryset. The filterset form can then be rendered to display the input errors. - ``STRICTNESS.IGNORE`` Instead of returning an empty queryset, invalid filters effectively become a noop. Valid filters are applied to the queryset however. - ``STRICTNESS.RAISE_VALIDATION_ERROR`` This raises a ``ValidationError`` for all invalid filters. This behavior is generally useful with APIs. If the ``strict`` option is not provided, then the filterset will default to the value of the ``FILTERS_STRICTNESS`` setting. Overriding ``FilterSet`` methods -------------------------------- ``filter_for_lookup()`` ~~~~~~~~~~~~~~~~~~~~~~~ Prior to version 0.13.0, filter generation did not take into account the ``lookup_expr`` used. This commonly caused malformed filters to be generated for 'isnull', 'in', and 'range' lookups (as well as transformed lookups). The current implementation provides the following behavior: - 'isnull' lookups return a ``BooleanFilter`` - 'in' lookups return a filter derived from the CSV-based ``BaseInFilter``. - 'range' lookups return a filter derived from the CSV-based ``BaseRangeFilter``. If you want to override the ``filter_class`` and ``params`` used to instantiate filters for a model field, you can override ``filter_for_lookup()``. Ex:: class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = { 'release_date': ['exact', 'range'], } @classmethod def filter_for_lookup(cls, f, lookup_type): # override date range lookups if isinstance(f, models.DateField) and lookup_type == 'range': return django_filters.DateRangeFilter, {} # use default behavior otherwise return super(ProductFilter, cls).filter_for_lookup(f, lookup_type) django-filter-1.1.0/docs/ref/settings.txt0000644000076500000240000000562313107513130021130 0ustar carltonstaff00000000000000================== Settings Reference ================== Here is a list of all available settings of django-filters and their default values. All settings are prefixed with ``FILTERS_``, although this is a bit verbose it helps to make it easy to identify these settings. FILTERS_EMPTY_CHOICE_LABEL -------------------------- Default: ``'---------'`` Set the default value for ``ChoiceFilter.empty_label``. You may disable the empty choice by setting this to ``None``. FILTERS_NULL_CHOICE_LABEL ------------------------- Default: ``None`` Set the default value for ``ChoiceFilter.null_label``. You may enable the null choice by setting a non-``None`` value. FILTERS_NULL_CHOICE_VALUE ------------------------- Default: ``'null'`` Set the default value for ``ChoiceFilter.null_value``. You may want to change this value if the default ``'null'`` string conflicts with an actual choice. FILTERS_DISABLE_HELP_TEXT ------------------------- Default: ``False`` Some filters provide informational ``help_text``. For example, csv-based filters (``filters.BaseCSVFilter``) inform users that "Multiple values may be separated by commas". You may set this to ``True`` to disable the ``help_text`` for **all** filters, removing the text from the rendered form's output. .. _verbose-lookups-setting: FILTERS_VERBOSE_LOOKUPS ----------------------- .. note:: This is considered an advanced setting and is subject to change. Default: .. code-block:: python # refer to 'django_filters.conf.DEFAULTS' 'VERBOSE_LOOKUPS': { 'exact': _(''), 'iexact': _(''), 'contains': _('contains'), 'icontains': _('contains'), ... } This setting controls the verbose output for generated filter labels. Instead of getting expression parts such as "lt" and "contained_by", the verbose label would contain "is less than" and "is contained by". Verbose output may be disabled by setting this to a falsy value. This setting also accepts callables. The callable should not require arguments and should return a dictionary. This is useful for extending or overriding the default terms without having to copy the entire set of terms to your settings. For example, you could add verbose output for "exact" lookups. .. code-block:: python # settings.py def FILTERS_VERBOSE_LOOKUPS(): from django_filters.conf import DEFAULTS verbose_lookups = DEFAULTS['VERBOSE_LOOKUPS'].copy() verbose_lookups.update({ 'exact': 'is equal to', }) return verbose_lookups FILTERS_STRICTNESS ------------------ Default: ``STRICTNESS.RETURN_NO_RESULTS`` Set the global default for FilterSet :ref:`strictness `. If ``strict`` is not provided to the filterset, it will default to this setting. You can change the setting like so: .. code-block:: python # settings.py from django_filters import STRICTNESS FILTERS_STRICTNESS = STRICTNESS.RETURN_NO_RESULTS django-filter-1.1.0/docs/ref/widgets.txt0000644000076500000240000000460313172067725020753 0ustar carltonstaff00000000000000================ Widget Reference ================ This is a reference document with a list of the provided widgets and their arguments. .. _link-widget: ``LinkWidget`` ~~~~~~~~~~~~~~ This widget renders each option as a link, instead of an actual . It has one method that you can override for additional customizability. ``option_string()`` should return a string with 3 Python keyword argument placeholders: 1. ``attrs``: This is a string with all the attributes that will be on the final ```` tag. 2. ``query_string``: This is the query string for use in the ``href`` option on the ```` element. 3. ``label``: This is the text to be displayed to the user. .. _boolean-widget: ``BooleanWidget`` ~~~~~~~~~~~~~~~~~ This widget converts its input into Python's True/False values. It will convert all case variations of ``True`` and ``False`` into the internal Python values. To use it, pass this into the ``widgets`` argument of the ``BooleanFilter``: .. code-block:: python active = BooleanFilter(widget=BooleanWidget()) .. _csv-widget: ``CSVWidget`` ~~~~~~~~~~~~~ This widget expects a comma separated value and converts it into a list of string values. It is expected that the field class handle a list of values as well as type conversion. .. _range-widget: ``RangeWidget`` ~~~~~~~~~~~~~~~ This widget is used with ``RangeFilter`` and its subclasses. It generates two form input elements which generally act as start/end values in a range. Under the hood, it is django's ``forms.TextInput`` widget and excepts the same arguments and values. To use it, pass it to ``widget`` argument of a ``RangeField``: .. code-block:: python date_range = DateFromToRangeFilter(widget=RangeWidget(attrs={'placeholder': 'YYYY/MM/DD'})) ``SuffixedMultiWidget`` ~~~~~~~~~~~~~~~~~~~~~~~ Extends Django's builtin ``MultiWidget`` to append custom suffixes instead of indices. For example, take a range widget that accepts minimum and maximum bounds. By default, the resulting query params would look like the following: .. code-block:: http GET /products?price_0=10&price_1=25 HTTP/1.1 By using ``SuffixedMultiWidget`` instead, you can provide human-friendly suffixes. .. code-block:: python class RangeWidget(SuffixedMultiWidget): suffixes = ['min', 'max'] The query names are now a little more ergonomic. .. code-block:: http GET /products?price_min=10&price_max=25 HTTP/1.1 django-filter-1.1.0/LICENSE0000644000076500000240000000271712410601557016040 0ustar carltonstaff00000000000000Copyright (c) Alex Gaynor and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-filter-1.1.0/MANIFEST.in0000644000076500000240000000054413065267357016602 0ustar carltonstaff00000000000000include AUTHORS include CHANGES.rst include LICENSE include README.rst include runshell.py include runtests.py recursive-include docs * recursive-include requirements * recursive-include tests * recursive-include django_filters/locale * recursive-include django_filters/templates *.html prune docs/_build global-exclude __pycache__ global-exclude *.py[co] django-filter-1.1.0/PKG-INFO0000644000076500000240000001065713172070045016130 0ustar carltonstaff00000000000000Metadata-Version: 1.1 Name: django-filter 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 Author-email: carlton.gibson@noumenal.es License: BSD Description: Django Filter ============= Django-filter is a reusable Django application allowing users to declaratively add dynamic ``QuerySet`` filtering from URL parameters. Full documentation on `read the docs`_. .. image:: https://travis-ci.org/carltongibson/django-filter.svg?branch=master :target: https://travis-ci.org/carltongibson/django-filter .. image:: https://codecov.io/gh/carltongibson/django-filter/branch/develop/graph/badge.svg :target: https://codecov.io/gh/carltongibson/django-filter .. image:: https://badge.fury.io/py/django-filter.svg :target: http://badge.fury.io/py/django-filter Requirements ------------ * **Python**: 2.7, 3.4, 3.5, 3.6 * **Django**: 1.8, 1.10, 1.11 * **DRF**: 3.7 Installation ------------ Install using pip: .. code-block:: sh pip install django-filter Then add ``'django_filters'`` to your ``INSTALLED_APPS``. .. code-block:: python INSTALLED_APPS = [ ... 'django_filters', ] Usage ----- Django-filter can be used for generating interfaces similar to the Django admin's ``list_filter`` interface. It has an API very similar to Django's ``ModelForms``. For example, if you had a Product model you could have a filterset for it with the code: .. code-block:: python import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['name', 'price', 'manufacturer'] And then in your view you could do: .. code-block:: python def product_list(request): filter = ProductFilter(request.GET, queryset=Product.objects.all()) return render(request, 'my_app/template.html', {'filter': filter}) Usage with Django REST Framework -------------------------------- Django-filter provides a custom ``FilterSet`` and filter backend for use with Django REST Framework. To use this adjust your import to use ``django_filters.rest_framework.FilterSet``. .. code-block:: python from django_filters import rest_framework as filters class ProductFilter(filters.FilterSet): class Meta: model = Product fields = ('category', 'in_stock') For more details see the `DRF integration docs`_. Support ------- If you have questions about usage or development you can join the `mailing list`_. .. _`read the docs`: https://django-filter.readthedocs.io/en/develop/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/develop/guide/rest_framework.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django Classifier: Framework :: Django :: 1.8 Classifier: Framework :: Django :: 1.10 Classifier: Framework :: Django :: 1.11 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Framework :: Django django-filter-1.1.0/README.rst0000644000076500000240000000477213172067725016536 0ustar carltonstaff00000000000000Django Filter ============= Django-filter is a reusable Django application allowing users to declaratively add dynamic ``QuerySet`` filtering from URL parameters. Full documentation on `read the docs`_. .. image:: https://travis-ci.org/carltongibson/django-filter.svg?branch=master :target: https://travis-ci.org/carltongibson/django-filter .. image:: https://codecov.io/gh/carltongibson/django-filter/branch/develop/graph/badge.svg :target: https://codecov.io/gh/carltongibson/django-filter .. image:: https://badge.fury.io/py/django-filter.svg :target: http://badge.fury.io/py/django-filter Requirements ------------ * **Python**: 2.7, 3.4, 3.5, 3.6 * **Django**: 1.8, 1.10, 1.11 * **DRF**: 3.7 Installation ------------ Install using pip: .. code-block:: sh pip install django-filter Then add ``'django_filters'`` to your ``INSTALLED_APPS``. .. code-block:: python INSTALLED_APPS = [ ... 'django_filters', ] Usage ----- Django-filter can be used for generating interfaces similar to the Django admin's ``list_filter`` interface. It has an API very similar to Django's ``ModelForms``. For example, if you had a Product model you could have a filterset for it with the code: .. code-block:: python import django_filters class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['name', 'price', 'manufacturer'] And then in your view you could do: .. code-block:: python def product_list(request): filter = ProductFilter(request.GET, queryset=Product.objects.all()) return render(request, 'my_app/template.html', {'filter': filter}) Usage with Django REST Framework -------------------------------- Django-filter provides a custom ``FilterSet`` and filter backend for use with Django REST Framework. To use this adjust your import to use ``django_filters.rest_framework.FilterSet``. .. code-block:: python from django_filters import rest_framework as filters class ProductFilter(filters.FilterSet): class Meta: model = Product fields = ('category', 'in_stock') For more details see the `DRF integration docs`_. Support ------- If you have questions about usage or development you can join the `mailing list`_. .. _`read the docs`: https://django-filter.readthedocs.io/en/develop/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/develop/guide/rest_framework.html django-filter-1.1.0/requirements/0000755000076500000240000000000013172070045017545 5ustar carltonstaff00000000000000django-filter-1.1.0/requirements/maintainer.txt0000644000076500000240000000072012770314705022443 0ustar carltonstaff00000000000000alabaster==0.7.7 argh==0.26.1 Babel==2.2.0 backports.ssl-match-hostname==3.4.0.2 bumpversion==0.5.3 certifi==2015.9.6.2 docutils==0.12 funcsigs==0.4 Jinja2==2.8 livereload==2.4.0 MarkupSafe==0.23 pathtools==0.1.2 pbr==1.7.0 pkginfo==1.2.1 Pygments==2.1.3 pytz==2016.6.1 PyYAML==3.11 requests==2.9.1 requests-toolbelt==0.6.0 six==1.9.0 snowballstemmer==1.2.1 Sphinx==1.3.6 sphinx-autobuild==0.6.0 sphinx-rtd-theme==0.1.9 tornado==4.2.1 twine==1.6.5 watchdog==0.8.3 django-filter-1.1.0/requirements/test-ci.txt0000644000076500000240000000010013107513130021640 0ustar carltonstaff00000000000000markdown==2.6.4 coreapi django-crispy-forms coverage mock pytz django-filter-1.1.0/requirements/test.txt0000644000076500000240000000005213006450377021270 0ustar carltonstaff00000000000000-r test-ci.txt django djangorestframework django-filter-1.1.0/runshell.py0000755000076500000240000000076512754141601017245 0ustar carltonstaff00000000000000#!/usr/bin/env python import os import sys import django from django.core.management import execute_from_command_line def runshell(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") execute_from_command_line( sys.argv[:1] + ['migrate', '--noinput', '-v', '0'] + (['--run-syncdb'] if django.VERSION >= (1, 9) else [])) argv = sys.argv[:1] + ['shell'] + sys.argv[1:] execute_from_command_line(argv) if __name__ == '__main__': runshell() django-filter-1.1.0/runtests.py0000755000076500000240000000050212643540653017274 0ustar carltonstaff00000000000000#!/usr/bin/env python import os import sys from django.core.management import execute_from_command_line def runtests(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") argv = sys.argv[:1] + ['test'] + sys.argv[1:] execute_from_command_line(argv) if __name__ == '__main__': runtests() django-filter-1.1.0/setup.cfg0000644000076500000240000000043613172070045016646 0ustar carltonstaff00000000000000[metadata] license-file = LICENSE [wheel] universal = 1 [isort] skip = .tox atomic = true multi_line_output = 3 known_standard_library = mock known_third_party = django,pytz,rest_framework known_first_party = django_filters [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-filter-1.1.0/setup.py0000644000076500000240000000405113172067725016547 0ustar carltonstaff00000000000000import os import sys from setuptools import setup, find_packages f = open('README.rst') readme = f.read() f.close() version = '1.1.0' if sys.argv[-1] == 'publish': if os.system("pip freeze | grep wheel"): print("wheel not installed.\nUse `pip install wheel`.\nExiting.") sys.exit() if os.system("pip freeze | grep twine"): print("twine not installed.\nUse `pip install twine`.\nExiting.") sys.exit() os.system("python setup.py sdist bdist_wheel") os.system("twine upload dist/*") print("You probably want to also tag the version now:") print(" git tag -a %s -m 'version %s'" % (version, version)) print(" git push --tags") sys.exit() setup( name='django-filter', version=version, description=('Django-filter is a reusable Django application for allowing' ' users to filter querysets dynamically.'), long_description=readme, author='Alex Gaynor', author_email='alex.gaynor@gmail.com', maintainer='Carlton Gibson', maintainer_email='carlton.gibson@noumenal.es', url='https://github.com/carltongibson/django-filter/tree/master', packages=find_packages(exclude=['tests*']), include_package_data=True, license='BSD', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Framework :: Django', 'Framework :: Django :: 1.8', 'Framework :: Django :: 1.10', 'Framework :: Django :: 1.11', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Framework :: Django', ], zip_safe=False, ) django-filter-1.1.0/tests/0000755000076500000240000000000013172070045016164 5ustar carltonstaff00000000000000django-filter-1.1.0/tests/__init__.py0000644000076500000240000000000012410601557020265 0ustar carltonstaff00000000000000django-filter-1.1.0/tests/models.py0000644000076500000240000001333213172067725020036 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django import forms from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ REGULAR = 0 MANAGER = 1 ADMIN = 2 STATUS_CHOICES = ( (REGULAR, 'Regular'), (MANAGER, 'Manager'), (ADMIN, 'Admin'), ) # classes for testing filters with inherited fields class SubCharField(models.CharField): pass class SubSubCharField(SubCharField): pass class SubnetMaskField(models.Field): empty_strings_allowed = False description = "Subnet Mask" def __init__(self, *args, **kwargs): kwargs['max_length'] = 15 models.Field.__init__(self, *args, **kwargs) def get_internal_type(self): return "GenericIPAddressField" def formfield(self, **kwargs): defaults = {'form_class': forms.GenericIPAddressField} defaults.update(kwargs) return super(SubnetMaskField, self).formfield(**defaults) @python_2_unicode_compatible class User(models.Model): username = models.CharField(_('username'), max_length=255) first_name = SubCharField(max_length=100) last_name = SubSubCharField(max_length=100) status = models.IntegerField(choices=STATUS_CHOICES, default=0) is_active = models.BooleanField(default=False) favorite_books = models.ManyToManyField('Book', related_name='lovers') def __str__(self): return self.username @python_2_unicode_compatible class ManagerGroup(models.Model): users = models.ManyToManyField(User, limit_choices_to={'is_active': True}, related_name='member_of') manager = models.ForeignKey(User, limit_choices_to=lambda: {'status': MANAGER}, related_name='manager_of', on_delete=models.CASCADE) def __str__(self): return self.manager.name + ' group' @python_2_unicode_compatible class AdminUser(User): class Meta: proxy = True def __str__(self): return "%s (ADMIN)" % self.username @python_2_unicode_compatible class Comment(models.Model): text = models.TextField() author = models.ForeignKey(User, related_name='comments', on_delete=models.CASCADE) date = models.DateField() time = models.TimeField() def __str__(self): return "%s said %s" % (self.author, self.text[:25]) class Article(models.Model): name = models.CharField(verbose_name='title', max_length=200, blank=True) published = models.DateTimeField() author = models.ForeignKey(User, null=True, on_delete=models.CASCADE) def __str__(self): if self.author_id: return "%s on %s" % (self.author, self.published) return "Anonymous on %s" % self.published @python_2_unicode_compatible class Book(models.Model): title = models.CharField(max_length=100) price = models.DecimalField(max_digits=6, decimal_places=2) average_rating = models.FloatField() def __str__(self): return self.title class Place(models.Model): name = models.CharField(max_length=100) class Meta: abstract = True class Restaurant(Place): serves_pizza = models.BooleanField(default=False) class NetworkSetting(models.Model): ip = models.GenericIPAddressField() mask = SubnetMaskField() cidr = models.CharField(max_length=18, blank=True, verbose_name="CIDR") @python_2_unicode_compatible class Company(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name class Meta: ordering = ['name'] @python_2_unicode_compatible class Location(models.Model): company = models.ForeignKey(Company, related_name='locations', on_delete=models.CASCADE) name = models.CharField(max_length=100) zip_code = models.CharField(max_length=10) open_days = models.CharField(max_length=7) def __str__(self): return '%s: %s' % (self.company.name, self.name) class Account(models.Model): name = models.CharField(max_length=100) in_good_standing = models.BooleanField(default=False) friendly = models.BooleanField(default=False) class Profile(models.Model): account = models.OneToOneField(Account, related_name='profile', on_delete=models.CASCADE) likes_coffee = models.BooleanField(default=False) likes_tea = models.BooleanField(default=False) class BankAccount(Account): amount_saved = models.IntegerField(default=0) class Node(models.Model): name = models.CharField(max_length=20) adjacents = models.ManyToManyField('self') class DirectedNode(models.Model): name = models.CharField(max_length=20) outbound_nodes = models.ManyToManyField('self', symmetrical=False, related_name='inbound_nodes') class Worker(models.Model): name = models.CharField(max_length=100) class HiredWorker(models.Model): salary = models.IntegerField() hired_on = models.DateField() worker = models.ForeignKey(Worker, on_delete=models.CASCADE) business = models.ForeignKey('Business', on_delete=models.CASCADE) class Business(models.Model): name = models.CharField(max_length=100) employees = models.ManyToManyField(Worker, through=HiredWorker, related_name='employers') class UUIDTestModel(models.Model): uuid = models.UUIDField() class SpacewalkRecord(models.Model): """Cumulative space walk record. See: https://en.wikipedia.org/wiki/List_of_cumulative_spacewalk_records """ astronaut = models.CharField(max_length=100) duration = models.DurationField() django-filter-1.1.0/tests/rest_framework/0000755000076500000240000000000013172070045021216 5ustar carltonstaff00000000000000django-filter-1.1.0/tests/rest_framework/__init__.py0000644000076500000240000000011112773017247023333 0ustar carltonstaff00000000000000default_app_config = 'tests.rest_framework.apps.RestFrameworkTestConfig' django-filter-1.1.0/tests/rest_framework/apps.py0000644000076500000240000000027112773017247022546 0ustar carltonstaff00000000000000 from django.apps import AppConfig class RestFrameworkTestConfig(AppConfig): name = 'tests.rest_framework' label = 'drf_test_app' verbose_name = "Rest Framework Test App" django-filter-1.1.0/tests/rest_framework/models.py0000644000076500000240000000122112770314705023056 0ustar carltonstaff00000000000000 from django.db import models from django.utils.translation import ugettext_lazy as _ class BasicModel(models.Model): text = models.CharField( max_length=100, verbose_name=_("Text comes here"), help_text=_("Text description.") ) class BaseFilterableItem(models.Model): text = models.CharField(max_length=100) class FilterableItem(BaseFilterableItem): decimal = models.DecimalField(max_digits=4, decimal_places=2) date = models.DateField() class DjangoFilterOrderingModel(models.Model): date = models.DateField() text = models.CharField(max_length=10) class Meta: ordering = ['-date'] django-filter-1.1.0/tests/rest_framework/templates/0000755000076500000240000000000013172070045023214 5ustar carltonstaff00000000000000django-filter-1.1.0/tests/rest_framework/templates/filter_template.html0000644000076500000240000000000512773017247027270 0ustar carltonstaff00000000000000Test django-filter-1.1.0/tests/rest_framework/test_backends.py0000644000076500000240000001754513172067725024430 0ustar carltonstaff00000000000000from __future__ import unicode_literals import datetime import warnings from decimal import Decimal from unittest import skipIf from django.db.models import BooleanField from django.test import TestCase from django.test.utils import override_settings from rest_framework import generics, serializers from rest_framework.test import APIRequestFactory from django_filters import compat, filters from django_filters.rest_framework import ( DjangoFilterBackend, FilterSet, backends ) from .models import FilterableItem factory = APIRequestFactory() class FilterableItemSerializer(serializers.ModelSerializer): class Meta: model = FilterableItem fields = '__all__' # Basic filter on a list view. class FilterFieldsRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_fields = ['decimal', 'date'] filter_backends = (DjangoFilterBackend,) # These class are used to test a filter class. class SeveralFieldsFilter(FilterSet): text = filters.CharFilter(lookup_expr='icontains') decimal = filters.NumberFilter(lookup_expr='lt') date = filters.DateFilter(lookup_expr='gt') class Meta: model = FilterableItem fields = ['text', 'decimal', 'date'] class FilterClassRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (DjangoFilterBackend,) @skipIf(compat.coreapi is None, 'coreapi must be installed') class GetSchemaFieldsTests(TestCase): def test_fields_with_filter_fields_list(self): backend = DjangoFilterBackend() fields = backend.get_schema_fields(FilterFieldsRootView()) fields = [f.name for f in fields] self.assertEqual(fields, ['decimal', 'date']) def test_filter_fields_list_with_bad_get_queryset(self): """ See: * https://github.com/carltongibson/django-filter/issues/551 """ class BadGetQuerySetView(FilterFieldsRootView): def get_queryset(self): raise AttributeError("I don't have that") backend = DjangoFilterBackend() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") fields = backend.get_schema_fields(BadGetQuerySetView()) self.assertEqual(fields, [], "get_schema_fields should handle AttributeError") warning = "{} is not compatible with schema generation".format(BadGetQuerySetView) self.assertEqual(len(w), 1) self.assertEqual(str(w[0].message), warning) def test_fields_with_filter_fields_dict(self): class DictFilterFieldsRootView(FilterFieldsRootView): filter_fields = { 'decimal': ['exact', 'lt', 'gt'], } backend = DjangoFilterBackend() fields = backend.get_schema_fields(DictFilterFieldsRootView()) fields = [f.name for f in fields] self.assertEqual(fields, ['decimal', 'decimal__lt', 'decimal__gt']) def test_fields_with_filter_class(self): backend = DjangoFilterBackend() fields = backend.get_schema_fields(FilterClassRootView()) schemas = [f.schema for f in fields] fields = [f.name for f in fields] self.assertEqual(fields, ['text', 'decimal', 'date']) self.assertIsInstance(schemas[0], compat.coreschema.String) self.assertIsInstance(schemas[1], compat.coreschema.Number) self.assertIsInstance(schemas[2], compat.coreschema.String) def test_field_required(self): class RequiredFieldsFilter(SeveralFieldsFilter): required_text = filters.CharFilter(required=True) class Meta(SeveralFieldsFilter.Meta): fields = SeveralFieldsFilter.Meta.fields + ['required_text'] class FilterClassWithRequiredFieldsView(FilterClassRootView): filter_class = RequiredFieldsFilter backend = DjangoFilterBackend() fields = backend.get_schema_fields(FilterClassWithRequiredFieldsView()) required = [f.required for f in fields] fields = [f.name for f in fields] self.assertEqual(fields, ['text', 'decimal', 'date', 'required_text']) self.assertFalse(required[0]) self.assertFalse(required[1]) self.assertFalse(required[2]) self.assertTrue(required[3]) def tests_field_with_request_callable(self): def qs(request): # users expect a valid request object to be provided which cannot # be guaranteed during schema generation. self.fail("callable queryset should not be invoked during schema generation") class F(SeveralFieldsFilter): f = filters.ModelChoiceFilter(queryset=qs) class View(FilterClassRootView): filter_class = F view = View() view.request = factory.get('/') backend = DjangoFilterBackend() fields = backend.get_schema_fields(view) fields = [f.name for f in fields] self.assertEqual(fields, ['text', 'decimal', 'date', 'f']) class TemplateTests(TestCase): def test_backend_output(self): """ Ensure backend renders default if template path does not exist """ view = FilterFieldsRootView() backend = view.filter_backends[0] request = view.initialize_request(factory.get('/')) html = backend().to_html(request, view.get_queryset(), view) self.assertHTMLEqual(html, """

    Field filters

    """) def test_template_path(self): view = FilterFieldsRootView() class Backend(view.filter_backends[0]): template = 'filter_template.html' request = view.initialize_request(factory.get('/')) html = Backend().to_html(request, view.get_queryset(), view) self.assertHTMLEqual(html, "Test") @override_settings(TEMPLATES=[]) def test_DTL_missing(self): # The backend should be importable even if the DTL is not used. # See: https://github.com/carltongibson/django-filter/issues/506 try: from importlib import reload # python 3.4 except ImportError: from imp import reload reload(backends) def test_multiple_engines(self): # See: https://github.com/carltongibson/django-filter/issues/578 DTL = {'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True} ALT = {'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, 'NAME': 'alt'} # multiple DTL backends with override_settings(TEMPLATES=[DTL, ALT]): self.test_backend_output() class DefaultFilterSetTests(TestCase): def test_default_meta_inheritance(self): # https://github.com/carltongibson/django-filter/issues/663 class F(FilterSet): class Meta: filter_overrides = {BooleanField: {}} class Backend(DjangoFilterBackend): default_filter_set = F view = FilterFieldsRootView() backend = Backend() filter_class = backend.get_filter_class(view, view.get_queryset()) filter_overrides = filter_class._meta.filter_overrides # derived filter_class.Meta should inherit from default_filter_set.Meta self.assertIn(BooleanField, filter_overrides) self.assertDictEqual(filter_overrides[BooleanField], {}) django-filter-1.1.0/tests/rest_framework/test_filters.py0000644000076500000240000000063613172067725024317 0ustar carltonstaff00000000000000 from django.test import TestCase from django_filters.rest_framework import filters from django_filters.widgets import BooleanWidget class BooleanFilterTests(TestCase): def test_widget(self): # Ensure that `BooleanFilter` uses the correct widget when importing # from `rest_framework.filters`. f = filters.BooleanFilter() self.assertEqual(f.extra['widget'], BooleanWidget) django-filter-1.1.0/tests/rest_framework/test_filterset.py0000644000076500000240000000301613172067725024643 0ustar carltonstaff00000000000000from unittest import skipIf from django.conf import settings from django.test import TestCase from django.test.utils import override_settings from django_filters.compat import is_crispy from django_filters.rest_framework import FilterSet, filters from django_filters.widgets import BooleanWidget from ..models import Article, User class ArticleFilter(FilterSet): class Meta: model = Article fields = ['author'] class FilterSetFilterForFieldTests(TestCase): def test_isodatetimefilter(self): field = Article._meta.get_field('published') result = FilterSet.filter_for_field(field, 'published') self.assertIsInstance(result, filters.IsoDateTimeFilter) self.assertEqual(result.name, 'published') def test_booleanfilter_widget(self): field = User._meta.get_field('is_active') result = FilterSet.filter_for_field(field, 'is_active') self.assertIsInstance(result, filters.BooleanFilter) self.assertEqual(result.extra['widget'], BooleanWidget) @skipIf(is_crispy(), 'django_crispy_forms must be installed') @override_settings(INSTALLED_APPS=settings.INSTALLED_APPS + ('crispy_forms', )) class CrispyFormsCompatTests(TestCase): def test_crispy_helper(self): # ensure the helper is present on the form self.assertTrue(hasattr(ArticleFilter().form, 'helper')) def test_form_initialization(self): # ensure that crispy compat does not prematurely initialize the form self.assertFalse(hasattr(ArticleFilter(), '_form')) django-filter-1.1.0/tests/rest_framework/test_integration.py0000644000076500000240000003511113172067725025166 0ustar carltonstaff00000000000000from __future__ import unicode_literals import datetime from decimal import Decimal from django.conf.urls import url from django.test import TestCase from django.test.utils import override_settings from django.utils.dateparse import parse_date from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory from django_filters import STRICTNESS, filters from django_filters.rest_framework import DjangoFilterBackend, FilterSet from .models import ( BaseFilterableItem, BasicModel, DjangoFilterOrderingModel, FilterableItem ) try: from django.urls import reverse except ImportError: # Django < 1.10 compatibility from django.core.urlresolvers import reverse factory = APIRequestFactory() class FilterableItemSerializer(serializers.ModelSerializer): class Meta: model = FilterableItem fields = '__all__' # Basic filter on a list view. class FilterFieldsRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_fields = ['decimal', 'date'] filter_backends = (DjangoFilterBackend,) # These class are used to test a filter class. class SeveralFieldsFilter(FilterSet): text = filters.CharFilter(lookup_expr='icontains') decimal = filters.NumberFilter(lookup_expr='lt') date = filters.DateFilter(lookup_expr='gt') class Meta: model = FilterableItem fields = ['text', 'decimal', 'date'] class FilterClassRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (DjangoFilterBackend,) # These classes are used to test a misconfigured filter class. class MisconfiguredFilter(FilterSet): text = filters.CharFilter(lookup_expr='icontains') class Meta: model = BasicModel fields = ['text'] class IncorrectlyConfiguredRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = MisconfiguredFilter filter_backends = (DjangoFilterBackend,) class FilterClassDetailView(generics.RetrieveAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (DjangoFilterBackend,) # These classes are used to test base model filter support class BaseFilterableItemFilter(FilterSet): text = filters.CharFilter() class Meta: model = BaseFilterableItem fields = '__all__' class BaseFilterableItemFilterRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = BaseFilterableItemFilter filter_backends = (DjangoFilterBackend,) # Regression test for #814 class FilterFieldsQuerysetView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_fields = ['decimal', 'date'] filter_backends = (DjangoFilterBackend,) class GetQuerysetView(generics.ListCreateAPIView): serializer_class = FilterableItemSerializer filter_class = SeveralFieldsFilter filter_backends = (DjangoFilterBackend,) def get_queryset(self): return FilterableItem.objects.all() urlpatterns = [ url(r'^(?P\d+)/$', FilterClassDetailView.as_view(), name='detail-view'), url(r'^$', FilterClassRootView.as_view(), name='root-view'), url(r'^get-queryset/$', GetQuerysetView.as_view(), name='get-queryset-view'), ] class CommonFilteringTestCase(TestCase): def _serialize_object(self, obj): return {'id': obj.id, 'text': obj.text, 'decimal': str(obj.decimal), 'date': obj.date.isoformat()} def setUp(self): """ Create 10 FilterableItem instances. """ base_data = ('a', Decimal('0.25'), datetime.date(2012, 10, 8)) for i in range(10): text = chr(i + ord(base_data[0])) * 3 # Produces string 'aaa', 'bbb', etc. decimal = base_data[1] + i date = base_data[2] - datetime.timedelta(days=i * 2) FilterableItem(text=text, decimal=decimal, date=date).save() self.objects = FilterableItem.objects self.data = [ self._serialize_object(obj) for obj in self.objects.all() ] class IntegrationTestFiltering(CommonFilteringTestCase): """ Integration tests for filtered list views. """ def test_get_filtered_fields_root_view(self): """ GET requests to paginated ListCreateAPIView should return paginated results. """ view = FilterFieldsRootView.as_view() # Basic test with no filter. request = factory.get('/') response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, self.data) # Tests that the decimal filter works. search_decimal = Decimal('2.25') request = factory.get('/', {'decimal': '%s' % search_decimal}) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal] self.assertEqual(response.data, expected_data) # Tests that the date filter works. search_date = datetime.date(2012, 9, 22) request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-09-22' response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if parse_date(f['date']) == search_date] self.assertEqual(response.data, expected_data) def test_filter_with_queryset(self): """ Regression test for #814. """ view = FilterFieldsQuerysetView.as_view() # Tests that the decimal filter works. search_decimal = Decimal('2.25') request = factory.get('/', {'decimal': '%s' % search_decimal}) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal] self.assertEqual(response.data, expected_data) def test_filter_with_get_queryset_only(self): """ Regression test for #834. """ view = GetQuerysetView.as_view() request = factory.get('/get-queryset/') view(request).render() # Used to raise "issubclass() arg 2 must be a class or tuple of classes" # here when neither `model' nor `queryset' was specified. def test_get_filtered_class_root_view(self): """ GET requests to filtered ListCreateAPIView that have a filter_class set should return filtered results. """ view = FilterClassRootView.as_view() # Basic test with no filter. request = factory.get('/') response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, self.data) # Tests that the decimal filter set with 'lt' in the filter class works. search_decimal = Decimal('4.25') request = factory.get('/', {'decimal': '%s' % search_decimal}) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if Decimal(f['decimal']) < search_decimal] self.assertEqual(response.data, expected_data) # Tests that the date filter set with 'gt' in the filter class works. search_date = datetime.date(2012, 10, 2) request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02' response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if parse_date(f['date']) > search_date] self.assertEqual(response.data, expected_data) # Tests that the text filter set with 'icontains' in the filter class works. search_text = 'ff' request = factory.get('/', {'text': '%s' % search_text}) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if search_text in f['text'].lower()] self.assertEqual(response.data, expected_data) # Tests that multiple filters works. search_decimal = Decimal('5.25') search_date = datetime.date(2012, 10, 2) request = factory.get('/', { 'decimal': '%s' % (search_decimal,), 'date': '%s' % (search_date,) }) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) expected_data = [f for f in self.data if parse_date(f['date']) > search_date and Decimal(f['decimal']) < search_decimal] self.assertEqual(response.data, expected_data) def test_incorrectly_configured_filter(self): """ An error should be displayed when the filter class is misconfigured. """ view = IncorrectlyConfiguredRootView.as_view() request = factory.get('/') self.assertRaises(AssertionError, view, request) def test_base_model_filter(self): """ The `get_filter_class` model checks should allow base model filters. """ view = BaseFilterableItemFilterRootView.as_view() request = factory.get('/?text=aaa') response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) def test_unknown_filter(self): """ GET requests with filters that aren't configured should return 200. """ view = FilterFieldsRootView.as_view() search_integer = 10 request = factory.get('/', {'integer': '%s' % search_integer}) response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) def test_html_rendering(self): """ Make sure response renders w/ backend """ view = FilterFieldsRootView.as_view() request = factory.get('/') request.META['HTTP_ACCEPT'] = 'text/html' response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) @override_settings(FILTERS_STRICTNESS=STRICTNESS.RAISE_VALIDATION_ERROR) def test_strictness_validation_error(self): """ Ensure validation errors return a proper error response instead of an internal server error. """ view = FilterFieldsRootView.as_view() request = factory.get('/?decimal=foobar') response = view(request).render() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data, {'decimal': ['Enter a number.']}) @override_settings(ROOT_URLCONF='tests.rest_framework.test_integration') class IntegrationTestDetailFiltering(CommonFilteringTestCase): """ Integration tests for filtered detail views. """ def _get_url(self, item): return reverse('detail-view', kwargs=dict(pk=item.pk)) def test_get_filtered_detail_view(self): """ GET requests to filtered RetrieveAPIView that have a filter_class set should return filtered results. """ item = self.objects.all()[0] data = self._serialize_object(item) # Basic test with no filter. response = self.client.get(self._get_url(item)) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, data) # Tests that the decimal filter set that should fail. search_decimal = Decimal('4.25') high_item = self.objects.filter(decimal__gt=search_decimal)[0] response = self.client.get( '{url}'.format(url=self._get_url(high_item)), {'decimal': '{param}'.format(param=search_decimal)}) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) # Tests that the decimal filter set that should succeed. search_decimal = Decimal('4.25') low_item = self.objects.filter(decimal__lt=search_decimal)[0] low_item_data = self._serialize_object(low_item) response = self.client.get( '{url}'.format(url=self._get_url(low_item)), {'decimal': '{param}'.format(param=search_decimal)}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, low_item_data) # Tests that multiple filters works. search_decimal = Decimal('5.25') search_date = datetime.date(2012, 10, 2) valid_item = self.objects.filter(decimal__lt=search_decimal, date__gt=search_date)[0] valid_item_data = self._serialize_object(valid_item) response = self.client.get( '{url}'.format(url=self._get_url(valid_item)), { 'decimal': '{decimal}'.format(decimal=search_decimal), 'date': '{date}'.format(date=search_date) }) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, valid_item_data) class DjangoFilterOrderingSerializer(serializers.ModelSerializer): class Meta: model = DjangoFilterOrderingModel fields = '__all__' class DjangoFilterOrderingTests(TestCase): def setUp(self): data = [{ 'date': datetime.date(2012, 10, 8), 'text': 'abc' }, { 'date': datetime.date(2013, 10, 8), 'text': 'bcd' }, { 'date': datetime.date(2014, 10, 8), 'text': 'cde' }] for d in data: DjangoFilterOrderingModel.objects.create(**d) def test_default_ordering(self): class DjangoFilterOrderingView(generics.ListAPIView): serializer_class = DjangoFilterOrderingSerializer queryset = DjangoFilterOrderingModel.objects.all() filter_backends = (DjangoFilterBackend,) filter_fields = ['text'] ordering = ('-date',) view = DjangoFilterOrderingView.as_view() request = factory.get('/') response = view(request) self.assertEqual( response.data, [ {'id': 3, 'date': '2014-10-08', 'text': 'cde'}, {'id': 2, 'date': '2013-10-08', 'text': 'bcd'}, {'id': 1, 'date': '2012-10-08', 'text': 'abc'} ] ) django-filter-1.1.0/tests/settings.py0000644000076500000240000000147613172067725020421 0ustar carltonstaff00000000000000 # ensure package/conf is importable from django_filters import STRICTNESS from django_filters.conf import DEFAULTS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, } INSTALLED_APPS = ( 'django.contrib.contenttypes', 'django.contrib.staticfiles', 'django.contrib.auth', 'rest_framework', 'django_filters', 'tests.rest_framework', 'tests', ) MIDDLEWARE = [] ROOT_URLCONF = 'tests.urls' USE_TZ = True SECRET_KEY = 'foobar' TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, }] STATIC_URL = '/static/' # help verify that DEFAULTS is importable from conf. def FILTERS_VERBOSE_LOOKUPS(): return DEFAULTS['VERBOSE_LOOKUPS'] FILTERS_STRICTNESS = STRICTNESS.RETURN_NO_RESULTS django-filter-1.1.0/tests/tags0000644000076500000240000043156213006452203017054 0ustar carltonstaff00000000000000!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ !_TAG_PROGRAM_AUTHOR Universal Ctags Team // !_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/ !_TAG_PROGRAM_URL https://ctags.io/ /official site/ !_TAG_PROGRAM_VERSION 0.0.0 /6e5c9a9/ ADMIN models.py 11;" v line:11 access:public Account models.py 156;" c line:156 access:public AdminUser models.py 77;" c line:77 access:public AllValuesFilterTests test_filtering.py 891;" c line:891 access:public AllValuesFilterTests test_filters.py 976;" c line:976 access:public AllValuesMultipleFilterTests test_filtering.py 931;" c line:931 access:public Article models.py 97;" c line:97 access:public ArticleFilter test_filtering.py 1531;" c line:1531 function:CSVFilterTests.setUp file: access:private Backend rest_framework/test_backends.py 351;" c line:351 function:IntegrationTestFiltering.test_template_path file: access:private BankAccount models.py 168;" c line:168 access:public BaseCSVFieldTests test_fields.py 176;" c line:176 access:public BaseFilterableItem rest_framework/models.py 14;" c line:14 access:public BaseFilterableItemFilter rest_framework/test_backends.py 86;" c line:86 access:public BaseFilterableItemFilterRootView rest_framework/test_backends.py 94;" c line:94 access:public BaseInFilterTests test_filters.py 1085;" c line:1085 access:public BaseRangeFieldTests test_fields.py 217;" c line:217 access:public BaseRangeFilterTests test_filters.py 1096;" c line:1096 access:public BasicModel rest_framework/models.py 6;" c line:6 access:public Book models.py 109;" c line:109 access:public BooleanFilterTests rest_framework/test_filters.py 8;" c line:8 access:public BooleanFilterTests test_filtering.py 97;" c line:97 access:public BooleanFilterTests test_filters.py 259;" c line:259 access:public BooleanWidgetTests test_widgets.py 148;" c line:148 access:public Business models.py 195;" c line:195 access:public CSVFilterTests test_filtering.py 1506;" c line:1506 access:public CSVFilterTests test_filters.py 1039;" c line:1039 access:public CSVSelect test_widgets.py 234;" c line:234 class:CSVSelectTests access:public CSVSelectTests test_widgets.py 233;" c line:233 access:public CSVWidgetTests test_widgets.py 177;" c line:177 access:public CharFilterTests test_filtering.py 48;" c line:48 access:public CharFilterTests test_filters.py 243;" c line:243 access:public ChoiceFilterTests test_filtering.py 126;" c line:126 access:public ChoiceFilterTests test_filters.py 302;" c line:302 access:public Comment models.py 86;" c line:86 access:public CommonFilteringTestCase rest_framework/test_backends.py 154;" c line:154 access:public Company models.py 135;" c line:135 access:public CustomFilterWithBooleanCheckTests test_filters.py 216;" c line:216 access:public CustomTestFilter test_filters.py 221;" c line:221 function:CustomFilterWithBooleanCheckTests.setUp file: access:private DATABASES settings.py 1;" v line:1 access:public DateFilterTests test_filtering.py 254;" c line:254 access:public DateFilterTests test_filters.py 523;" c line:523 access:public DateFromToRangeFilterTests test_filtering.py 795;" c line:795 access:public DateFromToRangeFilterTests test_filters.py 842;" c line:842 access:public DateRangeFieldTests test_fields.py 51;" c line:51 access:public DateRangeFilterTests test_filtering.py 723;" c line:723 access:public DateRangeFilterTests test_filters.py 759;" c line:759 access:public DateTimeFilterTests test_filtering.py 301;" c line:301 access:public DateTimeFilterTests test_filters.py 531;" c line:531 access:public DateTimeFromToRangeFilterTests test_filtering.py 841;" c line:841 access:public DateTimeFromToRangeFilterTests test_filters.py 886;" c line:886 access:public DateTimeRangeFieldTests test_fields.py 67;" c line:67 access:public DateTimeYearInFilter test_filters.py 1044;" c line:1044 function:CSVFilterTests.setUp file: access:private DbFieldDefaultFiltersTests test_filterset.py 68;" c line:68 access:public DecimalCSVField test_fields.py 178;" c line:178 function:BaseCSVFieldTests.setUp file: access:private DecimalRangeField test_fields.py 219;" c line:219 function:BaseRangeFieldTests.setUp file: access:private DefaultSettingsTests test_conf.py 10;" c line:10 access:public DictFilterFieldsRootView rest_framework/test_backends.py 135;" c line:135 function:GetSchemaFieldsTests.test_fields_with_filter_fields_dict file: access:private DirectedNode models.py 177;" c line:177 access:public DjangoFilterOrderingModel rest_framework/models.py 23;" c line:23 access:public DjangoFilterOrderingSerializer rest_framework/test_backends.py 424;" c line:424 access:public DjangoFilterOrderingTests rest_framework/test_backends.py 430;" c line:430 access:public DjangoFilterOrderingView rest_framework/test_backends.py 447;" c line:447 function:DjangoFilterOrderingTests.test_default_ordering file: access:private DurationFilterTests test_filtering.py 336;" c line:336 access:public DurationFilterTests test_filters.py 547;" c line:547 access:public F test_conf.py 39;" c line:39 class:StrictnessTests access:public F test_filtering.py 1014;" c line:1014 function:O2ORelationshipTests.test_o2o_relation file: access:private F test_filtering.py 1028;" c line:1028 function:O2ORelationshipTests.test_o2o_relation_dictionary file: access:private F test_filtering.py 1041;" c line:1041 function:O2ORelationshipTests.test_reverse_o2o_relation file: access:private F test_filtering.py 104;" c line:104 function:BooleanFilterTests.test_filtering file: access:private F test_filtering.py 1054;" c line:1054 function:O2ORelationshipTests.test_o2o_relation_attribute file: access:private F test_filtering.py 1067;" c line:1067 function:O2ORelationshipTests.test_o2o_relation_attribute2 file: access:private F test_filtering.py 1080;" c line:1080 function:O2ORelationshipTests.test_reverse_o2o_relation_attribute file: access:private F test_filtering.py 1093;" c line:1093 function:O2ORelationshipTests.test_reverse_o2o_relation_attribute2 file: access:private F test_filtering.py 1118;" c line:1118 function:FKRelationshipTests.test_fk_relation file: access:private F test_filtering.py 1142;" c line:1142 function:FKRelationshipTests.test_reverse_fk_relation file: access:private F test_filtering.py 1151;" c line:1151 function:FKRelationshipTests.test_reverse_fk_relation file: access:private F test_filtering.py 1171;" c line:1171 function:FKRelationshipTests.test_fk_relation_attribute file: access:private F test_filtering.py 1180;" c line:1180 function:FKRelationshipTests.test_fk_relation_attribute file: access:private F test_filtering.py 1201;" c line:1201 function:FKRelationshipTests.test_reverse_fk_relation_attribute file: access:private F test_filtering.py 1210;" c line:1210 function:FKRelationshipTests.test_reverse_fk_relation_attribute file: access:private F test_filtering.py 1232;" c line:1232 function:FKRelationshipTests.test_reverse_fk_relation_multiple_attributes file: access:private F test_filtering.py 1260;" c line:1260 function:M2MRelationshipTests.test_m2m_relation file: access:private F test_filtering.py 1279;" c line:1279 function:M2MRelationshipTests.test_reverse_m2m_relation file: access:private F test_filtering.py 1289;" c line:1289 function:M2MRelationshipTests.test_reverse_m2m_relation file: access:private F test_filtering.py 1301;" c line:1301 function:M2MRelationshipTests.test_m2m_relation_attribute file: access:private F test_filtering.py 1313;" c line:1313 function:M2MRelationshipTests.test_m2m_relation_attribute file: access:private F test_filtering.py 1328;" c line:1328 function:M2MRelationshipTests.test_m2m_relation_attribute file: access:private F test_filtering.py 1339;" c line:1339 function:M2MRelationshipTests.test_reverse_m2m_relation_attribute file: access:private F test_filtering.py 1352;" c line:1352 function:M2MRelationshipTests.test_reverse_m2m_relation_attribute file: access:private F test_filtering.py 1367;" c line:1367 function:M2MRelationshipTests.test_reverse_m2m_relation_attribute file: access:private F test_filtering.py 1380;" c line:1380 function:M2MRelationshipTests.test_m2m_relation_multiple_attributes file: access:private F test_filtering.py 1399;" c line:1399 function:M2MRelationshipTests.test_reverse_m2m_relation_multiple_attributes file: access:private F test_filtering.py 142;" c line:142 function:ChoiceFilterTests.test_filtering file: access:private F test_filtering.py 1434;" c line:1434 function:SymmetricalSelfReferentialRelationshipTests.test_relation file: access:private F test_filtering.py 1457;" c line:1457 function:NonSymmetricalSelfReferentialRelationshipTests.test_forward_relation file: access:private F test_filtering.py 1467;" c line:1467 function:NonSymmetricalSelfReferentialRelationshipTests.test_reverse_relation file: access:private F test_filtering.py 1492;" c line:1492 function:TransformedQueryExpressionFilterTests.test_filtering file: access:private F test_filtering.py 1658;" c line:1658 function:OrderingFilterTests.test_ordering file: access:private F test_filtering.py 1673;" c line:1673 function:OrderingFilterTests.test_ordering_with_select_widget file: access:private F test_filtering.py 167;" c line:167 function:ChoiceFilterTests.test_filtering_on_explicitly_defined_field file: access:private F test_filtering.py 1698;" c line:1698 function:MiscFilterSetTests.test_filtering_with_declared_filters file: access:private F test_filtering.py 1712;" c line:1712 function:MiscFilterSetTests.test_filtering_with_multiple_filters file: access:private F test_filtering.py 1728;" c line:1728 function:MiscFilterSetTests.test_filter_with_initial file: access:private F test_filtering.py 1745;" c line:1745 function:MiscFilterSetTests.test_qs_count file: access:private F test_filtering.py 1771;" c line:1771 function:MiscFilterSetTests.test_invalid_field_lookup file: access:private F test_filtering.py 189;" c line:189 function:ChoiceFilterTests.test_filtering_on_empty_choice file: access:private F test_filtering.py 202;" c line:202 function:ChoiceFilterTests.test_filtering_on_null_choice file: access:private F test_filtering.py 228;" c line:228 function:MultipleChoiceFilterTests.test_filtering file: access:private F test_filtering.py 267;" c line:267 function:DateFilterTests.test_filtering file: access:private F test_filtering.py 291;" c line:291 function:TimeFilterTests.test_filtering file: access:private F test_filtering.py 317;" c line:317 function:DateTimeFilterTests.test_filtering file: access:private F test_filtering.py 367;" c line:367 function:DurationFilterTests.test_filtering file: access:private F test_filtering.py 392;" c line:392 function:DurationFilterTests.test_filtering_with_single_lookup_expr_dictionary file: access:private F test_filtering.py 417;" c line:417 function:DurationFilterTests.test_filtering_with_multiple_lookup_exprs file: access:private F test_filtering.py 442;" c line:442 function:ModelChoiceFilterTests.test_filtering file: access:private F test_filtering.py 461;" c line:461 function:ModelChoiceFilterTests.test_callable_queryset file: access:private F test_filtering.py 500;" c line:500 function:ModelMultipleChoiceFilterTests.test_filtering file: access:private F test_filtering.py 519;" c line:519 function:ModelMultipleChoiceFilterTests.test_filtering_dictionary file: access:private F test_filtering.py 538;" c line:538 function:ModelMultipleChoiceFilterTests.test_filtering_on_all_of_subset_of_choices file: access:private F test_filtering.py 563;" c line:563 function:ModelMultipleChoiceFilterTests.test_filtering_on_non_required_fields file: access:private F test_filtering.py 58;" c line:58 function:CharFilterTests.test_filtering file: access:private F test_filtering.py 602;" c line:602 function:NumberFilterTests.test_filtering file: access:private F test_filtering.py 611;" c line:611 function:NumberFilterTests.test_filtering_with_single_lookup_expr file: access:private F test_filtering.py 623;" c line:623 function:NumberFilterTests.test_filtering_with_single_lookup_expr_dictionary file: access:private F test_filtering.py 633;" c line:633 function:NumberFilterTests.test_filtering_with_multiple_lookup_exprs file: access:private F test_filtering.py 650;" c line:650 function:NumberFilterTests.test_filtering_with_multiple_lookup_exprs file: access:private F test_filtering.py 676;" c line:676 function:RangeFilterTests.test_filtering file: access:private F test_filtering.py 741;" c line:741 function:DateRangeFilterTests.test_filtering_for_year file: access:private F test_filtering.py 755;" c line:755 function:DateRangeFilterTests.test_filtering_for_month file: access:private F test_filtering.py 770;" c line:770 function:DateRangeFilterTests.test_filtering_for_week file: access:private F test_filtering.py 781;" c line:781 function:DateRangeFilterTests.test_filtering_for_today file: access:private F test_filtering.py 805;" c line:805 function:DateFromToRangeFilterTests.test_filtering file: access:private F test_filtering.py 828;" c line:828 function:DateFromToRangeFilterTests.test_filtering_ignores_time file: access:private F test_filtering.py 82;" c line:82 function:IntegerFilterTest.test_filtering file: access:private F test_filtering.py 854;" c line:854 function:DateTimeFromToRangeFilterTests.test_filtering file: access:private F test_filtering.py 878;" c line:878 function:TimeRangeFilterTests.test_filtering file: access:private F test_filtering.py 898;" c line:898 function:AllValuesFilterTests.test_filtering file: access:private F test_filtering.py 916;" c line:916 function:AllValuesFilterTests.test_filtering_without_strict file: access:private F test_filtering.py 938;" c line:938 function:AllValuesMultipleFilterTests.test_filtering file: access:private F test_filtering.py 962;" c line:962 function:FilterMethodTests.test_filtering file: access:private F test_filtering.py 982;" c line:982 function:FilterMethodTests.test_filtering_callable file: access:private F test_filters.py 586;" c line:586 function:ModelChoiceFilterTests.test_get_queryset_override file: access:private F test_filterset.py 298;" c line:298 function:FilterSetClassCreationTests.test_no_filters file: access:private F test_filterset.py 305;" c line:305 function:FilterSetClassCreationTests.test_declaring_filter file: access:private F test_filterset.py 314;" c line:314 function:FilterSetClassCreationTests.test_model_derived file: access:private F test_filterset.py 325;" c line:325 function:FilterSetClassCreationTests.test_model_no_fields_or_exclude file: access:private F test_filterset.py 335;" c line:335 function:FilterSetClassCreationTests.test_model_exclude_is_none file: access:private F test_filterset.py 346;" c line:346 function:FilterSetClassCreationTests.test_model_exclude_empty file: access:private F test_filterset.py 357;" c line:357 function:FilterSetClassCreationTests.test_declared_and_model_derived file: access:private F test_filterset.py 370;" c line:370 function:FilterSetClassCreationTests.test_meta_fields_with_declared_and_model_derived file: access:private F test_filterset.py 382;" c line:382 function:FilterSetClassCreationTests.test_meta_fields_dictionary_derived file: access:private F test_filterset.py 395;" c line:395 function:FilterSetClassCreationTests.test_meta_fields_containing_autofield file: access:private F test_filterset.py 407;" c line:407 function:FilterSetClassCreationTests.test_meta_fields_dictionary_autofield file: access:private F test_filterset.py 424;" c line:424 function:FilterSetClassCreationTests.test_meta_fields_containing_unknown file: access:private F test_filterset.py 436;" c line:436 function:FilterSetClassCreationTests.test_meta_fields_dictionary_containing_unknown file: access:private F test_filterset.py 446;" c line:446 function:FilterSetClassCreationTests.test_meta_exlude_with_declared_and_declared_wins file: access:private F test_filterset.py 459;" c line:459 function:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins file: access:private F test_filterset.py 473;" c line:473 function:FilterSetClassCreationTests.test_meta_exlude_with_no_fields file: access:private F test_filterset.py 484;" c line:484 function:FilterSetClassCreationTests.test_filterset_class_inheritance file: access:private F test_filterset.py 493;" c line:493 function:FilterSetClassCreationTests.test_filterset_class_inheritance file: access:private F test_filterset.py 505;" c line:505 function:FilterSetClassCreationTests.test_abstract_model_inheritance file: access:private F test_filterset.py 512;" c line:512 function:FilterSetClassCreationTests.test_abstract_model_inheritance file: access:private F test_filterset.py 520;" c line:520 function:FilterSetClassCreationTests.test_custom_field_gets_filter_from_override file: access:private F test_filterset.py 532;" c line:532 function:FilterSetClassCreationTests.test_filterset_for_proxy_model file: access:private F test_filterset.py 545;" c line:545 function:FilterSetClassCreationTests.test_filterset_for_mti_model file: access:private F test_filterset.py 564;" c line:564 function:FilterSetInstantiationTests.test_creating_instance file: access:private F test_filterset.py 580;" c line:580 function:FilterSetInstantiationTests.test_creating_bound_instance file: access:private F test_filterset.py 589;" c line:589 function:FilterSetInstantiationTests.test_creating_with_queryset file: access:private F test_filterset.py 599;" c line:599 function:FilterSetInstantiationTests.test_creating_with_request file: access:private F test_filterset.py 612;" c line:612 function:FilterSetStrictnessTests.test_settings_default file: access:private F test_filterset.py 624;" c line:624 function:FilterSetStrictnessTests.test_meta_value file: access:private F test_filterset.py 632;" c line:632 function:FilterSetStrictnessTests.test_init_default file: access:private F test_filterset.py 641;" c line:641 function:FilterSetStrictnessTests.test_legacy_value file: access:private F test_filterset.py 656;" c line:656 function:FilterSetTogetherTests.test_fields_set file: access:private F test_filterset.py 674;" c line:674 function:FilterSetTogetherTests.test_single_fields_set file: access:private F test_filterset.py 704;" c line:704 function:FilterMethodTests.test_method_name file: access:private F test_filterset.py 719;" c line:719 function:FilterMethodTests.test_method_callable file: access:private F test_filterset.py 728;" c line:728 function:FilterMethodTests.test_request_available_during_method_called file: access:private F test_filterset.py 744;" c line:744 function:FilterMethodTests.test_method_with_overridden_filter file: access:private F test_filterset.py 768;" c line:768 function:FilterMethodTests.test_method_self_is_parent file: access:private F test_filterset.py 781;" c line:781 function:FilterMethodTests.test_method_unresolvable file: access:private F test_filterset.py 793;" c line:793 function:FilterMethodTests.test_method_uncallable file: access:private F test_forms.py 109;" c line:109 function:FilterSetFormTests.test_form_fields_using_widget file: access:private F test_forms.py 129;" c line:129 function:FilterSetFormTests.test_form_field_with_custom_label file: access:private F test_forms.py 141;" c line:141 function:FilterSetFormTests.test_form_field_with_manual_name file: access:private F test_forms.py 153;" c line:153 function:FilterSetFormTests.test_form_field_with_manual_name_and_label file: access:private F test_forms.py 165;" c line:165 function:FilterSetFormTests.test_filter_with_initial file: access:private F test_forms.py 176;" c line:176 function:FilterSetFormTests.test_form_is_not_bound file: access:private F test_forms.py 186;" c line:186 function:FilterSetFormTests.test_form_is_bound file: access:private F test_forms.py 19;" c line:19 function:FilterSetFormTests.test_form_from_empty_filterset file: access:private F test_forms.py 200;" c line:200 function:FilterSetFormTests.test_limit_choices_to file: access:private F test_forms.py 213;" c line:213 function:FilterSetFormTests.test_disabled_help_text file: access:private F test_forms.py 26;" c line:26 function:FilterSetFormTests.test_form file: access:private F test_forms.py 39;" c line:39 function:FilterSetFormTests.test_custom_form file: access:private F test_forms.py 49;" c line:49 function:FilterSetFormTests.test_form_prefix file: access:private F test_forms.py 61;" c line:61 function:FilterSetFormTests.test_form_fields file: access:private F test_forms.py 75;" c line:75 function:FilterSetFormTests.test_form_fields_exclusion file: access:private F test_forms.py 86;" c line:86 function:FilterSetFormTests.test_complex_form_fields file: access:private FILTERS_VERBOSE_LOOKUPS settings.py 35;" f line:35 access:public signature:() FKRelationshipTests test_filtering.py 1106;" c line:1106 access:public FilterClassDetailView rest_framework/test_backends.py 78;" c line:78 access:public FilterClassRootView rest_framework/test_backends.py 55;" c line:55 access:public FilterFieldsQuerysetView rest_framework/test_backends.py 102;" c line:102 access:public FilterFieldsRootView rest_framework/test_backends.py 37;" c line:37 access:public FilterMethodTests test_filtering.py 954;" c line:954 access:public FilterMethodTests test_filterset.py 690;" c line:690 access:public FilterSetClassCreationTests test_filterset.py 295;" c line:295 access:public FilterSetFilterForFieldTests rest_framework/test_filterset.py 10;" c line:10 access:public FilterSetFilterForFieldTests test_filterset.py 115;" c line:115 access:public FilterSetFilterForLookupTests test_filterset.py 207;" c line:207 access:public FilterSetFilterForReverseFieldTests test_filterset.py 247;" c line:247 access:public FilterSetFormTests test_forms.py 16;" c line:16 access:public FilterSetInstantiationTests test_filterset.py 561;" c line:561 access:public FilterSetStrictnessTests test_filterset.py 609;" c line:609 access:public FilterSetTogetherTests test_filterset.py 648;" c line:648 access:public FilterTests test_filters.py 73;" c line:73 access:public FilterableItem rest_framework/models.py 18;" c line:18 access:public FilterableItemSerializer rest_framework/test_backends.py 30;" c line:30 access:public FtiF test_filterset.py 550;" c line:550 function:FilterSetClassCreationTests.test_filterset_for_mti_model file: access:private G test_filterset.py 489;" c line:489 function:FilterSetClassCreationTests.test_filterset_class_inheritance file: access:private G test_filterset.py 500;" c line:500 function:FilterSetClassCreationTests.test_filterset_class_inheritance file: access:private GenericClassBasedViewTests test_views.py 26;" c line:26 access:public GenericFunctionalViewTests test_views.py 68;" c line:68 access:public GenericViewTestCase test_views.py 15;" c line:15 access:public GetFieldPartsTests test_utils.py 23;" c line:23 access:public GetModelFieldTests test_utils.py 51;" c line:51 access:public GetQuerysetView rest_framework/test_backends.py 109;" c line:109 access:public GetSchemaFieldsTests rest_framework/test_backends.py 126;" c line:126 access:public HelperMethodsTests test_filterset.py 53;" c line:53 access:public HiredWorker models.py 188;" c line:188 access:public INSTALLED_APPS settings.py 8;" v line:8 access:public IncorrectlyConfiguredRootView rest_framework/test_backends.py 71;" c line:71 access:public IntegerFilterTest test_filtering.py 71;" c line:71 access:public IntegrationTestDetailFiltering rest_framework/test_backends.py 372;" c line:372 access:public IntegrationTestFiltering rest_framework/test_backends.py 176;" c line:176 access:public IsoDateTimeFieldTests test_fields.py 129;" c line:129 access:public LabelForFilterTests test_utils.py 258;" c line:258 access:public LinkWidgetTests test_widgets.py 46;" c line:46 access:public Location models.py 146;" c line:146 access:public LookupBoolTests test_fields.py 22;" c line:22 access:public LookupTypeFieldTests test_fields.py 98;" c line:98 access:public LookupTypeWidgetTests test_widgets.py 15;" c line:15 access:public LookupTypesTests test_filters.py 995;" c line:995 access:public M2MRelationshipTests test_filtering.py 1242;" c line:1242 access:public MANAGER models.py 10;" v line:10 access:public MIDDLEWARE settings.py 17;" v line:17 access:public ManagerGroup models.py 63;" c line:63 access:public Meta models.py 121;" c line:121 class:Place access:public Meta models.py 141;" c line:141 class:Company access:public Meta models.py 78;" c line:78 class:AdminUser access:public Meta rest_framework/models.py 27;" c line:27 class:DjangoFilterOrderingModel access:public Meta rest_framework/test_backends.py 31;" c line:31 class:FilterableItemSerializer access:public Meta rest_framework/test_backends.py 425;" c line:425 class:DjangoFilterOrderingSerializer access:public Meta rest_framework/test_backends.py 50;" c line:50 class:SeveralFieldsFilter access:public Meta rest_framework/test_backends.py 66;" c line:66 class:MisconfiguredFilter access:public Meta rest_framework/test_backends.py 89;" c line:89 class:BaseFilterableItemFilter access:public Meta test_conf.py 40;" c line:40 class:StrictnessTests.F access:public Meta test_filtering.py 1015;" c line:1015 class:O2ORelationshipTests.test_o2o_relation.F access:public Meta test_filtering.py 1029;" c line:1029 class:O2ORelationshipTests.test_o2o_relation_dictionary.F access:public Meta test_filtering.py 1042;" c line:1042 class:O2ORelationshipTests.test_reverse_o2o_relation.F access:public Meta test_filtering.py 1055;" c line:1055 class:O2ORelationshipTests.test_o2o_relation_attribute.F access:public Meta test_filtering.py 105;" c line:105 class:BooleanFilterTests.test_filtering.F access:public Meta test_filtering.py 1068;" c line:1068 class:O2ORelationshipTests.test_o2o_relation_attribute2.F access:public Meta test_filtering.py 1081;" c line:1081 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute.F access:public Meta test_filtering.py 1094;" c line:1094 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute2.F access:public Meta test_filtering.py 1119;" c line:1119 class:FKRelationshipTests.test_fk_relation.F access:public Meta test_filtering.py 1143;" c line:1143 class:FKRelationshipTests.test_reverse_fk_relation.F access:public Meta test_filtering.py 1154;" c line:1154 class:FKRelationshipTests.test_reverse_fk_relation.F access:public Meta test_filtering.py 1172;" c line:1172 class:FKRelationshipTests.test_fk_relation_attribute.F access:public Meta test_filtering.py 1183;" c line:1183 class:FKRelationshipTests.test_fk_relation_attribute.F access:public Meta test_filtering.py 1202;" c line:1202 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F access:public Meta test_filtering.py 1213;" c line:1213 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F access:public Meta test_filtering.py 1233;" c line:1233 class:FKRelationshipTests.test_reverse_fk_relation_multiple_attributes.F access:public Meta test_filtering.py 1261;" c line:1261 class:M2MRelationshipTests.test_m2m_relation.F access:public Meta test_filtering.py 1280;" c line:1280 class:M2MRelationshipTests.test_reverse_m2m_relation.F access:public Meta test_filtering.py 1292;" c line:1292 class:M2MRelationshipTests.test_reverse_m2m_relation.F access:public Meta test_filtering.py 1302;" c line:1302 class:M2MRelationshipTests.test_m2m_relation_attribute.F access:public Meta test_filtering.py 1316;" c line:1316 class:M2MRelationshipTests.test_m2m_relation_attribute.F access:public Meta test_filtering.py 1331;" c line:1331 class:M2MRelationshipTests.test_m2m_relation_attribute.F access:public Meta test_filtering.py 1340;" c line:1340 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F access:public Meta test_filtering.py 1355;" c line:1355 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F access:public Meta test_filtering.py 1370;" c line:1370 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F access:public Meta test_filtering.py 1381;" c line:1381 class:M2MRelationshipTests.test_m2m_relation_multiple_attributes.F access:public Meta test_filtering.py 1400;" c line:1400 class:M2MRelationshipTests.test_reverse_m2m_relation_multiple_attributes.F access:public Meta test_filtering.py 1435;" c line:1435 class:SymmetricalSelfReferentialRelationshipTests.test_relation.F access:public Meta test_filtering.py 143;" c line:143 class:ChoiceFilterTests.test_filtering.F access:public Meta test_filtering.py 1458;" c line:1458 class:NonSymmetricalSelfReferentialRelationshipTests.test_forward_relation.F access:public Meta test_filtering.py 1468;" c line:1468 class:NonSymmetricalSelfReferentialRelationshipTests.test_reverse_relation.F access:public Meta test_filtering.py 1493;" c line:1493 class:TransformedQueryExpressionFilterTests.test_filtering.F access:public Meta test_filtering.py 1524;" c line:1524 class:CSVFilterTests.setUp.UserFilter access:public Meta test_filtering.py 1532;" c line:1532 class:CSVFilterTests.setUp.ArticleFilter access:public Meta test_filtering.py 1663;" c line:1663 class:OrderingFilterTests.test_ordering.F access:public Meta test_filtering.py 1679;" c line:1679 class:OrderingFilterTests.test_ordering_with_select_widget.F access:public Meta test_filtering.py 1701;" c line:1701 class:MiscFilterSetTests.test_filtering_with_declared_filters.F access:public Meta test_filtering.py 170;" c line:170 class:ChoiceFilterTests.test_filtering_on_explicitly_defined_field.F access:public Meta test_filtering.py 1713;" c line:1713 class:MiscFilterSetTests.test_filtering_with_multiple_filters.F access:public Meta test_filtering.py 1731;" c line:1731 class:MiscFilterSetTests.test_filter_with_initial.F access:public Meta test_filtering.py 1746;" c line:1746 class:MiscFilterSetTests.test_qs_count.F access:public Meta test_filtering.py 1772;" c line:1772 class:MiscFilterSetTests.test_invalid_field_lookup.F access:public Meta test_filtering.py 190;" c line:190 class:ChoiceFilterTests.test_filtering_on_empty_choice.F access:public Meta test_filtering.py 209;" c line:209 class:ChoiceFilterTests.test_filtering_on_null_choice.F access:public Meta test_filtering.py 231;" c line:231 class:MultipleChoiceFilterTests.test_filtering.F access:public Meta test_filtering.py 268;" c line:268 class:DateFilterTests.test_filtering.F access:public Meta test_filtering.py 292;" c line:292 class:TimeFilterTests.test_filtering.F access:public Meta test_filtering.py 318;" c line:318 class:DateTimeFilterTests.test_filtering.F access:public Meta test_filtering.py 368;" c line:368 class:DurationFilterTests.test_filtering.F access:public Meta test_filtering.py 393;" c line:393 class:DurationFilterTests.test_filtering_with_single_lookup_expr_dictionary.F access:public Meta test_filtering.py 421;" c line:421 class:DurationFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public Meta test_filtering.py 443;" c line:443 class:ModelChoiceFilterTests.test_filtering.F access:public Meta test_filtering.py 464;" c line:464 class:ModelChoiceFilterTests.test_callable_queryset.F access:public Meta test_filtering.py 501;" c line:501 class:ModelMultipleChoiceFilterTests.test_filtering.F access:public Meta test_filtering.py 520;" c line:520 class:ModelMultipleChoiceFilterTests.test_filtering_dictionary.F access:public Meta test_filtering.py 539;" c line:539 class:ModelMultipleChoiceFilterTests.test_filtering_on_all_of_subset_of_choices.F access:public Meta test_filtering.py 566;" c line:566 class:ModelMultipleChoiceFilterTests.test_filtering_on_non_required_fields.F access:public Meta test_filtering.py 59;" c line:59 class:CharFilterTests.test_filtering.F access:public Meta test_filtering.py 603;" c line:603 class:NumberFilterTests.test_filtering.F access:public Meta test_filtering.py 614;" c line:614 class:NumberFilterTests.test_filtering_with_single_lookup_expr.F access:public Meta test_filtering.py 624;" c line:624 class:NumberFilterTests.test_filtering_with_single_lookup_expr_dictionary.F access:public Meta test_filtering.py 636;" c line:636 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public Meta test_filtering.py 653;" c line:653 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public Meta test_filtering.py 679;" c line:679 class:RangeFilterTests.test_filtering.F access:public Meta test_filtering.py 744;" c line:744 class:DateRangeFilterTests.test_filtering_for_year.F access:public Meta test_filtering.py 758;" c line:758 class:DateRangeFilterTests.test_filtering_for_month.F access:public Meta test_filtering.py 773;" c line:773 class:DateRangeFilterTests.test_filtering_for_week.F access:public Meta test_filtering.py 784;" c line:784 class:DateRangeFilterTests.test_filtering_for_today.F access:public Meta test_filtering.py 808;" c line:808 class:DateFromToRangeFilterTests.test_filtering.F access:public Meta test_filtering.py 831;" c line:831 class:DateFromToRangeFilterTests.test_filtering_ignores_time.F access:public Meta test_filtering.py 83;" c line:83 class:IntegerFilterTest.test_filtering.F access:public Meta test_filtering.py 857;" c line:857 class:DateTimeFromToRangeFilterTests.test_filtering.F access:public Meta test_filtering.py 881;" c line:881 class:TimeRangeFilterTests.test_filtering.F access:public Meta test_filtering.py 901;" c line:901 class:AllValuesFilterTests.test_filtering.F access:public Meta test_filtering.py 919;" c line:919 class:AllValuesFilterTests.test_filtering_without_strict.F access:public Meta test_filtering.py 941;" c line:941 class:AllValuesMultipleFilterTests.test_filtering.F access:public Meta test_filtering.py 965;" c line:965 class:FilterMethodTests.test_filtering.F access:public Meta test_filtering.py 985;" c line:985 class:FilterMethodTests.test_filtering_callable.F access:public Meta test_filterset.py 231;" c line:231 class:FilterSetFilterForLookupTests.test_isnull_with_filter_overrides.OFilterSet access:public Meta test_filterset.py 315;" c line:315 class:FilterSetClassCreationTests.test_model_derived.F access:public Meta test_filterset.py 326;" c line:326 class:FilterSetClassCreationTests.test_model_no_fields_or_exclude.F access:public Meta test_filterset.py 336;" c line:336 class:FilterSetClassCreationTests.test_model_exclude_is_none.F access:public Meta test_filterset.py 347;" c line:347 class:FilterSetClassCreationTests.test_model_exclude_empty.F access:public Meta test_filterset.py 360;" c line:360 class:FilterSetClassCreationTests.test_declared_and_model_derived.F access:public Meta test_filterset.py 373;" c line:373 class:FilterSetClassCreationTests.test_meta_fields_with_declared_and_model_derived.F access:public Meta test_filterset.py 384;" c line:384 class:FilterSetClassCreationTests.test_meta_fields_dictionary_derived.F access:public Meta test_filterset.py 398;" c line:398 class:FilterSetClassCreationTests.test_meta_fields_containing_autofield.F access:public Meta test_filterset.py 410;" c line:410 class:FilterSetClassCreationTests.test_meta_fields_dictionary_autofield.F access:public Meta test_filterset.py 427;" c line:427 class:FilterSetClassCreationTests.test_meta_fields_containing_unknown.F access:public Meta test_filterset.py 438;" c line:438 class:FilterSetClassCreationTests.test_meta_fields_dictionary_containing_unknown.F access:public Meta test_filterset.py 449;" c line:449 class:FilterSetClassCreationTests.test_meta_exlude_with_declared_and_declared_wins.F access:public Meta test_filterset.py 462;" c line:462 class:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins.F access:public Meta test_filterset.py 474;" c line:474 class:FilterSetClassCreationTests.test_meta_exlude_with_no_fields.F access:public Meta test_filterset.py 485;" c line:485 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F access:public Meta test_filterset.py 496;" c line:496 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F access:public Meta test_filterset.py 506;" c line:506 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F access:public Meta test_filterset.py 513;" c line:513 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F access:public Meta test_filterset.py 521;" c line:521 class:FilterSetClassCreationTests.test_custom_field_gets_filter_from_override.F access:public Meta test_filterset.py 533;" c line:533 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.F access:public Meta test_filterset.py 538;" c line:538 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.ProxyF access:public Meta test_filterset.py 546;" c line:546 class:FilterSetClassCreationTests.test_filterset_for_mti_model.F access:public Meta test_filterset.py 551;" c line:551 class:FilterSetClassCreationTests.test_filterset_for_mti_model.FtiF access:public Meta test_filterset.py 565;" c line:565 class:FilterSetInstantiationTests.test_creating_instance.F access:public Meta test_filterset.py 581;" c line:581 class:FilterSetInstantiationTests.test_creating_bound_instance.F access:public Meta test_filterset.py 590;" c line:590 class:FilterSetInstantiationTests.test_creating_with_queryset.F access:public Meta test_filterset.py 600;" c line:600 class:FilterSetInstantiationTests.test_creating_with_request.F access:public Meta test_filterset.py 613;" c line:613 class:FilterSetStrictnessTests.test_settings_default.F access:public Meta test_filterset.py 625;" c line:625 class:FilterSetStrictnessTests.test_meta_value.F access:public Meta test_filterset.py 633;" c line:633 class:FilterSetStrictnessTests.test_init_default.F access:public Meta test_filterset.py 642;" c line:642 class:FilterSetStrictnessTests.test_legacy_value.F access:public Meta test_filterset.py 657;" c line:657 class:FilterSetTogetherTests.test_fields_set.F access:public Meta test_filterset.py 675;" c line:675 class:FilterSetTogetherTests.test_single_fields_set.F access:public Meta test_filterset.py 771;" c line:771 class:FilterMethodTests.test_method_self_is_parent.F access:public Meta test_forms.py 114;" c line:114 class:FilterSetFormTests.test_form_fields_using_widget.F access:public Meta test_forms.py 132;" c line:132 class:FilterSetFormTests.test_form_field_with_custom_label.F access:public Meta test_forms.py 144;" c line:144 class:FilterSetFormTests.test_form_field_with_manual_name.F access:public Meta test_forms.py 156;" c line:156 class:FilterSetFormTests.test_form_field_with_manual_name_and_label.F access:public Meta test_forms.py 168;" c line:168 class:FilterSetFormTests.test_filter_with_initial.F access:public Meta test_forms.py 177;" c line:177 class:FilterSetFormTests.test_form_is_not_bound.F access:public Meta test_forms.py 187;" c line:187 class:FilterSetFormTests.test_form_is_bound.F access:public Meta test_forms.py 201;" c line:201 class:FilterSetFormTests.test_limit_choices_to.F access:public Meta test_forms.py 214;" c line:214 class:FilterSetFormTests.test_disabled_help_text.F access:public Meta test_forms.py 27;" c line:27 class:FilterSetFormTests.test_form.F access:public Meta test_forms.py 40;" c line:40 class:FilterSetFormTests.test_custom_form.F access:public Meta test_forms.py 50;" c line:50 class:FilterSetFormTests.test_form_prefix.F access:public Meta test_forms.py 62;" c line:62 class:FilterSetFormTests.test_form_fields.F access:public Meta test_forms.py 78;" c line:78 class:FilterSetFormTests.test_form_fields_exclusion.F access:public Meta test_forms.py 90;" c line:90 class:FilterSetFormTests.test_complex_form_fields.F access:public MiscFilterSetTests test_filtering.py 1689;" c line:1689 access:public MiscFilterSetTests test_filterset.py 820;" c line:820 access:public MisconfiguredFilter rest_framework/test_backends.py 63;" c line:63 access:public ModelChoiceFilterTests test_filtering.py 431;" c line:431 access:public ModelChoiceFilterTests test_filters.py 555;" c line:555 access:public ModelMultipleChoiceFilterTests test_filtering.py 480;" c line:480 access:public ModelMultipleChoiceFilterTests test_filters.py 597;" c line:597 access:public ModuleImportTests test_filters.py 51;" c line:51 access:public MultipleChoiceFilterTests test_filtering.py 220;" c line:220 access:public MultipleChoiceFilterTests test_filters.py 386;" c line:386 access:public MyFilterSet test_views.py 58;" c line:58 function:GenericClassBasedViewTests.test_view_with_bad_filterset file: access:private MyForm test_forms.py 36;" c line:36 function:FilterSetFormTests.test_custom_form file: access:private NetworkSetting models.py 129;" c line:129 access:public Node models.py 172;" c line:172 access:public NonSymmetricalSelfReferentialRelationshipTests test_filtering.py 1444;" c line:1444 access:public NumberFilterTests test_filtering.py 591;" c line:591 access:public NumberFilterTests test_filters.py 640;" c line:640 access:public NumberInFilter test_filters.py 1041;" c line:1041 function:CSVFilterTests.setUp file: access:private NumberInFilter test_filters.py 1087;" c line:1087 function:BaseInFilterTests.test_filtering file: access:private NumberInFilter test_filters.py 1098;" c line:1098 function:BaseRangeFilterTests.test_filtering file: access:private NumericRangeFilterTests test_filters.py 668;" c line:668 access:public O2ORelationshipTests test_filtering.py 996;" c line:996 access:public OFilterSet test_filterset.py 230;" c line:230 function:FilterSetFilterForLookupTests.test_isnull_with_filter_overrides file: access:private OrderingFilterTests test_filtering.py 1649;" c line:1649 access:public OrderingFilterTests test_filters.py 1107;" c line:1107 access:public OverrideSettingsTests test_conf.py 76;" c line:76 access:public Place models.py 118;" c line:118 access:public Profile models.py 162;" c line:162 access:public ProxyF test_filterset.py 537;" c line:537 function:FilterSetClassCreationTests.test_filterset_for_proxy_model file: access:private REGULAR models.py 9;" v line:9 access:public ROOT_URLCONF settings.py 19;" v line:19 access:public RangeFieldTests test_fields.py 36;" c line:36 access:public RangeFilterTests test_filtering.py 661;" c line:661 access:public RangeFilterTests test_filters.py 710;" c line:710 access:public RangeWidgetTests test_widgets.py 124;" c line:124 access:public ResolveFieldTests test_utils.py 62;" c line:62 access:public RestFrameworkTestConfig rest_framework/apps.py 5;" c line:5 access:public Restaurant models.py 125;" c line:125 access:public SECRET_KEY settings.py 23;" v line:23 access:public STATIC_URL settings.py 31;" v line:31 access:public STATUS_CHOICES models.py 13;" v line:13 access:public SeveralFieldsFilter rest_framework/test_backends.py 45;" c line:45 access:public SpacewalkRecord models.py 206;" c line:206 access:public StrictnessTests test_conf.py 38;" c line:38 access:public SubCharField models.py 21;" c line:21 access:public SubSubCharField models.py 25;" c line:25 access:public SubnetMaskField models.py 29;" c line:29 access:public SymmetricalSelfReferentialRelationshipTests test_filtering.py 1421;" c line:1421 access:public TEMPLATES settings.py 25;" v line:25 access:public TestFilter test_filterset.py 694;" c line:694 function:FilterMethodTests.test_none file: access:private TestFilter test_filterset.py 807;" c line:807 function:FilterMethodTests.test_method_set_unset file: access:private TimeFilterTests test_filtering.py 277;" c line:277 access:public TimeFilterTests test_filters.py 539;" c line:539 access:public TimeRangeFieldTests test_fields.py 83;" c line:83 access:public TimeRangeFilterTests test_filtering.py 867;" c line:867 access:public TimeRangeFilterTests test_filters.py 932;" c line:932 access:public TransformedQueryExpressionFilterTests test_filtering.py 1481;" c line:1481 access:public USE_TZ settings.py 21;" v line:21 access:public UUIDFilterTests test_filters.py 251;" c line:251 access:public UUIDTestModel models.py 202;" c line:202 access:public User models.py 47;" c line:47 access:public UserFilter test_filtering.py 1523;" c line:1523 function:CSVFilterTests.setUp file: access:private VerboseFieldNameTests test_utils.py 209;" c line:209 access:public VerboseLookupExprTests test_utils.py 236;" c line:236 access:public Worker models.py 184;" c line:184 access:public _ rest_framework/models.py 3;" x line:3 __init__ models.py 33;" m line:33 class:SubnetMaskField access:public signature:(self, *args, **kwargs) __init__ test_filtering.py 543;" m line:543 class:ModelMultipleChoiceFilterTests.test_filtering_on_all_of_subset_of_choices.F access:public signature:(self, *args, **kwargs) __str__ models.py 102;" m line:102 class:Article access:public signature:(self) __str__ models.py 114;" m line:114 class:Book access:public signature:(self) __str__ models.py 138;" m line:138 class:Company access:public signature:(self) __str__ models.py 152;" m line:152 class:Location access:public signature:(self) __str__ models.py 58;" m line:58 class:User access:public signature:(self) __str__ models.py 72;" m line:72 class:ManagerGroup access:public signature:(self) __str__ models.py 81;" m line:81 class:AdminUser access:public signature:(self) __str__ models.py 93;" m line:93 class:Comment access:public signature:(self) _get_url rest_framework/test_backends.py 376;" m line:376 class:IntegrationTestDetailFiltering access:protected signature:(self, item) _serialize_object rest_framework/test_backends.py 155;" m line:155 class:CommonFilteringTestCase access:protected signature:(self, obj) abstract models.py 122;" v line:122 class:Place.Meta access:public account models.py 163;" v line:163 class:Profile access:public account test_filtering.py 1699;" v line:1699 class:MiscFilterSetTests.test_filtering_with_declared_filters.F access:public adjacents models.py 174;" v line:174 class:Node access:public amount_saved models.py 169;" v line:169 class:BankAccount access:public astronaut models.py 212;" v line:212 class:SpacewalkRecord access:public author models.py 100;" v line:100 class:Article access:public author models.py 88;" v line:88 class:Comment access:public author test_filtering.py 203;" v line:203 class:ChoiceFilterTests.test_filtering_on_null_choice.F access:public author test_filtering.py 462;" v line:462 class:ModelChoiceFilterTests.test_callable_queryset.F access:public author test_filtering.py 564;" v line:564 class:ModelMultipleChoiceFilterTests.test_filtering_on_non_required_fields.F access:public author__username test_filtering.py 1181;" v line:1181 class:FKRelationshipTests.test_fk_relation_attribute.F access:public average_rating models.py 112;" v line:112 class:Book access:public base_url test_views.py 27;" v line:27 class:GenericClassBasedViewTests access:public base_url test_views.py 69;" v line:69 class:GenericFunctionalViewTests access:public book_title test_forms.py 142;" v line:142 class:FilterSetFormTests.test_form_field_with_manual_name.F access:public business models.py 192;" v line:192 class:HiredWorker access:public checkItemsEqual test_filterset.py 46;" f line:46 access:public signature:(L1, L2) choices test_filters.py 374;" f line:374 function:ChoiceFilterTests.test_callable_choices file: access:private signature:() comments test_filtering.py 1152;" v line:1152 class:FKRelationshipTests.test_reverse_fk_relation.F access:public comments__text test_filtering.py 1211;" v line:1211 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F access:public company models.py 147;" v line:147 class:Location access:public date models.py 90;" v line:90 class:Comment access:public date rest_framework/models.py 20;" v line:20 class:FilterableItem access:public date rest_framework/models.py 24;" v line:24 class:DjangoFilterOrderingModel access:public date rest_framework/test_backends.py 48;" v line:48 class:SeveralFieldsFilter access:public date test_filtering.py 742;" v line:742 class:DateRangeFilterTests.test_filtering_for_year.F access:public date test_filtering.py 756;" v line:756 class:DateRangeFilterTests.test_filtering_for_month.F access:public date test_filtering.py 771;" v line:771 class:DateRangeFilterTests.test_filtering_for_week.F access:public date test_filtering.py 782;" v line:782 class:DateRangeFilterTests.test_filtering_for_today.F access:public decimal rest_framework/models.py 19;" v line:19 class:FilterableItem access:public decimal rest_framework/test_backends.py 47;" v line:47 class:SeveralFieldsFilter access:public default_app_config rest_framework/__init__.py 1;" v line:1 access:public description models.py 31;" v line:31 class:SubnetMaskField access:public dj_settings test_conf.py 91;" x line:91 duration models.py 213;" v line:213 class:SpacewalkRecord access:public employees models.py 197;" v line:197 class:Business access:public empty_strings_allowed models.py 30;" v line:30 class:SubnetMaskField access:public exclude test_filterset.py 338;" v line:338 class:FilterSetClassCreationTests.test_model_exclude_is_none.F.Meta access:public exclude test_filterset.py 349;" v line:349 class:FilterSetClassCreationTests.test_model_exclude_empty.F.Meta access:public exclude test_filterset.py 451;" v line:451 class:FilterSetClassCreationTests.test_meta_exlude_with_declared_and_declared_wins.F.Meta access:public exclude test_filterset.py 465;" v line:465 class:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins.F.Meta access:public exclude test_filterset.py 476;" v line:476 class:FilterSetClassCreationTests.test_meta_exlude_with_no_fields.F.Meta access:public exclude_username test_forms.py 88;" v line:88 class:FilterSetFormTests.test_complex_form_fields.F access:public f test_filterset.py 705;" v line:705 class:FilterMethodTests.test_method_name.F access:public f test_filterset.py 720;" v line:720 class:FilterMethodTests.test_method_callable.F access:public f test_filterset.py 729;" v line:729 class:FilterMethodTests.test_request_available_during_method_called.F access:public f test_filterset.py 745;" v line:745 class:FilterMethodTests.test_method_with_overridden_filter.F access:public f test_filterset.py 769;" v line:769 class:FilterMethodTests.test_method_self_is_parent.F access:public f test_filterset.py 782;" v line:782 class:FilterMethodTests.test_method_unresolvable.F access:public f test_filterset.py 794;" v line:794 class:FilterMethodTests.test_method_uncallable.F access:public f1 test_forms.py 154;" v line:154 class:FilterSetFormTests.test_form_field_with_manual_name_and_label.F access:public factory rest_framework/test_backends.py 27;" v line:27 access:public favorite_books models.py 56;" v line:56 class:User access:public favorite_books__title test_filtering.py 1314;" v line:1314 class:M2MRelationshipTests.test_m2m_relation_attribute.F access:public favorite_books__title test_filtering.py 1329;" v line:1329 class:M2MRelationshipTests.test_m2m_relation_attribute.F access:public fields rest_framework/test_backends.py 33;" v line:33 class:FilterableItemSerializer.Meta access:public fields rest_framework/test_backends.py 427;" v line:427 class:DjangoFilterOrderingSerializer.Meta access:public fields rest_framework/test_backends.py 52;" v line:52 class:SeveralFieldsFilter.Meta access:public fields rest_framework/test_backends.py 68;" v line:68 class:MisconfiguredFilter.Meta access:public fields rest_framework/test_backends.py 91;" v line:91 class:BaseFilterableItemFilter.Meta access:public fields test_filtering.py 1017;" v line:1017 class:O2ORelationshipTests.test_o2o_relation.F.Meta access:public fields test_filtering.py 1031;" v line:1031 class:O2ORelationshipTests.test_o2o_relation_dictionary.F.Meta access:public fields test_filtering.py 1044;" v line:1044 class:O2ORelationshipTests.test_reverse_o2o_relation.F.Meta access:public fields test_filtering.py 1057;" v line:1057 class:O2ORelationshipTests.test_o2o_relation_attribute.F.Meta access:public fields test_filtering.py 1070;" v line:1070 class:O2ORelationshipTests.test_o2o_relation_attribute2.F.Meta access:public fields test_filtering.py 107;" v line:107 class:BooleanFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 1083;" v line:1083 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute.F.Meta access:public fields test_filtering.py 1096;" v line:1096 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute2.F.Meta access:public fields test_filtering.py 1121;" v line:1121 class:FKRelationshipTests.test_fk_relation.F.Meta access:public fields test_filtering.py 1145;" v line:1145 class:FKRelationshipTests.test_reverse_fk_relation.F.Meta access:public fields test_filtering.py 1156;" v line:1156 class:FKRelationshipTests.test_reverse_fk_relation.F.Meta access:public fields test_filtering.py 1174;" v line:1174 class:FKRelationshipTests.test_fk_relation_attribute.F.Meta access:public fields test_filtering.py 1185;" v line:1185 class:FKRelationshipTests.test_fk_relation_attribute.F.Meta access:public fields test_filtering.py 1204;" v line:1204 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F.Meta access:public fields test_filtering.py 1215;" v line:1215 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F.Meta access:public fields test_filtering.py 1235;" v line:1235 class:FKRelationshipTests.test_reverse_fk_relation_multiple_attributes.F.Meta access:public fields test_filtering.py 1263;" v line:1263 class:M2MRelationshipTests.test_m2m_relation.F.Meta access:public fields test_filtering.py 1282;" v line:1282 class:M2MRelationshipTests.test_reverse_m2m_relation.F.Meta access:public fields test_filtering.py 1294;" v line:1294 class:M2MRelationshipTests.test_reverse_m2m_relation.F.Meta access:public fields test_filtering.py 1304;" v line:1304 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1318;" v line:1318 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1333;" v line:1333 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1342;" v line:1342 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1357;" v line:1357 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1372;" v line:1372 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public fields test_filtering.py 1383;" v line:1383 class:M2MRelationshipTests.test_m2m_relation_multiple_attributes.F.Meta access:public fields test_filtering.py 1402;" v line:1402 class:M2MRelationshipTests.test_reverse_m2m_relation_multiple_attributes.F.Meta access:public fields test_filtering.py 1437;" v line:1437 class:SymmetricalSelfReferentialRelationshipTests.test_relation.F.Meta access:public fields test_filtering.py 145;" v line:145 class:ChoiceFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 1460;" v line:1460 class:NonSymmetricalSelfReferentialRelationshipTests.test_forward_relation.F.Meta access:public fields test_filtering.py 1470;" v line:1470 class:NonSymmetricalSelfReferentialRelationshipTests.test_reverse_relation.F.Meta access:public fields test_filtering.py 1495;" v line:1495 class:TransformedQueryExpressionFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 1526;" v line:1526 class:CSVFilterTests.setUp.UserFilter.Meta access:public fields test_filtering.py 1534;" v line:1534 class:CSVFilterTests.setUp.ArticleFilter.Meta access:public fields test_filtering.py 1665;" v line:1665 class:OrderingFilterTests.test_ordering.F.Meta access:public fields test_filtering.py 1681;" v line:1681 class:OrderingFilterTests.test_ordering_with_select_widget.F.Meta access:public fields test_filtering.py 1703;" v line:1703 class:MiscFilterSetTests.test_filtering_with_declared_filters.F.Meta access:public fields test_filtering.py 1715;" v line:1715 class:MiscFilterSetTests.test_filtering_with_multiple_filters.F.Meta access:public fields test_filtering.py 172;" v line:172 class:ChoiceFilterTests.test_filtering_on_explicitly_defined_field.F.Meta access:public fields test_filtering.py 1733;" v line:1733 class:MiscFilterSetTests.test_filter_with_initial.F.Meta access:public fields test_filtering.py 1748;" v line:1748 class:MiscFilterSetTests.test_qs_count.F.Meta access:public fields test_filtering.py 1774;" v line:1774 class:MiscFilterSetTests.test_invalid_field_lookup.F.Meta access:public fields test_filtering.py 192;" v line:192 class:ChoiceFilterTests.test_filtering_on_empty_choice.F.Meta access:public fields test_filtering.py 233;" v line:233 class:MultipleChoiceFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 270;" v line:270 class:DateFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 294;" v line:294 class:TimeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 320;" v line:320 class:DateTimeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 370;" v line:370 class:DurationFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 395;" v line:395 class:DurationFilterTests.test_filtering_with_single_lookup_expr_dictionary.F.Meta access:public fields test_filtering.py 423;" v line:423 class:DurationFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public fields test_filtering.py 445;" v line:445 class:ModelChoiceFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 466;" v line:466 class:ModelChoiceFilterTests.test_callable_queryset.F.Meta access:public fields test_filtering.py 503;" v line:503 class:ModelMultipleChoiceFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 522;" v line:522 class:ModelMultipleChoiceFilterTests.test_filtering_dictionary.F.Meta access:public fields test_filtering.py 541;" v line:541 class:ModelMultipleChoiceFilterTests.test_filtering_on_all_of_subset_of_choices.F.Meta access:public fields test_filtering.py 568;" v line:568 class:ModelMultipleChoiceFilterTests.test_filtering_on_non_required_fields.F.Meta access:public fields test_filtering.py 605;" v line:605 class:NumberFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 616;" v line:616 class:NumberFilterTests.test_filtering_with_single_lookup_expr.F.Meta access:public fields test_filtering.py 61;" v line:61 class:CharFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 626;" v line:626 class:NumberFilterTests.test_filtering_with_single_lookup_expr_dictionary.F.Meta access:public fields test_filtering.py 638;" v line:638 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public fields test_filtering.py 655;" v line:655 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public fields test_filtering.py 681;" v line:681 class:RangeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 746;" v line:746 class:DateRangeFilterTests.test_filtering_for_year.F.Meta access:public fields test_filtering.py 760;" v line:760 class:DateRangeFilterTests.test_filtering_for_month.F.Meta access:public fields test_filtering.py 775;" v line:775 class:DateRangeFilterTests.test_filtering_for_week.F.Meta access:public fields test_filtering.py 786;" v line:786 class:DateRangeFilterTests.test_filtering_for_today.F.Meta access:public fields test_filtering.py 810;" v line:810 class:DateFromToRangeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 833;" v line:833 class:DateFromToRangeFilterTests.test_filtering_ignores_time.F.Meta access:public fields test_filtering.py 859;" v line:859 class:DateTimeFromToRangeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 85;" v line:85 class:IntegerFilterTest.test_filtering.F.Meta access:public fields test_filtering.py 883;" v line:883 class:TimeRangeFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 903;" v line:903 class:AllValuesFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 921;" v line:921 class:AllValuesFilterTests.test_filtering_without_strict.F.Meta access:public fields test_filtering.py 943;" v line:943 class:AllValuesMultipleFilterTests.test_filtering.F.Meta access:public fields test_filtering.py 967;" v line:967 class:FilterMethodTests.test_filtering.F.Meta access:public fields test_filtering.py 987;" v line:987 class:FilterMethodTests.test_filtering_callable.F.Meta access:public fields test_filterset.py 317;" v line:317 class:FilterSetClassCreationTests.test_model_derived.F.Meta access:public fields test_filterset.py 362;" v line:362 class:FilterSetClassCreationTests.test_declared_and_model_derived.F.Meta access:public fields test_filterset.py 375;" v line:375 class:FilterSetClassCreationTests.test_meta_fields_with_declared_and_model_derived.F.Meta access:public fields test_filterset.py 386;" v line:386 class:FilterSetClassCreationTests.test_meta_fields_dictionary_derived.F.Meta access:public fields test_filterset.py 400;" v line:400 class:FilterSetClassCreationTests.test_meta_fields_containing_autofield.F.Meta access:public fields test_filterset.py 412;" v line:412 class:FilterSetClassCreationTests.test_meta_fields_dictionary_autofield.F.Meta access:public fields test_filterset.py 429;" v line:429 class:FilterSetClassCreationTests.test_meta_fields_containing_unknown.F.Meta access:public fields test_filterset.py 440;" v line:440 class:FilterSetClassCreationTests.test_meta_fields_dictionary_containing_unknown.F.Meta access:public fields test_filterset.py 464;" v line:464 class:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins.F.Meta access:public fields test_filterset.py 487;" v line:487 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F.Meta access:public fields test_filterset.py 498;" v line:498 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F.Meta access:public fields test_filterset.py 508;" v line:508 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F.Meta access:public fields test_filterset.py 515;" v line:515 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F.Meta access:public fields test_filterset.py 523;" v line:523 class:FilterSetClassCreationTests.test_custom_field_gets_filter_from_override.F.Meta access:public fields test_filterset.py 535;" v line:535 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.F.Meta access:public fields test_filterset.py 540;" v line:540 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.ProxyF.Meta access:public fields test_filterset.py 548;" v line:548 class:FilterSetClassCreationTests.test_filterset_for_mti_model.F.Meta access:public fields test_filterset.py 553;" v line:553 class:FilterSetClassCreationTests.test_filterset_for_mti_model.FtiF.Meta access:public fields test_filterset.py 567;" v line:567 class:FilterSetInstantiationTests.test_creating_instance.F.Meta access:public fields test_filterset.py 583;" v line:583 class:FilterSetInstantiationTests.test_creating_bound_instance.F.Meta access:public fields test_filterset.py 592;" v line:592 class:FilterSetInstantiationTests.test_creating_with_queryset.F.Meta access:public fields test_filterset.py 602;" v line:602 class:FilterSetInstantiationTests.test_creating_with_request.F.Meta access:public fields test_filterset.py 659;" v line:659 class:FilterSetTogetherTests.test_fields_set.F.Meta access:public fields test_filterset.py 677;" v line:677 class:FilterSetTogetherTests.test_single_fields_set.F.Meta access:public fields test_forms.py 116;" v line:116 class:FilterSetFormTests.test_form_fields_using_widget.F.Meta access:public fields test_forms.py 134;" v line:134 class:FilterSetFormTests.test_form_field_with_custom_label.F.Meta access:public fields test_forms.py 146;" v line:146 class:FilterSetFormTests.test_form_field_with_manual_name.F.Meta access:public fields test_forms.py 158;" v line:158 class:FilterSetFormTests.test_form_field_with_manual_name_and_label.F.Meta access:public fields test_forms.py 170;" v line:170 class:FilterSetFormTests.test_filter_with_initial.F.Meta access:public fields test_forms.py 179;" v line:179 class:FilterSetFormTests.test_form_is_not_bound.F.Meta access:public fields test_forms.py 189;" v line:189 class:FilterSetFormTests.test_form_is_bound.F.Meta access:public fields test_forms.py 203;" v line:203 class:FilterSetFormTests.test_limit_choices_to.F.Meta access:public fields test_forms.py 216;" v line:216 class:FilterSetFormTests.test_disabled_help_text.F.Meta access:public fields test_forms.py 29;" v line:29 class:FilterSetFormTests.test_form.F.Meta access:public fields test_forms.py 42;" v line:42 class:FilterSetFormTests.test_custom_form.F.Meta access:public fields test_forms.py 52;" v line:52 class:FilterSetFormTests.test_form_prefix.F.Meta access:public fields test_forms.py 64;" v line:64 class:FilterSetFormTests.test_form_fields.F.Meta access:public fields test_forms.py 80;" v line:80 class:FilterSetFormTests.test_form_fields_exclusion.F.Meta access:public fields test_forms.py 92;" v line:92 class:FilterSetFormTests.test_complex_form_fields.F.Meta access:public filter test_filters.py 222;" m line:222 class:CustomFilterWithBooleanCheckTests.setUp.CustomTestFilter access:public signature:(self_, qs, value) filter test_filterset.py 695;" v line:695 class:FilterMethodTests.test_none.TestFilter access:public filter test_filterset.py 808;" v line:808 class:FilterMethodTests.test_method_set_unset.TestFilter access:public filter_backends rest_framework/test_backends.py 106;" v line:106 class:FilterFieldsQuerysetView access:public filter_backends rest_framework/test_backends.py 112;" v line:112 class:GetQuerysetView access:public filter_backends rest_framework/test_backends.py 41;" v line:41 class:FilterFieldsRootView access:public filter_backends rest_framework/test_backends.py 450;" v line:450 class:DjangoFilterOrderingTests.test_default_ordering.DjangoFilterOrderingView access:public filter_backends rest_framework/test_backends.py 59;" v line:59 class:FilterClassRootView access:public filter_backends rest_framework/test_backends.py 75;" v line:75 class:IncorrectlyConfiguredRootView access:public filter_backends rest_framework/test_backends.py 82;" v line:82 class:FilterClassDetailView access:public filter_backends rest_framework/test_backends.py 98;" v line:98 class:BaseFilterableItemFilterRootView access:public filter_class rest_framework/test_backends.py 111;" v line:111 class:GetQuerysetView access:public filter_class rest_framework/test_backends.py 58;" v line:58 class:FilterClassRootView access:public filter_class rest_framework/test_backends.py 74;" v line:74 class:IncorrectlyConfiguredRootView access:public filter_class rest_framework/test_backends.py 81;" v line:81 class:FilterClassDetailView access:public filter_class rest_framework/test_backends.py 97;" v line:97 class:BaseFilterableItemFilterRootView access:public filter_f test_filterset.py 707;" m line:707 class:FilterMethodTests.test_method_name.F access:public signature:(self, qs, name, value) filter_f test_filterset.py 716;" f line:716 function:FilterMethodTests.test_method_callable file: access:private signature:(qs, name, value) filter_f test_filterset.py 731;" m line:731 class:FilterMethodTests.test_request_available_during_method_called.F access:public signature:(self, qs, name, value) filter_f test_filterset.py 747;" m line:747 class:FilterMethodTests.test_method_with_overridden_filter.F access:public signature:(self, qs, name, value) filter_f test_filterset.py 774;" m line:774 class:FilterMethodTests.test_method_self_is_parent.F access:public signature:(inner_self, qs, name, value) filter_f test_filterset.py 795;" v line:795 class:FilterMethodTests.test_method_uncallable.F access:public filter_fields rest_framework/test_backends.py 105;" v line:105 class:FilterFieldsQuerysetView access:public filter_fields rest_framework/test_backends.py 136;" v line:136 class:GetSchemaFieldsTests.test_fields_with_filter_fields_dict.DictFilterFieldsRootView access:public filter_fields rest_framework/test_backends.py 40;" v line:40 class:FilterFieldsRootView access:public filter_fields rest_framework/test_backends.py 451;" v line:451 class:DjangoFilterOrderingTests.test_default_ordering.DjangoFilterOrderingView access:public filter_overrides test_filterset.py 232;" v line:232 class:FilterSetFilterForLookupTests.test_isnull_with_filter_overrides.OFilterSet.Meta access:public filter_overrides test_filterset.py 525;" v line:525 class:FilterSetClassCreationTests.test_custom_field_gets_filter_from_override.F.Meta access:public filter_username test_filtering.py 969;" m line:969 class:FilterMethodTests.test_filtering.F access:public signature:(self, queryset, name, value) filter_username test_filtering.py 979;" f line:979 function:FilterMethodTests.test_filtering_callable file: access:private signature:(queryset, name, value) first_name models.py 49;" v line:49 class:User access:public form test_forms.py 43;" v line:43 class:FilterSetFormTests.test_custom_form.F.Meta access:public formfield models.py 40;" m line:40 class:SubnetMaskField access:public signature:(self, **kwargs) friendly models.py 159;" v line:159 class:Account access:public get_internal_type models.py 37;" m line:37 class:SubnetMaskField access:public signature:(self) get_queryset rest_framework/test_backends.py 114;" m line:114 class:GetQuerysetView access:public signature:(self) get_queryset test_filters.py 587;" v line:587 class:ModelChoiceFilterTests.test_get_queryset_override.F access:public hired_on models.py 190;" v line:190 class:HiredWorker access:public in_good_standing models.py 158;" v line:158 class:Account access:public ip models.py 130;" v line:130 class:NetworkSetting access:public is_active models.py 54;" v line:54 class:User access:public is_filter test_filters.py 52;" m line:52 class:ModuleImportTests access:public signature:(self, name, value) label rest_framework/apps.py 7;" v line:7 class:RestFrameworkTestConfig access:public last_name models.py 50;" v line:50 class:User access:public likes_coffee models.py 164;" v line:164 class:Profile access:public likes_tea models.py 165;" v line:165 class:Profile access:public lovers test_filtering.py 1290;" v line:1290 class:M2MRelationshipTests.test_reverse_m2m_relation.F access:public lovers__username test_filtering.py 1353;" v line:1353 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F access:public lovers__username test_filtering.py 1368;" v line:1368 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F access:public manager models.py 67;" v line:67 class:ManagerGroup access:public mask models.py 131;" v line:131 class:NetworkSetting access:public max_duration test_filtering.py 419;" v line:419 class:DurationFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public min_duration test_filtering.py 418;" v line:418 class:DurationFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public model rest_framework/test_backends.py 32;" v line:32 class:FilterableItemSerializer.Meta access:public model rest_framework/test_backends.py 426;" v line:426 class:DjangoFilterOrderingSerializer.Meta access:public model rest_framework/test_backends.py 51;" v line:51 class:SeveralFieldsFilter.Meta access:public model rest_framework/test_backends.py 67;" v line:67 class:MisconfiguredFilter.Meta access:public model rest_framework/test_backends.py 90;" v line:90 class:BaseFilterableItemFilter.Meta access:public model test_conf.py 41;" v line:41 class:StrictnessTests.F.Meta access:public model test_filtering.py 1016;" v line:1016 class:O2ORelationshipTests.test_o2o_relation.F.Meta access:public model test_filtering.py 1030;" v line:1030 class:O2ORelationshipTests.test_o2o_relation_dictionary.F.Meta access:public model test_filtering.py 1043;" v line:1043 class:O2ORelationshipTests.test_reverse_o2o_relation.F.Meta access:public model test_filtering.py 1056;" v line:1056 class:O2ORelationshipTests.test_o2o_relation_attribute.F.Meta access:public model test_filtering.py 1069;" v line:1069 class:O2ORelationshipTests.test_o2o_relation_attribute2.F.Meta access:public model test_filtering.py 106;" v line:106 class:BooleanFilterTests.test_filtering.F.Meta access:public model test_filtering.py 1082;" v line:1082 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute.F.Meta access:public model test_filtering.py 1095;" v line:1095 class:O2ORelationshipTests.test_reverse_o2o_relation_attribute2.F.Meta access:public model test_filtering.py 1120;" v line:1120 class:FKRelationshipTests.test_fk_relation.F.Meta access:public model test_filtering.py 1144;" v line:1144 class:FKRelationshipTests.test_reverse_fk_relation.F.Meta access:public model test_filtering.py 1155;" v line:1155 class:FKRelationshipTests.test_reverse_fk_relation.F.Meta access:public model test_filtering.py 1173;" v line:1173 class:FKRelationshipTests.test_fk_relation_attribute.F.Meta access:public model test_filtering.py 1184;" v line:1184 class:FKRelationshipTests.test_fk_relation_attribute.F.Meta access:public model test_filtering.py 1203;" v line:1203 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F.Meta access:public model test_filtering.py 1214;" v line:1214 class:FKRelationshipTests.test_reverse_fk_relation_attribute.F.Meta access:public model test_filtering.py 1234;" v line:1234 class:FKRelationshipTests.test_reverse_fk_relation_multiple_attributes.F.Meta access:public model test_filtering.py 1262;" v line:1262 class:M2MRelationshipTests.test_m2m_relation.F.Meta access:public model test_filtering.py 1281;" v line:1281 class:M2MRelationshipTests.test_reverse_m2m_relation.F.Meta access:public model test_filtering.py 1293;" v line:1293 class:M2MRelationshipTests.test_reverse_m2m_relation.F.Meta access:public model test_filtering.py 1303;" v line:1303 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1317;" v line:1317 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1332;" v line:1332 class:M2MRelationshipTests.test_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1341;" v line:1341 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1356;" v line:1356 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1371;" v line:1371 class:M2MRelationshipTests.test_reverse_m2m_relation_attribute.F.Meta access:public model test_filtering.py 1382;" v line:1382 class:M2MRelationshipTests.test_m2m_relation_multiple_attributes.F.Meta access:public model test_filtering.py 1401;" v line:1401 class:M2MRelationshipTests.test_reverse_m2m_relation_multiple_attributes.F.Meta access:public model test_filtering.py 1436;" v line:1436 class:SymmetricalSelfReferentialRelationshipTests.test_relation.F.Meta access:public model test_filtering.py 144;" v line:144 class:ChoiceFilterTests.test_filtering.F.Meta access:public model test_filtering.py 1459;" v line:1459 class:NonSymmetricalSelfReferentialRelationshipTests.test_forward_relation.F.Meta access:public model test_filtering.py 1469;" v line:1469 class:NonSymmetricalSelfReferentialRelationshipTests.test_reverse_relation.F.Meta access:public model test_filtering.py 1494;" v line:1494 class:TransformedQueryExpressionFilterTests.test_filtering.F.Meta access:public model test_filtering.py 1525;" v line:1525 class:CSVFilterTests.setUp.UserFilter.Meta access:public model test_filtering.py 1533;" v line:1533 class:CSVFilterTests.setUp.ArticleFilter.Meta access:public model test_filtering.py 1664;" v line:1664 class:OrderingFilterTests.test_ordering.F.Meta access:public model test_filtering.py 1680;" v line:1680 class:OrderingFilterTests.test_ordering_with_select_widget.F.Meta access:public model test_filtering.py 1702;" v line:1702 class:MiscFilterSetTests.test_filtering_with_declared_filters.F.Meta access:public model test_filtering.py 1714;" v line:1714 class:MiscFilterSetTests.test_filtering_with_multiple_filters.F.Meta access:public model test_filtering.py 171;" v line:171 class:ChoiceFilterTests.test_filtering_on_explicitly_defined_field.F.Meta access:public model test_filtering.py 1732;" v line:1732 class:MiscFilterSetTests.test_filter_with_initial.F.Meta access:public model test_filtering.py 1747;" v line:1747 class:MiscFilterSetTests.test_qs_count.F.Meta access:public model test_filtering.py 1773;" v line:1773 class:MiscFilterSetTests.test_invalid_field_lookup.F.Meta access:public model test_filtering.py 191;" v line:191 class:ChoiceFilterTests.test_filtering_on_empty_choice.F.Meta access:public model test_filtering.py 210;" v line:210 class:ChoiceFilterTests.test_filtering_on_null_choice.F.Meta access:public model test_filtering.py 232;" v line:232 class:MultipleChoiceFilterTests.test_filtering.F.Meta access:public model test_filtering.py 269;" v line:269 class:DateFilterTests.test_filtering.F.Meta access:public model test_filtering.py 293;" v line:293 class:TimeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 319;" v line:319 class:DateTimeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 369;" v line:369 class:DurationFilterTests.test_filtering.F.Meta access:public model test_filtering.py 394;" v line:394 class:DurationFilterTests.test_filtering_with_single_lookup_expr_dictionary.F.Meta access:public model test_filtering.py 422;" v line:422 class:DurationFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public model test_filtering.py 444;" v line:444 class:ModelChoiceFilterTests.test_filtering.F.Meta access:public model test_filtering.py 465;" v line:465 class:ModelChoiceFilterTests.test_callable_queryset.F.Meta access:public model test_filtering.py 502;" v line:502 class:ModelMultipleChoiceFilterTests.test_filtering.F.Meta access:public model test_filtering.py 521;" v line:521 class:ModelMultipleChoiceFilterTests.test_filtering_dictionary.F.Meta access:public model test_filtering.py 540;" v line:540 class:ModelMultipleChoiceFilterTests.test_filtering_on_all_of_subset_of_choices.F.Meta access:public model test_filtering.py 567;" v line:567 class:ModelMultipleChoiceFilterTests.test_filtering_on_non_required_fields.F.Meta access:public model test_filtering.py 604;" v line:604 class:NumberFilterTests.test_filtering.F.Meta access:public model test_filtering.py 60;" v line:60 class:CharFilterTests.test_filtering.F.Meta access:public model test_filtering.py 615;" v line:615 class:NumberFilterTests.test_filtering_with_single_lookup_expr.F.Meta access:public model test_filtering.py 625;" v line:625 class:NumberFilterTests.test_filtering_with_single_lookup_expr_dictionary.F.Meta access:public model test_filtering.py 637;" v line:637 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public model test_filtering.py 654;" v line:654 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F.Meta access:public model test_filtering.py 680;" v line:680 class:RangeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 745;" v line:745 class:DateRangeFilterTests.test_filtering_for_year.F.Meta access:public model test_filtering.py 759;" v line:759 class:DateRangeFilterTests.test_filtering_for_month.F.Meta access:public model test_filtering.py 774;" v line:774 class:DateRangeFilterTests.test_filtering_for_week.F.Meta access:public model test_filtering.py 785;" v line:785 class:DateRangeFilterTests.test_filtering_for_today.F.Meta access:public model test_filtering.py 809;" v line:809 class:DateFromToRangeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 832;" v line:832 class:DateFromToRangeFilterTests.test_filtering_ignores_time.F.Meta access:public model test_filtering.py 84;" v line:84 class:IntegerFilterTest.test_filtering.F.Meta access:public model test_filtering.py 858;" v line:858 class:DateTimeFromToRangeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 882;" v line:882 class:TimeRangeFilterTests.test_filtering.F.Meta access:public model test_filtering.py 902;" v line:902 class:AllValuesFilterTests.test_filtering.F.Meta access:public model test_filtering.py 920;" v line:920 class:AllValuesFilterTests.test_filtering_without_strict.F.Meta access:public model test_filtering.py 942;" v line:942 class:AllValuesMultipleFilterTests.test_filtering.F.Meta access:public model test_filtering.py 966;" v line:966 class:FilterMethodTests.test_filtering.F.Meta access:public model test_filtering.py 986;" v line:986 class:FilterMethodTests.test_filtering_callable.F.Meta access:public model test_filterset.py 316;" v line:316 class:FilterSetClassCreationTests.test_model_derived.F.Meta access:public model test_filterset.py 327;" v line:327 class:FilterSetClassCreationTests.test_model_no_fields_or_exclude.F.Meta access:public model test_filterset.py 337;" v line:337 class:FilterSetClassCreationTests.test_model_exclude_is_none.F.Meta access:public model test_filterset.py 348;" v line:348 class:FilterSetClassCreationTests.test_model_exclude_empty.F.Meta access:public model test_filterset.py 361;" v line:361 class:FilterSetClassCreationTests.test_declared_and_model_derived.F.Meta access:public model test_filterset.py 374;" v line:374 class:FilterSetClassCreationTests.test_meta_fields_with_declared_and_model_derived.F.Meta access:public model test_filterset.py 385;" v line:385 class:FilterSetClassCreationTests.test_meta_fields_dictionary_derived.F.Meta access:public model test_filterset.py 399;" v line:399 class:FilterSetClassCreationTests.test_meta_fields_containing_autofield.F.Meta access:public model test_filterset.py 411;" v line:411 class:FilterSetClassCreationTests.test_meta_fields_dictionary_autofield.F.Meta access:public model test_filterset.py 428;" v line:428 class:FilterSetClassCreationTests.test_meta_fields_containing_unknown.F.Meta access:public model test_filterset.py 439;" v line:439 class:FilterSetClassCreationTests.test_meta_fields_dictionary_containing_unknown.F.Meta access:public model test_filterset.py 450;" v line:450 class:FilterSetClassCreationTests.test_meta_exlude_with_declared_and_declared_wins.F.Meta access:public model test_filterset.py 463;" v line:463 class:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins.F.Meta access:public model test_filterset.py 475;" v line:475 class:FilterSetClassCreationTests.test_meta_exlude_with_no_fields.F.Meta access:public model test_filterset.py 486;" v line:486 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F.Meta access:public model test_filterset.py 497;" v line:497 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F.Meta access:public model test_filterset.py 507;" v line:507 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F.Meta access:public model test_filterset.py 514;" v line:514 class:FilterSetClassCreationTests.test_abstract_model_inheritance.F.Meta access:public model test_filterset.py 522;" v line:522 class:FilterSetClassCreationTests.test_custom_field_gets_filter_from_override.F.Meta access:public model test_filterset.py 534;" v line:534 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.F.Meta access:public model test_filterset.py 539;" v line:539 class:FilterSetClassCreationTests.test_filterset_for_proxy_model.ProxyF.Meta access:public model test_filterset.py 547;" v line:547 class:FilterSetClassCreationTests.test_filterset_for_mti_model.F.Meta access:public model test_filterset.py 552;" v line:552 class:FilterSetClassCreationTests.test_filterset_for_mti_model.FtiF.Meta access:public model test_filterset.py 566;" v line:566 class:FilterSetInstantiationTests.test_creating_instance.F.Meta access:public model test_filterset.py 582;" v line:582 class:FilterSetInstantiationTests.test_creating_bound_instance.F.Meta access:public model test_filterset.py 591;" v line:591 class:FilterSetInstantiationTests.test_creating_with_queryset.F.Meta access:public model test_filterset.py 601;" v line:601 class:FilterSetInstantiationTests.test_creating_with_request.F.Meta access:public model test_filterset.py 614;" v line:614 class:FilterSetStrictnessTests.test_settings_default.F.Meta access:public model test_filterset.py 626;" v line:626 class:FilterSetStrictnessTests.test_meta_value.F.Meta access:public model test_filterset.py 634;" v line:634 class:FilterSetStrictnessTests.test_init_default.F.Meta access:public model test_filterset.py 643;" v line:643 class:FilterSetStrictnessTests.test_legacy_value.F.Meta access:public model test_filterset.py 658;" v line:658 class:FilterSetTogetherTests.test_fields_set.F.Meta access:public model test_filterset.py 676;" v line:676 class:FilterSetTogetherTests.test_single_fields_set.F.Meta access:public model test_filterset.py 772;" v line:772 class:FilterMethodTests.test_method_self_is_parent.F.Meta access:public model test_forms.py 115;" v line:115 class:FilterSetFormTests.test_form_fields_using_widget.F.Meta access:public model test_forms.py 133;" v line:133 class:FilterSetFormTests.test_form_field_with_custom_label.F.Meta access:public model test_forms.py 145;" v line:145 class:FilterSetFormTests.test_form_field_with_manual_name.F.Meta access:public model test_forms.py 157;" v line:157 class:FilterSetFormTests.test_form_field_with_manual_name_and_label.F.Meta access:public model test_forms.py 169;" v line:169 class:FilterSetFormTests.test_filter_with_initial.F.Meta access:public model test_forms.py 178;" v line:178 class:FilterSetFormTests.test_form_is_not_bound.F.Meta access:public model test_forms.py 188;" v line:188 class:FilterSetFormTests.test_form_is_bound.F.Meta access:public model test_forms.py 202;" v line:202 class:FilterSetFormTests.test_limit_choices_to.F.Meta access:public model test_forms.py 215;" v line:215 class:FilterSetFormTests.test_disabled_help_text.F.Meta access:public model test_forms.py 28;" v line:28 class:FilterSetFormTests.test_form.F.Meta access:public model test_forms.py 41;" v line:41 class:FilterSetFormTests.test_custom_form.F.Meta access:public model test_forms.py 51;" v line:51 class:FilterSetFormTests.test_form_prefix.F.Meta access:public model test_forms.py 63;" v line:63 class:FilterSetFormTests.test_form_fields.F.Meta access:public model test_forms.py 79;" v line:79 class:FilterSetFormTests.test_form_fields_exclusion.F.Meta access:public model test_forms.py 91;" v line:91 class:FilterSetFormTests.test_complex_form_fields.F.Meta access:public name models.py 119;" v line:119 class:Place access:public name models.py 136;" v line:136 class:Company access:public name models.py 148;" v line:148 class:Location access:public name models.py 157;" v line:157 class:Account access:public name models.py 173;" v line:173 class:Node access:public name models.py 178;" v line:178 class:DirectedNode access:public name models.py 185;" v line:185 class:Worker access:public name models.py 196;" v line:196 class:Business access:public name models.py 98;" v line:98 class:Article access:public name rest_framework/apps.py 6;" v line:6 class:RestFrameworkTestConfig access:public o test_filtering.py 1659;" v line:1659 class:OrderingFilterTests.test_ordering.F access:public o test_filtering.py 1674;" v line:1674 class:OrderingFilterTests.test_ordering_with_select_widget.F access:public open_days models.py 150;" v line:150 class:Location access:public ordering models.py 142;" v line:142 class:Company.Meta access:public ordering rest_framework/models.py 28;" v line:28 class:DjangoFilterOrderingModel.Meta access:public ordering rest_framework/test_backends.py 452;" v line:452 class:DjangoFilterOrderingTests.test_default_ordering.DjangoFilterOrderingView access:public other test_filterset.py 494;" v line:494 class:FilterSetClassCreationTests.test_filterset_class_inheritance.F access:public outbound_nodes models.py 179;" v line:179 class:DirectedNode access:public price models.py 111;" v line:111 class:Book access:public price test_filtering.py 612;" v line:612 class:NumberFilterTests.test_filtering_with_single_lookup_expr.F access:public price test_filtering.py 634;" v line:634 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public price test_filtering.py 651;" v line:651 class:NumberFilterTests.test_filtering_with_multiple_lookup_exprs.F access:public price test_filtering.py 677;" v line:677 class:RangeFilterTests.test_filtering.F access:public proxy models.py 79;" v line:79 class:AdminUser.Meta access:public published models.py 99;" v line:99 class:Article access:public published test_filtering.py 806;" v line:806 class:DateFromToRangeFilterTests.test_filtering.F access:public published test_filtering.py 829;" v line:829 class:DateFromToRangeFilterTests.test_filtering_ignores_time.F access:public published test_filtering.py 855;" v line:855 class:DateTimeFromToRangeFilterTests.test_filtering.F access:public queryset rest_framework/test_backends.py 103;" v line:103 class:FilterFieldsQuerysetView access:public queryset rest_framework/test_backends.py 38;" v line:38 class:FilterFieldsRootView access:public queryset rest_framework/test_backends.py 449;" v line:449 class:DjangoFilterOrderingTests.test_default_ordering.DjangoFilterOrderingView access:public queryset rest_framework/test_backends.py 56;" v line:56 class:FilterClassRootView access:public queryset rest_framework/test_backends.py 72;" v line:72 class:IncorrectlyConfiguredRootView access:public queryset rest_framework/test_backends.py 79;" v line:79 class:FilterClassDetailView access:public queryset rest_framework/test_backends.py 95;" v line:95 class:BaseFilterableItemFilterRootView access:public reference_dt test_fields.py 131;" v line:131 class:IsoDateTimeFieldTests access:public reference_str test_fields.py 130;" v line:130 class:IsoDateTimeFieldTests access:public salary models.py 189;" v line:189 class:HiredWorker access:public serializer_class rest_framework/test_backends.py 104;" v line:104 class:FilterFieldsQuerysetView access:public serializer_class rest_framework/test_backends.py 110;" v line:110 class:GetQuerysetView access:public serializer_class rest_framework/test_backends.py 39;" v line:39 class:FilterFieldsRootView access:public serializer_class rest_framework/test_backends.py 448;" v line:448 class:DjangoFilterOrderingTests.test_default_ordering.DjangoFilterOrderingView access:public serializer_class rest_framework/test_backends.py 57;" v line:57 class:FilterClassRootView access:public serializer_class rest_framework/test_backends.py 73;" v line:73 class:IncorrectlyConfiguredRootView access:public serializer_class rest_framework/test_backends.py 80;" v line:80 class:FilterClassDetailView access:public serializer_class rest_framework/test_backends.py 96;" v line:96 class:BaseFilterableItemFilterRootView access:public serves_pizza models.py 126;" v line:126 class:Restaurant access:public setUp rest_framework/test_backends.py 158;" m line:158 class:CommonFilteringTestCase access:public signature:(self) setUp rest_framework/test_backends.py 431;" m line:431 class:DjangoFilterOrderingTests access:public signature:(self) setUp test_fields.py 177;" m line:177 class:BaseCSVFieldTests access:public signature:(self) setUp test_fields.py 218;" m line:218 class:BaseRangeFieldTests access:public signature:(self) setUp test_filtering.py 1244;" m line:1244 class:M2MRelationshipTests access:public signature:(self) setUp test_filtering.py 1423;" m line:1423 class:SymmetricalSelfReferentialRelationshipTests access:public signature:(self) setUp test_filtering.py 1446;" m line:1446 class:NonSymmetricalSelfReferentialRelationshipTests access:public signature:(self) setUp test_filtering.py 1508;" m line:1508 class:CSVFilterTests access:public signature:(self) setUp test_filtering.py 1651;" m line:1651 class:OrderingFilterTests access:public signature:(self) setUp test_filtering.py 1691;" m line:1691 class:MiscFilterSetTests access:public signature:(self) setUp test_filtering.py 348;" m line:348 class:DurationFilterTests access:public signature:(self) setUp test_filtering.py 482;" m line:482 class:ModelMultipleChoiceFilterTests access:public signature:(self) setUp test_filtering.py 593;" m line:593 class:NumberFilterTests access:public signature:(self) setUp test_filtering.py 663;" m line:663 class:RangeFilterTests access:public signature:(self) setUp test_filtering.py 725;" m line:725 class:DateRangeFilterTests access:public signature:(self) setUp test_filtering.py 956;" m line:956 class:FilterMethodTests access:public signature:(self) setUp test_filtering.py 998;" m line:998 class:O2ORelationshipTests access:public signature:(self) setUp test_filters.py 1040;" m line:1040 class:CSVFilterTests access:public signature:(self) setUp test_filters.py 218;" m line:218 class:CustomFilterWithBooleanCheckTests access:public signature:(self) setUp test_filterset.py 650;" m line:650 class:FilterSetTogetherTests access:public signature:(self) setUp test_views.py 17;" m line:17 class:GenericViewTestCase access:public signature:(self) setUpTestData test_filtering.py 129;" m line:129 class:ChoiceFilterTests access:public signature:(cls) status models.py 52;" v line:52 class:User access:public status test_filtering.py 168;" v line:168 class:ChoiceFilterTests.test_filtering_on_explicitly_defined_field.F access:public status test_filtering.py 1729;" v line:1729 class:MiscFilterSetTests.test_filter_with_initial.F access:public status test_filtering.py 229;" v line:229 class:MultipleChoiceFilterTests.test_filtering.F access:public status test_forms.py 110;" v line:110 class:FilterSetFormTests.test_form_fields_using_widget.F access:public status test_forms.py 166;" v line:166 class:FilterSetFormTests.test_filter_with_initial.F access:public strict test_filtering.py 922;" v line:922 class:AllValuesFilterTests.test_filtering_without_strict.F.Meta access:public strict test_filterset.py 627;" v line:627 class:FilterSetStrictnessTests.test_meta_value.F.Meta access:public strict test_filterset.py 635;" v line:635 class:FilterSetStrictnessTests.test_init_default.F.Meta access:public template rest_framework/test_backends.py 352;" v line:352 class:IntegrationTestFiltering.test_template_path.Backend access:public test_DTL_missing rest_framework/test_backends.py 360;" m line:360 class:IntegrationTestFiltering access:public signature:(self) test_abstract_model_inheritance test_filterset.py 504;" m line:504 class:FilterSetClassCreationTests access:public signature:(self) test_attribute_override test_conf.py 78;" m line:78 class:OverrideSettingsTests access:public signature:(self) test_backend_output rest_framework/test_backends.py 324;" m line:324 class:IntegrationTestFiltering access:public signature:(self) test_backwards_related_field test_utils.py 231;" m line:231 class:VerboseFieldNameTests access:public signature:(self) test_base_model_filter rest_framework/test_backends.py 292;" m line:292 class:IntegrationTestFiltering access:public signature:(self) test_booleanfilter_widget rest_framework/test_filterset.py 18;" m line:18 class:FilterSetFilterForFieldTests access:public signature:(self) test_callable_choices test_filters.py 373;" m line:373 class:ChoiceFilterTests access:public signature:(self) test_callable_queryset test_filtering.py 451;" m line:451 class:ModelChoiceFilterTests access:public signature:(self) test_callable_queryset test_filters.py 569;" m line:569 class:ModelChoiceFilterTests access:public signature:(self) test_callable_queryset test_filters.py 626;" m line:626 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_choices_from_fields test_filters.py 1150;" m line:1150 class:OrderingFilterTests access:public signature:(self) test_choices_unaltered test_filters.py 1137;" m line:1137 class:OrderingFilterTests access:public signature:(self) test_clean test_fields.py 105;" m line:105 class:LookupTypeFieldTests access:public signature:(self) test_clean test_fields.py 183;" m line:183 class:BaseCSVFieldTests access:public signature:(self) test_clean test_fields.py 224;" m line:224 class:BaseRangeFieldTests access:public signature:(self) test_clean test_fields.py 42;" m line:42 class:RangeFieldTests access:public signature:(self) test_clean test_fields.py 58;" m line:58 class:DateRangeFieldTests access:public signature:(self) test_clean test_fields.py 74;" m line:74 class:DateTimeRangeFieldTests access:public signature:(self) test_clean test_fields.py 89;" m line:89 class:TimeRangeFieldTests access:public signature:(self) test_complex_form_fields test_forms.py 85;" m line:85 class:FilterSetFormTests access:public signature:(self) test_concrete_field test_filters.py 1055;" m line:1055 class:CSVFilterTests access:public signature:(self) test_conjoined_default_value test_filters.py 399;" m line:399 class:MultipleChoiceFilterTests access:public signature:(self) test_conjoined_true test_filters.py 403;" m line:403 class:MultipleChoiceFilterTests access:public signature:(self) test_creating test_filters.py 761;" m line:761 class:DateRangeFilterTests access:public signature:(self) test_creating_bound_instance test_filterset.py 579;" m line:579 class:FilterSetInstantiationTests access:public signature:(self) test_creating_instance test_filterset.py 563;" m line:563 class:FilterSetInstantiationTests access:public signature:(self) test_creating_with_queryset test_filterset.py 588;" m line:588 class:FilterSetInstantiationTests access:public signature:(self) test_creating_with_request test_filterset.py 598;" m line:598 class:FilterSetInstantiationTests access:public signature:(self) test_creation test_filters.py 75;" m line:75 class:FilterTests access:public signature:(self) test_creation_order test_filters.py 80;" m line:80 class:FilterTests access:public signature:(self) test_custom_field_gets_filter_from_override test_filterset.py 519;" m line:519 class:FilterSetClassCreationTests access:public signature:(self) test_custom_form test_forms.py 35;" m line:35 class:FilterSetFormTests access:public signature:(self) test_custom_lookup_exprs test_filters.py 996;" m line:996 class:LookupTypesTests access:public signature:(self) test_datetime_filtering test_filtering.py 1595;" m line:1595 class:CSVFilterTests access:public signature:(self) test_datetime_string_is_parsed test_fields.py 133;" m line:133 class:IsoDateTimeFieldTests access:public signature:(self) test_datetime_string_with_timezone_is_parsed test_fields.py 138;" m line:138 class:IsoDateTimeFieldTests access:public signature:(self) test_datetime_timezone_awareness test_fields.py 148;" m line:148 class:IsoDateTimeFieldTests access:public signature:(self) test_datetime_timezone_naivety test_fields.py 162;" m line:162 class:IsoDateTimeFieldTests access:public signature:(self) test_datetime_zulu test_fields.py 143;" m line:143 class:IsoDateTimeFieldTests access:public signature:(self) test_declared_and_model_derived test_filterset.py 356;" m line:356 class:FilterSetClassCreationTests access:public signature:(self) test_declaring_filter test_filterset.py 304;" m line:304 class:FilterSetClassCreationTests access:public signature:(self) test_default_field test_filters.py 1050;" m line:1050 class:CSVFilterTests access:public signature:(self) test_default_field test_filters.py 1108;" m line:1108 class:OrderingFilterTests access:public signature:(self) test_default_field test_filters.py 245;" m line:245 class:CharFilterTests access:public signature:(self) test_default_field test_filters.py 253;" m line:253 class:UUIDFilterTests access:public signature:(self) test_default_field test_filters.py 261;" m line:261 class:BooleanFilterTests access:public signature:(self) test_default_field test_filters.py 304;" m line:304 class:ChoiceFilterTests access:public signature:(self) test_default_field test_filters.py 388;" m line:388 class:MultipleChoiceFilterTests access:public signature:(self) test_default_field test_filters.py 525;" m line:525 class:DateFilterTests access:public signature:(self) test_default_field test_filters.py 533;" m line:533 class:DateTimeFilterTests access:public signature:(self) test_default_field test_filters.py 541;" m line:541 class:TimeFilterTests access:public signature:(self) test_default_field test_filters.py 549;" m line:549 class:DurationFilterTests access:public signature:(self) test_default_field test_filters.py 642;" m line:642 class:NumberFilterTests access:public signature:(self) test_default_field test_filters.py 670;" m line:670 class:NumericRangeFilterTests access:public signature:(self) test_default_field test_filters.py 712;" m line:712 class:RangeFilterTests access:public signature:(self) test_default_field test_filters.py 766;" m line:766 class:DateRangeFilterTests access:public signature:(self) test_default_field test_filters.py 844;" m line:844 class:DateFromToRangeFilterTests access:public signature:(self) test_default_field test_filters.py 85;" m line:85 class:FilterTests access:public signature:(self) test_default_field test_filters.py 888;" m line:888 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_default_field test_filters.py 934;" m line:934 class:TimeRangeFilterTests access:public signature:(self) test_default_field_with_assigning_model test_filters.py 983;" m line:983 class:AllValuesFilterTests access:public signature:(self) test_default_field_with_queryset test_filters.py 562;" m line:562 class:ModelChoiceFilterTests access:public signature:(self) test_default_field_with_queryset test_filters.py 604;" m line:604 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_default_field_without_assigning_model test_filters.py 978;" m line:978 class:AllValuesFilterTests access:public signature:(self) test_default_field_without_queryset test_filters.py 557;" m line:557 class:ModelChoiceFilterTests access:public signature:(self) test_default_field_without_queryset test_filters.py 599;" m line:599 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_default_ordering rest_framework/test_backends.py 446;" m line:446 class:DjangoFilterOrderingTests access:public signature:(self) test_derived_widget test_fields.py 197;" m line:197 class:BaseCSVFieldTests access:public signature:(self) test_disable_help_text test_conf.py 16;" m line:16 class:DefaultSettingsTests access:public signature:(self) test_disabled_help_text test_forms.py 212;" m line:212 class:FilterSetFormTests access:public signature:(self) test_empty_choice test_filters.py 309;" m line:309 class:ChoiceFilterTests access:public signature:(self) test_empty_choice_label test_conf.py 28;" m line:28 class:DefaultSettingsTests access:public signature:(self) test_exact test_utils.py 238;" m line:238 class:VerboseLookupExprTests access:public signature:(self) test_exact_lookup test_utils.py 276;" m line:276 class:LabelForFilterTests access:public signature:(self) test_exclusion_label test_utils.py 268;" m line:268 class:LabelForFilterTests access:public signature:(self) test_expected_db_fields_do_not_get_filters test_filterset.py 102;" m line:102 class:DbFieldDefaultFiltersTests access:public signature:(self) test_expected_db_fields_get_filters test_filterset.py 70;" m line:70 class:DbFieldDefaultFiltersTests access:public signature:(self) test_field test_fields.py 100;" m line:100 class:LookupTypeFieldTests access:public signature:(self) test_field test_fields.py 38;" m line:38 class:RangeFieldTests access:public signature:(self) test_field test_fields.py 53;" m line:53 class:DateRangeFieldTests access:public signature:(self) test_field test_fields.py 69;" m line:69 class:DateTimeRangeFieldTests access:public signature:(self) test_field test_fields.py 85;" m line:85 class:TimeRangeFieldTests access:public signature:(self) test_field test_utils.py 219;" m line:219 class:VerboseFieldNameTests access:public signature:(self) test_field test_utils.py 25;" m line:25 class:GetFieldPartsTests access:public signature:(self) test_field_extra_params test_filters.py 124;" m line:124 class:FilterTests access:public signature:(self) test_field_labels test_filters.py 1163;" m line:1163 class:OrderingFilterTests access:public signature:(self) test_field_params test_filters.py 114;" m line:114 class:FilterTests access:public signature:(self) test_field_that_is_subclassed test_filterset.py 152;" m line:152 class:FilterSetFilterForFieldTests access:public signature:(self) test_field_with_choices test_filterset.py 144;" m line:144 class:FilterSetFilterForFieldTests access:public signature:(self) test_field_with_extras test_filterset.py 135;" m line:135 class:FilterSetFilterForFieldTests access:public signature:(self) test_field_with_list_lookup_expr test_filters.py 107;" m line:107 class:FilterTests access:public signature:(self) test_field_with_lookup_expr test_filters.py 1078;" m line:1078 class:CSVFilterTests access:public signature:(self) test_field_with_lookup_expr test_filters.py 695;" m line:695 class:NumericRangeFilterTests access:public signature:(self) test_field_with_lookup_expr_and_exlusion test_filters.py 102;" m line:102 class:FilterTests access:public signature:(self) test_field_with_none_lookup_expr test_filters.py 95;" m line:95 class:FilterTests access:public signature:(self) test_field_with_required_filter test_filters.py 134;" m line:134 class:FilterTests access:public signature:(self) test_field_with_single_lookup_expr test_filters.py 90;" m line:90 class:FilterTests access:public signature:(self) test_field_with_verbose_name test_utils.py 223;" m line:223 class:VerboseFieldNameTests access:public signature:(self) test_fields_set test_filterset.py 655;" m line:655 class:FilterSetTogetherTests access:public signature:(self) test_fields_with_filter_class rest_framework/test_backends.py 146;" m line:146 class:GetSchemaFieldsTests access:public signature:(self) test_fields_with_filter_fields_dict rest_framework/test_backends.py 134;" m line:134 class:GetSchemaFieldsTests access:public signature:(self) test_fields_with_filter_fields_list rest_framework/test_backends.py 127;" m line:127 class:GetSchemaFieldsTests access:public signature:(self) test_filter_conjoined_true test_filters.py 459;" m line:459 class:MultipleChoiceFilterTests access:public signature:(self) test_filter_for_IN_lookup test_filterset.py 215;" m line:215 class:FilterSetFilterForLookupTests access:public signature:(self) test_filter_for_ISNULL_lookup test_filterset.py 209;" m line:209 class:FilterSetFilterForLookupTests access:public signature:(self) test_filter_for_RANGE_lookup test_filterset.py 222;" m line:222 class:FilterSetFilterForLookupTests access:public signature:(self) test_filter_found_for_autofield test_filterset.py 129;" m line:129 class:FilterSetFilterForFieldTests access:public signature:(self) test_filter_found_for_field test_filterset.py 117;" m line:117 class:FilterSetFilterForFieldTests access:public signature:(self) test_filter_found_for_uuidfield test_filterset.py 123;" m line:123 class:FilterSetFilterForFieldTests access:public signature:(self) test_filter_overrides test_filterset.py 203;" m line:203 class:FilterSetFilterForFieldTests access:public signature:(self) test_filter_using_method test_filters.py 200;" m line:200 class:FilterTests access:public signature:(self) test_filter_with_get_queryset_only rest_framework/test_backends.py 223;" m line:223 class:IntegrationTestFiltering access:public signature:(self) test_filter_with_initial test_filtering.py 1725;" m line:1725 class:MiscFilterSetTests access:public signature:(self) test_filter_with_initial test_forms.py 164;" m line:164 class:FilterSetFormTests access:public signature:(self) test_filter_with_queryset rest_framework/test_backends.py 209;" m line:209 class:IntegrationTestFiltering access:public signature:(self) test_filtering test_filtering.py 141;" m line:141 class:ChoiceFilterTests access:public signature:(self) test_filtering test_filtering.py 1483;" m line:1483 class:TransformedQueryExpressionFilterTests access:public signature:(self) test_filtering test_filtering.py 222;" m line:222 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering test_filtering.py 256;" m line:256 class:DateFilterTests access:public signature:(self) test_filtering test_filtering.py 279;" m line:279 class:TimeFilterTests access:public signature:(self) test_filtering test_filtering.py 303;" m line:303 class:DateTimeFilterTests access:public signature:(self) test_filtering test_filtering.py 365;" m line:365 class:DurationFilterTests access:public signature:(self) test_filtering test_filtering.py 433;" m line:433 class:ModelChoiceFilterTests access:public signature:(self) test_filtering test_filtering.py 499;" m line:499 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_filtering test_filtering.py 50;" m line:50 class:CharFilterTests access:public signature:(self) test_filtering test_filtering.py 601;" m line:601 class:NumberFilterTests access:public signature:(self) test_filtering test_filtering.py 675;" m line:675 class:RangeFilterTests access:public signature:(self) test_filtering test_filtering.py 73;" m line:73 class:IntegerFilterTest access:public signature:(self) test_filtering test_filtering.py 797;" m line:797 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering test_filtering.py 843;" m line:843 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering test_filtering.py 869;" m line:869 class:TimeRangeFilterTests access:public signature:(self) test_filtering test_filtering.py 893;" m line:893 class:AllValuesFilterTests access:public signature:(self) test_filtering test_filtering.py 933;" m line:933 class:AllValuesMultipleFilterTests access:public signature:(self) test_filtering test_filtering.py 961;" m line:961 class:FilterMethodTests access:public signature:(self) test_filtering test_filtering.py 99;" m line:99 class:BooleanFilterTests access:public signature:(self) test_filtering test_filters.py 1066;" m line:1066 class:CSVFilterTests access:public signature:(self) test_filtering test_filters.py 1086;" m line:1086 class:BaseInFilterTests access:public signature:(self) test_filtering test_filters.py 1097;" m line:1097 class:BaseRangeFilterTests access:public signature:(self) test_filtering test_filters.py 1113;" m line:1113 class:OrderingFilterTests access:public signature:(self) test_filtering test_filters.py 143;" m line:143 class:FilterTests access:public signature:(self) test_filtering test_filters.py 266;" m line:266 class:BooleanFilterTests access:public signature:(self) test_filtering test_filters.py 407;" m line:407 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering test_filters.py 647;" m line:647 class:NumberFilterTests access:public signature:(self) test_filtering test_filters.py 675;" m line:675 class:NumericRangeFilterTests access:public signature:(self) test_filtering test_filters.py 771;" m line:771 class:DateRangeFilterTests access:public signature:(self) test_filtering_callable test_filtering.py 978;" m line:978 class:FilterMethodTests access:public signature:(self) test_filtering_descending test_filters.py 1119;" m line:1119 class:OrderingFilterTests access:public signature:(self) test_filtering_dictionary test_filtering.py 518;" m line:518 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_filtering_exclude test_filters.py 150;" m line:150 class:FilterTests access:public signature:(self) test_filtering_exclude test_filters.py 273;" m line:273 class:BooleanFilterTests access:public signature:(self) test_filtering_exclude test_filters.py 422;" m line:422 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering_exclude test_filters.py 657;" m line:657 class:NumberFilterTests access:public signature:(self) test_filtering_exclude test_filters.py 682;" m line:682 class:NumericRangeFilterTests access:public signature:(self) test_filtering_exclude test_filters.py 724;" m line:724 class:RangeFilterTests access:public signature:(self) test_filtering_for_7_days test_filters.py 803;" m line:803 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_month test_filtering.py 754;" m line:754 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_this_month test_filters.py 794;" m line:794 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_this_year test_filters.py 785;" m line:785 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_today test_filtering.py 780;" m line:780 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_today test_filters.py 818;" m line:818 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_week test_filtering.py 769;" m line:769 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_year test_filtering.py 740;" m line:740 class:DateRangeFilterTests access:public signature:(self) test_filtering_for_yesterday test_filters.py 829;" m line:829 class:DateRangeFilterTests access:public signature:(self) test_filtering_ignores_lookup_expr test_filters.py 751;" m line:751 class:RangeFilterTests access:public signature:(self) test_filtering_ignores_lookup_expr test_filters.py 877;" m line:877 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_ignores_lookup_expr test_filters.py 922;" m line:922 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering_ignores_lookup_expr test_filters.py 967;" m line:967 class:TimeRangeFilterTests access:public signature:(self) test_filtering_ignores_time test_filtering.py 817;" m line:817 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_lookup_expr test_filters.py 294;" m line:294 class:BooleanFilterTests access:public signature:(self) test_filtering_on_all_of_subset_of_choices test_filtering.py 537;" m line:537 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_filtering_on_empty_choice test_filtering.py 188;" m line:188 class:ChoiceFilterTests access:public signature:(self) test_filtering_on_explicitly_defined_field test_filtering.py 161;" m line:161 class:ChoiceFilterTests access:public signature:(self) test_filtering_on_non_required_fields test_filtering.py 560;" m line:560 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_filtering_on_null_choice test_filtering.py 199;" m line:199 class:ChoiceFilterTests access:public signature:(self) test_filtering_on_required_skipped_when_len_of_value_is_len_of_field_choices test_filters.py 437;" m line:437 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering_range test_filters.py 717;" m line:717 class:RangeFilterTests access:public signature:(self) test_filtering_range test_filters.py 849;" m line:849 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_range test_filters.py 893;" m line:893 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering_range test_filters.py 939;" m line:939 class:TimeRangeFilterTests access:public signature:(self) test_filtering_requires_name test_filters.py 393;" m line:393 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering_skipped_with_blank_value test_filters.py 164;" m line:164 class:FilterTests access:public signature:(self) test_filtering_skipped_with_blank_value test_filters.py 280;" m line:280 class:BooleanFilterTests access:public signature:(self) test_filtering_skipped_with_empty_list_value_and_some_choices test_filters.py 452;" m line:452 class:MultipleChoiceFilterTests access:public signature:(self) test_filtering_skipped_with_list_value_with_blank test_filters.py 185;" m line:185 class:FilterTests access:public signature:(self) test_filtering_skipped_with_list_value_with_blank_lookup test_filters.py 192;" m line:192 class:FilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 1072;" m line:1072 class:CSVFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 1131;" m line:1131 class:OrderingFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 171;" m line:171 class:FilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 287;" m line:287 class:BooleanFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 689;" m line:689 class:NumericRangeFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 745;" m line:745 class:RangeFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 871;" m line:871 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 916;" m line:916 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering_skipped_with_none_value test_filters.py 961;" m line:961 class:TimeRangeFilterTests access:public signature:(self) test_filtering_skipped_with_out_of_range_value test_filters.py 778;" m line:778 class:DateRangeFilterTests access:public signature:(self) test_filtering_start test_filters.py 731;" m line:731 class:RangeFilterTests access:public signature:(self) test_filtering_start test_filters.py 857;" m line:857 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_start test_filters.py 902;" m line:902 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering_start test_filters.py 947;" m line:947 class:TimeRangeFilterTests access:public signature:(self) test_filtering_stop test_filters.py 738;" m line:738 class:RangeFilterTests access:public signature:(self) test_filtering_stop test_filters.py 864;" m line:864 class:DateFromToRangeFilterTests access:public signature:(self) test_filtering_stop test_filters.py 909;" m line:909 class:DateTimeFromToRangeFilterTests access:public signature:(self) test_filtering_stop test_filters.py 954;" m line:954 class:TimeRangeFilterTests access:public signature:(self) test_filtering_to_field_name test_filters.py 611;" m line:611 class:ModelMultipleChoiceFilterTests access:public signature:(self) test_filtering_uses_distinct test_filters.py 208;" m line:208 class:FilterTests access:public signature:(self) test_filtering_uses_name test_filters.py 157;" m line:157 class:FilterTests access:public signature:(self) test_filtering_with_declared_filters test_filtering.py 1697;" m line:1697 class:MiscFilterSetTests access:public signature:(self) test_filtering_with_fields test_filters.py 1125;" m line:1125 class:OrderingFilterTests access:public signature:(self) test_filtering_with_list_value test_filters.py 178;" m line:178 class:FilterTests access:public signature:(self) test_filtering_with_multiple_filters test_filtering.py 1711;" m line:1711 class:MiscFilterSetTests access:public signature:(self) test_filtering_with_multiple_lookup_exprs test_filtering.py 415;" m line:415 class:DurationFilterTests access:public signature:(self) test_filtering_with_multiple_lookup_exprs test_filtering.py 632;" m line:632 class:NumberFilterTests access:public signature:(self) test_filtering_with_single_lookup_expr test_filtering.py 610;" m line:610 class:NumberFilterTests access:public signature:(self) test_filtering_with_single_lookup_expr_dictionary test_filtering.py 390;" m line:390 class:DurationFilterTests access:public signature:(self) test_filtering_with_single_lookup_expr_dictionary test_filtering.py 622;" m line:622 class:NumberFilterTests access:public signature:(self) test_filtering_without_strict test_filtering.py 911;" m line:911 class:AllValuesFilterTests access:public signature:(self) test_filters_for_model test_filterset.py 60;" m line:60 class:HelperMethodsTests access:public signature:(self) test_filterset_class_inheritance test_filterset.py 483;" m line:483 class:FilterSetClassCreationTests access:public signature:(self) test_filterset_factory test_filterset.py 64;" m line:64 class:HelperMethodsTests access:public signature:(self) test_filterset_for_mti_model test_filterset.py 544;" m line:544 class:FilterSetClassCreationTests access:public signature:(self) test_filterset_for_proxy_model test_filterset.py 531;" m line:531 class:FilterSetClassCreationTests access:public signature:(self) test_fk_relation test_filtering.py 1108;" m line:1108 class:FKRelationshipTests access:public signature:(self) test_fk_relation_attribute test_filtering.py 1161;" m line:1161 class:FKRelationshipTests access:public signature:(self) test_fk_relation_attribute_on_m2m_relation test_filtering.py 1417;" m line:1417 class:M2MRelationshipTests access:public signature:(self) test_fk_relation_multiple_attributes test_filtering.py 1221;" m line:1221 class:FKRelationshipTests access:public signature:(self) test_fk_relation_on_m2m_relation test_filtering.py 1413;" m line:1413 class:M2MRelationshipTests access:public signature:(self) test_form test_forms.py 25;" m line:25 class:FilterSetFormTests access:public signature:(self) test_form_field_with_custom_label test_forms.py 128;" m line:128 class:FilterSetFormTests access:public signature:(self) test_form_field_with_manual_name test_forms.py 140;" m line:140 class:FilterSetFormTests access:public signature:(self) test_form_field_with_manual_name_and_label test_forms.py 152;" m line:152 class:FilterSetFormTests access:public signature:(self) test_form_fields test_forms.py 60;" m line:60 class:FilterSetFormTests access:public signature:(self) test_form_fields_exclusion test_forms.py 74;" m line:74 class:FilterSetFormTests access:public signature:(self) test_form_fields_using_widget test_forms.py 108;" m line:108 class:FilterSetFormTests access:public signature:(self) test_form_from_empty_filterset test_forms.py 18;" m line:18 class:FilterSetFormTests access:public signature:(self) test_form_is_bound test_forms.py 185;" m line:185 class:FilterSetFormTests access:public signature:(self) test_form_is_not_bound test_forms.py 175;" m line:175 class:FilterSetFormTests access:public signature:(self) test_form_prefix test_forms.py 48;" m line:48 class:FilterSetFormTests access:public signature:(self) test_forward_relation test_filtering.py 1456;" m line:1456 class:NonSymmetricalSelfReferentialRelationshipTests access:public signature:(self) test_forwards_related_field test_utils.py 227;" m line:227 class:VerboseFieldNameTests access:public signature:(self) test_forwards_related_field test_utils.py 35;" m line:35 class:GetFieldPartsTests access:public signature:(self) test_get_declared_filters test_filterset.py 56;" m line:56 class:HelperMethodsTests access:public signature:(self) test_get_filtered_class_root_view rest_framework/test_backends.py 233;" m line:233 class:IntegrationTestFiltering access:public signature:(self) test_get_filtered_detail_view rest_framework/test_backends.py 379;" m line:379 class:IntegrationTestDetailFiltering access:public signature:(self) test_get_filtered_fields_root_view rest_framework/test_backends.py 181;" m line:181 class:IntegrationTestFiltering access:public signature:(self) test_get_queryset_override test_filters.py 582;" m line:582 class:ModelChoiceFilterTests access:public signature:(self) test_help_text_exclude test_conf.py 25;" m line:25 class:DefaultSettingsTests access:public signature:(self) test_help_text_filter test_conf.py 22;" m line:22 class:DefaultSettingsTests access:public signature:(self) test_html_rendering rest_framework/test_backends.py 314;" m line:314 class:IntegrationTestFiltering access:public signature:(self) test_ignore test_conf.py 46;" m line:46 class:StrictnessTests access:public signature:(self) test_imports test_filters.py 57;" m line:57 class:ModuleImportTests access:public signature:(self) test_incorrectly_configured_filter rest_framework/test_backends.py 283;" m line:283 class:IntegrationTestFiltering access:public signature:(self) test_init_default test_filterset.py 631;" m line:631 class:FilterSetStrictnessTests access:public signature:(self) test_invalid_field_lookup test_filtering.py 1767;" m line:1767 class:MiscFilterSetTests access:public signature:(self) test_invalid_lookup_expression test_utils.py 188;" m line:188 class:ResolveFieldTests access:public signature:(self) test_invalid_name test_utils.py 215;" m line:215 class:VerboseFieldNameTests access:public signature:(self) test_invalid_transformed_lookup_expression test_utils.py 198;" m line:198 class:ResolveFieldTests access:public signature:(self) test_isnull_with_filter_overrides test_filterset.py 229;" m line:229 class:FilterSetFilterForLookupTests access:public signature:(self) test_isodatetimefilter rest_framework/test_filterset.py 12;" m line:12 class:FilterSetFilterForFieldTests access:public signature:(self) test_legacy_differentiation test_conf.py 70;" m line:70 class:StrictnessTests access:public signature:(self) test_legacy_ignore test_conf.py 58;" m line:58 class:StrictnessTests access:public signature:(self) test_legacy_raise_validation_error test_conf.py 66;" m line:66 class:StrictnessTests access:public signature:(self) test_legacy_return_no_results test_conf.py 62;" m line:62 class:StrictnessTests access:public signature:(self) test_legacy_value test_filterset.py 640;" m line:640 class:FilterSetStrictnessTests access:public signature:(self) test_limit_choices_to test_forms.py 195;" m line:195 class:FilterSetFormTests access:public signature:(self) test_lookup_false test_fields.py 29;" m line:29 class:LookupBoolTests access:public signature:(self) test_lookup_false test_filters.py 229;" m line:229 class:CustomFilterWithBooleanCheckTests access:public signature:(self) test_lookup_true test_fields.py 23;" m line:23 class:LookupBoolTests access:public signature:(self) test_lookup_true test_filters.py 235;" m line:235 class:CustomFilterWithBooleanCheckTests access:public signature:(self) test_m2m_field_with_through_model test_filterset.py 186;" m line:186 class:FilterSetFilterForFieldTests access:public signature:(self) test_m2m_relation test_filtering.py 1259;" m line:1259 class:M2MRelationshipTests access:public signature:(self) test_m2m_relation_attribute test_filtering.py 1300;" m line:1300 class:M2MRelationshipTests access:public signature:(self) test_m2m_relation_multiple_attributes test_filtering.py 1379;" m line:1379 class:M2MRelationshipTests access:public signature:(self) test_meta_exlude_with_declared_and_declared_wins test_filterset.py 445;" m line:445 class:FilterSetClassCreationTests access:public signature:(self) test_meta_exlude_with_no_fields test_filterset.py 472;" m line:472 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_and_exlude_and_exclude_wins test_filterset.py 458;" m line:458 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_containing_autofield test_filterset.py 394;" m line:394 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_containing_unknown test_filterset.py 422;" m line:422 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_dictionary_autofield test_filterset.py 406;" m line:406 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_dictionary_containing_unknown test_filterset.py 434;" m line:434 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_dictionary_derived test_filterset.py 381;" m line:381 class:FilterSetClassCreationTests access:public signature:(self) test_meta_fields_with_declared_and_model_derived test_filterset.py 369;" m line:369 class:FilterSetClassCreationTests access:public signature:(self) test_meta_value test_filterset.py 623;" m line:623 class:FilterSetStrictnessTests access:public signature:(self) test_method_callable test_filterset.py 715;" m line:715 class:FilterMethodTests access:public signature:(self) test_method_name test_filterset.py 703;" m line:703 class:FilterMethodTests access:public signature:(self) test_method_self_is_parent test_filterset.py 763;" m line:763 class:FilterMethodTests access:public signature:(self) test_method_set_unset test_filterset.py 805;" m line:805 class:FilterMethodTests access:public signature:(self) test_method_uncallable test_filterset.py 792;" m line:792 class:FilterMethodTests access:public signature:(self) test_method_unresolvable test_filterset.py 780;" m line:780 class:FilterMethodTests access:public signature:(self) test_method_with_overridden_filter test_filterset.py 741;" m line:741 class:FilterMethodTests access:public signature:(self) test_missing_attribute_override test_conf.py 88;" m line:88 class:OverrideSettingsTests access:public signature:(self) test_missing_keys test_utils.py 248;" m line:248 class:VerboseLookupExprTests access:public signature:(self) test_model_derived test_filterset.py 313;" m line:313 class:FilterSetClassCreationTests access:public signature:(self) test_model_exclude_empty test_filterset.py 344;" m line:344 class:FilterSetClassCreationTests access:public signature:(self) test_model_exclude_is_none test_filterset.py 333;" m line:333 class:FilterSetClassCreationTests access:public signature:(self) test_model_no_fields_or_exclude test_filterset.py 324;" m line:324 class:FilterSetClassCreationTests access:public signature:(self) test_no__getitem__ test_filterset.py 822;" m line:822 class:MiscFilterSetTests access:public signature:(self) test_no_filters test_filterset.py 297;" m line:297 class:FilterSetClassCreationTests access:public signature:(self) test_no_qs_proxying test_filterset.py 830;" m line:830 class:MiscFilterSetTests access:public signature:(self) test_non_existent_field test_utils.py 31;" m line:31 class:GetFieldPartsTests access:public signature:(self) test_non_existent_field test_utils.py 53;" m line:53 class:GetModelFieldTests access:public signature:(self) test_non_existent_setting test_conf.py 111;" m line:111 class:OverrideSettingsTests access:public signature:(self) test_non_filters_setting test_conf.py 103;" m line:103 class:OverrideSettingsTests access:public signature:(self) test_non_symmetrical_selfref_m2m_field test_filterset.py 177;" m line:177 class:FilterSetFilterForFieldTests access:public signature:(self) test_none test_filterset.py 692;" m line:692 class:FilterMethodTests access:public signature:(self) test_none test_utils.py 211;" m line:211 class:VerboseFieldNameTests access:public signature:(self) test_normalize_fields test_filters.py 1177;" m line:1177 class:OrderingFilterTests access:public signature:(self) test_null_choice test_filters.py 330;" m line:330 class:ChoiceFilterTests access:public signature:(self) test_null_choice_label test_conf.py 31;" m line:31 class:DefaultSettingsTests access:public signature:(self) test_null_choice_value test_conf.py 34;" m line:34 class:DefaultSettingsTests access:public signature:(self) test_numeric_filtering test_filtering.py 1545;" m line:1545 class:CSVFilterTests access:public signature:(self) test_o2o_relation test_filtering.py 1012;" m line:1012 class:O2ORelationshipTests access:public signature:(self) test_o2o_relation_attribute test_filtering.py 1053;" m line:1053 class:O2ORelationshipTests access:public signature:(self) test_o2o_relation_attribute2 test_filtering.py 1066;" m line:1066 class:O2ORelationshipTests access:public signature:(self) test_o2o_relation_dictionary test_filtering.py 1026;" m line:1026 class:O2ORelationshipTests access:public signature:(self) test_ordering test_filtering.py 1657;" m line:1657 class:OrderingFilterTests access:public signature:(self) test_ordering_with_select_widget test_filtering.py 1672;" m line:1672 class:OrderingFilterTests access:public signature:(self) test_overridden_settings test_utils.py 253;" m line:253 class:VerboseLookupExprTests access:public signature:(self) test_parent_unresolvable test_filterset.py 754;" m line:754 class:FilterMethodTests access:public signature:(self) test_qs_count test_filtering.py 1744;" m line:1744 class:MiscFilterSetTests access:public signature:(self) test_raise_validation_error test_conf.py 54;" m line:54 class:StrictnessTests access:public signature:(self) test_related_field test_utils.py 57;" m line:57 class:GetModelFieldTests access:public signature:(self) test_related_filtering test_filtering.py 1623;" m line:1623 class:CSVFilterTests access:public signature:(self) test_related_model test_utils.py 264;" m line:264 class:LabelForFilterTests access:public signature:(self) test_related_model_exclusion test_utils.py 272;" m line:272 class:LabelForFilterTests access:public signature:(self) test_relation test_filtering.py 1433;" m line:1433 class:SymmetricalSelfReferentialRelationshipTests access:public signature:(self) test_render_used_html5 test_fields.py 112;" m line:112 class:LookupTypeFieldTests access:public signature:(self) test_request_available_during_method_called test_filterset.py 727;" m line:727 class:FilterMethodTests access:public signature:(self) test_resolve_forward_related_lookups test_utils.py 79;" m line:79 class:ResolveFieldTests access:public signature:(self) test_resolve_implicit_exact_lookup test_utils.py 175;" m line:175 class:ResolveFieldTests access:public signature:(self) test_resolve_plain_lookups test_utils.py 64;" m line:64 class:ResolveFieldTests access:public signature:(self) test_resolve_reverse_related_lookups test_utils.py 101;" m line:101 class:ResolveFieldTests access:public signature:(self) test_resolve_transformed_lookups test_utils.py 123;" m line:123 class:ResolveFieldTests access:public signature:(self) test_return_no_results test_conf.py 50;" m line:50 class:StrictnessTests access:public signature:(self) test_reverse_fk_relation test_filtering.py 1130;" m line:1130 class:FKRelationshipTests access:public signature:(self) test_reverse_fk_relation_attribute test_filtering.py 1189;" m line:1189 class:FKRelationshipTests access:public signature:(self) test_reverse_fk_relation_multiple_attributes test_filtering.py 1225;" m line:1225 class:FKRelationshipTests access:public signature:(self) test_reverse_fk_relationship test_filterset.py 258;" m line:258 class:FilterSetFilterForReverseFieldTests access:public signature:(self) test_reverse_m2m_field_with_through_model test_filterset.py 285;" m line:285 class:FilterSetFilterForReverseFieldTests access:public signature:(self) test_reverse_m2m_relation test_filtering.py 1278;" m line:1278 class:M2MRelationshipTests access:public signature:(self) test_reverse_m2m_relation_attribute test_filtering.py 1338;" m line:1338 class:M2MRelationshipTests access:public signature:(self) test_reverse_m2m_relation_multiple_attributes test_filtering.py 1398;" m line:1398 class:M2MRelationshipTests access:public signature:(self) test_reverse_m2m_relationship test_filterset.py 267;" m line:267 class:FilterSetFilterForReverseFieldTests access:public signature:(self) test_reverse_non_symmetrical_selfref_m2m_field test_filterset.py 276;" m line:276 class:FilterSetFilterForReverseFieldTests access:public signature:(self) test_reverse_o2o_relation test_filtering.py 1040;" m line:1040 class:O2ORelationshipTests access:public signature:(self) test_reverse_o2o_relation_attribute test_filtering.py 1079;" m line:1079 class:O2ORelationshipTests access:public signature:(self) test_reverse_o2o_relation_attribute2 test_filtering.py 1092;" m line:1092 class:O2ORelationshipTests access:public signature:(self) test_reverse_o2o_relationship test_filterset.py 249;" m line:249 class:FilterSetFilterForReverseFieldTests access:public signature:(self) test_reverse_related_field test_utils.py 42;" m line:42 class:GetFieldPartsTests access:public signature:(self) test_reverse_relation test_filtering.py 1466;" m line:1466 class:NonSymmetricalSelfReferentialRelationshipTests access:public signature:(self) test_settings_default test_conf.py 43;" m line:43 class:StrictnessTests access:public signature:(self) test_settings_default test_filterset.py 611;" m line:611 class:FilterSetStrictnessTests access:public signature:(self) test_settings_overrides test_filters.py 365;" m line:365 class:ChoiceFilterTests access:public signature:(self) test_single_fields_set test_filterset.py 673;" m line:673 class:FilterSetTogetherTests access:public signature:(self) test_standard_label test_utils.py 260;" m line:260 class:LabelForFilterTests access:public signature:(self) test_strictness test_conf.py 19;" m line:19 class:DefaultSettingsTests access:public signature:(self) test_string_filtering test_filtering.py 1570;" m line:1570 class:CSVFilterTests access:public signature:(self) test_symmetrical_selfref_m2m_field test_filterset.py 168;" m line:168 class:FilterSetFilterForFieldTests access:public signature:(self) test_template_path rest_framework/test_backends.py 348;" m line:348 class:IntegrationTestFiltering access:public signature:(self) test_transformed_lookup_expr test_filterset.py 196;" m line:196 class:FilterSetFilterForFieldTests access:public signature:(self) test_unknown_field_type_error test_filterset.py 157;" m line:157 class:FilterSetFilterForFieldTests access:public signature:(self) test_unknown_filter rest_framework/test_backends.py 303;" m line:303 class:IntegrationTestFiltering access:public signature:(self) test_validation_error test_fields.py 190;" m line:190 class:BaseCSVFieldTests access:public signature:(self) test_validation_error test_fields.py 228;" m line:228 class:BaseRangeFieldTests access:public signature:(self) test_verbose_expression test_utils.py 244;" m line:244 class:VerboseLookupExprTests access:public signature:(self) test_verbose_lookups test_conf.py 12;" m line:12 class:DefaultSettingsTests access:public signature:(self) test_view test_views.py 29;" m line:29 class:GenericClassBasedViewTests access:public signature:(self) test_view test_views.py 71;" m line:71 class:GenericFunctionalViewTests access:public signature:(self) test_view_filtering_on_price test_views.py 76;" m line:76 class:GenericFunctionalViewTests access:public signature:(self) test_view_filtering_on_title test_views.py 34;" m line:34 class:GenericClassBasedViewTests access:public signature:(self) test_view_with_bad_filterset test_views.py 57;" m line:57 class:GenericClassBasedViewTests access:public signature:(self) test_view_with_filterset_not_model test_views.py 40;" m line:40 class:GenericClassBasedViewTests access:public signature:(self) test_view_without_filterset_or_model test_views.py 50;" m line:50 class:GenericClassBasedViewTests access:public signature:(self) test_widget rest_framework/test_filters.py 10;" m line:10 class:BooleanFilterTests access:public signature:(self) test_widget test_filters.py 1210;" m line:1210 class:OrderingFilterTests access:public signature:(self) test_widget test_widgets.py 126;" m line:126 class:RangeWidgetTests access:public signature:(self) test_widget test_widgets.py 178;" m line:178 class:CSVWidgetTests access:public signature:(self) test_widget test_widgets.py 237;" m line:237 class:CSVSelectTests access:public signature:(self) test_widget test_widgets.py 53;" m line:53 class:LinkWidgetTests access:public signature:(self) test_widget_attributes test_widgets.py 139;" m line:139 class:RangeWidgetTests access:public signature:(self) test_widget_render test_widgets.py 151;" m line:151 class:BooleanWidgetTests access:public signature:(self) test_widget_render test_widgets.py 21;" m line:21 class:LookupTypeWidgetTests access:public signature:(self) test_widget_requires_field test_widgets.py 17;" m line:17 class:LookupTypeWidgetTests access:public signature:(self) test_widget_value_from_datadict test_widgets.py 117;" m line:117 class:LinkWidgetTests access:public signature:(self) test_widget_value_from_datadict test_widgets.py 160;" m line:160 class:BooleanWidgetTests access:public signature:(self) test_widget_value_from_datadict test_widgets.py 198;" m line:198 class:CSVWidgetTests access:public signature:(self) test_widget_with_blank_choice test_widgets.py 102;" m line:102 class:LinkWidgetTests access:public signature:(self) test_widget_with_option_groups test_widgets.py 79;" m line:79 class:LinkWidgetTests access:public signature:(self) test_widget_without_choices test_widgets.py 48;" m line:48 class:LinkWidgetTests access:public signature:(self) test_zero_to_zero test_filters.py 702;" m line:702 class:NumericRangeFilterTests access:public signature:(self) text models.py 87;" v line:87 class:Comment access:public text rest_framework/models.py 15;" v line:15 class:BaseFilterableItem access:public text rest_framework/models.py 25;" v line:25 class:DjangoFilterOrderingModel access:public text rest_framework/models.py 7;" v line:7 class:BasicModel access:public text rest_framework/test_backends.py 46;" v line:46 class:SeveralFieldsFilter access:public text rest_framework/test_backends.py 64;" v line:64 class:MisconfiguredFilter access:public text rest_framework/test_backends.py 87;" v line:87 class:BaseFilterableItemFilter access:public time models.py 91;" v line:91 class:Comment access:public time test_filtering.py 879;" v line:879 class:TimeRangeFilterTests.test_filtering.F access:public title models.py 110;" v line:110 class:Book access:public title test_forms.py 130;" v line:130 class:FilterSetFormTests.test_form_field_with_custom_label.F access:public title test_forms.py 76;" v line:76 class:FilterSetFormTests.test_form_fields_exclusion.F access:public to_d test_fields.py 18;" f line:18 access:public signature:(float_value) together test_filterset.py 660;" v line:660 class:FilterSetTogetherTests.test_fields_set.F.Meta access:public together test_filterset.py 678;" v line:678 class:FilterSetTogetherTests.test_single_fields_set.F.Meta access:public urlpatterns rest_framework/test_backends.py 118;" v line:118 access:public urlpatterns urls.py 10;" v line:10 access:public username models.py 48;" v line:48 class:User access:public username test_filtering.py 899;" v line:899 class:AllValuesFilterTests.test_filtering.F access:public username test_filtering.py 917;" v line:917 class:AllValuesFilterTests.test_filtering_without_strict.F access:public username test_filtering.py 939;" v line:939 class:AllValuesMultipleFilterTests.test_filtering.F access:public username test_filtering.py 963;" v line:963 class:FilterMethodTests.test_filtering.F access:public username test_filtering.py 983;" v line:983 class:FilterMethodTests.test_filtering_callable.F access:public username test_filterset.py 306;" v line:306 class:FilterSetClassCreationTests.test_declaring_filter.F access:public username test_filterset.py 358;" v line:358 class:FilterSetClassCreationTests.test_declared_and_model_derived.F access:public username test_filterset.py 371;" v line:371 class:FilterSetClassCreationTests.test_meta_fields_with_declared_and_model_derived.F access:public username test_filterset.py 396;" v line:396 class:FilterSetClassCreationTests.test_meta_fields_containing_autofield.F access:public username test_filterset.py 408;" v line:408 class:FilterSetClassCreationTests.test_meta_fields_dictionary_autofield.F access:public username test_filterset.py 425;" v line:425 class:FilterSetClassCreationTests.test_meta_fields_containing_unknown.F access:public username test_filterset.py 447;" v line:447 class:FilterSetClassCreationTests.test_meta_exlude_with_declared_and_declared_wins.F access:public username test_filterset.py 460;" v line:460 class:FilterSetClassCreationTests.test_meta_fields_and_exlude_and_exclude_wins.F access:public username test_forms.py 87;" v line:87 class:FilterSetFormTests.test_complex_form_fields.F access:public users models.py 64;" v line:64 class:ManagerGroup access:public users test_filtering.py 458;" f line:458 function:ModelChoiceFilterTests.test_callable_queryset file: access:private signature:(request) uuid models.py 203;" v line:203 class:UUIDTestModel access:public verbose_name rest_framework/apps.py 8;" v line:8 class:RestFrameworkTestConfig access:public worker models.py 191;" v line:191 class:HiredWorker access:public zip_code models.py 149;" v line:149 class:Location access:public django-filter-1.1.0/tests/templates/0000755000076500000240000000000013172070045020162 5ustar carltonstaff00000000000000django-filter-1.1.0/tests/templates/tests/0000755000076500000240000000000013172070045021324 5ustar carltonstaff00000000000000django-filter-1.1.0/tests/templates/tests/book_filter.html0000644000076500000240000000011112410601557024504 0ustar carltonstaff00000000000000{{ filter.form }} {% for obj in filter.qs %} {{ obj }} {% endfor %} django-filter-1.1.0/tests/test_conf.py0000644000076500000240000001115613172067725020541 0ustar carltonstaff00000000000000 from django.test import TestCase, override_settings from django_filters import STRICTNESS, FilterSet from django_filters.conf import is_callable, settings from tests.models import User class DefaultSettingsTests(TestCase): def test_verbose_lookups(self): self.assertIsInstance(settings.VERBOSE_LOOKUPS, dict) self.assertIn('exact', settings.VERBOSE_LOOKUPS) def test_disable_help_text(self): self.assertFalse(settings.DISABLE_HELP_TEXT) def test_strictness(self): self.assertEqual(settings.STRICTNESS, STRICTNESS.RETURN_NO_RESULTS) def test_help_text_filter(self): self.assertTrue(settings.HELP_TEXT_FILTER) def test_help_text_exclude(self): self.assertTrue(settings.HELP_TEXT_EXCLUDE) def test_empty_choice_label(self): self.assertEqual(settings.EMPTY_CHOICE_LABEL, '---------') def test_null_choice_label(self): self.assertIsNone(settings.NULL_CHOICE_LABEL) def test_null_choice_value(self): self.assertEqual(settings.NULL_CHOICE_VALUE, 'null') class StrictnessTests(TestCase): class F(FilterSet): class Meta: model = User fields = [] def test_settings_default(self): self.assertEqual(self.F().strict, STRICTNESS.RETURN_NO_RESULTS) def test_ignore(self): with override_settings(FILTERS_STRICTNESS=STRICTNESS.IGNORE): self.assertEqual(self.F().strict, STRICTNESS.IGNORE) def test_return_no_results(self): with override_settings(FILTERS_STRICTNESS=STRICTNESS.RETURN_NO_RESULTS): self.assertEqual(self.F().strict, STRICTNESS.RETURN_NO_RESULTS) def test_raise_validation_error(self): with override_settings(FILTERS_STRICTNESS=STRICTNESS.RAISE_VALIDATION_ERROR): self.assertEqual(self.F().strict, STRICTNESS.RAISE_VALIDATION_ERROR) def test_legacy_ignore(self): with override_settings(FILTERS_STRICTNESS=False): self.assertEqual(self.F().strict, STRICTNESS.IGNORE) def test_legacy_return_no_results(self): with override_settings(FILTERS_STRICTNESS=True): self.assertEqual(self.F().strict, STRICTNESS.RETURN_NO_RESULTS) def test_legacy_raise_validation_error(self): with override_settings(FILTERS_STRICTNESS='RAISE'): self.assertEqual(self.F().strict, STRICTNESS.RAISE_VALIDATION_ERROR) def test_legacy_differentiation(self): self.assertNotEqual(STRICTNESS.IGNORE, False) self.assertNotEqual(STRICTNESS.RETURN_NO_RESULTS, True) self.assertNotEqual(STRICTNESS.RAISE_VALIDATION_ERROR, 'RAISE') class OverrideSettingsTests(TestCase): def test_attribute_override(self): self.assertIsInstance(settings.VERBOSE_LOOKUPS, dict) original = settings.VERBOSE_LOOKUPS with override_settings(FILTERS_VERBOSE_LOOKUPS=None): self.assertIsNone(settings.VERBOSE_LOOKUPS) self.assertIs(settings.VERBOSE_LOOKUPS, original) def test_missing_attribute_override(self): # ensure that changed setting behaves correctly when # not originally present in the user's settings. from django.conf import settings as dj_settings self.assertFalse(hasattr(dj_settings, 'FILTERS_HELP_TEXT_FILTER')) # Default value self.assertTrue(settings.HELP_TEXT_FILTER) with override_settings(FILTERS_HELP_TEXT_FILTER=None): self.assertIsNone(settings.HELP_TEXT_FILTER) # Revert to default self.assertTrue(settings.HELP_TEXT_FILTER) def test_non_filters_setting(self): self.assertFalse(hasattr(settings, 'USE_TZ')) with override_settings(USE_TZ=False): self.assertFalse(hasattr(settings, 'USE_TZ')) self.assertFalse(hasattr(settings, 'USE_TZ')) def test_non_existent_setting(self): self.assertFalse(hasattr(settings, 'FILTERS_FOOBAR')) self.assertFalse(hasattr(settings, 'FOOBAR')) with override_settings(FILTERS_FOOBAR='blah'): self.assertFalse(hasattr(settings, 'FILTERS_FOOBAR')) self.assertFalse(hasattr(settings, 'FOOBAR')) self.assertFalse(hasattr(settings, 'FILTERS_FOOBAR')) self.assertFalse(hasattr(settings, 'FOOBAR')) class IsCallableTests(TestCase): def test_behavior(self): def func(): pass class Class(object): def __call__(self): pass def method(self): pass c = Class() self.assertTrue(is_callable(func)) self.assertFalse(is_callable(Class)) self.assertTrue(is_callable(c)) self.assertTrue(is_callable(c.method)) django-filter-1.1.0/tests/test_deprecations.py0000644000076500000240000000337713172067725022302 0ustar carltonstaff00000000000000 import warnings from django.test import TestCase from django_filters import FilterSet, filters class TogetherOptionDeprecationTests(TestCase): def test_deprecation(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") class F(FilterSet): class Meta: together = ['a', 'b'] self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn('The `Meta.together` option has been deprecated', str(w[0].message)) class FilterNameDeprecationTests(TestCase): def test_declaration(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") class F(FilterSet): foo = filters.CharFilter(name='foo') self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn("`Filter.name` has been renamed to `Filter.field_name`.", str(w[0].message)) def test_name_property(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") filters.CharFilter().name self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn("`Filter.name` has been renamed to `Filter.field_name`.", str(w[0].message)) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") filters.CharFilter().name = 'bar' self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[0].category, DeprecationWarning)) self.assertIn("`Filter.name` has been renamed to `Filter.field_name`.", str(w[0].message)) django-filter-1.1.0/tests/test_fields.py0000644000076500000240000002145413172067725021064 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals import decimal from datetime import datetime, time, timedelta, tzinfo import pytz from django import forms from django.test import TestCase, override_settings from django.utils import timezone from django_filters.fields import ( BaseCSVField, BaseRangeField, DateRangeField, DateTimeRangeField, IsoDateTimeField, Lookup, LookupTypeField, RangeField, TimeRangeField ) from django_filters.widgets import BaseCSVWidget, CSVWidget, RangeWidget def to_d(float_value): return decimal.Decimal('%.2f' % float_value) class LookupBoolTests(TestCase): def test_lookup_true(self): self.assertTrue(Lookup(True, 'exact')) self.assertTrue(Lookup(1, 'exact')) self.assertTrue(Lookup('1', 'exact')) self.assertTrue(Lookup(datetime.now(), 'exact')) def test_lookup_false(self): self.assertFalse(Lookup(False, 'exact')) self.assertFalse(Lookup(0, 'exact')) self.assertFalse(Lookup('', 'exact')) self.assertFalse(Lookup(None, 'exact')) 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, required=False) self.assertEqual( f.clean(['12.34', '55']), slice(to_d(12.34), to_d(55))) self.assertIsNone(f.clean([])) class DateRangeFieldTests(TestCase): def test_field(self): f = DateRangeField() self.assertEqual(len(f.fields), 2) @override_settings(USE_TZ=False) def test_clean(self): w = RangeWidget() f = DateRangeField(widget=w, required=False) self.assertEqual( f.clean(['2015-01-01', '2015-01-10']), slice(datetime(2015, 1, 1, 0, 0, 0), datetime(2015, 1, 10, 23, 59, 59, 999999))) self.assertIsNone(f.clean([])) class DateTimeRangeFieldTests(TestCase): def test_field(self): f = DateTimeRangeField() self.assertEqual(len(f.fields), 2) @override_settings(USE_TZ=False) def test_clean(self): w = RangeWidget() f = DateTimeRangeField(widget=w) self.assertEqual( f.clean(['2015-01-01 10:30', '2015-01-10 8:45']), slice(datetime(2015, 1, 1, 10, 30, 0), datetime(2015, 1, 10, 8, 45, 0))) class TimeRangeFieldTests(TestCase): def test_field(self): f = DateRangeField() self.assertEqual(len(f.fields), 2) def test_clean(self): w = RangeWidget() f = TimeRangeField(widget=w) self.assertEqual( f.clean(['10:15', '12:30']), slice(time(10, 15, 0), time(12, 30, 0))) class LookupTypeFieldTests(TestCase): def test_field(self): inner = forms.DecimalField() f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')]) self.assertEqual(len(f.fields), 2) def test_clean(self): inner = forms.DecimalField() f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')], required=False) self.assertEqual( f.clean(['12.34', 'lt']), Lookup(to_d(12.34), 'lt')) self.assertEqual( f.clean([]), Lookup(value=None, lookup_type='exact')) def test_render_used_html5(self): inner = forms.DecimalField() f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')]) self.assertHTMLEqual(f.widget.render('price', ''), """ """) self.assertHTMLEqual(f.widget.render('price', ['abc', 'lt']), """ """) class IsoDateTimeFieldTests(TestCase): reference_str = "2015-07-19T13:34:51.759" reference_dt = datetime(2015, 7, 19, 13, 34, 51, 759000) field = IsoDateTimeField() def parse_input(self, value): return self.field.strptime(value, IsoDateTimeField.ISO_8601) def test_datetime_string_is_parsed(self): d = self.parse_input(self.reference_str) self.assertTrue(isinstance(d, datetime)) def test_datetime_string_with_timezone_is_parsed(self): d = self.parse_input(self.reference_str + "+01:00") self.assertTrue(isinstance(d, datetime)) def test_datetime_zulu(self): d = self.parse_input(self.reference_str + "Z") self.assertTrue(isinstance(d, datetime)) @override_settings(TIME_ZONE='UTC') def test_datetime_timezone_awareness(self): utc, tokyo = pytz.timezone('UTC'), pytz.timezone('Asia/Tokyo') # by default, use the server timezone reference = utc.localize(self.reference_dt) parsed = self.parse_input(self.reference_str) self.assertIsInstance(parsed.tzinfo, tzinfo) self.assertEqual(parsed, reference) # if set, use the active timezone reference = tokyo.localize(self.reference_dt) with timezone.override(tokyo): parsed = self.parse_input(self.reference_str) self.assertIsInstance(parsed.tzinfo, tzinfo) self.assertEqual(parsed.tzinfo.zone, tokyo.zone) self.assertEqual(parsed, reference) # if provided, utc offset should have precedence reference = utc.localize(self.reference_dt - timedelta(hours=1)) parsed = self.parse_input(self.reference_str + "+01:00") self.assertIsInstance(parsed.tzinfo, tzinfo) self.assertEqual(parsed, reference) @override_settings(USE_TZ=False) def test_datetime_timezone_naivety(self): reference = self.reference_dt.replace() parsed = self.parse_input(self.reference_str + "+01:00") self.assertIsNone(parsed.tzinfo) self.assertEqual(parsed, reference - timedelta(hours=1)) parsed = self.parse_input(self.reference_str) self.assertIsNone(parsed.tzinfo) self.assertEqual(parsed, reference) def test_datetime_non_iso_format(self): f = IsoDateTimeField() parsed = f.strptime('19-07-2015T51:34:13.759', '%d-%m-%YT%S:%M:%H.%f') self.assertTrue(isinstance(parsed, datetime)) self.assertEqual(parsed, self.reference_dt) def test_datetime_wrong_format(self): with self.assertRaises(ValueError): self.parse_input('19-07-2015T51:34:13.759') class BaseCSVFieldTests(TestCase): def setUp(self): class DecimalCSVField(BaseCSVField, forms.DecimalField): pass self.field = DecimalCSVField() def test_clean(self): self.assertEqual(self.field.clean(None), None) self.assertEqual(self.field.clean(''), []) self.assertEqual(self.field.clean(['1']), [1]) self.assertEqual(self.field.clean(['1', '2']), [1, 2]) self.assertEqual(self.field.clean(['1', '2', '3']), [1, 2, 3]) def test_validation_error(self): with self.assertRaises(forms.ValidationError): self.field.clean(['']) with self.assertRaises(forms.ValidationError): self.field.clean(['a', 'b', 'c']) def test_derived_widget(self): with self.assertRaises(AssertionError) as excinfo: BaseCSVField(widget=RangeWidget()) msg = str(excinfo.exception) self.assertIn("'BaseCSVField.widget' must be a widget class", msg) self.assertIn("RangeWidget", msg) widget = CSVWidget(attrs={'class': 'class'}) field = BaseCSVField(widget=widget) self.assertIsInstance(field.widget, CSVWidget) self.assertEqual(field.widget.attrs, {'class': 'class'}) field = BaseCSVField(widget=CSVWidget) self.assertIsInstance(field.widget, CSVWidget) field = BaseCSVField(widget=forms.Select) self.assertIsInstance(field.widget, forms.Select) self.assertIsInstance(field.widget, BaseCSVWidget) class BaseRangeFieldTests(TestCase): def setUp(self): class DecimalRangeField(BaseRangeField, forms.DecimalField): pass self.field = DecimalRangeField() def test_clean(self): self.assertEqual(self.field.clean(None), None) self.assertEqual(self.field.clean(['1', '2']), [1, 2]) def test_validation_error(self): with self.assertRaises(forms.ValidationError): self.field.clean('') with self.assertRaises(forms.ValidationError): self.field.clean(['']) with self.assertRaises(forms.ValidationError): self.field.clean(['1']) with self.assertRaises(forms.ValidationError): self.field.clean(['1', '2', '3']) django-filter-1.1.0/tests/test_filtering.py0000644000076500000240000020731213172067725021600 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals import datetime import mock import unittest import django from django import forms from django.test import TestCase, override_settings from django.utils import six, timezone from django.utils.timezone import now from django_filters.exceptions import FieldLookupError from django_filters.filters import ( AllValuesFilter, AllValuesMultipleFilter, CharFilter, ChoiceFilter, DateFromToRangeFilter, DateRangeFilter, DateTimeFromToRangeFilter, DurationFilter, ModelChoiceFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter, NumberFilter, OrderingFilter, RangeFilter, TimeRangeFilter, TypedMultipleChoiceFilter ) from django_filters.filterset import FilterSet from .models import ( STATUS_CHOICES, Account, Article, BankAccount, Book, Comment, Company, DirectedNode, Location, Node, Profile, SpacewalkRecord, User ) class CharFilterTests(TestCase): def test_filtering(self): b1 = Book.objects.create( title="Ender's Game", price='1.00', average_rating=3.0) b2 = Book.objects.create( title="Rainbow Six", price='1.00', average_rating=3.0) b3 = Book.objects.create( title="Snowcrash", price='1.00', average_rating=3.0) class F(FilterSet): class Meta: model = Book fields = ['title'] qs = Book.objects.all() f = F(queryset=qs) self.assertQuerysetEqual(f.qs, [b1.pk, b2.pk, b3.pk], lambda o: o.pk, ordered=False) f = F({'title': 'Snowcrash'}, queryset=qs) self.assertQuerysetEqual(f.qs, [b3.pk], lambda o: o.pk) class IntegerFilterTest(TestCase): def test_filtering(self): default_values = { 'in_good_standing': True, 'friendly': False, } b1 = BankAccount.objects.create(amount_saved=0, **default_values) b2 = BankAccount.objects.create(amount_saved=3, **default_values) b3 = BankAccount.objects.create(amount_saved=10, **default_values) class F(FilterSet): class Meta: model = BankAccount fields = ['amount_saved'] qs = BankAccount.objects.all() f = F(queryset=qs) self.assertQuerysetEqual(f.qs, [b1.pk, b2.pk, b3.pk], lambda o: o.pk, ordered=False) f = F({'amount_saved': '10'}, queryset=qs) self.assertQuerysetEqual(f.qs, [b3.pk], lambda o: o.pk) f = F({'amount_saved': '0'}, queryset=qs) self.assertQuerysetEqual(f.qs, [b1.pk], lambda o: o.pk) class BooleanFilterTests(TestCase): def test_filtering(self): User.objects.create(username='alex', is_active=False) User.objects.create(username='jacob', is_active=True) User.objects.create(username='aaron', is_active=False) class F(FilterSet): class Meta: model = User fields = ['is_active'] qs = User.objects.all() # '2' and '3' are how the field expects the data from the browser f = F({'is_active': '2'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['jacob'], lambda o: o.username, False) f = F({'is_active': '3'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex', 'aaron'], lambda o: o.username, False) f = F({'is_active': '1'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex', 'aaron', 'jacob'], lambda o: o.username, False) class ChoiceFilterTests(TestCase): @classmethod def setUpTestData(cls): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) Article.objects.create(author_id=1, published=now()) Article.objects.create(author_id=2, published=now()) Article.objects.create(author_id=3, published=now()) Article.objects.create(author_id=4, published=now()) Article.objects.create(author_id=None, published=now()) def test_filtering(self): class F(FilterSet): class Meta: model = User fields = ['status'] f = F() self.assertQuerysetEqual(f.qs, ['aaron', 'alex', 'jacob', 'carl'], lambda o: o.username, False) f = F({'status': '1'}) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username, False) f = F({'status': '2'}) self.assertQuerysetEqual(f.qs, ['jacob', 'aaron'], lambda o: o.username, False) f = F({'status': '0'}) self.assertQuerysetEqual(f.qs, ['carl'], lambda o: o.username, False) def test_filtering_on_explicitly_defined_field(self): """ Test for #30. If you explicitly declare ChoiceFilter fields you **MUST** pass `choices`. """ class F(FilterSet): status = ChoiceFilter(choices=STATUS_CHOICES) class Meta: model = User fields = ['status'] f = F() self.assertQuerysetEqual(f.qs, ['aaron', 'alex', 'jacob', 'carl'], lambda o: o.username, False) f = F({'status': '1'}) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username, False) f = F({'status': '2'}) self.assertQuerysetEqual(f.qs, ['jacob', 'aaron'], lambda o: o.username, False) f = F({'status': '0'}) self.assertQuerysetEqual(f.qs, ['carl'], lambda o: o.username, False) def test_filtering_on_empty_choice(self): class F(FilterSet): class Meta: model = User fields = ['status'] f = F({'status': ''}) self.assertQuerysetEqual(f.qs, ['aaron', 'alex', 'jacob', 'carl'], lambda o: o.username, False) def test_filtering_on_null_choice(self): choices = [(u.pk, str(u)) for u in User.objects.order_by('id')] class F(FilterSet): author = ChoiceFilter( choices=choices, null_value='null', null_label='NULL', ) class Meta: model = Article fields = ['author'] # sanity check to make sure the filter is setup correctly f = F({'author': '1'}) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: str(o.author), False) f = F({'author': 'null'}) self.assertQuerysetEqual(f.qs, [None], lambda o: o.author, False) class MultipleChoiceFilterTests(TestCase): def test_filtering(self): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) class F(FilterSet): status = MultipleChoiceFilter(choices=STATUS_CHOICES) class Meta: model = User fields = ['status'] qs = User.objects.all().order_by('username') f = F(queryset=qs) self.assertQuerysetEqual( f.qs, ['aaron', 'jacob', 'alex', 'carl'], lambda o: o.username, False) f = F({'status': ['0']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['carl'], lambda o: o.username) f = F({'status': ['0', '1']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['alex', 'carl'], lambda o: o.username) f = F({'status': ['0', '1', '2']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['aaron', 'alex', 'carl', 'jacob'], lambda o: o.username) def test_filtering_on_null_choice(self): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) Article.objects.create(author_id=1, published=now()) Article.objects.create(author_id=2, published=now()) Article.objects.create(author_id=3, published=now()) Article.objects.create(author_id=4, published=now()) Article.objects.create(author_id=None, published=now()) choices = [(u.pk, str(u)) for u in User.objects.order_by('id')] class F(FilterSet): author = MultipleChoiceFilter( choices=choices, null_value='null', null_label='NULL', ) class Meta: model = Article fields = ['author'] # sanity check to make sure the filter is setup correctly f = F({'author': ['1']}) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: str(o.author), False) f = F({'author': ['null']}) self.assertQuerysetEqual(f.qs, [None], lambda o: o.author, False) f = F({'author': ['1', 'null']}) self.assertQuerysetEqual( f.qs, ['alex', None], lambda o: o.author and str(o.author), False) class TypedMultipleChoiceFilterTests(TestCase): def test_filtering(self): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) class F(FilterSet): status = TypedMultipleChoiceFilter(choices=STATUS_CHOICES, coerce=lambda x: x[0:2]) class Meta: model = User fields = ['status'] qs = User.objects.all().order_by('username') f = F(queryset=qs) self.assertQuerysetEqual( f.qs, ['aa', 'ja', 'al', 'ca'], lambda o: o.username[0:2], False) f = F({'status': ['0']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['ca'], lambda o: o.username[0:2]) f = F({'status': ['0', '1']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['al', 'ca'], lambda o: o.username[0:2]) f = F({'status': ['0', '1', '2']}, queryset=qs) self.assertQuerysetEqual( f.qs, ['aa', 'al', 'ca', 'ja'], lambda o: o.username[0:2]) class DateFilterTests(TestCase): def test_filtering(self): today = now().date() timestamp = now().time().replace(microsecond=0) last_week = today - datetime.timedelta(days=7) check_date = six.text_type(last_week) u = User.objects.create(username='alex') Comment.objects.create(author=u, time=timestamp, date=today) Comment.objects.create(author=u, time=timestamp, date=last_week) Comment.objects.create(author=u, time=timestamp, date=today) Comment.objects.create(author=u, time=timestamp, date=last_week) class F(FilterSet): class Meta: model = Comment fields = ['date'] f = F({'date': check_date}, queryset=Comment.objects.all()) self.assertEqual(len(f.qs), 2) self.assertQuerysetEqual(f.qs, [2, 4], lambda o: o.pk, False) class TimeFilterTests(TestCase): def test_filtering(self): today = now().date() now_time = now().time().replace(microsecond=0) ten_min_ago = (now() - datetime.timedelta(minutes=10)) fixed_time = ten_min_ago.time().replace(microsecond=0) check_time = six.text_type(fixed_time) u = User.objects.create(username='alex') Comment.objects.create(author=u, time=now_time, date=today) Comment.objects.create(author=u, time=fixed_time, date=today) Comment.objects.create(author=u, time=now_time, date=today) Comment.objects.create(author=u, time=fixed_time, date=today) class F(FilterSet): class Meta: model = Comment fields = ['time'] f = F({'time': check_time}, queryset=Comment.objects.all()) self.assertEqual(len(f.qs), 2) self.assertQuerysetEqual(f.qs, [2, 4], lambda o: o.pk, False) class DateTimeFilterTests(TestCase): def test_filtering(self): now_dt = now() ten_min_ago = now_dt - datetime.timedelta(minutes=10) one_day_ago = now_dt - datetime.timedelta(days=1) u = User.objects.create(username='alex') Article.objects.create(author=u, published=now_dt) Article.objects.create(author=u, published=ten_min_ago) Article.objects.create(author=u, published=one_day_ago) tz = timezone.get_current_timezone() # make naive, like a browser would send local_ten_min_ago = timezone.make_naive(ten_min_ago, tz) check_dt = six.text_type(local_ten_min_ago) class F(FilterSet): class Meta: model = Article fields = ['published'] qs = Article.objects.all() f = F({'published': ten_min_ago}, queryset=qs) self.assertEqual(len(f.qs), 1) self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk) # this is how it would come through a browser f = F({'published': check_dt}, queryset=qs) self.assertEqual( len(f.qs), 1, "%s isn't matching %s when cleaned" % (check_dt, ten_min_ago)) self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk) class DurationFilterTests(TestCase): """Duration filter tests. The preferred format for durations in Django is '%d %H:%M:%S.%f'. See django.utils.dateparse.parse_duration Django is not fully ISO 8601 compliant (yet): year, month, and week designators are not supported, so a duration string like "P3Y6M4DT12H30M5S" cannot be used. See https://en.wikipedia.org/wiki/ISO_8601#Durations """ def setUp(self): self.r1 = SpacewalkRecord.objects.create( astronaut="Anatoly Solovyev", duration=datetime.timedelta(hours=82, minutes=22)) self.r2 = SpacewalkRecord.objects.create( astronaut="Michael Lopez-Alegria", duration=datetime.timedelta(hours=67, minutes=40)) self.r3 = SpacewalkRecord.objects.create( astronaut="Jerry L. Ross", duration=datetime.timedelta(hours=58, minutes=32)) self.r4 = SpacewalkRecord.objects.create( astronaut="John M. Grunsfeld", duration=datetime.timedelta(hours=58, minutes=30)) self.r5 = SpacewalkRecord.objects.create( astronaut="Richard Mastracchio", duration=datetime.timedelta(hours=53, minutes=4)) def test_filtering(self): class F(FilterSet): class Meta: model = SpacewalkRecord fields = ['duration'] qs = SpacewalkRecord.objects.all() # Django style: 3 days, 10 hours, 22 minutes. f = F({'duration': '3 10:22:00'}, queryset=qs) self.assertQuerysetEqual(f.qs, [self.r1], lambda x: x) # ISO 8601: 3 days, 10 hours, 22 minutes. f = F({'duration': 'P3DT10H22M'}, queryset=qs) self.assertQuerysetEqual(f.qs, [self.r1], lambda x: x) # Django style: 82 hours, 22 minutes. f = F({'duration': '82:22:00'}, queryset=qs) self.assertQuerysetEqual(f.qs, [self.r1], lambda x: x) # ISO 8601: 82 hours, 22 minutes. f = F({'duration': 'PT82H22M'}, queryset=qs) self.assertQuerysetEqual(f.qs, [self.r1], lambda x: x) def test_filtering_with_single_lookup_expr_dictionary(self): class F(FilterSet): class Meta: model = SpacewalkRecord fields = {'duration': ['gt', 'gte', 'lt', 'lte']} qs = SpacewalkRecord.objects.order_by('-duration') f = F({'duration__gt': 'PT58H30M'}, queryset=qs) self.assertQuerysetEqual( f.qs, [self.r1, self.r2, self.r3], lambda x: x) f = F({'duration__gte': 'PT58H30M'}, queryset=qs) self.assertQuerysetEqual( f.qs, [self.r1, self.r2, self.r3, self.r4], lambda x: x) f = F({'duration__lt': 'PT58H30M'}, queryset=qs) self.assertQuerysetEqual( f.qs, [self.r5], lambda x: x) f = F({'duration__lte': 'PT58H30M'}, queryset=qs) self.assertQuerysetEqual( f.qs, [self.r4, self.r5], lambda x: x) def test_filtering_with_multiple_lookup_exprs(self): class F(FilterSet): min_duration = DurationFilter(name='duration', lookup_expr='gte') max_duration = DurationFilter(name='duration', lookup_expr='lte') class Meta: model = SpacewalkRecord fields = '__all__' qs = SpacewalkRecord.objects.order_by('duration') f = F({'min_duration': 'PT55H', 'max_duration': 'PT60H'}, queryset=qs) self.assertQuerysetEqual(f.qs, [self.r4, self.r3], lambda x: x) class ModelChoiceFilterTests(TestCase): def test_filtering(self): alex = User.objects.create(username='alex') jacob = User.objects.create(username='jacob') date = now().date() time = now().time() Comment.objects.create(author=jacob, time=time, date=date) Comment.objects.create(author=alex, time=time, date=date) Comment.objects.create(author=jacob, time=time, date=date) class F(FilterSet): class Meta: model = Comment fields = ['author'] qs = Comment.objects.all() f = F({'author': jacob.pk}, queryset=qs) self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False) @override_settings(FILTERS_NULL_CHOICE_LABEL='No Author') def test_filtering_null(self): Article.objects.create(published=now()) alex = User.objects.create(username='alex') Article.objects.create(author=alex, published=now()) class F(FilterSet): class Meta: model = Article fields = ['author', 'name'] qs = Article.objects.all() f = F({'author': 'null'}, queryset=qs) self.assertQuerysetEqual(f.qs, [None], lambda o: o.author, False) def test_callable_queryset(self): # Sanity check for callable queryset arguments. # Ensure that nothing is improperly cached User.objects.create(username='alex') jacob = User.objects.create(username='jacob') aaron = User.objects.create(username='aaron') def users(request): return User.objects.filter(pk__lt=request.user.pk) class F(FilterSet): author = ModelChoiceFilter(name='author', queryset=users) class Meta: model = Comment fields = ['author'] qs = Comment.objects.all() request = mock.Mock() request.user = jacob f = F(queryset=qs, request=request).filters['author'].field self.assertQuerysetEqual(f.queryset, [1], lambda o: o.pk, False) request.user = aaron f = F(queryset=qs, request=request).filters['author'].field self.assertQuerysetEqual(f.queryset, [1, 2], lambda o: o.pk, False) class ModelMultipleChoiceFilterTests(TestCase): def setUp(self): alex = User.objects.create(username='alex') User.objects.create(username='jacob') aaron = User.objects.create(username='aaron') b1 = Book.objects.create(title="Ender's Game", price='1.00', average_rating=3.0) b2 = Book.objects.create(title="Rainbow Six", price='1.00', average_rating=3.0) b3 = Book.objects.create(title="Snowcrash", price='1.00', average_rating=3.0) Book.objects.create(title="Stranger in a Strage Land", price='1.00', average_rating=3.0) alex.favorite_books.add(b1, b2) aaron.favorite_books.add(b1, b3) self.alex = alex def test_filtering(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books'] qs = User.objects.all().order_by('username') f = F({'favorite_books': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['1', '3']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['2']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) f = F({'favorite_books': ['4']}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.username) @override_settings(FILTERS_NULL_CHOICE_LABEL='No Favorites') def test_filtering_null(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books'] qs = User.objects.all() f = F({'favorite_books': ['null']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['jacob'], lambda o: o.username) def test_filtering_dictionary(self): class F(FilterSet): class Meta: model = User fields = {'favorite_books': ['exact']} qs = User.objects.all().order_by('username') f = F({'favorite_books': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['1', '3']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['2']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) f = F({'favorite_books': ['4']}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.username) def test_filtering_on_all_of_subset_of_choices(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books'] def __init__(self, *args, **kwargs): super(F, self).__init__(*args, **kwargs) # This filter has a limited number of choices. self.filters['favorite_books'].extra.update({ 'queryset': Book.objects.filter(id__in=[1, 2]) }) self.filters['favorite_books'].extra['required'] = True qs = User.objects.all().order_by('username') # Select all the given choices. f = F({'favorite_books': ['1', '2']}, queryset=qs) # The results should only include matching users - not Jacob. self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) def test_filtering_on_non_required_fields(self): # See issue #132 - filtering with all options on a non-required # field should exclude any results where the field is null. class F(FilterSet): author = ModelMultipleChoiceFilter(queryset=User.objects.all()) class Meta: model = Article fields = ['author'] published = now() Article.objects.create(published=published, author=self.alex) Article.objects.create(published=published, author=self.alex) Article.objects.create(published=published) qs = Article.objects.all() # Select all authors. authors = [ str(user.id) for user in User.objects.all() ] f = F({'author': authors}, queryset=qs) # The results should not include anonymous articles self.assertEqual( set(f.qs), set(Article.objects.exclude(author__isnull=True)), ) class NumberFilterTests(TestCase): def setUp(self): Book.objects.create(title="Ender's Game", price='10.0', average_rating=4.7999999999999998) Book.objects.create(title="Rainbow Six", price='15.0', average_rating=4.5999999999999996) Book.objects.create(title="Snowcrash", price='20.0', average_rating=4.2999999999999998) def test_filtering(self): class F(FilterSet): class Meta: model = Book fields = ['price'] f = F({'price': 10}, queryset=Book.objects.all()) self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title) def test_filtering_with_single_lookup_expr(self): class F(FilterSet): price = NumberFilter(lookup_expr='lt') class Meta: model = Book fields = ['price'] f = F({'price': 16}, queryset=Book.objects.all().order_by('title')) self.assertQuerysetEqual( f.qs, ['Ender\'s Game', 'Rainbow Six'], lambda o: o.title) def test_filtering_with_single_lookup_expr_dictionary(self): class F(FilterSet): class Meta: model = Book fields = {'price': ['lt']} f = F({'price__lt': 16}, queryset=Book.objects.all().order_by('title')) self.assertQuerysetEqual( f.qs, ['Ender\'s Game', 'Rainbow Six'], lambda o: o.title) def test_filtering_with_multiple_lookup_exprs(self): class F(FilterSet): price = NumberFilter(lookup_expr=['lt', 'gt']) class Meta: model = Book fields = ['price'] qs = Book.objects.all() f = F({'price_0': '15', 'price_1': 'lt'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title) f = F({'price_0': '15', 'price_1': 'lt'}) self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title) f = F({'price_0': '', 'price_1': 'lt'}) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Rainbow Six', 'Snowcrash'], lambda o: o.title, ordered=False) class F(FilterSet): price = NumberFilter(lookup_expr=['lt', 'gt', 'exact']) class Meta: model = Book fields = ['price'] f = F({'price_0': '15'}) self.assertQuerysetEqual(f.qs, ['Rainbow Six'], lambda o: o.title) class RangeFilterTests(TestCase): def setUp(self): Book.objects.create(title="Ender's Game", price='10.0', average_rating=4.7999999999999998) Book.objects.create(title="Rainbow Six", price='15.0', average_rating=4.5999999999999996) Book.objects.create(title="Snowcrash", price='20.0', average_rating=4.2999999999999998) Book.objects.create(title="Refund", price='-10.0', average_rating=5.0) Book.objects.create(title="Free Book", price='0.0', average_rating=0.0) def test_filtering(self): class F(FilterSet): price = RangeFilter() class Meta: model = Book fields = ['price'] qs = Book.objects.all().order_by('title') f = F(queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Free Book', 'Rainbow Six', 'Refund', 'Snowcrash'], lambda o: o.title) f = F({'price_0': '5', 'price_1': '15'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Rainbow Six'], lambda o: o.title) f = F({'price_0': '11'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Rainbow Six', 'Snowcrash'], lambda o: o.title) f = F({'price_1': '19'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Free Book', 'Rainbow Six', 'Refund'], lambda o: o.title) f = F({'price_0': '0', 'price_1': '12'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Free Book'], lambda o: o.title) f = F({'price_0': '-11', 'price_1': '0'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Free Book', 'Refund'], lambda o: o.title) f = F({'price_0': '0', 'price_1': '0'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Free Book'], lambda o: o.title) # TODO: # year & month filtering could be better. The problem is that the test dates # are relative to today, which is always changing. So, two_weeks_ago is not a # valid date for 'this month' during the first half of the month, but is during # the second half. Similary, five_days_ago is not during 'this year' when the # tests are ran on January 1. All we can test is what is absolutely never valid # eg, a date from two_years_ago is never a valid date for 'this year'. class DateRangeFilterTests(TestCase): def setUp(self): today = now().date() five_days_ago = today - datetime.timedelta(days=5) two_weeks_ago = today - datetime.timedelta(days=14) two_months_ago = today - datetime.timedelta(days=62) two_years_ago = today - datetime.timedelta(days=800) alex = User.objects.create(username='alex') time = now().time() Comment.objects.create(date=two_weeks_ago, author=alex, time=time) Comment.objects.create(date=two_years_ago, author=alex, time=time) Comment.objects.create(date=five_days_ago, author=alex, time=time) Comment.objects.create(date=today, author=alex, time=time) Comment.objects.create(date=two_months_ago, author=alex, time=time) def test_filtering_for_year(self): class F(FilterSet): date = DateRangeFilter() class Meta: model = Comment fields = ['date'] f = F({'date': '4'}) # this year # assert what is NOT valid for now. # self.assertQuerysetEqual(f.qs, [1, 3, 4, 5], lambda o: o.pk, False) self.assertNotIn(2, f.qs.values_list('pk', flat=True)) def test_filtering_for_month(self): class F(FilterSet): date = DateRangeFilter() class Meta: model = Comment fields = ['date'] f = F({'date': '3'}) # this month # assert what is NOT valid for now. # self.assertQuerysetEqual(f.qs, [1, 3, 4], lambda o: o.pk, False) self.assertNotIn(2, f.qs.values_list('pk', flat=True)) self.assertNotIn(5, f.qs.values_list('pk', flat=True)) def test_filtering_for_week(self): class F(FilterSet): date = DateRangeFilter() class Meta: model = Comment fields = ['date'] f = F({'date': '2'}) # this week self.assertQuerysetEqual(f.qs, [3, 4], lambda o: o.pk, False) def test_filtering_for_today(self): class F(FilterSet): date = DateRangeFilter() class Meta: model = Comment fields = ['date'] f = F({'date': '1'}) # today self.assertQuerysetEqual(f.qs, [4], lambda o: o.pk, False) # it will be difficult to test for TZ related issues, where "today" means # different things to both user and server. class DateFromToRangeFilterTests(TestCase): def test_filtering(self): adam = User.objects.create(username='adam') kwargs = {'text': 'test', 'author': adam, 'time': '10:00'} Comment.objects.create(date=datetime.date(2016, 1, 1), **kwargs) Comment.objects.create(date=datetime.date(2016, 1, 2), **kwargs) Comment.objects.create(date=datetime.date(2016, 1, 3), **kwargs) Comment.objects.create(date=datetime.date(2016, 1, 3), **kwargs) class F(FilterSet): published = DateFromToRangeFilter(name='date') class Meta: model = Comment fields = ['date'] results = F(data={ 'published_0': '2016-01-02', 'published_1': '2016-01-03'}) self.assertEqual(len(results.qs), 3) def test_filtering_ignores_time(self): tz = timezone.get_current_timezone() Article.objects.create( published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)) class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2016-01-02', 'published_1': '2016-01-03'}) self.assertEqual(len(results.qs), 3) @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='America/Sao_Paulo') def test_filtering_dst_start_midnight(self): tz = timezone.get_default_timezone() Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 14, 23, 59))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 15, 0, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 15, 1, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 16, 0, 0))) class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2017-10-15', 'published_1': '2017-10-15'}) self.assertEqual(len(results.qs), 2) @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='America/Sao_Paulo') def test_filtering_dst_ends_midnight(self): tz = timezone.get_default_timezone() Article.objects.create(published=tz.localize(datetime.datetime(2017, 2, 19, 0, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 2, 18, 23, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 2, 18, 0, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 2, 17, 15, 0))) class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2017-02-18', 'published_1': '2017-02-18'}) self.assertEqual(len(results.qs), 2) @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='Europe/Paris') def test_filtering_dst_start(self): tz = timezone.get_default_timezone() Article.objects.create(published=tz.localize(datetime.datetime(2017, 3, 25, 23, 59))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 3, 26, 0, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 3, 26, 2, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 3, 26, 3, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 3, 27, 0, 0))) class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2017-3-26', 'published_1': '2017-3-26'}) self.assertEqual(len(results.qs), 3) @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='Europe/Paris') def test_filtering_dst_end(self): tz = timezone.get_default_timezone() Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 28, 23, 59))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 29, 0, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 29, 2, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 29, 3, 0))) Article.objects.create(published=tz.localize(datetime.datetime(2017, 10, 30, 0, 0))) class F(FilterSet): published = DateFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2017-10-29', 'published_1': '2017-10-29'}) self.assertEqual(len(results.qs), 3) class DateTimeFromToRangeFilterTests(TestCase): def test_filtering(self): tz = timezone.get_current_timezone() Article.objects.create( published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)) Article.objects.create( published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)) class F(FilterSet): published = DateTimeFromToRangeFilter() class Meta: model = Article fields = ['published'] results = F(data={ 'published_0': '2016-01-02 10:00', 'published_1': '2016-01-03 19:00'}) self.assertEqual(len(results.qs), 2) class TimeRangeFilterTests(TestCase): def test_filtering(self): adam = User.objects.create(username='adam') kwargs = { 'text': 'test', 'author': adam, 'date': datetime.date.today()} Comment.objects.create(time='7:30', **kwargs) Comment.objects.create(time='8:00', **kwargs) Comment.objects.create(time='9:30', **kwargs) Comment.objects.create(time='11:00', **kwargs) class F(FilterSet): time = TimeRangeFilter() class Meta: model = Comment fields = ['time'] results = F(data={ 'time_0': '8:00', 'time_1': '10:00'}) self.assertEqual(len(results.qs), 2) class AllValuesFilterTests(TestCase): def test_filtering(self): User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') class F(FilterSet): username = AllValuesFilter() class Meta: model = User fields = ['username'] self.assertEqual(list(F().qs), list(User.objects.all())) self.assertEqual(list(F({'username': 'alex'}).qs), [User.objects.get(username='alex')]) self.assertEqual(list(F({'username': 'jose'}).qs), list()) def test_filtering_without_strict(self): User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') class F(FilterSet): username = AllValuesFilter() class Meta: model = User fields = ['username'] strict = False self.assertEqual(list(F().qs), list(User.objects.all())) self.assertEqual(list(F({'username': 'alex'}).qs), [User.objects.get(username='alex')]) self.assertEqual(list(F({'username': 'jose'}).qs), list(User.objects.all())) class AllValuesMultipleFilterTests(TestCase): def test_filtering(self): User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') class F(FilterSet): username = AllValuesMultipleFilter() class Meta: model = User fields = ['username'] self.assertEqual(list(F().qs), list(User.objects.all())) self.assertEqual(list(F({'username': ['alex']}).qs), [User.objects.get(username='alex')]) self.assertEqual(list(F({'username': ['alex', 'jacob']}).qs), list(User.objects.filter(username__in=['alex', 'jacob']))) self.assertEqual(list(F({'username': ['jose']}).qs), list()) class FilterMethodTests(TestCase): def setUp(self): User.objects.create(username='alex') User.objects.create(username='jacob') User.objects.create(username='aaron') def test_filtering(self): class F(FilterSet): username = CharFilter(method='filter_username') class Meta: model = User fields = ['username'] def filter_username(self, queryset, name, value): return queryset.filter(**{name: value}) self.assertEqual(list(F().qs), list(User.objects.all())) self.assertEqual(list(F({'username': 'alex'}).qs), [User.objects.get(username='alex')]) self.assertEqual(list(F({'username': 'jose'}).qs), list()) def test_filtering_callable(self): def filter_username(queryset, name, value): return queryset.filter(**{name: value}) class F(FilterSet): username = CharFilter(method=filter_username) class Meta: model = User fields = ['username'] self.assertEqual(list(F().qs), list(User.objects.all())) self.assertEqual(list(F({'username': 'alex'}).qs), [User.objects.get(username='alex')]) self.assertEqual(list(F({'username': 'jose'}).qs), list()) class O2ORelationshipTests(TestCase): def setUp(self): a1 = Account.objects.create( name='account1', in_good_standing=False, friendly=False) a2 = Account.objects.create( name='account2', in_good_standing=True, friendly=True) a3 = Account.objects.create( name='account3', in_good_standing=True, friendly=False) a4 = Account.objects.create( name='account4', in_good_standing=False, friendly=True) Profile.objects.create(account=a1, likes_coffee=True, likes_tea=False) Profile.objects.create(account=a2, likes_coffee=False, likes_tea=True) Profile.objects.create(account=a3, likes_coffee=True, likes_tea=True) Profile.objects.create(account=a4, likes_coffee=False, likes_tea=False) def test_o2o_relation(self): class F(FilterSet): class Meta: model = Profile fields = ('account',) f = F() self.assertEqual(f.qs.count(), 4) f = F({'account': 1}) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk) def test_o2o_relation_dictionary(self): class F(FilterSet): class Meta: model = Profile fields = {'account': ['exact'], } f = F() self.assertEqual(f.qs.count(), 4) f = F({'account': 1}) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk) def test_reverse_o2o_relation(self): class F(FilterSet): class Meta: model = Account fields = ('profile',) f = F() self.assertEqual(f.qs.count(), 4) f = F({'profile': 1}) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk) def test_o2o_relation_attribute(self): class F(FilterSet): class Meta: model = Profile fields = ('account__in_good_standing',) f = F() self.assertEqual(f.qs.count(), 4) f = F({'account__in_good_standing': '2'}) self.assertEqual(f.qs.count(), 2) self.assertQuerysetEqual(f.qs, [2, 3], lambda o: o.pk, False) def test_o2o_relation_attribute2(self): class F(FilterSet): class Meta: model = Profile fields = ('account__in_good_standing', 'account__friendly',) f = F() self.assertEqual(f.qs.count(), 4) f = F({'account__in_good_standing': '2', 'account__friendly': '2'}) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk) def test_reverse_o2o_relation_attribute(self): class F(FilterSet): class Meta: model = Account fields = ('profile__likes_coffee',) f = F() self.assertEqual(f.qs.count(), 4) f = F({'profile__likes_coffee': '2'}) self.assertEqual(f.qs.count(), 2) self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False) def test_reverse_o2o_relation_attribute2(self): class F(FilterSet): class Meta: model = Account fields = ('profile__likes_coffee', 'profile__likes_tea') f = F() self.assertEqual(f.qs.count(), 4) f = F({'profile__likes_coffee': '2', 'profile__likes_tea': '2'}) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [3], lambda o: o.pk) class FKRelationshipTests(TestCase): def test_fk_relation(self): company1 = Company.objects.create(name='company1') company2 = Company.objects.create(name='company2') Location.objects.create( company=company1, open_days="some", zip_code="90210") Location.objects.create( company=company2, open_days="WEEKEND", zip_code="11111") Location.objects.create( company=company1, open_days="monday", zip_code="12345") class F(FilterSet): class Meta: model = Location fields = ('company',) f = F() self.assertEqual(f.qs.count(), 3) f = F({'company': 1}) self.assertEqual(f.qs.count(), 2) self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False) def test_reverse_fk_relation(self): alex = User.objects.create(username='alex') jacob = User.objects.create(username='jacob') date = now().date() time = now().time() Comment.objects.create(text='comment 1', author=jacob, time=time, date=date) Comment.objects.create(text='comment 2', author=alex, time=time, date=date) Comment.objects.create(text='comment 3', author=jacob, time=time, date=date) class F(FilterSet): class Meta: model = User fields = ['comments'] qs = User.objects.all() f = F({'comments': [2]}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) class F(FilterSet): comments = AllValuesFilter() class Meta: model = User fields = ['comments'] f = F({'comments': 2}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) def test_fk_relation_attribute(self): now_dt = now() alex = User.objects.create(username='alex') jacob = User.objects.create(username='jacob') User.objects.create(username='aaron') Article.objects.create(author=alex, published=now_dt) Article.objects.create(author=jacob, published=now_dt) Article.objects.create(author=alex, published=now_dt) class F(FilterSet): class Meta: model = Article fields = ['author__username'] self.assertEqual(list(F.base_filters), ['author__username']) self.assertEqual(F({'author__username': 'alex'}).qs.count(), 2) self.assertEqual(F({'author__username': 'jacob'}).qs.count(), 1) class F(FilterSet): author__username = AllValuesFilter() class Meta: model = Article fields = ['author__username'] self.assertEqual(F({'author__username': 'alex'}).qs.count(), 2) def test_reverse_fk_relation_attribute(self): alex = User.objects.create(username='alex') jacob = User.objects.create(username='jacob') date = now().date() time = now().time() Comment.objects.create(text='comment 1', author=jacob, time=time, date=date) Comment.objects.create(text='comment 2', author=alex, time=time, date=date) Comment.objects.create(text='comment 3', author=jacob, time=time, date=date) class F(FilterSet): class Meta: model = User fields = ['comments__text'] qs = User.objects.all() f = F({'comments__text': 'comment 2'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) class F(FilterSet): comments__text = AllValuesFilter() class Meta: model = User fields = ['comments__text'] f = F({'comments__text': 'comment 2'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) @unittest.skip('todo - need correct models') def test_fk_relation_multiple_attributes(self): pass @unittest.expectedFailure def test_reverse_fk_relation_multiple_attributes(self): company = Company.objects.create(name='company') Location.objects.create( company=company, open_days="some", zip_code="90210") Location.objects.create( company=company, open_days="WEEKEND", zip_code="11111") class F(FilterSet): class Meta: model = Company fields = ('locations__zip_code', 'locations__open_days') f = F({'locations__zip_code': '90210', 'locations__open_days': 'WEEKEND'}) self.assertEqual(f.qs.count(), 0) class M2MRelationshipTests(TestCase): def setUp(self): alex = User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=1) aaron = User.objects.create(username='aaron', status=1) b1 = Book.objects.create(title="Ender's Game", price='1.00', average_rating=3.0) b2 = Book.objects.create(title="Rainbow Six", price='2.00', average_rating=4.0) b3 = Book.objects.create(title="Snowcrash", price='1.00', average_rating=4.0) Book.objects.create(title="Stranger in a Strage Land", price='2.00', average_rating=3.0) alex.favorite_books.add(b1, b2) aaron.favorite_books.add(b1, b3) def test_m2m_relation(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books'] qs = User.objects.all().order_by('username') f = F({'favorite_books': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['1', '3']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books': ['2']}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) f = F({'favorite_books': ['4']}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.username) def test_reverse_m2m_relation(self): class F(FilterSet): class Meta: model = Book fields = ['lovers'] qs = Book.objects.all().order_by('title') f = F({'lovers': [1]}, queryset=qs) self.assertQuerysetEqual( f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) class F(FilterSet): lovers = AllValuesFilter() class Meta: model = Book fields = ['lovers'] f = F({'lovers': 1}, queryset=qs) self.assertQuerysetEqual( f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) def test_m2m_relation_attribute(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books__title'] qs = User.objects.all().order_by('username') f = F({'favorite_books__title': "Ender's Game"}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron', 'alex'], lambda o: o.username) f = F({'favorite_books__title': 'Rainbow Six'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) class F(FilterSet): favorite_books__title = MultipleChoiceFilter() class Meta: model = User fields = ['favorite_books__title'] f = F() self.assertEqual( len(f.filters['favorite_books__title'].field.choices), 0) # f = F({'favorite_books__title': ['1', '3']}, # queryset=qs) # self.assertQuerysetEqual( # f.qs, ['aaron', 'alex'], lambda o: o.username) class F(FilterSet): favorite_books__title = AllValuesFilter() class Meta: model = User fields = ['favorite_books__title'] f = F({'favorite_books__title': "Snowcrash"}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron'], lambda o: o.username) def test_reverse_m2m_relation_attribute(self): class F(FilterSet): class Meta: model = Book fields = ['lovers__username'] qs = Book.objects.all().order_by('title') f = F({'lovers__username': "alex"}, queryset=qs) self.assertQuerysetEqual( f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) f = F({'lovers__username': 'jacob'}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.title) class F(FilterSet): lovers__username = MultipleChoiceFilter() class Meta: model = Book fields = ['lovers__username'] f = F() self.assertEqual( len(f.filters['lovers__username'].field.choices), 0) # f = F({'lovers__username': ['1', '3']}, # queryset=qs) # self.assertQuerysetEqual( # f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) class F(FilterSet): lovers__username = AllValuesFilter() class Meta: model = Book fields = ['lovers__username'] f = F({'lovers__username': "alex"}, queryset=qs) self.assertQuerysetEqual( f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) @unittest.expectedFailure def test_m2m_relation_multiple_attributes(self): class F(FilterSet): class Meta: model = User fields = ['favorite_books__price', 'favorite_books__average_rating'] qs = User.objects.all().order_by('username') f = F({'favorite_books__price': "1.00", 'favorite_books__average_rating': 4.0}, queryset=qs) self.assertQuerysetEqual(f.qs, ['aaron'], lambda o: o.username) f = F({'favorite_books__price': "3.00", 'favorite_books__average_rating': 4.0}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.username) @unittest.expectedFailure def test_reverse_m2m_relation_multiple_attributes(self): class F(FilterSet): class Meta: model = Book fields = ['lovers__status', 'lovers__username'] qs = Book.objects.all().order_by('title') f = F({'lovers__status': 1, 'lovers__username': "alex"}, queryset=qs) self.assertQuerysetEqual( f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title) f = F({'lovers__status': 1, 'lovers__username': 'jacob'}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.title) @unittest.skip('todo') def test_fk_relation_on_m2m_relation(self): pass @unittest.skip('todo') def test_fk_relation_attribute_on_m2m_relation(self): pass class SymmetricalSelfReferentialRelationshipTests(TestCase): def setUp(self): n1 = Node.objects.create(name='one') n2 = Node.objects.create(name='two') n3 = Node.objects.create(name='three') n4 = Node.objects.create(name='four') n1.adjacents.add(n2) n2.adjacents.add(n3) n2.adjacents.add(n4) n4.adjacents.add(n1) def test_relation(self): class F(FilterSet): class Meta: model = Node fields = ['adjacents'] qs = Node.objects.all().order_by('pk') f = F({'adjacents': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, [2, 4], lambda o: o.pk) class NonSymmetricalSelfReferentialRelationshipTests(TestCase): def setUp(self): n1 = DirectedNode.objects.create(name='one') n2 = DirectedNode.objects.create(name='two') n3 = DirectedNode.objects.create(name='three') n4 = DirectedNode.objects.create(name='four') n1.outbound_nodes.add(n2) n2.outbound_nodes.add(n3) n2.outbound_nodes.add(n4) n4.outbound_nodes.add(n1) def test_forward_relation(self): class F(FilterSet): class Meta: model = DirectedNode fields = ['outbound_nodes'] qs = DirectedNode.objects.all().order_by('pk') f = F({'outbound_nodes': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, [4], lambda o: o.pk) def test_reverse_relation(self): class F(FilterSet): class Meta: model = DirectedNode fields = ['inbound_nodes'] qs = DirectedNode.objects.all().order_by('pk') f = F({'inbound_nodes': ['1']}, queryset=qs) self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk) # use naive datetimes, as pytz is required to perform # date lookups when timezones are involved. @override_settings(USE_TZ=False) @unittest.skipIf(django.VERSION < (1, 9), "version does not support transformed lookup expressions") class TransformedQueryExpressionFilterTests(TestCase): def test_filtering(self): now_dt = datetime.datetime.now() after_5pm = now_dt.replace(hour=18) before_5pm = now_dt.replace(hour=16) u = User.objects.create(username='alex') a = Article.objects.create(author=u, published=after_5pm) Article.objects.create(author=u, published=before_5pm) class F(FilterSet): class Meta: model = Article fields = {'published': ['hour__gte']} qs = Article.objects.all() f = F({'published__hour__gte': 17}, queryset=qs) self.assertEqual(len(f.qs), 1) self.assertQuerysetEqual(f.qs, [a.pk], lambda o: o.pk) # use naive datetimes, as pytz is required to perform # date lookups when timezones are involved. @override_settings(USE_TZ=False) class CSVFilterTests(TestCase): def setUp(self): u1 = User.objects.create(username='alex', status=1) u2 = User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) now_dt = datetime.datetime.now() after_5pm = now_dt.replace(hour=18) before_5pm = now_dt.replace(hour=16) Article.objects.create(author=u1, published=after_5pm) Article.objects.create(author=u2, published=after_5pm) Article.objects.create(author=u1, published=before_5pm) Article.objects.create(author=u2, published=before_5pm) class UserFilter(FilterSet): class Meta: model = User fields = { 'username': ['in'], 'status': ['in'], } class ArticleFilter(FilterSet): class Meta: model = Article fields = { 'author': ['in'], 'published': ['in'], } self.user_filter = UserFilter self.article_filter = ArticleFilter self.after_5pm = after_5pm.strftime('%Y-%m-%d %H:%M:%S.%f') self.before_5pm = before_5pm.strftime('%Y-%m-%d %H:%M:%S.%f') def test_numeric_filtering(self): F = self.user_filter qs = User.objects.all() f = F(queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'status__in': ''}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'status__in': ','}, queryset=qs) self.assertEqual(f.qs.count(), 0) f = F({'status__in': '0'}, queryset=qs) self.assertEqual(f.qs.count(), 1) f = F({'status__in': '0,2'}, queryset=qs) self.assertEqual(f.qs.count(), 3) f = F({'status__in': '0,,1'}, queryset=qs) self.assertEqual(f.qs.count(), 2) f = F({'status__in': '2'}, queryset=qs) self.assertEqual(f.qs.count(), 2) def test_string_filtering(self): F = self.user_filter qs = User.objects.all() f = F(queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'username__in': ''}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'username__in': ','}, queryset=qs) self.assertEqual(f.qs.count(), 0) f = F({'username__in': 'alex'}, queryset=qs) self.assertEqual(f.qs.count(), 1) f = F({'username__in': 'alex,aaron'}, queryset=qs) self.assertEqual(f.qs.count(), 2) f = F({'username__in': 'alex,,aaron'}, queryset=qs) self.assertEqual(f.qs.count(), 2) f = F({'username__in': 'alex,'}, queryset=qs) self.assertEqual(f.qs.count(), 1) def test_datetime_filtering(self): F = self.article_filter after = self.after_5pm before = self.before_5pm qs = Article.objects.all() f = F(queryset=qs) self.assertEqual(len(f.qs), 4) self.assertEqual(f.qs.count(), 4) f = F({'published__in': ''}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'published__in': ','}, queryset=qs) self.assertEqual(f.qs.count(), 0) f = F({'published__in': '%s' % (after, )}, queryset=qs) self.assertEqual(f.qs.count(), 2) f = F({'published__in': '%s,%s' % (after, before, )}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'published__in': '%s,,%s' % (after, before, )}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'published__in': '%s,' % (after, )}, queryset=qs) self.assertEqual(f.qs.count(), 2) def test_related_filtering(self): F = self.article_filter qs = Article.objects.all() f = F(queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'author__in': ''}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'author__in': ','}, queryset=qs) self.assertEqual(f.qs.count(), 0) f = F({'author__in': '1'}, queryset=qs) self.assertEqual(f.qs.count(), 2) f = F({'author__in': '1,2'}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'author__in': '1,,2'}, queryset=qs) self.assertEqual(f.qs.count(), 4) f = F({'author__in': '1,'}, queryset=qs) self.assertEqual(f.qs.count(), 2) class OrderingFilterTests(TestCase): def setUp(self): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) def test_ordering(self): class F(FilterSet): o = OrderingFilter( fields=('username', ) ) class Meta: model = User fields = ['username'] qs = User.objects.all() f = F({'o': 'username'}, queryset=qs) names = f.qs.values_list('username', flat=True) self.assertEqual(list(names), ['aaron', 'alex', 'carl', 'jacob']) def test_ordering_with_select_widget(self): class F(FilterSet): o = OrderingFilter( widget=forms.Select, fields=('username', ) ) class Meta: model = User fields = ['username'] qs = User.objects.all() f = F({'o': 'username'}, queryset=qs) names = f.qs.values_list('username', flat=True) self.assertEqual(list(names), ['aaron', 'alex', 'carl', 'jacob']) class MiscFilterSetTests(TestCase): def setUp(self): User.objects.create(username='alex', status=1) User.objects.create(username='jacob', status=2) User.objects.create(username='aaron', status=2) User.objects.create(username='carl', status=0) def test_filtering_with_declared_filters(self): class F(FilterSet): account = CharFilter(name='username') class Meta: model = User fields = ['account'] qs = mock.NonCallableMagicMock() f = F({'account': 'jdoe'}, queryset=qs) result = f.qs self.assertNotEqual(qs, result) qs.all.return_value.filter.assert_called_with(username__exact='jdoe') def test_filtering_with_multiple_filters(self): class F(FilterSet): class Meta: model = User fields = ['status', 'username'] qs = User.objects.all() f = F({'username': 'alex', 'status': '1'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) f = F({'username': 'alex', 'status': '2'}, queryset=qs) self.assertQuerysetEqual(f.qs, [], lambda o: o.pk) def test_filter_with_initial(self): # Initial values are a form presentation option - the FilterSet should # not use an initial value as a default value to filter by. class F(FilterSet): status = ChoiceFilter(choices=STATUS_CHOICES, initial=1) class Meta: model = User fields = ['status'] qs = User.objects.all() users = ['alex', 'jacob', 'aaron', 'carl'] f = F(queryset=qs) self.assertQuerysetEqual(f.qs.order_by('pk'), users, lambda o: o.username) f = F({'status': 0}, queryset=qs) self.assertQuerysetEqual(f.qs, ['carl'], lambda o: o.username) def test_qs_count(self): class F(FilterSet): class Meta: model = User fields = ['status'] qs = User.objects.all() f = F(queryset=qs) self.assertEqual(len(f.qs), 4) self.assertEqual(f.qs.count(), 4) f = F({'status': '0'}, queryset=qs) self.assertEqual(len(f.qs), 1) self.assertEqual(f.qs.count(), 1) f = F({'status': '1'}, queryset=qs) self.assertEqual(len(f.qs), 1) self.assertEqual(f.qs.count(), 1) f = F({'status': '2'}, queryset=qs) self.assertEqual(len(f.qs), 2) self.assertEqual(f.qs.count(), 2) def test_invalid_field_lookup(self): # We want to ensure that non existent lookups (or just simple misspellings) # throw a useful exception containg the field and lookup expr. with self.assertRaises(FieldLookupError) as context: class F(FilterSet): class Meta: model = User fields = {'username': ['flub']} exc = str(context.exception) self.assertIn('tests.User.username', exc) self.assertIn('flub', exc) django-filter-1.1.0/tests/test_filters.py0000644000076500000240000014321613172067725021267 0ustar carltonstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals import inspect import mock from collections import OrderedDict from datetime import date, datetime, time, timedelta from django import forms from django.test import TestCase, override_settings from django.utils import translation from django.utils.translation import ugettext as _ from django_filters import filters, widgets from django_filters.fields import ( BaseCSVField, DateRangeField, DateTimeRangeField, Lookup, LookupTypeField, RangeField, TimeRangeField ) from django_filters.filters import ( LOOKUP_TYPES, AllValuesFilter, BaseCSVFilter, BaseInFilter, BaseRangeFilter, BooleanFilter, CharFilter, ChoiceFilter, DateFilter, DateFromToRangeFilter, DateRangeFilter, DateTimeFilter, DateTimeFromToRangeFilter, DurationFilter, Filter, ModelChoiceFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter, NumberFilter, NumericRangeFilter, OrderingFilter, RangeFilter, TimeFilter, TimeRangeFilter, TypedMultipleChoiceFilter, UUIDFilter ) from tests.models import Book, User class ModuleImportTests(TestCase): def is_filter(self, name, value): return ( isinstance(value, type) and issubclass(value, Filter) ) def test_imports(self): # msg = "Expected `filters.%s` to be imported in `filters.__all__`" filter_classes = [ key for key, value in inspect.getmembers(filters) if isinstance(value, type) and issubclass(value, Filter) ] # sanity check self.assertIn('Filter', filter_classes) self.assertIn('BooleanFilter', filter_classes) for f in filter_classes: self.assertIn(f, filters.__all__) class FilterTests(TestCase): def test_creation(self): f = Filter() self.assertEqual(f.lookup_expr, 'exact') self.assertEqual(f.exclude, False) def test_creation_order(self): f = Filter() f2 = Filter() self.assertTrue(f2.creation_counter > f.creation_counter) def test_default_field(self): f = Filter() field = f.field self.assertIsInstance(field, forms.Field) def test_field_with_single_lookup_expr(self): f = Filter(lookup_expr='iexact') field = f.field self.assertIsInstance(field, forms.Field) def test_field_with_none_lookup_expr(self): f = Filter(lookup_expr=None) field = f.field self.assertIsInstance(field, LookupTypeField) choice_field = field.fields[1] self.assertEqual(len(choice_field.choices), len(LOOKUP_TYPES)) def test_field_with_lookup_expr_and_exlusion(self): f = Filter(lookup_expr=None, exclude=True) field = f.field self.assertIsInstance(field, LookupTypeField) def test_field_with_list_lookup_expr(self): f = Filter(lookup_expr=('istartswith', 'iendswith')) field = f.field self.assertIsInstance(field, LookupTypeField) choice_field = field.fields[1] self.assertEqual(len(choice_field.choices), 2) def test_field_params(self): with mock.patch.object(Filter, 'field_class', spec=['__call__']) as mocked: f = Filter(name='somefield', label='somelabel', widget='somewidget') f.field mocked.assert_called_once_with(required=False, label='somelabel', widget='somewidget') def test_field_extra_params(self): with mock.patch.object(Filter, 'field_class', spec=['__call__']) as mocked: f = Filter(someattr='someattr') f.field mocked.assert_called_once_with(required=mock.ANY, label=mock.ANY, someattr='someattr') def test_field_required_default(self): # filter form fields should not be required by default with mock.patch.object(Filter, 'field_class', spec=['__call__']) as mocked: f = Filter() f.field mocked.assert_called_once_with(required=False, label=mock.ANY) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = Filter() result = f.filter(qs, 'value') qs.filter.assert_called_once_with(None__exact='value') self.assertNotEqual(qs, result) def test_filtering_exclude(self): qs = mock.Mock(spec=['filter', 'exclude']) f = Filter(exclude=True) result = f.filter(qs, 'value') qs.exclude.assert_called_once_with(None__exact='value') self.assertNotEqual(qs, result) def test_filtering_uses_name(self): qs = mock.Mock(spec=['filter']) f = Filter(name='somefield') f.filter(qs, 'value') result = qs.filter.assert_called_once_with(somefield__exact='value') self.assertNotEqual(qs, result) def test_filtering_skipped_with_blank_value(self): qs = mock.Mock() f = Filter() result = f.filter(qs, '') self.assertListEqual(qs.method_calls, []) self.assertEqual(qs, result) def test_filtering_skipped_with_none_value(self): qs = mock.Mock() f = Filter() result = f.filter(qs, None) self.assertListEqual(qs.method_calls, []) self.assertEqual(qs, result) def test_filtering_with_list_value(self): qs = mock.Mock(spec=['filter']) f = Filter(name='somefield', lookup_expr=['some_lookup_expr']) result = f.filter(qs, Lookup('value', 'some_lookup_expr')) qs.filter.assert_called_once_with(somefield__some_lookup_expr='value') self.assertNotEqual(qs, result) def test_filtering_skipped_with_list_value_with_blank(self): qs = mock.Mock() f = Filter(name='somefield', lookup_expr=['some_lookup_expr']) result = f.filter(qs, Lookup('', 'some_lookup_expr')) self.assertListEqual(qs.method_calls, []) self.assertEqual(qs, result) def test_filtering_skipped_with_list_value_with_blank_lookup(self): return # Now field is required to provide valid lookup_expr if it provides any qs = mock.Mock(spec=['filter']) f = Filter(name='somefield', lookup_expr=None) result = f.filter(qs, Lookup('value', '')) qs.filter.assert_called_once_with(somefield__exact='value') self.assertNotEqual(qs, result) def test_filter_using_method(self): qs = mock.NonCallableMock(spec=[]) method = mock.Mock() f = Filter(method=method) result = f.filter(qs, 'value') method.assert_called_once_with(qs, None, 'value') self.assertNotEqual(qs, result) def test_filtering_uses_distinct(self): qs = mock.Mock(spec=['filter', 'distinct']) f = Filter(name='somefield', distinct=True) f.filter(qs, 'value') result = qs.distinct.assert_called_once_with() self.assertNotEqual(qs, result) class CustomFilterWithBooleanCheckTests(TestCase): def setUp(self): super(CustomFilterWithBooleanCheckTests, self).setUp() class CustomTestFilter(Filter): def filter(self_, qs, value): if not value: return qs return super(CustomTestFilter, self_).filter(qs, value) self.test_filter_class = CustomTestFilter def test_lookup_false(self): qs = mock.Mock(spec=['filter']) f = self.test_filter_class(name='somefield') result = f.filter(qs, Lookup('', 'exact')) self.assertEqual(qs, result) def test_lookup_true(self): qs = mock.Mock(spec=['filter']) f = self.test_filter_class(name='somefield') result = f.filter(qs, Lookup('somesearch', 'exact')) qs.filter.assert_called_once_with(somefield__exact='somesearch') self.assertNotEqual(qs, result) class CharFilterTests(TestCase): def test_default_field(self): f = CharFilter() field = f.field self.assertIsInstance(field, forms.CharField) class UUIDFilterTests(TestCase): def test_default_field(self): f = UUIDFilter() field = f.field self.assertIsInstance(field, forms.UUIDField) class BooleanFilterTests(TestCase): def test_default_field(self): f = BooleanFilter() field = f.field self.assertIsInstance(field, forms.NullBooleanField) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = BooleanFilter(name='somefield') result = f.filter(qs, True) qs.filter.assert_called_once_with(somefield__exact=True) self.assertNotEqual(qs, result) def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) f = BooleanFilter(name='somefield', exclude=True) result = f.filter(qs, True) qs.exclude.assert_called_once_with(somefield__exact=True) self.assertNotEqual(qs, result) def test_filtering_skipped_with_blank_value(self): qs = mock.Mock() f = BooleanFilter(name='somefield') result = f.filter(qs, '') self.assertListEqual(qs.method_calls, []) self.assertEqual(qs, result) def test_filtering_skipped_with_none_value(self): qs = mock.Mock() f = BooleanFilter(name='somefield') result = f.filter(qs, None) self.assertListEqual(qs.method_calls, []) self.assertEqual(qs, result) def test_filtering_lookup_expr(self): qs = mock.Mock(spec=['filter']) f = BooleanFilter(name='somefield', lookup_expr='isnull') result = f.filter(qs, True) qs.filter.assert_called_once_with(somefield__isnull=True) self.assertNotEqual(qs, result) class ChoiceFilterTests(TestCase): def test_default_field(self): f = ChoiceFilter() field = f.field self.assertIsInstance(field, forms.ChoiceField) def test_empty_choice(self): # default value f = ChoiceFilter(choices=[('a', 'a')]) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('a', 'a'), ]) # set value, allow blank label f = ChoiceFilter(choices=[('a', 'a')], empty_label='') self.assertEqual(list(f.field.choices), [ ('', ''), ('a', 'a'), ]) # disable empty choice w/ None f = ChoiceFilter(choices=[('a', 'a')], empty_label=None) self.assertEqual(list(f.field.choices), [ ('a', 'a'), ]) def test_null_choice(self): # default is to be disabled f = ChoiceFilter(choices=[('a', 'a')], ) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('a', 'a'), ]) # set label, allow blank label f = ChoiceFilter(choices=[('a', 'a')], null_label='') self.assertEqual(list(f.field.choices), [ ('', '---------'), ('null', ''), ('a', 'a'), ]) # set null value f = ChoiceFilter(choices=[('a', 'a')], null_value='NULL', null_label='') self.assertEqual(list(f.field.choices), [ ('', '---------'), ('NULL', ''), ('a', 'a'), ]) # explicitly disable f = ChoiceFilter(choices=[('a', 'a')], null_label=None) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('a', 'a'), ]) def test_null_multiplechoice(self): # default is to be disabled f = MultipleChoiceFilter(choices=[('a', 'a')], ) self.assertEqual(list(f.field.choices), [ ('a', 'a'), ]) # set label, allow blank label f = MultipleChoiceFilter(choices=[('a', 'a')], null_label='') self.assertEqual(list(f.field.choices), [ ('null', ''), ('a', 'a'), ]) # set null value f = MultipleChoiceFilter(choices=[('a', 'a')], null_value='NULL', null_label='') self.assertEqual(list(f.field.choices), [ ('NULL', ''), ('a', 'a'), ]) # explicitly disable f = MultipleChoiceFilter(choices=[('a', 'a')], null_label=None) self.assertEqual(list(f.field.choices), [ ('a', 'a'), ]) @override_settings( FILTERS_EMPTY_CHOICE_LABEL='EMPTY LABEL', FILTERS_NULL_CHOICE_LABEL='NULL LABEL', FILTERS_NULL_CHOICE_VALUE='NULL VALUE', ) def test_settings_overrides(self): f = ChoiceFilter(choices=[('a', 'a')], ) self.assertEqual(list(f.field.choices), [ ('', 'EMPTY LABEL'), ('NULL VALUE', 'NULL LABEL'), ('a', 'a'), ]) f = MultipleChoiceFilter(choices=[('a', 'a')], ) self.assertEqual(list(f.field.choices), [ ('NULL VALUE', 'NULL LABEL'), ('a', 'a'), ]) def test_callable_choices(self): def choices(): yield ('a', 'a') yield ('b', 'b') f = ChoiceFilter(choices=choices) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('a', 'a'), ('b', 'b'), ]) def test_callable_choices_is_lazy(self): def choices(): self.fail('choices should not be called during initialization') ChoiceFilter(choices=choices) class MultipleChoiceFilterTests(TestCase): def test_default_field(self): f = MultipleChoiceFilter() field = f.field self.assertIsInstance(field, forms.MultipleChoiceField) def test_filtering_requires_name(self): qs = mock.Mock(spec=['filter']) f = MultipleChoiceFilter() with self.assertRaises(TypeError): f.filter(qs, ['value']) def test_conjoined_default_value(self): f = MultipleChoiceFilter() self.assertFalse(f.conjoined) def test_conjoined_true(self): f = MultipleChoiceFilter(conjoined=True) self.assertTrue(f.conjoined) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = MultipleChoiceFilter(name='somefield') with mock.patch('django_filters.filters.Q') as mockQclass: mockQ1, mockQ2 = mock.MagicMock(), mock.MagicMock() mockQclass.side_effect = [mockQ1, mockQ2] f.filter(qs, ['value']) self.assertEqual(mockQclass.call_args_list, [mock.call(), mock.call(somefield='value')]) mockQ1.__ior__.assert_called_once_with(mockQ2) qs.filter.assert_called_once_with(mockQ1.__ior__.return_value) qs.filter.return_value.distinct.assert_called_once_with() def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) f = MultipleChoiceFilter(name='somefield', exclude=True) with mock.patch('django_filters.filters.Q') as mockQclass: mockQ1, mockQ2 = mock.MagicMock(), mock.MagicMock() mockQclass.side_effect = [mockQ1, mockQ2] f.filter(qs, ['value']) self.assertEqual(mockQclass.call_args_list, [mock.call(), mock.call(somefield='value')]) mockQ1.__ior__.assert_called_once_with(mockQ2) qs.exclude.assert_called_once_with(mockQ1.__ior__.return_value) qs.exclude.return_value.distinct.assert_called_once_with() def test_filtering_on_required_skipped_when_len_of_value_is_len_of_field_choices(self): qs = mock.Mock(spec=[]) f = MultipleChoiceFilter(name='somefield', required=True) f.always_filter = False result = f.filter(qs, []) self.assertEqual(len(f.field.choices), 0) self.assertEqual(qs, result) f.field.choices = ['some', 'values', 'here'] result = f.filter(qs, ['some', 'values', 'here']) self.assertEqual(qs, result) result = f.filter(qs, ['other', 'values', 'there']) self.assertEqual(qs, result) def test_filtering_skipped_with_empty_list_value_and_some_choices(self): qs = mock.Mock(spec=[]) f = MultipleChoiceFilter(name='somefield') f.field.choices = ['some', 'values', 'here'] result = f.filter(qs, []) self.assertEqual(qs, result) def test_filter_conjoined_true(self): """Tests that a filter with `conjoined=True` returns objects that have all the values included in `value`. For example filter users that have all of this books. """ book_kwargs = {'price': 1, 'average_rating': 1} books = [] books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) user1 = User.objects.create() user2 = User.objects.create() user3 = User.objects.create() user4 = User.objects.create() user5 = User.objects.create() user1.favorite_books.add(books[0], books[1]) user2.favorite_books.add(books[0], books[1], books[2]) user3.favorite_books.add(books[1], books[2]) user4.favorite_books.add(books[2], books[3]) user5.favorite_books.add(books[4], books[5]) filter_list = ( ((books[0].pk, books[0].pk), # values [1, 2]), # list of user.pk that have `value` books ((books[1].pk, books[1].pk), [1, 2, 3]), ((books[2].pk, books[2].pk), [2, 3, 4]), ((books[3].pk, books[3].pk), [4, ]), ((books[4].pk, books[4].pk), [5, ]), ((books[0].pk, books[1].pk), [1, 2]), ((books[0].pk, books[2].pk), [2, ]), ((books[1].pk, books[2].pk), [2, 3]), ((books[2].pk, books[3].pk), [4, ]), ((books[4].pk, books[5].pk), [5, ]), ((books[3].pk, books[4].pk), []), ) users = User.objects.all() for item in filter_list: f = MultipleChoiceFilter(name='favorite_books__pk', conjoined=True) queryset = f.filter(users, item[0]) expected_pks = [c[0] for c in queryset.values_list('pk')] self.assertListEqual( expected_pks, item[1], 'Lists Differ: {0} != {1} for case {2}'.format( expected_pks, item[1], item[0])) class TypedMultipleChoiceFilterTests(TestCase): def test_default_field(self): f = TypedMultipleChoiceFilter() field = f.field self.assertIsInstance(field, forms.TypedMultipleChoiceField) def test_filtering_requires_name(self): qs = mock.Mock(spec=['filter']) f = TypedMultipleChoiceFilter() with self.assertRaises(TypeError): f.filter(qs, ['value']) def test_conjoined_default_value(self): f = TypedMultipleChoiceFilter() self.assertFalse(f.conjoined) def test_conjoined_true(self): f = TypedMultipleChoiceFilter(conjoined=True) self.assertTrue(f.conjoined) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = TypedMultipleChoiceFilter(name='somefield') with mock.patch('django_filters.filters.Q') as mockQclass: mockQ1, mockQ2 = mock.MagicMock(), mock.MagicMock() mockQclass.side_effect = [mockQ1, mockQ2] f.filter(qs, ['value']) self.assertEqual(mockQclass.call_args_list, [mock.call(), mock.call(somefield='value')]) mockQ1.__ior__.assert_called_once_with(mockQ2) qs.filter.assert_called_once_with(mockQ1.__ior__.return_value) qs.filter.return_value.distinct.assert_called_once_with() def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) f = TypedMultipleChoiceFilter(name='somefield', exclude=True) with mock.patch('django_filters.filters.Q') as mockQclass: mockQ1, mockQ2 = mock.MagicMock(), mock.MagicMock() mockQclass.side_effect = [mockQ1, mockQ2] f.filter(qs, ['value']) self.assertEqual(mockQclass.call_args_list, [mock.call(), mock.call(somefield='value')]) mockQ1.__ior__.assert_called_once_with(mockQ2) qs.exclude.assert_called_once_with(mockQ1.__ior__.return_value) qs.exclude.return_value.distinct.assert_called_once_with() def test_filtering_on_required_skipped_when_len_of_value_is_len_of_field_choices(self): qs = mock.Mock(spec=[]) f = TypedMultipleChoiceFilter(name='somefield', required=True) f.always_filter = False result = f.filter(qs, []) self.assertEqual(len(f.field.choices), 0) self.assertEqual(qs, result) f.field.choices = ['some', 'values', 'here'] result = f.filter(qs, ['some', 'values', 'here']) self.assertEqual(qs, result) result = f.filter(qs, ['other', 'values', 'there']) self.assertEqual(qs, result) def test_filtering_skipped_with_empty_list_value_and_some_choices(self): qs = mock.Mock(spec=[]) f = TypedMultipleChoiceFilter(name='somefield') f.field.choices = ['some', 'values', 'here'] result = f.filter(qs, []) self.assertEqual(qs, result) def test_filter_conjoined_true(self): """Tests that a filter with `conjoined=True` returns objects that have all the values included in `value`. For example filter users that have all of this books. """ book_kwargs = {'price': 1, 'average_rating': 1} books = [] books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) books.append(Book.objects.create(**book_kwargs)) user1 = User.objects.create() user2 = User.objects.create() user3 = User.objects.create() user4 = User.objects.create() user5 = User.objects.create() user1.favorite_books.add(books[0], books[1]) user2.favorite_books.add(books[0], books[1], books[2]) user3.favorite_books.add(books[1], books[2]) user4.favorite_books.add(books[2], books[3]) user5.favorite_books.add(books[4], books[5]) filter_list = ( ((books[0].pk, books[0].pk), # values [1, 2]), # list of user.pk that have `value` books ((books[1].pk, books[1].pk), [1, 2, 3]), ((books[2].pk, books[2].pk), [2, 3, 4]), ((books[3].pk, books[3].pk), [4, ]), ((books[4].pk, books[4].pk), [5, ]), ((books[0].pk, books[1].pk), [1, 2]), ((books[0].pk, books[2].pk), [2, ]), ((books[1].pk, books[2].pk), [2, 3]), ((books[2].pk, books[3].pk), [4, ]), ((books[4].pk, books[5].pk), [5, ]), ((books[3].pk, books[4].pk), []), ) users = User.objects.all() for item in filter_list: f = TypedMultipleChoiceFilter(name='favorite_books__pk', conjoined=True) queryset = f.filter(users, item[0]) expected_pks = [c[0] for c in queryset.values_list('pk')] self.assertListEqual( expected_pks, item[1], 'Lists Differ: {0} != {1} for case {2}'.format( expected_pks, item[1], item[0])) class DateFilterTests(TestCase): def test_default_field(self): f = DateFilter() field = f.field self.assertIsInstance(field, forms.DateField) class DateTimeFilterTests(TestCase): def test_default_field(self): f = DateTimeFilter() field = f.field self.assertIsInstance(field, forms.DateTimeField) class TimeFilterTests(TestCase): def test_default_field(self): f = TimeFilter() field = f.field self.assertIsInstance(field, forms.TimeField) class DurationFilterTests(TestCase): def test_default_field(self): f = DurationFilter() field = f.field self.assertIsInstance(field, forms.DurationField) class ModelChoiceFilterTests(TestCase): def test_default_field_without_queryset(self): f = ModelChoiceFilter() with self.assertRaises(TypeError): f.field @override_settings( FILTERS_EMPTY_CHOICE_LABEL='EMPTY', FILTERS_NULL_CHOICE_VALUE='NULL', ) def test_empty_choices(self): f = ModelChoiceFilter(queryset=User.objects.all(), null_value='null', null_label='NULL') self.assertEqual(list(f.field.choices), [ ('', 'EMPTY'), ('null', 'NULL'), ]) def test_default_field_with_queryset(self): qs = mock.NonCallableMock(spec=[]) f = ModelChoiceFilter(queryset=qs) field = f.field self.assertIsInstance(field, forms.ModelChoiceField) self.assertEqual(field.queryset, qs) def test_callable_queryset(self): request = mock.NonCallableMock(spec=[]) qs = mock.NonCallableMock(spec=[]) qs_callable = mock.Mock(return_value=qs) f = ModelChoiceFilter(queryset=qs_callable) f.parent = mock.Mock(request=request) field = f.field qs_callable.assert_called_with(request) self.assertEqual(field.queryset, qs) def test_get_queryset_override(self): request = mock.NonCallableMock(spec=[]) qs = mock.NonCallableMock(spec=[]) class F(ModelChoiceFilter): get_queryset = mock.create_autospec(ModelChoiceFilter.get_queryset, return_value=qs) f = F() f.parent = mock.Mock(request=request) field = f.field f.get_queryset.assert_called_with(f, request) self.assertEqual(field.queryset, qs) class ModelMultipleChoiceFilterTests(TestCase): def test_default_field_without_queryset(self): f = ModelMultipleChoiceFilter() with self.assertRaises(TypeError): f.field @override_settings( FILTERS_EMPTY_CHOICE_LABEL='EMPTY', FILTERS_NULL_CHOICE_VALUE='NULL', ) def test_empty_choices(self): f = ModelMultipleChoiceFilter(queryset=User.objects.all(), null_value='null', null_label='NULL') self.assertEqual(list(f.field.choices), [ ('null', 'NULL'), ]) def test_default_field_with_queryset(self): qs = mock.NonCallableMock(spec=[]) f = ModelMultipleChoiceFilter(queryset=qs) field = f.field self.assertIsInstance(field, forms.ModelMultipleChoiceField) self.assertEqual(field.queryset, qs) def test_filtering_to_field_name(self): qs = User.objects.all() f = ModelMultipleChoiceFilter(name='first_name', to_field_name='first_name', queryset=qs) user = User.objects.create(first_name='Firstname') self.assertEqual(f.get_filter_predicate(user), {'first_name': 'Firstname'}) self.assertEqual(f.get_filter_predicate('FilterValue'), {'first_name': 'FilterValue'}) self.assertEqual(list(f.filter(qs, ['Firstname'])), [user]) self.assertEqual(list(f.filter(qs, [user])), [user]) def test_callable_queryset(self): request = mock.NonCallableMock(spec=[]) qs = mock.NonCallableMock(spec=[]) qs_callable = mock.Mock(return_value=qs) f = ModelMultipleChoiceFilter(queryset=qs_callable) f.parent = mock.Mock(request=request) field = f.field qs_callable.assert_called_with(request) self.assertEqual(field.queryset, qs) class NumberFilterTests(TestCase): def test_default_field(self): f = NumberFilter() field = f.field self.assertIsInstance(field, forms.DecimalField) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = NumberFilter() f.filter(qs, 1) qs.filter.assert_called_once_with(None__exact=1) # Also test 0 as it once had a bug qs.reset_mock() f.filter(qs, 0) qs.filter.assert_called_once_with(None__exact=0) def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) f = NumberFilter(exclude=True) f.filter(qs, 1) qs.exclude.assert_called_once_with(None__exact=1) # Also test 0 as it once had a bug qs.reset_mock() f.filter(qs, 0) qs.exclude.assert_called_once_with(None__exact=0) class NumericRangeFilterTests(TestCase): def test_default_field(self): f = NumericRangeFilter() field = f.field self.assertIsInstance(field, RangeField) def test_filtering(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=20, stop=30) f = NumericRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__exact=(20, 30)) def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) value = mock.Mock(start=20, stop=30) f = NumericRangeFilter(exclude=True) f.filter(qs, value) qs.exclude.assert_called_once_with(None__exact=(20, 30)) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = NumericRangeFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_field_with_lookup_expr(self): qs = mock.Mock() value = mock.Mock(start=20, stop=30) f = NumericRangeFilter(lookup_expr=('overlap')) f.filter(qs, value) qs.filter.assert_called_once_with(None__overlap=(20, 30)) def test_zero_to_zero(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=0, stop=0) f = NumericRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__exact=(0, 0)) def test_filtering_startswith(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=20, stop=None) f = NumericRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__startswith=20) def test_filtering_endswith(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=None, stop=30) f = NumericRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__endswith=30) class RangeFilterTests(TestCase): def test_default_field(self): f = RangeFilter() field = f.field self.assertIsInstance(field, RangeField) def test_filtering_range(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=20, stop=30) f = RangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__range=(20, 30)) def test_filtering_exclude(self): qs = mock.Mock(spec=['exclude']) value = mock.Mock(start=20, stop=30) f = RangeFilter(exclude=True) f.filter(qs, value) qs.exclude.assert_called_once_with(None__range=(20, 30)) def test_filtering_start(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=20, stop=None) f = RangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__gte=20) def test_filtering_stop(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=None, stop=30) f = RangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__lte=30) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = RangeFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_filtering_ignores_lookup_expr(self): qs = mock.Mock() value = mock.Mock(start=20, stop=30) f = RangeFilter(lookup_expr='gte') f.filter(qs, value) qs.filter.assert_called_once_with(None__range=(20, 30)) class DateRangeFilterTests(TestCase): def test_creating(self): f = DateRangeFilter() self.assertIn('choices', f.extra) self.assertEqual(len(DateRangeFilter.options), len(f.extra['choices'])) def test_default_field(self): f = DateRangeFilter() field = f.field self.assertIsInstance(field, forms.ChoiceField) def test_filtering(self): # skip filtering, as it's an empty value qs = mock.Mock(spec=[]) f = DateRangeFilter() result = f.filter(qs, '') self.assertEqual(qs, result) def test_filtering_skipped_with_out_of_range_value(self): # Field validation should prevent this from occuring qs = mock.Mock(spec=[]) f = DateRangeFilter() with self.assertRaises(AssertionError): f.filter(qs, 999) def test_filtering_for_this_year(self): qs = mock.Mock(spec=['filter']) with mock.patch('django_filters.filters.now') as mock_now: now_dt = mock_now.return_value f = DateRangeFilter() f.filter(qs, '4') qs.filter.assert_called_once_with( None__year=now_dt.year) def test_filtering_for_this_month(self): qs = mock.Mock(spec=['filter']) with mock.patch('django_filters.filters.now') as mock_now: now_dt = mock_now.return_value f = DateRangeFilter() f.filter(qs, '3') qs.filter.assert_called_once_with( None__year=now_dt.year, None__month=now_dt.month) def test_filtering_for_7_days(self): qs = mock.Mock(spec=['filter']) with mock.patch('django_filters.filters.now'), \ mock.patch('django_filters.filters.timedelta') as mock_td, \ mock.patch('django_filters.filters._truncate') as mock_truncate: mock_d1, mock_d2 = mock.MagicMock(), mock.MagicMock() mock_truncate.side_effect = [mock_d1, mock_d2] f = DateRangeFilter() f.filter(qs, '2') self.assertEqual( mock_td.call_args_list, [mock.call(days=7), mock.call(days=1)] ) qs.filter.assert_called_once_with(None__lt=mock_d2, None__gte=mock_d1) def test_filtering_for_today(self): qs = mock.Mock(spec=['filter']) with mock.patch('django_filters.filters.now') as mock_now: now_dt = mock_now.return_value f = DateRangeFilter() f.filter(qs, '1') qs.filter.assert_called_once_with( None__year=now_dt.year, None__month=now_dt.month, None__day=now_dt.day) def test_filtering_for_yesterday(self): qs = mock.Mock(spec=['filter']) with mock.patch('django_filters.filters.now') as mock_now: now_dt = mock_now.return_value f = DateRangeFilter() f.filter(qs, '5') qs.filter.assert_called_once_with( None__year=now_dt.year, None__month=now_dt.month, None__day=(now_dt - timedelta(days=1)).day, ) class DateFromToRangeFilterTests(TestCase): def test_default_field(self): f = DateFromToRangeFilter() field = f.field self.assertIsInstance(field, DateRangeField) def test_filtering_range(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=date(2015, 4, 7), stop=date(2015, 9, 6)) f = DateFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(date(2015, 4, 7), date(2015, 9, 6))) def test_filtering_start(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=date(2015, 4, 7), stop=None) f = DateFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__gte=date(2015, 4, 7)) def test_filtering_stop(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=None, stop=date(2015, 9, 6)) f = DateFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__lte=date(2015, 9, 6)) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = DateFromToRangeFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_filtering_ignores_lookup_expr(self): qs = mock.Mock() value = mock.Mock(start=date(2015, 4, 7), stop=date(2015, 9, 6)) f = DateFromToRangeFilter(lookup_expr='gte') f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(date(2015, 4, 7), date(2015, 9, 6))) class DateTimeFromToRangeFilterTests(TestCase): def test_default_field(self): f = DateTimeFromToRangeFilter() field = f.field self.assertIsInstance(field, DateTimeRangeField) def test_filtering_range(self): qs = mock.Mock(spec=['filter']) value = mock.Mock( start=datetime(2015, 4, 7, 8, 30), stop=datetime(2015, 9, 6, 11, 45)) f = DateTimeFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(datetime(2015, 4, 7, 8, 30), datetime(2015, 9, 6, 11, 45))) def test_filtering_start(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=datetime(2015, 4, 7, 8, 30), stop=None) f = DateTimeFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__gte=datetime(2015, 4, 7, 8, 30)) def test_filtering_stop(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=None, stop=datetime(2015, 9, 6, 11, 45)) f = DateTimeFromToRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__lte=datetime(2015, 9, 6, 11, 45)) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = DateTimeFromToRangeFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_filtering_ignores_lookup_expr(self): qs = mock.Mock() value = mock.Mock( start=datetime(2015, 4, 7, 8, 30), stop=datetime(2015, 9, 6, 11, 45)) f = DateTimeFromToRangeFilter(lookup_expr='gte') f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(datetime(2015, 4, 7, 8, 30), datetime(2015, 9, 6, 11, 45))) class TimeRangeFilterTests(TestCase): def test_default_field(self): f = TimeRangeFilter() field = f.field self.assertIsInstance(field, TimeRangeField) def test_filtering_range(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=time(10, 15), stop=time(12, 30)) f = TimeRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(time(10, 15), time(12, 30))) def test_filtering_start(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=time(10, 15), stop=None) f = TimeRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__gte=time(10, 15)) def test_filtering_stop(self): qs = mock.Mock(spec=['filter']) value = mock.Mock(start=None, stop=time(12, 30)) f = TimeRangeFilter() f.filter(qs, value) qs.filter.assert_called_once_with(None__lte=time(12, 30)) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = TimeRangeFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_filtering_ignores_lookup_expr(self): qs = mock.Mock() value = mock.Mock(start=time(10, 15), stop=time(12, 30)) f = TimeRangeFilter(lookup_expr='gte') f.filter(qs, value) qs.filter.assert_called_once_with( None__range=(time(10, 15), time(12, 30))) class AllValuesFilterTests(TestCase): def test_default_field_without_assigning_model(self): f = AllValuesFilter() with self.assertRaises(AttributeError): f.field def test_default_field_with_assigning_model(self): mocked = mock.Mock() chained_call = '.'.join(['_default_manager', 'distinct.return_value', 'order_by.return_value', 'values_list.return_value']) mocked.configure_mock(**{chained_call: iter([])}) f = AllValuesFilter() f.model = mocked field = f.field self.assertIsInstance(field, forms.ChoiceField) def test_empty_value_in_choices(self): f = AllValuesFilter(name='username') f.model = User self.assertEqual(list(f.field.choices), [ ('', '---------'), ]) class LookupTypesTests(TestCase): def test_custom_lookup_exprs(self): filters.LOOKUP_TYPES = [ ('', '---------'), ('exact', 'Is equal to'), ('not_exact', 'Is not equal to'), ('lt', 'Lesser than'), ('gt', 'Greater than'), ('gte', 'Greater than or equal to'), ('lte', 'Lesser than or equal to'), ('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('not_contains', 'Does not contain'), ] f = Filter(lookup_expr=None) field = f.field choice_field = field.fields[1] all_choices = choice_field.choices self.assertIsInstance(field, LookupTypeField) self.assertEqual(all_choices, filters.LOOKUP_TYPES) self.assertEqual(all_choices[1][0], 'exact') self.assertEqual(all_choices[1][1], 'Is equal to') custom_f = Filter(lookup_expr=('endswith', 'not_contains')) custom_field = custom_f.field custom_choice_field = custom_field.fields[1] my_custom_choices = custom_choice_field.choices available_lookup_exprs = [ ('endswith', 'Ends with'), ('not_contains', 'Does not contain'), ] self.assertIsInstance(custom_field, LookupTypeField) self.assertEqual(my_custom_choices, available_lookup_exprs) self.assertEqual(my_custom_choices[0][0], 'endswith') self.assertEqual(my_custom_choices[0][1], 'Ends with') self.assertEqual(my_custom_choices[1][0], 'not_contains') self.assertEqual(my_custom_choices[1][1], 'Does not contain') class CSVFilterTests(TestCase): def setUp(self): class NumberInFilter(BaseCSVFilter, NumberFilter): pass class DateTimeYearInFilter(BaseCSVFilter, DateTimeFilter): pass self.number_in = NumberInFilter(lookup_expr='in') self.datetimeyear_in = DateTimeYearInFilter(lookup_expr='year__in') def test_default_field(self): f = BaseCSVFilter() field = f.field self.assertIsInstance(field, forms.Field) def test_concrete_field(self): field = self.number_in.field self.assertIsInstance(field, forms.DecimalField) self.assertIsInstance(field, BaseCSVField) self.assertEqual(field.__class__.__name__, 'DecimalInField') field = self.datetimeyear_in.field self.assertIsInstance(field, forms.DateTimeField) self.assertIsInstance(field, BaseCSVField) self.assertEqual(field.__class__.__name__, 'DateTimeYearInField') def test_filtering(self): qs = mock.Mock(spec=['filter']) f = self.number_in f.filter(qs, [1, 2]) qs.filter.assert_called_once_with(None__in=[1, 2]) def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['filter']) f = self.number_in result = f.filter(qs, None) self.assertEqual(qs, result) def test_field_with_lookup_expr(self): qs = mock.Mock() f = self.datetimeyear_in f.filter(qs, [1, 2]) qs.filter.assert_called_once_with(None__year__in=[1, 2]) class BaseInFilterTests(TestCase): def test_filtering(self): class NumberInFilter(BaseInFilter, NumberFilter): pass qs = mock.Mock(spec=['filter']) f = NumberInFilter() f.filter(qs, [1, 2]) qs.filter.assert_called_once_with(None__in=[1, 2]) class BaseRangeFilterTests(TestCase): def test_filtering(self): class NumberInFilter(BaseRangeFilter, NumberFilter): pass qs = mock.Mock(spec=['filter']) f = NumberInFilter() f.filter(qs, [1, 2]) qs.filter.assert_called_once_with(None__range=[1, 2]) class OrderingFilterTests(TestCase): def test_default_field(self): f = OrderingFilter() field = f.field self.assertIsInstance(field, forms.ChoiceField) def test_filtering(self): qs = mock.Mock(spec=['order_by']) f = OrderingFilter() f.filter(qs, ['a', 'b']) qs.order_by.assert_called_once_with('a', 'b') def test_filtering_descending(self): qs = mock.Mock(spec=['order_by']) f = OrderingFilter() f.filter(qs, ['-a']) qs.order_by.assert_called_once_with('-a') def test_filtering_with_fields(self): qs = mock.Mock(spec=['order_by']) f = OrderingFilter(fields={'a': 'b'}) f.filter(qs, ['b', '-b']) qs.order_by.assert_called_once_with('a', '-a') def test_filtering_skipped_with_none_value(self): qs = mock.Mock(spec=['order_by']) f = OrderingFilter() result = f.filter(qs, None) self.assertEqual(qs, result) def test_choices_unaltered(self): # provided 'choices' should not be altered when 'fields' is present f = OrderingFilter( choices=(('a', 'A'), ('b', 'B')), fields=(('a', 'c'), ('b', 'd')), ) self.assertSequenceEqual(list(f.field.choices), ( ('', '---------'), ('a', 'A'), ('b', 'B'), )) def test_choices_from_fields(self): f = OrderingFilter( fields=(('a', 'c'), ('b', 'd')), ) self.assertSequenceEqual(list(f.field.choices), ( ('', '---------'), ('c', 'C'), ('-c', 'C (descending)'), ('d', 'D'), ('-d', 'D (descending)'), )) def test_field_labels(self): f = OrderingFilter( fields=(('a', 'c'), ('b', 'd')), field_labels={'a': 'foo'}, ) self.assertSequenceEqual(list(f.field.choices), ( ('', '---------'), ('c', 'foo'), ('-c', 'foo (descending)'), ('d', 'D'), ('-d', 'D (descending)'), )) def test_field_labels_descending(self): f = OrderingFilter( fields=['username'], field_labels={ 'username': 'BLABLA', '-username': 'XYZXYZ', } ) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('username', 'BLABLA'), ('-username', 'XYZXYZ'), ]) def test_normalize_fields(self): f = OrderingFilter.normalize_fields O = OrderedDict self.assertIn('a', f({'a': 'b'})) self.assertEqual( f(O([('a', 'b'), ('c', 'd')])), O([('a', 'b'), ('c', 'd')]) ) self.assertEqual( f([('a', 'b'), ('c', 'd')]), O([('a', 'b'), ('c', 'd')]) ) self.assertEqual( f(['a', 'b']), O([('a', 'a'), ('b', 'b')]) ) with self.assertRaises(AssertionError) as ctx: f(None) self.assertEqual(str(ctx.exception), "'fields' must be an iterable (e.g., a list, tuple, or mapping).") with self.assertRaises(AssertionError) as ctx: f([('a', 'b', 'c')]) self.assertEqual(str(ctx.exception), "'fields' must contain strings or (field name, param name) pairs.") with self.assertRaises(AssertionError) as ctx: f([0, 1, 2]) self.assertEqual(str(ctx.exception), "'fields' must contain strings or (field name, param name) pairs.") def test_widget(self): f = OrderingFilter() widget = f.field.widget self.assertIsInstance(widget, widgets.BaseCSVWidget) self.assertIsInstance(widget, forms.Select) def test_translation_sanity(self): with translation.override('pl'): self.assertEqual(_('Username'), 'Nazwa użytkownika') self.assertEqual(_('%s (descending)') % _('Username'), 'Nazwa użytkownika (malejąco)') def test_translation_default_label(self): with translation.override('pl'): f = OrderingFilter(fields=['username']) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('username', 'Nazwa użytkownika'), ('-username', 'Nazwa użytkownika (malejąco)'), ]) def test_translation_override_label(self): with translation.override('pl'): f = OrderingFilter( fields=['username'], field_labels={'username': 'BLABLA'}, ) self.assertEqual(list(f.field.choices), [ ('', '---------'), ('username', 'BLABLA'), ('-username', 'BLABLA (malejąco)'), ]) def test_help_text(self): # regression test for #756 - the ususal CSV help_text is not relevant to ordering filters. self.assertEqual(OrderingFilter().field.help_text, '') self.assertEqual(OrderingFilter(help_text='a').field.help_text, 'a') django-filter-1.1.0/tests/test_filterset.py0000644000076500000240000007247213172067725021625 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals import mock import unittest import django from django.core.exceptions import ValidationError from django.db import models from django.test import TestCase, override_settings from django_filters.constants import STRICTNESS from django_filters.filters import ( BaseInFilter, BaseRangeFilter, BooleanFilter, CharFilter, ChoiceFilter, DateRangeFilter, Filter, FilterMethod, ModelChoiceFilter, ModelMultipleChoiceFilter, NumberFilter, UUIDFilter ) from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS, FilterSet from django_filters.widgets import BooleanWidget from .models import ( Account, AdminUser, Article, BankAccount, Book, Business, Comment, DirectedNode, NetworkSetting, Node, Profile, Restaurant, SubnetMaskField, User, UUIDTestModel, Worker ) def checkItemsEqual(L1, L2): """ TestCase.assertItemsEqual() is not available in Python 2.6. """ return len(L1) == len(L2) and sorted(L1) == sorted(L2) class HelperMethodsTests(TestCase): @unittest.skip('todo') def test_get_declared_filters(self): pass @unittest.skip('todo') def test_filters_for_model(self): pass @unittest.skip('todo') def test_filterset_factory(self): pass class DbFieldDefaultFiltersTests(TestCase): def test_expected_db_fields_get_filters(self): to_check = [ models.BooleanField, models.CharField, models.CommaSeparatedIntegerField, models.DateField, models.DateTimeField, models.DecimalField, models.EmailField, models.FilePathField, models.FloatField, models.IntegerField, models.GenericIPAddressField, models.NullBooleanField, models.PositiveIntegerField, models.PositiveSmallIntegerField, models.SlugField, models.SmallIntegerField, models.TextField, models.TimeField, models.DurationField, models.URLField, models.ForeignKey, models.OneToOneField, models.ManyToManyField, models.UUIDField, ] msg = "%s expected to be found in FILTER_FOR_DBFIELD_DEFAULTS" for m in to_check: self.assertIn(m, FILTER_FOR_DBFIELD_DEFAULTS, msg % m.__name__) def test_expected_db_fields_do_not_get_filters(self): to_check = [ models.Field, models.BigIntegerField, models.FileField, models.ImageField, ] msg = "%s expected to not be found in FILTER_FOR_DBFIELD_DEFAULTS" for m in to_check: self.assertNotIn(m, FILTER_FOR_DBFIELD_DEFAULTS, msg % m.__name__) class FilterSetFilterForFieldTests(TestCase): def test_filter_found_for_field(self): f = User._meta.get_field('username') result = FilterSet.filter_for_field(f, 'username') self.assertIsInstance(result, CharFilter) self.assertEqual(result.name, 'username') def test_filter_found_for_uuidfield(self): f = UUIDTestModel._meta.get_field('uuid') result = FilterSet.filter_for_field(f, 'uuid') self.assertIsInstance(result, UUIDFilter) self.assertEqual(result.name, 'uuid') def test_filter_found_for_autofield(self): f = User._meta.get_field('id') result = FilterSet.filter_for_field(f, 'id') self.assertIsInstance(result, NumberFilter) self.assertEqual(result.name, 'id') def test_field_with_extras(self): f = User._meta.get_field('favorite_books') result = FilterSet.filter_for_field(f, 'favorite_books') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'favorite_books') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Book) def test_field_with_choices(self): f = User._meta.get_field('status') result = FilterSet.filter_for_field(f, 'status') self.assertIsInstance(result, ChoiceFilter) self.assertEqual(result.name, 'status') self.assertTrue('choices' in result.extra) self.assertIsNotNone(result.extra['choices']) def test_field_that_is_subclassed(self): f = User._meta.get_field('first_name') result = FilterSet.filter_for_field(f, 'first_name') self.assertIsInstance(result, CharFilter) def test_unknown_field_type_error(self): f = NetworkSetting._meta.get_field('mask') with self.assertRaises(AssertionError) as excinfo: FilterSet.filter_for_field(f, 'mask') self.assertIn( "FilterSet resolved field 'mask' with 'exact' lookup " "to an unrecognized field type SubnetMaskField", excinfo.exception.args[0]) def test_symmetrical_selfref_m2m_field(self): f = Node._meta.get_field('adjacents') result = FilterSet.filter_for_field(f, 'adjacents') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'adjacents') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Node) def test_non_symmetrical_selfref_m2m_field(self): f = DirectedNode._meta.get_field('outbound_nodes') result = FilterSet.filter_for_field(f, 'outbound_nodes') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'outbound_nodes') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, DirectedNode) def test_m2m_field_with_through_model(self): f = Business._meta.get_field('employees') result = FilterSet.filter_for_field(f, 'employees') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'employees') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Worker) @unittest.skipIf(django.VERSION < (1, 9), "version does not support transformed lookup expressions") def test_transformed_lookup_expr(self): f = Comment._meta.get_field('date') result = FilterSet.filter_for_field(f, 'date', 'year__gte') self.assertIsInstance(result, NumberFilter) self.assertEqual(result.name, 'date') @unittest.skip('todo') def test_filter_overrides(self): pass class FilterSetFilterForLookupTests(TestCase): def test_filter_for_ISNULL_lookup(self): f = Article._meta.get_field('author') result, params = FilterSet.filter_for_lookup(f, 'isnull') self.assertEqual(result, BooleanFilter) self.assertDictEqual(params, {}) def test_filter_for_IN_lookup(self): f = Article._meta.get_field('author') result, params = FilterSet.filter_for_lookup(f, 'in') self.assertTrue(issubclass(result, ModelChoiceFilter)) self.assertTrue(issubclass(result, BaseInFilter)) self.assertEqual(params['to_field_name'], 'id') def test_filter_for_RANGE_lookup(self): f = Article._meta.get_field('author') result, params = FilterSet.filter_for_lookup(f, 'range') self.assertTrue(issubclass(result, ModelChoiceFilter)) self.assertTrue(issubclass(result, BaseRangeFilter)) self.assertEqual(params['to_field_name'], 'id') def test_isnull_with_filter_overrides(self): class OFilterSet(FilterSet): class Meta: filter_overrides = { models.BooleanField: { 'filter_class': BooleanFilter, 'extra': lambda f: { 'widget': BooleanWidget, }, }, } f = Article._meta.get_field('author') result, params = OFilterSet.filter_for_lookup(f, 'isnull') self.assertEqual(result, BooleanFilter) self.assertEqual(params['widget'], BooleanWidget) class FilterSetFilterForReverseFieldTests(TestCase): def test_reverse_o2o_relationship(self): f = Account._meta.get_field('profile') result = FilterSet.filter_for_reverse_field(f, 'profile') self.assertIsInstance(result, ModelChoiceFilter) self.assertEqual(result.name, 'profile') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Profile) def test_reverse_fk_relationship(self): f = User._meta.get_field('comments') result = FilterSet.filter_for_reverse_field(f, 'comments') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'comments') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Comment) def test_reverse_m2m_relationship(self): f = Book._meta.get_field('lovers') result = FilterSet.filter_for_reverse_field(f, 'lovers') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'lovers') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, User) def test_reverse_non_symmetrical_selfref_m2m_field(self): f = DirectedNode._meta.get_field('inbound_nodes') result = FilterSet.filter_for_reverse_field(f, 'inbound_nodes') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'inbound_nodes') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, DirectedNode) def test_reverse_m2m_field_with_through_model(self): f = Worker._meta.get_field('employers') result = FilterSet.filter_for_reverse_field(f, 'employers') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.name, 'employers') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Business) class FilterSetClassCreationTests(TestCase): def test_no_filters(self): class F(FilterSet): pass self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 0) def test_declaring_filter(self): class F(FilterSet): username = CharFilter() self.assertEqual(len(F.declared_filters), 1) self.assertListEqual(list(F.declared_filters), ['username']) self.assertEqual(len(F.base_filters), 1) self.assertListEqual(list(F.base_filters), ['username']) def test_model_derived(self): class F(FilterSet): class Meta: model = Book fields = '__all__' self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 3) self.assertListEqual(list(F.base_filters), ['title', 'price', 'average_rating']) def test_model_no_fields_or_exclude(self): with self.assertRaises(AssertionError) as excinfo: class F(FilterSet): class Meta: model = Book self.assertIn( "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude'", str(excinfo.exception) ) def test_model_fields_empty(self): class F(FilterSet): class Meta: model = Book fields = [] self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 0) self.assertListEqual(list(F.base_filters), []) def test_model_exclude_empty(self): # equivalent to fields = '__all__' class F(FilterSet): class Meta: model = Book exclude = [] self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 3) self.assertListEqual(list(F.base_filters), ['title', 'price', 'average_rating']) def test_declared_and_model_derived(self): class F(FilterSet): username = CharFilter() class Meta: model = Book fields = '__all__' self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 4) self.assertListEqual(list(F.base_filters), ['title', 'price', 'average_rating', 'username']) def test_meta_fields_with_declared_and_model_derived(self): class F(FilterSet): username = CharFilter() class Meta: model = Book fields = ('username', 'price') self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 2) self.assertListEqual(list(F.base_filters), ['username', 'price']) def test_meta_fields_dictionary_derived(self): class F(FilterSet): class Meta: model = Book fields = {'price': ['exact', 'gte', 'lte'], } self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 3) expected_list = ['price', 'price__gte', 'price__lte', ] self.assertTrue(checkItemsEqual(list(F.base_filters), expected_list)) def test_meta_fields_containing_autofield(self): class F(FilterSet): username = CharFilter() class Meta: model = Book fields = ('id', 'username', 'price') self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 3) self.assertListEqual(list(F.base_filters), ['id', 'username', 'price']) def test_meta_fields_dictionary_autofield(self): class F(FilterSet): username = CharFilter() class Meta: model = Book fields = {'id': ['exact'], 'username': ['exact'], } self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 2) expected_list = ['id', 'username'] self.assertTrue(checkItemsEqual(list(F.base_filters), expected_list)) def test_meta_fields_containing_unknown(self): with self.assertRaises(TypeError) as excinfo: class F(FilterSet): username = CharFilter() class Meta: model = Book fields = ('username', 'price', 'other', 'another') self.assertEqual( str(excinfo.exception), "'Meta.fields' contains fields that are not defined on this FilterSet: " "other, another" ) def test_meta_fields_dictionary_containing_unknown(self): with self.assertRaises(TypeError): class F(FilterSet): class Meta: model = Book fields = {'id': ['exact'], 'title': ['exact'], 'other': ['exact'], } def test_meta_exlude_with_declared_and_declared_wins(self): class F(FilterSet): username = CharFilter() class Meta: model = Book exclude = ('username', 'price') self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 3) self.assertListEqual(list(F.base_filters), ['title', 'average_rating', 'username']) def test_meta_fields_and_exlude_and_exclude_wins(self): class F(FilterSet): username = CharFilter() class Meta: model = Book fields = ('username', 'title', 'price') exclude = ('title',) self.assertEqual(len(F.declared_filters), 1) self.assertEqual(len(F.base_filters), 2) self.assertListEqual(list(F.base_filters), ['username', 'price']) def test_meta_exlude_with_no_fields(self): class F(FilterSet): class Meta: model = Book exclude = ('price', ) self.assertEqual(len(F.declared_filters), 0) self.assertEqual(len(F.base_filters), 2) self.assertListEqual(list(F.base_filters), ['title', 'average_rating']) def test_filterset_class_inheritance(self): class F(FilterSet): class Meta: model = Book fields = '__all__' class G(F): pass self.assertEqual(set(F.base_filters), set(G.base_filters)) class F(FilterSet): other = CharFilter class Meta: model = Book fields = '__all__' class G(F): pass self.assertEqual(set(F.base_filters), set(G.base_filters)) def test_abstract_model_inheritance(self): class F(FilterSet): class Meta: model = Restaurant fields = '__all__' self.assertEqual(set(F.base_filters), set(['name', 'serves_pizza'])) class F(FilterSet): class Meta: model = Restaurant fields = ['name', 'serves_pizza'] self.assertEqual(set(F.base_filters), set(['name', 'serves_pizza'])) def test_custom_field_gets_filter_from_override(self): class F(FilterSet): class Meta: model = NetworkSetting fields = '__all__' filter_overrides = { SubnetMaskField: {'filter_class': CharFilter} } self.assertEqual(list(F.base_filters.keys()), ['ip', 'mask', 'cidr']) def test_custom_declared_field_no_warning(self): class F(FilterSet): mask = CharFilter() class Meta: model = NetworkSetting fields = ['mask'] self.assertEqual(list(F.base_filters.keys()), ['mask']) def test_filterset_for_proxy_model(self): class F(FilterSet): class Meta: model = User fields = '__all__' class ProxyF(FilterSet): class Meta: model = AdminUser fields = '__all__' self.assertEqual(list(F.base_filters), list(ProxyF.base_filters)) def test_filterset_for_mti_model(self): class F(FilterSet): class Meta: model = Account fields = '__all__' class FtiF(FilterSet): class Meta: model = BankAccount fields = '__all__' # fails due to 'account_ptr' getting picked up self.assertEqual( list(F.base_filters) + ['amount_saved'], list(FtiF.base_filters)) def test_declared_filter_disabling(self): class Parent(FilterSet): f1 = CharFilter() f2 = CharFilter() class Child(Parent): f1 = None class Grandchild(Child): pass self.assertEqual(len(Parent.base_filters), 2) self.assertEqual(len(Child.base_filters), 1) self.assertEqual(len(Grandchild.base_filters), 1) class FilterSetInstantiationTests(TestCase): def test_creating_instance(self): class F(FilterSet): class Meta: model = User fields = ['username'] f = F() self.assertFalse(f.is_bound) self.assertIsNotNone(f.queryset) self.assertEqual(len(f.filters), len(F.base_filters)) for name, filter_ in f.filters.items(): self.assertEqual( filter_.model, User, "%s does not have model set correctly" % name) def test_creating_bound_instance(self): class F(FilterSet): class Meta: model = User fields = ['username'] f = F({'username': 'username'}) self.assertTrue(f.is_bound) def test_creating_with_queryset(self): class F(FilterSet): class Meta: model = User fields = ['username'] m = mock.Mock() f = F(queryset=m) self.assertEqual(f.queryset, m) def test_creating_with_request(self): class F(FilterSet): class Meta: model = User fields = ['username'] m = mock.Mock() f = F(request=m) self.assertEqual(f.request, m) class FilterSetStrictnessTests(TestCase): def test_settings_default(self): class F(FilterSet): class Meta: model = User fields = [] # Ensure default is not IGNORE self.assertEqual(F().strict, STRICTNESS.RETURN_NO_RESULTS) # override and test with override_settings(FILTERS_STRICTNESS=STRICTNESS.IGNORE): self.assertEqual(F().strict, STRICTNESS.IGNORE) def test_meta_value(self): class F(FilterSet): class Meta: model = User fields = [] strict = STRICTNESS.IGNORE self.assertEqual(F().strict, STRICTNESS.IGNORE) def test_init_default(self): class F(FilterSet): class Meta: model = User fields = [] strict = STRICTNESS.IGNORE strict = STRICTNESS.RAISE_VALIDATION_ERROR self.assertEqual(F(strict=strict).strict, strict) def test_legacy_value(self): class F(FilterSet): class Meta: model = User fields = [] self.assertEqual(F(strict=False).strict, STRICTNESS.IGNORE) class FilterSetTogetherTests(TestCase): def setUp(self): self.alex = User.objects.create(username='alex', status=1) self.jacob = User.objects.create(username='jacob', status=2) self.qs = User.objects.all().order_by('id') def test_fields_set(self): class F(FilterSet): class Meta: model = User fields = ['username', 'status', 'is_active', 'first_name'] together = [ ('username', 'status'), ('first_name', 'is_active'), ] strict = STRICTNESS.RAISE_VALIDATION_ERROR f = F({}, queryset=self.qs) self.assertEqual(f.qs.count(), 2) f = F({'username': 'alex'}, queryset=self.qs) with self.assertRaises(ValidationError): f.qs.count() f = F({'username': 'alex', 'status': 1}, queryset=self.qs) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [self.alex.pk], lambda o: o.pk) def test_single_fields_set(self): class F(FilterSet): class Meta: model = User fields = ['username', 'status'] together = ['username', 'status'] strict = STRICTNESS.RAISE_VALIDATION_ERROR f = F({}, queryset=self.qs) self.assertEqual(f.qs.count(), 2) f = F({'username': 'alex'}, queryset=self.qs) with self.assertRaises(ValidationError): f.qs.count() f = F({'username': 'alex', 'status': 1}, queryset=self.qs) self.assertEqual(f.qs.count(), 1) self.assertQuerysetEqual(f.qs, [self.alex.pk], lambda o: o.pk) def test_empty_values(self): class F(FilterSet): class Meta: model = User fields = ['username', 'status'] together = ['username', 'status'] f = F({'username': '', 'status': ''}, queryset=self.qs) self.assertEqual(f.qs.count(), 2) f = F({'username': 'alex', 'status': ''}, queryset=self.qs) self.assertEqual(f.qs.count(), 0) # test filter.method here, as it depends on its parent FilterSet class FilterMethodTests(TestCase): def test_none(self): # use a mock to bypass bound/unbound method equality class TestFilter(Filter): filter = mock.Mock() f = TestFilter(method=None) self.assertIsNone(f.method) # passing method=None should not modify filter function self.assertIs(f.filter, TestFilter.filter) def test_method_name(self): class F(FilterSet): f = Filter(method='filter_f') def filter_f(self, qs, name, value): pass f = F({}, queryset=User.objects.all()) self.assertEqual(f.filters['f'].method, 'filter_f') self.assertEqual(f.filters['f'].filter.method, f.filter_f) self.assertIsInstance(f.filters['f'].filter, FilterMethod) def test_method_callable(self): def filter_f(qs, name, value): pass class F(FilterSet): f = Filter(method=filter_f) f = F({}, queryset=User.objects.all()) self.assertEqual(f.filters['f'].method, filter_f) self.assertEqual(f.filters['f'].filter.method, filter_f) self.assertIsInstance(f.filters['f'].filter, FilterMethod) def test_request_available_during_method_called(self): class F(FilterSet): f = Filter(method='filter_f') def filter_f(self, qs, name, value): # call mock request object to prove self.request can be accessed self.request() m = mock.Mock() f = F({}, queryset=User.objects.all(), request=m) # call the filter f.filters['f'].filter.method(User.objects.all(), 'f', '') m.assert_called_once_with() def test_method_with_overridden_filter(self): # Some filter classes override the base filter() method. We need # to ensure that passing a method argument still works correctly class F(FilterSet): f = DateRangeFilter(method='filter_f') def filter_f(self, qs, name, value): pass f = F({}, queryset=User.objects.all()) self.assertEqual(f.filters['f'].method, 'filter_f') self.assertEqual(f.filters['f'].filter.method, f.filter_f) def test_parent_unresolvable(self): f = Filter(method='filter_f') with self.assertRaises(AssertionError) as w: f.filter(User.objects.all(), 0) self.assertIn("'None'", str(w.exception)) self.assertIn('parent', str(w.exception)) self.assertIn('filter_f', str(w.exception)) def test_method_self_is_parent(self): # Ensure the method isn't 're-parented' on the `FilterMethod` helper class. # Filter methods should have access to the filterset's properties. request = mock.Mock() class F(FilterSet): f = CharFilter(method='filter_f') class Meta: model = User fields = [] def filter_f(inner_self, qs, name, value): self.assertIsInstance(inner_self, F) self.assertIs(inner_self.request, request) F({'f': 'foo'}, request=request, queryset=User.objects.all()).qs def test_method_unresolvable(self): class F(FilterSet): f = Filter(method='filter_f') f = F({}, queryset=User.objects.all()) with self.assertRaises(AssertionError) as w: f.filters['f'].filter(User.objects.all(), 0) self.assertIn('%s.%s' % (F.__module__, F.__name__), str(w.exception)) self.assertIn('.filter_f()', str(w.exception)) def test_method_uncallable(self): class F(FilterSet): f = Filter(method='filter_f') filter_f = 4 f = F({}, queryset=User.objects.all()) with self.assertRaises(AssertionError) as w: f.filters['f'].filter(User.objects.all(), 0) self.assertIn('%s.%s' % (F.__module__, F.__name__), str(w.exception)) self.assertIn('.filter_f()', str(w.exception)) def test_method_set_unset(self): # use a mock to bypass bound/unbound method equality class TestFilter(Filter): filter = mock.Mock() f = TestFilter(method='filter_f') self.assertEqual(f.method, 'filter_f') self.assertIsInstance(f.filter, FilterMethod) # setting None should revert to Filter.filter f.method = None self.assertIsNone(f.method) self.assertIs(f.filter, TestFilter.filter) class MiscFilterSetTests(TestCase): def test_no__getitem__(self): # The DTL processes variable lookups by the following rules: # https://docs.djangoproject.com/en/1.9/ref/templates/language/#variables # A __getitem__ implementation precedes normal attribute access, and in # the case of #58, will force the queryset to evaluate when it should # not (eg, when rendering a blank form). self.assertFalse(hasattr(FilterSet, '__getitem__')) def test_no_qs_proxying(self): # The FilterSet should not proxy .qs methods - just access .qs directly self.assertFalse(hasattr(FilterSet, '__len__')) self.assertFalse(hasattr(FilterSet, '__iter__')) django-filter-1.1.0/tests/test_forms.py0000644000076500000240000001604613172067725020745 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django import forms from django.test import TestCase, override_settings from django_filters.filters import CharFilter, ChoiceFilter from django_filters.filterset import FilterSet from .models import MANAGER, REGULAR, STATUS_CHOICES, Book, ManagerGroup, User class FilterSetFormTests(TestCase): def test_form_from_empty_filterset(self): class F(FilterSet): pass f = F(queryset=Book.objects.all()).form self.assertIsInstance(f, forms.Form) def test_form(self): class F(FilterSet): class Meta: model = Book fields = ('title',) f = F().form self.assertIsInstance(f, forms.Form) self.assertEqual(list(f.fields), ['title']) def test_custom_form(self): class MyForm(forms.Form): pass class F(FilterSet): class Meta: model = Book fields = '__all__' form = MyForm f = F().form self.assertIsInstance(f, MyForm) def test_form_prefix(self): class F(FilterSet): class Meta: model = Book fields = ('title',) f = F().form self.assertIsNone(f.prefix) f = F(prefix='prefix').form self.assertEqual(f.prefix, 'prefix') def test_form_fields(self): class F(FilterSet): class Meta: model = User fields = ['status'] f = F().form self.assertEqual(len(f.fields), 1) self.assertIn('status', f.fields) self.assertSequenceEqual( list(f.fields['status'].choices), (('', '---------'), ) + STATUS_CHOICES ) def test_form_fields_exclusion(self): class F(FilterSet): title = CharFilter(exclude=True) class Meta: model = Book fields = ('title',) f = F().form self.assertEqual(f.fields['title'].label, "Exclude title") def test_complex_form_fields(self): class F(FilterSet): username = CharFilter(label='Filter for users with username') exclude_username = CharFilter(name='username', lookup_expr='iexact', exclude=True) class Meta: model = User fields = { 'status': ['exact', 'lt', 'gt'], 'favorite_books__title': ['iexact', 'in'], 'manager_of__users__username': ['exact'], } fields = F().form.fields self.assertEqual(fields['username'].label, 'Filter for users with username') self.assertEqual(fields['exclude_username'].label, 'Exclude username') self.assertEqual(fields['status'].label, 'Status') self.assertEqual(fields['status__lt'].label, 'Status is less than') self.assertEqual(fields['status__gt'].label, 'Status is greater than') self.assertEqual(fields['favorite_books__title__iexact'].label, 'Favorite books title') self.assertEqual(fields['favorite_books__title__in'].label, 'Favorite books title is in') self.assertEqual(fields['manager_of__users__username'].label, 'Manager of users username') def test_form_fields_using_widget(self): class F(FilterSet): status = ChoiceFilter(widget=forms.RadioSelect, choices=STATUS_CHOICES, empty_label=None) class Meta: model = User fields = ['status', 'username'] f = F().form self.assertEqual(len(f.fields), 2) self.assertIn('status', f.fields) self.assertIn('username', f.fields) self.assertSequenceEqual( list(f.fields['status'].choices), STATUS_CHOICES ) self.assertIsInstance(f.fields['status'].widget, forms.RadioSelect) def test_form_field_with_custom_label(self): class F(FilterSet): title = CharFilter(label="Book title") class Meta: model = Book fields = ('title',) f = F().form self.assertEqual(f.fields['title'].label, "Book title") self.assertEqual(f['title'].label, 'Book title') def test_form_field_with_manual_name(self): class F(FilterSet): book_title = CharFilter(name='title') class Meta: model = Book fields = ('book_title',) f = F().form self.assertEqual(f.fields['book_title'].label, "Title") self.assertEqual(f['book_title'].label, "Title") def test_form_field_with_manual_name_and_label(self): class F(FilterSet): f1 = CharFilter(name='title', label="Book title") class Meta: model = Book fields = ('f1',) f = F().form self.assertEqual(f.fields['f1'].label, "Book title") self.assertEqual(f['f1'].label, 'Book title') def test_filter_with_initial(self): class F(FilterSet): status = ChoiceFilter(choices=STATUS_CHOICES, initial=1) class Meta: model = User fields = ['status'] f = F().form self.assertEqual(f.fields['status'].initial, 1) def test_form_is_not_bound(self): class F(FilterSet): class Meta: model = Book fields = ('title',) f = F().form self.assertFalse(f.is_bound) self.assertEqual(f.data, {}) def test_form_is_bound(self): class F(FilterSet): class Meta: model = Book fields = ('title',) f = F({'title': 'Some book'}).form self.assertTrue(f.is_bound) self.assertEqual(f.data, {'title': 'Some book'}) def test_limit_choices_to(self): User.objects.create(username='inactive', is_active=False, status=REGULAR) User.objects.create(username='active', is_active=True, status=REGULAR) User.objects.create(username='manager', is_active=False, status=MANAGER) class F(FilterSet): class Meta: model = ManagerGroup fields = ['users', 'manager'] f = F().form self.assertEqual( list(f.fields['users'].choices), [(2, 'active')] ) self.assertEqual( list(f.fields['manager'].choices), [('', '---------'), (3, 'manager')] ) def test_disabled_help_text(self): class F(FilterSet): class Meta: model = Book fields = { # 'in' lookups are CSV-based, which have a `help_text`. 'title': ['in'] } self.assertEqual( F().form.fields['title__in'].help_text, 'Multiple values may be separated by commas.' ) with override_settings(FILTERS_DISABLE_HELP_TEXT=True): self.assertEqual( F().form.fields['title__in'].help_text, '' ) django-filter-1.1.0/tests/test_utils.py0000644000076500000240000003216313172067725020755 0ustar carltonstaff00000000000000import datetime import unittest import django from django.db import models from django.db.models.constants import LOOKUP_SEP from django.db.models.fields.related import ForeignObjectRel from django.forms import ValidationError from django.test import TestCase, override_settings from django.utils.functional import Promise from django.utils.timezone import get_default_timezone from django_filters import STRICTNESS, FilterSet from django_filters.exceptions import FieldLookupError from django_filters.utils import ( get_field_parts, get_model_field, handle_timezone, label_for_filter, raw_validation, resolve_field, verbose_field_name, verbose_lookup_expr ) from .models import ( Account, Article, Book, Business, Company, HiredWorker, NetworkSetting, User ) class GetFieldPartsTests(TestCase): def test_field(self): parts = get_field_parts(User, 'username') self.assertEqual(len(parts), 1) self.assertIsInstance(parts[0], models.CharField) def test_non_existent_field(self): result = get_model_field(User, 'unknown__name') self.assertIsNone(result) def test_forwards_related_field(self): parts = get_field_parts(User, 'favorite_books__title') self.assertEqual(len(parts), 2) self.assertIsInstance(parts[0], models.ManyToManyField) self.assertIsInstance(parts[1], models.CharField) def test_reverse_related_field(self): parts = get_field_parts(User, 'manager_of__users__username') self.assertEqual(len(parts), 3) self.assertIsInstance(parts[0], ForeignObjectRel) self.assertIsInstance(parts[1], models.ManyToManyField) self.assertIsInstance(parts[2], models.CharField) class GetModelFieldTests(TestCase): def test_non_existent_field(self): result = get_model_field(User, 'unknown__name') self.assertIsNone(result) def test_related_field(self): result = get_model_field(Business, 'hiredworker__worker') self.assertEqual(result, HiredWorker._meta.get_field('worker')) class ResolveFieldTests(TestCase): def test_resolve_plain_lookups(self): """ Check that the standard query terms can be correctly resolved. eg, an 'EXACT' lookup on a user's username """ model_field = User._meta.get_field('username') lookups = model_field.class_lookups.keys() # This is simple - the final ouput of an untransformed field is itself. # The lookups are the default lookups registered to the class. for term in lookups: field, lookup = resolve_field(model_field, term) self.assertIsInstance(field, models.CharField) self.assertEqual(lookup, term) def test_resolve_forward_related_lookups(self): """ Check that lookups can be resolved for related fields in the forwards direction. """ lookups = ['exact', 'gte', 'gt', 'lte', 'lt', 'in', 'isnull', ] # ForeignKey model_field = Article._meta.get_field('author') for term in lookups: field, lookup = resolve_field(model_field, term) self.assertIsInstance(field, models.ForeignKey) self.assertEqual(lookup, term) # ManyToManyField model_field = User._meta.get_field('favorite_books') for term in lookups: field, lookup = resolve_field(model_field, term) self.assertIsInstance(field, models.ManyToManyField) self.assertEqual(lookup, term) @unittest.skipIf(django.VERSION < (1, 9), "version does not reverse lookups") def test_resolve_reverse_related_lookups(self): """ Check that lookups can be resolved for related fields in the reverse direction. """ lookups = ['exact', 'gte', 'gt', 'lte', 'lt', 'in', 'isnull', ] # ManyToOneRel model_field = User._meta.get_field('article') for term in lookups: field, lookup = resolve_field(model_field, term) self.assertIsInstance(field, models.ManyToOneRel) self.assertEqual(lookup, term) # ManyToManyRel model_field = Book._meta.get_field('lovers') for term in lookups: field, lookup = resolve_field(model_field, term) self.assertIsInstance(field, models.ManyToManyRel) self.assertEqual(lookup, term) @unittest.skipIf(django.VERSION < (1, 9), "version does not support transformed lookup expressions") def test_resolve_transformed_lookups(self): """ Check that chained field transforms are correctly resolved. eg, a 'date__year__gte' lookup on an article's 'published' timestamp. """ # Use a DateTimeField, so we can check multiple transforms. # eg, date__year__gte model_field = Article._meta.get_field('published') standard_lookups = [ 'exact', 'iexact', 'gte', 'gt', 'lte', 'lt', ] date_lookups = [ 'year', 'month', 'day', 'week_day', ] datetime_lookups = date_lookups + [ 'hour', 'minute', 'second', ] # ex: 'date__gt' for lookup in standard_lookups: field, resolved_lookup = resolve_field(model_field, LOOKUP_SEP.join(['date', lookup])) self.assertIsInstance(field, models.DateField) self.assertEqual(resolved_lookup, lookup) # ex: 'year__iexact' for part in datetime_lookups: for lookup in standard_lookups: field, resolved_lookup = resolve_field(model_field, LOOKUP_SEP.join([part, lookup])) self.assertIsInstance(field, models.IntegerField) self.assertEqual(resolved_lookup, lookup) # ex: 'date__year__lte' for part in date_lookups: for lookup in standard_lookups: field, resolved_lookup = resolve_field(model_field, LOOKUP_SEP.join(['date', part, lookup])) self.assertIsInstance(field, models.IntegerField) self.assertEqual(resolved_lookup, lookup) @unittest.skipIf(django.VERSION < (1, 9), "version does not support transformed lookup expressions") def test_resolve_implicit_exact_lookup(self): # Use a DateTimeField, so we can check multiple transforms. # eg, date__year__gte model_field = Article._meta.get_field('published') field, lookup = resolve_field(model_field, 'date') self.assertIsInstance(field, models.DateField) self.assertEqual(lookup, 'exact') field, lookup = resolve_field(model_field, 'date__year') self.assertIsInstance(field, models.IntegerField) self.assertEqual(lookup, 'exact') def test_invalid_lookup_expression(self): model_field = Article._meta.get_field('published') with self.assertRaises(FieldLookupError) as context: resolve_field(model_field, 'invalid_lookup') exc = str(context.exception) self.assertIn(str(model_field), exc) self.assertIn('invalid_lookup', exc) def test_invalid_transformed_lookup_expression(self): model_field = Article._meta.get_field('published') with self.assertRaises(FieldLookupError) as context: resolve_field(model_field, 'date__invalid_lookup') exc = str(context.exception) self.assertIn(str(model_field), exc) self.assertIn('date__invalid_lookup', exc) class VerboseFieldNameTests(TestCase): def test_none(self): verbose_name = verbose_field_name(Article, None) self.assertEqual(verbose_name, '[invalid name]') def test_invalid_name(self): verbose_name = verbose_field_name(Article, 'foobar') self.assertEqual(verbose_name, '[invalid name]') def test_field(self): verbose_name = verbose_field_name(Article, 'author') self.assertEqual(verbose_name, 'author') def test_field_with_verbose_name(self): verbose_name = verbose_field_name(Article, 'name') self.assertEqual(verbose_name, 'title') def test_field_all_caps(self): verbose_name = verbose_field_name(NetworkSetting, 'cidr') self.assertEqual(verbose_name, 'CIDR') def test_forwards_related_field(self): verbose_name = verbose_field_name(Article, 'author__username') self.assertEqual(verbose_name, 'author username') def test_backwards_related_field(self): verbose_name = verbose_field_name(Book, 'lovers__first_name') self.assertEqual(verbose_name, 'lovers first name') def test_backwards_related_field_multi_word(self): verbose_name = verbose_field_name(User, 'manager_of') self.assertEqual(verbose_name, 'manager of') def test_lazy_text(self): # sanity check field = User._meta.get_field('username') self.assertIsInstance(field.verbose_name, Promise) verbose_name = verbose_field_name(User, 'username') self.assertEqual(verbose_name, 'username') def test_forwards_fk(self): verbose_name = verbose_field_name(Article, 'author') self.assertEqual(verbose_name, 'author') def test_backwards_fk(self): # https://github.com/carltongibson/django-filter/issues/716 # related_name is set verbose_name = verbose_field_name(Company, 'locations') self.assertEqual(verbose_name, 'locations') # related_name not set. Auto-generated relation is `article_set` # _meta.get_field raises FieldDoesNotExist verbose_name = verbose_field_name(User, 'article_set') self.assertEqual(verbose_name, '[invalid name]') # WRONG NAME! Returns ManyToOneRel with related_name == None. verbose_name = verbose_field_name(User, 'article') self.assertEqual(verbose_name, '[invalid name]') class VerboseLookupExprTests(TestCase): def test_exact(self): # Exact should default to empty. A verbose expression is unnecessary, # and this behavior works well with list syntax for `Meta.fields`. verbose_lookup = verbose_lookup_expr('exact') self.assertEqual(verbose_lookup, '') def test_verbose_expression(self): verbose_lookup = verbose_lookup_expr('date__lt') self.assertEqual(verbose_lookup, 'date is less than') def test_missing_keys(self): verbose_lookup = verbose_lookup_expr('foo__bar__lt') self.assertEqual(verbose_lookup, 'foo bar is less than') @override_settings(FILTERS_VERBOSE_LOOKUPS={'exact': 'is equal to'}) def test_overridden_settings(self): verbose_lookup = verbose_lookup_expr('exact') self.assertEqual(verbose_lookup, 'is equal to') class LabelForFilterTests(TestCase): def test_standard_label(self): label = label_for_filter(Article, 'name', 'in') self.assertEqual(label, 'Title is in') def test_related_model(self): label = label_for_filter(Article, 'author__first_name', 'in') self.assertEqual(label, 'Author first name is in') def test_exclusion_label(self): label = label_for_filter(Article, 'name', 'in', exclude=True) self.assertEqual(label, 'Exclude title is in') def test_related_model_exclusion(self): label = label_for_filter(Article, 'author__first_name', 'in', exclude=True) self.assertEqual(label, 'Exclude author first name is in') def test_exact_lookup(self): label = label_for_filter(Article, 'name', 'exact') self.assertEqual(label, 'Title') def test_field_all_caps(self): label = label_for_filter(NetworkSetting, 'cidr', 'contains', exclude=True) self.assertEqual(label, 'Exclude CIDR contains') class HandleTimezone(TestCase): @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='America/Sao_Paulo') def test_handle_dst_ending(self): dst_ending_date = datetime.datetime(2017, 2, 18, 23, 59, 59, 999999) handled = handle_timezone(dst_ending_date, False) self.assertEqual(handled, get_default_timezone().localize(dst_ending_date, False)) @unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware') @override_settings(TIME_ZONE='America/Sao_Paulo') def test_handle_dst_starting(self): dst_starting_date = datetime.datetime(2017, 10, 15, 0, 0, 0, 0) handled = handle_timezone(dst_starting_date, True) self.assertEqual(handled, get_default_timezone().localize(dst_starting_date, True)) class RawValidationDataTests(TestCase): def test_simple(self): class F(FilterSet): class Meta: model = Article fields = ['id', 'author', 'name'] strict = STRICTNESS.RAISE_VALIDATION_ERROR f = F(data={'id': 'foo', 'author': 'bar', 'name': 'baz'}) with self.assertRaises(ValidationError) as exc: f.qs self.assertDictEqual(raw_validation(exc.exception), { 'id': ['Enter a number.'], 'author': ['Select a valid choice. That choice is not one of the available choices.'], }) django-filter-1.1.0/tests/test_views.py0000644000076500000240000001001713172067725020744 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings from django.test.client import RequestFactory from django_filters.filterset import FilterSet, filterset_factory from django_filters.views import FilterView from .models import Book @override_settings(ROOT_URLCONF='tests.urls') class GenericViewTestCase(TestCase): def setUp(self): Book.objects.create( title="Ender's Game", price='1.00', average_rating=3.0) Book.objects.create( title="Rainbow Six", price='1.00', average_rating=3.0) Book.objects.create( title="Snowcrash", price='1.00', average_rating=3.0) class GenericClassBasedViewTests(GenericViewTestCase): base_url = '/books/' def test_view(self): response = self.client.get(self.base_url) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) def test_view_filtering_on_title(self): response = self.client.get(self.base_url + '?title=Snowcrash') for b in ['Ender's Game', 'Rainbow Six']: self.assertNotContains(response, b) self.assertContains(response, 'Snowcrash') def test_view_with_filterset_not_model(self): factory = RequestFactory() request = factory.get(self.base_url) filterset = filterset_factory(Book) view = FilterView.as_view(filterset_class=filterset) response = view(request) self.assertEqual(response.status_code, 200) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) def test_view_with_model_no_filterset(self): factory = RequestFactory() request = factory.get(self.base_url) view = FilterView.as_view(model=Book) response = view(request) self.assertEqual(response.status_code, 200) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) def test_view_with_model_and_fields_no_filterset(self): factory = RequestFactory() request = factory.get(self.base_url + '?price=1.0') view = FilterView.as_view(model=Book, filter_fields=['price']) # filtering only by price response = view(request) self.assertEqual(response.status_code, 200) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) # not filtering by title request = factory.get(self.base_url + '?title=Snowcrash') response = view(request) self.assertEqual(response.status_code, 200) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) def test_view_without_filterset_or_model(self): factory = RequestFactory() request = factory.get(self.base_url) view = FilterView.as_view() with self.assertRaises(ImproperlyConfigured): view(request) def test_view_with_bad_filterset(self): class MyFilterSet(FilterSet): pass factory = RequestFactory() request = factory.get(self.base_url) view = FilterView.as_view(filterset_class=MyFilterSet) with self.assertRaises(ImproperlyConfigured): view(request) class GenericFunctionalViewTests(GenericViewTestCase): base_url = '/books-legacy/' def test_view(self): response = self.client.get(self.base_url) for b in ['Ender's Game', 'Rainbow Six', 'Snowcrash']: self.assertContains(response, b) # extra context self.assertEqual(response.context_data['foo'], 'bar') self.assertEqual(response.context_data['bar'], 'foo') def test_view_filtering_on_price(self): response = self.client.get(self.base_url + '?title=Snowcrash') for b in ['Ender's Game', 'Rainbow Six']: self.assertNotContains(response, b) self.assertContains(response, 'Snowcrash') django-filter-1.1.0/tests/test_widgets.py0000644000076500000240000003244313172067725021264 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django.forms import Select, TextInput from django.test import TestCase from django_filters.widgets import ( BaseCSVWidget, BooleanWidget, CSVWidget, LinkWidget, LookupTypeWidget, QueryArrayWidget, RangeWidget, SuffixedMultiWidget ) class LookupTypeWidgetTests(TestCase): def test_widget_requires_field(self): with self.assertRaises(TypeError): LookupTypeWidget() def test_widget_render(self): widgets = [TextInput(), Select(choices=(('a', 'a'), ('b', 'b')))] w = LookupTypeWidget(widgets) self.assertHTMLEqual(w.render('price', ''), """ """) self.assertHTMLEqual(w.render('price', None), """ """) self.assertHTMLEqual(w.render('price', ['2', 'a']), """ """) class LinkWidgetTests(TestCase): def test_widget_without_choices(self): w = LinkWidget() self.assertEqual(len(w.choices), 0) self.assertHTMLEqual(w.render('price', ''), """
      """) def test_widget(self): choices = ( ('test-val1', 'test-label1'), ('test-val2', 'test-label2'), ) w = LinkWidget(choices=choices) self.assertEqual(len(w.choices), 2) self.assertHTMLEqual(w.render('price', ''), """ """) self.assertHTMLEqual(w.render('price', None), """ """) self.assertHTMLEqual(w.render('price', 'test-val1'), """ """) def test_widget_with_option_groups(self): choices = ( ('Audio', ( ('vinyl', 'Vinyl'), ('cd', 'CD'), )), ('Video', ( ('vhs', 'VHS Tape'), ('dvd', 'DVD'), )), ('unknown', 'Unknown'), ) w = LinkWidget(choices=choices) self.assertHTMLEqual(w.render('media', ''), """ """) def test_widget_with_blank_choice(self): choices = ( ('', '---------'), ('test-val1', 'test-label1'), ('test-val2', 'test-label2'), ) w = LinkWidget(choices=choices) self.assertHTMLEqual(w.render('price', ''), """ """) def test_widget_value_from_datadict(self): w = LinkWidget() data = {'price': 'test-val1'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, 'test-val1') class SuffixedMultiWidgetTests(TestCase): def test_assertions(self): # number of widgets must match suffixes with self.assertRaises(AssertionError): SuffixedMultiWidget(widgets=[BooleanWidget]) # suffixes must be unique class W(SuffixedMultiWidget): suffixes = ['a', 'a'] with self.assertRaises(AssertionError): W(widgets=[BooleanWidget, BooleanWidget]) # should succeed class W(SuffixedMultiWidget): suffixes = ['a', 'b'] W(widgets=[BooleanWidget, BooleanWidget]) def test_render(self): class W(SuffixedMultiWidget): suffixes = ['min', 'max'] w = W(widgets=[TextInput, TextInput]) self.assertHTMLEqual(w.render('price', ''), """ """) # blank suffix class W(SuffixedMultiWidget): suffixes = [None, 'lookup'] w = W(widgets=[TextInput, TextInput]) self.assertHTMLEqual(w.render('price', ''), """ """) def test_value_from_datadict(self): class W(SuffixedMultiWidget): suffixes = ['min', 'max'] w = W(widgets=[TextInput, TextInput]) result = w.value_from_datadict({ 'price_min': '1', 'price_max': '2', }, {}, 'price') self.assertEqual(result, ['1', '2']) result = w.value_from_datadict({}, {}, 'price') self.assertEqual(result, [None, None]) # blank suffix class W(SuffixedMultiWidget): suffixes = ['', 'lookup'] w = W(widgets=[TextInput, TextInput]) result = w.value_from_datadict({ 'price': '1', 'price_lookup': 'lt', }, {}, 'price') self.assertEqual(result, ['1', 'lt']) class RangeWidgetTests(TestCase): def test_widget(self): w = RangeWidget() self.assertEqual(len(w.widgets), 2) self.assertHTMLEqual(w.render('price', ''), """ - """) self.assertHTMLEqual(w.render('price', slice(5.99, 9.99)), """ - """) def test_widget_attributes(self): w = RangeWidget(attrs={'type': 'date'}) self.assertEqual(len(w.widgets), 2) self.assertHTMLEqual(w.render('date', ''), """ - """) class BooleanWidgetTests(TestCase): """ """ def test_widget_render(self): w = BooleanWidget() self.assertHTMLEqual(w.render('price', ''), """ """) def test_widget_value_from_datadict(self): """ """ w = BooleanWidget() trueActive = {'active': 'true'} result = w.value_from_datadict(trueActive, {}, 'active') self.assertEqual(result, True) falseActive = {'active': 'false'} result = w.value_from_datadict(falseActive, {}, 'active') self.assertEqual(result, False) result = w.value_from_datadict({}, {}, 'active') self.assertEqual(result, None) class CSVWidgetTests(TestCase): def test_widget(self): w = CSVWidget() self.assertHTMLEqual(w.render('price', None), """ """) self.assertHTMLEqual(w.render('price', ''), """ """) self.assertHTMLEqual(w.render('price', []), """ """) self.assertHTMLEqual(w.render('price', '1'), """ """) self.assertHTMLEqual(w.render('price', '1,2'), """ """) self.assertHTMLEqual(w.render('price', ['1', '2']), """ """) self.assertHTMLEqual(w.render('price', [1, 2]), """ """) def test_widget_value_from_datadict(self): w = CSVWidget() data = {'price': None} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, None) data = {'price': '1'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1']) data = {'price': '1,2'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1', '2']) data = {'price': '1,,2'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1', '', '2']) data = {'price': '1,'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1', '']) data = {'price': ','} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['', '']) data = {'price': ''} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) result = w.value_from_datadict({}, {}, 'price') self.assertEqual(result, None) class CSVSelectTests(TestCase): class CSVSelect(BaseCSVWidget, Select): pass def test_widget(self): w = self.CSVSelect(choices=((1, 'a'), (2, 'b'))) self.assertHTMLEqual( w.render('price', None), """ """ ) self.assertHTMLEqual( w.render('price', ''), """ """) self.assertHTMLEqual( w.render('price', '1'), """ """) self.assertHTMLEqual( w.render('price', '1,2'), """ """ ) self.assertHTMLEqual(w.render('price', ['1', '2']), """ """) self.assertHTMLEqual(w.render('price', [1, 2]), """ """) class QueryArrayWidgetTests(TestCase): def test_widget_value_from_datadict(self): w = QueryArrayWidget() # Values can be provided as csv string: ?foo=bar,baz data = {'price': None} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) data = {'price': '1'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1']) data = {'price': '1,2'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(sorted(result), ['1', '2']) data = {'price': '1,,2'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(sorted(result), ['1', '2']) data = {'price': '1,'} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1']) data = {'price': ','} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) data = {'price': ''} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) result = w.value_from_datadict({}, {}, 'price') self.assertEqual(result, []) # Values can be provided as query array: ?foo[]=bar&foo[]=baz data = {'price[]': None} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) data = {'price[]': ['1']} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1']) data = {'price[]': ['1', '2']} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(sorted(result), ['1', '2']) data = {'price[]': ['1', '', '2']} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(sorted(result), ['1', '2']) data = {'price[]': ['1', '']} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, ['1']) data = {'price[]': ['', '']} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) data = {'price[]': []} result = w.value_from_datadict(data, {}, 'price') self.assertEqual(result, []) result = w.value_from_datadict({}, {}, 'price') self.assertEqual(result, []) django-filter-1.1.0/tests/urls.py0000644000076500000240000000060413172067725017536 0ustar carltonstaff00000000000000from __future__ import absolute_import, unicode_literals from django.conf.urls import url from django_filters.views import FilterView, object_filter from .models import Book def _foo(): return 'bar' urlpatterns = [ url(r'^books-legacy/$', object_filter, {'model': Book, 'extra_context': {'foo': _foo, 'bar': 'foo'}}), url(r'^books/$', FilterView.as_view(model=Book)), ]