init commit

master
Aleksey 4 months ago
commit ee7dfa6316

@ -0,0 +1,24 @@
Copyright (c) Alex Gaynor and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The names of its contributors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,150 @@
Metadata-Version: 2.1
Name: django-filter
Version: 24.2
Summary: Django-filter is a reusable Django application for allowing users to filter querysets dynamically.
Author-email: Alex Gaynor <alex.gaynor@gmail.com>
Maintainer-email: Carlton Gibson <carlton.gibson@noumenal.es>
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: Django>=4.2
Project-URL: Bug Tracker, https://github.com/carltongibson/django-filter/issues
Project-URL: Changelog, https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
Project-URL: Documentation, https://django-filter.readthedocs.io/en/main/
Project-URL: Homepage, https://github.com/carltongibson/django-filter/tree/main
Project-URL: Source Code, https://github.com/carltongibson/django-filter
Django Filter
=============
Django-filter is a reusable Django application allowing users to declaratively
add dynamic ``QuerySet`` filtering from URL parameters.
Full documentation on `read the docs`_.
.. image:: https://raw.githubusercontent.com/carltongibson/django-filter/python-coverage-comment-action-data/badge.svg
:target: https://github.com/carltongibson/django-filter/tree/python-coverage-comment-action-data
.. image:: https://badge.fury.io/py/django-filter.svg
:target: http://badge.fury.io/py/django-filter
Versioning and stability policy
-------------------------------
Django-Filter is a mature and stable package. It uses a two-part CalVer
versioning scheme, such as ``21.1``. The first number is the year. The second
is the release number within that year.
On an on-going basis, Django-Filter aims to support all current Django
versions, the matching current Python versions, and the latest version of
Django REST Framework.
Please see:
* `Status of supported Python versions <https://devguide.python.org/versions/#supported-versions>`_
* `List of supported Django versions <https://www.djangoproject.com/download/#supported-versions>`_
Support for Python and Django versions will be dropped when they reach
end-of-life. Support for Python versions will be dropped when they reach
end-of-life, even when still supported by a current version of Django.
Other breaking changes are rare. Where required, every effort will be made to
apply a "Year plus two" deprecation period. For example, a change initially
introduced in ``23.x`` would offer a fallback where feasible and finally be
removed in ``25.1``. Where fallbacks are not feasible, breaking changes without
deprecation will be called out in the release notes.
Installation
------------
Install using pip:
.. code-block:: sh
pip install django-filter
Then add ``'django_filters'`` to your ``INSTALLED_APPS``.
.. code-block:: python
INSTALLED_APPS = [
...
'django_filters',
]
Usage
-----
Django-filter can be used for generating interfaces similar to the Django
admin's ``list_filter`` interface. It has an API very similar to Django's
``ModelForms``. For example, if you had a Product model you could have a
filterset for it with the code:
.. code-block:: python
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'manufacturer']
And then in your view you could do:
.. code-block:: python
def product_list(request):
filter = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': filter})
Usage with Django REST Framework
--------------------------------
Django-filter provides a custom ``FilterSet`` and filter backend for use with
Django REST Framework.
To use this adjust your import to use
``django_filters.rest_framework.FilterSet``.
.. code-block:: python
from django_filters import rest_framework as filters
class ProductFilter(filters.FilterSet):
class Meta:
model = Product
fields = ('category', 'in_stock')
For more details see the `DRF integration docs`_.
Support
-------
If you need help you can start a `discussion`_. For commercial support, please
`contact Carlton Gibson via his website <https://noumenal.es/>`_.
.. _`discussion`: https://github.com/carltongibson/django-filter/discussions
.. _`read the docs`: https://django-filter.readthedocs.io/en/main/
.. _`DRF integration docs`: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html

@ -0,0 +1,79 @@
django_filter-24.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
django_filter-24.2.dist-info/LICENSE,sha256=4UQ8qx2nFmTo4lASXOByK3RcVWDurx7_w9HozSy9mAI,1487
django_filter-24.2.dist-info/METADATA,sha256=hjINlT2OR3cAAhT_hdIFWfqdt6WMx08BPasyr3d-Go0,5120
django_filter-24.2.dist-info/RECORD,,
django_filter-24.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
django_filter-24.2.dist-info/WHEEL,sha256=rSgq_JpHF9fHR1lx53qwg_1-2LypZE_qmcuXbVUq948,81
django_filters/__init__.py,sha256=HWgLpY-Ohqbs5SXhmhxMWR9txYylyds8oxmBaTc3gXY,796
django_filters/__pycache__/__init__.cpython-312.pyc,,
django_filters/__pycache__/compat.cpython-312.pyc,,
django_filters/__pycache__/conf.cpython-312.pyc,,
django_filters/__pycache__/constants.cpython-312.pyc,,
django_filters/__pycache__/exceptions.cpython-312.pyc,,
django_filters/__pycache__/fields.cpython-312.pyc,,
django_filters/__pycache__/filters.cpython-312.pyc,,
django_filters/__pycache__/filterset.cpython-312.pyc,,
django_filters/__pycache__/utils.cpython-312.pyc,,
django_filters/__pycache__/views.cpython-312.pyc,,
django_filters/__pycache__/widgets.cpython-312.pyc,,
django_filters/compat.py,sha256=eatCom2gnXt-HgpU9gyMKqh6Q5ok5RvxdBiL2dFZwas,545
django_filters/conf.py,sha256=VIxhOioS9Z6qM8PfNaaMtK8A1XSGqvD8tUAH3tmMGkM,3049
django_filters/constants.py,sha256=N36ZSZJKbcFJIh4DC4Oe7HvkKmYNyRhAHpaQiD5i6SM,63
django_filters/exceptions.py,sha256=c8EYPU4mY93QAIVGpf7datTHvQt6nrxYJO1SutzFzf4,253
django_filters/fields.py,sha256=F-iQW7OhtfO4BKe_Jm7bzBRUlcfOut8VxVm19-fGmtg,10373
django_filters/filters.py,sha256=inMCUojSetasYLF6wBuUv7FGmjOJBQryaCmyo9JBzCU,25326
django_filters/filterset.py,sha256=iCblMAcHJzl7Wrzv-Ija44t3FJDNoYl_lb81bQ4RnKQ,16239
django_filters/locale/ar/LC_MESSAGES/django.mo,sha256=utzbP4BsdW91KwGgFwyvXVY1uNZ8otdcUDoZZpIZ9Pg,2568
django_filters/locale/ar/LC_MESSAGES/django.po,sha256=P-SHUseAhEXgKNQDm2DWB4nFk5Nyh6DAXXSMmBEfx4g,3625
django_filters/locale/be/LC_MESSAGES/django.mo,sha256=lbp-b9nTHDvBb8ozSkyHWGlmi4X3WyKaObT9GB2fe9E,2819
django_filters/locale/be/LC_MESSAGES/django.po,sha256=gRsiOMvJ7K8tsa6rOLs2v5ROv5toyFtN6qnh1rppO1c,3696
django_filters/locale/bg/LC_MESSAGES/django.mo,sha256=ZPmu82dqvj3yd3-J0KLK-hxfwETzqKmq0c-Anozn5Go,2711
django_filters/locale/bg/LC_MESSAGES/django.po,sha256=zpGSdxLb1erXzUd3GdY6IfC4UlhrbEeNcNKjq3UkjeI,3740
django_filters/locale/cs/LC_MESSAGES/django.mo,sha256=vZuyiklIF_I3qs9pdhb3OTT2d63aIttgtcHY1b9Gsps,2368
django_filters/locale/cs/LC_MESSAGES/django.po,sha256=Su0bgXYM0-jA6hDrF_mPLxZSlw0j1waDapguCYnw-Gs,3242
django_filters/locale/da/LC_MESSAGES/django.mo,sha256=gPy5CaNJWYbCPqeqb6XPr1uynW9FEn8zV_-85RMJaZc,2166
django_filters/locale/da/LC_MESSAGES/django.po,sha256=mt_ypD4Mt0895YMZWN1bSwkqSI4tAKnPSLizrYD2h3g,3173
django_filters/locale/de/LC_MESSAGES/django.mo,sha256=IvgqQ0BQ7AiJSmdcGpKWheuLrzrXqs-lbp4Bac2jOdI,2277
django_filters/locale/de/LC_MESSAGES/django.po,sha256=QFubrkm9Vi0HmoS_5i_JJWGENMiw0MMfn8Kg_FzMYv8,3338
django_filters/locale/el/LC_MESSAGES/django.mo,sha256=2--juTiXF9v6u95krY9VwZCv2cXoJai6CXi4RWpi39w,2836
django_filters/locale/el/LC_MESSAGES/django.po,sha256=6tjIPpTNaiJueY3C0N2_vSc_7cY5XkTR2Zl9HWvH15c,3909
django_filters/locale/es/LC_MESSAGES/django.mo,sha256=5KCl_uUwge5RuGStcyMSsVPD6AOunjNvjuE-32PqWis,2279
django_filters/locale/es/LC_MESSAGES/django.po,sha256=y-fdqEXzSqbErT58yZYAsAfPdqT0gKShHJZClE3IScc,3426
django_filters/locale/es_AR/LC_MESSAGES/django.mo,sha256=OCKAVbT3ct5gf2_t5XsKryjlkIQDYZjC67Oz0j-YE6s,703
django_filters/locale/es_AR/LC_MESSAGES/django.po,sha256=GKRqcNqmulrygz9VxkDRyGS_uG2K0QNT2gyILEcU9BM,3035
django_filters/locale/fa/LC_MESSAGES/django.mo,sha256=HfEWFF_2l2ypvCJFCzbRf_CKgXlVyzvtVM0sZAl_KQU,2624
django_filters/locale/fa/LC_MESSAGES/django.po,sha256=n1vb3fQKigB-rUa6oTFJ2Tba7c5qzKRwgHuQn0zJIuM,3623
django_filters/locale/fi/LC_MESSAGES/django.po,sha256=Odsfeswbdf9hnwZIl24JE36MwOKnnE2yyvcaVzCOvPw,3433
django_filters/locale/fr/LC_MESSAGES/django.mo,sha256=c87Ugu3u0juDMskRegFA76SkfF5TMi-fexzHb8uWw9w,2344
django_filters/locale/fr/LC_MESSAGES/django.po,sha256=eF5fMIXDe98C3KMzUOkv7BppBg0YkvuhyLUlylyKnY8,3520
django_filters/locale/it/LC_MESSAGES/django.mo,sha256=TKIdnZSuYtyCpnl8X9jDyKFuIX6G69CmCvVaWpcuPXM,2268
django_filters/locale/it/LC_MESSAGES/django.po,sha256=5k7t_TvofiAmV5UlmpAH0t4d-ce7bySWFih0KIxkP_o,3380
django_filters/locale/nl/LC_MESSAGES/django.mo,sha256=TdtnxLBMuoMY1c0NxZwbGQX3xl4cI7xNP6iBQXpmm6I,2277
django_filters/locale/nl/LC_MESSAGES/django.po,sha256=ikeFeWaz2_CmWNzNwDHkm-m0Nkuksq9qgQ_oqH_SEeI,3287
django_filters/locale/pl/LC_MESSAGES/django.mo,sha256=-9taafe4N3mKLdZ4fEXkrj-azO-L4F0fGoxnDgTBuwU,1859
django_filters/locale/pl/LC_MESSAGES/django.po,sha256=kIM9yYIScAMrQ_W-Pt9DjNPKzeHxBtfOsRgovEBoroU,3720
django_filters/locale/pt_BR/LC_MESSAGES/django.mo,sha256=GLakV-03XUsCNKaofuG2fGCBIRGVYEMJiC-kD1UX4D0,2263
django_filters/locale/pt_BR/LC_MESSAGES/django.po,sha256=Qfc9NufXTeQrBLAxbnDZCeRl9TimgQPXTKHnEEiBcdQ,3434
django_filters/locale/ro/LC_MESSAGES/django.po,sha256=cCCKgqNv1deUxPdV2lRwniApgjPCA9Ft7QYm0rbmJdw,3478
django_filters/locale/ru/LC_MESSAGES/django.mo,sha256=1KrtkfLhq0BiDskKFffF5i53pM7Tp-bwsbPDe9F4Co0,2796
django_filters/locale/ru/LC_MESSAGES/django.po,sha256=t6hfrDsO95WwvfKuovZyAmTXz8LIuLULTGmXvfZ6PIQ,3863
django_filters/locale/sk/LC_MESSAGES/django.mo,sha256=em13cqJIPA3JLTp6JXPXuNNeDqJ7uaEuxxqtOvl9PLk,2394
django_filters/locale/sk/LC_MESSAGES/django.po,sha256=jg7V3CvkNYjJDMuu9GmfeSj-cC92ja58WdugKsc8GaY,3582
django_filters/locale/uk/LC_MESSAGES/django.mo,sha256=zgC01vyDPPS81GiD3C4WeQxtCt4_ift_pU-j_2l_LrU,2912
django_filters/locale/uk/LC_MESSAGES/django.po,sha256=LdEFmgfqczUfYUUdHHbv1SE-ljZ6oqEherQa39mZiwU,3919
django_filters/locale/zh_CN/LC_MESSAGES/django.mo,sha256=2aSG7Whwpj7iRY_7QcTV-ReuCm8JKsV-ktlRaAbYC0U,852
django_filters/locale/zh_CN/LC_MESSAGES/django.po,sha256=rogASVeUU81FcYxGClnyXOAhXJc-wBl2AQHYkWUg85E,3354
django_filters/rest_framework/__init__.py,sha256=HpNAGIdsBRJSkyM1QmqyOTb7I9VVwoMTbexbD21X6vE,113
django_filters/rest_framework/__pycache__/__init__.cpython-312.pyc,,
django_filters/rest_framework/__pycache__/backends.cpython-312.pyc,,
django_filters/rest_framework/__pycache__/filters.cpython-312.pyc,,
django_filters/rest_framework/__pycache__/filterset.cpython-312.pyc,,
django_filters/rest_framework/backends.py,sha256=2kVwpeH7SRfyO0rQhH9Wq8xY30c3hxuCT2IvVU3ETRc,5744
django_filters/rest_framework/filters.py,sha256=mh0XhgwhE95HXVOTE3SetP2uCnHjaDmutf3GdThy1l0,312
django_filters/rest_framework/filterset.py,sha256=3kbngqrt8vg0ckVEXSTeuNhvZbqsk648xS7qGooouxU,1174
django_filters/templates/django_filters/rest_framework/crispy_form.html,sha256=_Mg40d_4sWAuy7_Mzf1HRACbRgeheu0pGXy2UKpzd3s,108
django_filters/templates/django_filters/rest_framework/form.html,sha256=KoVGtezI-pWnC18jpCKy3vufR23QLpXXooCgmEFXjAA,211
django_filters/templates/django_filters/widgets/multiwidget.html,sha256=W0RT7BL9-sF-hCA_Ut4MfWaDwE8Z32syJs3anyurceg,118
django_filters/utils.py,sha256=ekqKtbEetmY7e3c6FK1fjbLwbmqy2UuYd_faswZW-Tg,11262
django_filters/views.py,sha256=dZ9uDeCHG7ee_pk1xgchbQp21G9acv84ELBHNBwFs3U,4034
django_filters/widgets.py,sha256=5DkHm5xcVzzkDO0y6nykx8TqrNCI7Q9V8Ql37yhKRRc,9251

@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: flit 3.8.0
Root-Is-Purelib: true
Tag: py3-none-any

@ -0,0 +1,39 @@
# flake8: noqa
from importlib import util as importlib_util
from .filters import *
from .filterset import FilterSet
# We make the `rest_framework` module available without an additional import.
# If DRF is not installed, no-op.
if importlib_util.find_spec("rest_framework"):
from . import rest_framework
del importlib_util
__version__ = "24.2"
def parse_version(version):
"""
'0.1.2.dev1' -> (0, 1, 2, 'dev1')
'0.1.2' -> (0, 1, 2)
"""
v = version.split(".")
ret = []
for p in v:
if p.isdigit():
ret.append(int(p))
else:
ret.append(p)
return tuple(ret)
VERSION = parse_version(__version__)
assert VERSION < (25,0), "Remove deprecated code"
class RemovedInDjangoFilter25Warning(DeprecationWarning):
pass

@ -0,0 +1,25 @@
from django.conf import settings
# django-crispy-forms is optional
try:
import crispy_forms
except ImportError:
crispy_forms = None
def is_crispy():
return "crispy_forms" in settings.INSTALLED_APPS and crispy_forms
# coreapi is optional (Note that uritemplate is a dependency of coreapi)
# Fixes #525 - cannot simply import from rest_framework.compat, due to
# import issues w/ django-guardian.
try:
import coreapi
except ImportError:
coreapi = None
try:
import coreschema
except ImportError:
coreschema = None

@ -0,0 +1,102 @@
from django.conf import settings as dj_settings
from django.core.signals import setting_changed
from django.utils.translation import gettext_lazy as _
from .utils import deprecate
DEFAULTS = {
"DISABLE_HELP_TEXT": False,
"DEFAULT_LOOKUP_EXPR": "exact",
# empty/null choices
"EMPTY_CHOICE_LABEL": "---------",
"NULL_CHOICE_LABEL": None,
"NULL_CHOICE_VALUE": "null",
"VERBOSE_LOOKUPS": {
# transforms don't need to be verbose, since their expressions are chained
"date": _("date"),
"year": _("year"),
"month": _("month"),
"day": _("day"),
"week_day": _("week day"),
"hour": _("hour"),
"minute": _("minute"),
"second": _("second"),
# standard lookups
"exact": "",
"iexact": "",
"contains": _("contains"),
"icontains": _("contains"),
"in": _("is in"),
"gt": _("is greater than"),
"gte": _("is greater than or equal to"),
"lt": _("is less than"),
"lte": _("is less than or equal to"),
"startswith": _("starts with"),
"istartswith": _("starts with"),
"endswith": _("ends with"),
"iendswith": _("ends with"),
"range": _("is in range"),
"isnull": _("is null"),
"regex": _("matches regex"),
"iregex": _("matches regex"),
"search": _("search"),
# postgres lookups
"contained_by": _("is contained by"),
"overlap": _("overlaps"),
"has_key": _("has key"),
"has_keys": _("has keys"),
"has_any_keys": _("has any keys"),
"trigram_similar": _("search"),
},
}
DEPRECATED_SETTINGS = []
def is_callable(value):
# check for callables, except types
return callable(value) and not isinstance(value, type)
class Settings:
def __getattr__(self, name):
if name not in DEFAULTS:
msg = "'%s' object has no attribute '%s'"
raise AttributeError(msg % (self.__class__.__name__, name))
value = self.get_setting(name)
if is_callable(value):
value = value()
# Cache the result
setattr(self, name, value)
return value
def get_setting(self, setting):
django_setting = "FILTERS_%s" % setting
if setting in DEPRECATED_SETTINGS and hasattr(dj_settings, django_setting):
deprecate("The '%s' setting has been deprecated." % django_setting)
return getattr(dj_settings, django_setting, DEFAULTS[setting])
def change_setting(self, setting, value, enter, **kwargs):
if not setting.startswith("FILTERS_"):
return
setting = setting[8:] # strip 'FILTERS_'
# ensure a valid app setting is being overridden
if setting not in DEFAULTS:
return
# if exiting, delete value to repopulate
if enter:
setattr(self, setting, value)
else:
delattr(self, setting)
settings = Settings()
setting_changed.connect(settings.change_setting)

