django-filter-2.1.0/0000755000076500000240000000000013421151434015022 5ustar carltonstaff00000000000000django-filter-2.1.0/AUTHORS0000644000076500000240000000051112524657704016105 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-2.1.0/CHANGES.rst0000644000076500000240000002421013421147136016627 0ustar carltonstaff00000000000000Version 2.1 (2019-1-20) ----------------------- * Fixed a regression in ``FilterView`` introduced in 2.0. An empty ``QuerySet`` was incorrectly used whenever the FilterSet was unbound (i.e. when there were no GET parameters). The correct, pre-2.0 behaviour is now restored. A workaround was to set ``strict=False`` on the ``FilterSet``. This is no longer necessary, so you may restore `strict` behaviour as desired. * Added ``IsoDateTimeFromToRangeFilter``. Allows From-To filtering using ISO-8601 formatted dates. Version 2.0 (2018-7-13) ----------------------- 2.0 introduced a number of small changes and tidy-ups. Please see the migration guide: https://django-filter.readthedocs.io/en/master/guide/migration.html#migrating-to-2-0 * Added testing for Python 3.7 (#944) * Improve exception message for invalid filter result (#943) * Test QueryDict against CSV filters (#937) * Add `renderer` argument to `render()` method of `BooleanWidget` (#923) * Fix lookups for reverse relationships (#915) * Refactor backend filterset instantiation (#865) * Improve view-related attribute name consistency (#867) * Fix distinct call for range filters (#855) * Fix empty value check for CSV range (#854) * Rework DateRangeFilter (#852) * Added testing for Django 2.1 * Rework 'lookup types' handling into LookupChoiceFilter (#851) * Add linting and docs builds to CI (#850) * Use DRF BooleanFilter for NullBooleanField (#844) * Added Brazilian locale (#841) * List Django as a dependency in setup.py (#846) * Keep coverage reports files off version control. (#924) * Update migration docs (#866) * Added be, cs and uk translations. Updated de and ru (#861) * Slovak translation (#886) * Added Django 2.0 support. (#836) * Fix warnings build (#829) * Add greek translation (#827) * Replaced super(ClassName, self) with super() (#821) * Fixed doc URL in utils.deprecate(). (#820) * Added danish translation to django-filter (#809) * Rework validation, add queryset filter method (#788) * Fix Schema warnings (#803) * Update {Range,LookupType}Widgets to use suffixes (#770) * Method signature improvements (#800) * Remove more deprecations (#801) * Drop python 2, Django<1.11 support (#797) * Remove 'Meta.together' option (#791) * [2.x] Remove some deprecations (#795) Version 1.1 (2017-10-19) ------------------------ * Add Deprecations for 2.0 (#792) * Improve IsoDateTimeField test clarity (#790) * Fix form attr references in tests (#789) * Simplify tox config, drop python 3.3 & django 1.8 (#787) * Make get_filter_name a classmethod, allowing it to be overriden for each FilterClass (#775) * Support active timezone (#750) * Docs Typo: django_filters -> filters in docs (#773) * Add Polish translations for some messages (#771) * Remove support for Django 1.9 (EOL) (#752) * Use required attribute from field when getting schema fields (#766) * Prevent circular ImportError hiding for rest_framework sub-package (#741) * Deprecate 'extra' field attrs on Filter (#734) * Add SuffixedMultiWidget (#681) * Fix null filtering for *Choice filters (#680) * Use isort on imports (#761) * Use urlencode from django.utils.http (#760) * Remove OrderingFilter.help_text (#757) * Update DRF test dependency to 3.6 (#747) Version 1.0.4 (2017-05-19) -------------------------- 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-2.1.0/LICENSE0000644000076500000240000000271712410601557016041 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-2.1.0/MANIFEST.in0000644000076500000240000000054413065267357016603 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-2.1.0/PKG-INFO0000644000076500000240000001140113421151434016114 0ustar carltonstaff00000000000000Metadata-Version: 1.2 Name: django-filter Version: 2.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: Alex Gaynor Author-email: alex.gaynor@gmail.com Maintainer: Carlton Gibson Maintainer-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://dev.azure.com/noumenal/Django%20Filter/_apis/build/status/Django%20Filter-CI :target: https://dev.azure.com/noumenal/Django%20Filter/_build/latest?definitionId=3 .. 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**: 3.4, 3.5, 3.6, 3.7 * **Django**: 1.11, 2.0, 2.1, 2.2 * **DRF**: 3.8+ From Version 2.0 Django Filter is Python 3 only. If you need to support Python 2.7 use the version 1.1 release. 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/master/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/master/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.11 Classifier: Framework :: Django :: 2.0 Classifier: Framework :: Django :: 2.1 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Framework :: Django Requires-Python: >=3.4 django-filter-2.1.0/README.rst0000644000076500000240000000545013421107001016504 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://dev.azure.com/noumenal/Django%20Filter/_apis/build/status/Django%20Filter-CI :target: https://dev.azure.com/noumenal/Django%20Filter/_build/latest?definitionId=3 .. 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**: 3.4, 3.5, 3.6, 3.7 * **Django**: 1.11, 2.0, 2.1, 2.2 * **DRF**: 3.8+ From Version 2.0 Django Filter is Python 3 only. If you need to support Python 2.7 use the version 1.1 release. 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/master/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/master/guide/rest_framework.html django-filter-2.1.0/django_filter.egg-info/0000755000076500000240000000000013421151434021323 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filter.egg-info/PKG-INFO0000644000076500000240000001140113421151434022415 0ustar carltonstaff00000000000000Metadata-Version: 1.2 Name: django-filter Version: 2.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: Alex Gaynor Author-email: alex.gaynor@gmail.com Maintainer: Carlton Gibson Maintainer-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://dev.azure.com/noumenal/Django%20Filter/_apis/build/status/Django%20Filter-CI :target: https://dev.azure.com/noumenal/Django%20Filter/_build/latest?definitionId=3 .. 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**: 3.4, 3.5, 3.6, 3.7 * **Django**: 1.11, 2.0, 2.1, 2.2 * **DRF**: 3.8+ From Version 2.0 Django Filter is Python 3 only. If you need to support Python 2.7 use the version 1.1 release. 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/master/ .. _`mailing list`: http://groups.google.com/group/django-filter .. _`DRF integration docs`: https://django-filter.readthedocs.io/en/master/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.11 Classifier: Framework :: Django :: 2.0 Classifier: Framework :: Django :: 2.1 Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Framework :: Django Requires-Python: >=3.4 django-filter-2.1.0/django_filter.egg-info/SOURCES.txt0000644000076500000240000000636613421151434023222 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/requires.txt 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/be/LC_MESSAGES/django.mo django_filters/locale/be/LC_MESSAGES/django.po django_filters/locale/cs/LC_MESSAGES/django.mo django_filters/locale/cs/LC_MESSAGES/django.po django_filters/locale/da/LC_MESSAGES/django.mo django_filters/locale/da/LC_MESSAGES/django.po django_filters/locale/de/LC_MESSAGES/django.mo django_filters/locale/de/LC_MESSAGES/django.po django_filters/locale/el/LC_MESSAGES/django.mo django_filters/locale/el/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/pt_BR/LC_MESSAGES/django.mo django_filters/locale/pt_BR/LC_MESSAGES/django.po django_filters/locale/ru/LC_MESSAGES/django.mo django_filters/locale/ru/LC_MESSAGES/django.po django_filters/locale/sk/LC_MESSAGES/django.mo django_filters/locale/sk/LC_MESSAGES/django.po django_filters/locale/uk/LC_MESSAGES/django.mo django_filters/locale/uk/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_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/utils.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-2.1.0/django_filter.egg-info/dependency_links.txt0000644000076500000240000000000113421151434025371 0ustar carltonstaff00000000000000 django-filter-2.1.0/django_filter.egg-info/not-zip-safe0000644000076500000240000000000113421150663023554 0ustar carltonstaff00000000000000 django-filter-2.1.0/django_filter.egg-info/requires.txt0000644000076500000240000000001513421151434023717 0ustar carltonstaff00000000000000Django>=1.11 django-filter-2.1.0/django_filter.egg-info/top_level.txt0000644000076500000240000000001713421151434024053 0ustar carltonstaff00000000000000django_filters django-filter-2.1.0/django_filters/0000755000076500000240000000000013421151434020014 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/__init__.py0000644000076500000240000000120413421147136022126 0ustar carltonstaff00000000000000# flake8: noqa import pkgutil 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__ = '2.1.0' def parse_version(version): ''' '0.1.2.dev1' -> (0, 1, 2, 'dev1') '0.1.2' -> (0, 1, 2) ''' v = version.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-2.1.0/django_filters/compat.py0000644000076500000240000000104113173602216021650 0ustar carltonstaff00000000000000from django.conf import settings # 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 django-filter-2.1.0/django_filters/conf.py0000644000076500000240000000572313173602216021325 0ustar carltonstaff00000000000000from django.conf import settings as dj_settings from django.core.signals import setting_changed from django.utils.translation import ugettext_lazy as _ from .utils import deprecate DEFAULTS = { 'DISABLE_HELP_TEXT': False, # empty/null choices 'EMPTY_CHOICE_LABEL': '---------', 'NULL_CHOICE_LABEL': None, 'NULL_CHOICE_VALUE': 'null', '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 = [ ] 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-2.1.0/django_filters/constants.py0000644000076500000240000000010013173602216022374 0ustar carltonstaff00000000000000 ALL_FIELDS = '__all__' EMPTY_VALUES = ([], (), {}, '', None) django-filter-2.1.0/django_filters/exceptions.py0000644000076500000240000000037613322050061022547 0ustar carltonstaff00000000000000 from django.core.exceptions import FieldError class FieldLookupError(FieldError): def __init__(self, model_field, lookup_expr): super().__init__( "Unsupported lookup '%s' for field '%s'." % (lookup_expr, model_field) ) django-filter-2.1.0/django_filters/fields.py0000644000076500000240000002324713421034026021641 0ustar carltonstaff00000000000000from collections import namedtuple from datetime import datetime, time 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 .constants import EMPTY_VALUES from .utils import handle_timezone from .widgets import ( BaseCSVWidget, CSVWidget, DateRangeWidget, LookupChoiceWidget, RangeWidget ) class RangeField(forms.MultiValueField): widget = RangeWidget def __init__(self, fields=None, *args, **kwargs): if fields is None: fields = ( forms.DecimalField(), forms.DecimalField()) super().__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: return slice(*data_list) return None class DateRangeField(RangeField): widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( forms.DateField(), forms.DateField()) super().__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): widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( forms.DateTimeField(), forms.DateTimeField()) super().__init__(fields, *args, **kwargs) class IsoDateTimeRangeField(RangeField): widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( IsoDateTimeField(), IsoDateTimeField()) super().__init__(fields, *args, **kwargs) class TimeRangeField(RangeField): widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( forms.TimeField(), forms.TimeField()) super().__init__(fields, *args, **kwargs) class Lookup(namedtuple('Lookup', ('value', 'lookup_expr'))): def __new__(cls, value, lookup_expr): if value in EMPTY_VALUES or lookup_expr in EMPTY_VALUES: raise ValueError( "Empty values ([], (), {}, '', None) are not " "valid Lookup arguments. Return None instead." ) return super().__new__(cls, value, lookup_expr) class LookupChoiceField(forms.MultiValueField): default_error_messages = { 'lookup_required': _('Select a lookup.'), } def __init__(self, field, lookup_choices, *args, **kwargs): empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL) fields = (field, ChoiceField(choices=lookup_choices, empty_label=empty_label)) widget = LookupChoiceWidget(widgets=[f.widget for f in fields]) kwargs['widget'] = widget kwargs['help_text'] = field.help_text super().__init__(fields, *args, **kwargs) def compress(self, data_list): if len(data_list) == 2: value, lookup_expr = data_list if value not in EMPTY_VALUES: if lookup_expr not in EMPTY_VALUES: return Lookup(value=value, lookup_expr=lookup_expr) else: raise forms.ValidationError( self.error_messages['lookup_required'], code='lookup_required') return None 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().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().__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().clean(value) assert value is None or isinstance(value, list) if value 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().__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().__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().__init__(*args, **kwargs) def _get_choices(self): return super()._get_choices() def _set_choices(self, value): super()._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().__init__(*args, **kwargs) class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField): iterator = ChoiceIterator def __init__(self, *args, **kwargs): self.empty_label = None super().__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().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()._check_values(value)) result += [self.null_value] if null else [] return result django-filter-2.1.0/django_filters/filters.py0000644000076500000240000005772513421034026022053 0ustar carltonstaff00000000000000from 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.forms.utils import pretty_name from django.utils.itercompat import is_iterable from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from .conf import settings from .constants import EMPTY_VALUES from .fields import ( BaseCSVField, BaseRangeField, ChoiceField, DateRangeField, DateTimeRangeField, IsoDateTimeField, IsoDateTimeRangeField, LookupChoiceField, ModelChoiceField, ModelMultipleChoiceField, MultipleChoiceField, RangeField, TimeRangeField ) from .utils import get_model_field, label_for_filter __all__ = [ 'AllValuesFilter', 'AllValuesMultipleFilter', 'BaseCSVFilter', 'BaseInFilter', 'BaseRangeFilter', 'BooleanFilter', 'CharFilter', 'ChoiceFilter', 'DateFilter', 'DateFromToRangeFilter', 'DateRangeFilter', 'DateTimeFilter', 'DateTimeFromToRangeFilter', 'DurationFilter', 'Filter', 'IsoDateTimeFilter', 'IsoDateTimeFromToRangeFilter', 'LookupChoiceFilter', 'ModelChoiceFilter', 'ModelMultipleChoiceFilter', 'MultipleChoiceFilter', 'NumberFilter', 'NumericRangeFilter', 'OrderingFilter', 'RangeFilter', 'TimeFilter', 'TimeRangeFilter', 'TypedChoiceFilter', 'TypedMultipleChoiceFilter', 'UUIDFilter', ] class Filter(object): creation_counter = 0 field_class = forms.Field def __init__(self, field_name=None, lookup_expr='exact', *, label=None, method=None, distinct=False, exclude=False, **kwargs): self.field_name = field_name self.lookup_expr = lookup_expr self.label = label self.method = method self.distinct = distinct self.exclude = exclude self.extra = kwargs self.extra.setdefault('required', False) self.creation_counter = Filter.creation_counter Filter.creation_counter += 1 # TODO: remove assertion in 2.1 assert not isinstance(self.lookup_expr, (type(None), list)), \ "The `lookup_expr` argument no longer accepts `None` or a list of " \ "expressions. Use the `LookupChoiceFilter` instead. See: " \ "https://django-filter.readthedocs.io/en/master/guide/migration.html" 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 label(): def fget(self): if self._label is None and hasattr(self, 'model'): self._label = label_for_filter( self.model, self.field_name, self.lookup_expr, self.exclude ) return self._label def fset(self, value): self._label = value return locals() label = property(**label()) @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) self._field = self.field_class(label=self.label, **field_kwargs) return self._field def filter(self, qs, value): if value in EMPTY_VALUES: return qs if self.distinct: qs = qs.distinct() lookup = '%s__%s' % (self.field_name, self.lookup_expr) qs = self.get_method(qs)(**{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().__init__(*args, **kwargs) def filter(self, qs, value): if value != self.null_value: return super().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().__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 formatted 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().__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().field class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter): field_class = ModelChoiceField def __init__(self, *args, **kwargs): kwargs.setdefault('empty_label', settings.EMPTY_CHOICE_LABEL) super().__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: value = (value.start, value.stop) elif value.start is not None: self.lookup_expr = 'startswith' value = value.start elif value.stop is not None: self.lookup_expr = 'endswith' value = value.stop return super().filter(qs, value) 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: self.lookup_expr = 'range' value = (value.start, value.stop) elif value.start is not None: self.lookup_expr = 'gte' value = value.start elif value.stop is not None: self.lookup_expr = 'lte' value = value.stop return super().filter(qs, value) def _truncate(dt): return dt.date() class DateRangeFilter(ChoiceFilter): choices = [ ('today', _('Today')), ('yesterday', _('Yesterday')), ('week', _('Past 7 days')), ('month', _('This month')), ('year', _('This year')), ] filters = { 'today': lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, '%s__month' % name: now().month, '%s__day' % name: now().day }), 'yesterday': lambda qs, name: qs.filter(**{ '%s__year' % name: (now() - timedelta(days=1)).year, '%s__month' % name: (now() - timedelta(days=1)).month, '%s__day' % name: (now() - timedelta(days=1)).day, }), 'week': lambda qs, name: qs.filter(**{ '%s__gte' % name: _truncate(now() - timedelta(days=7)), '%s__lt' % name: _truncate(now() + timedelta(days=1)), }), 'month': lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, '%s__month' % name: now().month }), 'year': lambda qs, name: qs.filter(**{ '%s__year' % name: now().year, }), } def __init__(self, choices=None, filters=None, *args, **kwargs): if choices is not None: self.choices = choices if filters is not None: self.filters = filters unique = set([x[0] for x in self.choices]) ^ set(self.filters) assert not unique, \ "Keys must be present in both 'choices' and 'filters'. Missing keys: " \ "'%s'" % ', '.join(sorted(unique)) # TODO: remove assertion in 2.1 assert not hasattr(self, 'options'), \ "The 'options' attribute has been replaced by 'choices' and 'filters'. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" # null choice not relevant kwargs.setdefault('null_label', None) super().__init__(choices=self.choices, *args, **kwargs) def filter(self, qs, value): if not value: return qs assert value in self.filters qs = self.filters[value](qs, self.field_name) return qs.distinct() if self.distinct else qs class DateFromToRangeFilter(RangeFilter): field_class = DateRangeField class DateTimeFromToRangeFilter(RangeFilter): field_class = DateTimeRangeField class IsoDateTimeFromToRangeFilter(RangeFilter): field_class = IsoDateTimeRangeField 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().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().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().__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().__init__(*args, **kwargs) class BaseRangeFilter(BaseCSVFilter): base_field_class = BaseRangeField def __init__(self, *args, **kwargs): kwargs.setdefault('lookup_expr', 'range') super().__init__(*args, **kwargs) class LookupChoiceFilter(Filter): """ A combined filter that allows users to select the lookup expression from a dropdown. * ``lookup_choices`` is an optional argument that accepts multiple input formats, and is ultimately normlized as the choices used in the lookup dropdown. See ``.get_lookup_choices()`` for more information. * ``field_class`` is an optional argument that allows you to set the inner form field class used to validate the value. Default: ``forms.CharField`` ex:: price = django_filters.LookupChoiceFilter( field_class=forms.DecimalField, lookup_choices=[ ('exact', 'Equals'), ('gt', 'Greater than'), ('lt', 'Less than'), ] ) """ field_class = forms.CharField outer_class = LookupChoiceField def __init__(self, field_name=None, lookup_choices=None, field_class=None, **kwargs): self.empty_label = kwargs.pop('empty_label', settings.EMPTY_CHOICE_LABEL) super(LookupChoiceFilter, self).__init__(field_name=field_name, **kwargs) self.lookup_choices = lookup_choices if field_class is not None: self.field_class = field_class @classmethod def normalize_lookup(cls, lookup): """ Normalize the lookup into a tuple of ``(lookup expression, display value)`` If the ``lookup`` is already a tuple, the tuple is not altered. If the ``lookup`` is a string, a tuple is returned with the lookup expression used as the basis for the display value. ex:: >>> LookupChoiceFilter.normalize_lookup(('exact', 'Equals')) ('exact', 'Equals') >>> LookupChoiceFilter.normalize_lookup('has_key') ('has_key', 'Has key') """ if isinstance(lookup, str): return (lookup, pretty_name(lookup)) return (lookup[0], lookup[1]) def get_lookup_choices(self): """ Get the lookup choices in a format suitable for ``django.forms.ChoiceField``. If the filter is initialized with ``lookup_choices``, this value is normalized and passed to the underlying ``LookupChoiceField``. If no choices are provided, they are generated from the corresponding model field's registered lookups. """ lookups = self.lookup_choices if lookups is None: field = get_model_field(self.model, self.field_name) lookups = field.get_lookups() return [self.normalize_lookup(l) for l in lookups] @property def field(self): if not hasattr(self, '_field'): inner_field = super().field lookups = self.get_lookup_choices() self._field = self.outer_class( inner_field, lookups, label=self.label, empty_label=self.empty_label, required=self.extra['required'], ) return self._field def filter(self, qs, lookup): if not lookup: return super(LookupChoiceFilter, self).filter(qs, None) self.lookup_expr = lookup.lookup_expr return super(LookupChoiceFilter, self).filter(qs, lookup.value) 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().__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, str) 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, str) 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-2.1.0/django_filters/filterset.py0000644000076500000240000003731113421034002022363 0ustar carltonstaff00000000000000import 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 ( ManyToManyRel, ManyToOneRel, OneToOneRel ) from .conf import settings from .constants import ALL_FIELDS from .filters import ( BaseInFilter, BaseRangeFilter, BooleanFilter, CharFilter, ChoiceFilter, DateFilter, DateTimeFilter, DurationFilter, Filter, ModelChoiceFilter, ModelMultipleChoiceFilter, NumberFilter, TimeFilter, UUIDFilter ) from .utils import ( get_all_model_fields, get_model_field, resolve_field, try_dbfield ) def remote_queryset(field): """ Get the queryset for the other side of a relationship. This works for both `RelatedField`s and `ForignObjectRel`s. """ model = field.related_model # Reverse relationships do not have choice limits if not hasattr(field, 'get_limit_choices_to'): return model._default_manager.all() limit_choices_to = field.get_limit_choices_to() return model._default_manager.complex_filter(limit_choices_to) 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.form = getattr(options, 'form', forms.Form) class FilterSetMetaclass(type): def __new__(cls, name, bases, attrs): attrs['declared_filters'] = cls.get_declared_filters(bases, attrs) new_class = super().__new__(cls, name, bases, attrs) new_class._meta = FilterSetOptions(getattr(new_class, 'Meta', None)) new_class.base_filters = new_class.get_filters() # TODO: remove assertion in 2.1 assert not hasattr(new_class, 'filter_for_reverse_field'), ( "`%(cls)s.filter_for_reverse_field` has been removed. " "`%(cls)s.filter_for_field` now generates filters for reverse fields. " "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" % {'cls': new_class.__name__} ) 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}, # Forward relationships models.OneToOneField: { 'filter_class': ModelChoiceFilter, 'extra': lambda f: { 'queryset': remote_queryset(f), 'to_field_name': f.remote_field.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': f.remote_field.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), } }, # Reverse relationships OneToOneRel: { 'filter_class': ModelChoiceFilter, 'extra': lambda f: { 'queryset': remote_queryset(f), 'null_label': settings.NULL_CHOICE_LABEL if f.null else None, } }, ManyToOneRel: { 'filter_class': ModelMultipleChoiceFilter, 'extra': lambda f: { 'queryset': remote_queryset(f), } }, ManyToManyRel: { '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, *, request=None, prefix=None): if queryset is None: queryset = self._meta.model._default_manager.all() model = queryset.model self.is_bound = data is not None self.data = data or {} self.queryset = queryset self.request = request self.form_prefix = prefix self.filters = copy.deepcopy(self.base_filters) # propagate the model and filterset to the filters for filter_ in self.filters.values(): filter_.model = model filter_.parent = self def is_valid(self): """ Return True if the underlying form has no errors, or False otherwise. """ return self.is_bound and self.form.is_valid() @property def errors(self): """ Return an ErrorDict for the data provided for the underlying form. """ return self.form.errors def filter_queryset(self, queryset): """ Filter the queryset with the underlying form's `cleaned_data`. You must call `is_valid()` or `errors` before calling this method. This method should be overridden if additional filtering needs to be applied to the queryset before it is cached. """ for name, value in self.form.cleaned_data.items(): queryset = self.filters[name].filter(queryset, value) assert isinstance(queryset, models.QuerySet), \ "Expected '%s.%s' to return a QuerySet, but got a %s instead." \ % (type(self).__name__, name, type(queryset).__name__) return queryset @property def qs(self): if not hasattr(self, '_qs'): qs = self.queryset.all() if self.is_bound: # ensure form validation before filtering self.errors qs = self.filter_queryset(qs) self._qs = qs return self._qs def get_form_class(self): """ Returns a django Form suitable of validating the filterset data. This method should be overridden if the form class needs to be customized relative to the filterset instance. """ fields = OrderedDict([ (name, filter_.field) for name, filter_ in self.filters.items()]) return type(str('%sForm' % self.__class__.__name__), (self._meta.form,), fields) @property def form(self): if not hasattr(self, '_form'): Form = self.get_form_class() 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) 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, field, field_name, lookup_expr='exact'): field, lookup_type = resolve_field(field, lookup_expr) default = { 'field_name': field_name, 'lookup_expr': lookup_expr, } filter_class, params = cls.filter_for_lookup(field, 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/master/ref/filterset.html" "#customise-filter-generation-with-filter-overrides" ) % (cls.__name__, field_name, lookup_expr, field.__class__.__name__) return filter_class(**default) @classmethod def filter_for_lookup(cls, field, lookup_type): DEFAULTS = dict(cls.FILTER_DEFAULTS) if hasattr(cls, '_meta'): DEFAULTS.update(cls._meta.filter_overrides) data = try_dbfield(DEFAULTS.get, field.__class__) or {} filter_class = data.get('filter_class') params = data.get('extra', lambda field: {})(field) # if there is no filter class, exit early if not filter_class: return None, {} # perform lookup specific checks if lookup_type == 'exact' and getattr(field, 'choices', None): return ChoiceFilter, {'choices': field.choices} if lookup_type == 'isnull': data = try_dbfield(DEFAULTS.get, models.BooleanField) filter_class = data.get('filter_class') params = data.get('extra', lambda field: {})(field) 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(BaseFilterSet, metaclass=FilterSetMetaclass): 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-2.1.0/django_filters/locale/0000755000076500000240000000000013421151434021253 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/be/0000755000076500000240000000000013421151434021641 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/be/LC_MESSAGES/0000755000076500000240000000000013421151434023426 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/be/LC_MESSAGES/django.mo0000644000076500000240000000535413322072216025234 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw[x^)AC $   " - F [ x          64 k z       %# "  )(& * !$'%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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2018-01-24 18:51+0500 PO-Revision-Date: 2016-09-29 11:47+0300 Last-Translator: Eugena Mikhaylikova Language-Team: TextTempearture Language: be 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-2.1.0/django_filters/locale/be/LC_MESSAGES/django.po0000644000076500000240000000700013322072216025225 0ustar carltonstaff00000000000000# #: conf.py:27 conf.py:28 conf.py:41 msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-24 18:51+0500\n" "PO-Revision-Date: 2016-09-29 11:47+0300\n" "Last-Translator: Eugena Mikhaylikova \n" "Language-Team: TextTempearture\n" "Language: be\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" #: conf.py:17 msgid "date" msgstr "дата" #: conf.py:18 msgid "year" msgstr "год" #: conf.py:19 msgid "month" msgstr "месяц" #: conf.py:20 msgid "day" msgstr "дзень" #: conf.py:21 msgid "week day" msgstr "дзень тыдня" #: conf.py:22 msgid "hour" msgstr "гадзіну" #: conf.py:23 msgid "minute" msgstr "хвіліна" #: conf.py:24 msgid "second" msgstr "секунда" #: conf.py:29 conf.py:30 msgid "contains" msgstr "змяшчае" #: conf.py:31 msgid "is in" msgstr "у" #: conf.py:32 msgid "is greater than" msgstr "больш чым" #: conf.py:33 msgid "is greater than or equal to" msgstr "больш або роўна" #: conf.py:34 msgid "is less than" msgstr "менш чым" #: conf.py:35 msgid "is less than or equal to" msgstr "менш або роўна" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "пачынаецца" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "заканчваецца" #: conf.py:40 msgid "is in range" msgstr "у дыяпазоне" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "адпавядае рэгулярнаму выразу" #: conf.py:44 conf.py:52 msgid "search" msgstr "пошук" #: conf.py:47 msgid "is contained by" msgstr "змяшчаецца ў" #: conf.py:48 msgid "overlaps" msgstr "перакрываецца" #: conf.py:49 msgid "has key" msgstr "мае ключ" #: conf.py:50 msgid "has keys" msgstr "мае ключы" #: conf.py:51 msgid "has any keys" msgstr "мае любыя ключы" #: fields.py:178 msgid "Range query expects two values." msgstr "Запыт дыяпазону чакае два значэння." #: filters.py:429 msgid "Any date" msgstr "Любая дата" #: filters.py:430 msgid "Today" msgstr "Сёння" #: filters.py:435 msgid "Past 7 days" msgstr "Мінулыя 7 дзён" #: filters.py:439 msgid "This month" msgstr "За гэты месяц" #: filters.py:443 msgid "This year" msgstr "У гэтым годзе" #: filters.py:446 msgid "Yesterday" msgstr "Учора" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Некалькі значэнняў могуць быць падзеленыя коскамі." #: filters.py:591 #, python-format msgid "%s (descending)" msgstr "%s (па змяншэнні)" #: filters.py:607 msgid "Ordering" msgstr "Парадак" #: rest_framework/filterset.py:30 #: 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 msgid "Field filters" msgstr "Фільтры па палях" #: utils.py:224 msgid "exclude" msgstr "выключаючы" #: widgets.py:57 msgid "All" msgstr "Усе" #: widgets.py:159 msgid "Unknown" msgstr "Не было прапанавана" #: widgets.py:160 msgid "Yes" msgstr "Ды" #: widgets.py:161 msgid "No" msgstr "Няма" django-filter-2.1.0/django_filters/locale/cs/0000755000076500000240000000000013421151434021660 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/cs/LC_MESSAGES/0000755000076500000240000000000013421151434023445 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/cs/LC_MESSAGES/django.mo0000644000076500000240000000446413322072216025254 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw,;DU$g&    $ ,8 M Xd k y      # 0  %# "  )(& * !$'%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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2018-01-24 11:03+0500 PO-Revision-Date: 2016-09-29 11:47+0300 Last-Translator: Eugena Mikhaylikova Language-Team: TextTempearture Language: cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; X-Generator: Poedit 1.8.9 %s (sestupně)VšechnoJakékoliv datumFiltry na políchVíce hodnot lze oddělit čárkami.NeŘád zPosledních 7 dníRozsah dotazu očekává dvě hodnoty.OdeslatTento měsícTento rokDnesNení nastavenoAnoVčeraobsahujedatumdenkončís výjimkoumá nějaké klíčemá klíčmá klíčehodinuje obsažen vvíce nežvětší nebo rovenvv rozsahuméně nežmenší nebo rovnéodpovídá normálnímu výrazuminutuměsícpřekrývajívyhledávánívteřinazačínáden v týdnurokdjango-filter-2.1.0/django_filters/locale/cs/LC_MESSAGES/django.po0000644000076500000240000000602013322072216025245 0ustar carltonstaff00000000000000# msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-24 11:03+0500\n" "PO-Revision-Date: 2016-09-29 11:47+0300\n" "Last-Translator: Eugena Mikhaylikova \n" "Language-Team: TextTempearture\n" "Language: cs\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>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Poedit 1.8.9\n" #: conf.py:17 msgid "date" msgstr "datum" #: conf.py:18 msgid "year" msgstr "rok" #: conf.py:19 msgid "month" msgstr "měsíc" #: conf.py:20 msgid "day" msgstr "den" #: conf.py:21 msgid "week day" msgstr "den v týdnu" #: conf.py:22 msgid "hour" msgstr "hodinu" #: conf.py:23 msgid "minute" msgstr "minutu" #: conf.py:24 msgid "second" msgstr "vteřina" #: conf.py:29 conf.py:30 msgid "contains" msgstr "obsahuje" #: conf.py:31 msgid "is in" msgstr "v" #: conf.py:32 msgid "is greater than" msgstr "více než" #: conf.py:33 msgid "is greater than or equal to" msgstr "větší nebo roven" #: conf.py:34 msgid "is less than" msgstr "méně než" #: conf.py:35 msgid "is less than or equal to" msgstr "menší nebo rovné" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "začíná" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "končí" #: conf.py:40 msgid "is in range" msgstr "v rozsahu" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "odpovídá normálnímu výrazu" #: conf.py:44 conf.py:52 msgid "search" msgstr "vyhledávání" #: conf.py:47 msgid "is contained by" msgstr "je obsažen v" #: conf.py:48 msgid "overlaps" msgstr "překrývají" #: conf.py:49 msgid "has key" msgstr "má klíč" #: conf.py:50 msgid "has keys" msgstr "má klíče" #: conf.py:51 msgid "has any keys" msgstr "má nějaké klíče" #: fields.py:178 msgid "Range query expects two values." msgstr "Rozsah dotazu očekává dvě hodnoty." #: filters.py:429 msgid "Any date" msgstr "Jakékoliv datum" #: filters.py:430 msgid "Today" msgstr "Dnes" #: filters.py:435 msgid "Past 7 days" msgstr "Posledních 7 dní" #: filters.py:439 msgid "This month" msgstr "Tento měsíc" #: filters.py:443 msgid "This year" msgstr "Tento rok" #: filters.py:446 msgid "Yesterday" msgstr "Včera" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Více hodnot lze oddělit čárkami." #: filters.py:591 msgid "%s (descending)" msgstr "%s (sestupně)" #: filters.py:607 msgid "Ordering" msgstr "Řád z" #: rest_framework/filterset.py:30 #: templates/django_filters/rest_framework/form.html:5 msgid "Submit" msgstr "Odeslat" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 msgid "Field filters" msgstr "Filtry na polích" #: utils.py:224 msgid "exclude" msgstr "s výjimkou" #: widgets.py:57 msgid "All" msgstr "Všechno" #: widgets.py:159 msgid "Unknown" msgstr "Není nastaveno" #: widgets.py:160 msgid "Yes" msgstr "Ano" #: widgets.py:161 msgid "No" msgstr "Ne" django-filter-2.1.0/django_filters/locale/da/0000755000076500000240000000000013421151434021637 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/da/LC_MESSAGES/0000755000076500000240000000000013421151434023424 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/da/LC_MESSAGES/django.mo0000644000076500000240000000412513322050061025217 0ustar carltonstaff00000000000000(\5pq+     #,1 5? GT\ejz     %:*ety'  .  &) 0;@ DPW t     '27 >JQ (%   &!' $"#%s (descending)AllAny dateMultiple values may be separated by commas.NoOrderingPast 7 daysRange query expects two values.This 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 dayyearMIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 2.0.1 Project-Id-Version: django-filter Language: da Last-Translator: Danni Randeris Language-Team: Danni Randeris POT-Creation-Date: PO-Revision-Date: %s (aftagende)AlleHvilken som helst dagFlere værdier kan adskilles via komma.NejSorteringSidste 7 dageInterval forespørgslen forventer to værdier.Denne månedDette årI dagUkendtJaI gårindeholderdatodagslutter medudeladhar hvilken som helst stringhar stringhar stringetimeer indeholdt afer større ender større end eller lig meder ier i intervalleter mindre ender mindre end eller lig medmatcher regexminutmånedoverlappersøgsekundstarter medugedagårdjango-filter-2.1.0/django_filters/locale/da/LC_MESSAGES/django.po0000644000076500000240000000565613322050061025234 0ustar carltonstaff00000000000000msgid "" msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.0.1\n" "Project-Id-Version: django-filter\n" "Language: da\n" "Last-Translator: Danni Randeris \n" "Language-Team: Danni Randeris \n" "POT-Creation-Date: 2017-10-28\n" "PO-Revision-Date: 2017-10-28\n" #: conf.py:17 msgid "date" msgstr "dato" #: conf.py:18 msgid "year" msgstr "år" #: conf.py:19 msgid "month" msgstr "måned" #: conf.py:20 msgid "day" msgstr "dag" #: conf.py:21 msgid "week day" msgstr "ugedag" #: conf.py:22 msgid "hour" msgstr "time" #: conf.py:23 msgid "minute" msgstr "minut" #: conf.py:24 msgid "second" msgstr "sekund" #: conf.py:29 conf.py:30 msgid "contains" msgstr "indeholder" #: conf.py:31 msgid "is in" msgstr "er i" #: conf.py:32 msgid "is greater than" msgstr "er større end" #: conf.py:33 msgid "is greater than or equal to" msgstr "er større end eller lig med" #: conf.py:34 msgid "is less than" msgstr "er mindre end" #: conf.py:35 msgid "is less than or equal to" msgstr "er mindre end eller lig med" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "starter med" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "slutter med" #: conf.py:40 msgid "is in range" msgstr "er i intervallet" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "matcher regex" #: conf.py:44 conf.py:52 msgid "search" msgstr "søg" #: conf.py:47 msgid "is contained by" msgstr "er indeholdt af" #: conf.py:48 msgid "overlaps" msgstr "overlapper" #: conf.py:49 msgid "has key" msgstr "har string" #: conf.py:50 msgid "has keys" msgstr "har stringe" #: conf.py:51 msgid "has any keys" msgstr "har hvilken som helst string" #: fields.py:178 msgid "Range query expects two values." msgstr "Interval forespørgslen forventer to værdier." #: filters.py:429 msgid "Any date" msgstr "Hvilken som helst dag" #: filters.py:430 msgid "Today" msgstr "I dag" #: filters.py:435 msgid "Past 7 days" msgstr "Sidste 7 dage" #: filters.py:439 msgid "This month" msgstr "Denne måned" #: filters.py:443 msgid "This year" msgstr "Dette år" #: filters.py:446 msgid "Yesterday" msgstr "I går" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Flere værdier kan adskilles via komma." #: filters.py:591 msgid "%s (descending)" msgstr "%s (aftagende)" #: filters.py:607 msgid "Ordering" msgstr "Sortering" #: rest_framework/filterset.py:30 #: templates/django_filters/rest_framework/form.html:5 #, fuzzy msgid "Submit" msgstr "Indsend" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 #, fuzzy msgid "Field filters" msgstr "Felt filtre" #: utils.py:224 msgid "exclude" msgstr "udelad" #: widgets.py:57 msgid "All" msgstr "Alle" #: widgets.py:159 msgid "Unknown" msgstr "Ukendt" #: widgets.py:160 msgid "Yes" msgstr "Ja" #: widgets.py:161 msgid "No" msgstr "Nej" django-filter-2.1.0/django_filters/locale/de/0000755000076500000240000000000013421151434021643 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/de/LC_MESSAGES/0000755000076500000240000000000013421151434023430 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/de/LC_MESSAGES/django.mo0000644000076500000240000000437213322072216025235 0ustar carltonstaff00000000000000)d; + ( / :DJR V`i nx    %,2;B IU^c  1AF N(\      &->Oho    $"!  ('% )  #&%s (descending)AllAny dateField filtersMultiple values may be separated by commas.NoOrderingPast 7 daysRange query expects two values.SubmitThis monthThis yearTodayUnknownYesYesterdaycontainsdateends 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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2018-01-24 11:03+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 DatenFeldfilterMehrere Werte können durch Kommas getrennt sein.NeinOrdnungLetzte 7 TageDie Bereichsabfrage erwartet zwei Werte.EinreichenDiesen MonatDieses JahrHeuteUnbekannteJaGesternenthältDatenendet mitausschließenhat irgendwelche Schlüsselhat Schlüsselhat SchlüsselStundeist enthalten inist größer alsist größer oder gleichist inist in Reichweiteist kleiner alsist kleiner oder gleichpasst auf RegexMinuteMonatüberlappenSuchezweitebeginnt mitWochentagJahrdjango-filter-2.1.0/django_filters/locale/de/LC_MESSAGES/django.po0000644000076500000240000000637513322072216025245 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:27 conf.py:28 conf.py:41 msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-24 11:03+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" #: conf.py:17 msgid "date" msgstr "Daten" #: conf.py:18 msgid "year" msgstr "Jahr" #: conf.py:19 msgid "month" msgstr "Monat" #: conf.py:20 #, fuzzy #| msgid "Today" msgid "day" msgstr "Heute" #: conf.py:21 msgid "week day" msgstr "Wochentag" #: conf.py:22 msgid "hour" msgstr "Stunde" #: conf.py:23 msgid "minute" msgstr "Minute" #: conf.py:24 msgid "second" msgstr "zweite" #: conf.py:29 conf.py:30 msgid "contains" msgstr "enthält" #: conf.py:31 msgid "is in" msgstr "ist in" #: conf.py:32 msgid "is greater than" msgstr "ist größer als" #: conf.py:33 msgid "is greater than or equal to" msgstr "ist größer oder gleich" #: conf.py:34 msgid "is less than" msgstr "ist kleiner als" #: conf.py:35 msgid "is less than or equal to" msgstr "ist kleiner oder gleich" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "beginnt mit" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "endet mit" #: conf.py:40 msgid "is in range" msgstr "ist in Reichweite" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "passt auf Regex" #: conf.py:44 conf.py:52 msgid "search" msgstr "Suche" #: conf.py:47 msgid "is contained by" msgstr "ist enthalten in" #: conf.py:48 msgid "overlaps" msgstr "überlappen" #: conf.py:49 msgid "has key" msgstr "hat Schlüssel" #: conf.py:50 msgid "has keys" msgstr "hat Schlüssel" #: conf.py:51 msgid "has any keys" msgstr "hat irgendwelche Schlüssel" #: fields.py:178 msgid "Range query expects two values." msgstr "Die Bereichsabfrage erwartet zwei Werte." #: filters.py:429 msgid "Any date" msgstr "Alle Daten" #: filters.py:430 msgid "Today" msgstr "Heute" #: filters.py:435 msgid "Past 7 days" msgstr "Letzte 7 Tage" #: filters.py:439 msgid "This month" msgstr "Diesen Monat" #: filters.py:443 msgid "This year" msgstr "Dieses Jahr" #: filters.py:446 msgid "Yesterday" msgstr "Gestern" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Mehrere Werte können durch Kommas getrennt sein." #: filters.py:591 #, python-format msgid "%s (descending)" msgstr "%s (absteigend)" #: filters.py:607 msgid "Ordering" msgstr "Ordnung" #: rest_framework/filterset.py:30 #: templates/django_filters/rest_framework/form.html:5 msgid "Submit" msgstr "Einreichen" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 msgid "Field filters" msgstr "Feldfilter" #: utils.py:224 msgid "exclude" msgstr "ausschließen" #: widgets.py:57 msgid "All" msgstr "Alle" #: widgets.py:159 msgid "Unknown" msgstr "Unbekannte" #: widgets.py:160 msgid "Yes" msgstr "Ja" #: widgets.py:161 msgid "No" msgstr "Nein" django-filter-2.1.0/django_filters/locale/el/0000755000076500000240000000000013421151434021653 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/el/LC_MESSAGES/0000755000076500000240000000000013421151434023440 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/el/LC_MESSAGES/django.mo0000644000076500000240000000547513322050061025244 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw +@`Z.BIXo  . = X u | & 0  ) $5 .Z      $ 4  %# "  )(& * !$'%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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2017-11-16 10:06+0200 PO-Revision-Date: 2017-11-16 10:04+0200 Last-Translator: Serafeim Papastefanos Language-Team: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language: de Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.5 %s (φθίνουσαΌλαΟποιαδήποτε ημερομηνίαΦίλτρα πεδίωνΟι πολλαπλές τιμές πρέπει να διαχωρίζονται με κόμμα.ΌχιΤαξινόμησηΤις προηγούμενες 7 ημέρεςΤο ερώτημα εύρους απαιτεί δύο τιμές,ΥποβολήΑυτό το μήναΑυτό το έτοςΣήμεραΆγνωστοΝαιΧτεςπεριέχειημερομηνίαημέρατελειώνει μεαπέκλεισεέχει οποιαδήποτε κλειδιάέχει το κλειδίέχει τα κλειδιάώραπεριέχεται σεείναι μεγαλύτερο απόείναι μεγαλύτερο ή ίσο τουείναι εντός τωνείναι εντος του εύρουςείναι μικρότερο απόείναι μικρότερο ή ίσο τουπεριέχει regexλεπτόμήναςεπικαλύπτεταιαναζήτησηδευτερόλεπτοξεκινά μεημέρα της εβδομάδαςέτοςdjango-filter-2.1.0/django_filters/locale/el/LC_MESSAGES/django.po0000644000076500000240000000757613322050061025253 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. # Serafeim Papastefanos , 2017. # #: .\conf.py:27 .\conf.py:28 .\conf.py:41 msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-11-16 10:06+0200\n" "PO-Revision-Date: 2017-11-16 10:04+0200\n" "Last-Translator: Serafeim Papastefanos \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.5\n" #: .\conf.py:17 msgid "date" msgstr "ημερομηνία" #: .\conf.py:18 msgid "year" msgstr "έτος" #: .\conf.py:19 msgid "month" msgstr "μήνας" #: .\conf.py:20 msgid "day" msgstr "ημέρα" #: .\conf.py:21 msgid "week day" msgstr "ημέρα της εβδομάδας" #: .\conf.py:22 msgid "hour" msgstr "ώρα" #: .\conf.py:23 msgid "minute" msgstr "λεπτό" #: .\conf.py:24 msgid "second" msgstr "δευτερόλεπτο" #: .\conf.py:29 .\conf.py:30 msgid "contains" msgstr "περιέχει" #: .\conf.py:31 msgid "is in" msgstr "είναι εντός των" #: .\conf.py:32 msgid "is greater than" msgstr "είναι μεγαλύτερο από" #: .\conf.py:33 msgid "is greater than or equal to" msgstr "είναι μεγαλύτερο ή ίσο του" #: .\conf.py:34 msgid "is less than" msgstr "είναι μικρότερο από" #: .\conf.py:35 msgid "is less than or equal to" msgstr "είναι μικρότερο ή ίσο του" #: .\conf.py:36 .\conf.py:37 msgid "starts with" msgstr "ξεκινά με" #: .\conf.py:38 .\conf.py:39 msgid "ends with" msgstr "τελειώνει με" #: .\conf.py:40 msgid "is in range" msgstr "είναι εντος του εύρους" #: .\conf.py:42 .\conf.py:43 msgid "matches regex" msgstr "περιέχει regex" #: .\conf.py:44 .\conf.py:52 msgid "search" msgstr "αναζήτηση" #: .\conf.py:47 msgid "is contained by" msgstr "περιέχεται σε" #: .\conf.py:48 msgid "overlaps" msgstr "επικαλύπτεται" #: .\conf.py:49 msgid "has key" msgstr "έχει το κλειδί" #: .\conf.py:50 msgid "has keys" msgstr "έχει τα κλειδιά" #: .\conf.py:51 msgid "has any keys" msgstr "έχει οποιαδήποτε κλειδιά" #: .\fields.py:178 msgid "Range query expects two values." msgstr "Το ερώτημα εύρους απαιτεί δύο τιμές," #: .\filters.py:429 msgid "Any date" msgstr "Οποιαδήποτε ημερομηνία" #: .\filters.py:430 msgid "Today" msgstr "Σήμερα" #: .\filters.py:435 msgid "Past 7 days" msgstr "Τις προηγούμενες 7 ημέρες" #: .\filters.py:439 msgid "This month" msgstr "Αυτό το μήνα" #: .\filters.py:443 msgid "This year" msgstr "Αυτό το έτος" #: .\filters.py:446 msgid "Yesterday" msgstr "Χτες" #: .\filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Οι πολλαπλές τιμές πρέπει να διαχωρίζονται με κόμμα." #: .\filters.py:591 #, python-format msgid "%s (descending)" msgstr "%s (φθίνουσα" #: .\filters.py:607 msgid "Ordering" msgstr "Ταξινόμηση" #: .\rest_framework\filterset.py:30 #: .\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 msgid "Field filters" msgstr "Φίλτρα πεδίων" #: .\utils.py:224 msgid "exclude" msgstr "απέκλεισε" #: .\widgets.py:57 msgid "All" msgstr "Όλα" #: .\widgets.py:159 msgid "Unknown" msgstr "Άγνωστο" #: .\widgets.py:160 msgid "Yes" msgstr "Ναι" #: .\widgets.py:161 msgid "No" msgstr "Όχι" django-filter-2.1.0/django_filters/locale/es_AR/0000755000076500000240000000000013421151434022244 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/es_AR/LC_MESSAGES/0000755000076500000240000000000013421151434024031 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/es_AR/LC_MESSAGES/django.mo0000644000076500000240000000135012643540653025641 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-2.1.0/django_filters/locale/es_AR/LC_MESSAGES/django.po0000644000076500000240000000176712643540653025660 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-2.1.0/django_filters/locale/es_ES/0000755000076500000240000000000013421151434022251 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/es_ES/LC_MESSAGES/0000755000076500000240000000000013421151434024036 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/es_ES/LC_MESSAGES/django.mo0000644000076500000240000000430713065267357025661 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-2.1.0/django_filters/locale/es_ES/LC_MESSAGES/django.po0000644000076500000240000000610713065267357025664 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-2.1.0/django_filters/locale/fr/0000755000076500000240000000000013421151434021662 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/fr/LC_MESSAGES/0000755000076500000240000000000013421151434023447 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/fr/LC_MESSAGES/django.mo0000644000076500000240000000140012411012542025233 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-2.1.0/django_filters/locale/fr/LC_MESSAGES/django.po0000644000076500000240000000205312411012542025243 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-2.1.0/django_filters/locale/pl/0000755000076500000240000000000013421151434021666 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/pl/LC_MESSAGES/0000755000076500000240000000000013421151434023453 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/pl/LC_MESSAGES/django.mo0000644000076500000240000000200312643540653025257 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-2.1.0/django_filters/locale/pl/LC_MESSAGES/django.po0000644000076500000240000000667413172067725025306 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-2.1.0/django_filters/locale/pt_BR/0000755000076500000240000000000013421151434022261 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/pt_BR/LC_MESSAGES/0000755000076500000240000000000013421151434024046 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/pt_BR/LC_MESSAGES/django.mo0000644000076500000240000000440013322072216025643 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irfw 5INW'g   #( :G ]i x!    %# "  )(& * !$'%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-12-11 22:04+0100 PO-Revision-Date: 2017-12-11 22:07-0200 Last-Translator: Anderson Scouto da Silva Language-Team: Language: pt_BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Generator: Poedit 1.8.13 Plural-Forms: nplurals=2; plural=(n > 1); %s (decrescente)TudoQualquer dataFiltros de campoValores múltiplos podem ser separados por vírgulas.NãoOrdenadoÚltimos 7 diasConsulta por range requer dois valores.EnviarEste mêsEste anoHojeDesconhecidoSimOntemcontémdatadiatermina comexcluircontém uma das chavescontém a chavecontém as chaveshoraestá contido poré maior queé maior ou igual quepresente emestá no rangeé menor queé menor ou igual quecoincide com a expressão regularminutomêssobrepõebuscarsegundocomeça comdia da semanaanodjango-filter-2.1.0/django_filters/locale/pt_BR/LC_MESSAGES/django.po0000644000076500000240000000622113322072216025651 0ustar carltonstaff00000000000000# Django Filter translation. # Copyright (C) 2017 # This file is distributed under the same license as the django_filter package. # Anderson Scouto da Silva, 2017. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-12-11 22:04+0100\n" "PO-Revision-Date: 2017-12-11 22:07-0200\n" "Last-Translator: Anderson Scouto da Silva\n" "Language-Team: \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.8.13\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: conf.py:26 msgid "date" msgstr "data" #: conf.py:27 msgid "year" msgstr "ano" #: conf.py:28 msgid "month" msgstr "mês" #: conf.py:29 msgid "day" msgstr "dia" #: conf.py:30 msgid "week day" msgstr "dia da 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 "contém" #: conf.py:40 msgid "is in" msgstr "presente em" #: conf.py:41 msgid "is greater than" msgstr "é maior que" #: conf.py:42 msgid "is greater than or equal to" msgstr "é maior ou igual que" #: conf.py:43 msgid "is less than" msgstr "é menor que" #: conf.py:44 msgid "is less than or equal to" msgstr "é menor ou igual que" #: conf.py:45 conf.py:46 msgid "starts with" msgstr "começa com" #: conf.py:47 conf.py:48 msgid "ends with" msgstr "termina com" #: conf.py:49 msgid "is in range" msgstr "está no range" #: conf.py:51 conf.py:52 msgid "matches regex" msgstr "coincide com a expressão regular" #: conf.py:53 conf.py:61 msgid "search" msgstr "buscar" #: conf.py:56 msgid "is contained by" msgstr "está contido por" #: conf.py:57 msgid "overlaps" msgstr "sobrepõe" #: conf.py:58 msgid "has key" msgstr "contém a chave" #: conf.py:59 msgid "has keys" msgstr "contém as chaves" #: conf.py:60 msgid "has any keys" msgstr "contém uma das chaves" #: fields.py:167 msgid "Range query expects two values." msgstr "Consulta por range requer dois valores." #: filters.py:443 msgid "Any date" msgstr "Qualquer data" #: filters.py:444 msgid "Today" msgstr "Hoje" #: filters.py:449 msgid "Past 7 days" msgstr "Últimos 7 dias" #: filters.py:453 msgid "This month" msgstr "Este mês" #: filters.py:457 msgid "This year" msgstr "Este ano" #: filters.py:460 msgid "Yesterday" msgstr "Ontem" #: filters.py:526 msgid "Multiple values may be separated by commas." msgstr "Valores múltiplos podem ser separados por vírgulas." #: filters.py:605 #, python-format msgid "%s (descending)" msgstr "%s (decrescente)" #: filters.py:621 msgid "Ordering" msgstr "Ordenado" #: utils.py:220 msgid "exclude" msgstr "excluir" #: widgets.py:71 msgid "All" msgstr "Tudo" #: widgets.py:119 msgid "Unknown" msgstr "Desconhecido" #: widgets.py:120 msgid "Yes" msgstr "Sim" #: widgets.py:121 msgid "No" msgstr "Não" #: 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-2.1.0/django_filters/locale/ru/0000755000076500000240000000000013421151434021701 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/ru/LC_MESSAGES/0000755000076500000240000000000013421151434023466 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/ru/LC_MESSAGES/django.mo0000644000076500000240000000532513322072216025272 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw9T[o\Ggz  ( I ] s z       D V c n      %# "  )(& * !$'%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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2018-01-24 11:03+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-2.1.0/django_filters/locale/ru/LC_MESSAGES/django.po0000644000076500000240000000724713322072216025302 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:27 conf.py:28 conf.py:41 msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-24 11:03+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" #: conf.py:17 msgid "date" msgstr "дата" #: conf.py:18 msgid "year" msgstr "год" #: conf.py:19 msgid "month" msgstr "месяц" #: conf.py:20 msgid "day" msgstr "день" #: conf.py:21 msgid "week day" msgstr "день недели" #: conf.py:22 msgid "hour" msgstr "час" #: conf.py:23 msgid "minute" msgstr "минута" #: conf.py:24 msgid "second" msgstr "секунда" #: conf.py:29 conf.py:30 msgid "contains" msgstr "содержит" #: conf.py:31 msgid "is in" msgstr "в" #: conf.py:32 msgid "is greater than" msgstr "больше чем" #: conf.py:33 msgid "is greater than or equal to" msgstr "больше или равно" #: conf.py:34 msgid "is less than" msgstr "меньше чем" #: conf.py:35 msgid "is less than or equal to" msgstr "меньше или равно" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "начинается" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "заканчивается" #: conf.py:40 msgid "is in range" msgstr "в диапазоне" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "соответствует регулярному выражению" #: conf.py:44 conf.py:52 msgid "search" msgstr "поиск" #: conf.py:47 msgid "is contained by" msgstr "содержится в" #: conf.py:48 msgid "overlaps" msgstr "перекрывается" #: conf.py:49 msgid "has key" msgstr "имеет ключ" #: conf.py:50 msgid "has keys" msgstr "имеет ключи" #: conf.py:51 msgid "has any keys" msgstr "имеет любые ключи" #: fields.py:178 msgid "Range query expects two values." msgstr "Запрос диапазона ожидает два значения." #: filters.py:429 msgid "Any date" msgstr "Любая дата" #: filters.py:430 msgid "Today" msgstr "Сегодня" #: filters.py:435 msgid "Past 7 days" msgstr "Прошедшие 7 дней" #: filters.py:439 msgid "This month" msgstr "За этот месяц" #: filters.py:443 msgid "This year" msgstr "В этом году" #: filters.py:446 msgid "Yesterday" msgstr "Вчера" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Несколько значений могут быть разделены запятыми." #: filters.py:591 #, python-format msgid "%s (descending)" msgstr "%s (по убыванию)" #: filters.py:607 msgid "Ordering" msgstr "Порядок" #: rest_framework/filterset.py:30 #: 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 msgid "Field filters" msgstr "Фильтры по полям" #: utils.py:224 msgid "exclude" msgstr "исключая" #: widgets.py:57 msgid "All" msgstr "Все" #: widgets.py:159 msgid "Unknown" msgstr "Не задано" #: widgets.py:160 msgid "Yes" msgstr "Да" #: widgets.py:161 msgid "No" msgstr "Нет" django-filter-2.1.0/django_filters/locale/sk/0000755000076500000240000000000013421151434021670 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/sk/LC_MESSAGES/0000755000076500000240000000000013421151434023455 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/sk/LC_MESSAGES/django.mo0000644000076500000240000000452313322072216025260 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw:JR e2r     (/ 4 >I b n{     $ - 5 @ O  %# "  )(& * !$'%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: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2018-03-25 19:11+0200 PO-Revision-Date: 2018-03-25 19:18+0058 Last-Translator: b'Erik Telepovsky ' Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; X-Translated-Using: django-rosetta 0.8.1 %s (klesajúco)VšetkyAkýkoľvek dátumFiltre poľaViacero hodnôt môže byť oddelených čiarkami.NieZoradeniePosledných 7 dníRozsah očakáva dve hodnoty.PotvrdiťTento mesiacTento rokDnesNeznámeÁnoVčeraobsahujedátumdeňkončí sneobsahujemá akékoľvek kľúčemá kľúčmá kľúčehodinaje obsiahnutýje vačší nežje vačší alebo rovný akoje vje v rozsahuje menší nežje menší alebo rovný akospĺňa regexminútamesiacpresahujehľadaťsekundazačína sdeň týždňarokdjango-filter-2.1.0/django_filters/locale/sk/LC_MESSAGES/django.po0000644000076500000240000000640713322072216025266 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. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-03-25 19:11+0200\n" "PO-Revision-Date: 2018-03-25 19:18+0058\n" "Last-Translator: b'Erik Telepovsky '\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=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Translated-Using: django-rosetta 0.8.1\n" #: conf.py:17 msgid "date" msgstr "dátum" #: conf.py:18 msgid "year" msgstr "rok" #: conf.py:19 msgid "month" msgstr "mesiac" #: conf.py:20 msgid "day" msgstr "deň" #: conf.py:21 msgid "week day" msgstr "deň týždňa" #: conf.py:22 msgid "hour" msgstr "hodina" #: conf.py:23 msgid "minute" msgstr "minúta" #: conf.py:24 msgid "second" msgstr "sekunda" #: conf.py:29 conf.py:30 msgid "contains" msgstr "obsahuje" #: conf.py:31 msgid "is in" msgstr "je v" #: conf.py:32 msgid "is greater than" msgstr "je vačší než" #: conf.py:33 msgid "is greater than or equal to" msgstr "je vačší alebo rovný ako" #: conf.py:34 msgid "is less than" msgstr "je menší než" #: conf.py:35 msgid "is less than or equal to" msgstr "je menší alebo rovný ako" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "začína s" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "končí s" #: conf.py:40 msgid "is in range" msgstr "je v rozsahu" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "spĺňa regex" #: conf.py:44 conf.py:52 msgid "search" msgstr "hľadať" #: conf.py:47 msgid "is contained by" msgstr "je obsiahnutý" #: conf.py:48 msgid "overlaps" msgstr "presahuje" #: conf.py:49 msgid "has key" msgstr "má kľúč" #: conf.py:50 msgid "has keys" msgstr "má kľúče" #: conf.py:51 msgid "has any keys" msgstr "má akékoľvek kľúče" #: fields.py:178 msgid "Range query expects two values." msgstr "Rozsah očakáva dve hodnoty." #: filters.py:429 msgid "Any date" msgstr "Akýkoľvek dátum" #: filters.py:430 msgid "Today" msgstr "Dnes" #: filters.py:435 msgid "Past 7 days" msgstr "Posledných 7 dní" #: filters.py:439 msgid "This month" msgstr "Tento mesiac" #: filters.py:443 msgid "This year" msgstr "Tento rok" #: filters.py:446 msgid "Yesterday" msgstr "Včera" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Viacero hodnôt môže byť oddelených čiarkami." #: filters.py:591 #, python-format msgid "%s (descending)" msgstr "%s (klesajúco)" #: filters.py:607 msgid "Ordering" msgstr "Zoradenie" #: rest_framework/filterset.py:30 #: templates/django_filters/rest_framework/form.html:5 msgid "Submit" msgstr "Potvrdiť" #: templates/django_filters/rest_framework/crispy_form.html:4 #: templates/django_filters/rest_framework/form.html:2 msgid "Field filters" msgstr "Filtre poľa" #: utils.py:224 msgid "exclude" msgstr "neobsahuje" #: widgets.py:57 msgid "All" msgstr "Všetky" #: widgets.py:159 msgid "Unknown" msgstr "Neznáme" #: widgets.py:160 msgid "Yes" msgstr "Áno" #: widgets.py:161 msgid "No" msgstr "Nie" django-filter-2.1.0/django_filters/locale/uk/0000755000076500000240000000000013421151434021672 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/uk/LC_MESSAGES/0000755000076500000240000000000013421151434023457 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/uk/LC_MESSAGES/django.mo0000644000076500000240000000536513322072216025267 0ustar carltonstaff00000000000000*l; + 8 ? JTZb fpy~     +9@FOV ]irw[xR $C<    4 !I k {   $    " 87 p        %# "  )(& * !$'%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: django-filter Report-Msgid-Bugs-To: POT-Creation-Date: 2018-01-24 11:03+0500 PO-Revision-Date: 2016-09-29 11:47+0300 Last-Translator: Eugena Mikhaylikova Language-Team: TextTempearture Language: uk 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-2.1.0/django_filters/locale/uk/LC_MESSAGES/django.po0000644000076500000240000000672113322072216025267 0ustar carltonstaff00000000000000# msgid "" msgstr "" "Project-Id-Version: django-filter\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-01-24 11:03+0500\n" "PO-Revision-Date: 2016-09-29 11:47+0300\n" "Last-Translator: Eugena Mikhaylikova \n" "Language-Team: TextTempearture\n" "Language: uk\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" #: conf.py:17 msgid "date" msgstr "дата" #: conf.py:18 msgid "year" msgstr "рік" #: conf.py:19 msgid "month" msgstr "місяць" #: conf.py:20 msgid "day" msgstr "день" #: conf.py:21 msgid "week day" msgstr "день тижня" #: conf.py:22 msgid "hour" msgstr "година" #: conf.py:23 msgid "minute" msgstr "хвилина" #: conf.py:24 msgid "second" msgstr "секунда" #: conf.py:29 conf.py:30 msgid "contains" msgstr "містить" #: conf.py:31 msgid "is in" msgstr "в" #: conf.py:32 msgid "is greater than" msgstr "більше ніж" #: conf.py:33 msgid "is greater than or equal to" msgstr "більше або дорівнює" #: conf.py:34 msgid "is less than" msgstr "менше ніж" #: conf.py:35 msgid "is less than or equal to" msgstr "менше або дорівнює" #: conf.py:36 conf.py:37 msgid "starts with" msgstr "починається" #: conf.py:38 conf.py:39 msgid "ends with" msgstr "закінчується" #: conf.py:40 msgid "is in range" msgstr "в діапазоні" #: conf.py:42 conf.py:43 msgid "matches regex" msgstr "відповідає регулярному виразу" #: conf.py:44 conf.py:52 msgid "search" msgstr "пошук" #: conf.py:47 msgid "is contained by" msgstr "міститься в" #: conf.py:48 msgid "overlaps" msgstr "перекривається" #: conf.py:49 msgid "has key" msgstr "має ключ" #: conf.py:50 msgid "has keys" msgstr "має ключі" #: conf.py:51 msgid "has any keys" msgstr "має будь-які ключі" #: fields.py:178 msgid "Range query expects two values." msgstr "Запит діапазону очікує два значення." #: filters.py:429 msgid "Any date" msgstr "Будь-яка дата" #: filters.py:430 msgid "Today" msgstr "Сьогодні" #: filters.py:435 msgid "Past 7 days" msgstr "Минулі 7 днів" #: filters.py:439 msgid "This month" msgstr "За цей місяць" #: filters.py:443 msgid "This year" msgstr "В цьому році" #: filters.py:446 msgid "Yesterday" msgstr "Вчора" #: filters.py:512 msgid "Multiple values may be separated by commas." msgstr "Кілька значень можуть бути розділені комами." #: filters.py:591 msgid "%s (descending)" msgstr "%s (по спадаючій)" #: filters.py:607 msgid "Ordering" msgstr "Порядок" #: rest_framework/filterset.py:30 #: 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 msgid "Field filters" msgstr "Фільтри по полях" #: utils.py:224 msgid "exclude" msgstr "виключаючи" #: widgets.py:57 msgid "All" msgstr "Усе" #: widgets.py:159 msgid "Unknown" msgstr "Не задано" #: widgets.py:160 msgid "Yes" msgstr "Так" #: widgets.py:161 msgid "No" msgstr "Немає" django-filter-2.1.0/django_filters/locale/zh_CN/0000755000076500000240000000000013421151434022254 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/zh_CN/LC_MESSAGES/0000755000076500000240000000000013421151434024041 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/locale/zh_CN/LC_MESSAGES/django.po0000644000076500000240000000243112670617671025662 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-2.1.0/django_filters/models.py0000644000076500000240000000000012410601557021642 0ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/rest_framework/0000755000076500000240000000000013421151434023046 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/rest_framework/__init__.py0000644000076500000240000000016113173602216025160 0ustar carltonstaff00000000000000# flake8: noqa from .backends import DjangoFilterBackend from .filterset import FilterSet from .filters import * django-filter-2.1.0/django_filters/rest_framework/backends.py0000644000076500000240000001201413421034002025157 0ustar carltonstaff00000000000000import warnings from django.template import loader from django.utils.deprecation import RenameMethodsBase from . import filters, filterset from .. import compat, utils # TODO: remove metaclass in 2.1 class RenameAttributes(utils.RenameAttributesBase, RenameMethodsBase): renamed_attributes = ( ('default_filter_set', 'filterset_base', utils.MigrationNotice), ) renamed_methods = ( ('get_filter_class', 'get_filterset_class', utils.MigrationNotice), ) class DjangoFilterBackend(metaclass=RenameAttributes): filterset_base = filterset.FilterSet raise_exception = True @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_filterset(self, request, queryset, view): filterset_class = self.get_filterset_class(view, queryset) if filterset_class is None: return None kwargs = self.get_filterset_kwargs(request, queryset, view) return filterset_class(**kwargs) def get_filterset_class(self, view, queryset=None): """ Return the `FilterSet` class used to filter the queryset. """ filterset_class = getattr(view, 'filterset_class', None) filterset_fields = getattr(view, 'filterset_fields', None) # TODO: remove assertion in 2.1 if filterset_class is None and hasattr(view, 'filter_class'): utils.deprecate( "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__) filterset_class = getattr(view, 'filter_class', None) # TODO: remove assertion in 2.1 if filterset_fields is None and hasattr(view, 'filter_fields'): utils.deprecate( "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__) filterset_fields = getattr(view, 'filter_fields', None) if filterset_class: filterset_model = filterset_class._meta.model # FilterSets do not need to specify a Meta class if filterset_model and queryset is not None: assert issubclass(queryset.model, filterset_model), \ 'FilterSet model %s does not match queryset model %s' % \ (filterset_model, queryset.model) return filterset_class if filterset_fields and queryset is not None: MetaBase = getattr(self.filterset_base, 'Meta', object) class AutoFilterSet(self.filterset_base): class Meta(MetaBase): model = queryset.model fields = filterset_fields return AutoFilterSet return None def get_filterset_kwargs(self, request, queryset, view): return { 'data': request.query_params, 'queryset': queryset, 'request': request, } def filter_queryset(self, request, queryset, view): filterset = self.get_filterset(request, queryset, view) if filterset is None: return queryset if not filterset.is_valid() and self.raise_exception: raise utils.translate_validation(filterset.errors) return filterset.qs def to_html(self, request, queryset, view): filterset = self.get_filterset(request, queryset, view) if filterset is None: return None template = loader.get_template(self.template) context = {'filter': filterset} 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=str(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()`' try: queryset = view.get_queryset() except Exception: queryset = None warnings.warn( "{} is not compatible with schema generation".format(view.__class__) ) filterset_class = self.get_filterset_class(view, queryset) return [] if not filterset_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 filterset_class.base_filters.items() ] django-filter-2.1.0/django_filters/rest_framework/filters.py0000644000076500000240000000047013322072216025071 0ustar carltonstaff00000000000000from django_filters import filters from ..filters import * # noqa from ..widgets import BooleanWidget __all__ = filters.__all__ class BooleanFilter(filters.BooleanFilter): def __init__(self, *args, **kwargs): kwargs.setdefault('widget', BooleanWidget) super().__init__(*args, **kwargs) django-filter-2.1.0/django_filters/rest_framework/filterset.py0000644000076500000240000000226113322072216025422 0ustar carltonstaff00000000000000from copy import deepcopy from django.db import models from django.utils.translation import ugettext_lazy as _ from django_filters import filterset from .. import compat 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}, models.NullBooleanField: {'filter_class': BooleanFilter}, }) class FilterSet(filterset.FilterSet): FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS @property def form(self): form = super().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 django-filter-2.1.0/django_filters/templates/0000755000076500000240000000000013421151434022012 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/templates/django_filters/0000755000076500000240000000000013421151434025004 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/templates/django_filters/rest_framework/0000755000076500000240000000000013421151434030036 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/templates/django_filters/rest_framework/crispy_form.html0000644000076500000240000000015413065267357033300 0ustar carltonstaff00000000000000{% load crispy_forms_tags %} {% load i18n %}

