")
+ return mark_safe("\n".join(output))
def render_options(self, choices, selected_choices, name):
selected_choices = set(force_str(v) for v in selected_choices)
@@ -43,16 +43,16 @@ def render_options(self, choices, selected_choices, name):
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))
+ 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)
+ 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):
+ def render_option(self, name, selected_choices, option_value, option_label):
option_value = force_str(option_value)
if option_label == BLANK_CHOICE_DASH[0][1]:
option_label = _("All")
@@ -64,9 +64,9 @@ def render_option(self, name, selected_choices,
except AttributeError:
url = urlencode(data)
return self.option_string() % {
- 'attrs': selected and ' class="selected"' or '',
- 'query_string': url,
- 'label': force_str(option_label)
+ "attrs": selected and ' class="selected"' or "",
+ "query_string": url,
+ "label": force_str(option_label),
}
def option_string(self):
@@ -80,6 +80,7 @@ class SuffixedMultiWidget(forms.MultiWidget):
- Suffixes must be unique.
- There must be the same number of suffixes as fields.
"""
+
suffixes = []
def __init__(self, *args, **kwargs):
@@ -89,12 +90,12 @@ def __init__(self, *args, **kwargs):
assert len(self.suffixes) == len(set(self.suffixes))
def suffixed(self, name, suffix):
- return '_'.join([name, suffix]) if suffix else name
+ 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)
+ for subcontext, suffix in zip(context["widget"]["subwidgets"], self.suffixes):
+ subcontext["name"] = self.suffixed(name, suffix)
return context
@@ -112,7 +113,7 @@ def value_omitted_from_data(self, data, files, name):
def replace_name(self, output, index):
result = search(r'name="(?P.*)_%d"' % index, output)
- name = result.group('name')
+ name = result.group("name")
name = self.suffixed(name, self.suffixes[index])
name = 'name="%s"' % name
@@ -125,8 +126,8 @@ def decompress(self, value):
class RangeWidget(SuffixedMultiWidget):
- template_name = 'django_filters/widgets/multiwidget.html'
- suffixes = ['min', 'max']
+ template_name = "django_filters/widgets/multiwidget.html"
+ suffixes = ["min", "max"]
def __init__(self, attrs=None):
widgets = (forms.TextInput, forms.TextInput)
@@ -139,11 +140,11 @@ def decompress(self, value):
class DateRangeWidget(RangeWidget):
- suffixes = ['after', 'before']
+ suffixes = ["after", "before"]
class LookupChoiceWidget(SuffixedMultiWidget):
- suffixes = [None, 'lookup']
+ suffixes = [None, "lookup"]
def decompress(self, value):
if value is None:
@@ -156,22 +157,16 @@ class BooleanWidget(forms.Select):
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')))
+ 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]
+ value = {True: "true", False: "false", "1": "true", "0": "false"}[value]
except KeyError:
- value = ''
+ value = ""
return super().render(name, value, attrs, renderer=renderer)
def value_from_datadict(self, data, files, name):
@@ -180,10 +175,10 @@ def value_from_datadict(self, data, files, name):
value = value.lower()
return {
- '1': True,
- '0': False,
- 'true': True,
- 'false': False,
+ "1": True,
+ "0": False,
+ "true": True,
+ "false": False,
True: True,
False: False,
}.get(value, None)
@@ -208,13 +203,13 @@ 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
+ if value == "": # empty value should parse as an empty list
return []
if isinstance(value, list):
# since django.forms.widgets.SelectMultiple tries to use getlist
# if available, we should return value if it's already an array
return value
- return value.split(',')
+ return value.split(",")
return None
def render(self, name, value, attrs=None, renderer=None):
@@ -223,13 +218,13 @@ def render(self, name, value, attrs=None, renderer=None):
if len(value) <= 1:
# delegate to main widget (Select, etc...) if not multiple values
- value = value[0] if value else ''
+ 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)
value = [force_str(self.surrogate.format_value(v)) for v in value]
- value = ','.join(list(value))
+ value = ",".join(list(value))
return self.surrogate.render(name, value, attrs, renderer=renderer)
@@ -255,13 +250,14 @@ class QueryArrayWidget(BaseCSVWidget, forms.TextInput):
def value_from_datadict(self, data, files, name):
if not isinstance(data, MultiValueDict):
+ data = data.copy()
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[key] = [x.strip() for x in value.rstrip(",").split(",") if x]
data = MultiValueDict(data)
- values_list = data.getlist(name, data.getlist('%s[]' % name)) or []
+ 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]
diff --git a/docs/conf.py b/docs/conf.py
index 5de3b5e91..7bc023380 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,39 +11,40 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import os
+import sys
from django_filters import __version__
# 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('.'))
+# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# 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']
+templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = '.txt'
+source_suffix = ".txt"
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
-master_doc = 'index'
+master_doc = "index"
# General information about the project.
-project = u'django-filter'
-copyright = u'2020, Alex Gaynor, Carlton Gibson and others.'
+project = u"django-filter"
+copyright = u"2022, 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
@@ -56,158 +57,161 @@
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
-#language = None
+# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# 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']
+exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# 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
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# 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'
+html_theme = "furo"
# 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 = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# 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
+# 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
+# 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']
+# 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'
+# 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
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = 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 = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Output file base name for HTML help builder.
-htmlhelp_basename = 'django-filterdoc'
+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': '',
+ # 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'),
+ (
+ "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
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
@@ -215,12 +219,17 @@
# 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)
+ (
+ "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
+# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@@ -229,28 +238,22 @@
# (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'),
+ (
+ "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 = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# 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()]
+# texinfo_show_urls = 'footnote'
diff --git a/docs/guide/install.txt b/docs/guide/install.txt
index 601765a6a..0ac4cf16f 100644
--- a/docs/guide/install.txt
+++ b/docs/guide/install.txt
@@ -21,14 +21,9 @@ Then add ``'django_filters'`` to your ``INSTALLED_APPS``.
Requirements
------------
-Django-filter is tested against all supported versions of Python and `Django`__,
-as well as the latest version of Django REST Framework (`DRF`__).
+Django-filter requires a current version of `Django`__ and is tested against
+all supported versions of Python, as well as the latest version of Django REST
+Framework (`DRF`__).
-__ https://www.djangoproject.com/download/
+__ https://www.djangoproject.com/download/#supported-versions
__ http://www.django-rest-framework.org/
-
-
-
-* **Python**: 3.5, 3.6, 3.7, 3.8
-* **Django**: 2.2, 3.0, 3.1
-* **DRF**: 3.10+
diff --git a/docs/guide/rest_framework.txt b/docs/guide/rest_framework.txt
index 04bbcade1..477d8cb9b 100644
--- a/docs/guide/rest_framework.txt
+++ b/docs/guide/rest_framework.txt
@@ -1,3 +1,5 @@
+.. _drf integration:
+
====================
Integration with DRF
====================
@@ -7,7 +9,6 @@ Integration with `Django Rest Framework`__ is provided through a DRF-specific ``
__ http://www.django-rest-framework.org/
__ http://www.django-rest-framework.org/api-guide/filtering/
-
Quickstart
----------
@@ -69,7 +70,7 @@ To enable filtering with a ``FilterSet``, add it to the ``filterset_class`` para
class Meta:
model = Product
- fields = ['category', 'in_stock', 'min_price', 'max_price']
+ fields = ['category', 'in_stock']
class ProductList(generics.ListAPIView):
diff --git a/docs/guide/tips.txt b/docs/guide/tips.txt
index e6ceb5036..715e9e540 100644
--- a/docs/guide/tips.txt
+++ b/docs/guide/tips.txt
@@ -28,7 +28,7 @@ lookup type. Under the hood, this will incorrectly be resolved as:
.. code-block:: python
- Produce.objects.filter(price__gt__exact=value)
+ Product.objects.filter(price__gt__exact=value)
The above will most likely generate a ``FieldError``. The correct configuration
would be:
@@ -38,6 +38,17 @@ would be:
class ProductFilter(django_filters.FilterSet):
price__gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt')
+When using ``filterset_fields``, you can also add the ``lookup_expr`` in the
+dict of fields like so:
+
+.. code-block:: python
+
+ # ... ModelViewSet with DjangoFilterBackend in filter_backends ...
+
+ filterset_fields = {
+ "price": ["gt", "exact"],
+ }
+
Missing ``lookup_expr`` for text search filters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -215,7 +226,7 @@ Filtering by relative times
Given a model with a timestamp field, it may be useful to filter based on relative times.
For instance, perhaps we want to get data from the past *n* hours.
-This could be accomplished the with a ``NumberFilter`` that invokes a custom method.
+This could be accomplished with a ``NumberFilter`` that invokes a custom method.
.. code-block:: python
diff --git a/docs/guide/usage.txt b/docs/guide/usage.txt
index da085478a..9f7e938da 100644
--- a/docs/guide/usage.txt
+++ b/docs/guide/usage.txt
@@ -8,8 +8,8 @@ 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.
+ If you're using django-filter with Django Rest Framework, it's recommended
+ that you read the :ref:`drf integration` docs after this guide.
The model
---------
@@ -20,10 +20,10 @@ Let's start with our model::
class Product(models.Model):
name = models.CharField(max_length=255)
- price = models.DecimalField()
+ price = models.DecimalField(max_digits=5, decimal_places=2)
description = models.TextField()
release_date = models.DateField()
- manufacturer = models.ForeignKey(Manufacturer)
+ manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
The filter
----------
@@ -331,7 +331,7 @@ to it as the class based view::
from myapp.models import Product
urlpatterns = [
- path("list/', object_filter, {'model': Product}, name="product-list),
+ path("list/", object_filter, {'model': Product}, name="product-list"),
]
The needed template and its context variables will also be the same as the
diff --git a/docs/ref/filters.txt b/docs/ref/filters.txt
index fcf0974dc..d5c8711b5 100644
--- a/docs/ref/filters.txt
+++ b/docs/ref/filters.txt
@@ -35,8 +35,8 @@ year part ``year__gt``.
.. _keyword-only-arguments:
-Keyword-only Arguments:
------------------------
+Keyword-only Arguments
+----------------------
The following are optional arguments that can be used to modify the behavior of
all filters.
diff --git a/docs/ref/filterset.txt b/docs/ref/filterset.txt
index fef637452..139f4190c 100644
--- a/docs/ref/filterset.txt
+++ b/docs/ref/filterset.txt
@@ -72,7 +72,7 @@ include both transforms and lookups, as detailed in the `lookup reference`__.
__ https://docs.djangoproject.com/en/stable/ref/models/lookups/#module-django.db.models.lookups
Note that it is **not** necessary to include declared filters in a ``fields``
-list - doing so will have no effect - and including declarative aliases in a
+list - doing so will only affect the order in which fields appear on a FilterSet's form. Including declarative aliases in a
``fields`` dict will raise an error.
.. code-block:: python
diff --git a/docs/ref/widgets.txt b/docs/ref/widgets.txt
index b9b34d14f..31543d3f1 100644
--- a/docs/ref/widgets.txt
+++ b/docs/ref/widgets.txt
@@ -54,7 +54,7 @@ well as type conversion.
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
+Under the hood, it is Django's ``forms.TextInput`` widget and accepts
the same arguments and values. To use it, pass it to ``widget`` argument of
a ``RangeField``:
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..e58d7c673
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,60 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "django-filter"
+authors = [{name = "Alex Gaynor", email = "alex.gaynor@gmail.com"}]
+maintainers = [{name = "Carlton Gibson", email = "carlton.gibson@noumenal.es"}]
+license = {text = "BSD"}
+description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
+readme = "README.rst"
+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 :: 3.2",
+ "Framework :: Django :: 4.0",
+ "Framework :: Django :: 4.1",
+ "Framework :: Django :: 4.2",
+ "Framework :: Django :: 5.0",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+]
+requires-python = ">=3.7"
+dependencies = ["Django>=3.2"]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://github.com/carltongibson/django-filter/tree/main"
+Documentation = "https://django-filter.readthedocs.io/en/main/"
+Changelog = "https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst"
+"Bug Tracker" = "https://github.com/carltongibson/django-filter/issues"
+"Source Code" = "https://github.com/carltongibson/django-filter"
+
+[tool.setuptools]
+zip-safe = false
+include-package-data = true
+license-files = ["LICENSE"]
+
+[tool.setuptools.packages.find]
+exclude = ["tests*"]
+namespaces = false
+
+[tool.isort]
+profile = "black"
+skip = [".tox"]
+known_third_party = ["django", "pytz", "rest_framework"]
+known_first_party = ["django_filters"]
+
+[tool.flit.module]
+name = "django_filters"
diff --git a/requirements/docs.txt b/requirements/docs.txt
new file mode 100644
index 000000000..303d17f6b
--- /dev/null
+++ b/requirements/docs.txt
@@ -0,0 +1,3 @@
+Sphinx
+furo
+.
diff --git a/requirements/maintainer.txt b/requirements/maintainer.txt
index baf75e0b7..e298a5828 100644
--- a/requirements/maintainer.txt
+++ b/requirements/maintainer.txt
@@ -1,25 +1,3 @@
-alabaster==0.7.7
-argh==0.26.1
-Babel==2.2.0
-backports.ssl-match-hostname==3.4.0.2
-certifi==2015.9.6.2
-docutils==0.12
-funcsigs==0.4
-Jinja2>=2.10.1
-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==5.1
-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
+wheel
+Sphinx
\ No newline at end of file
diff --git a/requirements/test-ci.txt b/requirements/test-ci.txt
index f10a9c33a..2f787bf94 100644
--- a/requirements/test-ci.txt
+++ b/requirements/test-ci.txt
@@ -1,8 +1,6 @@
markdown
-coreapi
django-crispy-forms
coverage
-mock
pytz
unittest-xml-reporting
diff --git a/runshell.py b/runshell.py
index 48af2821a..652d5e0f3 100755
--- a/runshell.py
+++ b/runshell.py
@@ -1,13 +1,15 @@
#!/usr/bin/env python
import os
import sys
+
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'])
- execute_from_command_line(sys.argv[:1] + ['shell'] + sys.argv[1:])
+ execute_from_command_line(sys.argv[:1] + ["migrate", "--noinput", "-v", "0"])
+ execute_from_command_line(sys.argv[:1] + ["shell"] + sys.argv[1:])
+
-if __name__ == '__main__':
+if __name__ == "__main__":
runshell()
diff --git a/runtests.py b/runtests.py
index 0de8c7707..cdf26b646 100755
--- a/runtests.py
+++ b/runtests.py
@@ -1,14 +1,15 @@
#!/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:]
+ argv = sys.argv[:1] + ["test"] + sys.argv[1:]
execute_from_command_line(argv)
-if __name__ == '__main__':
+if __name__ == "__main__":
runtests()
diff --git a/setup.cfg b/setup.cfg
index 3a8d64827..a75362422 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,14 +1,3 @@
-[metadata]
-license-file = LICENSE
-
-[isort]
-skip=.tox
-atomic=true
-multi_line_output=3
-extra_standard_library=mock
-known_third_party=django,pytz,rest_framework
-known_first_party=django_filters
-
[flake8]
max_line_length = 120
max_complexity = 10
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 1a7e626cc..000000000
--- a/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import os
-import sys
-from setuptools import setup, find_packages
-
-# FIXME: Main module requires django to be present, so cannot run setup.py in
-# clean environment.
-# from django_filters import __version__
-__version__ = '2.4.0'
-
-f = open('README.rst')
-readme = f.read()
-f.close()
-
-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 :: 2.2',
- 'Framework :: Django :: 3.0',
- 'Framework :: Django :: 3.1',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
- ],
- zip_safe=False,
- python_requires='>=3.5',
- install_requires=[
- 'Django>=2.2',
- ],
-)
diff --git a/tests/models.py b/tests/models.py
index 2ab2af002..e1a391b21 100644
--- a/tests/models.py
+++ b/tests/models.py
@@ -7,9 +7,9 @@
ADMIN = 2
STATUS_CHOICES = (
- (REGULAR, 'Regular'),
- (MANAGER, 'Manager'),
- (ADMIN, 'Admin'),
+ (REGULAR, "Regular"),
+ (MANAGER, "Manager"),
+ (ADMIN, "Admin"),
)
@@ -27,20 +27,20 @@ class SubnetMaskField(models.Field):
description = "Subnet Mask"
def __init__(self, *args, **kwargs):
- kwargs['max_length'] = 15
+ 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 = {"form_class": forms.GenericIPAddressField}
defaults.update(kwargs)
return super().formfield(**defaults)
class User(models.Model):
- username = models.CharField(_('username'), max_length=255)
+ username = models.CharField(_("username"), max_length=255)
first_name = SubCharField(max_length=100)
last_name = SubSubCharField(max_length=100)
@@ -49,23 +49,25 @@ class User(models.Model):
is_active = models.BooleanField(default=False)
is_employed = models.BooleanField(null=True, default=False)
- favorite_books = models.ManyToManyField('Book', related_name='lovers')
+ 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)
+ 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'
+ return self.manager.name + " group"
class AdminUser(User):
@@ -78,7 +80,7 @@ def __str__(self):
class Comment(models.Model):
text = models.TextField()
- author = models.ForeignKey(User, related_name='comments', on_delete=models.CASCADE)
+ author = models.ForeignKey(User, related_name="comments", on_delete=models.CASCADE)
date = models.DateField()
time = models.TimeField()
@@ -88,7 +90,7 @@ def __str__(self):
class Article(models.Model):
- name = models.CharField(verbose_name='title', max_length=200, blank=True)
+ name = models.CharField(verbose_name="title", max_length=200, blank=True)
published = models.DateTimeField()
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
@@ -132,17 +134,19 @@ def __str__(self):
return self.name
class Meta:
- ordering = ['name']
+ ordering = ["name"]
class Location(models.Model):
- company = models.ForeignKey(Company, related_name='locations', on_delete=models.CASCADE)
+ 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)
+ return "%s: %s" % (self.company.name, self.name)
class Account(models.Model):
@@ -152,7 +156,9 @@ class Account(models.Model):
class Profile(models.Model):
- account = models.OneToOneField(Account, related_name='profile', on_delete=models.CASCADE)
+ account = models.OneToOneField(
+ Account, related_name="profile", on_delete=models.CASCADE
+ )
likes_coffee = models.BooleanField(default=False)
likes_tea = models.BooleanField(default=False)
@@ -163,14 +169,14 @@ class BankAccount(Account):
class Node(models.Model):
name = models.CharField(max_length=20)
- adjacents = models.ManyToManyField('self')
+ 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')
+ outbound_nodes = models.ManyToManyField(
+ "self", symmetrical=False, related_name="inbound_nodes"
+ )
class Worker(models.Model):
@@ -181,14 +187,14 @@ 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)
+ 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')
+ employees = models.ManyToManyField(
+ Worker, through=HiredWorker, related_name="employers"
+ )
class UUIDTestModel(models.Model):
@@ -201,5 +207,6 @@ class SpacewalkRecord(models.Model):
See: https://en.wikipedia.org/wiki/List_of_cumulative_spacewalk_records
"""
+
astronaut = models.CharField(max_length=100)
duration = models.DurationField()
diff --git a/tests/rest_framework/__init__.py b/tests/rest_framework/__init__.py
index 488e6f11e..8bfe83a19 100644
--- a/tests/rest_framework/__init__.py
+++ b/tests/rest_framework/__init__.py
@@ -1 +1 @@
-default_app_config = 'tests.rest_framework.apps.RestFrameworkTestConfig'
+default_app_config = "tests.rest_framework.apps.RestFrameworkTestConfig"
diff --git a/tests/rest_framework/apps.py b/tests/rest_framework/apps.py
index 0a1efc13f..1fb9cb6a8 100644
--- a/tests/rest_framework/apps.py
+++ b/tests/rest_framework/apps.py
@@ -1,8 +1,7 @@
-
from django.apps import AppConfig
class RestFrameworkTestConfig(AppConfig):
- name = 'tests.rest_framework'
- label = 'drf_test_app'
+ name = "tests.rest_framework"
+ label = "drf_test_app"
verbose_name = "Rest Framework Test App"
diff --git a/tests/rest_framework/models.py b/tests/rest_framework/models.py
index a6f5e8a1c..3ae91db58 100644
--- a/tests/rest_framework/models.py
+++ b/tests/rest_framework/models.py
@@ -1,4 +1,3 @@
-
from django.db import models
from django.utils.translation import gettext_lazy as _
@@ -7,7 +6,7 @@ class BasicModel(models.Model):
text = models.CharField(
max_length=100,
verbose_name=_("Text comes here"),
- help_text=_("Text description.")
+ help_text=_("Text description."),
)
@@ -25,8 +24,10 @@ class DjangoFilterOrderingModel(models.Model):
text = models.CharField(max_length=10)
class Meta:
- ordering = ['-date']
+ ordering = ["-date"]
class CategoryItem(BaseFilterableItem):
- category = models.CharField(max_length=10, choices=(("home", "Home"), ("office", "Office")))
+ category = models.CharField(
+ max_length=10, choices=(("home", "Home"), ("office", "Office"))
+ )
diff --git a/tests/rest_framework/test_backends.py b/tests/rest_framework/test_backends.py
index 66fecf490..c38926253 100644
--- a/tests/rest_framework/test_backends.py
+++ b/tests/rest_framework/test_backends.py
@@ -3,16 +3,12 @@
from django.db.models import BooleanField
from django.test import TestCase
-from django.test.utils import override_settings
+from django.test.utils import ignore_warnings, 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 django_filters import RemovedInDjangoFilter25Warning, compat, filters
+from django_filters.rest_framework import DjangoFilterBackend, FilterSet, backends
from ..models import Article
from .models import CategoryItem, FilterableItem
@@ -23,24 +19,24 @@
class FilterableItemSerializer(serializers.ModelSerializer):
class Meta:
model = FilterableItem
- fields = '__all__'
+ fields = "__all__"
class CategoryItemSerializer(serializers.ModelSerializer):
class Meta:
model = CategoryItem
- fields = '__all__'
+ 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')
+ 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']
+ fields = ["text", "decimal", "date"]
# Basic filter on a list view.
@@ -51,7 +47,7 @@ class FilterableItemView(generics.ListCreateAPIView):
class FilterFieldsRootView(FilterableItemView):
- filterset_fields = ['decimal', 'date']
+ filterset_fields = ["decimal", "date"]
class FilterClassRootView(FilterableItemView):
@@ -66,12 +62,11 @@ class CategoryItemView(generics.ListCreateAPIView):
class GetFilterClassTests(TestCase):
-
def test_filterset_class(self):
class Filter(FilterSet):
class Meta:
model = FilterableItem
- fields = '__all__'
+ fields = "__all__"
backend = DjangoFilterBackend()
view = FilterableItemView()
@@ -97,7 +92,7 @@ def test_filterset_class_no_queryset(self):
class Filter(FilterSet):
class Meta:
model = FilterableItem
- fields = '__all__'
+ fields = "__all__"
backend = DjangoFilterBackend()
view = FilterableItemView()
@@ -109,7 +104,7 @@ class Meta:
def test_filterset_fields(self):
backend = DjangoFilterBackend()
view = FilterableItemView()
- view.filterset_fields = ['text', 'decimal', 'date']
+ view.filterset_fields = ["text", "decimal", "date"]
queryset = FilterableItem.objects.all()
filterset_class = backend.get_filterset_class(view, queryset)
@@ -118,7 +113,7 @@ def test_filterset_fields(self):
def test_filterset_fields_malformed(self):
backend = DjangoFilterBackend()
view = FilterableItemView()
- view.filterset_fields = ['non_existent']
+ view.filterset_fields = ["non_existent"]
queryset = FilterableItem.objects.all()
msg = "'Meta.fields' must not contain non-model field names: non_existent"
@@ -128,28 +123,29 @@ def test_filterset_fields_malformed(self):
def test_filterset_fields_no_queryset(self):
backend = DjangoFilterBackend()
view = FilterableItemView()
- view.filterset_fields = ['text', 'decimal', 'date']
+ 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')
+@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'])
+ 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']
+ filterset_fields = ["decimal", "date"]
def get_queryset(self):
raise AttributeError("I don't have that")
@@ -160,16 +156,20 @@ def get_queryset(self):
warnings.simplefilter("always")
fields = backend.get_schema_fields(BadGetQuerySetView())
- self.assertEqual(fields, [], "get_schema_fields should handle AttributeError")
+ self.assertEqual(
+ fields, [], "get_schema_fields should handle AttributeError"
+ )
- warning = "{} is not compatible with schema generation".format(BadGetQuerySetView)
+ 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']
+ filterset_fields = ["non_existent"]
backend = DjangoFilterBackend()
@@ -180,14 +180,14 @@ class View(FilterFieldsRootView):
def test_fields_with_filterset_fields_dict(self):
class DictFilterFieldsRootView(FilterFieldsRootView):
filterset_fields = {
- 'decimal': ['exact', 'lt', 'gt'],
+ "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'])
+ self.assertEqual(fields, ["decimal", "decimal__lt", "decimal__gt"])
def test_fields_with_filterset_class(self):
backend = DjangoFilterBackend()
@@ -195,7 +195,7 @@ def test_fields_with_filterset_class(self):
schemas = [f.schema for f in fields]
fields = [f.name for f in fields]
- self.assertEqual(fields, ['text', 'decimal', 'date'])
+ 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)
@@ -205,7 +205,7 @@ class RequiredFieldsFilter(SeveralFieldsFilter):
required_text = filters.CharFilter(required=True)
class Meta(SeveralFieldsFilter.Meta):
- fields = SeveralFieldsFilter.Meta.fields + ['required_text']
+ fields = SeveralFieldsFilter.Meta.fields + ["required_text"]
class FilterClassWithRequiredFieldsView(FilterClassRootView):
filterset_class = RequiredFieldsFilter
@@ -215,7 +215,7 @@ class FilterClassWithRequiredFieldsView(FilterClassRootView):
required = [f.required for f in fields]
fields = [f.name for f in fields]
- self.assertEqual(fields, ['text', 'decimal', 'date', 'required_text'])
+ self.assertEqual(fields, ["text", "decimal", "date", "required_text"])
self.assertFalse(required[0])
self.assertFalse(required[1])
self.assertFalse(required[2])
@@ -225,7 +225,9 @@ 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")
+ self.fail(
+ "callable queryset should not be invoked during schema generation"
+ )
class F(SeveralFieldsFilter):
f = filters.ModelChoiceFilter(queryset=qs)
@@ -234,41 +236,47 @@ class View(FilterClassRootView):
filterset_class = F
view = View()
- view.request = factory.get('/')
+ 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'])
+ self.assertEqual(fields, ["text", "decimal", "date", "f"])
class GetSchemaOperationParametersTests(TestCase):
+ @ignore_warnings(category=RemovedInDjangoFilter25Warning)
def test_get_operation_parameters_with_filterset_fields_list(self):
backend = DjangoFilterBackend()
fields = backend.get_schema_operation_parameters(FilterFieldsRootView())
- fields = [f['name'] for f in fields]
+ fields = [f["name"] for f in fields]
- self.assertEqual(fields, ['decimal', 'date'])
+ self.assertEqual(fields, ["decimal", "date"])
+ @ignore_warnings(category=RemovedInDjangoFilter25Warning)
def test_get_operation_parameters_with_filterset_fields_list_with_choices(self):
backend = DjangoFilterBackend()
fields = backend.get_schema_operation_parameters(CategoryItemView())
self.assertEqual(
fields,
- [{
- 'name': 'category',
- 'required': False,
- 'in': 'query',
- 'description': 'category',
- 'schema': {
- 'type': 'string',
- 'enum': ['home', 'office']
- },
-
- }]
+ [
+ {
+ "name": "category",
+ "required": False,
+ "in": "query",
+ "description": "category",
+ "schema": {"type": "string", "enum": ["home", "office"]},
+ }
+ ],
)
+ def test_deprecation_warning(self):
+ backend = DjangoFilterBackend()
+ msg = "Built-in schema generation is deprecated. Use drf-spectacular."
+ with self.assertWarnsMessage(RemovedInDjangoFilter25Warning, msg):
+ backend.get_schema_operation_parameters(FilterFieldsRootView())
+
class TemplateTests(TestCase):
def test_backend_output(self):
@@ -277,10 +285,12 @@ def test_backend_output(self):
"""
view = FilterFieldsRootView()
backend = view.filter_backends[0]
- request = view.initialize_request(factory.get('/'))
+ request = view.initialize_request(factory.get("/"))
html = backend().to_html(request, view.get_queryset(), view)
- self.assertHTMLEqual(html, """
+ self.assertHTMLEqual(
+ html,
+ """
Field filters
- """)
+ """,
+ )
def test_template_path(self):
view = FilterFieldsRootView()
class Backend(view.filter_backends[0]):
- template = 'filter_template.html'
+ template = "filter_template.html"
- request = view.initialize_request(factory.get('/'))
+ request = view.initialize_request(factory.get("/"))
html = Backend().to_html(request, view.get_queryset(), view)
self.assertHTMLEqual(html, "Test")
@@ -319,8 +330,15 @@ def test_DTL_missing(self):
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'}
+ 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]):
@@ -350,16 +368,15 @@ class Backend(DjangoFilterBackend):
class ValidationErrorTests(TestCase):
-
def test_errors(self):
class F(FilterSet):
class Meta:
model = Article
- fields = ['id', 'author', 'name']
+ fields = ["id", "author", "name"]
view = FilterFieldsRootView()
backend = DjangoFilterBackend()
- request = factory.get('/?id=foo&author=bar&name=baz')
+ request = factory.get("/?id=foo&author=bar&name=baz")
request = view.initialize_request(request)
queryset = Article.objects.all()
view.filterset_class = F
@@ -368,81 +385,18 @@ class Meta:
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)
+ self.assertDictEqual(
+ exc.exception.detail,
+ {
+ "id": ["Enter a number."],
+ "author": [
+ "Select a valid choice. That choice is not one of the available choices."
+ ],
+ },
+ )
class DjangoFilterBackendTestCase(TestCase):
-
@classmethod
def setUpTestData(cls):
cls.backend = DjangoFilterBackend()
@@ -461,13 +415,16 @@ def test_to_html_none_filter_class(self):
html = self.backend.to_html(mock.Mock(), mock.Mock(), mock.Mock())
self.assertIsNone(html)
+ @ignore_warnings(category=RemovedInDjangoFilter25Warning)
def test_get_schema_operation_parameters_userwarning(self):
with self.assertWarns(UserWarning):
view = mock.Mock()
- view.__class__.return_value = 'Test'
+ view.__class__.return_value = "Test"
view.get_queryset.side_effect = Exception
self.backend.get_schema_operation_parameters(view)
- @mock.patch('django_filters.compat.is_crispy', return_value=True)
+ @mock.patch("django_filters.compat.is_crispy", return_value=True)
def test_template_crispy(self, _):
- self.assertEqual(self.backend.template, 'django_filters/rest_framework/crispy_form.html')
+ self.assertEqual(
+ self.backend.template, "django_filters/rest_framework/crispy_form.html"
+ )
diff --git a/tests/rest_framework/test_filters.py b/tests/rest_framework/test_filters.py
index 8f454079c..eb904f6b3 100644
--- a/tests/rest_framework/test_filters.py
+++ b/tests/rest_framework/test_filters.py
@@ -8,31 +8,28 @@
class ModuleImportTests(TestCase):
def is_filter(self, name, value):
- return (
- isinstance(value, type) and issubclass(value, filters.Filter)
- )
+ 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)
+ 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)
+ 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)
+ self.assertEqual(f.extra["widget"], BooleanWidget)
diff --git a/tests/rest_framework/test_filterset.py b/tests/rest_framework/test_filterset.py
index f8b5aaeb1..b6f89cff5 100644
--- a/tests/rest_framework/test_filterset.py
+++ b/tests/rest_framework/test_filterset.py
@@ -4,7 +4,7 @@
from django.test import TestCase
from django.test.utils import override_settings
-from django_filters.compat import is_crispy
+from django_filters.compat import crispy_forms
from django_filters.rest_framework import FilterSet, filters
from django_filters.widgets import BooleanWidget
@@ -14,38 +14,36 @@
class ArticleFilter(FilterSet):
class Meta:
model = Article
- fields = ['author']
+ fields = ["author"]
class FilterSetFilterForFieldTests(TestCase):
-
def test_isodatetimefilter(self):
- field = Article._meta.get_field('published')
- result = FilterSet.filter_for_field(field, 'published')
+ field = Article._meta.get_field("published")
+ result = FilterSet.filter_for_field(field, "published")
self.assertIsInstance(result, filters.IsoDateTimeFilter)
- self.assertEqual(result.field_name, 'published')
+ 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')
+ 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)
+ 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')
+ 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)
+ self.assertEqual(result.extra["widget"], BooleanWidget)
-@skipIf(is_crispy(), 'django_crispy_forms must be installed')
-@override_settings(INSTALLED_APPS=settings.INSTALLED_APPS + ('crispy_forms', ))
+@skipIf(crispy_forms is None, "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'))
+ 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'))
+ self.assertFalse(hasattr(ArticleFilter(), "_form"))
diff --git a/tests/rest_framework/test_integration.py b/tests/rest_framework/test_integration.py
index 2c37618f8..ef72447d8 100644
--- a/tests/rest_framework/test_integration.py
+++ b/tests/rest_framework/test_integration.py
@@ -15,7 +15,7 @@
BaseFilterableItem,
BasicModel,
DjangoFilterOrderingModel,
- FilterableItem
+ FilterableItem,
)
factory = APIRequestFactory()
@@ -24,26 +24,26 @@
class FilterableItemSerializer(serializers.ModelSerializer):
class Meta:
model = FilterableItem
- fields = '__all__'
+ fields = "__all__"
# Basic filter on a list view.
class FilterFieldsRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
- filterset_fields = ['decimal', 'date']
+ 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')
+ 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']
+ fields = ["text", "decimal", "date"]
class FilterClassRootView(generics.ListCreateAPIView):
@@ -55,11 +55,11 @@ class FilterClassRootView(generics.ListCreateAPIView):
# These classes are used to test a misconfigured filter class.
class MisconfiguredFilter(FilterSet):
- text = filters.CharFilter(lookup_expr='icontains')
+ text = filters.CharFilter(lookup_expr="icontains")
class Meta:
model = BasicModel
- fields = ['text']
+ fields = ["text"]
class IncorrectlyConfiguredRootView(generics.ListCreateAPIView):
@@ -82,7 +82,7 @@ class BaseFilterableItemFilter(FilterSet):
class Meta:
model = BaseFilterableItem
- fields = '__all__'
+ fields = "__all__"
class BaseFilterableItemFilterRootView(generics.ListCreateAPIView):
@@ -96,7 +96,7 @@ class BaseFilterableItemFilterRootView(generics.ListCreateAPIView):
class FilterFieldsQuerysetView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
- filterset_fields = ['decimal', 'date']
+ filterset_fields = ["decimal", "date"]
filter_backends = (DjangoFilterBackend,)
@@ -110,21 +110,26 @@ def get_queryset(self):
urlpatterns = [
- path('/', FilterClassDetailView.as_view(), name='detail-view'),
- path('', FilterClassRootView.as_view(), name='root-view'),
- path('get-queryset/', GetQuerysetView.as_view(), name='get-queryset-view'),
+ path("/", FilterClassDetailView.as_view(), name="detail-view"),
+ path("", FilterClassRootView.as_view(), name="root-view"),
+ path("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()}
+ 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))
+ 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
@@ -132,10 +137,7 @@ def setUp(self):
FilterableItem(text=text, decimal=decimal, date=date).save()
self.objects = FilterableItem.objects
- self.data = [
- self._serialize_object(obj)
- for obj in self.objects.all()
- ]
+ self.data = [self._serialize_object(obj) for obj in self.objects.all()]
class IntegrationTestFiltering(CommonFilteringTestCase):
@@ -150,25 +152,29 @@ def test_get_filtered_fields_root_view(self):
view = FilterFieldsRootView.as_view()
# Basic test with no filter.
- request = factory.get('/')
+ 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})
+ 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]
+ 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'
+ 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]
+ 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):
@@ -178,11 +184,13 @@ def test_filter_with_queryset(self):
view = FilterFieldsQuerysetView.as_view()
# Tests that the decimal filter works.
- search_decimal = Decimal('2.25')
- request = factory.get('/', {'decimal': '%s' % search_decimal})
+ 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]
+ 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):
@@ -190,7 +198,7 @@ def test_filter_with_get_queryset_only(self):
Regression test for #834.
"""
view = GetQuerysetView.as_view()
- request = factory.get('/get-queryset/')
+ 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.
@@ -203,46 +211,51 @@ def test_get_filtered_class_root_view(self):
view = FilterClassRootView.as_view()
# Basic test with no filter.
- request = factory.get('/')
+ 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})
+ 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]
+ 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'
+ 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]
+ 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})
+ 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()]
+ 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_decimal = Decimal("5.25")
search_date = datetime.date(2012, 10, 2)
- request = factory.get('/', {
- 'decimal': '%s' % (search_decimal,),
- 'date': '%s' % (search_date,)
- })
+ 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]
+ 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):
@@ -251,7 +264,7 @@ def test_incorrectly_configured_filter(self):
"""
view = IncorrectlyConfiguredRootView.as_view()
- request = factory.get('/')
+ request = factory.get("/")
self.assertRaises(AssertionError, view, request)
def test_base_model_filter(self):
@@ -260,7 +273,7 @@ def test_base_model_filter(self):
"""
view = BaseFilterableItemFilterRootView.as_view()
- request = factory.get('/?text=aaa')
+ request = factory.get("/?text=aaa")
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
@@ -272,7 +285,7 @@ def test_unknown_filter(self):
view = FilterFieldsRootView.as_view()
search_integer = 10
- request = factory.get('/', {'integer': '%s' % search_integer})
+ request = factory.get("/", {"integer": "%s" % search_integer})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -281,8 +294,8 @@ def test_html_rendering(self):
Make sure response renders w/ backend
"""
view = FilterFieldsRootView.as_view()
- request = factory.get('/')
- request.META['HTTP_ACCEPT'] = 'text/html'
+ request = factory.get("/")
+ request.META["HTTP_ACCEPT"] = "text/html"
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -292,17 +305,17 @@ def test_raise_validation_error(self):
an internal server error.
"""
view = FilterFieldsRootView.as_view()
- request = factory.get('/?decimal=foobar')
+ 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.']})
+ 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')
+ 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
@@ -311,21 +324,21 @@ class View(FilterFieldsRootView):
filter_backends = (Backend,)
view = View.as_view()
- request = factory.get('/?decimal=foobar&date=2017-01-01')
+ 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(response.data[0]["date"], "2017-01-01")
self.assertEqual(len(response.data), 1)
-@override_settings(ROOT_URLCONF='tests.rest_framework.test_integration')
+@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))
+ return reverse("detail-view", kwargs=dict(pk=item.pk))
def test_get_filtered_detail_view(self):
"""
@@ -341,33 +354,39 @@ def test_get_filtered_detail_view(self):
self.assertEqual(response.data, data)
# Tests that the decimal filter set that should fail.
- search_decimal = Decimal('4.25')
+ 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)})
+ "{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')
+ 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)})
+ "{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_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 = 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)
- })
+ "{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)
@@ -375,21 +394,16 @@ def test_get_filtered_detail_view(self):
class DjangoFilterOrderingSerializer(serializers.ModelSerializer):
class Meta:
model = DjangoFilterOrderingModel
- fields = '__all__'
+ 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'
- }]
+ 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)
@@ -399,18 +413,18 @@ class DjangoFilterOrderingView(generics.ListAPIView):
serializer_class = DjangoFilterOrderingSerializer
queryset = DjangoFilterOrderingModel.objects.all()
filter_backends = (DjangoFilterBackend,)
- filterset_fields = ['text']
- ordering = ('-date',)
+ filterset_fields = ["text"]
+ ordering = ("-date",)
view = DjangoFilterOrderingView.as_view()
- request = factory.get('/')
+ 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'}
- ]
+ {"id": 3, "date": "2014-10-08", "text": "cde"},
+ {"id": 2, "date": "2013-10-08", "text": "bcd"},
+ {"id": 1, "date": "2012-10-08", "text": "abc"},
+ ],
)
diff --git a/tests/settings.py b/tests/settings.py
index f0af9c768..6ed8ddc5b 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -1,47 +1,51 @@
-
# ensure package/conf is importable
from django_filters.conf import DEFAULTS
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ':memory:',
+ "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',
+ "django.contrib.contenttypes",
+ "django.contrib.staticfiles",
+ "django.contrib.auth",
+ "rest_framework",
+ "django_filters",
+ "tests.rest_framework",
+ "tests",
)
MIDDLEWARE = []
-ROOT_URLCONF = 'tests.urls'
+ROOT_URLCONF = "tests.urls"
USE_TZ = True
-TIME_ZONE = 'UTC'
+TIME_ZONE = "UTC"
-SECRET_KEY = 'foobar'
+SECRET_KEY = "foobar"
-TEMPLATES = [{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'APP_DIRS': True,
-}]
+TEMPLATES = [
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "APP_DIRS": True,
+ }
+]
-STATIC_URL = '/static/'
+STATIC_URL = "/static/"
# XMLTestRunner output
-TEST_OUTPUT_DIR = '.xmlcoverage'
+TEST_OUTPUT_DIR = ".xmlcoverage"
# help verify that DEFAULTS is importable from conf.
def FILTERS_VERBOSE_LOOKUPS():
- return DEFAULTS['VERBOSE_LOOKUPS']
+ return DEFAULTS["VERBOSE_LOOKUPS"]
+
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
diff --git a/tests/test_conf.py b/tests/test_conf.py
index 3fb522285..c7fc0f592 100644
--- a/tests/test_conf.py
+++ b/tests/test_conf.py
@@ -6,29 +6,27 @@
class DefaultSettingsTests(TestCase):
-
def test_verbose_lookups(self):
self.assertIsInstance(settings.VERBOSE_LOOKUPS, dict)
- self.assertIn('exact', settings.VERBOSE_LOOKUPS)
+ self.assertIn("exact", settings.VERBOSE_LOOKUPS)
def test_default_lookup_expr(self):
- self.assertEqual(settings.DEFAULT_LOOKUP_EXPR, 'exact')
+ self.assertEqual(settings.DEFAULT_LOOKUP_EXPR, "exact")
def test_disable_help_text(self):
self.assertFalse(settings.DISABLE_HELP_TEXT)
def test_empty_choice_label(self):
- self.assertEqual(settings.EMPTY_CHOICE_LABEL, '---------')
+ 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')
+ self.assertEqual(settings.NULL_CHOICE_VALUE, "null")
class OverrideSettingsTests(TestCase):
-
def test_attribute_override(self):
self.assertIsInstance(settings.VERBOSE_LOOKUPS, dict)
@@ -43,7 +41,8 @@ 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'))
+
+ self.assertFalse(hasattr(dj_settings, "FILTERS_DISABLE_HELP_TEXT"))
# Default value
self.assertFalse(settings.DISABLE_HELP_TEXT)
@@ -55,27 +54,26 @@ def test_missing_attribute_override(self):
self.assertFalse(settings.DISABLE_HELP_TEXT)
def test_non_filters_setting(self):
- self.assertFalse(hasattr(settings, 'USE_TZ'))
+ self.assertFalse(hasattr(settings, "USE_TZ"))
with override_settings(USE_TZ=False):
- self.assertFalse(hasattr(settings, 'USE_TZ'))
+ self.assertFalse(hasattr(settings, "USE_TZ"))
- 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'))
+ 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'))
+ 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'))
+ self.assertFalse(hasattr(settings, "FILTERS_FOOBAR"))
+ self.assertFalse(hasattr(settings, "FOOBAR"))
class IsCallableTests(TestCase):
-
def test_behavior(self):
def func():
pass
@@ -96,12 +94,11 @@ def method(self):
class SettingsObjectTestCase(TestCase):
-
- @mock.patch('django_filters.conf.DEPRECATED_SETTINGS', ['TEST_123'])
- @mock.patch.dict('django_filters.conf.DEFAULTS', {'TEST_123': True})
+ @mock.patch("django_filters.conf.DEPRECATED_SETTINGS", ["TEST_123"])
+ @mock.patch.dict("django_filters.conf.DEFAULTS", {"TEST_123": True})
def test_get_setting_deprecated(self):
with override_settings(FILTERS_TEST_123=True):
with self.assertWarns(DeprecationWarning):
- settings.change_setting('FILTERS_TEST_123', True, True)
- test_setting = settings.get_setting('TEST_123')
+ settings.change_setting("FILTERS_TEST_123", True, True)
+ test_setting = settings.get_setting("TEST_123")
self.assertTrue(test_setting)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 2e1680924..d30de8946 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1,6 +1,9 @@
+import datetime as dt
import decimal
+import unittest
from datetime import datetime, time, timedelta, tzinfo
+import django
import pytz
from django import forms
from django.test import TestCase, override_settings
@@ -16,34 +19,33 @@
Lookup,
LookupChoiceField,
RangeField,
- TimeRangeField
+ TimeRangeField,
)
from django_filters.widgets import BaseCSVWidget, CSVWidget, RangeWidget
def to_d(float_value):
- return decimal.Decimal('%.2f' % float_value)
+ return decimal.Decimal("%.2f" % float_value)
class LookupTests(TestCase):
def test_empty_attrs(self):
- with self.assertRaisesMessage(ValueError, ''):
+ with self.assertRaisesMessage(ValueError, ""):
Lookup(None, None)
- with self.assertRaisesMessage(ValueError, ''):
- Lookup('', '')
+ with self.assertRaisesMessage(ValueError, ""):
+ Lookup("", "")
def test_empty_value(self):
- with self.assertRaisesMessage(ValueError, ''):
- Lookup('', 'exact')
+ with self.assertRaisesMessage(ValueError, ""):
+ Lookup("", "exact")
def test_empty_lookup_expr(self):
- with self.assertRaisesMessage(ValueError, ''):
- Lookup('Value', '')
+ with self.assertRaisesMessage(ValueError, ""):
+ Lookup("Value", "")
class RangeFieldTests(TestCase):
-
def test_field(self):
f = RangeField()
self.assertEqual(len(f.fields), 2)
@@ -52,14 +54,11 @@ 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.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)
@@ -69,14 +68,15 @@ 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)))
+ 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)
@@ -86,13 +86,12 @@ 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)))
+ 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)
@@ -101,15 +100,16 @@ def test_clean(self):
w = RangeWidget()
f = IsoDateTimeRangeField(widget=w)
expected = slice(
- datetime(2015, 1, 1, 9, 30, 1, 123000, tzinfo=timezone.utc),
- datetime(2015, 1, 10, 7, 45, 2, 345000, tzinfo=timezone.utc)
+ datetime(2015, 1, 1, 9, 30, 1, 123000, tzinfo=dt.timezone.utc),
+ datetime(2015, 1, 10, 7, 45, 2, 345000, tzinfo=dt.timezone.utc),
+ )
+ actual = f.clean(
+ ["2015-01-01T10:30:01.123000+01:00", "2015-01-10T08:45:02.345000+01:00"]
)
- actual = f.clean(['2015-01-01T10:30:01.123000+01:00', '2015-01-10T08:45:02.345000+01:00'])
self.assertEqual(expected, actual)
class TimeRangeFieldTests(TestCase):
-
def test_field(self):
f = DateRangeField()
self.assertEqual(len(f.fields), 2)
@@ -119,45 +119,46 @@ def test_clean(self):
f = TimeRangeField(widget=w)
self.assertEqual(
- f.clean(['10:15', '12:30']),
- slice(time(10, 15, 0), time(12, 30, 0)))
+ 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')])
+ 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)
+ 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', ''])
+ 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', ''), """
+ f = LookupChoiceField(inner, [("gt", "gt"), ("lt", "lt")], empty_label=None)
+ self.assertHTMLEqual(
+ f.widget.render("price", ""),
+ """
""")
- self.assertHTMLEqual(f.widget.render('price', ['abc', 'lt']), """
+ """,
+ )
+ self.assertHTMLEqual(
+ f.widget.render("price", ["abc", "lt"]),
+ """
""")
+ """,
+ )
class IsoDateTimeFieldTests(TestCase):
@@ -180,9 +181,10 @@ def test_datetime_zulu(self):
d = self.parse_input(self.reference_str + "Z")
self.assertTrue(isinstance(d, datetime))
- @override_settings(TIME_ZONE='UTC')
+ @unittest.skipUnless(django.VERSION < (5, 0), "pytz support removed in Django 5.0")
+ @override_settings(TIME_ZONE="UTC")
def test_datetime_timezone_awareness(self):
- utc, tokyo = pytz.timezone('UTC'), pytz.timezone('Asia/Tokyo')
+ utc, tokyo = pytz.timezone("UTC"), pytz.timezone("Asia/Tokyo")
# by default, use the server timezone
reference = utc.localize(self.reference_dt)
@@ -218,35 +220,45 @@ def test_datetime_timezone_naivety(self):
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')
+ 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')
+ self.parse_input("19-07-2015T51:34:13.759")
class BaseCSVFieldTests(TestCase):
- def setUp(self):
- class DecimalCSVField(BaseCSVField, forms.DecimalField):
- pass
-
- self.field = DecimalCSVField()
+ class DecimalCSVField(BaseCSVField, forms.DecimalField):
+ pass
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])
+ # Filter class sets required=False by default
+ field = self.DecimalCSVField(required=False)
+
+ self.assertEqual(field.clean(None), None)
+ self.assertEqual(field.clean(""), [])
+ self.assertEqual(field.clean(["1"]), [1])
+ self.assertEqual(field.clean(["1", "2"]), [1, 2])
+ self.assertEqual(field.clean(["1", "2", "3"]), [1, 2, 3])
def test_validation_error(self):
- with self.assertRaises(forms.ValidationError):
- self.field.clean([''])
+ field = self.DecimalCSVField()
+
+ msg = "Enter a number."
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean(["a", "b", "c"])
+
+ def test_required_error(self):
+ field = self.DecimalCSVField(required=True)
- with self.assertRaises(forms.ValidationError):
- self.field.clean(['a', 'b', 'c'])
+ msg = "This field is required."
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean(None)
+
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean([""])
def test_derived_widget(self):
with self.assertRaises(AssertionError) as excinfo:
@@ -256,10 +268,10 @@ def test_derived_widget(self):
self.assertIn("'BaseCSVField.widget' must be a widget class", msg)
self.assertIn("RangeWidget", msg)
- widget = CSVWidget(attrs={'class': 'class'})
+ widget = CSVWidget(attrs={"class": "class"})
field = BaseCSVField(widget=widget)
self.assertIsInstance(field.widget, CSVWidget)
- self.assertEqual(field.widget.attrs, {'class': 'class'})
+ self.assertEqual(field.widget.attrs, {"class": "class"})
field = BaseCSVField(widget=CSVWidget)
self.assertIsInstance(field.widget, CSVWidget)
@@ -270,24 +282,34 @@ def test_derived_widget(self):
class BaseRangeFieldTests(TestCase):
- def setUp(self):
- class DecimalRangeField(BaseRangeField, forms.DecimalField):
- pass
-
- self.field = DecimalRangeField()
+ class DecimalRangeField(BaseRangeField, forms.DecimalField):
+ pass
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])
+ # Filter class sets required=False by default
+ field = self.DecimalRangeField(required=False)
+
+ self.assertEqual(field.clean(None), None)
+ self.assertEqual(field.clean(""), [])
+ self.assertEqual(field.clean([]), [])
+ self.assertEqual(field.clean(["1", "2"]), [1, 2])
def test_validation_error(self):
- with self.assertRaises(forms.ValidationError):
- self.field.clean([''])
+ field = self.DecimalRangeField()
+
+ msg = "Range query expects two values."
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean(["1"])
+
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean(["1", "2", "3"])
+
+ def test_required_error(self):
+ field = self.DecimalRangeField(required=True)
- with self.assertRaises(forms.ValidationError):
- self.field.clean(['1'])
+ msg = "This field is required."
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean(None)
- with self.assertRaises(forms.ValidationError):
- self.field.clean(['1', '2', '3'])
+ with self.assertRaisesMessage(forms.ValidationError, msg):
+ field.clean([""])
diff --git a/tests/test_filtering.py b/tests/test_filtering.py
index 18f2b4821..6775e7f0e 100644
--- a/tests/test_filtering.py
+++ b/tests/test_filtering.py
@@ -1,18 +1,21 @@
import contextlib
import datetime
-import mock
import unittest
from operator import attrgetter
+from unittest import mock
+import django
from django import forms
from django.http import QueryDict
-from django.test import TestCase, override_settings
+from django.test import override_settings
from django.utils import timezone
from django.utils.timezone import make_aware, now
+from django_filters.compat import TestCase
from django_filters.filters import (
AllValuesFilter,
AllValuesMultipleFilter,
+ BaseInFilter,
CharFilter,
ChoiceFilter,
DateFromToRangeFilter,
@@ -27,9 +30,10 @@
OrderingFilter,
RangeFilter,
TimeRangeFilter,
- TypedMultipleChoiceFilter
+ TypedMultipleChoiceFilter,
)
from django_filters.filterset import FilterSet
+from django_filters.widgets import QueryArrayWidget
from .models import (
STATUS_CHOICES,
@@ -44,40 +48,36 @@
Node,
Profile,
SpacewalkRecord,
- User
+ 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)
+ 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']
+ 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)
+ 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,
+ "in_good_standing": True,
+ "friendly": False,
}
b1 = BankAccount.objects.create(amount_saved=0, **default_values)
b2 = BankAccount.objects.create(amount_saved=3, **default_values)
@@ -86,55 +86,52 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = BankAccount
- fields = ['amount_saved']
+ 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)
+ 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)
+ 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']
+ 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": "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": "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)
+ 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)
+ 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())
@@ -146,21 +143,20 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = User
- fields = ['status']
+ 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)
+ 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": "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)
+ f = F({"status": "0"})
+ self.assertQuerySetEqual(f.qs, ["carl"], lambda o: o.username, False)
def test_filtering_on_explicitly_defined_field(self):
"""
@@ -168,98 +164,96 @@ def test_filtering_on_explicitly_defined_field(self):
If you explicitly declare ChoiceFilter fields you **MUST** pass `choices`.
"""
+
class F(FilterSet):
status = ChoiceFilter(choices=STATUS_CHOICES)
class Meta:
model = User
- fields = ['status']
+ 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)
+ 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": "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)
+ 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']
+ fields = ["status"]
- f = F({'status': ''})
- self.assertQuerysetEqual(f.qs,
- ['aaron', 'alex', 'jacob', 'carl'],
- lambda o: o.username, False)
+ 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')]
+ 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',
+ null_value="null",
+ null_label="NULL",
)
class Meta:
model = Article
- fields = ['author']
+ 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": "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": "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)
+ 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']
+ fields = ["status"]
- qs = User.objects.all().order_by('username')
+ qs = User.objects.all().order_by("username")
f = F(queryset=qs)
- self.assertQuerysetEqual(
- f.qs, ['aaron', 'jacob', 'alex', 'carl'],
- lambda o: o.username, False)
+ 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"]}, 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"]}, 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)
+ 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)
+ 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())
@@ -267,75 +261,73 @@ def test_filtering_on_null_choice(self):
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')]
+ 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',
+ null_value="null",
+ null_label="NULL",
)
class Meta:
model = Article
- fields = ['author']
+ 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": ["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": ["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)
+ 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)
+ 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])
+ status = TypedMultipleChoiceFilter(
+ choices=STATUS_CHOICES, coerce=lambda x: x[0:2]
+ )
class Meta:
model = User
- fields = ['status']
+ fields = ["status"]
- qs = User.objects.all().order_by('username')
+ 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)
+ 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"]}, 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"]}, 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])
+ 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')
+ 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)
@@ -344,22 +336,21 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = Comment
- fields = ['date']
+ fields = ["date"]
- f = F({'date': check_date}, queryset=Comment.objects.all())
+ 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)
+ 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))
+ 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')
+ 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)
@@ -368,20 +359,19 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = Comment
- fields = ['time']
+ fields = ["time"]
- f = F({'time': check_time}, queryset=Comment.objects.all())
+ 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)
+ 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')
+ 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)
@@ -394,20 +384,19 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
qs = Article.objects.all()
- f = F({'published': ten_min_ago}, queryset=qs)
+ f = F({"published": ten_min_ago}, queryset=qs)
self.assertEqual(len(f.qs), 1)
- self.assertQuerysetEqual(f.qs, [2], lambda o: o.pk)
+ 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)
+ 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)
+ 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):
@@ -422,94 +411,93 @@ class DurationFilterTests(TestCase):
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))
+ duration=datetime.timedelta(hours=82, minutes=22),
+ )
self.r2 = SpacewalkRecord.objects.create(
astronaut="Michael Lopez-Alegria",
- duration=datetime.timedelta(hours=67, minutes=40))
+ duration=datetime.timedelta(hours=67, minutes=40),
+ )
self.r3 = SpacewalkRecord.objects.create(
- astronaut="Jerry L. Ross",
- duration=datetime.timedelta(hours=58, minutes=32))
+ 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))
+ duration=datetime.timedelta(hours=58, minutes=30),
+ )
self.r5 = SpacewalkRecord.objects.create(
astronaut="Richard Mastracchio",
- duration=datetime.timedelta(hours=53, minutes=4))
+ duration=datetime.timedelta(hours=53, minutes=4),
+ )
def test_filtering(self):
-
class F(FilterSet):
class Meta:
model = SpacewalkRecord
- fields = ['duration']
+ 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)
+ 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)
+ 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)
+ 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)
+ 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']}
+ fields = {"duration": ["gt", "gte", "lt", "lte"]}
- qs = SpacewalkRecord.objects.order_by('-duration')
+ 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__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__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__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)
+ 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')
+ min_duration = DurationFilter(field_name="duration", lookup_expr="gte")
+ max_duration = DurationFilter(field_name="duration", lookup_expr="lte")
class Meta:
model = SpacewalkRecord
- fields = '__all__'
+ fields = "__all__"
- qs = SpacewalkRecord.objects.order_by('duration')
+ 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)
+ 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')
+ 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)
@@ -519,70 +507,67 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = Comment
- fields = ['author']
+ fields = ["author"]
qs = Comment.objects.all()
- f = F({'author': jacob.pk}, queryset=qs)
- self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False)
+ 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')
+ @override_settings(FILTERS_NULL_CHOICE_LABEL="No Author")
def test_filtering_null(self):
Article.objects.create(published=now())
- alex = User.objects.create(username='alex')
+ alex = User.objects.create(username="alex")
Article.objects.create(author=alex, published=now())
class F(FilterSet):
class Meta:
model = Article
- fields = ['author', 'name']
+ fields = ["author", "name"]
qs = Article.objects.all()
- f = F({'author': 'null'}, queryset=qs)
- self.assertQuerysetEqual(f.qs, [None], lambda o: o.author, False)
+ 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')
+ 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)
+ author = ModelChoiceFilter(field_name="author", queryset=users)
class Meta:
model = Comment
- fields = ['author']
+ 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)
+ 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)
+ 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 = 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)
@@ -592,74 +577,74 @@ def test_filtering(self):
class F(FilterSet):
class Meta:
model = User
- fields = ['favorite_books']
+ 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)
+ 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": ["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": ["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)
+ f = F({"favorite_books": ["4"]}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, [], lambda o: o.username)
- @override_settings(FILTERS_NULL_CHOICE_LABEL='No Favorites')
+ @override_settings(FILTERS_NULL_CHOICE_LABEL="No Favorites")
def test_filtering_null(self):
class F(FilterSet):
class Meta:
model = User
- fields = ['favorite_books']
+ fields = ["favorite_books"]
qs = User.objects.all()
- f = F({'favorite_books': ['null']}, queryset=qs)
+ f = F({"favorite_books": ["null"]}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['jacob'], lambda o: o.username)
+ 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']}
+ 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)
+ 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": ["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": ["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)
+ 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']
+ 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.update(
+ {"queryset": Book.objects.filter(id__in=[1, 2])}
+ )
- self.filters['favorite_books'].extra['required'] = True
+ self.filters["favorite_books"].extra["required"] = True
- qs = User.objects.all().order_by('username')
+ qs = User.objects.all().order_by("username")
# Select all the given choices.
- f = F({'favorite_books': ['1', '2']}, queryset=qs)
+ 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)
+ 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
@@ -669,7 +654,7 @@ class F(FilterSet):
class Meta:
model = Article
- fields = ['author']
+ fields = ["author"]
published = now()
Article.objects.create(published=published, author=self.alex)
@@ -679,11 +664,8 @@ class Meta:
qs = Article.objects.all()
# Select all authors.
- authors = [
- str(user.id)
- for user in User.objects.all()
- ]
- f = F({'author': authors}, queryset=qs)
+ 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(
@@ -693,38 +675,40 @@ class Meta:
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)
+ 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']
+ fields = ["price"]
- f = F({'price': 10}, queryset=Book.objects.all())
- self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title)
+ 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)
+ 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):
@@ -732,49 +716,44 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ['price']
+ fields = ["price"]
- qs = Book.objects.all().order_by('title')
+ 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)
+ 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,
+ )
-class DateRangeFilterTests(TestCase):
+ 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']
+ fields = ["date"]
@contextlib.contextmanager
def relative_to(self, today):
@@ -785,7 +764,7 @@ def relative_to(self, today):
two_months_ago = today - datetime.timedelta(days=62)
two_years_ago = today - datetime.timedelta(days=800)
- alex = User.objects.create(username='alex')
+ 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)
@@ -794,315 +773,364 @@ def relative_to(self, today):
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:
+ 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'})
+ 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)
+ 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'})
+ 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)
+ self.assertQuerySetEqual(f.qs, [1, 3, 4, 5], lambda o: o.pk, False)
def test_filtering_for_week(self):
- f = self.CommentFilter({'date': 'week'})
+ 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)
+ self.assertQuerySetEqual(f.qs, [3, 4, 5], lambda o: o.pk, False)
def test_filtering_for_yesterday(self):
- f = self.CommentFilter({'date': 'yesterday'})
+ 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)
+ self.assertQuerySetEqual(f.qs, [5], lambda o: o.pk, False)
def test_filtering_for_today(self):
- f = self.CommentFilter({'date': 'today'})
+ 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)
+ 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'}
+ 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')
+ published = DateFromToRangeFilter(field_name="date")
class Meta:
model = Comment
- fields = ['date']
+ fields = ["date"]
- results = F(data={
- 'published_after': '2016-01-02',
- 'published_before': '2016-01-03'})
+ 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))
+ published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
+ )
class F(FilterSet):
published = DateFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2016-01-02',
- 'published_before': '2016-01-03'})
+ 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')
+ @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
+ @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)))
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 14, 23, 59), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 15, 0, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 15, 1, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 16, 0, 0), tz, False)
+ )
class F(FilterSet):
published = DateFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2017-10-15',
- 'published_before': '2017-10-15'})
+ 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')
+ @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
+ @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)))
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 2, 19, 0, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 2, 18, 23, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 2, 18, 0, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 2, 17, 15, 0), tz, False)
+ )
class F(FilterSet):
published = DateFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2017-02-18',
- 'published_before': '2017-02-18'})
+ 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')
+ @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
+ @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)))
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 3, 25, 23, 59), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 3, 26, 0, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 3, 26, 2, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 3, 26, 3, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 3, 27, 0, 0), tz, False)
+ )
class F(FilterSet):
published = DateFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2017-3-26',
- 'published_before': '2017-3-26'})
+ 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')
+ @unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
+ @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)))
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 28, 23, 59), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 29, 0, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 29, 2, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 29, 3, 0), tz, False)
+ )
+ Article.objects.create(
+ published=make_aware(datetime.datetime(2017, 10, 30, 0, 0), tz, False)
+ )
class F(FilterSet):
published = DateFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2017-10-29',
- 'published_before': '2017-10-29'})
+ 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))
+ published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
+ )
class F(FilterSet):
published = DateTimeFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ fields = ["published"]
- results = F(data={
- 'published_after': '2016-01-02 10:00',
- 'published_before': '2016-01-03 19:00'})
+ results = F(
+ data={
+ "published_after": "2016-01-02 10:00",
+ "published_before": "2016-01-03 19:00",
+ }
+ )
self.assertEqual(len(results.qs), 2)
-@unittest.expectedFailure
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))
+ published=datetime.datetime(2016, 1, 1, 10, 0, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 2, 12, 45, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 18, 15, tzinfo=tz)
+ )
Article.objects.create(
- published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz))
+ published=datetime.datetime(2016, 1, 3, 19, 30, tzinfo=tz)
+ )
class F(FilterSet):
published = IsoDateTimeFromToRangeFilter()
class Meta:
model = Article
- fields = ['published']
+ 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")})
+ 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)
+ 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']
+ fields = ["time"]
- results = F(data={
- 'time_after': '8:00',
- 'time_before': '10:00'})
+ 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')
+ 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']
+ 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"}).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()))
+ 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')
+ 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']
+ fields = ["username"]
self.assertEqual(list(F().qs), list(User.objects.all()))
- self.assertEqual(list(F({'username': ['alex']}).qs),
- [User.objects.get(username='alex')])
- self.assertEqual(list(F({'username': ['alex', 'jacob']}).qs),
- list(User.objects.filter(username__in=['alex', 'jacob'])))
+ self.assertEqual(
+ list(F({"username": ["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()))
+ 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')
+ 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')
+ username = CharFilter(method="filter_username")
class Meta:
model = User
- fields = ['username']
+ 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())
+ 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):
@@ -1113,185 +1141,184 @@ class F(FilterSet):
class Meta:
model = User
- fields = ['username']
+ 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())
+ 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)
+ name="account1", in_good_standing=False, friendly=False
+ )
a2 = Account.objects.create(
- name='account2', in_good_standing=True, friendly=True)
+ name="account2", in_good_standing=True, friendly=True
+ )
a3 = Account.objects.create(
- name='account3', in_good_standing=True, friendly=False)
+ name="account3", in_good_standing=True, friendly=False
+ )
a4 = Account.objects.create(
- name='account4', in_good_standing=False, friendly=True)
+ 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',)
+ fields = ("account",)
f = F()
self.assertEqual(f.qs.count(), 4)
- f = F({'account': 1})
+ f = F({"account": 1})
self.assertEqual(f.qs.count(), 1)
- self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk)
+ 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'], }
+ fields = {
+ "account": ["exact"],
+ }
f = F()
self.assertEqual(f.qs.count(), 4)
- f = F({'account': 1})
+ f = F({"account": 1})
self.assertEqual(f.qs.count(), 1)
- self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk)
+ self.assertQuerySetEqual(f.qs, [1], lambda o: o.pk)
def test_reverse_o2o_relation(self):
class F(FilterSet):
class Meta:
model = Account
- fields = ('profile',)
+ fields = ("profile",)
f = F()
self.assertEqual(f.qs.count(), 4)
- f = F({'profile': 1})
+ f = F({"profile": 1})
self.assertEqual(f.qs.count(), 1)
- self.assertQuerysetEqual(f.qs, [1], lambda o: o.pk)
+ 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',)
+ fields = ("account__in_good_standing",)
f = F()
self.assertEqual(f.qs.count(), 4)
- f = F({'account__in_good_standing': '2'})
+ f = F({"account__in_good_standing": "2"})
self.assertEqual(f.qs.count(), 2)
- self.assertQuerysetEqual(f.qs, [2, 3], lambda o: o.pk, False)
+ 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',)
+ 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'})
+ 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)
+ 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',)
+ fields = ("profile__likes_coffee",)
f = F()
self.assertEqual(f.qs.count(), 4)
- f = F({'profile__likes_coffee': '2'})
+ f = F({"profile__likes_coffee": "2"})
self.assertEqual(f.qs.count(), 2)
- self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False)
+ 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')
+ 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'})
+ 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)
+ 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")
+ 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',)
+ fields = ("company",)
f = F()
self.assertEqual(f.qs.count(), 3)
- f = F({'company': 1})
+ f = F({"company": 1})
self.assertEqual(f.qs.count(), 2)
- self.assertQuerysetEqual(f.qs, [1, 3], lambda o: o.pk, False)
+ 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')
+ 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)
+ 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']
+ fields = ["comments"]
qs = User.objects.all()
- f = F({'comments': [2]}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
+ 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']
+ fields = ["comments"]
- f = F({'comments': 2}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
+ 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')
+ 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)
@@ -1300,88 +1327,79 @@ def test_fk_relation_attribute(self):
class F(FilterSet):
class Meta:
model = Article
- fields = ['author__username']
+ 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)
+ 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']
+ fields = ["author__username"]
- self.assertEqual(F({'author__username': 'alex'}).qs.count(), 2)
+ 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')
+ 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)
+ 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']
+ fields = ["comments__text"]
qs = User.objects.all()
- f = F({'comments__text': 'comment 2'}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
+ 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']
+ fields = ["comments__text"]
- f = F({'comments__text': 'comment 2'}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['alex'], lambda o: o.username)
+ f = F({"comments__text": "comment 2"}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)
- @unittest.skip('todo - need correct models')
+ @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")
+ 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')
+ fields = ("locations__zip_code", "locations__open_days")
- f = F({'locations__zip_code': '90210',
- 'locations__open_days': 'WEEKEND'})
+ 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 = 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)
@@ -1389,108 +1407,125 @@ def test_m2m_relation(self):
class F(FilterSet):
class Meta:
model = User
- fields = ['favorite_books']
+ 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)
+ 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": ["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": ["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)
+ 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']
+ 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)
+ 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']
+ fields = ["lovers"]
- f = F({'lovers': 1}, queryset=qs)
- self.assertQuerysetEqual(
- f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)
+ 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']
+ 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)
+ 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)
+ f = F({"favorite_books__title": "Rainbow Six"}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, ["alex"], lambda o: o.username)
+ # No choices given, so filtering does nothing.
class F(FilterSet):
favorite_books__title = MultipleChoiceFilter()
class Meta:
model = User
- fields = ['favorite_books__title']
+ 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)
+ self.assertEqual(len(f.filters["favorite_books__title"].field.choices), 0)
+
+ # Specifying choices allows filter to work. (See also AllValues variants.)
+ class F(FilterSet):
+ favorite_books__title = MultipleChoiceFilter(
+ choices=[
+ (b.title, b.title) for b in Book.objects.all()
+ ]
+ )
+
+ class Meta:
+ model = User
+ fields = ["favorite_books__title"]
+
+ f = F({'favorite_books__title': ["Ender's Game", "Snowcrash"]}, queryset=qs)
+ self.assertIs(True, f.form.is_valid(), list(f.filters["favorite_books__title"].field.choices))
+ 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']
+ fields = ["favorite_books__title"]
- f = F({'favorite_books__title': "Snowcrash"}, queryset=qs)
- self.assertQuerysetEqual(f.qs, ['aaron'], lambda o: o.username)
+ 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']
+ 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)
+ 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)
+ 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']
+ fields = ["lovers__username"]
f = F()
- self.assertEqual(
- len(f.filters['lovers__username'].field.choices), 0)
+ self.assertEqual(len(f.filters["lovers__username"].field.choices), 0)
# f = F({'lovers__username': ['1', '3']},
# queryset=qs)
- # self.assertQuerysetEqual(
+ # self.assertQuerySetEqual(
# f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)
class F(FilterSet):
@@ -1498,62 +1533,64 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ['lovers__username']
+ fields = ["lovers__username"]
- f = F({'lovers__username': "alex"}, queryset=qs)
- self.assertQuerysetEqual(
- f.qs, ["Ender's Game", "Rainbow Six"], lambda o: o.title)
+ 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']
+ 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)
+ 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)
+ 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']
+ 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)
+ 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)
+ f = F({"lovers__status": 1, "lovers__username": "jacob"}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, [], lambda o: o.title)
- @unittest.skip('todo')
+ @unittest.skip("todo")
def test_fk_relation_on_m2m_relation(self):
pass
- @unittest.skip('todo')
+ @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 = 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)
@@ -1563,20 +1600,19 @@ def test_relation(self):
class F(FilterSet):
class Meta:
model = Node
- fields = ['adjacents']
+ 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)
+ 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 = 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)
@@ -1586,112 +1622,131 @@ def test_forward_relation(self):
class F(FilterSet):
class Meta:
model = DirectedNode
- fields = ['outbound_nodes']
+ 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)
+ 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']
+ 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)
+ 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')
+@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')
+ 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']}
+ fields = {"published": ["hour__gte"]}
qs = Article.objects.all()
- f = F({'published__hour__gte': 17}, queryset=qs)
+ f = F({"published__hour__gte": 17}, queryset=qs)
self.assertEqual(len(f.qs), 1)
- self.assertQuerysetEqual(f.qs, [a.pk], lambda o: o.pk)
+ 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)
+ price = LookupChoiceFilter(
+ lookup_choices=["lt", "gt"], field_class=forms.DecimalField
+ )
class Meta:
model = Book
- fields = ['price']
+ 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)
+ 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'})
+ 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.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)
+ 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'})
+ f = self.BookFilter({"price": "asdf", "price_lookup": "lt"})
self.assertFalse(f.is_valid())
- self.assertEqual(f.errors, {
- 'price': ['Enter a number.'],
- })
+ self.assertEqual(
+ f.errors,
+ {
+ "price": ["Enter a number."],
+ },
+ )
def test_lookup_choices_validation(self):
- f = self.BookFilter({'price': '1', 'price_lookup': 'asdf'})
+ 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.'],
- })
+ 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'})
+ f = self.BookFilter({"price": "1"})
self.assertFalse(f.is_valid())
- self.assertEqual(f.errors, {
- 'price': ['Select a lookup.'],
- })
+ self.assertEqual(
+ f.errors,
+ {
+ "price": ["Select a lookup."],
+ },
+ )
-@override_settings(TIME_ZONE='UTC')
+@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)
+ 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)
@@ -1706,124 +1761,145 @@ class UserFilter(FilterSet):
class Meta:
model = User
fields = {
- 'username': ['in'],
- 'status': ['in'],
+ "username": ["in"],
+ "status": ["in"],
}
class ArticleFilter(FilterSet):
class Meta:
model = Article
fields = {
- 'author': ['in'],
- 'published': ['in'],
+ "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')
+ 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')
+ 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]),
+ (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'))
+ 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')
+ 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]),
+ (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'))
+ 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')
+ 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]),
+ (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'))
+ 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')
+ 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]),
+ (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'))
+ self.assertQuerySetEqual(
+ F(params, queryset=qs).qs, expected, attrgetter("pk")
+ )
-@override_settings(TIME_ZONE='UTC')
+@override_settings(TIME_ZONE="UTC")
class CSVRangeFilterTests(TestCase):
-
class ArticleFilter(FilterSet):
class Meta:
model = Article
fields = {
- 'published': ['range'],
+ "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)
+ 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)
@@ -1835,9 +1911,9 @@ def setUpTestData(cls):
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')
+ 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
@@ -1846,108 +1922,144 @@ def test_filtering(self):
self.assertEqual(f.qs.count(), 4)
# empty value is a noop
- f = F({'published__range': ''})
+ 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': ','})
+ f = F({"published__range": ","})
self.assertEqual(f.qs.count(), 0)
- f = F({'published__range': '%s' % (self.before_5pm, )})
+ 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, )})
+ 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, )})
+ 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, )})
+ 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)
+ 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', )
- )
+ o = OrderingFilter(fields=("username",))
class Meta:
model = User
- fields = ['username']
+ 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'])
+ tests = [
+ {"o": "username"},
+ QueryDict("o=username,"),
+ ]
+ for data in tests:
+ with self.subTest(data=data):
+ f = F(data, 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', )
- )
+ o = OrderingFilter(widget=forms.Select, fields=("username",))
class Meta:
model = User
- fields = ['username']
+ 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'])
+ f = F({"o": "username"}, queryset=qs)
+ names = f.qs.values_list("username", flat=True)
+ self.assertEqual(list(names), ["aaron", "alex", "carl", "jacob"])
+ def test_csv_input(self):
+ class F(FilterSet):
+ o = OrderingFilter(widget=forms.Select, fields=("username",),)
-class MiscFilterSetTests(TestCase):
+ class Meta:
+ model = User
+ fields = ["username"]
+ qs = User.objects.all()
+ tests = [
+ {"o": ","},
+ QueryDict("o=%2c"),
+ QueryDict("o=,"),
+ ]
+ for data in tests:
+ with self.subTest(data=data):
+ f = F(data, queryset=qs)
+ self.assertIs(True, f.is_valid())
+ names = f.qs.values_list("username", flat=True)
+ self.assertEqual(list(names), ['alex', 'jacob', 'aaron', 'carl'])
+
+
+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)
+ User.objects.create(username="alex", last_name="johnson", status=1)
+ User.objects.create(username="jacob", last_name="johnson", status=2)
+ User.objects.create(username="aaron", last_name="white", status=2)
+ User.objects.create(username="carl", last_name="black", status=0)
def test_filtering_with_declared_filters(self):
class F(FilterSet):
- account = CharFilter(field_name='username')
+ account = CharFilter(field_name="username")
class Meta:
model = User
- fields = ['account']
+ fields = ["account"]
qs = MockQuerySet()
- F({'account': 'jdoe'}, queryset=qs).qs
- qs.all.return_value.filter.assert_called_with(username__exact='jdoe')
+ 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)
+ 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']
+ 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": "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)
+ 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
@@ -1957,36 +2069,69 @@ class F(FilterSet):
class Meta:
model = User
- fields = ['status']
+ fields = ["status"]
qs = User.objects.all()
- users = ['alex', 'jacob', 'aaron', 'carl']
+ users = ["alex", "jacob", "aaron", "carl"]
f = F(queryset=qs)
- self.assertQuerysetEqual(f.qs.order_by('pk'), users, lambda o: o.username)
+ 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)
+ 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']
+ 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)
+ f = F({"status": "0"}, queryset=qs)
self.assertEqual(len(f.qs), 1)
self.assertEqual(f.qs.count(), 1)
- f = F({'status': '1'}, queryset=qs)
+ f = F({"status": "1"}, queryset=qs)
self.assertEqual(len(f.qs), 1)
self.assertEqual(f.qs.count(), 1)
- f = F({'status': '2'}, queryset=qs)
+ f = F({"status": "2"}, queryset=qs)
self.assertEqual(len(f.qs), 2)
self.assertEqual(f.qs.count(), 2)
+
+ def test_filtering_with_widgets(self):
+ class CharInFilter(BaseInFilter, CharFilter):
+ pass
+
+ class F(FilterSet):
+ last_name = CharInFilter(widget=QueryArrayWidget)
+ username = CharInFilter()
+
+ class Meta:
+ model = User
+ fields = ["last_name", "username"]
+
+ qs = User.objects.all()
+
+ f = F({"last_name": ["johnson"]}, queryset=qs)
+ self.assertQuerySetEqual(
+ f.qs, ["alex", "jacob"], lambda o: o.username, ordered=False
+ )
+
+ f = F({"last_name": ["johnson"], "username": "carl"}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, [], lambda o: o.username, ordered=False)
+
+ f = F({"last_name": ["johnson"], "username": "jacob"}, queryset=qs)
+ self.assertQuerySetEqual(f.qs, ["jacob"], lambda o: o.username, ordered=False)
+
+ f = F(
+ {"last_name": ["johnson", "white"], "username": "jacob, carl, aaron"},
+ queryset=qs,
+ )
+ self.assertQuerySetEqual(
+ f.qs, ["jacob", "aaron"], lambda o: o.username, ordered=False
+ )
diff --git a/tests/test_filters.py b/tests/test_filters.py
index d63074d7f..290fbc135 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -1,7 +1,7 @@
import inspect
-import mock
from collections import OrderedDict
from datetime import date, datetime, time, timedelta
+from unittest import mock
from django import forms
from django.test import TestCase, override_settings
@@ -16,7 +16,7 @@
IsoDateTimeRangeField,
Lookup,
RangeField,
- TimeRangeField
+ TimeRangeField,
)
from django_filters.filters import (
AllValuesFilter,
@@ -45,38 +45,35 @@
TimeFilter,
TimeRangeFilter,
TypedMultipleChoiceFilter,
- UUIDFilter
+ UUIDFilter,
)
from tests.models import Book, User
class ModuleImportTests(TestCase):
def is_filter(self, name, value):
- return (
- isinstance(value, type) and issubclass(value, Filter)
- )
+ 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)
+ 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)
+ 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.lookup_expr, "exact")
self.assertEqual(f.exclude, False)
def test_creation_order(self):
@@ -90,63 +87,58 @@ def test_default_field(self):
self.assertIsInstance(field, forms.Field)
def test_field_with_single_lookup_expr(self):
- f = Filter(lookup_expr='iexact')
+ f = Filter(lookup_expr="iexact")
field = f.field
self.assertIsInstance(field, forms.Field)
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')
+ 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')
+ 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')
+ 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')
+ 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:
+ 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)
+ mocked.assert_called_once_with(required=False, label=mock.ANY)
def test_filtering(self):
- qs = mock.Mock(spec=['filter'])
+ qs = mock.Mock(spec=["filter"])
f = Filter()
- result = f.filter(qs, 'value')
- qs.filter.assert_called_once_with(None__exact='value')
+ 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'])
+ qs = mock.Mock(spec=["filter", "exclude"])
f = Filter(exclude=True)
- result = f.filter(qs, 'value')
- qs.exclude.assert_called_once_with(None__exact='value')
+ 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')
+ 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, '')
+ result = f.filter(qs, "")
self.assertListEqual(qs.method_calls, [])
self.assertEqual(qs, result)
@@ -161,20 +153,19 @@ 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')
+ 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')
+ 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
@@ -182,7 +173,6 @@ def test_default_field(self):
class UUIDFilterTests(TestCase):
-
def test_default_field(self):
f = UUIDFilter()
field = f.field
@@ -190,50 +180,48 @@ def test_default_field(self):
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')
+ 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)
+ 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, '')
+ 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')
+ 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')
+ 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
@@ -241,131 +229,182 @@ def test_default_field(self):
def test_empty_choice(self):
# default value
- f = ChoiceFilter(choices=[('a', 'a')])
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ('a', 'a'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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', )
+ 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'),
- ])
+ 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')
+ yield ("a", "a")
+ yield ("b", "b")
f = ChoiceFilter(choices=choices)
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ('a', 'a'),
- ('b', 'b'),
- ])
+ 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')
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = MultipleChoiceFilter()
with self.assertRaises(TypeError):
- f.filter(qs, ['value'])
+ f.filter(qs, ["value"])
def test_conjoined_default_value(self):
f = MultipleChoiceFilter()
@@ -378,72 +417,78 @@ def test_conjoined_true(self):
def test_is_noop_false(self):
f = MultipleChoiceFilter(required=False)
f.always_filter = False
- self.assertFalse(f.is_noop(None, ['value']))
+ self.assertFalse(f.is_noop(None, ["value"]))
def test_filtering(self):
- qs = mock.Mock(spec=['filter'])
- f = MultipleChoiceFilter(field_name='somefield')
- with mock.patch('django_filters.filters.Q') as mockQclass:
+ 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'])
+ f.filter(qs, ["value"])
- self.assertEqual(mockQclass.call_args_list,
- [mock.call(), mock.call(somefield='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:
+ 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'])
+ f.filter(qs, ["value"])
- self.assertEqual(mockQclass.call_args_list,
- [mock.call(), mock.call(somefield='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_with_lookup_expr(self):
- qs = mock.Mock(spec=['filter'])
- f = MultipleChoiceFilter(field_name='somefield', lookup_expr='icontains')
- with mock.patch('django_filters.filters.Q') as mockQclass:
+ qs = mock.Mock(spec=["filter"])
+ f = MultipleChoiceFilter(field_name="somefield", lookup_expr="icontains")
+ with mock.patch("django_filters.filters.Q") as mockQclass:
mockQ1, mockQ2 = mock.MagicMock(), mock.MagicMock()
mockQclass.side_effect = [mockQ1, mockQ2]
- f.filter(qs, ['value'])
+ f.filter(qs, ["value"])
- self.assertEqual(mockQclass.call_args_list,
- [mock.call(), mock.call(somefield__icontains='value')])
+ self.assertEqual(
+ mockQclass.call_args_list,
+ [mock.call(), mock.call(somefield__icontains="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_on_required_skipped_when_len_of_value_is_len_of_field_choices(self):
+ 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 = 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'])
+ f.field.choices = ["some", "values", "here"]
+ result = f.filter(qs, ["some", "values", "here"])
self.assertEqual(qs, result)
- result = f.filter(qs, ['other', 'values', 'there'])
+ 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']
+ f = MultipleChoiceFilter(field_name="somefield")
+ f.field.choices = ["some", "values", "here"]
result = f.filter(qs, [])
self.assertEqual(qs, result)
@@ -453,7 +498,7 @@ def test_filter_conjoined_true(self):
users that have all of this books.
"""
- book_kwargs = {'price': 1, 'average_rating': 1}
+ book_kwargs = {"price": 1, "average_rating": 1}
books = []
books.append(Book.objects.create(**book_kwargs))
books.append(Book.objects.create(**book_kwargs))
@@ -475,54 +520,72 @@ def test_filter_conjoined_true(self):
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),
- []),
+ (
+ (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)
+ 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')]
+ 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]))
+ "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'])
+ qs = mock.Mock(spec=["filter"])
f = TypedMultipleChoiceFilter()
with self.assertRaises(TypeError):
- f.filter(qs, ['value'])
+ f.filter(qs, ["value"])
def test_conjoined_default_value(self):
f = TypedMultipleChoiceFilter()
@@ -533,54 +596,58 @@ def test_conjoined_true(self):
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:
+ 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'])
+ f.filter(qs, ["value"])
- self.assertEqual(mockQclass.call_args_list,
- [mock.call(), mock.call(somefield='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:
+ 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'])
+ f.filter(qs, ["value"])
- self.assertEqual(mockQclass.call_args_list,
- [mock.call(), mock.call(somefield='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):
+ 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 = 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'])
+ f.field.choices = ["some", "values", "here"]
+ result = f.filter(qs, ["some", "values", "here"])
self.assertEqual(qs, result)
- result = f.filter(qs, ['other', 'values', 'there'])
+ 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']
+ f = TypedMultipleChoiceFilter(field_name="somefield")
+ f.field.choices = ["some", "values", "here"]
result = f.filter(qs, [])
self.assertEqual(qs, result)
@@ -590,7 +657,7 @@ def test_filter_conjoined_true(self):
users that have all of this books.
"""
- book_kwargs = {'price': 1, 'average_rating': 1}
+ book_kwargs = {"price": 1, "average_rating": 1}
books = []
books.append(Book.objects.create(**book_kwargs))
books.append(Book.objects.create(**book_kwargs))
@@ -612,44 +679,64 @@ def test_filter_conjoined_true(self):
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),
- []),
+ (
+ (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)
+ 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')]
+ 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]))
+ "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
@@ -657,7 +744,6 @@ def test_default_field(self):
class DateTimeFilterTests(TestCase):
-
def test_default_field(self):
f = DateTimeFilter()
field = f.field
@@ -665,7 +751,6 @@ def test_default_field(self):
class TimeFilterTests(TestCase):
-
def test_default_field(self):
f = TimeFilter()
field = f.field
@@ -673,7 +758,6 @@ def test_default_field(self):
class DurationFilterTests(TestCase):
-
def test_default_field(self):
f = DurationFilter()
field = f.field
@@ -691,21 +775,26 @@ def get_mock_queryset(self):
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', )
+ 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'),
- ])
+ 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()
@@ -732,7 +821,9 @@ def test_get_queryset_override(self):
qs = self.get_mock_queryset()
class F(ModelChoiceFilter):
- get_queryset = mock.create_autospec(ModelChoiceFilter.get_queryset, return_value=qs)
+ get_queryset = mock.create_autospec(
+ ModelChoiceFilter.get_queryset, return_value=qs
+ )
f = F()
f.parent = mock.Mock(request=request)
@@ -743,20 +834,25 @@ class F(ModelChoiceFilter):
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', )
+ 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'),
- ])
+ 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()
@@ -767,17 +863,17 @@ def test_default_field_with_queryset(self):
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')
+ 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(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, ["Firstname"])), [user])
self.assertEqual(list(f.filter(qs, [user])), [user])
def test_callable_queryset(self):
@@ -795,14 +891,13 @@ def test_callable_queryset(self):
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'])
+ qs = mock.Mock(spec=["filter"])
f = NumberFilter()
f.filter(qs, 1)
qs.filter.assert_called_once_with(None__exact=1)
@@ -812,7 +907,7 @@ def test_filtering(self):
qs.filter.assert_called_once_with(None__exact=0)
def test_filtering_exclude(self):
- qs = mock.Mock(spec=['exclude'])
+ qs = mock.Mock(spec=["exclude"])
f = NumberFilter(exclude=True)
f.filter(qs, 1)
qs.exclude.assert_called_once_with(None__exact=1)
@@ -823,28 +918,27 @@ def test_filtering_exclude(self):
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'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = NumericRangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -852,26 +946,26 @@ def test_filtering_skipped_with_none_value(self):
def test_field_with_lookup_expr(self):
qs = mock.Mock()
value = mock.Mock(start=20, stop=30)
- f = NumericRangeFilter(lookup_expr=('overlap'))
+ 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'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
value = mock.Mock(start=None, stop=30)
f = NumericRangeFilter()
f.filter(qs, value)
@@ -900,42 +994,41 @@ def test_filtering_distinct(self):
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'])
+ 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'])
+ 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'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = RangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -943,7 +1036,7 @@ def test_filtering_skipped_with_none_value(self):
def test_filtering_ignores_lookup_expr(self):
qs = mock.Mock()
value = mock.Mock(start=20, stop=30)
- f = RangeFilter(lookup_expr='gte')
+ f = RangeFilter(lookup_expr="gte")
f.filter(qs, value)
qs.filter.assert_called_once_with(None__range=(20, 30))
@@ -970,17 +1063,16 @@ def test_filtering_distinct(self):
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'])
+ 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'])
+ self.assertIs(f.choices, f.extra["choices"])
def test_default_field(self):
f = DateRangeFilter()
@@ -991,81 +1083,76 @@ def test_filtering(self):
# skip filtering, as it's an empty value
qs = mock.Mock(spec=[])
f = DateRangeFilter()
- result = f.filter(qs, '')
+ 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
+ # Field validation should prevent this from occurring
qs = mock.Mock(spec=[])
f = DateRangeFilter()
with self.assertRaises(AssertionError):
- f.filter(qs, 'tomorrow')
+ 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'"
+ 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})
+ 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_choices_with_optgroups_dont_mistmatch(self):
+ DateRangeFilter(
+ choices=[("group", ("a", "a")), ("b", "b")], filters={"a": None, "b": None}
+ )
def test_filtering_for_this_year(self):
- qs = mock.Mock(spec=['filter'])
- with mock.patch('django_filters.filters.now') as mock_now:
+ 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)
+ 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:
+ 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')
+ f.filter(qs, "month")
qs.filter.assert_called_once_with(
- None__year=now_dt.year, None__month=now_dt.month)
+ 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:
+ 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')
+ f.filter(qs, "week")
self.assertEqual(
- mock_td.call_args_list,
- [mock.call(days=7), mock.call(days=1)]
+ 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:
+ 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')
+ 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)
+ 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:
+ 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')
+ 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,
@@ -1074,36 +1161,36 @@ def test_filtering_for_yesterday(self):
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'])
+ 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)))
+ None__range=(date(2015, 4, 7), date(2015, 9, 6))
+ )
def test_filtering_start(self):
- qs = mock.Mock(spec=['filter'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = DateFromToRangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1111,44 +1198,46 @@ def test_filtering_skipped_with_none_value(self):
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 = DateFromToRangeFilter(lookup_expr="gte")
f.filter(qs, value)
qs.filter.assert_called_once_with(
- None__range=(date(2015, 4, 7), date(2015, 9, 6)))
+ 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'])
+ qs = mock.Mock(spec=["filter"])
value = mock.Mock(
- start=datetime(2015, 4, 7, 8, 30), stop=datetime(2015, 9, 6, 11, 45))
+ 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)))
+ None__range=(datetime(2015, 4, 7, 8, 30), datetime(2015, 9, 6, 11, 45))
+ )
def test_filtering_start(self):
- qs = mock.Mock(spec=['filter'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = DateTimeFromToRangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1156,45 +1245,48 @@ def test_filtering_skipped_with_none_value(self):
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')
+ 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)))
+ 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'])
+ qs = mock.Mock(spec=["filter"])
value = mock.Mock(
- start=datetime(2015, 4, 7, 8, 30), stop=datetime(2015, 9, 6, 11, 45))
+ 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)))
+ None__range=(datetime(2015, 4, 7, 8, 30), datetime(2015, 9, 6, 11, 45))
+ )
def test_filtering_start(self):
- qs = mock.Mock(spec=['filter'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = IsoDateTimeFromToRangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1202,44 +1294,44 @@ def test_filtering_skipped_with_none_value(self):
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')
+ 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)))
+ 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'])
+ 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)))
+ qs.filter.assert_called_once_with(None__range=(time(10, 15), time(12, 30)))
def test_filtering_start(self):
- qs = mock.Mock(spec=['filter'])
+ 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'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = TimeRangeFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1247,14 +1339,12 @@ def test_filtering_skipped_with_none_value(self):
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 = TimeRangeFilter(lookup_expr="gte")
f.filter(qs, value)
- qs.filter.assert_called_once_with(
- None__range=(time(10, 15), time(12, 30)))
+ 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):
@@ -1262,9 +1352,14 @@ def test_default_field_without_assigning_model(self):
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'])
+ 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
@@ -1272,86 +1367,99 @@ def test_default_field_with_assigning_model(self):
self.assertIsInstance(field, forms.ChoiceField)
def test_empty_value_in_choices(self):
- f = AllValuesFilter(field_name='username')
+ f = AllValuesFilter(field_name="username")
f.model = User
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ])
+ 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'))
+ 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'))
+ 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 = 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
+ 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')
+ 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'
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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'),
- ])
+ 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=[])
+ f = LookupChoiceFilter(field_name="username", lookup_choices=[])
choice_field = f.field.fields[1]
- self.assertEqual(list(choice_field.choices), [('', '---------')])
+ self.assertEqual(list(choice_field.choices), [("", "---------")])
def test_lookup_choices_empty_label_disabled(self):
- f = LookupChoiceFilter(field_name='username', empty_label=None, lookup_choices=[])
+ 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')
+ 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)
@@ -1363,8 +1471,8 @@ class NumberInFilter(BaseCSVFilter, NumberFilter):
class DateTimeYearInFilter(BaseCSVFilter, DateTimeFilter):
pass
- self.number_in = NumberInFilter(lookup_expr='in')
- self.datetimeyear_in = DateTimeYearInFilter(lookup_expr='year__in')
+ self.number_in = NumberInFilter(lookup_expr="in")
+ self.datetimeyear_in = DateTimeYearInFilter(lookup_expr="year__in")
def test_default_field(self):
f = BaseCSVFilter()
@@ -1375,21 +1483,21 @@ 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')
+ 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')
+ self.assertEqual(field.__class__.__name__, "DateTimeYearInField")
def test_filtering(self):
- qs = mock.Mock(spec=['filter'])
+ 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'])
+ qs = mock.Mock(spec=["filter"])
f = self.number_in
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1406,7 +1514,7 @@ def test_filtering(self):
class NumberInFilter(BaseInFilter, NumberFilter):
pass
- qs = mock.Mock(spec=['filter'])
+ qs = mock.Mock(spec=["filter"])
f = NumberInFilter()
f.filter(qs, [1, 2])
qs.filter.assert_called_once_with(None__in=[1, 2])
@@ -1417,7 +1525,7 @@ def test_filtering(self):
class NumberInFilter(BaseRangeFilter, NumberFilter):
pass
- qs = mock.Mock(spec=['filter'])
+ qs = mock.Mock(spec=["filter"])
f = NumberInFilter()
f.filter(qs, [1, 2])
qs.filter.assert_called_once_with(None__range=[1, 2])
@@ -1430,25 +1538,25 @@ def test_default_field(self):
self.assertIsInstance(field, forms.ChoiceField)
def test_filtering(self):
- qs = mock.Mock(spec=['order_by'])
+ qs = mock.Mock(spec=["order_by"])
f = OrderingFilter()
- f.filter(qs, ['a', 'b'])
- qs.order_by.assert_called_once_with('a', 'b')
+ 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'])
+ qs = mock.Mock(spec=["order_by"])
f = OrderingFilter()
- f.filter(qs, ['-a'])
- qs.order_by.assert_called_once_with('-a')
+ 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')
+ 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'])
+ qs = mock.Mock(spec=["order_by"])
f = OrderingFilter()
result = f.filter(qs, None)
self.assertEqual(qs, result)
@@ -1456,90 +1564,102 @@ def test_filtering_skipped_with_none_value(self):
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')),
+ choices=(("a", "A"), ("b", "B")),
+ fields=(("a", "c"), ("b", "d")),
)
- self.assertSequenceEqual(list(f.field.choices), (
- ('', '---------'),
- ('a', 'A'),
- ('b', 'B'),
- ))
+ self.assertSequenceEqual(
+ list(f.field.choices),
+ (
+ ("", "---------"),
+ ("a", "A"),
+ ("b", "B"),
+ ),
+ )
def test_choices_from_fields(self):
f = OrderingFilter(
- fields=(('a', 'c'), ('b', 'd')),
+ fields=(("a", "c"), ("b", "d")),
)
- self.assertSequenceEqual(list(f.field.choices), (
- ('', '---------'),
- ('c', 'C'),
- ('-c', 'C (descending)'),
- ('d', 'D'),
- ('-d', 'D (descending)'),
- ))
+ 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'},
+ 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)'),
- ))
+ 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'],
+ fields=["username"],
field_labels={
- 'username': 'BLABLA',
- '-username': 'XYZXYZ',
- }
+ "username": "BLABLA",
+ "-username": "XYZXYZ",
+ },
)
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ('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.assertIn("a", f({"a": "b"}))
- self.assertEqual(
- f(O([('a', 'b'), ('c', 'd')])),
- O([('a', 'b'), ('c', 'd')])
- )
+ 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"), ("c", "d")]), O([("a", "b"), ("c", "d")]))
- self.assertEqual(
- f(['a', 'b']),
- O([('a', 'a'), ('b', 'b')])
- )
+ 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).")
+ 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.")
+ 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.")
+ self.assertEqual(
+ str(ctx.exception),
+ "'fields' must contain strings or (field name, param name) pairs.",
+ )
def test_widget(self):
f = OrderingFilter()
@@ -1549,34 +1669,42 @@ def test_widget(self):
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)')
+ 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'])
+ with translation.override("pl"):
+ f = OrderingFilter(fields=["username"])
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ('username', 'Nazwa użytkownika'),
- ('-username', 'Nazwa użytkownika (malejąco)'),
- ])
+ 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'):
+ with translation.override("pl"):
f = OrderingFilter(
- fields=['username'],
- field_labels={'username': 'BLABLA'},
+ fields=["username"],
+ field_labels={"username": "BLABLA"},
)
- self.assertEqual(list(f.field.choices), [
- ('', '---------'),
- ('username', 'BLABLA'),
- ('-username', 'BLABLA (malejąco)'),
- ])
+ self.assertEqual(
+ list(f.field.choices),
+ [
+ ("", "---------"),
+ ("username", "BLABLA"),
+ ("-username", "BLABLA (malejąco)"),
+ ],
+ )
def test_help_text(self):
# regression test for #756 - the usual 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')
+ self.assertEqual(OrderingFilter().field.help_text, "")
+ self.assertEqual(OrderingFilter(help_text="a").field.help_text, "a")
diff --git a/tests/test_filterset.py b/tests/test_filterset.py
index d3cd949cd..e8da1469d 100644
--- a/tests/test_filterset.py
+++ b/tests/test_filterset.py
@@ -1,5 +1,5 @@
-import mock
import unittest
+from unittest import mock
from django.db import models
from django.test import TestCase, override_settings
@@ -17,7 +17,7 @@
ModelChoiceFilter,
ModelMultipleChoiceFilter,
NumberFilter,
- UUIDFilter
+ UUIDFilter,
)
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS, FilterSet
from django_filters.widgets import BooleanWidget
@@ -38,28 +38,26 @@
SubnetMaskField,
User,
UUIDTestModel,
- Worker
+ Worker,
)
from .utils import MockQuerySet
class HelperMethodsTests(TestCase):
-
- @unittest.skip('todo')
+ @unittest.skip("todo")
def test_get_declared_filters(self):
pass
- @unittest.skip('todo')
+ @unittest.skip("todo")
def test_filters_for_model(self):
pass
- @unittest.skip('todo')
+ @unittest.skip("todo")
def test_filterset_factory(self):
pass
class DbFieldDefaultFiltersTests(TestCase):
-
def test_expected_db_fields_get_filters(self):
to_check = [
models.BooleanField,
@@ -106,214 +104,199 @@ def test_expected_db_fields_do_not_get_filters(self):
class FilterSetFilterForFieldTests(TestCase):
-
def test_filter_found_for_field(self):
- f = User._meta.get_field('username')
- result = FilterSet.filter_for_field(f, 'username')
+ f = User._meta.get_field("username")
+ result = FilterSet.filter_for_field(f, "username")
self.assertIsInstance(result, CharFilter)
- self.assertEqual(result.field_name, 'username')
+ 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')
+ f = UUIDTestModel._meta.get_field("uuid")
+ result = FilterSet.filter_for_field(f, "uuid")
self.assertIsInstance(result, UUIDFilter)
- self.assertEqual(result.field_name, 'uuid')
+ 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')
+ f = User._meta.get_field("id")
+ result = FilterSet.filter_for_field(f, "id")
self.assertIsInstance(result, NumberFilter)
- self.assertEqual(result.field_name, 'id')
+ 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')
+ 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)
+ 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')
+ 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'])
+ 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')
+ 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')
+ f = NetworkSetting._meta.get_field("mask")
with self.assertRaises(AssertionError) as excinfo:
- FilterSet.filter_for_field(f, 'mask')
+ 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])
+ excinfo.exception.args[0],
+ )
def test_symmetrical_selfref_m2m_field(self):
- f = Node._meta.get_field('adjacents')
- result = FilterSet.filter_for_field(f, 'adjacents')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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')
+ self.assertEqual(result.field_name, "date")
- @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR='icontains')
+ @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR="icontains")
def test_modified_default_lookup(self):
- f = User._meta.get_field('username')
- result = FilterSet.filter_for_field(f, 'username')
+ f = User._meta.get_field("username")
+ result = FilterSet.filter_for_field(f, "username")
self.assertIsInstance(result, CharFilter)
- self.assertEqual(result.lookup_expr, 'icontains')
+ self.assertEqual(result.lookup_expr, "icontains")
- @unittest.skip('todo')
+ @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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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,
+ "filter_class": BooleanFilter,
+ "extra": lambda f: {
+ "widget": BooleanWidget,
},
},
}
- f = Article._meta.get_field('author')
- result, params = OFilterSet.filter_for_lookup(f, 'isnull')
+ f = Article._meta.get_field("author")
+ result, params = OFilterSet.filter_for_lookup(f, "isnull")
self.assertEqual(result, BooleanFilter)
- self.assertEqual(params['widget'], BooleanWidget)
+ 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')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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)
+ 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')
+ 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
+ self.assertEqual(result.field_name, "lovers")
+ self.assertEqual(result.lookup_expr, "isnull")
class FilterSetClassCreationTests(TestCase):
-
def test_no_filters(self):
class F(FilterSet):
pass
@@ -326,47 +309,47 @@ class F(FilterSet):
username = CharFilter()
self.assertEqual(len(F.declared_filters), 1)
- self.assertListEqual(list(F.declared_filters), ['username'])
+ self.assertListEqual(list(F.declared_filters), ["username"])
self.assertEqual(len(F.base_filters), 1)
- self.assertListEqual(list(F.base_filters), ['username'])
+ self.assertListEqual(list(F.base_filters), ["username"])
- @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR='icontains')
+ @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR="icontains")
def test_declaring_filter_other_default_lookup(self):
class F(FilterSet):
username = CharFilter()
- self.assertEqual(F.base_filters['username'].lookup_expr, 'icontains')
+ self.assertEqual(F.base_filters["username"].lookup_expr, "icontains")
def test_model_derived(self):
class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ 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'])
+ self.assertListEqual(list(F.base_filters), ["title", "price", "average_rating"])
- @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR='icontains')
+ @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR="icontains")
def test_model_derived_other_default_lookup(self):
class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ fields = "__all__"
for filter_ in F.base_filters.values():
- self.assertEqual(filter_.lookup_expr, 'icontains')
+ self.assertEqual(filter_.lookup_expr, "icontains")
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)
+ str(excinfo.exception),
)
def test_model_fields_empty(self):
@@ -388,8 +371,7 @@ class Meta:
self.assertEqual(len(F.declared_filters), 0)
self.assertEqual(len(F.base_filters), 3)
- self.assertListEqual(list(F.base_filters),
- ['title', 'price', 'average_rating'])
+ self.assertListEqual(list(F.base_filters), ["title", "price", "average_rating"])
def test_declared_and_model_derived(self):
class F(FilterSet):
@@ -397,12 +379,13 @@ class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ 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'])
+ self.assertListEqual(
+ list(F.base_filters), ["title", "price", "average_rating", "username"]
+ )
def test_meta_fields_with_declared_and_model_derived(self):
class F(FilterSet):
@@ -410,36 +393,47 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ('username', 'price')
+ 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'])
+ 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'], }
+ 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', ]
+ expected_list = [
+ "price",
+ "price__gte",
+ "price__lte",
+ ]
self.assertCountEqual(list(F.base_filters), expected_list)
- @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR='lte')
+ @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR="lte")
def test_meta_fields_dictionary_derived_other_default_lookup(self):
class F(FilterSet):
-
class Meta:
model = Book
- fields = {'price': ['exact', 'gte', 'lte'], }
+ fields = {
+ "price": ["exact", "gte", "lte"],
+ }
self.assertEqual(len(F.declared_filters), 0)
self.assertEqual(len(F.base_filters), 3)
- expected_list = ['price__exact', 'price__gte', 'price', ]
+ expected_list = [
+ "price__exact",
+ "price__gte",
+ "price",
+ ]
self.assertCountEqual(list(F.base_filters), expected_list)
def test_meta_fields_containing_autofield(self):
@@ -448,11 +442,11 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ('id', 'username', 'price')
+ 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'])
+ self.assertListEqual(list(F.base_filters), ["id", "username", "price"])
def test_meta_fields_dictionary_autofield(self):
class F(FilterSet):
@@ -461,40 +455,40 @@ class F(FilterSet):
class Meta:
model = User
fields = {
- 'id': ['exact'],
- 'username': ['exact'],
+ "id": ["exact"],
+ "username": ["exact"],
}
self.assertEqual(len(F.declared_filters), 1)
self.assertEqual(len(F.base_filters), 2)
- expected_list = ['id', 'username']
+ expected_list = ["id", "username"]
self.assertCountEqual(list(F.base_filters), expected_list)
def test_meta_fields_list_containing_unknown_fields(self):
- msg = ("'Meta.fields' must not contain non-model field names: "
- "other, another")
+ msg = "'Meta.fields' must not contain non-model field names: " "other, another"
with self.assertRaisesMessage(TypeError, msg):
+
class F(FilterSet):
username = CharFilter()
class Meta:
model = Book
- fields = ('username', 'price', 'other', 'another')
+ fields = ("username", "price", "other", "another")
def test_meta_fields_dict_containing_unknown_fields(self):
msg = "'Meta.fields' must not contain non-model field names: other"
with self.assertRaisesMessage(TypeError, msg):
- class F(FilterSet):
+ class F(FilterSet):
class Meta:
model = Book
fields = {
- 'id': ['exact'],
- 'title': ['exact'],
- 'other': ['exact'],
+ "id": ["exact"],
+ "title": ["exact"],
+ "other": ["exact"],
}
def test_meta_fields_dict_containing_declarative_alias(self):
@@ -502,27 +496,29 @@ def test_meta_fields_dict_containing_declarative_alias(self):
msg = "'Meta.fields' must not contain non-model field names: other"
with self.assertRaisesMessage(TypeError, msg):
+
class F(FilterSet):
other = CharFilter()
class Meta:
model = Book
fields = {
- 'id': ['exact'],
- 'title': ['exact'],
- 'other': ['exact'],
+ "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.
+ # throw a useful exception containing the field and lookup expr.
msg = "Unsupported lookup 'flub' for field 'tests.User.username'."
with self.assertRaisesMessage(FieldLookupError, msg):
+
class F(FilterSet):
class Meta:
model = User
- fields = {'username': ['flub']}
+ fields = {"username": ["flub"]}
def test_meta_exlude_with_declared_and_declared_wins(self):
class F(FilterSet):
@@ -530,12 +526,13 @@ class F(FilterSet):
class Meta:
model = Book
- exclude = ('username', 'price')
+ 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'])
+ self.assertListEqual(
+ list(F.base_filters), ["title", "average_rating", "username"]
+ )
def test_meta_fields_and_exlude_and_exclude_wins(self):
class F(FilterSet):
@@ -543,33 +540,32 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ('username', 'title', 'price')
- exclude = ('title',)
+ 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'])
+ 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', )
+ 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'])
+ self.assertListEqual(list(F.base_filters), ["title", "average_rating"])
def test_filterset_class_inheritance(self):
class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ fields = "__all__"
class G(F):
pass
+
self.assertEqual(set(F.base_filters), set(G.base_filters))
class F(FilterSet):
@@ -577,38 +573,37 @@ class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ 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__'
+ fields = "__all__"
- self.assertEqual(set(F.base_filters), set(['name', 'serves_pizza']))
+ self.assertEqual(set(F.base_filters), set(["name", "serves_pizza"]))
class F(FilterSet):
class Meta:
model = Restaurant
- fields = ['name', 'serves_pizza']
+ fields = ["name", "serves_pizza"]
- self.assertEqual(set(F.base_filters), set(['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__'
+ fields = "__all__"
- filter_overrides = {
- SubnetMaskField: {'filter_class': CharFilter}
- }
+ filter_overrides = {SubnetMaskField: {"filter_class": CharFilter}}
- self.assertEqual(list(F.base_filters.keys()), ['ip', 'mask', 'cidr'])
+ self.assertEqual(list(F.base_filters.keys()), ["ip", "mask", "cidr"])
def test_custom_declared_field_no_warning(self):
class F(FilterSet):
@@ -616,20 +611,20 @@ class F(FilterSet):
class Meta:
model = NetworkSetting
- fields = ['mask']
+ fields = ["mask"]
- self.assertEqual(list(F.base_filters.keys()), ['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__'
+ fields = "__all__"
class ProxyF(FilterSet):
class Meta:
model = AdminUser
- fields = '__all__'
+ fields = "__all__"
self.assertEqual(list(F.base_filters), list(ProxyF.base_filters))
@@ -637,17 +632,17 @@ def test_filterset_for_mti_model(self):
class F(FilterSet):
class Meta:
model = Account
- fields = '__all__'
+ fields = "__all__"
class FtiF(FilterSet):
class Meta:
model = BankAccount
- fields = '__all__'
+ fields = "__all__"
# fails due to 'account_ptr' getting picked up
self.assertEqual(
- list(F.base_filters) + ['amount_saved'],
- list(FtiF.base_filters))
+ list(F.base_filters) + ["amount_saved"], list(FtiF.base_filters)
+ )
def test_declared_filter_disabling(self):
class Parent(FilterSet):
@@ -664,18 +659,18 @@ class Grandchild(Child):
self.assertEqual(len(Child.base_filters), 1)
self.assertEqual(len(Grandchild.base_filters), 1)
- @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR='lt')
+ @override_settings(FILTERS_DEFAULT_LOOKUP_EXPR="lt")
def test_transforms_other_default_lookup(self):
class F(FilterSet):
class Meta:
model = Article
fields = {
- 'published': ['lt', 'year__lt'],
+ "published": ["lt", "year__lt"],
}
self.assertEqual(len(F.base_filters), 2)
- expected_list = ['published', 'published__year']
+ expected_list = ["published", "published__year"]
self.assertCountEqual(list(F.base_filters), expected_list)
def test_declared_filter_multiple_inheritance(self):
@@ -689,7 +684,7 @@ class F(A, B):
pass
filters = {name: type(f) for name, f in F.declared_filters.items()}
- self.assertEqual(filters, {'f': CharFilter})
+ self.assertEqual(filters, {"f": CharFilter})
def test_declared_filter_multiple_inheritance_field_ordering(self):
class Base(FilterSet):
@@ -713,20 +708,19 @@ class F(A, B):
# - `F.f2` should override `Base.F2`
# - `A.f3` should override `B.f3`
assert fields == {
- 'f1': CharFilter,
- 'f2': NumberFilter,
- 'f3': NumberFilter,
- 'f4': CharFilter,
- 'f5': CharFilter,
+ "f1": CharFilter,
+ "f2": NumberFilter,
+ "f3": NumberFilter,
+ "f4": CharFilter,
+ "f5": CharFilter,
}
class FilterSetInstantiationTests(TestCase):
-
class F(FilterSet):
class Meta:
model = User
- fields = ['username']
+ fields = ["username"]
def test_creating_instance(self):
f = self.F()
@@ -735,12 +729,11 @@ def test_creating_instance(self):
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)
+ filter_.model, User, "%s does not have model set correctly" % name
+ )
def test_creating_bound_instance(self):
- f = self.F({'username': 'username'})
+ f = self.F({"username": "username"})
self.assertTrue(f.is_bound)
def test_creating_with_queryset(self):
@@ -755,20 +748,18 @@ def test_creating_with_request(self):
class FilterSetQuerysetTests(TestCase):
-
class F(FilterSet):
invalid = CharFilter(method=lambda *args: None)
class Meta:
model = User
- fields = ['username', 'invalid']
+ fields = ["username", "invalid"]
def test_filter_queryset_called_once(self):
m = MockQuerySet()
- f = self.F({'username': 'bob'}, queryset=m)
+ f = self.F({"username": "bob"}, queryset=m)
- with mock.patch.object(f, 'filter_queryset',
- wraps=f.filter_queryset) as fn:
+ with mock.patch.object(f, "filter_queryset", wraps=f.filter_queryset) as fn:
f.qs
fn.assert_called_once_with(m.all())
f.qs
@@ -777,8 +768,7 @@ def test_filter_queryset_called_once(self):
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:
+ with mock.patch.object(f, "get_form_class", wraps=f.get_form_class) as fn:
f.form
fn.assert_called_once()
f.form
@@ -798,17 +788,16 @@ def test_form_caching(self):
def test_qs_triggers_form_validation(self):
m = MockQuerySet()
- f = self.F({'username': 'bob'}, queryset=m)
+ f = self.F({"username": "bob"}, queryset=m)
- with mock.patch.object(f.form, 'full_clean',
- wraps=f.form.full_clean) as fn:
+ 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)
+ 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):
@@ -817,7 +806,6 @@ def test_filters_must_return_queryset(self):
# 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):
@@ -831,15 +819,15 @@ class TestFilter(Filter):
def test_method_name(self):
class F(FilterSet):
- f = Filter(method='filter_f')
+ 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)
+ 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):
@@ -849,13 +837,13 @@ 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)
+ 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')
+ f = Filter(method="filter_f")
def filter_f(self, qs, name, value):
# call mock request object to prove self.request can be accessed
@@ -864,30 +852,30 @@ def filter_f(self, qs, name, value):
m = mock.Mock()
f = F({}, queryset=User.objects.all(), request=m)
# call the filter
- f.filters['f'].filter.method(User.objects.all(), 'f', '')
+ 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')
+ 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)
+ 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')
+ 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))
+ 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.
@@ -895,7 +883,7 @@ def test_method_self_is_parent(self):
request = MockQuerySet()
class F(FilterSet):
- f = CharFilter(method='filter_f')
+ f = CharFilter(method="filter_f")
class Meta:
model = User
@@ -906,40 +894,40 @@ def filter_f(inner_self, qs, name, value):
self.assertIs(inner_self.request, request)
return qs
- F({'f': 'foo'}, request=request, queryset=User.objects.all()).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 = Filter(method="filter_f")
f = F({}, queryset=User.objects.all())
with self.assertRaises(AssertionError) as w:
- f.filters['f'].filter(User.objects.all(), 0)
+ 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))
+ 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')
+ 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)
+ 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))
+ 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')
+ f = TestFilter(method="filter_f")
+ self.assertEqual(f.method, "filter_f")
self.assertIsInstance(f.filter, FilterMethod)
# setting None should revert to Filter.filter
@@ -949,16 +937,15 @@ class TestFilter(Filter):
class MiscFilterSetTests(TestCase):
-
def test_no__getitem__(self):
# The DTL processes variable lookups by the following rules:
# https://docs.djangoproject.com/en/stable/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__'))
+ 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__'))
+ self.assertFalse(hasattr(FilterSet, "__len__"))
+ self.assertFalse(hasattr(FilterSet, "__iter__"))
diff --git a/tests/test_forms.py b/tests/test_forms.py
index 568d7482a..013c677b9 100644
--- a/tests/test_forms.py
+++ b/tests/test_forms.py
@@ -8,7 +8,6 @@
class FilterSetFormTests(TestCase):
-
def test_form_from_empty_filterset(self):
class F(FilterSet):
pass
@@ -20,11 +19,11 @@ def test_form(self):
class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
f = F().form
self.assertIsInstance(f, forms.Form)
- self.assertEqual(list(f.fields), ['title'])
+ self.assertEqual(list(f.fields), ["title"])
def test_custom_form(self):
class MyForm(forms.Form):
@@ -33,7 +32,7 @@ class MyForm(forms.Form):
class F(FilterSet):
class Meta:
model = Book
- fields = '__all__'
+ fields = "__all__"
form = MyForm
f = F().form
@@ -43,26 +42,25 @@ def test_form_prefix(self):
class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
f = F().form
self.assertIsNone(f.prefix)
- f = F(prefix='prefix').form
- self.assertEqual(f.prefix, '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']
+ fields = ["status"]
f = F().form
self.assertEqual(len(f.fields), 1)
- self.assertIn('status', f.fields)
+ self.assertIn("status", f.fields)
self.assertSequenceEqual(
- list(f.fields['status'].choices),
- (('', '---------'), ) + STATUS_CHOICES
+ list(f.fields["status"].choices), (("", "---------"),) + STATUS_CHOICES
)
def test_form_fields_exclusion(self):
@@ -71,53 +69,58 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
f = F().form
- self.assertEqual(f.fields['title'].label, "Exclude title")
+ 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)
+ 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'],
+ "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')
+ 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)
+ status = ChoiceFilter(
+ widget=forms.RadioSelect, choices=STATUS_CHOICES, empty_label=None
+ )
class Meta:
model = User
- fields = ['status', 'username']
+ 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)
+ 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):
@@ -125,35 +128,35 @@ class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
f = F().form
- self.assertEqual(f.fields['title'].label, "Book title")
- self.assertEqual(f['title'].label, 'Book title')
+ 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')
+ book_title = CharFilter(field_name="title")
class Meta:
model = Book
- fields = ('book_title',)
+ fields = ("book_title",)
f = F().form
- self.assertEqual(f.fields['book_title'].label, "Title")
- self.assertEqual(f['book_title'].label, "Title")
+ 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")
+ f1 = CharFilter(field_name="title", label="Book title")
class Meta:
model = Book
- fields = ('f1',)
+ fields = ("f1",)
f = F().form
- self.assertEqual(f.fields['f1'].label, "Book title")
- self.assertEqual(f['f1'].label, 'Book title')
+ self.assertEqual(f.fields["f1"].label, "Book title")
+ self.assertEqual(f["f1"].label, "Book title")
def test_filter_with_initial(self):
class F(FilterSet):
@@ -161,16 +164,16 @@ class F(FilterSet):
class Meta:
model = User
- fields = ['status']
+ fields = ["status"]
f = F().form
- self.assertEqual(f.fields['status'].initial, 1)
+ self.assertEqual(f.fields["status"].initial, 1)
def test_form_is_not_bound(self):
class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
f = F().form
self.assertFalse(f.is_bound)
@@ -180,27 +183,26 @@ def test_form_is_bound(self):
class F(FilterSet):
class Meta:
model = Book
- fields = ('title',)
+ fields = ("title",)
- f = F({'title': 'Some book'}).form
+ f = F({"title": "Some book"}).form
self.assertTrue(f.is_bound)
- self.assertEqual(f.data, {'title': 'Some book'})
+ 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)
+ 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']
+ fields = ["users", "manager"]
+
f = F().form
+ self.assertEqual(list(f.fields["users"].choices), [(2, "active")])
self.assertEqual(
- list(f.fields['users'].choices), [(2, 'active')]
- )
- self.assertEqual(
- list(f.fields['manager'].choices), [('', '---------'), (3, 'manager')]
+ list(f.fields["manager"].choices), [("", "---------"), (3, "manager")]
)
def test_disabled_help_text(self):
@@ -209,28 +211,24 @@ class Meta:
model = Book
fields = {
# 'in' lookups are CSV-based, which have a `help_text`.
- 'title': ['in']
+ "title": ["in"]
}
self.assertEqual(
- F().form.fields['title__in'].help_text,
- 'Multiple values may be separated by commas.'
+ 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,
- ''
- )
+ self.assertEqual(F().form.fields["title__in"].help_text, "")
class FilterSetValidityTests(TestCase):
-
class F(FilterSet):
class Meta:
model = Book
- fields = ['title', 'price']
+ fields = ["title", "price"]
def test_not_bound(self):
f = self.F()
@@ -241,31 +239,31 @@ def test_not_bound(self):
self.assertEqual(f.errors, {})
def test_is_bound_and_valid(self):
- f = self.F({'title': 'Some book'})
+ 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.data, {"title": "Some book"})
self.assertEqual(f.errors, {})
def test_is_bound_and_not_valid(self):
- f = self.F({'price': 'four dollars'})
+ 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.']})
+ self.assertEqual(f.data, {"price": "four dollars"})
+ self.assertEqual(f.errors, {"price": ["Enter a number."]})
def test_number_filter_max_value_validation(self):
class F(FilterSet):
class Meta:
model = Book
- fields = ['average_rating']
+ fields = ["average_rating"]
- f = F({'average_rating': '1E1001'})
+ f = F({"average_rating": "1E1001"})
self.assertTrue(f.is_bound)
self.assertFalse(f.is_valid())
self.assertEqual(
f.errors,
- {'average_rating': ['Ensure this value is less than or equal to 1e+50.']}
+ {"average_rating": ["Ensure this value is less than or equal to 1e+50."]},
)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7a57fae46..305fc7270 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,12 +1,14 @@
import datetime
+import unittest
import warnings
+import django
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields.related import ForeignObjectRel
from django.test import TestCase, override_settings
from django.utils.functional import Promise
-from django.utils.timezone import get_default_timezone
+from django.utils.timezone import get_default_timezone, make_aware
from django_filters import FilterSet
from django_filters.exceptions import FieldLookupError
@@ -21,33 +23,22 @@
resolve_field,
translate_validation,
verbose_field_name,
- verbose_lookup_expr
+ verbose_lookup_expr,
)
-from .models import (
- Article,
- Book,
- Business,
- Company,
- HiredWorker,
- NetworkSetting,
- User
-)
+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'
+ str(MigrationNotice("Message.")),
+ "Message. See: https://django-filter.readthedocs.io/en/main/guide/migration.html",
)
class RenameAttributes(RenameAttributesBase):
- renamed_attributes = (
- ('old', 'new', DeprecationWarning),
- )
+ renamed_attributes = (("old", "new", DeprecationWarning),)
class SENTINEL:
@@ -55,9 +46,8 @@ class SENTINEL:
class RenameAttributesBaseTests(TestCase):
-
def check(self, recorded, count):
- expected = '`Example.old` attribute should be renamed `new`.'
+ expected = "`Example.old` attribute should be renamed `new`."
self.assertEqual(len(recorded), count)
for _ in range(count):
@@ -67,7 +57,7 @@ def check(self, recorded, count):
def test_class_creation_warnings(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
class Example(metaclass=RenameAttributes):
old = SENTINEL
@@ -77,16 +67,16 @@ class Example(metaclass=RenameAttributes):
def test_renamed_attribute_in_class_dict(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('ignore')
+ warnings.simplefilter("ignore")
class Example(metaclass=RenameAttributes):
old = SENTINEL
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
# Ensure `old` and `new` are not both in class dict.
- self.assertNotIn('old', Example.__dict__)
- self.assertIn('new', Example.__dict__)
+ self.assertNotIn("old", Example.__dict__)
+ self.assertIn("new", Example.__dict__)
# Ensure `old` value assigned to `new`.
self.assertEqual(Example.new, SENTINEL)
@@ -95,12 +85,12 @@ class Example(metaclass=RenameAttributes):
def test_class_accessor_warnings(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('ignore')
+ warnings.simplefilter("ignore")
class Example(metaclass=RenameAttributes):
new = None
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
self.assertIsNone(Example.new)
self.assertIsNone(Example.old)
@@ -113,12 +103,12 @@ class Example(metaclass=RenameAttributes):
def test_instance_accessor_warnings(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('ignore')
+ warnings.simplefilter("ignore")
class Example(metaclass=RenameAttributes):
new = None
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
example = Example()
self.check(recorded, 0)
@@ -134,7 +124,7 @@ class Example(metaclass=RenameAttributes):
def test_class_instance_values(self):
with warnings.catch_warnings(record=True):
- warnings.simplefilter('ignore')
+ warnings.simplefilter("ignore")
class Example(metaclass=RenameAttributes):
new = None
@@ -150,11 +140,11 @@ class Example(metaclass=RenameAttributes):
def test_getter_reachable(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
class Example(metaclass=RenameAttributes):
def __getattr__(self, name):
- if name == 'test':
+ if name == "test":
return SENTINEL
return self.__getattribute__(name)
@@ -164,11 +154,11 @@ def __getattr__(self, name):
def test_parent_getter_reachable(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
class Parent:
def __getattr__(self, name):
- if name == 'test':
+ if name == "test":
return SENTINEL
return self.__getattribute__(name)
@@ -181,11 +171,11 @@ class Example(Parent, metaclass=RenameAttributes):
def test_setter_reachable(self):
with warnings.catch_warnings(record=True) as recorded:
- warnings.simplefilter('always')
+ warnings.simplefilter("always")
class Example(metaclass=RenameAttributes):
def __setattr__(self, name, value):
- if name == 'test':
+ if name == "test":
value = SENTINEL
super().__setattr__(name, value)
@@ -196,55 +186,71 @@ def __setattr__(self, name, value):
class GetFieldPartsTests(TestCase):
-
def test_field(self):
- parts = get_field_parts(User, 'username')
+ 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')
+ result = get_model_field(User, "unknown__name")
self.assertIsNone(result)
def test_forwards_related_field(self):
- parts = get_field_parts(User, 'favorite_books__title')
+ 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')
+ 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)
+ def test_lazy_relationship_not_ready(self):
+ """
+ This simulates trying to create a FilterSet before the app registry has
+ been populated. Lazy relationships have not yet been resolved from their
+ strings into their remote model references.
+ """
-class GetModelFieldTests(TestCase):
+ class TestModel(models.Model):
+ fk = models.ForeignKey("remote.Model", on_delete=models.CASCADE)
+ msg = (
+ "Unable to resolve relationship `fk__f` for `tests.TestModel`. "
+ "Django is most likely not initialized, and its apps registry "
+ "not populated. Ensure Django has finished setup before loading "
+ "`FilterSet`s."
+ )
+ with self.assertRaisesMessage(RuntimeError, msg):
+ get_field_parts(TestModel, "fk__f")
+
+
+class GetModelFieldTests(TestCase):
def test_non_existent_field(self):
- result = get_model_field(User, 'unknown__name')
+ 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'))
+ 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')
+ 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.
+ # This is simple - the final output 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)
@@ -256,17 +262,25 @@ 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', ]
+ lookups = [
+ "exact",
+ "gte",
+ "gt",
+ "lte",
+ "lt",
+ "in",
+ "isnull",
+ ]
# ForeignKey
- model_field = Article._meta.get_field('author')
+ 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')
+ model_field = User._meta.get_field("favorite_books")
for term in lookups:
field, lookup = resolve_field(model_field, term)
self.assertIsInstance(field, models.ManyToManyField)
@@ -277,17 +291,25 @@ 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', ]
+ lookups = [
+ "exact",
+ "gte",
+ "gt",
+ "lte",
+ "lt",
+ "in",
+ "isnull",
+ ]
# ManyToOneRel
- model_field = User._meta.get_field('article')
+ 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')
+ model_field = Book._meta.get_field("lovers")
for term in lookups:
field, lookup = resolve_field(model_field, term)
self.assertIsInstance(field, models.ManyToManyRel)
@@ -300,252 +322,278 @@ def test_resolve_transformed_lookups(self):
"""
# Use a DateTimeField, so we can check multiple transforms.
# eg, date__year__gte
- model_field = Article._meta.get_field('published')
+ model_field = Article._meta.get_field("published")
standard_lookups = [
- 'exact',
- 'iexact',
- 'gte',
- 'gt',
- 'lte',
- 'lt',
+ "exact",
+ "iexact",
+ "gte",
+ "gt",
+ "lte",
+ "lt",
]
date_lookups = [
- 'year',
- 'month',
- 'day',
- 'week_day',
+ "year",
+ "month",
+ "day",
+ "week_day",
]
datetime_lookups = date_lookups + [
- 'hour',
- 'minute',
- 'second',
+ "hour",
+ "minute",
+ "second",
]
# ex: 'date__gt'
for lookup in standard_lookups:
- field, resolved_lookup = resolve_field(model_field, LOOKUP_SEP.join(['date', lookup]))
+ 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]))
+ 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]))
+ 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')
+ model_field = Article._meta.get_field("published")
- field, lookup = resolve_field(model_field, 'date')
+ field, lookup = resolve_field(model_field, "date")
self.assertIsInstance(field, models.DateField)
- self.assertEqual(lookup, 'exact')
+ self.assertEqual(lookup, "exact")
- field, lookup = resolve_field(model_field, 'date__year')
+ field, lookup = resolve_field(model_field, "date__year")
self.assertIsInstance(field, models.IntegerField)
- self.assertEqual(lookup, 'exact')
+ self.assertEqual(lookup, "exact")
def test_invalid_lookup_expression(self):
- model_field = Article._meta.get_field('published')
+ model_field = Article._meta.get_field("published")
with self.assertRaises(FieldLookupError) as context:
- resolve_field(model_field, 'invalid_lookup')
+ resolve_field(model_field, "invalid_lookup")
exc = str(context.exception)
self.assertIn(str(model_field), exc)
- self.assertIn('invalid_lookup', exc)
+ self.assertIn("invalid_lookup", exc)
def test_invalid_transformed_lookup_expression(self):
- model_field = Article._meta.get_field('published')
+ model_field = Article._meta.get_field("published")
with self.assertRaises(FieldLookupError) as context:
- resolve_field(model_field, 'date__invalid_lookup')
+ resolve_field(model_field, "date__invalid_lookup")
exc = str(context.exception)
self.assertIn(str(model_field), exc)
- self.assertIn('date__invalid_lookup', 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]')
+ self.assertEqual(verbose_name, "[invalid name]")
def test_invalid_name(self):
- verbose_name = verbose_field_name(Article, 'foobar')
- self.assertEqual(verbose_name, '[invalid name]')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ field = User._meta.get_field("username")
self.assertIsInstance(field.verbose_name, Promise)
- verbose_name = verbose_field_name(User, 'username')
- self.assertEqual(verbose_name, 'username')
+ 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')
+ 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')
+ 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]')
+ 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]')
+ 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, '')
+ 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')
+ 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')
+ 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'})
+ @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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ 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')
+ label = label_for_filter(NetworkSetting, "cidr", "contains", exclude=True)
+ self.assertEqual(label, "Exclude CIDR contains")
+@unittest.skipUnless(django.VERSION < (5, 0), "is_dst removed in Django 5.0")
class HandleTimezone(TestCase):
-
- @override_settings(TIME_ZONE='America/Sao_Paulo')
+ @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))
+ self.assertEqual(
+ handled,
+ make_aware(dst_ending_date, get_default_timezone(), False),
+ )
- @override_settings(TIME_ZONE='America/Sao_Paulo')
+ @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))
+ self.assertEqual(
+ handled,
+ make_aware(dst_starting_date, get_default_timezone(), True),
+ )
class TranslateValidationDataTests(TestCase):
-
class F(FilterSet):
class Meta:
model = Article
- fields = ['id', 'author', 'name']
+ fields = ["id", "author", "name"]
- choice = MultipleChoiceFilter(choices=[('1', 'one'), ('2', 'two')])
+ choice = MultipleChoiceFilter(choices=[("1", "one"), ("2", "two")])
def test_error_detail(self):
- f = self.F(data={
- 'id': 'foo',
- 'author': 'bar',
- 'name': 'baz',
- 'choice': ['3'],
- })
+ 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.'],
- })
+ 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'],
- })
+ 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',
- }],
- })
+ 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",
+ }
+ ],
+ },
+ )
diff --git a/tests/test_views.py b/tests/test_views.py
index c6bd776e3..98d84797a 100644
--- a/tests/test_views.py
+++ b/tests/test_views.py
@@ -1,5 +1,3 @@
-import warnings
-
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, override_settings
from django.test.client import RequestFactory
@@ -11,31 +9,27 @@
from .models import Book
-@override_settings(ROOT_URLCONF='tests.urls')
+@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)
+ 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/'
+ base_url = "/books/"
def test_view(self):
response = self.client.get(self.base_url)
- for b in ["Ender's Game", 'Rainbow Six', 'Snowcrash']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(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']:
+ response = self.client.get(self.base_url + "?title=Snowcrash")
+ for b in ["Ender's Game", "Rainbow Six"]:
self.assertNotContains(response, html.escape(b))
- self.assertContains(response, 'Snowcrash')
+ self.assertContains(response, "Snowcrash")
def test_view_with_filterset_not_model(self):
factory = RequestFactory()
@@ -44,7 +38,7 @@ def test_view_with_filterset_not_model(self):
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']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(b))
def test_view_with_model_no_filterset(self):
@@ -53,46 +47,49 @@ def test_view_with_model_no_filterset(self):
view = FilterView.as_view(model=Book)
response = view(request)
self.assertEqual(response.status_code, 200)
- for b in ["Ender's Game", 'Rainbow Six', 'Snowcrash']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(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'])
+ 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']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(b))
# not filtering by title
- request = factory.get(self.base_url + '?title=Snowcrash')
+ 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']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(b))
def test_view_with_strict_errors(self):
factory = RequestFactory()
- request = factory.get(self.base_url + '?title=Snowcrash&price=four dollars')
+ 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']]
+ 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')
+ 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']]
+ titles = [o.title for o in response.context_data["object_list"]]
self.assertEqual(response.status_code, 200)
- self.assertEqual(titles, ['Snowcrash'],)
+ self.assertEqual(
+ titles,
+ ["Snowcrash"],
+ )
def test_view_without_filterset_or_model(self):
factory = RequestFactory()
@@ -111,46 +108,33 @@ class MyFilterSet(FilterSet):
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')
+ 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']]
+ titles = [o.title for o in response.context_data["object_list"]]
self.assertEqual(response.status_code, 200)
- self.assertEqual(titles, ['Snowcrash'])
+ self.assertEqual(titles, ["Snowcrash"])
class GenericFunctionalViewTests(GenericViewTestCase):
- base_url = '/books-legacy/'
+ base_url = "/books-legacy/"
def test_view(self):
response = self.client.get(self.base_url)
- for b in ["Ender's Game", 'Rainbow Six', 'Snowcrash']:
+ for b in ["Ender's Game", "Rainbow Six", "Snowcrash"]:
self.assertContains(response, html.escape(b))
# extra context
- self.assertEqual(response.context_data['foo'], 'bar')
- self.assertEqual(response.context_data['bar'], 'foo')
+ 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']:
+ response = self.client.get(self.base_url + "?title=Snowcrash")
+ for b in ["Ender's Game", "Rainbow Six"]:
self.assertNotContains(response, html.escape(b))
- self.assertContains(response, 'Snowcrash')
+ self.assertContains(response, "Snowcrash")
diff --git a/tests/test_widgets.py b/tests/test_widgets.py
index 930da8ee2..f50cc1bed 100644
--- a/tests/test_widgets.py
+++ b/tests/test_widgets.py
@@ -9,117 +9,145 @@
LookupChoiceWidget,
QueryArrayWidget,
RangeWidget,
- SuffixedMultiWidget
+ 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')))]
+ widgets = [TextInput(), Select(choices=(("a", "a"), ("b", "b")))]
w = LookupChoiceWidget(widgets)
- self.assertHTMLEqual(w.render('price', ''), """
+ self.assertHTMLEqual(
+ w.render("price", ""),
+ """
""")
+ """,
+ )
- self.assertHTMLEqual(w.render('price', None), """
+ self.assertHTMLEqual(
+ w.render("price", None),
+ """
""")
+ """,
+ )
- self.assertHTMLEqual(w.render('price', ['2', 'a']), """
+ 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', ''), """