@ -0,0 +1,4 @@
ALL_FIELDS = "__all__"
EMPTY_VALUES = ([], (), {}, "", None)

@ -0,0 +1,8 @@
from django.core.exceptions import FieldError
class FieldLookupError(FieldError):
def __init__(self, model_field, lookup_expr):
super().__init__(
"Unsupported lookup '%s' for field '%s'." % (lookup_expr, model_field)
)

@ -0,0 +1,324 @@
from collections import namedtuple
from datetime import datetime, time
from django import forms
from django.utils.dateparse import parse_datetime
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from .conf import settings
from .constants import EMPTY_VALUES
from .utils import handle_timezone
from .widgets import (
BaseCSVWidget,
CSVWidget,
DateRangeWidget,
LookupChoiceWidget,
RangeWidget,
)
try:
from django.utils.choices import BaseChoiceIterator, normalize_choices
except ImportError:
DJANGO_50 = False
else:
DJANGO_50 = True
class RangeField(forms.MultiValueField):
widget = RangeWidget
def __init__(self, fields=None, *args, **kwargs):
if fields is None:
fields = (forms.DecimalField(), forms.DecimalField())
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
return slice(*data_list)
return None
class DateRangeField(RangeField):
widget = DateRangeWidget
def __init__(self, *args, **kwargs):
fields = (forms.DateField(), forms.DateField())
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
start_date, stop_date = data_list
if start_date:
start_date = handle_timezone(
datetime.combine(start_date, time.min), False
)
if stop_date:
stop_date = handle_timezone(
datetime.combine(stop_date, time.max), False
)
return slice(start_date, stop_date)
return None
class DateTimeRangeField(RangeField):
widget = DateRangeWidget
def __init__(self, *args, **kwargs):
fields = (forms.DateTimeField(), forms.DateTimeField())
super().__init__(fields, *args, **kwargs)
class IsoDateTimeRangeField(RangeField):
widget = DateRangeWidget
def __init__(self, *args, **kwargs):
fields = (IsoDateTimeField(), IsoDateTimeField())
super().__init__(fields, *args, **kwargs)
class TimeRangeField(RangeField):
widget = DateRangeWidget
def __init__(self, *args, **kwargs):
fields = (forms.TimeField(), forms.TimeField())
super().__init__(fields, *args, **kwargs)
class Lookup(namedtuple("Lookup", ("value", "lookup_expr"))):
def __new__(cls, value, lookup_expr):
if value in EMPTY_VALUES or lookup_expr in EMPTY_VALUES:
raise ValueError(
"Empty values ([], (), {}, '', None) are not "
"valid Lookup arguments. Return None instead."
)
return super().__new__(cls, value, lookup_expr)
class LookupChoiceField(forms.MultiValueField):
default_error_messages = {
"lookup_required": _("Select a lookup."),
}
def __init__(self, field, lookup_choices, *args, **kwargs):
empty_label = kwargs.pop("empty_label", settings.EMPTY_CHOICE_LABEL)
fields = (field, ChoiceField(choices=lookup_choices, empty_label=empty_label))
widget = LookupChoiceWidget(widgets=[f.widget for f in fields])
kwargs["widget"] = widget
kwargs["help_text"] = field.help_text
super().__init__(fields, *args, **kwargs)
def compress(self, data_list):
if len(data_list) == 2:
value, lookup_expr = data_list
if value not in EMPTY_VALUES:
if lookup_expr not in EMPTY_VALUES:
return Lookup(value=value, lookup_expr=lookup_expr)
else:
raise forms.ValidationError(
self.error_messages["lookup_required"], code="lookup_required"
)
return None
class IsoDateTimeField(forms.DateTimeField):
"""
Supports 'iso-8601' date format too which is out the scope of
the ``datetime.strptime`` standard library
# ISO 8601: ``http://www.w3.org/TR/NOTE-datetime``
Based on Gist example by David Medina https://gist.github.com/copitux/5773821
"""
ISO_8601 = "iso-8601"
input_formats = [ISO_8601]
def strptime(self, value, format):
value = force_str(value)
if format == self.ISO_8601:
parsed = parse_datetime(value)
if parsed is None: # Continue with other formats if doesn't match
raise ValueError
return handle_timezone(parsed)
return super().strptime(value, format)
class BaseCSVField(forms.Field):
"""
Base field for validating CSV types. Value validation is performed by
secondary base classes.
ex::
class IntegerCSVField(BaseCSVField, filters.IntegerField):
pass
"""
base_widget_class = BaseCSVWidget
def __init__(self, *args, **kwargs):
widget = kwargs.get("widget") or self.widget
kwargs["widget"] = self._get_widget_class(widget)
super().__init__(*args, **kwargs)
def _get_widget_class(self, widget):
# passthrough, allows for override
if isinstance(widget, BaseCSVWidget) or (
isinstance(widget, type) and issubclass(widget, BaseCSVWidget)
):
return widget
# complain since we are unable to reconstruct widget instances
assert isinstance(
widget, type
), "'%s.widget' must be a widget class, not %s." % (
self.__class__.__name__,
repr(widget),
)
bases = (
self.base_widget_class,
widget,
)
return type(str("CSV%s" % widget.__name__), bases, {})
def clean(self, value):
if value in self.empty_values and self.required:
raise forms.ValidationError(
self.error_messages["required"], code="required"
)
if value is None:
return None
return [super(BaseCSVField, self).clean(v) for v in value]
class BaseRangeField(BaseCSVField):
# Force use of text input, as range must always have two inputs. A date
# input would only allow a user to input one value and would always fail.
widget = CSVWidget
default_error_messages = {"invalid_values": _("Range query expects two values.")}
def clean(self, value):
value = super().clean(value)
assert value is None or isinstance(value, list)
if value and len(value) != 2:
raise forms.ValidationError(
self.error_messages["invalid_values"], code="invalid_values"
)
return value
class ChoiceIterator(BaseChoiceIterator if DJANGO_50 else object):
# Emulates the behavior of ModelChoiceIterator, but instead wraps
# the field's _choices iterable.
def __init__(self, field, choices):
self.field = field
self.choices = choices
def __iter__(self):
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
if self.field.null_label is not None:
yield (self.field.null_value, self.field.null_label)
if DJANGO_50:
yield from normalize_choices(self.choices)
else:
yield from self.choices
def __len__(self):
add = 1 if self.field.empty_label is not None else 0
add += 1 if self.field.null_label is not None else 0
return len(self.choices) + add
class ModelChoiceIterator(forms.models.ModelChoiceIterator):
# Extends the base ModelChoiceIterator to add in 'null' choice handling.
# This is a bit verbose since we have to insert the null choice after the
# empty choice, but before the remainder of the choices.
def __iter__(self):
iterable = super().__iter__()
if self.field.empty_label is not None:
yield next(iterable)
if self.field.null_label is not None:
yield (self.field.null_value, self.field.null_label)
yield from iterable
def __len__(self):
add = 1 if self.field.null_label is not None else 0
return super().__len__() + add
class ChoiceIteratorMixin:
def __init__(self, *args, **kwargs):
self.null_label = kwargs.pop("null_label", settings.NULL_CHOICE_LABEL)
self.null_value = kwargs.pop("null_value", settings.NULL_CHOICE_VALUE)
super().__init__(*args, **kwargs)
@property
def choices(self):
return super().choices
@choices.setter
def choices(self, value):
if DJANGO_50:
value = self.iterator(self, value)
# Simple `super()` syntax for calling a parent property setter is
# unsupported. See https://github.com/python/cpython/issues/59170
super(ChoiceIteratorMixin, self.__class__).choices.__set__(self, value)
else:
super()._set_choices(value)
value = self.iterator(self, self._choices)
self._choices = self.widget.choices = value
# Unlike their Model* counterparts, forms.ChoiceField and forms.MultipleChoiceField do not set empty_label
class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField):
iterator = ChoiceIterator
def __init__(self, *args, **kwargs):
self.empty_label = kwargs.pop("empty_label", settings.EMPTY_CHOICE_LABEL)
super().__init__(*args, **kwargs)
class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField):
iterator = ChoiceIterator
def __init__(self, *args, **kwargs):
self.empty_label = None
super().__init__(*args, **kwargs)
class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField):
iterator = ModelChoiceIterator
def to_python(self, value):
# bypass the queryset value check
if self.null_label is not None and value == self.null_value:
return value
return super().to_python(value)
class ModelMultipleChoiceField(ChoiceIteratorMixin, forms.ModelMultipleChoiceField):
iterator = ModelChoiceIterator
def _check_values(self, value):
null = self.null_label is not None and value and self.null_value in value
if null: # remove the null value and any potential duplicates
value = [v for v in value if v != self.null_value]
result = list(super()._check_values(value))
result += [self.null_value] if null else []
return result