{% trans "Field filters" %}

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

{% trans "Field filters" %}

{{ filter.form.as_p }}
django-filter-2.1.0/django_filters/templates/django_filters/widgets/0000755000076500000240000000000013421151434026452 5ustar carltonstaff00000000000000django-filter-2.1.0/django_filters/templates/django_filters/widgets/multiwidget.html0000644000076500000240000000016613065267357031721 0ustar carltonstaff00000000000000{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %} django-filter-2.1.0/django_filters/utils.py0000644000076500000240000002377213421034026021536 0ustar carltonstaff00000000000000import warnings from collections import OrderedDict 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.utils import timezone from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext as _ from .exceptions import FieldLookupError def deprecate(msg, level_modifier=0): warnings.warn(msg, MigrationNotice, stacklevel=3 + level_modifier) class MigrationNotice(DeprecationWarning): url = 'https://django-filter.readthedocs.io/en/master/guide/migration.html' def __init__(self, message): super().__init__('%s See: %s' % (message, self.url)) class RenameAttributesBase(type): """ Handles the deprecation paths when renaming an attribute. It does the following: - Defines accessors that redirect to the renamed attributes. - Complain whenever an old attribute is accessed. This is conceptually based on `django.utils.deprecation.RenameMethodsBase`. """ renamed_attributes = () def __new__(metacls, name, bases, attrs): # remove old attributes before creating class old_names = [r[0] for r in metacls.renamed_attributes] old_names = [name for name in old_names if name in attrs] old_attrs = {name: attrs.pop(name) for name in old_names} # get a handle to any accessors defined on the class cls_getattr = attrs.pop('__getattr__', None) cls_setattr = attrs.pop('__setattr__', None) new_class = super().__new__(metacls, name, bases, attrs) def __getattr__(self, name): name = type(self).get_name(name) if cls_getattr is not None: return cls_getattr(self, name) elif hasattr(super(new_class, self), '__getattr__'): return super(new_class, self).__getattr__(name) return self.__getattribute__(name) def __setattr__(self, name, value): name = type(self).get_name(name) if cls_setattr is not None: return cls_setattr(self, name, value) return super(new_class, self).__setattr__(name, value) new_class.__getattr__ = __getattr__ new_class.__setattr__ = __setattr__ # set renamed attributes for name, value in old_attrs.items(): setattr(new_class, name, value) return new_class def get_name(metacls, name): """ Get the real attribute name. If the attribute has been renamed, the new name will be returned and a deprecation warning issued. """ for renamed_attribute in metacls.renamed_attributes: old_name, new_name, deprecation_warning = renamed_attribute if old_name == name: warnings.warn("`%s.%s` attribute should be renamed `%s`." % (metacls.__name__, old_name, new_name), deprecation_warning, 3) return new_name return name def __getattr__(metacls, name): return super().__getattribute__(metacls.get_name(name)) def __setattr__(metacls, name, value): return super().__setattr__(metacls.get_name(name), value) 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(f.remote_field, '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 = field.remote_field.model._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: raise FieldLookupError(model_field, lookup_expr) from e def handle_timezone(value, is_dst=None): if settings.USE_TZ and timezone.is_naive(value): return timezone.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, str): 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 translate_validation(error_dict): """ Translate a Django ErrorDict into its DRF ValidationError. """ # it's necessary to lazily import the exception, as it can otherwise create # an import loop when importing django_filters inside the project settings. from rest_framework.exceptions import ValidationError, ErrorDetail exc = OrderedDict( (key, [ErrorDetail(e.message % (e.params or ()), code=e.code) for e in error_list]) for key, error_list in error_dict.as_data().items() ) return ValidationError(exc) django-filter-2.1.0/django_filters/views.py0000644000076500000240000001012513421046035021522 0ustar carltonstaff00000000000000from 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 from .utils import MigrationNotice, RenameAttributesBase # TODO: remove metaclass in 2.1 class FilterMixinRenames(RenameAttributesBase): renamed_attributes = ( ('filter_fields', 'filterset_fields', MigrationNotice), ) class FilterMixin(metaclass=FilterMixinRenames): """ A mixin that provides a way to show and handle a FilterSet in a request. """ filterset_class = None filterset_fields = ALL_FIELDS strict = True 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.filterset_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 def get_strict(self): return self.strict class BaseFilterView(FilterMixin, MultipleObjectMixin, View): def get(self, request, *args, **kwargs): filterset_class = self.get_filterset_class() self.filterset = self.get_filterset(filterset_class) if not self.filterset.is_bound or self.filterset.is_valid() or not self.get_strict(): self.object_list = self.filterset.qs else: self.object_list = self.filterset.queryset.none() 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().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-2.1.0/django_filters/widgets.py0000644000076500000240000002067113421034026022037 0ustar carltonstaff00000000000000from collections.abc import Iterable from itertools import chain from re import search, sub 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.translation import ugettext as _ class LinkWidget(forms.Widget): def __init__(self, attrs=None, choices=()): super().__init__(attrs) self.choices = choices def value_from_datadict(self, data, files, name): value = super().value_from_datadict(data, files, name) self.data = data return value def render(self, name, value, attrs=None, choices=(), renderer=None): if not hasattr(self, 'data'): self.data = {} if value is None: value = '' 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().__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().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) ) 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(SuffixedMultiWidget): template_name = 'django_filters/widgets/multiwidget.html' suffixes = ['min', 'max'] def __init__(self, attrs=None): widgets = (forms.TextInput, forms.TextInput) super().__init__(widgets, attrs) def decompress(self, value): if value: return [value.start, value.stop] return [None, None] class DateRangeWidget(RangeWidget): suffixes = ['after', 'before'] class LookupChoiceWidget(SuffixedMultiWidget): suffixes = [None, 'lookup'] 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().__init__(attrs, choices) def render(self, name, value, attrs=None, renderer=None): try: value = { True: 'true', False: 'false', '1': 'true', '0': 'false' }[value] except KeyError: value = '' return super().render(name, value, attrs, renderer=renderer) def value_from_datadict(self, data, files, name): value = data.get(name, None) if isinstance(value, str): 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, str) def value_from_datadict(self, data, files, name): value = super().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, renderer=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().render(name, value, attrs, renderer=renderer) # 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(surrogate.format_value(v)) for v in value] value = ','.join(list(value)) return surrogate.render(name, value, attrs, renderer=renderer) 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, str): 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-2.1.0/docs/0000755000076500000240000000000013421151434015752 5ustar carltonstaff00000000000000django-filter-2.1.0/docs/.DS_Store0000644000076500000240000001400412435162061017436 0ustar carltonstaff00000000000000Bud1ldvSrnlong_buildvSrnlong  @ @ @ @ EDSDB ` @ @ @django-filter-2.1.0/docs/Makefile0000644000076500000240000001303612563340337017425 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-2.1.0/docs/assets/0000755000076500000240000000000013421151434017254 5ustar carltonstaff00000000000000django-filter-2.1.0/docs/assets/form.png0000644000076500000240000003255613013341253020735 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-2.1.0/docs/conf.py0000644000076500000240000002003013421147136017250 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'2019, Alex Gaynor, Carlton Gibson 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 = '2.1' # The full version, including alpha/beta/rc tags. release = '2.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-2.1.0/docs/dev/0000755000076500000240000000000013421151434016530 5ustar carltonstaff00000000000000django-filter-2.1.0/docs/dev/tests.txt0000644000076500000240000000364113172067725020453 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-2.1.0/docs/guide/0000755000076500000240000000000013421151434017047 5ustar carltonstaff00000000000000django-filter-2.1.0/docs/guide/install.txt0000644000076500000240000000122313421147136021260 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**: 3.4, 3.5, 3.6, 3.7 * **Django**: 1.11, 2.0, 2.1, 2.2 * **DRF**: 3.8+ django-filter-2.1.0/docs/guide/migration.txt0000644000076500000240000002461413364274537021630 0ustar carltonstaff00000000000000=============== Migration Guide =============== ----------------- Enabling warnings ----------------- To view 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 ---------------- Migrating to 2.0 ---------------- This release contains several changes that break forwards compatibility. This includes removed features, renamed attributes and arguments, and some reworked features. Due to the nature of these changes, it is not feasible to release a fully forwards-compatible migration release. Please review the below list of changes and update your code accordingly. ``Filter.lookup_expr`` list form removed (`#851`__) --------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/851 The ``Filter.lookup_expr`` argument no longer accepts ``None`` or a list of expressions. Use the :ref:`LookupChoiceFilter ` instead. FilterSet ``filter_for_reverse_field`` removed (`#915`__) --------------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/915 The ``filter_for_field`` method now generates filters for reverse relationships, removing the need for ``filter_for_reverse_field``. As a result, reverse relationships now also obey ``Meta.filter_overrides``. View attributes renamed (`#867`__) ---------------------------------- __ https://github.com/carltongibson/django-filter/pull/867 Several view-related attributes have been renamed to improve consistency with other parts of the library. The following classes are affected: * DRF ``ViewSet.filter_class`` => ``filterset_class`` * DRF ``ViewSet.filter_fields`` => ``filterset_fields`` * ``DjangoFilterBackend.default_filter_set`` => ``filterset_base`` * ``DjangoFilterBackend.get_filter_class()`` => ``get_filterset_class()`` * ``FilterMixin.filter_fields`` => ``filterset_fields`` FilterSet ``Meta.together`` option removed (`#791`__) ----------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/791 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" section in future docs. FilterSet "strictness" handling moved to view (`#788`__) -------------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/788 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. ``Filter.name`` renamed to ``Filter.field_name`` (`#792`__) ----------------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/792 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. ``Filter.widget`` and ``Filter.required`` removed (`#734`__) ------------------------------------------------------------ __ https://github.com/carltongibson/django-filter/pull/734 The filter class no longer directly stores arguments passed to its form field. All arguments are located in the filter's ``.extra`` dict. ``MultiWidget`` replaced by ``SuffixedMultiWidget`` (`#770`__) -------------------------------------------------------------- __ https://github.com/carltongibson/django-filter/pull/770 ``RangeWidget``, ``DateRangeWidget``, and ``LookupTypeWidget`` now inherit from ``SuffixedMultiWidget``, changing the suffixes of their query param names. For example, ``RangeWidget`` now has ``_min`` and ``_max`` suffixes instead of ``_0`` and ``_1``. ---------------- 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. MethodFilter and Filter.action replaced by Filter.method (`#382`__) ------------------------------------------------------------------- __ 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 (`#440`__) ------------------------------------------------- __ 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 (`#450`__) ---------------------------------------------------------------------------- __ 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 (`#430`__) ----------------------------------------------- __ 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 (`#472`__) -------------------------------------------------------- __ 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`` (`#437`__) ------------------------------------------------------------------------------------ __ 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 (`#562`__) ----------------------------------------------------------------------- __ https://github.com/carltongibson/django-filter/issues/562 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-2.1.0/docs/guide/rest_framework.txt0000644000076500000240000001701113421034026022637 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,) filterset_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 ``filterset_class`` ------------------------------------------- To enable filtering with a ``FilterSet``, add it to the ``filterset_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(field_name="price", lookup_expr='gte') max_price = filters.NumberFilter(field_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,) filterset_class = ProductFilter Using the ``filterset_fields`` shortcut --------------------------------------- You may bypass creating a ``FilterSet`` by instead adding ``filterset_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,) filterset_fields = ('category', 'in_stock') # Equivalent FilterSet: class ProductFilter(filters.FilterSet): class Meta: model = Product fields = ('category', 'in_stock') Note that using ``filterset_fields`` and ``filterset_class`` together is not supported. Overriding FilterSet creation ----------------------------- ``FilterSet`` creation can be customized by overriding the following methods on the backend class: * ``.get_filterset(self, request, queryset, view)`` * ``.get_filterset_class(self, view, queryset=None)`` * ``.get_filterset_kwargs(self, request, queryset, view)`` You can override these methods on a case-by-case basis for each view, creating unique backends, or these methods can be used to write your own hooks to the view class. .. code-block:: python class MyFilterBackend(filters.DjangoFilterBackend): def get_filterset_kwargs(self, request, queryset, view): kwargs = super().get_filterset_kwargs(request, queryset, view) # merge filterset kwargs provided by view class if hasattr(view, 'get_filterset_kwargs'): kwargs.update(view.get_filterset_kwargs()) return kwargs class BooksFilter(filters.FilterSet): def __init__(self, *args, author=None, **kwargs): super().__init__(*args, **kwargs) # do something w/ author class BookViewSet(viewsets.ModelViewSet): filter_backends = [MyFilterBackend] filterset_class = BookFilter def get_filterset_kwargs(self): return { 'author': self.get_author(), } 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. django-filter-2.1.0/docs/guide/tips.txt0000644000076500000240000002101213364274537020603 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 ``field_name`` and ``lookup_expr`` not configured ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While ``field_name`` and ``lookup_expr`` are optional, it is recommended that you specify them. By default, if ``field_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(field_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(field_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(field_name='category', lookup_expr='in') uncategorized = django_filters.BooleanFilter(field_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(field_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(field_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( field_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(field_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-2.1.0/docs/guide/usage.txt0000644000076500000240000002543013364274537020740 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(field_name='price', lookup_expr='gt') price__lt = django_filters.NumberFilter(field_name='price', lookup_expr='lt') release_year = django_filters.NumberFilter(field_name='release_date', lookup_expr='year') release_year__gt = django_filters.NumberFilter(field_name='release_date', lookup_expr='year__gt') release_year__lt = django_filters.NumberFilter(field_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: - ``field_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 ``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. .. _`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 ``filterset_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-2.1.0/docs/index.txt0000644000076500000240000000123413013341253017617 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-2.1.0/docs/make.bat0000644000076500000240000001176612410601557017375 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-2.1.0/docs/ref/0000755000076500000240000000000013421151434016526 5ustar carltonstaff00000000000000django-filter-2.1.0/docs/ref/fields.txt0000644000076500000240000000151713013341253020536 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-2.1.0/docs/ref/filters.txt0000644000076500000240000006511513421034026020744 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. Note that they are joined to construct the complete `lookup expression`_ that is the left hand side of the ORM ``.filter()`` call. .. _`lookup expression`: https://docs.djangoproject.com/en/dev/ref/models/lookups/#module-django.db.models.lookups ``field_name`` ~~~~~~~~~~~~~~ The name of the model field that is filtered against. If this argument is not provided, it defaults the filter's attribute name on the ``FilterSet`` class. Field names can traverse relationships by joining the related parts with the ORM lookup separator (``__``). e.g., a product's ``manufacturer__name``. ``lookup_expr`` ~~~~~~~~~~~~~~~ The `field lookup`_ that should be performed in the filter call. Defaults to ``exact``. The ``lookup_expr`` can contain transforms if the expression parts are joined by the ORM lookup separator (``__``). e.g., filter a datetime by its year part ``year__gt``. .. _`Field lookup`: https://docs.djangoproject.com/en/dev/ref/models/querysets/#field-lookups .. _keyword-only-arguments: Keyword-only Arguments: ----------------------- The following are optional arguments that can be used to modify the behavior of all filters. ``label`` ~~~~~~~~~ The label as it will appear 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 ``field_name`` and the parts of the ``lookup_expr`` (see: :ref:`verbose-lookups-setting`). .. _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 callable receives a ``QuerySet``, the name of the model field to filter on, and the value to filter with. It should return a filtered ``Queryset``. Note that the value is validated by the ``Filter.field``, 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(field_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(field_name='published_on', method=filter_not_empty) class Meta: model = Book fields = ['published'] ``distinct`` ~~~~~~~~~~~~ A boolean 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 relationships. Defaults to ``False``. ``exclude`` ~~~~~~~~~~~ A boolean 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``. Some field-related arguments: ``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. 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( field_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_min': '5', 'price_max': '15'}, queryset=qs) # Min-Only: Books costing more the 11€ f = F({'price_min': '11'}, queryset=qs) # Max-Only: Books costing less than 19€ f = F({'price_max': '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_after': '2016-01-01', 'date_before': '2016-02-01'}) # Min-Only: Comments added after 2016-01-01 f = F({'date_after': '2016-01-01'}) # Max-Only: Comments added before 2016-02-01 f = F({'date_before': '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_after': '2016-01-01', 'published_before': '2016-02-01'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 f = F({'published_after': '2016-01-01'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-02-01 f = F({'published_before': '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_after': '2016-01-01 8:00', 'published_before': '2016-01-01 10:00'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 8:00 f = F({'published_after': '2016-01-01 8:00'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-01-01 10:00 f = F({'published_before': '2016-01-01 10:00'}) assert len(f.qs) == 2 ``IsoDateTimeFromToRangeFilter`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to a ``RangeFilter`` except it uses ISO 8601 formatted values instead of numerical values. It can be used with ``IsoDateTimeField``. Example:: class Article(models.Model): published = dajngo_filters.IsoDateTimeField() class F(FilterSet): published = IsoDateTimeFromToRangeFilter() class Meta: model = Article fields = ['published'] Article.objects.create(published='2016-01-01T8:00:00+01:00') Article.objects.create(published='2016-01-01T9:30:00+01:00') Article.objects.create(published='2016-01-02T8:00:00+01:00') # Range: Articles published 2016-01-01 between 8:00 and 10:00 f = F({'published_after': '2016-01-01T8:00:00+01:00', 'published_before': '2016-01-01T10:00:00+01:00'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 8:00 f = F({'published_after': '2016-01-01T8:00:00+01:00'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-01-01 10:00 f = F({'published_before': '2016-01-01T10:00:00+0100'}) 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_after': '8:00', 'time_before': '10:00'}) # Min-Only: Comments added after 8:00 f = F({'time_after': '8:00'}) # Max-Only: Comments added before 10:00 f = F({'time_before': '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. .. _lookup-choice-filter: ``LookupChoiceFilter`` ~~~~~~~~~~~~~~~~~~~~~~ A combined filter that allows users to select the lookup expression from a dropdown. * ``lookup_choices`` is an optional argument that accepts multiple input formats, and is ultimately normalized as the choices used in the lookup dropdown. See ``.get_lookup_choices()`` for more information. * ``field_class`` is an optional argument that allows you to set the inner form field class used to validate the value. Default: ``forms.CharField`` ex:: price = django_filters.LookupChoiceFilter( field_class=forms.DecimalField, lookup_choices=[ ('exact', 'Equals'), ('gt', 'Greater than'), ('lt', 'Less than'), ] ) .. _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(field_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(BaseRangeFilter, NumberFilter): pass class F(FilterSet): id__range = NumberRangeFilter(field_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(field_name='username') status = NumberFilter(field_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(field_name='username') status = NumberFilter(field_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-2.1.0/docs/ref/filterset.txt0000644000076500000240000001334613173602216021302 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:`filter_overrides ` .. _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.`` .. _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, }, }, } Overriding ``FilterSet`` methods -------------------------------- When overriding classmethods, calling ``super(MyFilterSet, cls)`` may result in a ``NameError`` exception. This is due to the ``FilterSetMetaclass`` calling these classmethods before the ``FilterSet`` class has been fully created. There are two recommmended workarounds: 1. If using python 3.6 or newer, use the argumentless ``super()`` syntax. 2. For older versions of python, use an intermediate class. Ex:: class Intermediate(django_filters.FilterSet): @classmethod def method(cls, arg): super(Intermediate, cls).method(arg) ... class ProductFilter(Intermediate): class Meta: model = Product fields = ['...'] ``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().filter_for_lookup(f, lookup_type) django-filter-2.1.0/docs/ref/settings.txt0000644000076500000240000000477513322072216021144 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 django-filter-2.1.0/docs/ref/widgets.txt0000644000076500000240000000460313364274537020760 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-2.1.0/requirements/0000755000076500000240000000000013421151434017545 5ustar carltonstaff00000000000000django-filter-2.1.0/requirements/maintainer.txt0000644000076500000240000000066113406002226022435 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.20.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 watchdog==0.8.3 django-filter-2.1.0/requirements/test-ci.txt0000644000076500000240000000012713421034026021653 0ustar carltonstaff00000000000000markdown==2.6.4 coreapi django-crispy-forms coverage mock pytz unittest-xml-reporting django-filter-2.1.0/requirements/test.txt0000644000076500000240000000005213006450377021271 0ustar carltonstaff00000000000000-r test-ci.txt django djangorestframework django-filter-2.1.0/runshell.py0000755000076500000240000000076512754141601017246 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-2.1.0/runtests.py0000755000076500000240000000050212643540653017275 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-2.1.0/setup.cfg0000644000076500000240000000044613421151434016647 0ustar carltonstaff00000000000000[metadata] license-file = LICENSE [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 [flake8] max_line_length = 120 max_complexity = 10 [egg_info] tag_build = tag_date = 0 django-filter-2.1.0/setup.py0000644000076500000240000000410213421151263016531 0ustar carltonstaff00000000000000import os import sys from setuptools import setup, find_packages f = open('README.rst') readme = f.read() f.close() version = '2.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.11', 'Framework :: Django :: 2.0', 'Framework :: Django :: 2.1', # 'Framework :: Django :: 2.2', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Framework :: Django', ], zip_safe=False, python_requires='>=3.4', install_requires=[ 'Django>=1.11', ], ) django-filter-2.1.0/tests/0000755000076500000240000000000013421151434016164 5ustar carltonstaff00000000000000django-filter-2.1.0/tests/__init__.py0000644000076500000240000000000012410601557020266 0ustar carltonstaff00000000000000django-filter-2.1.0/tests/models.py0000644000076500000240000001267313322072216020032 0ustar carltonstaff00000000000000from django import forms from django.db import models 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().formfield(**defaults) 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) is_employed = models.NullBooleanField(default=False) favorite_books = models.ManyToManyField('Book', related_name='lovers') def __str__(self): return self.username 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' class AdminUser(User): class Meta: proxy = True def __str__(self): return "%s (ADMIN)" % self.username 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 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") class Company(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name class Meta: ordering = ['name'] 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-2.1.0/tests/rest_framework/0000755000076500000240000000000013421151434021216 5ustar carltonstaff00000000000000django-filter-2.1.0/tests/rest_framework/__init__.py0000644000076500000240000000011112773017247023334 0ustar carltonstaff00000000000000default_app_config = 'tests.rest_framework.apps.RestFrameworkTestConfig' django-filter-2.1.0/tests/rest_framework/apps.py0000644000076500000240000000027112773017247022547 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-2.1.0/tests/rest_framework/models.py0000644000076500000240000000122112770314705023057 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-2.1.0/tests/rest_framework/templates/0000755000076500000240000000000013421151434023214 5ustar carltonstaff00000000000000django-filter-2.1.0/tests/rest_framework/templates/filter_template.html0000644000076500000240000000000512773017247027271 0ustar carltonstaff00000000000000Test django-filter-2.1.0/tests/rest_framework/test_backends.py0000644000076500000240000003370513322072216024411 0ustar carltonstaff00000000000000import warnings 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 Article from .models import FilterableItem factory = APIRequestFactory() class FilterableItemSerializer(serializers.ModelSerializer): class Meta: model = FilterableItem fields = '__all__' # 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'] # Basic filter on a list view. class FilterableItemView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_backends = (DjangoFilterBackend,) class FilterFieldsRootView(FilterableItemView): filterset_fields = ['decimal', 'date'] class FilterClassRootView(FilterableItemView): filterset_class = SeveralFieldsFilter class GetFilterClassTests(TestCase): def test_filterset_class(self): class Filter(FilterSet): class Meta: model = FilterableItem fields = '__all__' backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_class = Filter queryset = FilterableItem.objects.all() filterset_class = backend.get_filterset_class(view, queryset) self.assertIs(filterset_class, Filter) def test_filterset_class_no_meta(self): class Filter(FilterSet): pass backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_class = Filter queryset = FilterableItem.objects.all() filterset_class = backend.get_filterset_class(view, queryset) self.assertIs(filterset_class, Filter) def test_filterset_class_no_queryset(self): class Filter(FilterSet): class Meta: model = FilterableItem fields = '__all__' backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_class = Filter filterset_class = backend.get_filterset_class(view, None) self.assertIs(filterset_class, Filter) def test_filterset_fields(self): backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_fields = ['text', 'decimal', 'date'] queryset = FilterableItem.objects.all() filterset_class = backend.get_filterset_class(view, queryset) self.assertEqual(filterset_class._meta.fields, view.filterset_fields) def test_filterset_fields_malformed(self): backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_fields = ['non_existent'] queryset = FilterableItem.objects.all() msg = "'Meta.fields' contains fields that are not defined on this FilterSet: non_existent" with self.assertRaisesMessage(TypeError, msg): backend.get_filterset_class(view, queryset) def test_filterset_fields_no_queryset(self): backend = DjangoFilterBackend() view = FilterableItemView() view.filterset_fields = ['text', 'decimal', 'date'] filterset_class = backend.get_filterset_class(view, None) self.assertIsNone(filterset_class) @skipIf(compat.coreapi is None, 'coreapi must be installed') class GetSchemaFieldsTests(TestCase): def test_fields_with_filterset_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_filterset_fields_list_with_bad_get_queryset(self): """ See: * https://github.com/carltongibson/django-filter/issues/551 """ class BadGetQuerySetView(FilterFieldsRootView): filterset_fields = ['decimal', 'date'] 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_malformed_filterset_fields(self): # Malformed filter fields should raise an exception class View(FilterFieldsRootView): filterset_fields = ['non_existent'] backend = DjangoFilterBackend() msg = "'Meta.fields' contains fields that are not defined on this FilterSet: non_existent" with self.assertRaisesMessage(TypeError, msg): backend.get_schema_fields(View()) def test_fields_with_filterset_fields_dict(self): class DictFilterFieldsRootView(FilterFieldsRootView): filterset_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_filterset_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): filterset_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): filterset_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 AutoFilterSetTests(TestCase): def test_autofilter_meta_inheritance(self): # https://github.com/carltongibson/django-filter/issues/663 class F(FilterSet): class Meta: filter_overrides = {BooleanField: {}} class Backend(DjangoFilterBackend): filterset_base = F view = FilterFieldsRootView() backend = Backend() filterset_class = backend.get_filterset_class(view, view.get_queryset()) filter_overrides = filterset_class._meta.filter_overrides # derived filterset_class.Meta should inherit from default_filter_set.Meta self.assertIn(BooleanField, filter_overrides) self.assertDictEqual(filter_overrides[BooleanField], {}) class ValidationErrorTests(TestCase): def test_errors(self): class F(FilterSet): class Meta: model = Article fields = ['id', 'author', 'name'] view = FilterFieldsRootView() backend = DjangoFilterBackend() request = factory.get('/?id=foo&author=bar&name=baz') request = view.initialize_request(request) queryset = Article.objects.all() view.filterset_class = F with self.assertRaises(serializers.ValidationError) as exc: backend.filter_queryset(request, queryset, view) # test output, does not include error code self.assertDictEqual(exc.exception.detail, { 'id': ['Enter a number.'], 'author': ['Select a valid choice. That choice is not one of the available choices.'], }) class RenamedBackendAttributesTests(TestCase): def test_get_filter_class(self): expected = "`Backend.get_filter_class` method should be renamed `get_filterset_class`. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Backend(DjangoFilterBackend): def get_filter_class(self): pass message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) def test_default_filter_set(self): expected = "`Backend.default_filter_set` attribute should be renamed `filterset_base`. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Backend(DjangoFilterBackend): default_filter_set = None message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) class RenamedViewSetAttributesTests(TestCase): def test_filter_class(self): expected = "`View.filter_class` attribute should be renamed `filterset_class`. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class View(generics.ListCreateAPIView): filter_class = None view = View() backend = DjangoFilterBackend() backend.get_filterset_class(view, None) message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) def test_filter_fields(self): expected = "`View.filter_fields` attribute should be renamed `filterset_fields`. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class View(generics.ListCreateAPIView): filter_fields = None view = View() backend = DjangoFilterBackend() # import pdb; pdb.set_trace() backend.get_filterset_class(view, None) message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) django-filter-2.1.0/tests/rest_framework/test_filters.py0000644000076500000240000000212113322072216024273 0ustar carltonstaff00000000000000import inspect from django.test import TestCase from django_filters.rest_framework import filters from django_filters.widgets import BooleanWidget class ModuleImportTests(TestCase): def is_filter(self, name, value): return ( isinstance(value, type) and issubclass(value, filters.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, filters.Filter) ] # sanity check self.assertIn('Filter', filter_classes) self.assertIn('BooleanFilter', filter_classes) for f in filter_classes: self.assertIn(f, filters.__all__) 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-2.1.0/tests/rest_framework/test_filterset.py0000644000076500000240000000350213322072216024630 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.field_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) def test_booleanfilter_widget_nullbooleanfield(self): field = User._meta.get_field('is_employed') result = FilterSet.filter_for_field(field, 'is_employed') 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-2.1.0/tests/rest_framework/test_integration.py0000644000076500000240000003617213322072216025163 0ustar carltonstaff00000000000000import 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.urls import reverse from django.utils.dateparse import parse_date from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory from django_filters import filters from django_filters.rest_framework import DjangoFilterBackend, FilterSet from .models import ( BaseFilterableItem, BasicModel, DjangoFilterOrderingModel, 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 filterset_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 filterset_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 filterset_class = MisconfiguredFilter filter_backends = (DjangoFilterBackend,) class FilterClassDetailView(generics.RetrieveAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filterset_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 filterset_class = BaseFilterableItemFilter filter_backends = (DjangoFilterBackend,) # Regression test for #814 class FilterFieldsQuerysetView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filterset_fields = ['decimal', 'date'] filter_backends = (DjangoFilterBackend,) class GetQuerysetView(generics.ListCreateAPIView): serializer_class = FilterableItemSerializer filterset_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 filterset_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_filterset_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) def test_raise_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.']}) def test_permissive(self): """ Permissive handling should return a partially filtered result set. """ FilterableItem.objects.create(decimal=Decimal('1.23'), date='2017-01-01') FilterableItem.objects.create(decimal=Decimal('1.23'), date='2016-01-01') class Backend(DjangoFilterBackend): raise_exception = False class View(FilterFieldsRootView): filter_backends = (Backend,) view = View.as_view() request = factory.get('/?decimal=foobar&date=2017-01-01') response = view(request).render() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]['date'], '2017-01-01') self.assertEqual(len(response.data), 1) @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 filterset_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,) filterset_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-2.1.0/tests/settings.py0000644000076500000240000000134413173602216020403 0ustar carltonstaff00000000000000 # ensure package/conf is importable 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'] django-filter-2.1.0/tests/tags0000644000076500000240000043156213006452203017055 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-2.1.0/tests/templates/0000755000076500000240000000000013421151434020162 5ustar carltonstaff00000000000000django-filter-2.1.0/tests/templates/tests/0000755000076500000240000000000013421151434021324 5ustar carltonstaff00000000000000django-filter-2.1.0/tests/templates/tests/book_filter.html0000644000076500000240000000011313421045664024513 0ustar carltonstaff00000000000000{{ filter.form }} {% for obj in object_list %} {{ obj }} {% endfor %} django-filter-2.1.0/tests/test_conf.py0000644000076500000240000000534113173602216020530 0ustar carltonstaff00000000000000 from django.test import TestCase, override_settings from django_filters.conf import is_callable, settings 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_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 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_DISABLE_HELP_TEXT')) # Default value self.assertFalse(settings.DISABLE_HELP_TEXT) with override_settings(FILTERS_DISABLE_HELP_TEXT=True): self.assertTrue(settings.DISABLE_HELP_TEXT) # Revert to default self.assertFalse(settings.DISABLE_HELP_TEXT) 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-2.1.0/tests/test_fields.py0000644000076500000240000002262713421034026021051 0ustar carltonstaff00000000000000import 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, IsoDateTimeRangeField, Lookup, LookupChoiceField, RangeField, TimeRangeField ) from django_filters.widgets import BaseCSVWidget, CSVWidget, RangeWidget def to_d(float_value): return decimal.Decimal('%.2f' % float_value) class LookupTests(TestCase): def test_empty_attrs(self): with self.assertRaisesMessage(ValueError, ''): Lookup(None, None) with self.assertRaisesMessage(ValueError, ''): Lookup('', '') def test_empty_value(self): with self.assertRaisesMessage(ValueError, ''): Lookup('', 'exact') def test_empty_lookup_expr(self): with self.assertRaisesMessage(ValueError, ''): Lookup('Value', '') class RangeFieldTests(TestCase): def test_field(self): f = RangeField() self.assertEqual(len(f.fields), 2) def test_clean(self): w = RangeWidget() f = RangeField(widget=w, 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 IsoDateTimeRangeFieldTests(TestCase): def test_field(self): f = IsoDateTimeRangeField() self.assertEqual(len(f.fields), 2) @override_settings(USE_TZ=False) def test_clean(self): w = RangeWidget() f = IsoDateTimeRangeField(widget=w) self.assertEqual( f.clean(['2015-01-01T10:30:01.123000+01:00', '2015-01-10T08:45:02.345000+01:00']), slice(datetime(2015, 1, 1, 9, 30, 1, 123000), datetime(2015, 1, 10, 7, 45, 2, 345000))) 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 LookupChoiceFieldTests(TestCase): def test_field(self): inner = forms.DecimalField() f = LookupChoiceField(inner, [('gt', 'gt'), ('lt', 'lt')]) self.assertEqual(len(f.fields), 2) def test_clean(self): inner = forms.DecimalField() f = LookupChoiceField(inner, [('gt', 'gt'), ('lt', 'lt')], required=False) self.assertEqual( f.clean(['12.34', 'lt']), Lookup(to_d(12.34), 'lt')) self.assertEqual( f.clean([]), None) with self.assertRaisesMessage(forms.ValidationError, 'Select a lookup.'): f.clean(['12.34', '']) def test_render_used_html5(self): inner = forms.DecimalField() f = LookupChoiceField(inner, [('gt', 'gt'), ('lt', 'lt')], empty_label=None) 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(''), []) self.assertEqual(self.field.clean([]), []) 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(['1']) with self.assertRaises(forms.ValidationError): self.field.clean(['1', '2', '3']) django-filter-2.1.0/tests/test_filtering.py0000644000076500000240000021247513421034026021570 0ustar carltonstaff00000000000000import contextlib import datetime import mock import unittest from operator import attrgetter from django import forms from django.http import QueryDict from django.test import TestCase, override_settings from django.utils import timezone from django.utils.timezone import make_aware, now from django_filters.filters import ( AllValuesFilter, AllValuesMultipleFilter, CharFilter, ChoiceFilter, DateFromToRangeFilter, DateRangeFilter, DateTimeFromToRangeFilter, DurationFilter, IsoDateTimeFromToRangeFilter, LookupChoiceFilter, ModelChoiceFilter, ModelMultipleChoiceFilter, MultipleChoiceFilter, 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 ) from .utils import MockQuerySet 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 = str(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 = str(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 = str(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(field_name='duration', lookup_expr='gte') max_duration = DurationFilter(field_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(field_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().__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) 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_min': '5', 'price_max': '15'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Rainbow Six'], lambda o: o.title) f = F({'price_min': '11'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Rainbow Six', 'Snowcrash'], lambda o: o.title) f = F({'price_max': '19'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Free Book', 'Rainbow Six', 'Refund'], lambda o: o.title) f = F({'price_min': '0', 'price_max': '12'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Free Book'], lambda o: o.title) f = F({'price_min': '-11', 'price_max': '0'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Free Book', 'Refund'], lambda o: o.title) f = F({'price_min': '0', 'price_max': '0'}, queryset=qs) self.assertQuerysetEqual(f.qs, ['Free Book'], lambda o: o.title) class DateRangeFilterTests(TestCase): class CommentFilter(FilterSet): date = DateRangeFilter() class Meta: model = Comment fields = ['date'] @contextlib.contextmanager def relative_to(self, today): today = make_aware(today) yesterday = today - datetime.timedelta(days=1) 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=yesterday, author=alex, time=time) Comment.objects.create(date=two_months_ago, author=alex, time=time) with mock.patch('django_filters.filters.now') as mock_now: mock_now.return_value = today yield def test_filtering_for_year(self): f = self.CommentFilter({'date': 'year'}) with self.relative_to(datetime.datetime(now().year, 4, 1)): self.assertQuerysetEqual(f.qs, [1, 3, 4, 5, 6], lambda o: o.pk, False) def test_filtering_for_month(self): f = self.CommentFilter({'date': 'month'}) with self.relative_to(datetime.datetime(now().year, 4, 21)): self.assertQuerysetEqual(f.qs, [1, 3, 4, 5], lambda o: o.pk, False) def test_filtering_for_week(self): f = self.CommentFilter({'date': 'week'}) with self.relative_to(datetime.datetime(now().year, 1, 1)): self.assertQuerysetEqual(f.qs, [3, 4, 5], lambda o: o.pk, False) def test_filtering_for_yesterday(self): f = self.CommentFilter({'date': 'yesterday'}) with self.relative_to(datetime.datetime(now().year, 1, 1)): self.assertQuerysetEqual(f.qs, [5], lambda o: o.pk, False) def test_filtering_for_today(self): f = self.CommentFilter({'date': 'today'}) with self.relative_to(datetime.datetime(now().year, 1, 1)): self.assertQuerysetEqual(f.qs, [4], lambda o: o.pk, False) 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(field_name='date') class Meta: model = Comment fields = ['date'] results = F(data={ 'published_after': '2016-01-02', 'published_before': '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_after': '2016-01-02', 'published_before': '2016-01-03'}) self.assertEqual(len(results.qs), 3) @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_after': '2017-10-15', 'published_before': '2017-10-15'}) self.assertEqual(len(results.qs), 2) @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_after': '2017-02-18', 'published_before': '2017-02-18'}) self.assertEqual(len(results.qs), 2) @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_after': '2017-3-26', 'published_before': '2017-3-26'}) self.assertEqual(len(results.qs), 3) @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_after': '2017-10-29', 'published_before': '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_after': '2016-01-02 10:00', 'published_before': '2016-01-03 19:00'}) self.assertEqual(len(results.qs), 2) class IsoDateTimeFromToRangeFilterTests(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 = IsoDateTimeFromToRangeFilter() class Meta: model = Article fields = ['published'] dt = (datetime.datetime.now(tz=tz)) results = F(data={ 'published_after': '2016-01-02T10:00:00.000000' + dt.strftime("%z"), 'published_before': '2016-01-03T19:00:00.000000' + dt.strftime("%z")}) 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_after': '8:00', 'time_before': '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')]) # invalid choice self.assertFalse(F({'username': 'jose'}).is_valid()) 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']))) # invalid choice self.assertFalse(F({'username': 'jose'}).is_valid()) self.assertEqual(list(F({'username': 'jose'}).qs), list(User.objects.all())) 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) @override_settings(TIME_ZONE='UTC') class TransformedQueryExpressionFilterTests(TestCase): def test_filtering(self): now_dt = 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) class LookupChoiceFilterTests(TestCase): class BookFilter(FilterSet): price = LookupChoiceFilter(lookup_choices=['lt', 'gt'], field_class=forms.DecimalField) class Meta: model = Book fields = ['price'] @classmethod def setUpTestData(cls): 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): F = self.BookFilter f = F({'price': '15', 'price_lookup': 'lt'}) self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title) f = F({'price': '15', 'price_lookup': 'lt'}) self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title) f = F({'price': '', 'price_lookup': 'lt'}) self.assertTrue(f.is_valid()) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Rainbow Six', 'Snowcrash'], lambda o: o.title, ordered=False) f = F({'price': '15'}) self.assertFalse(f.is_valid()) self.assertQuerysetEqual(f.qs, ['Ender\'s Game', 'Rainbow Six', 'Snowcrash'], lambda o: o.title, ordered=False) def test_inner_field_class_validation(self): f = self.BookFilter({'price': 'asdf', 'price_lookup': 'lt'}) self.assertFalse(f.is_valid()) self.assertEqual(f.errors, { 'price': ['Enter a number.'], }) def test_lookup_choices_validation(self): f = self.BookFilter({'price': '1', 'price_lookup': 'asdf'}) self.assertFalse(f.is_valid()) self.assertEqual(f.errors, { 'price': ['Select a valid choice. asdf is not one of the available choices.'], }) def test_lookup_omitted(self): f = self.BookFilter({'price': '1'}) self.assertFalse(f.is_valid()) self.assertEqual(f.errors, { 'price': ['Select a lookup.'], }) @override_settings(TIME_ZONE='UTC') 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 = 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.order_by('pk') cases = [ (None, [1, 2, 3, 4]), (QueryDict('status__in=1&status__in=2'), [2, 3]), ({'status__in': ''}, [1, 2, 3, 4]), ({'status__in': ','}, []), ({'status__in': '0'}, [4]), ({'status__in': '0,2'}, [2, 3, 4]), ({'status__in': '0,,1'}, [1, 4]), ({'status__in': '2'}, [2, 3]), ] for params, expected in cases: with self.subTest(params=params, expected=expected): self.assertQuerysetEqual(F(params, queryset=qs).qs, expected, attrgetter('pk')) def test_string_filtering(self): F = self.user_filter qs = User.objects.order_by('pk') cases = [ (None, [1, 2, 3, 4]), (QueryDict('username__in=alex&username__in=aaron'), [3]), ({'username__in': ''}, [1, 2, 3, 4]), ({'username__in': ','}, []), ({'username__in': 'alex'}, [1]), ({'username__in': 'alex,aaron'}, [1, 3]), ({'username__in': 'alex,,aaron'}, [1, 3]), ({'username__in': 'alex,'}, [1]), ] for params, expected in cases: with self.subTest(params=params, expected=expected): self.assertQuerysetEqual(F(params, queryset=qs).qs, expected, attrgetter('pk')) def test_datetime_filtering(self): F = self.article_filter qs = Article.objects.order_by('pk') after = self.after_5pm before = self.before_5pm cases = [ (None, [1, 2, 3, 4]), (QueryDict('published__in=%s&published__in=%s' % (after, before)), [3, 4]), ({'published__in': ''}, [1, 2, 3, 4]), ({'published__in': ','}, []), ({'published__in': '%s' % (after, )}, [1, 2]), ({'published__in': '%s,%s' % (after, before, )}, [1, 2, 3, 4]), ({'published__in': '%s,,%s' % (after, before, )}, [1, 2, 3, 4]), ({'published__in': '%s,' % (after, )}, [1, 2]), ] for params, expected in cases: with self.subTest(params=params, expected=expected): self.assertQuerysetEqual(F(params, queryset=qs).qs, expected, attrgetter('pk')) def test_related_filtering(self): F = self.article_filter qs = Article.objects.order_by('pk') cases = [ (None, [1, 2, 3, 4]), (QueryDict('author__in=1&author__in=2'), [2, 4]), ({'author__in': ''}, [1, 2, 3, 4]), ({'author__in': ','}, []), ({'author__in': '1'}, [1, 3]), ({'author__in': '1,2'}, [1, 2, 3, 4]), ({'author__in': '1,,2'}, [1, 2, 3, 4]), ({'author__in': '1,'}, [1, 3]), ] for params, expected in cases: with self.subTest(params=params, expected=expected): self.assertQuerysetEqual(F(params, queryset=qs).qs, expected, attrgetter('pk')) @override_settings(TIME_ZONE='UTC') class CSVRangeFilterTests(TestCase): class ArticleFilter(FilterSet): class Meta: model = Article fields = { 'published': ['range'], } @classmethod def setUpTestData(cls): 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 = now() after_5pm = now_dt.replace(hour=18) around_5pm = now_dt.replace(hour=17) before_5pm = now_dt.replace(hour=16) Article.objects.create(author=u1, published=after_5pm) Article.objects.create(author=u2, published=around_5pm) Article.objects.create(author=u1, published=around_5pm) Article.objects.create(author=u2, published=before_5pm) cls.after_5pm = after_5pm.strftime('%Y-%m-%d %H:%M:%S.%f') cls.around_5pm = around_5pm.strftime('%Y-%m-%d %H:%M:%S.%f') cls.before_5pm = before_5pm.strftime('%Y-%m-%d %H:%M:%S.%f') def test_filtering(self): F = self.ArticleFilter f = F() self.assertEqual(f.qs.count(), 4) # empty value is a noop f = F({'published__range': ''}) self.assertTrue(f.is_valid()) self.assertEqual(f.qs.count(), 4) # empty values are interpreted as None types f = F({'published__range': ','}) self.assertEqual(f.qs.count(), 0) f = F({'published__range': '%s' % (self.before_5pm, )}) self.assertFalse(f.is_valid()) f = F({'published__range': '%s,%s' % (self.before_5pm, self.around_5pm, )}) self.assertEqual(f.qs.count(), 3) f = F({'published__range': '%s,,%s' % (self.before_5pm, self.after_5pm, )}) self.assertFalse(f.is_valid()) # empty value is interpreted as None type f = F({'published__range': '%s,' % (self.before_5pm, )}) self.assertEqual(f.qs.count(), 0) 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(field_name='username') class Meta: model = User fields = ['account'] qs = MockQuerySet() F({'account': 'jdoe'}, queryset=qs).qs qs.all.return_value.filter.assert_called_with(username__exact='jdoe') def test_filtering_without_meta(self): class F(FilterSet): username = CharFilter() f = F({'username': 'alex'}, queryset=User.objects.all()) self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username) 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) django-filter-2.1.0/tests/test_filters.py0000644000076500000240000015212613421034026021251 0ustar carltonstaff00000000000000# -*- coding: utf-8 -*- 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, IsoDateTimeRangeField, Lookup, RangeField, TimeRangeField ) from django_filters.filters import ( AllValuesFilter, BaseCSVFilter, BaseInFilter, BaseRangeFilter, BooleanFilter, CharFilter, ChoiceFilter, DateFilter, DateFromToRangeFilter, DateRangeFilter, DateTimeFilter, DateTimeFromToRangeFilter, DurationFilter, Filter, IsoDateTimeFromToRangeFilter, LookupChoiceFilter, 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_lookup_types_removal(self): msg = ( "The `lookup_expr` argument no longer accepts `None` or a list of " "expressions. Use the `LookupChoiceFilter` instead. See: " "https://django-filter.readthedocs.io/en/master/guide/migration.html" ) with self.assertRaisesMessage(AssertionError, msg): Filter(lookup_expr=[]) with self.assertRaisesMessage(AssertionError, msg): Filter(lookup_expr=None) def test_field_params(self): with mock.patch.object(Filter, 'field_class', spec=['__call__']) as mocked: f = Filter(field_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(field_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_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(field_name='somefield', distinct=True) f.filter(qs, 'value') result = qs.distinct.assert_called_once_with() 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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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(field_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 MockQuerySetMixin: def get_mock_queryset(self): """ Configure mock QuerySet with required methods. """ qs = mock.NonCallableMock(spec=[]) qs.all = mock.Mock(return_value=qs) return qs class ModelChoiceFilterTests(TestCase, MockQuerySetMixin): 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 = self.get_mock_queryset() 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 = self.get_mock_queryset() 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 = self.get_mock_queryset() 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, MockQuerySetMixin): 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 = self.get_mock_queryset() 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(field_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 = self.get_mock_queryset() 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) def test_filtering_distinct(self): f = NumericRangeFilter(distinct=True) # range qs = mock.Mock() f.filter(qs, mock.Mock(start=20, stop=30)) qs.distinct.assert_called_once() qs.distinct.return_value.filter.assert_called_once_with(None__exact=(20, 30)) # min qs = mock.Mock() f.filter(qs, mock.Mock(start=20, stop=None)) qs.distinct.assert_called_once() qs.distinct.return_value.filter.assert_called_once_with(None__startswith=20) # max qs = mock.Mock() f.filter(qs, mock.Mock(start=None, stop=30)) qs.distinct.assert_called_once() qs.distinct.return_value.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)) def test_filtering_distinct(self): f = RangeFilter(distinct=True) # range qs = mock.Mock() f.filter(qs, mock.Mock(start=20, stop=30)) qs.distinct.assert_called_once() qs.distinct.return_value.filter.assert_called_once_with(None__range=(20, 30)) # min qs = mock.Mock() f.filter(qs, mock.Mock(start=20, stop=None)) qs.distinct.assert_called_once() qs.distinct.return_value.filter.assert_called_once_with(None__gte=20) # max qs = mock.Mock() f.filter(qs, mock.Mock(start=None, stop=30)) qs.distinct.assert_called_once() qs.distinct.return_value.filter.assert_called_once_with(None__lte=30) class DateRangeFilterTests(TestCase): def test_creating(self): f = DateRangeFilter(empty_label=None) self.assertEqual(len(f.choices), 5) self.assertIs(f.choices, f.extra['choices']) f = DateRangeFilter(empty_label=None, choices=[], filters=[]) self.assertEqual(f.choices, []) self.assertEqual(f.filters, []) self.assertEqual(len(f.choices), 0) self.assertIs(f.choices, 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, 'tomorrow') def test_choices_and_filters_mismatch(self): msg = "Keys must be present in both 'choices' and 'filters'. Missing keys: 'a, b'" with self.assertRaisesMessage(AssertionError, msg): DateRangeFilter(choices=[('a', 'a')], filters={'b': None}) def test_options_removed(self): msg = "The 'options' attribute has been replaced by 'choices' and 'filters'. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" class F(DateRangeFilter): options = None with self.assertRaisesMessage(AssertionError, msg): F() 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, 'year') 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, 'month') 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, 'week') 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, 'today') 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, 'yesterday') qs.filter.assert_called_once_with( None__year=(now_dt - timedelta(days=1)).year, None__month=(now_dt - timedelta(days=1)).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 IsoDateTimeFromToRangeFilterTests(TestCase): def test_default_field(self): f = IsoDateTimeFromToRangeFilter() field = f.field self.assertIsInstance(field, IsoDateTimeRangeField) 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 = IsoDateTimeFromToRangeFilter() 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 = IsoDateTimeFromToRangeFilter() 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 = IsoDateTimeFromToRangeFilter() 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 = IsoDateTimeFromToRangeFilter() 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 = IsoDateTimeFromToRangeFilter(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(field_name='username') f.model = User self.assertEqual(list(f.field.choices), [ ('', '---------'), ]) class LookupChoiceFilterTests(TestCase): def test_normalize_lookup_no_display_label(self): # display label has underscores replaced and is capitalized display_label = LookupChoiceFilter.normalize_lookup('has_key') self.assertEqual(display_label, ('has_key', 'Has key')) def test_normalize_lookup_with_display_label(self): # display label is not transformed if provided display_label = LookupChoiceFilter.normalize_lookup(('equal', 'equals')) self.assertEqual(display_label, ('equal', 'equals')) def test_lookup_choices_default(self): # Lookup choices should default to the model field's registered lookups f = LookupChoiceFilter(field_name='username', lookup_choices=None) f.model = User choice_field = f.field.fields[1] self.assertEqual( len(choice_field.choices), len(User._meta.get_field('username').get_lookups()) + 1 ) field_choices = dict(choice_field.choices) self.assertEqual(field_choices['exact'], 'Exact') self.assertEqual(field_choices['startswith'], 'Startswith') def test_lookup_choices_list(self): f = LookupChoiceFilter(field_name='username', lookup_choices=[ 'exact', 'startswith', 'has_key' ]) choice_field = f.field.fields[1] self.assertEqual(list(choice_field.choices), [ ('', '---------'), ('exact', 'Exact'), ('startswith', 'Startswith'), ('has_key', 'Has key'), ]) def test_lookup_choices_pairs(self): f = LookupChoiceFilter(field_name='username', lookup_choices=[ ('exact', 'Is equal to'), ('startswith', 'Starts with'), ]) choice_field = f.field.fields[1] self.assertEqual(list(choice_field.choices), [ ('', '---------'), ('exact', 'Is equal to'), ('startswith', 'Starts with'), ]) def test_lookup_choices_empty_label_default(self): f = LookupChoiceFilter(field_name='username', lookup_choices=[]) choice_field = f.field.fields[1] self.assertEqual(list(choice_field.choices), [('', '---------')]) def test_lookup_choices_empty_label_disabled(self): f = LookupChoiceFilter(field_name='username', empty_label=None, lookup_choices=[]) choice_field = f.field.fields[1] self.assertEqual(list(choice_field.choices), []) def test_filtering(self): qs = mock.Mock(spec=['filter']) f = LookupChoiceFilter(field_name='somefield', lookup_choices=['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) 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 # noqa 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-2.1.0/tests/test_filterset.py0000644000076500000240000007122313421045724021607 0ustar carltonstaff00000000000000import mock import unittest from django.db import models from django.test import TestCase from django_filters.exceptions import FieldLookupError 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 ) from .utils import MockQuerySet 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.field_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.field_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.field_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.field_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.field_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.field_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.field_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.field_name, 'employees') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Worker) 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.field_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 ReverseFilterSetFilterForFieldTests(TestCase): # Test reverse relationships for `filter_for_field` def test_reverse_o2o_relationship(self): f = Account._meta.get_field('profile') result = FilterSet.filter_for_field(f, 'profile') self.assertIsInstance(result, ModelChoiceFilter) self.assertEqual(result.field_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_field(f, 'comments') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.field_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_field(f, 'lovers') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.field_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_field(f, 'inbound_nodes') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.field_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_field(f, 'employers') self.assertIsInstance(result, ModelMultipleChoiceFilter) self.assertEqual(result.field_name, 'employers') self.assertTrue('queryset' in result.extra) self.assertIsNotNone(result.extra['queryset']) self.assertEqual(result.extra['queryset'].model, Business) def test_reverse_relationship_lookup_expr(self): f = Book._meta.get_field('lovers') result = FilterSet.filter_for_field(f, 'lovers', 'isnull') self.assertIsInstance(result, BooleanFilter) self.assertEqual(result.field_name, 'lovers') self.assertEqual(result.lookup_expr, 'isnull') class FilterSetFilterForReverseFieldTests(TestCase): def test_method_raises_assertion(self): msg = ("`F.filter_for_reverse_field` has been removed. " "`F.filter_for_field` now generates filters for reverse fields.") with self.assertRaisesMessage(AssertionError, msg): class F(FilterSet): @classmethod def filter_for_reverse_field(cls, field, field_name): pass 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_fields_invalid_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) 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): class F(FilterSet): class Meta: model = User fields = ['username'] def test_creating_instance(self): f = self.F() self.assertFalse(f.is_bound) self.assertIsNotNone(f.queryset) self.assertEqual(len(f.filters), len(self.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): f = self.F({'username': 'username'}) self.assertTrue(f.is_bound) def test_creating_with_queryset(self): m = mock.Mock() f = self.F(queryset=m) self.assertEqual(f.queryset, m) def test_creating_with_request(self): m = mock.Mock() f = self.F(request=m) self.assertEqual(f.request, m) class FilterSetQuerysetTests(TestCase): class F(FilterSet): invalid = CharFilter(method=lambda *args: None) class Meta: model = User fields = ['username', 'invalid'] def test_filter_queryset_called_once(self): m = MockQuerySet() f = self.F({'username': 'bob'}, queryset=m) with mock.patch.object(f, 'filter_queryset', wraps=f.filter_queryset) as fn: f.qs fn.assert_called_once_with(m.all()) f.qs fn.assert_called_once_with(m.all()) def test_get_form_class_called_once(self): f = self.F() with mock.patch.object(f, 'get_form_class', wraps=f.get_form_class) as fn: f.form fn.assert_called_once() f.form fn.assert_called_once() def test_qs_caching(self): m = mock.Mock() f = self.F(queryset=m) self.assertIs(f.qs, m.all()) self.assertIs(f.qs, f.qs) def test_form_caching(self): f = self.F() self.assertIs(f.form, f.form) def test_qs_triggers_form_validation(self): m = MockQuerySet() f = self.F({'username': 'bob'}, queryset=m) with mock.patch.object(f.form, 'full_clean', wraps=f.form.full_clean) as fn: fn.assert_not_called() f.qs fn.assert_called() def test_filters_must_return_queryset(self): m = MockQuerySet() f = self.F({'invalid': 'result'}, queryset=m) msg = "Expected 'F.invalid' to return a QuerySet, but got a NoneType instead." with self.assertRaisesMessage(AssertionError, msg): f.qs # 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 = MockQuerySet() 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) return qs 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-2.1.0/tests/test_forms.py0000644000076500000240000001760613173602216020740 0ustar carltonstaff00000000000000from 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(field_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(field_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(field_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, '' ) class FilterSetValidityTests(TestCase): class F(FilterSet): class Meta: model = Book fields = ['title', 'price'] def test_not_bound(self): f = self.F() self.assertFalse(f.is_bound) self.assertFalse(f.is_valid()) self.assertEqual(f.data, {}) self.assertEqual(f.errors, {}) def test_is_bound_and_valid(self): f = self.F({'title': 'Some book'}) self.assertTrue(f.is_bound) self.assertTrue(f.is_valid()) self.assertEqual(f.data, {'title': 'Some book'}) self.assertEqual(f.errors, {}) def test_is_bound_and_not_valid(self): f = self.F({'price': 'four dollars'}) self.assertTrue(f.is_bound) self.assertFalse(f.is_valid()) self.assertEqual(f.data, {'price': 'four dollars'}) self.assertEqual(f.errors, {'price': ['Enter a number.']}) django-filter-2.1.0/tests/test_utils.py0000644000076500000240000004467313421034026020750 0ustar carltonstaff00000000000000import datetime import warnings from django.db import models from django.db.models.constants import LOOKUP_SEP from django.db.models.fields.related import ForeignObjectRel 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 FilterSet from django_filters.exceptions import FieldLookupError from django_filters.filters import MultipleChoiceFilter from django_filters.utils import ( MigrationNotice, RenameAttributesBase, get_field_parts, get_model_field, handle_timezone, label_for_filter, resolve_field, translate_validation, verbose_field_name, verbose_lookup_expr ) from .models import ( Article, Book, Business, Company, HiredWorker, NetworkSetting, User ) class MigrationNoticeTests(TestCase): def test_message(self): self.assertEqual( str(MigrationNotice('Message.')), 'Message. See: https://django-filter.readthedocs.io/en/master/guide/migration.html' ) class RenameAttributes(RenameAttributesBase): renamed_attributes = ( ('old', 'new', DeprecationWarning), ) class SENTINEL: pass class RenameAttributesBaseTests(TestCase): def check(self, recorded, count): expected = '`Example.old` attribute should be renamed `new`.' self.assertEqual(len(recorded), count) for _ in range(count): message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) def test_class_creation_warnings(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Example(metaclass=RenameAttributes): old = SENTINEL # single warning for renamed attr on creation self.check(recorded, 1) def test_renamed_attribute_in_class_dict(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('ignore') class Example(metaclass=RenameAttributes): old = SENTINEL warnings.simplefilter('always') # Ensure `old` and `new` are not both in class dict. self.assertNotIn('old', Example.__dict__) self.assertIn('new', Example.__dict__) # Ensure `old` value assigned to `new`. self.assertEqual(Example.new, SENTINEL) self.check(recorded, 0) def test_class_accessor_warnings(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('ignore') class Example(metaclass=RenameAttributes): new = None warnings.simplefilter('always') self.assertIsNone(Example.new) self.assertIsNone(Example.old) self.check(recorded, 1) Example.old = SENTINEL self.assertIs(Example.new, SENTINEL) self.assertIs(Example.old, SENTINEL) self.check(recorded, 2) def test_instance_accessor_warnings(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('ignore') class Example(metaclass=RenameAttributes): new = None warnings.simplefilter('always') example = Example() self.check(recorded, 0) self.assertIsNone(example.new) self.assertIsNone(example.old) self.check(recorded, 1) example.old = SENTINEL self.assertIs(example.new, SENTINEL) self.assertIs(example.old, SENTINEL) self.check(recorded, 2) def test_class_instance_values(self): with warnings.catch_warnings(record=True): warnings.simplefilter('ignore') class Example(metaclass=RenameAttributes): new = None example = Example() # setting instance should not affect class example.old = SENTINEL self.assertIsNone(Example.old) self.assertIsNone(Example.new) self.assertIs(example.old, SENTINEL) self.assertIs(example.new, SENTINEL) def test_getter_reachable(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Example(metaclass=RenameAttributes): def __getattr__(self, name): if name == 'test': return SENTINEL return self.__getattribute__(name) example = Example() self.assertIs(example.test, SENTINEL) self.check(recorded, 0) def test_parent_getter_reachable(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Parent: def __getattr__(self, name): if name == 'test': return SENTINEL return self.__getattribute__(name) class Example(Parent, metaclass=RenameAttributes): pass example = Example() self.assertIs(example.test, SENTINEL) self.check(recorded, 0) def test_setter_reachable(self): with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class Example(metaclass=RenameAttributes): def __setattr__(self, name, value): if name == 'test': value = SENTINEL super().__setattr__(name, value) example = Example() example.test = None self.assertIs(example.test, SENTINEL) self.check(recorded, 0) 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) 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) 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) 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): @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)) @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 TranslateValidationDataTests(TestCase): class F(FilterSet): class Meta: model = Article fields = ['id', 'author', 'name'] choice = MultipleChoiceFilter(choices=[('1', 'one'), ('2', 'two')]) def test_error_detail(self): f = self.F(data={ 'id': 'foo', 'author': 'bar', 'name': 'baz', 'choice': ['3'], }) exc = translate_validation(f.errors) self.assertDictEqual(exc.detail, { 'id': ['Enter a number.'], 'author': ['Select a valid choice. That choice is not one of the available choices.'], 'choice': ['Select a valid choice. 3 is not one of the available choices.'], }) def test_full_error_details(self): f = self.F(data={ 'id': 'foo', 'author': 'bar', 'name': 'baz', 'choice': ['3'], }) exc = translate_validation(f.errors) self.assertEqual(exc.get_full_details(), { 'id': [{'message': 'Enter a number.', 'code': 'invalid'}], 'author': [{ 'message': 'Select a valid choice. That choice is not one of the available choices.', 'code': 'invalid_choice', }], 'choice': [{ 'message': 'Select a valid choice. 3 is not one of the available choices.', 'code': 'invalid_choice', }], }) django-filter-2.1.0/tests/test_views.py0000644000076500000240000001347613421060161020741 0ustar carltonstaff00000000000000import warnings 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, filterset_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_with_strict_errors(self): factory = RequestFactory() request = factory.get(self.base_url + '?title=Snowcrash&price=four dollars') view = FilterView.as_view(model=Book) response = view(request) titles = [o.title for o in response.context_data['object_list']] self.assertEqual(response.status_code, 200) self.assertEqual(titles, []) def test_view_with_non_strict_errors(self): factory = RequestFactory() request = factory.get(self.base_url + '?title=Snowcrash&price=four dollars') view = FilterView.as_view(model=Book, strict=False) response = view(request) titles = [o.title for o in response.context_data['object_list']] self.assertEqual(response.status_code, 200) self.assertEqual(titles, ['Snowcrash'],) 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) def test_filter_fields_removed(self): expected = "`View.filter_fields` attribute should be renamed `filterset_fields`. " \ "See: https://django-filter.readthedocs.io/en/master/guide/migration.html" with warnings.catch_warnings(record=True) as recorded: warnings.simplefilter('always') class View(FilterView): filter_fields = None message = str(recorded.pop().message) self.assertEqual(message, expected) self.assertEqual(len(recorded), 0) def test_view_with_unbound_filter_form_returns_initial_queryset(self): factory = RequestFactory() request = factory.get(self.base_url) queryset = Book.objects.filter(title='Snowcrash') view = FilterView.as_view(model=Book, queryset=queryset) response = view(request) titles = [o.title for o in response.context_data['object_list']] self.assertEqual(response.status_code, 200) self.assertEqual(titles, ['Snowcrash']) 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-2.1.0/tests/test_widgets.py0000644000076500000240000003236513322072216021254 0ustar carltonstaff00000000000000from django.forms import Select, TextInput from django.test import TestCase from django_filters.widgets import ( BaseCSVWidget, BooleanWidget, CSVWidget, LinkWidget, LookupChoiceWidget, QueryArrayWidget, RangeWidget, SuffixedMultiWidget ) class LookupTypeWidgetTests(TestCase): def test_widget_requires_field(self): with self.assertRaises(TypeError): LookupChoiceWidget() def test_widget_render(self): widgets = [TextInput(), Select(choices=(('a', 'a'), ('b', 'b')))] w = LookupChoiceWidget(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-2.1.0/tests/urls.py0000644000076500000240000000051313322072216017522 0ustar carltonstaff00000000000000from 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)), ] django-filter-2.1.0/tests/utils.py0000644000076500000240000000046513322072236017705 0ustar carltonstaff00000000000000from unittest import mock from django.db import models class MockQuerySet: """ Generate a mock that is suitably similar to a QuerySet """ def __new__(self): m = mock.Mock(spec_set=models.QuerySet()) m.filter.return_value = m m.all.return_value = m return m