diff --git a/stubs/django-filter/@tests/django_settings.py b/stubs/django-filter/@tests/django_settings.py new file mode 100644 index 000000000000..f44d14e04e15 --- /dev/null +++ b/stubs/django-filter/@tests/django_settings.py @@ -0,0 +1,12 @@ +SECRET_KEY = "1" + +INSTALLED_APPS = ( + "django.contrib.contenttypes", + "django.contrib.sites", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.admin.apps.SimpleAdminConfig", + "django.contrib.staticfiles", + "django.contrib.auth", + "django_filters", +) diff --git a/stubs/django-filter/@tests/stubtest_allowlist.txt b/stubs/django-filter/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000000..e38c1aad8afc --- /dev/null +++ b/stubs/django-filter/@tests/stubtest_allowlist.txt @@ -0,0 +1,12 @@ +# Iterator class attributes: Runtime behavior differs due to Django 5.0 compatibility logic in choice setters +django_filters.fields.ChoiceField.iterator +django_filters.fields.ModelChoiceField.iterator +django_filters.fields.ModelMultipleChoiceField.iterator +django_filters.fields.MultipleChoiceField.iterator + +# Lookup NamedTuple: Parameter name mismatch between inferred stub and runtime +django_filters.fields.Lookup.__new__ +django_filters.fields.Lookup.__doc__ + +# ChoiceIteratorMixin.choices: Cannot define choices property due to incompatibility with base class ChoiceField +django_filters.fields.ChoiceIteratorMixin.choices diff --git a/stubs/django-filter/METADATA.toml b/stubs/django-filter/METADATA.toml new file mode 100644 index 000000000000..5fdbcdfb6b14 --- /dev/null +++ b/stubs/django-filter/METADATA.toml @@ -0,0 +1,7 @@ +version = "25.1.*" +upstream_repository = "https://github.com/carltongibson/django-filter/" +requires = ["django-stubs"] + +[tool.stubtest] +mypy_plugins = ["mypy_django_plugin.main"] +mypy_plugins_config = {"django-stubs" = {"django_settings_module" = "@tests.django_settings"}} diff --git a/stubs/django-filter/django_filters/__init__.pyi b/stubs/django-filter/django_filters/__init__.pyi new file mode 100644 index 000000000000..1ed733b2476a --- /dev/null +++ b/stubs/django-filter/django_filters/__init__.pyi @@ -0,0 +1,10 @@ +from typing import Final + +from .filters import * +from .filterset import FilterSet as FilterSet, UnknownFieldBehavior as UnknownFieldBehavior + +__version__: Final[str] + +def parse_version(version: str) -> tuple[str | int]: ... + +VERSION: tuple[str | int, ...] diff --git a/stubs/django-filter/django_filters/compat.pyi b/stubs/django-filter/django_filters/compat.pyi new file mode 100644 index 000000000000..87785dfab536 --- /dev/null +++ b/stubs/django-filter/django_filters/compat.pyi @@ -0,0 +1 @@ +def is_crispy() -> bool: ... diff --git a/stubs/django-filter/django_filters/conf.pyi b/stubs/django-filter/django_filters/conf.pyi new file mode 100644 index 000000000000..b519d1da8b5d --- /dev/null +++ b/stubs/django-filter/django_filters/conf.pyi @@ -0,0 +1,17 @@ +from _typeshed import Unused +from typing import Any + +DEFAULTS: dict[str, Any] # Configuration values can be strings, booleans, callables, etc. +DEPRECATED_SETTINGS: list[str] + +def is_callable(value: Any) -> bool: ... # Accepts any value to test if it's callable + +class Settings: + # Setting values can be of any type, so getter and setter methods return/accept Any + def __getattr__(self, name: str) -> Any: ... # Returns setting values of various types + def get_setting(self, setting: str) -> Any: ... # Setting values vary by configuration option + def change_setting( + self, setting: str, value: Any, enter: bool, **kwargs: Unused + ) -> None: ... # Accepts any setting value type + +settings: Settings diff --git a/stubs/django-filter/django_filters/constants.pyi b/stubs/django-filter/django_filters/constants.pyi new file mode 100644 index 000000000000..1a88634ea477 --- /dev/null +++ b/stubs/django-filter/django_filters/constants.pyi @@ -0,0 +1,6 @@ +from typing import Any, Final + +# String constant used to indicate all model fields should be included +ALL_FIELDS: Final[str] = "__all__" +# Collection of values considered empty by Django filters - tuple type allows various empty containers +EMPTY_VALUES: Final[Any] = ... diff --git a/stubs/django-filter/django_filters/exceptions.pyi b/stubs/django-filter/django_filters/exceptions.pyi new file mode 100644 index 000000000000..725c08b5cada --- /dev/null +++ b/stubs/django-filter/django_filters/exceptions.pyi @@ -0,0 +1,8 @@ +from typing import Any + +from django.core.exceptions import FieldError +from django.db import models + +class FieldLookupError(FieldError): + # Field type params are runtime-determined + def __init__(self, model_field: models.Field[Any, Any], lookup_expr: str) -> None: ... diff --git a/stubs/django-filter/django_filters/fields.pyi b/stubs/django-filter/django_filters/fields.pyi new file mode 100644 index 000000000000..1d9678da586e --- /dev/null +++ b/stubs/django-filter/django_filters/fields.pyi @@ -0,0 +1,100 @@ +from collections.abc import Sequence +from typing import Any, NamedTuple +from typing_extensions import TypeAlias + +from django import forms + +DJANGO_50: bool + +# Ref: django-stubs/forms/fields.pyi +# Problem: attribute `widget` is always of type `Widget` after field instantiation. +# However, on class level it can be set to `Type[Widget]` too. +# If we annotate it as `Union[Widget, Type[Widget]]`, every code that uses field +# instances will not typecheck. +# If we annotate it as `Widget`, any widget subclasses that do e.g. +# `widget = Select` will not typecheck. +# `Any` gives too much freedom, but does not create false positives. +_ClassLevelWidget: TypeAlias = Any + +class RangeField(forms.MultiValueField): + widget: _ClassLevelWidget = ... + def __init__( + self, fields: tuple[forms.Field, forms.Field] | None = None, *args: Any, **kwargs: Any + ) -> None: ... # Args/kwargs can be any field params, passes to parent + def compress(self, data_list: list[Any] | None) -> slice | None: ... # Data list elements can be any field value type + +class DateRangeField(RangeField): + widget: _ClassLevelWidget = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent + def compress(self, data_list: list[Any] | None) -> slice | None: ... # Date values in list can be any date type + +class DateTimeRangeField(RangeField): + widget: _ClassLevelWidget = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent + +class IsoDateTimeRangeField(RangeField): + widget: _ClassLevelWidget = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent + +class TimeRangeField(RangeField): + widget: _ClassLevelWidget = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for parent + +class Lookup(NamedTuple): + value: Any # Lookup values can be any filterable type + lookup_expr: str + +class LookupChoiceField(forms.MultiValueField): + def __init__( + self, field: forms.Field, lookup_choices: Sequence[tuple[str, str]], *args: Any, **kwargs: Any + ) -> None: ... # Args/kwargs can be any field params, uses kwargs for empty_label + def compress(self, data_list: list[Any] | None) -> Lookup | None: ... # Data list can contain any lookup components + +class IsoDateTimeField(forms.DateTimeField): + ISO_8601: str + input_formats: list[str] + def strptime(self, value: str, format: str) -> Any: ... # Returns datetime objects or parsing results + +class BaseCSVField(forms.Field): + base_widget_class: _ClassLevelWidget = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for widget config + def clean(self, value: Any) -> Any: ... # Cleaned values can be any valid field type + +class BaseRangeField(BaseCSVField): + widget: _ClassLevelWidget = ... + def clean(self, value: Any) -> Any: ... # Input and output values can be any range type + +class ChoiceIterator: + field: ChoiceField + choices: Sequence[tuple[Any, str]] # Choice values can be any type (int, str, Model, etc.) + def __init__( + self, field: ChoiceField, choices: Sequence[tuple[Any, str]] + ) -> None: ... # Choice values can be any selectable type + def __iter__(self) -> Any: ... # Iterator yields choice tuples with any value types + def __len__(self) -> int: ... + +class ModelChoiceIterator(forms.models.ModelChoiceIterator): + def __iter__(self) -> Any: ... # Iterator yields choice tuples with any value types + def __len__(self) -> int: ... + +class ChoiceIteratorMixin: + null_label: str | None + null_value: Any # Null choice values can be any type (None, empty string, etc.) + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for null config + +class ChoiceField(ChoiceIteratorMixin, forms.ChoiceField): + iterator = ChoiceIterator + empty_label: str | None + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params for label config + +class MultipleChoiceField(ChoiceIteratorMixin, forms.MultipleChoiceField): + iterator = ChoiceIterator + empty_label: str | None + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any field params, sets empty_label + +class ModelChoiceField(ChoiceIteratorMixin, forms.ModelChoiceField[Any]): + iterator = ModelChoiceIterator + def to_python(self, value: Any) -> Any: ... # Converts any input to Python model objects or values + +class ModelMultipleChoiceField(ChoiceIteratorMixin, forms.ModelMultipleChoiceField[Any]): + iterator = ModelChoiceIterator diff --git a/stubs/django-filter/django_filters/filters.pyi b/stubs/django-filter/django_filters/filters.pyi new file mode 100644 index 000000000000..714ddc89f720 --- /dev/null +++ b/stubs/django-filter/django_filters/filters.pyi @@ -0,0 +1,234 @@ +from collections.abc import Callable +from typing import Any + +from django import forms +from django.db.models import Q, QuerySet +from django.forms import Field + +from .fields import ( + BaseCSVField, + BaseRangeField, + DateRangeField, + DateTimeRangeField, + IsoDateTimeField, + IsoDateTimeRangeField, + LookupChoiceField, + ModelChoiceField, + ModelMultipleChoiceField, + RangeField, + TimeRangeField, +) + +__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: int + field_class: type[Any] # Subclasses specify more specific field types + field_name: str | None + lookup_expr: str + distinct: bool + exclude: bool + extra: dict[str, Any] # Field kwargs can include various types of parameters + def __init__( + self, + field_name: str | None = None, + lookup_expr: str | None = None, + *, + label: str | None = None, + method: Callable[..., Any] | str | None = None, # Filter methods can return various types + distinct: bool = False, + exclude: bool = False, + **kwargs: Any, # Field kwargs stored as extra (required, help_text, etc.) + ) -> None: ... + def get_method(self, qs: QuerySet[Any]) -> Callable[..., QuerySet[Any]]: ... # Returns QuerySet filtering methods + method: Callable[..., Any] | str | None # Custom filter methods return various types + label: str | None # Filter label for display + @property + def field(self) -> Field: ... + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... # Filter value can be any user input type + +class CharFilter(Filter): + field_class: type[forms.CharField] + +class BooleanFilter(Filter): + field_class: type[forms.NullBooleanField] + +class ChoiceFilter(Filter): + field_class: type[Any] # Base class for choice-based filters + null_value: Any # Null value can be any type (None, empty string, etc.) + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for null_value config + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + +class TypedChoiceFilter(Filter): + field_class: type[forms.TypedChoiceField] + +class UUIDFilter(Filter): + field_class: type[forms.UUIDField] + +class MultipleChoiceFilter(Filter): + field_class: type[Any] # Base class for multiple choice filters + always_filter: bool + conjoined: bool + null_value: Any # Multiple choice null values vary by implementation + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for distinct, conjoined, null_value config + def is_noop(self, qs: QuerySet[Any], value: Any) -> bool: ... # Value can be any filter input + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + def get_filter_predicate(self, v: Any) -> Q: ... # Predicate value can be any filter input type + +class TypedMultipleChoiceFilter(MultipleChoiceFilter): + field_class: type[forms.TypedMultipleChoiceField] # More specific than parent MultipleChoiceField + +class DateFilter(Filter): + field_class: type[forms.DateField] + +class DateTimeFilter(Filter): + field_class: type[forms.DateTimeField] + +class IsoDateTimeFilter(DateTimeFilter): + field_class: type[IsoDateTimeField] + +class TimeFilter(Filter): + field_class: type[forms.TimeField] + +class DurationFilter(Filter): + field_class: type[forms.DurationField] + +class QuerySetRequestMixin: + queryset: QuerySet[Any] | None + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for queryset config + def get_request(self) -> Any: ... # Request can be HttpRequest or other request types + def get_queryset(self, request: Any) -> QuerySet[Any]: ... # Request parameter accepts various request types + @property + def field(self) -> Field: ... + +class ModelChoiceFilter(QuerySetRequestMixin, ChoiceFilter): + field_class: type[ModelChoiceField] # More specific than parent ChoiceField + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for empty_label config + +class ModelMultipleChoiceFilter(QuerySetRequestMixin, MultipleChoiceFilter): + field_class: type[ModelMultipleChoiceField] # More specific than parent MultipleChoiceField + +class NumberFilter(Filter): + field_class: type[forms.DecimalField] + def get_max_validator(self) -> Any: ... # Validator can be various Django validator types + @property + def field(self) -> Field: ... + +class NumericRangeFilter(Filter): + field_class: type[RangeField] + lookup_expr: str + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + +class RangeFilter(Filter): + field_class: type[RangeField] + lookup_expr: str + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + +class DateRangeFilter(ChoiceFilter): + choices: list[tuple[str, str]] | None + filters: dict[str, Filter] | None + def __init__( + self, choices: list[tuple[str, str]] | None = None, filters: dict[str, Filter] | None = None, *args: Any, **kwargs: Any + ) -> None: ... # Uses args/kwargs for choice and filter configuration + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + +class DateFromToRangeFilter(RangeFilter): + field_class: type[DateRangeField] + +class DateTimeFromToRangeFilter(RangeFilter): + field_class: type[DateTimeRangeField] + +class IsoDateTimeFromToRangeFilter(RangeFilter): + field_class: type[IsoDateTimeRangeField] + +class TimeRangeFilter(RangeFilter): + field_class: type[TimeRangeField] + +class AllValuesFilter(ChoiceFilter): + @property + def field(self) -> Field: ... + +class AllValuesMultipleFilter(MultipleChoiceFilter): + @property + def field(self) -> Field: ... + +class BaseCSVFilter(Filter): + base_field_class: type[BaseCSVField] = ... + field_class: type[Any] # Base class for CSV-based filters + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for help_text and widget config + +class BaseInFilter(BaseCSVFilter): + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Sets lookup_expr and passes through + +class BaseRangeFilter(BaseCSVFilter): + base_field_class: type[BaseRangeField] = ... + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Sets lookup_expr and passes through + +class LookupChoiceFilter(Filter): + field_class: type[forms.CharField] + outer_class: type[LookupChoiceField] = ... + empty_label: str | None + lookup_choices: list[tuple[str, str]] | None + def __init__( + self, + field_name: str | None = None, + lookup_choices: list[tuple[str, str]] | None = None, + field_class: type[Field] | None = None, + **kwargs: Any, # Handles empty_label and other field config + ) -> None: ... + @classmethod + def normalize_lookup(cls, lookup: Any) -> tuple[Any, str]: ... + def get_lookup_choices(self) -> list[tuple[str, str]]: ... + @property + def field(self) -> Field: ... + lookup_expr: str + def filter(self, qs: QuerySet[Any], lookup: Any) -> QuerySet[Any]: ... + +class OrderingFilter(BaseCSVFilter, ChoiceFilter): + field_class: type[BaseCSVField] # Inherits CSV field behavior for comma-separated ordering + descending_fmt: str + param_map: dict[str, str] | None + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Uses kwargs for fields and field_labels config + def get_ordering_value(self, param: str) -> str: ... + def filter(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + @classmethod + def normalize_fields(cls, fields: Any) -> list[str]: ... + def build_choices(self, fields: Any, labels: dict[str, str] | None) -> list[tuple[str, str]]: ... + +class FilterMethod: + f: Filter + def __init__(self, filter_instance: Filter) -> None: ... + def __call__(self, qs: QuerySet[Any], value: Any) -> QuerySet[Any]: ... + @property + def method(self) -> Callable[..., Any]: ... diff --git a/stubs/django-filter/django_filters/filterset.pyi b/stubs/django-filter/django_filters/filterset.pyi new file mode 100644 index 000000000000..6a8b173ce965 --- /dev/null +++ b/stubs/django-filter/django_filters/filterset.pyi @@ -0,0 +1,85 @@ +from collections import OrderedDict +from enum import Enum +from typing import Any, ClassVar + +from django.db import models +from django.db.models import Model, QuerySet +from django.forms import Form +from django.http import HttpRequest, QueryDict + +from .filters import Filter + +def remote_queryset(field: models.Field[Any, Any]) -> QuerySet[Any]: ... # Field type params vary by model definition + +class UnknownFieldBehavior(Enum): + RAISE = "raise" + WARN = "warn" + IGNORE = "ignore" + +class FilterSetOptions: + model: type[Model] | None + fields: list[str] | dict[str, list[str]] | str | None + exclude: list[str] | None + filter_overrides: dict[type[models.Field[Any, Any]], dict[str, Any]] # Field override mapping + form: type[Form] + unknown_field_behavior: UnknownFieldBehavior + def __init__(self, options: Any | None = None) -> None: ... # Meta options can be various configuration types + +class FilterSetMetaclass(type): + # Class attrs vary by definition + def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> FilterSetMetaclass: ... + + # Class attrs vary by definition + @classmethod + def get_declared_filters(cls, bases: tuple[type, ...], attrs: dict[str, Any]) -> OrderedDict[str, Filter]: ... + +# Django field types vary widely - Any allows mapping all field types to their filters +FILTER_FOR_DBFIELD_DEFAULTS: dict[type[models.Field[Any, Any]], dict[str, Any]] + +class BaseFilterSet: + FILTER_DEFAULTS: ClassVar[dict[type[models.Field[Any, Any]], dict[str, Any]]] = ... # Field type mapping + is_bound: bool + base_filters: OrderedDict[str, Filter] + declared_filters: OrderedDict[str, Filter] + data: QueryDict | dict[str, Any] | None # Filter input data values vary + queryset: QuerySet[Any] | None # Base queryset for any model type + request: HttpRequest | None + form_prefix: str | None + filters: OrderedDict[str, Filter] + def __init__( + self, + data: QueryDict | dict[str, Any] | None = None, # Filter data values vary + queryset: QuerySet[Any] | None = None, # Base queryset for any model + *, + request: HttpRequest | None = None, + prefix: str | None = None, + ) -> None: ... + def is_valid(self) -> bool: ... + @property + def errors(self) -> dict[str, list[str]]: ... + def filter_queryset(self, queryset: QuerySet[Any]) -> QuerySet[Any]: ... # Works with any model type + @property + def qs(self) -> QuerySet[Any]: ... # Filtered queryset of any model + def get_form_class(self) -> type[Form]: ... + @property + def form(self) -> Form: ... + @classmethod + def get_fields(cls) -> dict[str, models.Field[Any, Any]]: ... # Model fields have varying type params + @classmethod + def get_filter_name(cls, field_name: str, lookup_expr: str) -> str: ... + @classmethod + def get_filters(cls) -> OrderedDict[str, Filter]: ... + @classmethod + def handle_unrecognized_field(cls, field_name: str, message: str) -> None: ... + @classmethod + def filter_for_field( + cls, field: models.Field[Any, Any], field_name: str, lookup_expr: str | None = None + ) -> Filter: ... # Accepts any Django field type + @classmethod + def filter_for_lookup(cls, field: models.Field[Any, Any], lookup_type: str) -> type[Filter]: ... # Field type varies by model + +class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass): ... + +def filterset_factory( + model: type[Model], filterset: FilterSetMetaclass = ..., fields: list[str] | dict[str, list[str]] | str | None = None +) -> type[FilterSet]: ... diff --git a/stubs/django-filter/django_filters/rest_framework/__init__.pyi b/stubs/django-filter/django_filters/rest_framework/__init__.pyi new file mode 100644 index 000000000000..adb475260a84 --- /dev/null +++ b/stubs/django-filter/django_filters/rest_framework/__init__.pyi @@ -0,0 +1,3 @@ +from .backends import DjangoFilterBackend as DjangoFilterBackend +from .filters import * +from .filterset import FilterSet as FilterSet diff --git a/stubs/django-filter/django_filters/rest_framework/backends.pyi b/stubs/django-filter/django_filters/rest_framework/backends.pyi new file mode 100644 index 000000000000..b45372d919af --- /dev/null +++ b/stubs/django-filter/django_filters/rest_framework/backends.pyi @@ -0,0 +1,30 @@ +from typing import Any +from typing_extensions import TypeAlias + +from django.db.models import QuerySet +from django.http import HttpRequest +from django_filters.filterset import FilterSetMetaclass + +from . import filterset + +# APIView placeholder - djangorestframework is optional, so we use Any for compatibility +_APIView: TypeAlias = Any + +class DjangoFilterBackend: + filterset_base: FilterSetMetaclass = ... + raise_exception: bool + @property + def template(self) -> str: ... + + # Works with any model type + def get_filterset(self, request: HttpRequest, queryset: QuerySet[Any], view: _APIView) -> filterset.FilterSet | None: ... + + # Any model queryset + def get_filterset_class(self, view: _APIView, queryset: QuerySet[Any] | None = None) -> type[filterset.FilterSet] | None: ... + + # Kwargs vary by filterset + def get_filterset_kwargs(self, request: HttpRequest, queryset: QuerySet[Any], view: _APIView) -> dict[str, Any]: ... + + # Filters any model type + def filter_queryset(self, request: HttpRequest, queryset: QuerySet[Any], view: _APIView) -> QuerySet[Any]: ... + def to_html(self, request: HttpRequest, queryset: QuerySet[Any], view: _APIView) -> str: ... # Renders form for any model diff --git a/stubs/django-filter/django_filters/rest_framework/filters.pyi b/stubs/django-filter/django_filters/rest_framework/filters.pyi new file mode 100644 index 000000000000..c289dc59509a --- /dev/null +++ b/stubs/django-filter/django_filters/rest_framework/filters.pyi @@ -0,0 +1,71 @@ +from typing import Any + +from ..filters import ( + AllValuesFilter, + AllValuesMultipleFilter, + BaseCSVFilter, + BaseInFilter, + BaseRangeFilter, + BooleanFilter as _BaseBooleanFilter, + CharFilter, + ChoiceFilter, + DateFilter, + DateFromToRangeFilter, + DateRangeFilter, + DateTimeFilter, + DateTimeFromToRangeFilter, + DurationFilter, + Filter, + IsoDateTimeFilter, + IsoDateTimeFromToRangeFilter, + LookupChoiceFilter, + ModelChoiceFilter, + ModelMultipleChoiceFilter, + MultipleChoiceFilter, + NumberFilter, + NumericRangeFilter, + OrderingFilter, + RangeFilter, + TimeFilter, + TimeRangeFilter, + TypedChoiceFilter, + TypedMultipleChoiceFilter, + UUIDFilter, +) + +__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", +] + +# REST framework specific BooleanFilter that uses BooleanWidget by default +class BooleanFilter(_BaseBooleanFilter): + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Accepts any filter initialization params diff --git a/stubs/django-filter/django_filters/rest_framework/filterset.pyi b/stubs/django-filter/django_filters/rest_framework/filterset.pyi new file mode 100644 index 000000000000..cd7eb9ab5813 --- /dev/null +++ b/stubs/django-filter/django_filters/rest_framework/filterset.pyi @@ -0,0 +1,17 @@ +from collections import OrderedDict +from typing import Any, ClassVar + +from django.db import models +from django.forms import Form +from django_filters import filterset +from django_filters.filters import Filter + +# REST framework field mappings support all Django field types +FILTER_FOR_DBFIELD_DEFAULTS: dict[type[models.Field[Any, Any]], dict[str, Any]] + +class FilterSet(filterset.FilterSet): + FILTER_DEFAULTS: ClassVar[dict[type[models.Field[Any, Any]], dict[str, Any]]] = ... # DRF field mappings + base_filters: OrderedDict[str, Filter] + declared_filters: OrderedDict[str, Filter] + @property + def form(self) -> Form: ... diff --git a/stubs/django-filter/django_filters/utils.pyi b/stubs/django-filter/django_filters/utils.pyi new file mode 100644 index 000000000000..247108ee84ec --- /dev/null +++ b/stubs/django-filter/django_filters/utils.pyi @@ -0,0 +1,36 @@ +from collections.abc import Callable +from datetime import datetime +from typing import Any + +from django.db import models +from django.db.models import Model + +def deprecate(msg: str, level_modifier: int = 0) -> None: ... + +class MigrationNotice(DeprecationWarning): + url: str + def __init__(self, message: str) -> None: ... + +class RenameAttributesBase(type): + renamed_attributes: tuple[tuple[str, str, DeprecationWarning], ...] = () + + # Class attrs vary by definition + def __new__(metacls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> RenameAttributesBase: ... + def get_name(metacls, name: str) -> str: ... + def __getattr__(metacls, name: str) -> Any: ... # Attribute values vary by name and class + def __setattr__(metacls, name: str, value: Any) -> None: ... # Attribute values can be any type + +def try_dbfield( + fn: Callable[[models.Field[Any, Any]], Any], field_class: type[models.Field[Any, Any]] +) -> Any: ... # Generic field operation +def get_all_model_fields(model: type[Model]) -> dict[str, models.Field[Any, Any]]: ... # Fields vary by model definition +def get_model_field(model: type[Model], field_name: str) -> models.Field[Any, Any]: ... # Field type unknown at static time +def get_field_parts(model: type[Model], field_name: str) -> list[models.Field[Any, Any]]: ... # Relationship fields vary +def resolve_field( + model_field: models.Field[Any, Any], lookup_expr: str +) -> tuple[models.Field[Any, Any], str]: ... # Generic field resolution +def handle_timezone(value: datetime, is_dst: bool | None = None) -> datetime: ... +def verbose_field_name(model: type[Model], field_name: str) -> str: ... +def verbose_lookup_expr(lookup_expr: str) -> str: ... +def label_for_filter(model: type[Model], field_name: str, lookup_expr: str, exclude: bool = False) -> str: ... +def translate_validation(error_dict: dict[str, list[str]]) -> dict[str, list[str]]: ... diff --git a/stubs/django-filter/django_filters/views.pyi b/stubs/django-filter/django_filters/views.pyi new file mode 100644 index 000000000000..0af9f1f21256 --- /dev/null +++ b/stubs/django-filter/django_filters/views.pyi @@ -0,0 +1,38 @@ +from _typeshed import Unused +from typing import Any + +from django.db.models import Model, QuerySet +from django.http import HttpRequest, HttpResponse +from django.views.generic import View +from django.views.generic.list import MultipleObjectMixin, MultipleObjectTemplateResponseMixin + +from .constants import ALL_FIELDS +from .filterset import FilterSet + +class FilterMixin: + filterset_class: type[FilterSet] | None + filterset_fields = ALL_FIELDS + strict: bool + def get_filterset_class(self) -> type[FilterSet] | None: ... + def get_filterset(self, filterset_class: type[FilterSet]) -> FilterSet: ... + def get_filterset_kwargs(self, filterset_class: type[FilterSet]) -> dict[str, Any]: ... # Filterset init params vary + def get_strict(self) -> bool: ... + +class BaseFilterView(FilterMixin, MultipleObjectMixin[Any], View): # Generic model type + filterset: FilterSet + object_list: QuerySet[Any] # Filtered objects of any model type + + def get(self, request: HttpRequest, *args: Unused, **kwargs: Unused) -> HttpResponse: ... + +class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView): + template_name_suffix: str + +def object_filter( + request: HttpRequest, + model: type[Model] | None = None, + queryset: QuerySet[Any] | None = None, # Base queryset for any model + template_name: str | None = None, + extra_context: dict[str, Any] | None = None, # Template context values vary + context_processors: list[Any] | None = None, # Context processors vary by implementation + filter_class: type[FilterSet] | None = None, +) -> HttpResponse: ... diff --git a/stubs/django-filter/django_filters/widgets.pyi b/stubs/django-filter/django_filters/widgets.pyi new file mode 100644 index 000000000000..ab1c51636d2a --- /dev/null +++ b/stubs/django-filter/django_filters/widgets.pyi @@ -0,0 +1,86 @@ +from collections.abc import Mapping, Sequence +from typing import Any + +from django import forms +from django.http import QueryDict +from django.utils.safestring import SafeString + +class LinkWidget(forms.Widget): + # Choice values can be any type (int, str, Model, etc.) + choices: Sequence[tuple[Any, str]] + # Choice values can be any selectable type + def __init__(self, attrs: dict[str, Any] | None = None, choices: Sequence[tuple[Any, str]] = ()) -> None: ... + data: QueryDict | dict[str, Any] + # Return value depends on widget data type + def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> Any: ... + # Widget value and renderer can be any type, choices parameter combines with class choices + def render( # type: ignore[override] + self, + name: str, + value: Any, + attrs: dict[str, Any] | None = None, + choices: Sequence[tuple[Any, str]] = (), + renderer: Any | None = None, + ) -> SafeString: ... + # Choice values and selections can be any type + def render_options(self, choices: Sequence[tuple[Any, str]], selected_choices: list[Any], name: str) -> str: ... + # Selected choices and option values can be any type + def render_option(self, name: str, selected_choices: list[Any], option_value: Any, option_label: str) -> str: ... + def option_string(self) -> str: ... + +class SuffixedMultiWidget(forms.MultiWidget): + suffixes: list[str] + def __init__(self, *args: Any, **kwargs: Any) -> None: ... # Args/kwargs can be any widget params for MultiWidget + def suffixed(self, name: str, suffix: str) -> str: ... + # Widget value and context can contain any data types + def get_context(self, name: str, value: Any, attrs: dict[str, Any] | None) -> dict[str, Any]: ... + # Returns list of any value types from widget data + def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> list[Any]: ... + # Widget data can contain any types + def value_omitted_from_data(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> bool: ... + def replace_name(self, output: str, index: int) -> str: ... + # Decompresses any widget value into list of components + def decompress(self, value: Any) -> list[Any] | None: ... + +class RangeWidget(SuffixedMultiWidget): + template_name: str + suffixes: list[str] + # Accepts any widget attribute types + def __init__(self, attrs: dict[str, Any] | None = None) -> None: ... + # Decompresses any range value into list components + def decompress(self, value: Any) -> list[Any] | None: ... + +class DateRangeWidget(RangeWidget): + suffixes: list[str] + +class LookupChoiceWidget(SuffixedMultiWidget): + suffixes: list[str] + # Decompresses any lookup choice value into components + def decompress(self, value: Any) -> list[Any] | None: ... + +class BooleanWidget(forms.Select): + # Accepts any widget attribute types + def __init__(self, attrs: dict[str, Any] | None = None) -> None: ... + # Widget value and renderer can be any type + def render(self, name: str, value: Any, attrs: dict[str, Any] | None = None, renderer: Any | None = None) -> SafeString: ... + # Return value type depends on widget data + def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> Any: ... + +class BaseCSVWidget(forms.Widget): + # Can be widget class or instance - __init__ converts to instance via instantiation or deepcopy + surrogate: type[Any] = ... + + # Args/kwargs can be any widget params for surrogate init + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + # CSV widget data can contain any types + def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> list[str]: ... + # Widget value and renderer can be any type + def render(self, name: str, value: Any, attrs: dict[str, Any] | None = None, renderer: Any | None = None) -> SafeString: ... + +class CSVWidget(BaseCSVWidget, forms.TextInput): + # Args/kwargs can be any widget params, attrs for styling + def __init__(self, *args: Any, attrs: dict[str, Any] | None = None, **kwargs: Any) -> None: ... + +class QueryArrayWidget(BaseCSVWidget, forms.TextInput): + # Query array widget data can contain any types + def value_from_datadict(self, data: Mapping[str, Any], files: Mapping[str, Any], name: str) -> list[str]: ...