@ -0,0 +1,852 @@
from collections import OrderedDict
from collections.abc import Iterable
from datetime import timedelta
from itertools import chain
from django import forms
from django.core.validators import MaxValueValidator
from django.db.models import Q
from django.db.models.constants import LOOKUP_SEP
from django.forms.utils import pretty_name
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from .conf import settings
from .constants import EMPTY_VALUES
from .fields import (
BaseCSVField,
BaseRangeField,
ChoiceField,
DateRangeField,
DateTimeRangeField,
IsoDateTimeField,
IsoDateTimeRangeField,
LookupChoiceField,
ModelChoiceField,
ModelMultipleChoiceField,
MultipleChoiceField,
RangeField,
TimeRangeField,
)
from .utils import get_model_field, label_for_filter
__all__ = [
"AllValuesFilter",
"AllValuesMultipleFilter",
"BaseCSVFilter",
"BaseInFilter",
"BaseRangeFilter",
"BooleanFilter",
"CharFilter",
"ChoiceFilter",
"DateFilter",
"DateFromToRangeFilter",
"DateRangeFilter",
"DateTimeFilter",
"DateTimeFromToRangeFilter",
"DurationFilter",
"Filter",
"IsoDateTimeFilter",
"IsoDateTimeFromToRangeFilter",
"LookupChoiceFilter",
"ModelChoiceFilter",
"ModelMultipleChoiceFilter",
"MultipleChoiceFilter",
"NumberFilter",
"NumericRangeFilter",
"OrderingFilter",
"RangeFilter",
"TimeFilter",
"TimeRangeFilter",
"TypedChoiceFilter",
"TypedMultipleChoiceFilter",
"UUIDFilter",
]
class Filter:
creation_counter = 0
field_class = forms.Field
def __init__(
self,
field_name=None,
lookup_expr=None,
*,
label=None,
method=None,
distinct=False,
exclude=False,
**kwargs
):
if lookup_expr is None:
lookup_expr = settings.DEFAULT_LOOKUP_EXPR
self.field_name = field_name
self.lookup_expr = lookup_expr
self.label = label
self.method = method
self.distinct = distinct
self.exclude = exclude
self.extra = kwargs
self.extra.setdefault("required", False)
self.creation_counter = Filter.creation_counter
Filter.creation_counter += 1
def get_method(self, qs):
"""Return filter method based on whether we're excluding
or simply filtering.
"""
return qs.exclude if self.exclude else qs.filter
def method():
"""
Filter method needs to be lazily resolved, as it may be dependent on
the 'parent' FilterSet.
"""
def fget(self):
return self._method
def fset(self, value):
self._method = value
# clear existing FilterMethod
if isinstance(self.filter, FilterMethod):
del self.filter
# override filter w/ FilterMethod.
if value is not None:
self.filter = FilterMethod(self)
return locals()
method = property(**method())
def label():
def fget(self):
if self._label is None and hasattr(self, "model"):
self._label = label_for_filter(
self.model, self.field_name, self.lookup_expr, self.exclude
)
return self._label
def fset(self, value):
self._label = value
return locals()
label = property(**label())
@property
def field(self):
if not hasattr(self, "_field"):
field_kwargs = self.extra.copy()
if settings.DISABLE_HELP_TEXT:
field_kwargs.pop("help_text", None)
self._field = self.field_class(label=self.label, **field_kwargs)
return self._field
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
if self.distinct:
qs = qs.distinct()
lookup = "%s__%s" % (self.field_name, self.lookup_expr)
qs = self.get_method(qs)(**{lookup: value})
return qs
class CharFilter(Filter):
field_class = forms.CharField
class BooleanFilter(Filter):
field_class = forms.NullBooleanField
class ChoiceFilter(Filter):
field_class = ChoiceField
def __init__(self, *args, **kwargs):
self.null_value = kwargs.get("null_value", settings.NULL_CHOICE_VALUE)
super().__init__(*args, **kwargs)
def filter(self, qs, value):
if value != self.null_value:
return super().filter(qs, value)
qs = self.get_method(qs)(
**{"%s__%s" % (self.field_name, self.lookup_expr): None}
)
return qs.distinct() if self.distinct else qs
class TypedChoiceFilter(Filter):
field_class = forms.TypedChoiceField
class UUIDFilter(Filter):
field_class = forms.UUIDField
class MultipleChoiceFilter(Filter):
"""
This filter performs OR(by default) or AND(using conjoined=True) query
on the selected options.
Advanced usage
--------------
Depending on your application logic, when all or no choices are selected,
filtering may be a no-operation. In this case you may wish to avoid the
filtering overhead, particularly if using a `distinct` call.
You can override `get_filter_predicate` to use a custom filter.
By default it will use the filter's name for the key, and the value will
be the model object - or in case of passing in `to_field_name` the
value of that attribute on the model.
Set `always_filter` to `False` after instantiation to enable the default
`is_noop` test. You can override `is_noop` if you need a different test
for your application.
`distinct` defaults to `True` as to-many relationships will generally
require this.
"""
field_class = MultipleChoiceField
always_filter = True
def __init__(self, *args, **kwargs):
kwargs.setdefault("distinct", True)
self.conjoined = kwargs.pop("conjoined", False)
self.null_value = kwargs.get("null_value", settings.NULL_CHOICE_VALUE)
super().__init__(*args, **kwargs)
def is_noop(self, qs, value):
"""
Return `True` to short-circuit unnecessary and potentially slow
filtering.
"""
if self.always_filter:
return False
# A reasonable default for being a noop...
if self.extra.get("required") and len(value) == len(self.field.choices):
return True
return False
def filter(self, qs, value):
if not value:
# Even though not a noop, no point filtering if empty.
return qs
if self.is_noop(qs, value):
return qs
if not self.conjoined:
q = Q()
for v in set(value):
if v == self.null_value:
v = None
predicate = self.get_filter_predicate(v)
if self.conjoined:
qs = self.get_method(qs)(**predicate)
else:
q |= Q(**predicate)
if not self.conjoined:
qs = self.get_method(qs)(q)
return qs.distinct() if self.distinct else qs
def get_filter_predicate(self, v):
name = self.field_name
if name and self.lookup_expr != settings.DEFAULT_LOOKUP_EXPR:
name = LOOKUP_SEP.join([name, self.lookup_expr])
try:
return {name: getattr(v, self.field.to_field_name)}
except (AttributeError, TypeError):
return {name: v}
class TypedMultipleChoiceFilter(MultipleChoiceFilter):
field_class = forms.TypedMultipleChoiceField
class DateFilter(Filter):
field_class = forms.DateField
class DateTimeFilter(Filter):
field_class = forms.DateTimeField
class IsoDateTimeFilter(DateTimeFilter):
"""
Uses IsoDateTimeField to support filtering on ISO 8601 formatted datetimes.
For context see:
* https://code.djangoproject.com/ticket/23448
* https://github.com/encode/django-rest-framework/issues/1338
* https://github.com/carltongibson/django-filter/pull/264
"""
field_class = IsoDateTimeField
class TimeFilter(Filter):
field_class = forms.TimeField
class DurationFilter(Filter):
field_class = forms.DurationField
class QuerySetRequestMixin:
"""
Add callable functionality to filters that support the ``queryset``
argument. If the ``queryset`` is callable, then it **must** accept the
``request`` object as a single argument.
This is useful for filtering querysets by properties on the ``request``
object, such as the user.
Example::
def departments(request):
company = request.user.company
return company.department_set.all()
class EmployeeFilter(filters.FilterSet):
department = filters.ModelChoiceFilter(queryset=departments)
...
The above example restricts the set of departments to those in the logged-in
user's associated company.
"""
def __init__(self, *args, **kwargs):
self.queryset = kwargs.get("queryset")
super().__init__(*args, **kwargs)
def get_request(self):
try:
return self.parent.request
except AttributeError:
return None
def get_queryset(self, request):
queryset = self.queryset
if callable(queryset):
return queryset(request)
return queryset
@property
def field(self):
request = self.get_request()
queryset = self.get_queryset(request)
if queryset is not None:
self.extra["queryset"] = queryset
return super().field
class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter):
field_class = ModelChoiceField
def __init__(self, *args, **kwargs):
kwargs.setdefault("empty_label", settings.EMPTY_CHOICE_LABEL)
super().__init__(*args, **kwargs)
class ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter):
field_class = ModelMultipleChoiceField
class NumberFilter(Filter):
field_class = forms.DecimalField
def get_max_validator(self):
"""
Return a MaxValueValidator for the field, or None to disable.
"""
return MaxValueValidator(1e50)
@property
def field(self):
if not hasattr(self, "_field"):
field = super().field
max_validator = self.get_max_validator()
if max_validator:
field.validators.append(max_validator)
self._field = field
return self._field
class NumericRangeFilter(Filter):
field_class = RangeField
def filter(self, qs, value):
if value:
if value.start is not None and value.stop is not None:
value = (value.start, value.stop)
elif value.start is not None:
self.lookup_expr = "startswith"
value = value.start
elif value.stop is not None:
self.lookup_expr = "endswith"
value = value.stop
return super().filter(qs, value)
class RangeFilter(Filter):
field_class = RangeField
def filter(self, qs, value):
if value:
if value.start is not None and value.stop is not None:
self.lookup_expr = "range"
value = (value.start, value.stop)
elif value.start is not None:
self.lookup_expr = "gte"
value = value.start
elif value.stop is not None:
self.lookup_expr = "lte"
value = value.stop
return super().filter(qs, value)
def _truncate(dt):
return dt.date()
class DateRangeFilter(ChoiceFilter):
choices = [
("today", _("Today")),
("yesterday", _("Yesterday")),
("week", _("Past 7 days")),
("month", _("This month")),
("year", _("This year")),
]
filters = {
"today": lambda qs, name: qs.filter(
**{
"%s__year" % name: now().year,
"%s__month" % name: now().month,
"%s__day" % name: now().day,
}
),
"yesterday": lambda qs, name: qs.filter(
**{
"%s__year" % name: (now() - timedelta(days=1)).year,
"%s__month" % name: (now() - timedelta(days=1)).month,
"%s__day" % name: (now() - timedelta(days=1)).day,
}
),
"week": lambda qs, name: qs.filter(
**{
"%s__gte" % name: _truncate(now() - timedelta(days=7)),
"%s__lt" % name: _truncate(now() + timedelta(days=1)),
}
),
"month": lambda qs, name: qs.filter(
**{"%s__year" % name: now().year, "%s__month" % name: now().month}
),
"year": lambda qs, name: qs.filter(
**{
"%s__year" % name: now().year,
}
),
}
def __init__(self, choices=None, filters=None, *args, **kwargs):
if choices is not None:
self.choices = choices
if filters is not None:
self.filters = filters
all_choices = list(
chain.from_iterable(
[subchoice[0] for subchoice in choice[1]]
if isinstance(choice[1], (list, tuple)) # This is an optgroup
else [choice[0]]
for choice in self.choices
)
)
unique = set(all_choices) ^ set(self.filters)
assert not unique, (
"Keys must be present in both 'choices' and 'filters'. Missing keys: "
"'%s'" % ", ".join(sorted(unique))
)
# null choice not relevant
kwargs.setdefault("null_label", None)
super().__init__(choices=self.choices, *args, **kwargs)
def filter(self, qs, value):
if not value:
return qs
assert value in self.filters
qs = self.filters[value](qs, self.field_name)
return qs.distinct() if self.distinct else qs
class DateFromToRangeFilter(RangeFilter):
field_class = DateRangeField
class DateTimeFromToRangeFilter(RangeFilter):
field_class = DateTimeRangeField
class IsoDateTimeFromToRangeFilter(RangeFilter):
field_class = IsoDateTimeRangeField
class TimeRangeFilter(RangeFilter):
field_class = TimeRangeField
class AllValuesFilter(ChoiceFilter):
@property
def field(self):
qs = self.model._default_manager.distinct()
qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
self.extra["choices"] = [(o, o) for o in qs]
return super().field
class AllValuesMultipleFilter(MultipleChoiceFilter):
@property
def field(self):
qs = self.model._default_manager.distinct()
qs = qs.order_by(self.field_name).values_list(self.field_name, flat=True)
self.extra["choices"] = [(o, o) for o in qs]
return super().field
class BaseCSVFilter(Filter):
"""
Base class for CSV type filters, such as IN and RANGE.
"""
base_field_class = BaseCSVField
def __init__(self, *args, **kwargs):
kwargs.setdefault("help_text", _("Multiple values may be separated by commas."))
super().__init__(*args, **kwargs)
class ConcreteCSVField(self.base_field_class, self.field_class):
pass
ConcreteCSVField.__name__ = self._field_class_name(
self.field_class, self.lookup_expr
)
self.field_class = ConcreteCSVField
@classmethod
def _field_class_name(cls, field_class, lookup_expr):
"""
Generate a suitable class name for the concrete field class. This is not
completely reliable, as not all field class names are of the format
<Type>Field.
ex::
BaseCSVFilter._field_class_name(DateTimeField, 'year__in')
returns 'DateTimeYearInField'
"""
# DateTimeField => DateTime
type_name = field_class.__name__
if type_name.endswith("Field"):
type_name = type_name[:-5]
# year__in => YearIn
parts = lookup_expr.split(LOOKUP_SEP)
expression_name = "".join(p.capitalize() for p in parts)
# DateTimeYearInField
return str("%s%sField" % (type_name, expression_name))
class BaseInFilter(BaseCSVFilter):
def __init__(self, *args, **kwargs):
kwargs.setdefault("lookup_expr", "in")
super().__init__(*args, **kwargs)
class BaseRangeFilter(BaseCSVFilter):
base_field_class = BaseRangeField
def __init__(self, *args, **kwargs):
kwargs.setdefault("lookup_expr", "range")
super().__init__(*args, **kwargs)
class LookupChoiceFilter(Filter):
"""
A combined filter that allows users to select the lookup expression from a dropdown.
* ``lookup_choices`` is an optional argument that accepts multiple input
formats, and is ultimately normalized as the choices used in the lookup
dropdown. See ``.get_lookup_choices()`` for more information.
* ``field_class`` is an optional argument that allows you to set the inner
form field class used to validate the value. Default: ``forms.CharField``
ex::
price = django_filters.LookupChoiceFilter(
field_class=forms.DecimalField,
lookup_choices=[
('exact', 'Equals'),
('gt', 'Greater than'),
('lt', 'Less than'),
]
)
"""
field_class = forms.CharField
outer_class = LookupChoiceField
def __init__(
self, field_name=None, lookup_choices=None, field_class=None, **kwargs
):
self.empty_label = kwargs.pop("empty_label", settings.EMPTY_CHOICE_LABEL)
super(LookupChoiceFilter, self).__init__(field_name=field_name, **kwargs)
self.lookup_choices = lookup_choices
if field_class is not None:
self.field_class = field_class
@classmethod
def normalize_lookup(cls, lookup):
"""
Normalize the lookup into a tuple of ``(lookup expression, display value)``
If the ``lookup`` is already a tuple, the tuple is not altered.
If the ``lookup`` is a string, a tuple is returned with the lookup
expression used as the basis for the display value.
ex::
>>> LookupChoiceFilter.normalize_lookup(('exact', 'Equals'))
('exact', 'Equals')
>>> LookupChoiceFilter.normalize_lookup('has_key')
('has_key', 'Has key')
"""
if isinstance(lookup, str):
return (lookup, pretty_name(lookup))
return (lookup[0], lookup[1])
def get_lookup_choices(self):
"""
Get the lookup choices in a format suitable for ``django.forms.ChoiceField``.
If the filter is initialized with ``lookup_choices``, this value is normalized
and passed to the underlying ``LookupChoiceField``. If no choices are provided,
they are generated from the corresponding model field's registered lookups.
"""
lookups = self.lookup_choices
if lookups is None:
field = get_model_field(self.model, self.field_name)
lookups = field.get_lookups()
return [self.normalize_lookup(lookup) for lookup in lookups]
@property
def field(self):
if not hasattr(self, "_field"):
inner_field = super().field
lookups = self.get_lookup_choices()
self._field = self.outer_class(
inner_field,
lookups,
label=self.label,
empty_label=self.empty_label,
required=self.extra["required"],
)
return self._field
def filter(self, qs, lookup):
if not lookup:
return super().filter(qs, None)
self.lookup_expr = lookup.lookup_expr
return super().filter(qs, lookup.value)
class OrderingFilter(BaseCSVFilter, ChoiceFilter):
"""
Enable queryset ordering. As an extension of ``ChoiceFilter`` it accepts
two additional arguments that are used to build the ordering choices.
* ``fields`` is a mapping of {model field name: parameter name}. The
parameter names are exposed in the choices and mask/alias the field
names used in the ``order_by()`` call. Similar to field ``choices``,
``fields`` accepts the 'list of two-tuples' syntax that retains order.
``fields`` may also just be an iterable of strings. In this case, the
field names simply double as the exposed parameter names.
* ``field_labels`` is an optional argument that allows you to customize
the display label for the corresponding parameter. It accepts a mapping
of {field name: human readable label}. Keep in mind that the key is the
field name, and not the exposed parameter name.
Additionally, you can just provide your own ``choices`` if you require
explicit control over the exposed options. For example, when you might
want to disable descending sort options.
This filter is also CSV-based, and accepts multiple ordering params. The
default select widget does not enable the use of this, but it is useful
for APIs.
"""
descending_fmt = _("%s (descending)")
def __init__(self, *args, **kwargs):
"""
``fields`` may be either a mapping or an iterable.
``field_labels`` must be a map of field names to display labels
"""
fields = kwargs.pop("fields", {})
fields = self.normalize_fields(fields)
field_labels = kwargs.pop("field_labels", {})
self.param_map = {v: k for k, v in fields.items()}
if "choices" not in kwargs:
kwargs["choices"] = self.build_choices(fields, field_labels)
kwargs.setdefault("label", _("Ordering"))
kwargs.setdefault("help_text", "")
kwargs.setdefault("null_label", None)
super().__init__(*args, **kwargs)
def get_ordering_value(self, param):
descending = param.startswith("-")
param = param[1:] if descending else param
field_name = self.param_map.get(param, param)
return "-%s" % field_name if descending else field_name
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
ordering = [
self.get_ordering_value(param)
for param in value
if param not in EMPTY_VALUES
]
return qs.order_by(*ordering)
@classmethod
def normalize_fields(cls, fields):
"""
Normalize the fields into an ordered map of {field name: param name}
"""
# fields is a mapping, copy into new OrderedDict
if isinstance(fields, dict):
return OrderedDict(fields)
# convert iterable of values => iterable of pairs (field name, param name)
assert isinstance(
fields, Iterable
), "'fields' must be an iterable (e.g., a list, tuple, or mapping)."
# fields is an iterable of field names
assert all(
isinstance(field, str)
or isinstance(field, Iterable)
and len(field) == 2 # may need to be wrapped in parens
for field in fields
), "'fields' must contain strings or (field name, param name) pairs."
return OrderedDict([(f, f) if isinstance(f, str) else f for f in fields])
def build_choices(self, fields, labels):
ascending = [
(param, labels.get(field, _(pretty_name(param))))
for field, param in fields.items()
]
descending = [
("-%s" % param, labels.get("-%s" % param, self.descending_fmt % label))
for param, label in ascending
]
# interleave the ascending and descending choices
return [val for pair in zip(ascending, descending) for val in pair]
class FilterMethod:
"""
This helper is used to override Filter.filter() when a 'method' argument
is passed. It proxies the call to the actual method on the filter's parent.
"""
def __init__(self, filter_instance):
self.f = filter_instance
def __call__(self, qs, value):
if value in EMPTY_VALUES:
return qs
return self.method(qs, self.f.field_name, value)
@property
def method(self):
"""
Resolve the method on the parent filterset.
"""
instance = self.f
# noop if 'method' is a function
if callable(instance.method):
return instance.method
# otherwise, method is the name of a method on the parent FilterSet.
assert hasattr(
instance, "parent"
), "Filter '%s' must have a parent FilterSet to find '.%s()'" % (
instance.field_name,
instance.method,
)
parent = instance.parent
method = getattr(parent, instance.method, None)
assert callable(
method
), "Expected parent FilterSet '%s.%s' to have a '.%s()' method." % (
parent.__class__.__module__,
parent.__class__.__name__,
instance.method,
)
return method

@ -0,0 +1,473 @@
import copy
from collections import OrderedDict
from django import forms
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields.related import ManyToManyRel, ManyToOneRel, OneToOneRel
from django.utils.datastructures import MultiValueDict
from .conf import settings
from .constants import ALL_FIELDS
from .filters import (
BaseInFilter,
BaseRangeFilter,
BooleanFilter,
CharFilter,
ChoiceFilter,
DateFilter,
DateTimeFilter,
DurationFilter,
Filter,
ModelChoiceFilter,
ModelMultipleChoiceFilter,
NumberFilter,
TimeFilter,
UUIDFilter,
)
from .utils import get_all_model_fields, get_model_field, resolve_field, try_dbfield
def remote_queryset(field):
"""
Get the queryset for the other side of a relationship. This works
for both `RelatedField`s and `ForeignObjectRel`s.
"""
model = field.related_model
# Reverse relationships do not have choice limits
if not hasattr(field, "get_limit_choices_to"):
return model._default_manager.all()
limit_choices_to = field.get_limit_choices_to()
return model._default_manager.complex_filter(limit_choices_to)
class FilterSetOptions:
def __init__(self, options=None):
self.model = getattr(options, "model", None)
self.fields = getattr(options, "fields", None)
self.exclude = getattr(options, "exclude", None)
self.filter_overrides = getattr(options, "filter_overrides", {})
self.form = getattr(options, "form", forms.Form)
class FilterSetMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs["declared_filters"] = cls.get_declared_filters(bases, attrs)
new_class = super().__new__(cls, name, bases, attrs)
new_class._meta = FilterSetOptions(getattr(new_class, "Meta", None))
new_class.base_filters = new_class.get_filters()
return new_class
@classmethod
def get_declared_filters(cls, bases, attrs):
filters = [
(filter_name, attrs.pop(filter_name))
for filter_name, obj in list(attrs.items())
if isinstance(obj, Filter)
]
# Default the `filter.field_name` to the attribute name on the filterset
for filter_name, f in filters:
if getattr(f, "field_name", None) is None:
f.field_name = filter_name
filters.sort(key=lambda x: x[1].creation_counter)
# Ensures a base class field doesn't override cls attrs, and maintains
# field precedence when inheriting multiple parents. e.g. if there is a
# class C(A, B), and A and B both define 'field', use 'field' from A.
known = set(attrs)
def visit(name):
known.add(name)
return name
base_filters = [
(visit(name), f)
for base in bases
if hasattr(base, "declared_filters")
for name, f in base.declared_filters.items()
if name not in known
]
return OrderedDict(base_filters + filters)
FILTER_FOR_DBFIELD_DEFAULTS = {
models.AutoField: {"filter_class": NumberFilter},
models.CharField: {"filter_class": CharFilter},
models.TextField: {"filter_class": CharFilter},
models.BooleanField: {"filter_class": BooleanFilter},
models.DateField: {"filter_class": DateFilter},
models.DateTimeField: {"filter_class": DateTimeFilter},
models.TimeField: {"filter_class": TimeFilter},
models.DurationField: {"filter_class": DurationFilter},
models.DecimalField: {"filter_class": NumberFilter},
models.SmallIntegerField: {"filter_class": NumberFilter},
models.IntegerField: {"filter_class": NumberFilter},
models.PositiveIntegerField: {"filter_class": NumberFilter},
models.PositiveSmallIntegerField: {"filter_class": NumberFilter},
models.FloatField: {"filter_class": NumberFilter},
models.NullBooleanField: {"filter_class": BooleanFilter},
models.SlugField: {"filter_class": CharFilter},
models.EmailField: {"filter_class": CharFilter},
models.FilePathField: {"filter_class": CharFilter},
models.URLField: {"filter_class": CharFilter},
models.GenericIPAddressField: {"filter_class": CharFilter},
models.CommaSeparatedIntegerField: {"filter_class": CharFilter},
models.UUIDField: {"filter_class": UUIDFilter},
# Forward relationships
models.OneToOneField: {
"filter_class": ModelChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
"to_field_name": f.remote_field.field_name,
"null_label": settings.NULL_CHOICE_LABEL if f.null else None,
},
},
models.ForeignKey: {
"filter_class": ModelChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
"to_field_name": f.remote_field.field_name,
"null_label": settings.NULL_CHOICE_LABEL if f.null else None,
},
},
models.ManyToManyField: {
"filter_class": ModelMultipleChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
},
},
# Reverse relationships
OneToOneRel: {
"filter_class": ModelChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
"null_label": settings.NULL_CHOICE_LABEL if f.null else None,
},
},
ManyToOneRel: {
"filter_class": ModelMultipleChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
},
},
ManyToManyRel: {
"filter_class": ModelMultipleChoiceFilter,
"extra": lambda f: {
"queryset": remote_queryset(f),
},
},
}
class BaseFilterSet:
FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
if queryset is None:
queryset = self._meta.model._default_manager.all()
model = queryset.model
self.is_bound = data is not None
self.data = data or MultiValueDict()
self.queryset = queryset
self.request = request
self.form_prefix = prefix
self.filters = copy.deepcopy(self.base_filters)
# propagate the model and filterset to the filters
for filter_ in self.filters.values():
filter_.model = model
filter_.parent = self
def is_valid(self):
"""
Return True if the underlying form has no errors, or False otherwise.
"""
return self.is_bound and self.form.is_valid()
@property
def errors(self):
"""
Return an ErrorDict for the data provided for the underlying form.
"""
return self.form.errors
def filter_queryset(self, queryset):
"""
Filter the queryset with the underlying form's `cleaned_data`. You must
call `is_valid()` or `errors` before calling this method.
This method should be overridden if additional filtering needs to be
applied to the queryset before it is cached.
"""
for name, value in self.form.cleaned_data.items():
queryset = self.filters[name].filter(queryset, value)
assert isinstance(
queryset, models.QuerySet
), "Expected '%s.%s' to return a QuerySet, but got a %s instead." % (
type(self).__name__,
name,
type(queryset).__name__,
)
return queryset
@property
def qs(self):
if not hasattr(self, "_qs"):
qs = self.queryset.all()
if self.is_bound:
# ensure form validation before filtering
self.errors
qs = self.filter_queryset(qs)
self._qs = qs
return self._qs
def get_form_class(self):
"""
Returns a django Form suitable of validating the filterset data.
This method should be overridden if the form class needs to be
customized relative to the filterset instance.
"""
fields = OrderedDict(
[(name, filter_.field) for name, filter_ in self.filters.items()]
)
return type(str("%sForm" % self.__class__.__name__), (self._meta.form,), fields)
@property
def form(self):
if not hasattr(self, "_form"):
Form = self.get_form_class()
if self.is_bound:
self._form = Form(self.data, prefix=self.form_prefix)
else:
self._form = Form(prefix=self.form_prefix)
return self._form
@classmethod
def get_fields(cls):
"""
Resolve the 'fields' argument that should be used for generating filters on the
filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'.
"""
model = cls._meta.model
fields = cls._meta.fields
exclude = cls._meta.exclude
assert not (fields is None and exclude is None), (
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
"has been deprecated since 0.15.0 and is now disallowed. Add an explicit "
"'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
)
# Setting exclude with no fields implies all other fields.
if exclude is not None and fields is None:
fields = ALL_FIELDS
# Resolve ALL_FIELDS into all fields for the filterset's model.
if fields == ALL_FIELDS:
fields = get_all_model_fields(model)
# Remove excluded fields
exclude = exclude or []
if not isinstance(fields, dict):
fields = [
(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude
]
else:
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def get_filter_name(cls, field_name, lookup_expr):
"""
Combine a field name and lookup expression into a usable filter name.
Exact lookups are the implicit default, so "exact" is stripped from the
end of the filter name.
"""
filter_name = LOOKUP_SEP.join([field_name, lookup_expr])
# This also works with transformed exact lookups, such as 'date__exact'
_default_expr = LOOKUP_SEP + settings.DEFAULT_LOOKUP_EXPR
if filter_name.endswith(_default_expr):
filter_name = filter_name[: -len(_default_expr)]
return filter_name
@classmethod
def get_filters(cls):
"""
Get all filters for the filterset. This is the combination of declared and
generated filters.
"""
# No model specified - skip filter generation
if not cls._meta.model:
return cls.declared_filters.copy()
# Determine the filters that should be included on the filterset.
filters = OrderedDict()
fields = cls.get_fields()
undefined = []
for field_name, lookups in fields.items():
field = get_model_field(cls._meta.model, field_name)
# warn if the field doesn't exist.
if field is None:
undefined.append(field_name)
for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr)
# If the filter is explicitly declared on the class, skip generation
if filter_name in cls.declared_filters:
filters[filter_name] = cls.declared_filters[filter_name]
continue
if field is not None:
filters[filter_name] = cls.filter_for_field(
field, field_name, lookup_expr
)
# Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)):
undefined = [f for f in undefined if f not in cls.declared_filters]
if undefined:
raise TypeError(
"'Meta.fields' must not contain non-model field names: %s"
% ", ".join(undefined)
)
# Add in declared filters. This is necessary since we don't enforce adding
# declared filters to the 'Meta.fields' option
filters.update(cls.declared_filters)
return filters
@classmethod
def filter_for_field(cls, field, field_name, lookup_expr=None):
if lookup_expr is None:
lookup_expr = settings.DEFAULT_LOOKUP_EXPR
field, lookup_type = resolve_field(field, lookup_expr)
default = {
"field_name": field_name,
"lookup_expr": lookup_expr,
}
filter_class, params = cls.filter_for_lookup(field, lookup_type)
default.update(params)
assert filter_class is not None, (
"%s resolved field '%s' with '%s' lookup to an unrecognized field "
"type %s. Try adding an override to 'Meta.filter_overrides'. See: "
"https://django-filter.readthedocs.io/en/main/ref/filterset.html"
"#customise-filter-generation-with-filter-overrides"
) % (cls.__name__, field_name, lookup_expr, field.__class__.__name__)
return filter_class(**default)
@classmethod
def filter_for_lookup(cls, field, lookup_type):
DEFAULTS = dict(cls.FILTER_DEFAULTS)
if hasattr(cls, "_meta"):
DEFAULTS.update(cls._meta.filter_overrides)
data = try_dbfield(DEFAULTS.get, field.__class__) or {}
filter_class = data.get("filter_class")
params = data.get("extra", lambda field: {})(field)
# if there is no filter class, exit early
if not filter_class:
return None, {}
# perform lookup specific checks
if lookup_type == "exact" and getattr(field, "choices", None):
return ChoiceFilter, {"choices": field.choices}
if lookup_type == "isnull":
data = try_dbfield(DEFAULTS.get, models.BooleanField)
filter_class = data.get("filter_class")
params = data.get("extra", lambda field: {})(field)
return filter_class, params
if lookup_type == "in":
class ConcreteInFilter(BaseInFilter, filter_class):
pass
ConcreteInFilter.__name__ = cls._csv_filter_class_name(
filter_class, lookup_type
)
return ConcreteInFilter, params
if lookup_type == "range":
class ConcreteRangeFilter(BaseRangeFilter, filter_class):
pass
ConcreteRangeFilter.__name__ = cls._csv_filter_class_name(
filter_class, lookup_type
)
return ConcreteRangeFilter, params
return filter_class, params
@classmethod
def _csv_filter_class_name(cls, filter_class, lookup_type):
"""
Generate a suitable class name for a concrete filter class. This is not
completely reliable, as not all filter class names are of the format
<Type>Filter.
ex::
FilterSet._csv_filter_class_name(DateTimeFilter, 'in')
returns 'DateTimeInFilter'
"""
# DateTimeFilter => DateTime
type_name = filter_class.__name__
if type_name.endswith("Filter"):
type_name = type_name[:-6]
# in => In
lookup_name = lookup_type.capitalize()
# DateTimeInFilter
return str("%s%sFilter" % (type_name, lookup_name))
class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass):
pass
def filterset_factory(model, filterset=FilterSet, fields=None):
attrs = {"model": model}
if fields is None:
if getattr(getattr(filterset, "Meta", {}), "fields", None) is None:
attrs["fields"] = ALL_FIELDS
else:
attrs["fields"] = fields
bases = (filterset.Meta,) if hasattr(filterset, "Meta") else ()
Meta = type("Meta", bases, attrs)
return type(filterset)(
str("%sFilterSet" % model._meta.object_name), (filterset,), {"Meta": Meta}
)

@ -0,0 +1,192 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# FULL NAME <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2020-03-24 00:48+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
"X-Generator: Gtranslator 2.91.7\n"
#: conf.py:16
msgid "date"
msgstr "تاريخ"
#: conf.py:17
msgid "year"
msgstr "سنة"
#: conf.py:18
msgid "month"
msgstr "شهر"
#: conf.py:19
msgid "day"
msgstr "يوم"
#: conf.py:20
msgid "week day"
msgstr "يوم الأسبوع"
#: conf.py:21
msgid "hour"
msgstr "ساعة"
#: conf.py:22
msgid "minute"
msgstr "دقيقة"
#: conf.py:23
msgid "second"
msgstr "ثانية"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "يحتوي على"
#: conf.py:29
msgid "is in"
msgstr "في داخل"
#: conf.py:30
msgid "is greater than"
msgstr "أكبر من"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "أكبر من أو يساوي"
#: conf.py:32
msgid "is less than"
msgstr "أصغر من"
#: conf.py:33
msgid "is less than or equal to"
msgstr "أصغر من أو يساوي"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "يبدأ ب"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "ينتهي ب"
#: conf.py:38
msgid "is in range"
msgstr "في النطاق"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "يطابق التعبير العادي"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "بحث"
#: conf.py:44
msgid "is contained by"
msgstr "موجود في"
#: conf.py:45
msgid "overlaps"
msgstr "يتداخل"
#: conf.py:46
msgid "has key"
msgstr "لديه مفتاح"
#: conf.py:47
msgid "has keys"
msgstr "لديه مفاتيح"
#: conf.py:48
msgid "has any keys"
msgstr "لديه أي مفاتيح"
#: fields.py:94
msgid "Select a lookup."
msgstr "حدد بحث"
#: fields.py:198
msgid "Range query expects two values."
msgstr "إستعلام النطاق يتوقع قيمتين"
#: filters.py:437
msgid "Today"
msgstr "اليوم"
#: filters.py:438
msgid "Yesterday"
msgstr "أمس"
#: filters.py:439
msgid "Past 7 days"
msgstr "الأيام السبعة الماضية"
#: filters.py:440
msgid "This month"
msgstr "هذا الشهر"
#: filters.py:441
msgid "This year"
msgstr "هذه السنة"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "يمكن فصل القيم المتعددة بفواصل."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (تنازلي)"
#: filters.py:737
msgid "Ordering"
msgstr "الترتيب"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "إرسال"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "مرشحات الحقل"
#: utils.py:308
msgid "exclude"
msgstr "استبعاد"
#: widgets.py:58
msgid "All"
msgstr "كل"
#: widgets.py:162
msgid "Unknown"
msgstr "مجهول"
#: widgets.py:162
msgid "Yes"
msgstr "نعم"
#: widgets.py:162
msgid "No"
msgstr "لا"

@ -0,0 +1,191 @@
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2016-09-29 11:47+0300\n"
"Last-Translator: Eugena Mikhaylikova <eugena.mihailikova@gmail.com>\n"
"Language-Team: TextTempearture\n"
"Language: be\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Poedit 1.8.9\n"
#: conf.py:16
msgid "date"
msgstr "дата"
#: conf.py:17
msgid "year"
msgstr "год"
#: conf.py:18
msgid "month"
msgstr "месяц"
#: conf.py:19
msgid "day"
msgstr "дзень"
#: conf.py:20
msgid "week day"
msgstr "дзень тыдня"
#: conf.py:21
msgid "hour"
msgstr "гадзіну"
#: conf.py:22
msgid "minute"
msgstr "хвіліна"
#: conf.py:23
msgid "second"
msgstr "секунда"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "змяшчае"
#: conf.py:29
msgid "is in"
msgstr "у"
#: conf.py:30
msgid "is greater than"
msgstr "больш чым"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "больш або роўна"
#: conf.py:32
msgid "is less than"
msgstr "менш чым"
#: conf.py:33
msgid "is less than or equal to"
msgstr "менш або роўна"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "пачынаецца"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "заканчваецца"
#: conf.py:38
msgid "is in range"
msgstr "у дыяпазоне"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "адпавядае рэгулярнаму выразу"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "пошук"
#: conf.py:44
msgid "is contained by"
msgstr "змяшчаецца ў"
#: conf.py:45
msgid "overlaps"
msgstr "перакрываецца"
#: conf.py:46
msgid "has key"
msgstr "мае ключ"
#: conf.py:47
msgid "has keys"
msgstr "мае ключы"
#: conf.py:48
msgid "has any keys"
msgstr "мае любыя ключы"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Запыт дыяпазону чакае два значэння."
#: filters.py:437
msgid "Today"
msgstr "Сёння"
#: filters.py:438
msgid "Yesterday"
msgstr "Учора"
#: filters.py:439
msgid "Past 7 days"
msgstr "Мінулыя 7 дзён"
#: filters.py:440
msgid "This month"
msgstr "За гэты месяц"
#: filters.py:441
msgid "This year"
msgstr "У гэтым годзе"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Некалькі значэнняў могуць быць падзеленыя коскамі."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (па змяншэнні)"
#: filters.py:737
msgid "Ordering"
msgstr "Парадак"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Адправіць"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Фільтры па палях"
#: utils.py:308
msgid "exclude"
msgstr "выключаючы"
#: widgets.py:58
msgid "All"
msgstr "Усе"
#: widgets.py:162
msgid "Unknown"
msgstr "Не было прапанавана"
#: widgets.py:162
msgid "Yes"
msgstr "Ды"
#: widgets.py:162
msgid "No"
msgstr "Няма"
#~ msgid "Any date"
#~ msgstr "Любая дата"

@ -0,0 +1,190 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Hristo Gatsinski <gatsinski@gmail.com>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2019-12-21 19:36+0200\n"
"Last-Translator: Hristo Gatsinski <gatsinski@gmail.com>\n"
"Language-Team: \n"
"Language: bg\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.9\n"
#: conf.py:16
msgid "date"
msgstr "дата"
#: conf.py:17
msgid "year"
msgstr "година"
#: conf.py:18
msgid "month"
msgstr "месец"
#: conf.py:19
msgid "day"
msgstr "ден"
#: conf.py:20
msgid "week day"
msgstr "ден от седмицата"
#: conf.py:21
msgid "hour"
msgstr "час"
#: conf.py:22
msgid "minute"
msgstr "минута"
#: conf.py:23
msgid "second"
msgstr "секунда"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "съдържа"
#: conf.py:29
msgid "is in"
msgstr "в"
#: conf.py:30
msgid "is greater than"
msgstr "е по-голям от"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "е по-голям или равен на"
#: conf.py:32
msgid "is less than"
msgstr "е по-малък от"
#: conf.py:33
msgid "is less than or equal to"
msgstr "е по-малък или равен на"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "започва с"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "завършва с"
#: conf.py:38
msgid "is in range"
msgstr "е в диапазона"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "съвпада с регуларен израз"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "търсене"
#: conf.py:44
msgid "is contained by"
msgstr "се съдържа от"
#: conf.py:45
msgid "overlaps"
msgstr "припокрива"
#: conf.py:46
msgid "has key"
msgstr "има ключ"
#: conf.py:47
msgid "has keys"
msgstr "има ключове"
#: conf.py:48
msgid "has any keys"
msgstr "има който и да е ключ"
#: fields.py:94
msgid "Select a lookup."
msgstr "Изберете справка"
#: fields.py:198
msgid "Range query expects two values."
msgstr "Търсенето по диапазон изисква две стойности"
#: filters.py:437
msgid "Today"
msgstr "Днес"
#: filters.py:438
msgid "Yesterday"
msgstr "Вчера"
#: filters.py:439
msgid "Past 7 days"
msgstr "Последните 7 дни"
#: filters.py:440
msgid "This month"
msgstr "Този месец"
#: filters.py:441
msgid "This year"
msgstr "Тази година"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Множество стойности може да се разделят със запетая"
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (намалавящ)"
#: filters.py:737
msgid "Ordering"
msgstr "Подредба"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Изпращане"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Филтри на полетата"
#: utils.py:308
msgid "exclude"
msgstr "изключва"
#: widgets.py:58
msgid "All"
msgstr "Всичко"
#: widgets.py:162
msgid "Unknown"
msgstr "Неизвестен"
#: widgets.py:162
msgid "Yes"
msgstr "Да"
#: widgets.py:162
msgid "No"
msgstr "Не"

@ -0,0 +1,190 @@
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2016-09-29 11:47+0300\n"
"Last-Translator: Eugena Mikhaylikova <eugena.mihailikova@gmail.com>\n"
"Language-Team: TextTempearture\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
"X-Generator: Poedit 1.8.9\n"
#: conf.py:16
msgid "date"
msgstr "datum"
#: conf.py:17
msgid "year"
msgstr "rok"
#: conf.py:18
msgid "month"
msgstr "měsíc"
#: conf.py:19
msgid "day"
msgstr "den"
#: conf.py:20
msgid "week day"
msgstr "den v týdnu"
#: conf.py:21
msgid "hour"
msgstr "hodinu"
#: conf.py:22
msgid "minute"
msgstr "minutu"
#: conf.py:23
msgid "second"
msgstr "vteřina"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "obsahuje"
#: conf.py:29
msgid "is in"
msgstr "v"
#: conf.py:30
msgid "is greater than"
msgstr "více než"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "větší nebo roven"
#: conf.py:32
msgid "is less than"
msgstr "méně než"
#: conf.py:33
msgid "is less than or equal to"
msgstr "menší nebo rovné"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "začíná"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "končí"
#: conf.py:38
msgid "is in range"
msgstr "v rozsahu"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "odpovídá normálnímu výrazu"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "vyhledávání"
#: conf.py:44
msgid "is contained by"
msgstr "je obsažen v"
#: conf.py:45
msgid "overlaps"
msgstr "překrývají"
#: conf.py:46
msgid "has key"
msgstr "má klíč"
#: conf.py:47
msgid "has keys"
msgstr "má klíče"
#: conf.py:48
msgid "has any keys"
msgstr "má nějaké klíče"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Rozsah dotazu očekává dvě hodnoty."
#: filters.py:437
msgid "Today"
msgstr "Dnes"
#: filters.py:438
msgid "Yesterday"
msgstr "Včera"
#: filters.py:439
msgid "Past 7 days"
msgstr "Posledních 7 dní"
#: filters.py:440
msgid "This month"
msgstr "Tento měsíc"
#: filters.py:441
msgid "This year"
msgstr "Tento rok"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Více hodnot lze oddělit čárkami."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (sestupně)"
#: filters.py:737
msgid "Ordering"
msgstr "Řád z"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Odeslat"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtry na polích"
#: utils.py:308
msgid "exclude"
msgstr "s výjimkou"
#: widgets.py:58
msgid "All"
msgstr "Všechno"
#: widgets.py:162
msgid "Unknown"
msgstr "Není nastaveno"
#: widgets.py:162
msgid "Yes"
msgstr "Ano"
#: widgets.py:162
msgid "No"
msgstr "Ne"
#~ msgid "Any date"
#~ msgstr "Jakékoliv datum"

@ -0,0 +1,190 @@
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2017-10-28\n"
"Last-Translator: Danni Randeris <danni@danniranderis.dk>\n"
"Language-Team: Danni Randeris <danni@danniranderis.dk>\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: conf.py:16
msgid "date"
msgstr "dato"
#: conf.py:17
msgid "year"
msgstr "år"
#: conf.py:18
msgid "month"
msgstr "måned"
#: conf.py:19
msgid "day"
msgstr "dag"
#: conf.py:20
msgid "week day"
msgstr "ugedag"
#: conf.py:21
msgid "hour"
msgstr "time"
#: conf.py:22
msgid "minute"
msgstr "minut"
#: conf.py:23
msgid "second"
msgstr "sekund"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "indeholder"
#: conf.py:29
msgid "is in"
msgstr "er i"
#: conf.py:30
msgid "is greater than"
msgstr "er større end"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "er større end eller lig med"
#: conf.py:32
msgid "is less than"
msgstr "er mindre end"
#: conf.py:33
msgid "is less than or equal to"
msgstr "er mindre end eller lig med"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "starter med"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "slutter med"
#: conf.py:38
msgid "is in range"
msgstr "er i intervallet"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "matcher regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "søg"
#: conf.py:44
msgid "is contained by"
msgstr "er indeholdt af"
#: conf.py:45
msgid "overlaps"
msgstr "overlapper"
#: conf.py:46
msgid "has key"
msgstr "har string"
#: conf.py:47
msgid "has keys"
msgstr "har stringe"
#: conf.py:48
msgid "has any keys"
msgstr "har hvilken som helst string"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Interval forespørgslen forventer to værdier."
#: filters.py:437
msgid "Today"
msgstr "I dag"
#: filters.py:438
msgid "Yesterday"
msgstr "I går"
#: filters.py:439
msgid "Past 7 days"
msgstr "Sidste 7 dage"
#: filters.py:440
msgid "This month"
msgstr "Denne måned"
#: filters.py:441
msgid "This year"
msgstr "Dette år"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Flere værdier kan adskilles via komma."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (aftagende)"
#: filters.py:737
msgid "Ordering"
msgstr "Sortering"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
#, fuzzy
msgid "Submit"
msgstr "Indsend"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
#, fuzzy
msgid "Field filters"
msgstr "Felt filtre"
#: utils.py:308
msgid "exclude"
msgstr "udelad"
#: widgets.py:58
msgid "All"
msgstr "Alle"
#: widgets.py:162
msgid "Unknown"
msgstr "Ukendt"
#: widgets.py:162
msgid "Yes"
msgstr "Ja"
#: widgets.py:162
msgid "No"
msgstr "Nej"
#~ msgid "Any date"
#~ msgstr "Hvilken som helst dag"

@ -0,0 +1,193 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2013-08-10 12:29+0100\n"
"Last-Translator: Florian Apolloner <florian@apolloner.eu>\n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.5.4\n"
#: conf.py:16
msgid "date"
msgstr "Datum"
#: conf.py:17
msgid "year"
msgstr "Jahr"
#: conf.py:18
msgid "month"
msgstr "Monat"
#: conf.py:19
msgid "day"
msgstr "Tag"
#: conf.py:20
msgid "week day"
msgstr "Wochentag"
#: conf.py:21
msgid "hour"
msgstr "Stunde"
#: conf.py:22
msgid "minute"
msgstr "Minute"
#: conf.py:23
msgid "second"
msgstr "Sekunde"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "enthält"
#: conf.py:29
msgid "is in"
msgstr "ist in"
#: conf.py:30
msgid "is greater than"
msgstr "ist größer als"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "ist größer oder gleich"
#: conf.py:32
msgid "is less than"
msgstr "ist kleiner als"
#: conf.py:33
msgid "is less than or equal to"
msgstr "ist kleiner oder gleich"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "beginnt mit"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "endet mit"
#: conf.py:38
msgid "is in range"
msgstr "ist im Bereich"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "passt auf Regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "Suche"
#: conf.py:44
msgid "is contained by"
msgstr "ist enthalten in"
#: conf.py:45
msgid "overlaps"
msgstr "überlappen"
#: conf.py:46
msgid "has key"
msgstr "hat Schlüssel"
#: conf.py:47
msgid "has keys"
msgstr "hat Schlüssel"
#: conf.py:48
msgid "has any keys"
msgstr "hat beliebige Schlüssel"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Die Bereichsabfrage erwartet zwei Werte."
#: filters.py:437
msgid "Today"
msgstr "Heute"
#: filters.py:438
msgid "Yesterday"
msgstr "Gestern"
#: filters.py:439
msgid "Past 7 days"
msgstr "Letzte 7 Tage"
#: filters.py:440
msgid "This month"
msgstr "Diesen Monat"
#: filters.py:441
msgid "This year"
msgstr "Dieses Jahr"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Mehrere Werte können durch Kommas getrennt sein."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (absteigend)"
#: filters.py:737
msgid "Ordering"
msgstr "Sortierung"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Absenden"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Feldfilter"
#: utils.py:308
msgid "exclude"
msgstr "ausschließen"
#: widgets.py:58
msgid "All"
msgstr "Alle"
#: widgets.py:162
msgid "Unknown"
msgstr "Unbekannte"
#: widgets.py:162
msgid "Yes"
msgstr "Ja"
#: widgets.py:162
msgid "No"
msgstr "Nein"
#~ msgid "Any date"
#~ msgstr "Alle Daten"

@ -0,0 +1,193 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Serafeim Papastefanos <spapas@gmail.com>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2017-11-16 10:04+0200\n"
"Last-Translator: Serafeim Papastefanos <spapas@gmail.com>\n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.5\n"
#: conf.py:16
msgid "date"
msgstr "ημερομηνία"
#: conf.py:17
msgid "year"
msgstr "έτος"
#: conf.py:18
msgid "month"
msgstr "μήνας"
#: conf.py:19
msgid "day"
msgstr "ημέρα"
#: conf.py:20
msgid "week day"
msgstr "ημέρα της εβδομάδας"
#: conf.py:21
msgid "hour"
msgstr "ώρα"
#: conf.py:22
msgid "minute"
msgstr "λεπτό"
#: conf.py:23
msgid "second"
msgstr "δευτερόλεπτο"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "περιέχει"
#: conf.py:29
msgid "is in"
msgstr "είναι εντός των"
#: conf.py:30
msgid "is greater than"
msgstr "είναι μεγαλύτερο από"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "είναι μεγαλύτερο ή ίσο του"
#: conf.py:32
msgid "is less than"
msgstr "είναι μικρότερο από"
#: conf.py:33
msgid "is less than or equal to"
msgstr "είναι μικρότερο ή ίσο του"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "ξεκινά με"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "τελειώνει με"
#: conf.py:38
msgid "is in range"
msgstr "είναι εντος του εύρους"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "περιέχει regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "αναζήτηση"
#: conf.py:44
msgid "is contained by"
msgstr "περιέχεται σε"
#: conf.py:45
msgid "overlaps"
msgstr "επικαλύπτεται"
#: conf.py:46
msgid "has key"
msgstr "έχει το κλειδί"
#: conf.py:47
msgid "has keys"
msgstr "έχει τα κλειδιά"
#: conf.py:48
msgid "has any keys"
msgstr "έχει οποιαδήποτε κλειδιά"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Το ερώτημα εύρους απαιτεί δύο τιμές,"
#: filters.py:437
msgid "Today"
msgstr "Σήμερα"
#: filters.py:438
msgid "Yesterday"
msgstr "Χτες"
#: filters.py:439
msgid "Past 7 days"
msgstr "Τις προηγούμενες 7 ημέρες"
#: filters.py:440
msgid "This month"
msgstr "Αυτό το μήνα"
#: filters.py:441
msgid "This year"
msgstr "Αυτό το έτος"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Οι πολλαπλές τιμές πρέπει να διαχωρίζονται με κόμμα."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (φθίνουσα"
#: filters.py:737
msgid "Ordering"
msgstr "Ταξινόμηση"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Υποβολή"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Φίλτρα πεδίων"
#: utils.py:308
msgid "exclude"
msgstr "απέκλεισε"
#: widgets.py:58
msgid "All"
msgstr "Όλα"
#: widgets.py:162
msgid "Unknown"
msgstr "Άγνωστο"
#: widgets.py:162
msgid "Yes"
msgstr "Ναι"
#: widgets.py:162
msgid "No"
msgstr "Όχι"
#~ msgid "Any date"
#~ msgstr "Οποιαδήποτε ημερομηνία"

@ -0,0 +1,195 @@
# Django Filter translation.
# Copyright (C) 2013
# This file is distributed under the same license as the django_filter package.
# Carlos Goce, 2017.
# Nicolás Stuardo, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-02-12 14:36+0000\n"
"Last-Translator: gallegonovato <fran-carro@hotmail.es>\n"
"Language-Team: Spanish <https://hosted.weblate.org/projects/django-filter/"
"django-filter/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.16-dev\n"
#: conf.py:16
msgid "date"
msgstr "fecha"
#: conf.py:17
msgid "year"
msgstr "año"
#: conf.py:18
msgid "month"
msgstr "mes"
#: conf.py:19
msgid "day"
msgstr "día"
#: conf.py:20
msgid "week day"
msgstr "día de la semana"
#: conf.py:21
msgid "hour"
msgstr "hora"
#: conf.py:22
msgid "minute"
msgstr "minuto"
#: conf.py:23
msgid "second"
msgstr "segundo"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "contiene"
#: conf.py:29
msgid "is in"
msgstr "presente en"
#: conf.py:30
msgid "is greater than"
msgstr "mayor que"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "mayor o igual que"
#: conf.py:32
msgid "is less than"
msgstr "menor que"
#: conf.py:33
msgid "is less than or equal to"
msgstr "menor o igual que"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "comienza por"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "termina por"
#: conf.py:38
msgid "is in range"
msgstr "en el rango"
#: conf.py:39
msgid "is null"
msgstr "es nulo"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "coincide con la expresión regular"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "buscar"
#: conf.py:44
msgid "is contained by"
msgstr "contenido en"
#: conf.py:45
msgid "overlaps"
msgstr "solapado"
#: conf.py:46
msgid "has key"
msgstr "contiene la clave"
#: conf.py:47
msgid "has keys"
msgstr "contiene las claves"
#: conf.py:48
msgid "has any keys"
msgstr "contiene alguna de las claves"
#: fields.py:94
msgid "Select a lookup."
msgstr "Seleccione un operador de consulta."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Consultar un rango requiere dos valores."
#: filters.py:437
msgid "Today"
msgstr "Hoy"
#: filters.py:438
msgid "Yesterday"
msgstr "Ayer"
#: filters.py:439
msgid "Past 7 days"
msgstr "Últimos 7 días"
#: filters.py:440
msgid "This month"
msgstr "Este mes"
#: filters.py:441
msgid "This year"
msgstr "Este año"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Múltiples valores separados por comas."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (descendente)"
#: filters.py:737
msgid "Ordering"
msgstr "Ordenado"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Enviar"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtros de campo"
#: utils.py:308
msgid "exclude"
msgstr "excluye"
#: widgets.py:58
msgid "All"
msgstr "Todo"
#: widgets.py:162
msgid "Unknown"
msgstr "Desconocido"
#: widgets.py:162
msgid "Yes"
msgstr "Sí"
#: widgets.py:162
msgid "No"
msgstr "No"
#~ msgid "Any date"
#~ msgstr "Cualquier fecha"

@ -0,0 +1,201 @@
# Django Filter translation.
# Copyright (C) 2013
# This file is distributed under the same license as the django_filter package.
# Gonzalo Bustos, 2015.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2015-10-11 20:53-0300\n"
"Last-Translator: Gonzalo Bustos\n"
"Language-Team: Spanish (Argentina)\n"
"Language: es_AR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.6.10\n"
#: conf.py:16
#, fuzzy
#| msgid "Any date"
msgid "date"
msgstr "Cualquier fecha"
#: conf.py:17
#, fuzzy
#| msgid "This year"
msgid "year"
msgstr "Este año"
#: conf.py:18
#, fuzzy
#| msgid "This month"
msgid "month"
msgstr "Este mes"
#: conf.py:19
#, fuzzy
#| msgid "Today"
msgid "day"
msgstr "Hoy"
#: conf.py:20
msgid "week day"
msgstr ""
#: conf.py:21
msgid "hour"
msgstr ""
#: conf.py:22
msgid "minute"
msgstr ""
#: conf.py:23
msgid "second"
msgstr ""
#: conf.py:27 conf.py:28
msgid "contains"
msgstr ""
#: conf.py:29
msgid "is in"
msgstr ""
#: conf.py:30
msgid "is greater than"
msgstr ""
#: conf.py:31
msgid "is greater than or equal to"
msgstr ""
#: conf.py:32
msgid "is less than"
msgstr ""
#: conf.py:33
msgid "is less than or equal to"
msgstr ""
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr ""
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr ""
#: conf.py:38
msgid "is in range"
msgstr ""
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr ""
#: conf.py:42 conf.py:49
msgid "search"
msgstr ""
#: conf.py:44
msgid "is contained by"
msgstr ""
#: conf.py:45
msgid "overlaps"
msgstr ""
#: conf.py:46
msgid "has key"
msgstr ""
#: conf.py:47
msgid "has keys"
msgstr ""
#: conf.py:48
msgid "has any keys"
msgstr ""
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr ""
#: filters.py:437
msgid "Today"
msgstr "Hoy"
#: filters.py:438
msgid "Yesterday"
msgstr ""
#: filters.py:439
msgid "Past 7 days"
msgstr "Últimos 7 días"
#: filters.py:440
msgid "This month"
msgstr "Este mes"
#: filters.py:441
msgid "This year"
msgstr "Este año"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr ""
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr ""
#: filters.py:737
msgid "Ordering"
msgstr ""
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr ""
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr ""
#: utils.py:308
msgid "exclude"
msgstr ""
#: widgets.py:58
msgid "All"
msgstr "Todos"
#: widgets.py:162
msgid "Unknown"
msgstr ""
#: widgets.py:162
msgid "Yes"
msgstr ""
#: widgets.py:162
msgid "No"
msgstr ""
#~ msgid "This is an exclusion filter"
#~ msgstr "Este es un filtro de exclusión"

@ -0,0 +1,190 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: conf.py:16
msgid "date"
msgstr "تاریخ"
#: conf.py:17
msgid "year"
msgstr "سال"
#: conf.py:18
msgid "month"
msgstr "ماه"
#: conf.py:19
msgid "day"
msgstr "روز"
#: conf.py:20
msgid "week day"
msgstr "روز هفته"
#: conf.py:21
msgid "hour"
msgstr "ساعت"
#: conf.py:22
msgid "minute"
msgstr "دقیقه"
#: conf.py:23
msgid "second"
msgstr "ثانیه"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "شامل"
#: conf.py:29
msgid "is in"
msgstr "هست در"
#: conf.py:30
msgid "is greater than"
msgstr "بزرگتر است از"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "بزرگتر یا مساوی است"
#: conf.py:32
msgid "is less than"
msgstr "کوچکتر است از"
#: conf.py:33
msgid "is less than or equal to"
msgstr "کوچکتر یا مساوی است"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "شروع می شود با"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "به پایان می رسد با"
#: conf.py:38
msgid "is in range"
msgstr "در محدوده"
#: conf.py:39
msgid "is null"
msgstr "خالی است"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "با ریجکس منطبق است"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "جستجو"
#: conf.py:44
msgid "is contained by"
msgstr "وجود دارد در"
#: conf.py:45
msgid "overlaps"
msgstr "تداخل دارد"
#: conf.py:46
msgid "has key"
msgstr "حاوی کلید است"
#: conf.py:47
msgid "has keys"
msgstr "حاوی کلیدها است"
#: conf.py:48
msgid "has any keys"
msgstr "حاوی هر کلیدی است"
#: fields.py:94
msgid "Select a lookup."
msgstr "یک لوک آپ را انتخاب کنید."
#: fields.py:198
msgid "Range query expects two values."
msgstr "محدوده کوئری دو مقدار را انتظار دارد."
#: filters.py:437
msgid "Today"
msgstr "امروز"
#: filters.py:438
msgid "Yesterday"
msgstr "دیروز"
#: filters.py:439
msgid "Past 7 days"
msgstr "۷ روز گذشته"
#: filters.py:440
msgid "This month"
msgstr "این ماه"
#: filters.py:441
msgid "This year"
msgstr "امسال"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "ممکن است چندین مقدار با کاما از هم جدا شوند."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (نزولی)"
#: filters.py:737
msgid "Ordering"
msgstr "مرتب سازی"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "ارسال"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "فیلترهای فیلد"
#: utils.py:308
msgid "exclude"
msgstr "به غیر از"
#: widgets.py:58
msgid "All"
msgstr "همه"
#: widgets.py:162
msgid "Unknown"
msgstr "ناشناس"
#: widgets.py:162
msgid "Yes"
msgstr "بله"
#: widgets.py:162
msgid "No"
msgstr "خیر"

@ -0,0 +1,191 @@
# Django Filter translation.
# Copyright (C) 2013
# This file is distributed under the same license as the django_filter package.
# Carlos Goce, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 17:45+0200\n"
"PO-Revision-Date: 2023-02-12 14:36+0000\n"
"Last-Translator: Janne Tervo <janne.tervo@iki.fi>\n"
"Language-Team: Finnish <https://hosted.weblate.org/projects/django-filter/"
"django-filter/fi/>\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.16-dev\n"
#: conf.py:16
msgid "date"
msgstr "päivämäärä"
#: conf.py:17
msgid "year"
msgstr "vuosi"
#: conf.py:18
msgid "month"
msgstr "kuukausi"
#: conf.py:19
msgid "day"
msgstr "päivä"
#: conf.py:20
msgid "week day"
msgstr "viikonpäivä"
#: conf.py:21
msgid "hour"
msgstr "tunti"
#: conf.py:22
msgid "minute"
msgstr "minuutti"
#: conf.py:23
msgid "second"
msgstr "sekunti"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "sisältää"
#: conf.py:29
msgid "is in"
msgstr "löytyy"
#: conf.py:30
msgid "is greater than"
msgstr "suurempi kuin"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "suurempi tai yhtäsuuri kuin"
#: conf.py:32
msgid "is less than"
msgstr "pienempi kuin"
#: conf.py:33
msgid "is less than or equal to"
msgstr "pienempi tai yhtäsuuri kuin"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "alkaa"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "päättyy"
#: conf.py:38
msgid "is in range"
msgstr "on välillä"
#: conf.py:39
msgid "is null"
msgstr "on null"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "täsmää säännölliseen lausekkeeseen"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "hae"
#: conf.py:44
msgid "is contained by"
msgstr "sisältyy kokonaan"
#: conf.py:45
msgid "overlaps"
msgstr "on päällekkäinen"
#: conf.py:46
msgid "has key"
msgstr "sisältää avaimen"
#: conf.py:47
msgid "has keys"
msgstr "sisältää avaimet"
#: conf.py:48
msgid "has any keys"
msgstr "sisältää minkä tahansa avaimista"
#: fields.py:94
msgid "Select a lookup."
msgstr "Hakuehto vaaditaan."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Välin hakuun tarvitaan kaksi arvoa."
#: filters.py:437
msgid "Today"
msgstr "Tänään"
#: filters.py:438
msgid "Yesterday"
msgstr "Eilen"
#: filters.py:439
msgid "Past 7 days"
msgstr "Edelliset 7 päivää"
#: filters.py:440
msgid "This month"
msgstr "Tässä kuussa"
#: filters.py:441
msgid "This year"
msgstr "Tänä vuonna"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Voit syöttää useita arvoja pilkulla erotettuna."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (laskeva)"
#: filters.py:737
msgid "Ordering"
msgstr "Järjestä"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Lähetä"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Kenttävalinnat"
#: utils.py:312
msgid "exclude"
msgstr "poissulje"
#: widgets.py:58
msgid "All"
msgstr "Kaikki"
#: widgets.py:162
msgid "Unknown"
msgstr "Tuntematon"
#: widgets.py:162
msgid "Yes"
msgstr "Kyllä"
#: widgets.py:162
msgid "No"
msgstr "Ei"

@ -0,0 +1,194 @@
# Django Filter translation.
# Copyright (C) 2013
# This file is distributed under the same license as the django_filter package.
# Axel Haustant <noirbizarre@gmail.com>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2024-01-18 14:00+0000\n"
"Last-Translator: Nils Van Zuijlen <nils.van-zuijlen@mailo.com>\n"
"Language-Team: French <https://hosted.weblate.org/projects/django-filter/"
"django-filter/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.4-dev\n"
#: conf.py:16
msgid "date"
msgstr "date"
#: conf.py:17
msgid "year"
msgstr "année"
#: conf.py:18
msgid "month"
msgstr "mois"
#: conf.py:19
msgid "day"
msgstr "jour"
#: conf.py:20
msgid "week day"
msgstr "jour de la semaine"
#: conf.py:21
msgid "hour"
msgstr "heure"
#: conf.py:22
msgid "minute"
msgstr "minute"
#: conf.py:23
msgid "second"
msgstr "seconde"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "contient"
#: conf.py:29
msgid "is in"
msgstr "est inclus dans"
#: conf.py:30
msgid "is greater than"
msgstr "supérieur à"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "supérieur ou égal à"
#: conf.py:32
msgid "is less than"
msgstr "inférieur à"
#: conf.py:33
msgid "is less than or equal to"
msgstr "inférieur ou égale à"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "commence par"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "se termine par"
#: conf.py:38
msgid "is in range"
msgstr "entre"
#: conf.py:39
msgid "is null"
msgstr "est nul"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "correspond à l'expression régulière"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "recherche"
#: conf.py:44
msgid "is contained by"
msgstr "est contenu dans"
#: conf.py:45
msgid "overlaps"
msgstr "chevauche"
#: conf.py:46
msgid "has key"
msgstr "contient la clé"
#: conf.py:47
msgid "has keys"
msgstr "contient les clés"
#: conf.py:48
msgid "has any keys"
msgstr "a une des clés"
#: fields.py:94
msgid "Select a lookup."
msgstr "Sélectionner un opérateur."
#: fields.py:198
msgid "Range query expects two values."
msgstr "La fourchette doit avoir 2 valeurs."
#: filters.py:437
msgid "Today"
msgstr "Aujourd'hui"
#: filters.py:438
msgid "Yesterday"
msgstr "Hier"
#: filters.py:439
msgid "Past 7 days"
msgstr "7 derniers jours"
#: filters.py:440
msgid "This month"
msgstr "Ce mois-ci"
#: filters.py:441
msgid "This year"
msgstr "Cette année"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Les valeurs multiples doivent être séparées par des virgules."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (décroissant)"
#: filters.py:737
msgid "Ordering"
msgstr "Tri"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Envoyer"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtres de champ"
#: utils.py:308
msgid "exclude"
msgstr "Exclut"
#: widgets.py:58
msgid "All"
msgstr "Tous"
#: widgets.py:162
msgid "Unknown"
msgstr "Inconnu"
#: widgets.py:162
msgid "Yes"
msgstr "Oui"
#: widgets.py:162
msgid "No"
msgstr "Non"
#~ msgid "This is an exclusion filter"
#~ msgstr "Ceci est un filtre d'exclusion"

@ -0,0 +1,194 @@
# Django Filter translation.
# Copyright (C) 2013
# This file is distributed under the same license as the django_filter package.
# Carlos Goce, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-06-11 16:51+0000\n"
"Last-Translator: Daniele Tricoli <eriol@mornie.org>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/django-filter/"
"django-filter/it/>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.18-dev\n"
#: conf.py:16
msgid "date"
msgstr "data"
#: conf.py:17
msgid "year"
msgstr "anno"
#: conf.py:18
msgid "month"
msgstr "mese"
#: conf.py:19
msgid "day"
msgstr "giorno"
#: conf.py:20
msgid "week day"
msgstr "giorno della settimana"
#: conf.py:21
msgid "hour"
msgstr "ora"
#: conf.py:22
msgid "minute"
msgstr "minuto"
#: conf.py:23
msgid "second"
msgstr "secondo"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "contiene"
#: conf.py:29
msgid "is in"
msgstr "presente in"
#: conf.py:30
msgid "is greater than"
msgstr "maggiore di"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "maggiore o uguale di"
#: conf.py:32
msgid "is less than"
msgstr "minore di"
#: conf.py:33
msgid "is less than or equal to"
msgstr "minore o uguale di"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "comincia per"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "termina per"
#: conf.py:38
msgid "is in range"
msgstr "nell'intervallo"
#: conf.py:39
msgid "is null"
msgstr "nullo"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "coincide con la espressione regolare"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "cerca"
#: conf.py:44
msgid "is contained by"
msgstr "contenuto in"
#: conf.py:45
msgid "overlaps"
msgstr "sovrapposto"
#: conf.py:46
msgid "has key"
msgstr "contiene la chiave"
#: conf.py:47
msgid "has keys"
msgstr "contiene le chiavi"
#: conf.py:48
msgid "has any keys"
msgstr "contiene qualsiasi chiave"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "La query di intervallo richiede due valori."
#: filters.py:437
msgid "Today"
msgstr "Oggi"
#: filters.py:438
msgid "Yesterday"
msgstr "Ieri"
#: filters.py:439
msgid "Past 7 days"
msgstr "Ultimi 7 giorni"
#: filters.py:440
msgid "This month"
msgstr "Questo mese"
#: filters.py:441
msgid "This year"
msgstr "Questo anno"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Più valori separati da virgole."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (decrescente)"
#: filters.py:737
msgid "Ordering"
msgstr "Ordinamento"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Invia"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtri del campo"
#: utils.py:308
msgid "exclude"
msgstr "escludi"
#: widgets.py:58
msgid "All"
msgstr "Tutti"
#: widgets.py:162
msgid "Unknown"
msgstr "Sconosciuto"
#: widgets.py:162
msgid "Yes"
msgstr "Sì"
#: widgets.py:162
msgid "No"
msgstr "No"
#~ msgid "Any date"
#~ msgstr "Qualsiasi data"

@ -0,0 +1,189 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-21 12:25+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Storm Heg <storm@stormbase.digital>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: conf.py:16
msgid "date"
msgstr "datum"
#: conf.py:17
msgid "year"
msgstr "jaar"
#: conf.py:18
msgid "month"
msgstr "maand"
#: conf.py:19
msgid "day"
msgstr "dag"
#: conf.py:20
msgid "week day"
msgstr "weekdag"
#: conf.py:21
msgid "hour"
msgstr "uur"
#: conf.py:22
msgid "minute"
msgstr "minuur"
#: conf.py:23
msgid "second"
msgstr "seconde"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "bevat"
#: conf.py:29
msgid "is in"
msgstr "zit in"
#: conf.py:30
msgid "is greater than"
msgstr "is groter dan"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "is groter dan of gelijk aan"
#: conf.py:32
msgid "is less than"
msgstr "is minder dan"
#: conf.py:33
msgid "is less than or equal to"
msgstr "is minder dan of gelijk aan"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "begint met"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "eindigt met"
#: conf.py:38
msgid "is in range"
msgstr "zit in bereik"
#: conf.py:39
msgid "is null"
msgstr "is null"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "matcht regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "zoek"
#: conf.py:44
msgid "is contained by"
msgstr "wordt bevat door"
#: conf.py:45
msgid "overlaps"
msgstr "overlapt"
#: conf.py:46
msgid "has key"
msgstr "heeft key"
#: conf.py:47
msgid "has keys"
msgstr "heeft keys"
#: conf.py:48
msgid "has any keys"
msgstr "heeft keys"
#: fields.py:94
msgid "Select a lookup."
msgstr "Selecteer een lookup."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Bereik query verwacht twee waarden."
#: filters.py:437
msgid "Today"
msgstr "Vandaag"
#: filters.py:438
msgid "Yesterday"
msgstr "Gisteren"
#: filters.py:439
msgid "Past 7 days"
msgstr "Afgelopen 7 dagen"
#: filters.py:440
msgid "This month"
msgstr "Deze maand"
#: filters.py:441
msgid "This year"
msgstr "Dit jaar"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Meerdere waarden kunnen gescheiden worden door komma's."
#: filters.py:721 tests/test_filters.py:1670
#, python-format
msgid "%s (descending)"
msgstr "%s (aflopend)"
#: filters.py:737
msgid "Ordering"
msgstr "Volgorde"
#: rest_framework/filterset.py:33
#: templates/rest_framework/form.html:5
msgid "Submit"
msgstr "Indienen"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Veld filters"
#: utils.py:323
msgid "exclude"
msgstr "uitsluiten"
#: widgets.py:58
msgid "All"
msgstr "Alles"
#: widgets.py:162
msgid "Unknown"
msgstr "Onbekend"
#: widgets.py:162
msgid "Yes"
msgstr "Ja"
#: widgets.py:162
msgid "No"
msgstr "Nee"

@ -0,0 +1,199 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: django_filters 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-04-10 20:47+0000\n"
"Last-Translator: Quadric <quadmachine@wp.pl>\n"
"Language-Team: Polish <https://hosted.weblate.org/projects/django-filter/"
"django-filter/pl/>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Generator: Weblate 4.17-dev\n"
#: conf.py:16
msgid "date"
msgstr "data"
#: conf.py:17
msgid "year"
msgstr "rok"
#: conf.py:18
msgid "month"
msgstr "miesiąc"
#: conf.py:19
msgid "day"
msgstr "dzień"
#: conf.py:20
msgid "week day"
msgstr "dzień tygodnia"
#: conf.py:21
msgid "hour"
msgstr "godzina"
#: conf.py:22
msgid "minute"
msgstr "minuta"
#: conf.py:23
msgid "second"
msgstr "sekunda"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "zawiera"
#: conf.py:29
msgid "is in"
msgstr "zawiera się w"
#: conf.py:30
msgid "is greater than"
msgstr "powyżej"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "powyżej lub równe"
#: conf.py:32
msgid "is less than"
msgstr "poniżej"
#: conf.py:33
msgid "is less than or equal to"
msgstr "poniżej lub równe"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "zaczyna się od"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "kończy się na"
#: conf.py:38
msgid "is in range"
msgstr "zawiera się w zakresie"
#: conf.py:39
msgid "is null"
msgstr "jest wartością null"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "pasuje do wyrażenia regularnego"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "szukaj"
#: conf.py:44
msgid "is contained by"
msgstr "zawiera się w"
#: conf.py:45
msgid "overlaps"
msgstr "nakłada się"
#: conf.py:46
msgid "has key"
msgstr "posiada klucz"
#: conf.py:47
msgid "has keys"
msgstr "posiada klucze"
#: conf.py:48
msgid "has any keys"
msgstr "posiada jakiekolwiek klucze"
#: fields.py:94
msgid "Select a lookup."
msgstr "Wybierz wyszukiwanie."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Zapytanie o zakres oczekuje dwóch wartości."
#: filters.py:437
msgid "Today"
msgstr "Dziś"
#: filters.py:438
msgid "Yesterday"
msgstr "Wczoraj"
#: filters.py:439
msgid "Past 7 days"
msgstr "Ostatnie 7 dni"
#: filters.py:440
msgid "This month"
msgstr "Ten miesiąc"
#: filters.py:441
msgid "This year"
msgstr "Ten rok"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Wiele wartości można rozdzielić przecinkami."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (malejąco)"
#: filters.py:737
msgid "Ordering"
msgstr "Sortowanie"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Wyślij"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtry pola"
#: utils.py:308
msgid "exclude"
msgstr "wyklucz"
#: widgets.py:58
msgid "All"
msgstr "Wszystko"
#: widgets.py:162
msgid "Unknown"
msgstr "Nieznane"
#: widgets.py:162
msgid "Yes"
msgstr "Tak"
#: widgets.py:162
msgid "No"
msgstr "Nie"
#~ msgid "Any date"
#~ msgstr "Dowolna data"
#~ msgid "This is an exclusion filter"
#~ msgstr "Jest to filtr wykluczający"

@ -0,0 +1,194 @@
# Django Filter translation.
# Copyright (C) 2017
# This file is distributed under the same license as the django_filter package.
# Anderson Scouto da Silva, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-06-30 13:51+0000\n"
"Last-Translator: Diogo Silva <diogosilv30@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/"
"django-filter/django-filter/pt_BR/>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.0-dev\n"
#: conf.py:16
msgid "date"
msgstr "data"
#: conf.py:17
msgid "year"
msgstr "ano"
#: conf.py:18
msgid "month"
msgstr "mês"
#: conf.py:19
msgid "day"
msgstr "dia"
#: conf.py:20
msgid "week day"
msgstr "dia da semana"
#: conf.py:21
msgid "hour"
msgstr "hora"
#: conf.py:22
msgid "minute"
msgstr "minuto"
#: conf.py:23
msgid "second"
msgstr "segundo"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "contém"
#: conf.py:29
msgid "is in"
msgstr "presente em"
#: conf.py:30
msgid "is greater than"
msgstr "é maior que"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "é maior ou igual que"
#: conf.py:32
msgid "is less than"
msgstr "é menor que"
#: conf.py:33
msgid "is less than or equal to"
msgstr "é menor ou igual que"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "começa com"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "termina com"
#: conf.py:38
msgid "is in range"
msgstr "está no range"
#: conf.py:39
msgid "is null"
msgstr "é nulo"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "coincide com a expressão regular"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "buscar"
#: conf.py:44
msgid "is contained by"
msgstr "está contido por"
#: conf.py:45
msgid "overlaps"
msgstr "sobrepõe"
#: conf.py:46
msgid "has key"
msgstr "contém a chave"
#: conf.py:47
msgid "has keys"
msgstr "contém as chaves"
#: conf.py:48
msgid "has any keys"
msgstr "contém uma das chaves"
#: fields.py:94
msgid "Select a lookup."
msgstr "Selecione uma pesquisa."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Consulta por range requer dois valores."
#: filters.py:437
msgid "Today"
msgstr "Hoje"
#: filters.py:438
msgid "Yesterday"
msgstr "Ontem"
#: filters.py:439
msgid "Past 7 days"
msgstr "Últimos 7 dias"
#: filters.py:440
msgid "This month"
msgstr "Este mês"
#: filters.py:441
msgid "This year"
msgstr "Este ano"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Valores múltiplos podem ser separados por vírgulas."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (decrescente)"
#: filters.py:737
msgid "Ordering"
msgstr "Ordenado"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Enviar"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtros de campo"
#: utils.py:308
msgid "exclude"
msgstr "excluir"
#: widgets.py:58
msgid "All"
msgstr "Tudo"
#: widgets.py:162
msgid "Unknown"
msgstr "Desconhecido"
#: widgets.py:162
msgid "Yes"
msgstr "Sim"
#: widgets.py:162
msgid "No"
msgstr "Não"
#~ msgid "Any date"
#~ msgstr "Qualquer data"

@ -0,0 +1,192 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 14:47+0000\n"
"PO-Revision-Date: 2023-02-10 16:28+0000\n"
"Last-Translator: Dan Braghis <dan@zerolab.org>\n"
"Language-Team: Romanian <https://hosted.weblate.org/projects/django-filter/"
"django-filter/ro/>\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
"20)) ? 1 : 2;\n"
"X-Generator: Weblate 4.16-dev\n"
#: conf.py:16
msgid "date"
msgstr "dată"
#: conf.py:17
msgid "year"
msgstr "an"
#: conf.py:18
msgid "month"
msgstr "lună"
#: conf.py:19
msgid "day"
msgstr "zi"
#: conf.py:20
msgid "week day"
msgstr "zi a săptămânii"
#: conf.py:21
msgid "hour"
msgstr "oră"
#: conf.py:22
msgid "minute"
msgstr "minută"
#: conf.py:23
msgid "second"
msgstr "secundă"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "conține"
#: conf.py:29
msgid "is in"
msgstr "este în"
#: conf.py:30
msgid "is greater than"
msgstr "este mai mare decât"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "este mai mare sau egal cu"
#: conf.py:32
msgid "is less than"
msgstr "este mai mic decât"
#: conf.py:33
msgid "is less than or equal to"
msgstr "este mai mic sau egal cu"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "începe cu"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "se termină cu"
#: conf.py:38
msgid "is in range"
msgstr "este în intervalul"
#: conf.py:39
msgid "is null"
msgstr "este nul"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "se potrivește cu expresia regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "căutare"
#: conf.py:44
msgid "is contained by"
msgstr "cuprins de"
#: conf.py:45
msgid "overlaps"
msgstr "se suprapune"
#: conf.py:46
msgid "has key"
msgstr "are cheia"
#: conf.py:47
msgid "has keys"
msgstr "are cheile"
#: conf.py:48
msgid "has any keys"
msgstr "are orice cheie"
#: fields.py:94
msgid "Select a lookup."
msgstr "Selectați o căutare."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Interogarea de interval așteaptă două valori."
#: filters.py:437
msgid "Today"
msgstr "Astăzi"
#: filters.py:438
msgid "Yesterday"
msgstr "Ieri"
#: filters.py:439
msgid "Past 7 days"
msgstr "Ultimele 7 zile"
#: filters.py:440
msgid "This month"
msgstr "Luna aceasta"
#: filters.py:441
msgid "This year"
msgstr "Anul acesta"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Valorile multiple pot fi separate prin virgule."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (descescător)"
#: filters.py:737
msgid "Ordering"
msgstr "Rânduire"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Trimite"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtre de câmp"
#: utils.py:312
msgid "exclude"
msgstr "exclude"
#: widgets.py:58
msgid "All"
msgstr "Toate"
#: widgets.py:162
msgid "Unknown"
msgstr "Necunoscut"
#: widgets.py:162
msgid "Yes"
msgstr "Da"
#: widgets.py:162
msgid "No"
msgstr "Nu"

@ -0,0 +1,195 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2016-09-29 11:47+0300\n"
"Last-Translator: Mikhail Mitrofanov <mm@elec.ru>\n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Poedit 1.8.9\n"
#: conf.py:16
msgid "date"
msgstr "дата"
#: conf.py:17
msgid "year"
msgstr "год"
#: conf.py:18
msgid "month"
msgstr "месяц"
#: conf.py:19
msgid "day"
msgstr "день"
#: conf.py:20
msgid "week day"
msgstr "день недели"
#: conf.py:21
msgid "hour"
msgstr "час"
#: conf.py:22
msgid "minute"
msgstr "минута"
#: conf.py:23
msgid "second"
msgstr "секунда"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "содержит"
#: conf.py:29
msgid "is in"
msgstr "в"
#: conf.py:30
msgid "is greater than"
msgstr "больше чем"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "больше или равно"
#: conf.py:32
msgid "is less than"
msgstr "меньше чем"
#: conf.py:33
msgid "is less than or equal to"
msgstr "меньше или равно"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "начинается"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "заканчивается"
#: conf.py:38
msgid "is in range"
msgstr "в диапазоне"
#: conf.py:39
msgid "is null"
msgstr ""
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "соответствует регулярному выражению"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "поиск"
#: conf.py:44
msgid "is contained by"
msgstr "содержится в"
#: conf.py:45
msgid "overlaps"
msgstr "перекрывается"
#: conf.py:46
msgid "has key"
msgstr "имеет ключ"
#: conf.py:47
msgid "has keys"
msgstr "имеет ключи"
#: conf.py:48
msgid "has any keys"
msgstr "имеет любые ключи"
#: fields.py:94
msgid "Select a lookup."
msgstr ""
#: fields.py:198
msgid "Range query expects two values."
msgstr "Запрос диапазона ожидает два значения."
#: filters.py:437
msgid "Today"
msgstr "Сегодня"
#: filters.py:438
msgid "Yesterday"
msgstr "Вчера"
#: filters.py:439
msgid "Past 7 days"
msgstr "Прошедшие 7 дней"
#: filters.py:440
msgid "This month"
msgstr "За этот месяц"
#: filters.py:441
msgid "This year"
msgstr "В этом году"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Несколько значений могут быть разделены запятыми."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (по убыванию)"
#: filters.py:737
msgid "Ordering"
msgstr "Порядок"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Отправить"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Фильтры по полям"
#: utils.py:308
msgid "exclude"
msgstr "исключая"
#: widgets.py:58
msgid "All"
msgstr "Все"
#: widgets.py:162
msgid "Unknown"
msgstr "Не задано"
#: widgets.py:162
msgid "Yes"
msgstr "Да"
#: widgets.py:162
msgid "No"
msgstr "Нет"
#~ msgid "Any date"
#~ msgstr "Любая дата"

@ -0,0 +1,196 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-07-21 19:07+0000\n"
"Last-Translator: Milan Šalka <salka.milan@googlemail.com>\n"
"Language-Team: Slovak <https://hosted.weblate.org/projects/django-filter/"
"django-filter/sk/>\n"
"Language: sk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n "
">= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
"X-Generator: Weblate 5.0-dev\n"
"X-Translated-Using: django-rosetta 0.8.1\n"
#: conf.py:16
msgid "date"
msgstr "dátum"
#: conf.py:17
msgid "year"
msgstr "rok"
#: conf.py:18
msgid "month"
msgstr "mesiac"
#: conf.py:19
msgid "day"
msgstr "deň"
#: conf.py:20
msgid "week day"
msgstr "deň týždňa"
#: conf.py:21
msgid "hour"
msgstr "hodina"
#: conf.py:22
msgid "minute"
msgstr "minúta"
#: conf.py:23
msgid "second"
msgstr "sekunda"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "obsahuje"
#: conf.py:29
msgid "is in"
msgstr "je v"
#: conf.py:30
msgid "is greater than"
msgstr "je vačší než"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "je vačší alebo rovný ako"
#: conf.py:32
msgid "is less than"
msgstr "je menší než"
#: conf.py:33
msgid "is less than or equal to"
msgstr "je menší alebo rovný ako"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "začína s"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "končí s"
#: conf.py:38
msgid "is in range"
msgstr "je v rozsahu"
#: conf.py:39
msgid "is null"
msgstr "je nulová"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "spĺňa regex"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "hľadať"
#: conf.py:44
msgid "is contained by"
msgstr "je obsiahnutý"
#: conf.py:45
msgid "overlaps"
msgstr "presahuje"
#: conf.py:46
msgid "has key"
msgstr "má kľúč"
#: conf.py:47
msgid "has keys"
msgstr "má kľúče"
#: conf.py:48
msgid "has any keys"
msgstr "má akékoľvek kľúče"
#: fields.py:94
msgid "Select a lookup."
msgstr "Vyberte vyhľadávanie."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Rozsah očakáva dve hodnoty."
#: filters.py:437
msgid "Today"
msgstr "Dnes"
#: filters.py:438
msgid "Yesterday"
msgstr "Včera"
#: filters.py:439
msgid "Past 7 days"
msgstr "Posledných 7 dní"
#: filters.py:440
msgid "This month"
msgstr "Tento mesiac"
#: filters.py:441
msgid "This year"
msgstr "Tento rok"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Viacero hodnôt môže byť oddelených čiarkami."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (klesajúco)"
#: filters.py:737
msgid "Ordering"
msgstr "Zoradenie"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Potvrdiť"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Filtre poľa"
#: utils.py:308
msgid "exclude"
msgstr "neobsahuje"
#: widgets.py:58
msgid "All"
msgstr "Všetky"
#: widgets.py:162
msgid "Unknown"
msgstr "Neznáme"
#: widgets.py:162
msgid "Yes"
msgstr "Áno"
#: widgets.py:162
msgid "No"
msgstr "Nie"
#~ msgid "Any date"
#~ msgstr "Akýkoľvek dátum"

@ -0,0 +1,193 @@
#
msgid ""
msgstr ""
"Project-Id-Version: django-filter\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2024-01-01 15:10+0000\n"
"Last-Translator: Сергій <sergiy.goncharuk.1@gmail.com>\n"
"Language-Team: Ukrainian <https://hosted.weblate.org/projects/django-filter/"
"django-filter/uk/>\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 "
"? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > "
"14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % "
"100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
"X-Generator: Weblate 5.4-dev\n"
#: conf.py:16
msgid "date"
msgstr "дата"
#: conf.py:17
msgid "year"
msgstr "рік"
#: conf.py:18
msgid "month"
msgstr "місяць"
#: conf.py:19
msgid "day"
msgstr "день"
#: conf.py:20
msgid "week day"
msgstr "день тижня"
#: conf.py:21
msgid "hour"
msgstr "година"
#: conf.py:22
msgid "minute"
msgstr "хвилина"
#: conf.py:23
msgid "second"
msgstr "секунда"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "містить"
#: conf.py:29
msgid "is in"
msgstr "в"
#: conf.py:30
msgid "is greater than"
msgstr "більше ніж"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "більше або дорівнює"
#: conf.py:32
msgid "is less than"
msgstr "менше ніж"
#: conf.py:33
msgid "is less than or equal to"
msgstr "менше або дорівнює"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "починається"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "закінчується"
#: conf.py:38
msgid "is in range"
msgstr "в діапазоні"
#: conf.py:39
msgid "is null"
msgstr "є порожнім"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "відповідає регулярному виразу"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "пошук"
#: conf.py:44
msgid "is contained by"
msgstr "міститься в"
#: conf.py:45
msgid "overlaps"
msgstr "перекривається"
#: conf.py:46
msgid "has key"
msgstr "має ключ"
#: conf.py:47
msgid "has keys"
msgstr "має ключі"
#: conf.py:48
msgid "has any keys"
msgstr "має будь-які ключі"
#: fields.py:94
msgid "Select a lookup."
msgstr "Оберіть оператор запиту."
#: fields.py:198
msgid "Range query expects two values."
msgstr "Запит діапазону очікує два значення."
#: filters.py:437
msgid "Today"
msgstr "Сьогодні"
#: filters.py:438
msgid "Yesterday"
msgstr "Вчора"
#: filters.py:439
msgid "Past 7 days"
msgstr "Минулі 7 днів"
#: filters.py:440
msgid "This month"
msgstr "За цей місяць"
#: filters.py:441
msgid "This year"
msgstr "В цьому році"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "Кілька значень можуть бути розділені комами."
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s (по спадаючій)"
#: filters.py:737
msgid "Ordering"
msgstr "Порядок"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "Відправити"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "Фільтри по полях"
#: utils.py:308
msgid "exclude"
msgstr "виключаючи"
#: widgets.py:58
msgid "All"
msgstr "Усе"
#: widgets.py:162
msgid "Unknown"
msgstr "Не задано"
#: widgets.py:162
msgid "Yes"
msgstr "Так"
#: widgets.py:162
msgid "No"
msgstr "Немає"
#~ msgid "Any date"
#~ msgstr "Будь-яка дата"

@ -0,0 +1,194 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Kane Blueriver <kxxoling@gmail.com>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-10 11:07+0000\n"
"PO-Revision-Date: 2023-05-07 03:57+0000\n"
"Last-Translator: Lattefang <370358679@qq.com>\n"
"Language-Team: Chinese (Simplified) <https://hosted.weblate.org/projects/"
"django-filter/django-filter/zh_Hans/>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.18-dev\n"
#: conf.py:16
msgid "date"
msgstr "日期"
#: conf.py:17
msgid "year"
msgstr "年"
#: conf.py:18
msgid "month"
msgstr "月"
#: conf.py:19
msgid "day"
msgstr "日"
#: conf.py:20
msgid "week day"
msgstr "工作日"
#: conf.py:21
msgid "hour"
msgstr "小时"
#: conf.py:22
msgid "minute"
msgstr "分钟"
#: conf.py:23
msgid "second"
msgstr "秒"
#: conf.py:27 conf.py:28
msgid "contains"
msgstr "包含"
#: conf.py:29
msgid "is in"
msgstr "在"
#: conf.py:30
msgid "is greater than"
msgstr "大于"
#: conf.py:31
msgid "is greater than or equal to"
msgstr "大于等于"
#: conf.py:32
msgid "is less than"
msgstr "小于"
#: conf.py:33
msgid "is less than or equal to"
msgstr "小于等于"
#: conf.py:34 conf.py:35
msgid "starts with"
msgstr "以……开始"
#: conf.py:36 conf.py:37
msgid "ends with"
msgstr "以……结尾"
#: conf.py:38
msgid "is in range"
msgstr "在范围内"
#: conf.py:39
msgid "is null"
msgstr "为空"
#: conf.py:40 conf.py:41
msgid "matches regex"
msgstr "匹配正则表达式"
#: conf.py:42 conf.py:49
msgid "search"
msgstr "搜索"
#: conf.py:44
msgid "is contained by"
msgstr "包含在"
#: conf.py:45
msgid "overlaps"
msgstr "重叠"
#: conf.py:46
msgid "has key"
msgstr "单值"
#: conf.py:47
msgid "has keys"
msgstr "多值"
#: conf.py:48
msgid "has any keys"
msgstr "任何值"
#: fields.py:94
msgid "Select a lookup."
msgstr "选择查找。"
#: fields.py:198
msgid "Range query expects two values."
msgstr "范围查询需要两个值。"
#: filters.py:437
msgid "Today"
msgstr "今日"
#: filters.py:438
msgid "Yesterday"
msgstr "昨日"
#: filters.py:439
msgid "Past 7 days"
msgstr "过去 7 日"
#: filters.py:440
msgid "This month"
msgstr "本月"
#: filters.py:441
msgid "This year"
msgstr "今年"
#: filters.py:543
msgid "Multiple values may be separated by commas."
msgstr "多个值可以用逗号分隔。"
#: filters.py:721
#, python-format
msgid "%s (descending)"
msgstr "%s降序"
#: filters.py:737
msgid "Ordering"
msgstr "排序"
#: rest_framework/filterset.py:33
#: templates/django_filters/rest_framework/form.html:5
msgid "Submit"
msgstr "提交"
#: templates/django_filters/rest_framework/crispy_form.html:4
#: templates/django_filters/rest_framework/form.html:2
msgid "Field filters"
msgstr "字段过滤器"
#: utils.py:308
msgid "exclude"
msgstr "排除"
#: widgets.py:58
msgid "All"
msgstr "全部"
#: widgets.py:162
msgid "Unknown"
msgstr "未知"
#: widgets.py:162
msgid "Yes"
msgstr "是"
#: widgets.py:162
msgid "No"
msgstr "否"
#~ msgid "This is an exclusion filter"
#~ msgstr "未启用该过滤器"

@ -0,0 +1,4 @@
# flake8: noqa
from .backends import DjangoFilterBackend
from .filters import *
from .filterset import FilterSet

@ -0,0 +1,165 @@
import warnings
from django.template import loader
from .. import compat, utils
from . import filters, filterset
class DjangoFilterBackend:
filterset_base = filterset.FilterSet
raise_exception = True
@property
def template(self):
if compat.is_crispy():
return "django_filters/rest_framework/crispy_form.html"
return "django_filters/rest_framework/form.html"
def get_filterset(self, request, queryset, view):
filterset_class = self.get_filterset_class(view, queryset)
if filterset_class is None:
return None
kwargs = self.get_filterset_kwargs(request, queryset, view)
return filterset_class(**kwargs)
def get_filterset_class(self, view, queryset=None):
"""
Return the `FilterSet` class used to filter the queryset.
"""
filterset_class = getattr(view, "filterset_class", None)
filterset_fields = getattr(view, "filterset_fields", None)
if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
assert issubclass(
queryset.model, filterset_model
), "FilterSet model %s does not match queryset model %s" % (
filterset_model,
queryset.model,
)
return filterset_class
if filterset_fields and queryset is not None:
MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
model = queryset.model
fields = filterset_fields
return AutoFilterSet
return None
def get_filterset_kwargs(self, request, queryset, view):
return {
"data": request.query_params,
"queryset": queryset,
"request": request,
}
def filter_queryset(self, request, queryset, view):
filterset = self.get_filterset(request, queryset, view)
if filterset is None:
return queryset
if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors)
return filterset.qs
def to_html(self, request, queryset, view):
filterset = self.get_filterset(request, queryset, view)
if filterset is None:
return None
template = loader.get_template(self.template)
context = {"filter": filterset}
return template.render(context, request)
def get_coreschema_field(self, field):
if isinstance(field, filters.NumberFilter):
field_cls = compat.coreschema.Number
else:
field_cls = compat.coreschema.String
return field_cls(description=str(field.extra.get("help_text", "")))
def get_schema_fields(self, view):
# This is not compatible with widgets where the query param differs from the
# filter's attribute name. Notably, this includes `MultiWidget`, where query
# params will be of the format `<name>_0`, `<name>_1`, etc...
from django_filters import RemovedInDjangoFilter25Warning
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
assert (
compat.coreapi is not None
), "coreapi must be installed to use `get_schema_fields()`"
assert (
compat.coreschema is not None
), "coreschema must be installed to use `get_schema_fields()`"
try:
queryset = view.get_queryset()
except Exception:
queryset = None
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)
filterset_class = self.get_filterset_class(view, queryset)
return (
[]
if not filterset_class
else [
compat.coreapi.Field(
name=field_name,
required=field.extra["required"],
location="query",
schema=self.get_coreschema_field(field),
)
for field_name, field in filterset_class.base_filters.items()
]
)
def get_schema_operation_parameters(self, view):
from django_filters import RemovedInDjangoFilter25Warning
warnings.warn(
"Built-in schema generation is deprecated. Use drf-spectacular.",
category=RemovedInDjangoFilter25Warning,
)
try:
queryset = view.get_queryset()
except Exception:
queryset = None
warnings.warn(
"{} is not compatible with schema generation".format(view.__class__)
)
filterset_class = self.get_filterset_class(view, queryset)
if not filterset_class:
return []
parameters = []
for field_name, field in filterset_class.base_filters.items():
parameter = {
"name": field_name,
"required": field.extra["required"],
"in": "query",
"description": field.label if field.label is not None else field_name,
"schema": {
"type": "string",
},
}
if field.extra and "choices" in field.extra:
parameter["schema"]["enum"] = [c[0] for c in field.extra["choices"]]
parameters.append(parameter)
return parameters

@ -0,0 +1,13 @@
from django_filters import filters
from ..filters import * # noqa
from ..widgets import BooleanWidget
__all__ = filters.__all__
class BooleanFilter(filters.BooleanFilter):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", BooleanWidget)
super().__init__(*args, **kwargs)

@ -0,0 +1,41 @@
from copy import deepcopy
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_filters import filterset
from .. import compat
from .filters import BooleanFilter, IsoDateTimeFilter
FILTER_FOR_DBFIELD_DEFAULTS = deepcopy(filterset.FILTER_FOR_DBFIELD_DEFAULTS)
FILTER_FOR_DBFIELD_DEFAULTS.update(
{
models.DateTimeField: {"filter_class": IsoDateTimeFilter},
models.BooleanField: {"filter_class": BooleanFilter},
models.NullBooleanField: {"filter_class": BooleanFilter},
}
)
class FilterSet(filterset.FilterSet):
FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS
@property
def form(self):
form = super().form
if compat.is_crispy():
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
layout_components = list(form.fields.keys()) + [
Submit("", _("Submit"), css_class="btn-default"),
]
helper = FormHelper()
helper.form_method = "GET"
helper.layout = Layout(*layout_components)
form.helper = helper
return form

@ -0,0 +1,5 @@
{% load crispy_forms_tags %}
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
{% crispy filter.form %}

@ -0,0 +1,6 @@
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
<form class="form" action="" method="get">
{{ filter.form.as_p }}
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
</form>

@ -0,0 +1 @@
{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}

@ -0,0 +1,350 @@
import datetime
import warnings
from collections import OrderedDict
import django
from django.conf import settings
from django.core.exceptions import FieldDoesNotExist, FieldError
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Expression
from django.db.models.fields.related import ForeignObjectRel, RelatedField
from django.utils import timezone
from django.utils.encoding import force_str
from django.utils.text import capfirst
from django.utils.translation import gettext as _
from .exceptions import FieldLookupError
def deprecate(msg, level_modifier=0):
warnings.warn(msg, MigrationNotice, stacklevel=3 + level_modifier)
class MigrationNotice(DeprecationWarning):
url = "https://django-filter.readthedocs.io/en/main/guide/migration.html"
def __init__(self, message):
super().__init__("%s See: %s" % (message, self.url))
class RenameAttributesBase(type):
"""
Handles the deprecation paths when renaming an attribute.
It does the following:
- Defines accessors that redirect to the renamed attributes.
- Complain whenever an old attribute is accessed.
This is conceptually based on `django.utils.deprecation.RenameMethodsBase`.
"""
renamed_attributes = ()
def __new__(metacls, name, bases, attrs):
# remove old attributes before creating class
old_names = [r[0] for r in metacls.renamed_attributes]
old_names = [name for name in old_names if name in attrs]
old_attrs = {name: attrs.pop(name) for name in old_names}
# get a handle to any accessors defined on the class
cls_getattr = attrs.pop("__getattr__", None)
cls_setattr = attrs.pop("__setattr__", None)
new_class = super().__new__(metacls, name, bases, attrs)
def __getattr__(self, name):
name = type(self).get_name(name)
if cls_getattr is not None:
return cls_getattr(self, name)
elif hasattr(super(new_class, self), "__getattr__"):
return super(new_class, self).__getattr__(name)
return self.__getattribute__(name)
def __setattr__(self, name, value):
name = type(self).get_name(name)
if cls_setattr is not None:
return cls_setattr(self, name, value)
return super(new_class, self).__setattr__(name, value)
new_class.__getattr__ = __getattr__
new_class.__setattr__ = __setattr__
# set renamed attributes
for name, value in old_attrs.items():
setattr(new_class, name, value)
return new_class
def get_name(metacls, name):
"""
Get the real attribute name. If the attribute has been renamed,
the new name will be returned and a deprecation warning issued.
"""
for renamed_attribute in metacls.renamed_attributes:
old_name, new_name, deprecation_warning = renamed_attribute
if old_name == name:
warnings.warn(
"`%s.%s` attribute should be renamed `%s`."
% (metacls.__name__, old_name, new_name),
deprecation_warning,
3,
)
return new_name
return name
def __getattr__(metacls, name):
return super().__getattribute__(metacls.get_name(name))
def __setattr__(metacls, name, value):
return super().__setattr__(metacls.get_name(name), value)
def try_dbfield(fn, field_class):
"""
Try ``fn`` with the DB ``field_class`` by walking its
MRO until a result is found.
ex::
_try_dbfield(field_dict.get, models.CharField)
"""
# walk the mro, as field_class could be a derived model field.
for cls in field_class.mro():
# skip if cls is models.Field
if cls is models.Field:
continue
data = fn(cls)
if data:
return data
def get_all_model_fields(model):
opts = model._meta
return [
f.name
for f in sorted(opts.fields + opts.many_to_many)
if not isinstance(f, models.AutoField)
and not (getattr(f.remote_field, "parent_link", False))
]
def get_model_field(model, field_name):
"""
Get a ``model`` field, traversing relationships
in the ``field_name``.
ex::
f = get_model_field(Book, 'author__first_name')
"""
fields = get_field_parts(model, field_name)
return fields[-1] if fields else None
def get_field_parts(model, field_name):
"""
Get the field parts that represent the traversable relationships from the
base ``model`` to the final field, described by ``field_name``.
ex::
>>> parts = get_field_parts(Book, 'author__first_name')
>>> [p.verbose_name for p in parts]
['author', 'first name']
"""
parts = field_name.split(LOOKUP_SEP)
opts = model._meta
fields = []
# walk relationships
for name in parts:
try:
field = opts.get_field(name)
except FieldDoesNotExist:
return None
fields.append(field)
try:
if isinstance(field, RelatedField):
opts = field.remote_field.model._meta
elif isinstance(field, ForeignObjectRel):
opts = field.related_model._meta
except AttributeError:
# Lazy relationships are not resolved until registry is populated.
raise RuntimeError(
"Unable to resolve relationship `%s` for `%s`. Django is most "
"likely not initialized, and its apps registry not populated. "
"Ensure Django has finished setup before loading `FilterSet`s."
% (field_name, model._meta.label)
)
return fields
def resolve_field(model_field, lookup_expr):
"""
Resolves a ``lookup_expr`` into its final output field, given
the initial ``model_field``. The lookup expression should only contain
transforms and lookups, not intermediary model field parts.
Note:
This method is based on django.db.models.sql.query.Query.build_lookup
For more info on the lookup API:
https://docs.djangoproject.com/en/stable/ref/models/lookups/
"""
query = model_field.model._default_manager.all().query
lhs = Expression(model_field)
lookups = lookup_expr.split(LOOKUP_SEP)
assert len(lookups) > 0
try:
while lookups:
name = lookups[0]
args = (lhs, name)
# If there is just one part left, try first get_lookup() so
# that if the lhs supports both transform and lookup for the
# name, then lookup will be picked.
if len(lookups) == 1:
final_lookup = lhs.get_lookup(name)
if not final_lookup:
# We didn't find a lookup. We are going to interpret
# the name as transform, and do an Exact lookup against
# it.
lhs = query.try_transform(*args)
final_lookup = lhs.get_lookup("exact")
return lhs.output_field, final_lookup.lookup_name
lhs = query.try_transform(*args)
lookups = lookups[1:]
except FieldError as e:
raise FieldLookupError(model_field, lookup_expr) from e
def handle_timezone(value, is_dst=None):
if settings.USE_TZ and timezone.is_naive(value):
# On pre-5.x versions, the default is to use zoneinfo, but pytz
# is still available under USE_DEPRECATED_PYTZ, and is_dst is
# meaningful there. Under those versions we should only use is_dst
# if USE_DEPRECATED_PYTZ is present and True; otherwise, we will cause
# deprecation warnings, and we should not. See #1580.
#
# This can be removed once 4.2 is no longer supported upstream.
if django.VERSION < (5, 0) and settings.USE_DEPRECATED_PYTZ:
return timezone.make_aware(value, timezone.get_current_timezone(), is_dst)
return timezone.make_aware(value, timezone.get_current_timezone())
elif not settings.USE_TZ and timezone.is_aware(value):
return timezone.make_naive(value, datetime.timezone.utc)
return value
def verbose_field_name(model, field_name):
"""
Get the verbose name for a given ``field_name``. The ``field_name``
will be traversed across relationships. Returns '[invalid name]' for
any field name that cannot be traversed.
ex::
>>> verbose_field_name(Article, 'author__name')
'author name'
"""
if field_name is None:
return "[invalid name]"
parts = get_field_parts(model, field_name)
if not parts:
return "[invalid name]"
names = []
for part in parts:
if isinstance(part, ForeignObjectRel):
if part.related_name:
names.append(part.related_name.replace("_", " "))
else:
return "[invalid name]"
else:
names.append(force_str(part.verbose_name))
return " ".join(names)
def verbose_lookup_expr(lookup_expr):
"""
Get a verbose, more humanized expression for a given ``lookup_expr``.
Each part in the expression is looked up in the ``FILTERS_VERBOSE_LOOKUPS``
dictionary. Missing keys will simply default to itself.
ex::
>>> verbose_lookup_expr('year__lt')
'year is less than'
# with `FILTERS_VERBOSE_LOOKUPS = {}`
>>> verbose_lookup_expr('year__lt')
'year lt'
"""
from .conf import settings as app_settings
VERBOSE_LOOKUPS = app_settings.VERBOSE_LOOKUPS or {}
lookups = [
force_str(VERBOSE_LOOKUPS.get(lookup, _(lookup)))
for lookup in lookup_expr.split(LOOKUP_SEP)
]
return " ".join(lookups)
def label_for_filter(model, field_name, lookup_expr, exclude=False):
"""
Create a generic label suitable for a filter.
ex::
>>> label_for_filter(Article, 'author__name', 'in')
'auther name is in'
"""
name = verbose_field_name(model, field_name)
verbose_expression = [_("exclude"), name] if exclude else [name]
# iterable lookups indicate a LookupTypeField, which should not be verbose
if isinstance(lookup_expr, str):
verbose_expression += [verbose_lookup_expr(lookup_expr)]
verbose_expression = [force_str(part) for part in verbose_expression if part]
verbose_expression = capfirst(" ".join(verbose_expression))
return verbose_expression
def translate_validation(error_dict):
"""
Translate a Django ErrorDict into its DRF ValidationError.
"""
# it's necessary to lazily import the exception, as it can otherwise create
# an import loop when importing django_filters inside the project settings.
from rest_framework.exceptions import ErrorDetail, ValidationError
exc = OrderedDict(
(
key,
[
ErrorDetail(e.message % (e.params or ()), code=e.code)
for e in error_list
],
)
for key, error_list in error_dict.as_data().items()
)
return ValidationError(exc)

@ -0,0 +1,129 @@
from django.core.exceptions import ImproperlyConfigured
from django.views.generic import View
from django.views.generic.list import (
MultipleObjectMixin,
MultipleObjectTemplateResponseMixin,
)
from .constants import ALL_FIELDS
from .filterset import filterset_factory
class FilterMixin:
"""
A mixin that provides a way to show and handle a FilterSet in a request.
"""
filterset_class = None
filterset_fields = ALL_FIELDS
strict = True
def get_filterset_class(self):
"""
Returns the filterset class to use in this view
"""
if self.filterset_class:
return self.filterset_class
elif self.model:
return filterset_factory(model=self.model, fields=self.filterset_fields)
else:
msg = "'%s' must define 'filterset_class' or 'model'"
raise ImproperlyConfigured(msg % self.__class__.__name__)
def get_filterset(self, filterset_class):
"""
Returns an instance of the filterset to be used in this view.
"""
kwargs = self.get_filterset_kwargs(filterset_class)
return filterset_class(**kwargs)
def get_filterset_kwargs(self, filterset_class):
"""
Returns the keyword arguments for instantiating the filterset.
"""
kwargs = {
"data": self.request.GET or None,
"request": self.request,
}
try:
kwargs.update(
{
"queryset": self.get_queryset(),
}
)
except ImproperlyConfigured:
# ignore the error here if the filterset has a model defined
# to acquire a queryset from
if filterset_class._meta.model is None:
msg = (
"'%s' does not define a 'model' and the view '%s' does "
"not return a valid queryset from 'get_queryset'. You "
"must fix one of them."
)
args = (filterset_class.__name__, self.__class__.__name__)
raise ImproperlyConfigured(msg % args)
return kwargs
def get_strict(self):
return self.strict
class BaseFilterView(FilterMixin, MultipleObjectMixin, View):
def get(self, request, *args, **kwargs):
filterset_class = self.get_filterset_class()
self.filterset = self.get_filterset(filterset_class)
if (
not self.filterset.is_bound
or self.filterset.is_valid()
or not self.get_strict()
):
self.object_list = self.filterset.qs
else:
self.object_list = self.filterset.queryset.none()
context = self.get_context_data(
filter=self.filterset, object_list=self.object_list
)
return self.render_to_response(context)
class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView):
"""
Render some list of objects with filter, set by `self.model` or
`self.queryset`.
`self.queryset` can actually be any iterable of items, not just a queryset.
"""
template_name_suffix = "_filter"
def object_filter(
request,
model=None,
queryset=None,
template_name=None,
extra_context=None,
context_processors=None,
filter_class=None,
):
class ECFilterView(FilterView):
"""Handle the extra_context from the functional object_filter view"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
extra_context = self.kwargs.get("extra_context") or {}
for k, v in extra_context.items():
if callable(v):
v = v()
context[k] = v
return context
kwargs = dict(
model=model,
queryset=queryset,
template_name=template_name,
filterset_class=filter_class,
)
view = ECFilterView.as_view(**kwargs)
return view(request, extra_context=extra_context)

@ -0,0 +1,270 @@
from collections.abc import Iterable
from copy import deepcopy
from itertools import chain
from re import search, sub
from django import forms
from django.db.models.fields import BLANK_CHOICE_DASH
from django.forms.utils import flatatt
from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_str
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
class LinkWidget(forms.Widget):
def __init__(self, attrs=None, choices=()):
super().__init__(attrs)
self.choices = choices
def value_from_datadict(self, data, files, name):
value = super().value_from_datadict(data, files, name)
self.data = data
return value
def render(self, name, value, attrs=None, choices=(), renderer=None):
if not hasattr(self, "data"):
self.data = {}
if value is None:
value = ""
final_attrs = self.build_attrs(self.attrs, extra_attrs=attrs)
output = ["<ul%s>" % flatatt(final_attrs)]
options = self.render_options(choices, [value], name)
if options:
output.append(options)
output.append("</ul>")
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)
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
for option in option_label:
output.append(self.render_option(name, selected_choices, *option))
else:
output.append(
self.render_option(
name, selected_choices, option_value, option_label
)
)
return "\n".join(output)
def render_option(self, name, selected_choices, option_value, option_label):
option_value = force_str(option_value)
if option_label == BLANK_CHOICE_DASH[0][1]:
option_label = _("All")
data = self.data.copy()
data[name] = option_value
selected = data == self.data or option_value in selected_choices
try:
url = data.urlencode()
except AttributeError:
url = urlencode(data)
return self.option_string() % {
"attrs": selected and ' class="selected"' or "",
"query_string": url,
"label": force_str(option_label),
}
def option_string(self):
return '<li><a%(attrs)s href="?%(query_string)s">%(label)s</a></li>'
class SuffixedMultiWidget(forms.MultiWidget):
"""
A MultiWidget that allows users to provide custom suffixes instead of indexes.
- Suffixes must be unique.
- There must be the same number of suffixes as fields.
"""
suffixes = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
assert len(self.widgets) == len(self.suffixes)
assert len(self.suffixes) == len(set(self.suffixes))
def suffixed(self, name, suffix):
return "_".join([name, suffix]) if suffix else name
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
for subcontext, suffix in zip(context["widget"]["subwidgets"], self.suffixes):
subcontext["name"] = self.suffixed(name, suffix)
return context
def value_from_datadict(self, data, files, name):
return [
widget.value_from_datadict(data, files, self.suffixed(name, suffix))
for widget, suffix in zip(self.widgets, self.suffixes)
]
def value_omitted_from_data(self, data, files, name):
return all(
widget.value_omitted_from_data(data, files, self.suffixed(name, suffix))
for widget, suffix in zip(self.widgets, self.suffixes)
)
def replace_name(self, output, index):
result = search(r'name="(?P<name>.*)_%d"' % index, output)
name = result.group("name")
name = self.suffixed(name, self.suffixes[index])
name = 'name="%s"' % name
return sub(r'name=".*_%d"' % index, name, output)
def decompress(self, value):
if value is None:
return [None, None]
return value
class RangeWidget(SuffixedMultiWidget):
template_name = "django_filters/widgets/multiwidget.html"
suffixes = ["min", "max"]
def __init__(self, attrs=None):
widgets = (forms.TextInput, forms.TextInput)
super().__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.start, value.stop]
return [None, None]
class DateRangeWidget(RangeWidget):
suffixes = ["after", "before"]
class LookupChoiceWidget(SuffixedMultiWidget):
suffixes = [None, "lookup"]
def decompress(self, value):
if value is None:
return [None, None]
return value
class BooleanWidget(forms.Select):
"""Convert true/false values into the internal Python True/False.
This can be used for AJAX queries that pass true/false from JavaScript's
internal types through.
"""
def __init__(self, attrs=None):
choices = (("", _("Unknown")), ("true", _("Yes")), ("false", _("No")))
super().__init__(attrs, choices)
def render(self, name, value, attrs=None, renderer=None):
try:
value = {True: "true", False: "false", "1": "true", "0": "false"}[value]
except KeyError:
value = ""
return super().render(name, value, attrs, renderer=renderer)
def value_from_datadict(self, data, files, name):
value = data.get(name, None)
if isinstance(value, str):
value = value.lower()
return {
"1": True,
"0": False,
"true": True,
"false": False,
True: True,
False: False,
}.get(value, None)
class BaseCSVWidget(forms.Widget):
# Surrogate widget for rendering multiple values
surrogate = forms.TextInput
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if isinstance(self.surrogate, type):
self.surrogate = self.surrogate()
else:
self.surrogate = deepcopy(self.surrogate)
def _isiterable(self, value):
return isinstance(value, Iterable) and not isinstance(value, str)
def value_from_datadict(self, data, files, name):
value = super().value_from_datadict(data, files, name)
if value is not None:
if value == "": # empty value should parse as an empty list
return []
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 None
def render(self, name, value, attrs=None, renderer=None):
if not self._isiterable(value):
value = [value]
if len(value) <= 1:
# delegate to main widget (Select, etc...) if not multiple values
value = value[0] if value else ""
return super().render(name, value, attrs, renderer=renderer)
# if we have multiple values, we need to force render as a text input
# (otherwise, the additional values are lost)
value = [force_str(self.surrogate.format_value(v)) for v in value]
value = ",".join(list(value))
return self.surrogate.render(name, value, attrs, renderer=renderer)
class CSVWidget(BaseCSVWidget, forms.TextInput):
def __init__(self, *args, attrs=None, **kwargs):
super().__init__(*args, attrs, **kwargs)
if attrs is not None:
self.surrogate.attrs.update(attrs)
class QueryArrayWidget(BaseCSVWidget, forms.TextInput):
"""
Enables request query array notation that might be consumed by MultipleChoiceFilter
1. Values can be provided as csv string: ?foo=bar,baz
2. Values can be provided as query array: ?foo[]=bar&foo[]=baz
3. Values can be provided as query array: ?foo=bar&foo=baz
Note: Duplicate and empty values are skipped from results
"""
def value_from_datadict(self, data, files, name):
if not isinstance(data, MultiValueDict):
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 = MultiValueDict(data)
values_list = data.getlist(name, data.getlist("%s[]" % name)) or []
# apparently its an array, so no need to process it's values as csv
# ?foo=1&foo=2 -> data.getlist(foo) -> foo = [1, 2]
# ?foo[]=1&foo[]=2 -> data.getlist(foo[]) -> foo = [1, 2]
if len(values_list) > 0:
ret = [x for x in values_list if x]
else:
ret = []
return list(set(ret))
Loading…
Cancel
Save

Powered by TurnKey Linux.