From 9efc9cf36872b1c9e3a2c0e33f29dc846bfe3803 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Thu, 14 Mar 2024 00:27:05 +0000 Subject: [PATCH 1/4] Remove unnecessary overrides in `django.contrib.admin.filters` --- django-stubs/contrib/admin/filters.pyi | 23 ++--------------------- scripts/stubtest/allowlist_todo.txt | 8 -------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/django-stubs/contrib/admin/filters.pyi b/django-stubs/contrib/admin/filters.pyi index dd5d76a6c..4ff405733 100644 --- a/django-stubs/contrib/admin/filters.pyi +++ b/django-stubs/contrib/admin/filters.pyi @@ -26,12 +26,11 @@ class SimpleListFilter(ListFilter): lookup_choices: Any def value(self) -> str | None: ... def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[Any, str]] | None: ... - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... class FieldListFilter(ListFilter): field: Field field_path: str - title: str + title: _StrOrPromise def __init__( self, field: Field, @@ -55,37 +54,30 @@ class FieldListFilter(ListFilter): ) -> FieldListFilter: ... class RelatedFieldListFilter(FieldListFilter): - used_parameters: dict[Any, Any] lookup_kwarg: str lookup_kwarg_isnull: str lookup_val: Any lookup_val_isnull: Any lookup_choices: Any lookup_title: str - title: str empty_value_display: Any @property def include_empty_choice(self) -> bool: ... def field_choices( self, field: RelatedField, request: HttpRequest, model_admin: ModelAdmin ) -> list[tuple[str, str]]: ... - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... class BooleanFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_kwarg2: str lookup_val: Any lookup_val2: Any - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... class ChoicesFieldListFilter(FieldListFilter): - title: str - used_parameters: dict[Any, Any] lookup_kwarg: str lookup_kwarg_isnull: str lookup_val: Any lookup_val_isnull: Any - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... class DateFieldListFilter(FieldListFilter): field_generic: Any @@ -94,28 +86,17 @@ class DateFieldListFilter(FieldListFilter): lookup_kwarg_until: Any links: Any lookup_kwarg_isnull: Any - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... class AllValuesFieldListFilter(FieldListFilter): - title: str - used_parameters: dict[Any, Any] lookup_kwarg: str lookup_kwarg_isnull: str lookup_val: Any lookup_val_isnull: Any empty_value_display: str lookup_choices: QuerySet - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... -class RelatedOnlyFieldListFilter(RelatedFieldListFilter): - lookup_kwarg: str - lookup_kwarg_isnull: str - lookup_val: Any - lookup_val_isnull: Any - title: str - used_parameters: dict[Any, Any] +class RelatedOnlyFieldListFilter(RelatedFieldListFilter): ... class EmptyFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_val: Any - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index 7158e0a71..ac8dcb551 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -10,8 +10,6 @@ django.conf.STATICFILES_STORAGE_DEPRECATED_MSG django.conf.global_settings.gettext_noop django.conf.urls.IncludedURLConf django.conf.urls.url -django.contrib.admin.AllValuesFieldListFilter.title -django.contrib.admin.ChoicesFieldListFilter.title django.contrib.admin.FieldListFilter.list_separator django.contrib.admin.FieldListFilter.title django.contrib.admin.ModelAdmin @@ -19,8 +17,6 @@ django.contrib.admin.ModelAdmin.log_addition django.contrib.admin.ModelAdmin.log_change django.contrib.admin.ModelAdmin.log_deletion django.contrib.admin.RelatedFieldListFilter.field_admin_ordering -django.contrib.admin.RelatedFieldListFilter.title -django.contrib.admin.RelatedOnlyFieldListFilter.title django.contrib.admin.StackedInline django.contrib.admin.TabularInline django.contrib.admin.action @@ -28,13 +24,9 @@ django.contrib.admin.apps.AdminConfig.default django.contrib.admin.decorators.action django.contrib.admin.decorators.display django.contrib.admin.display -django.contrib.admin.filters.AllValuesFieldListFilter.title -django.contrib.admin.filters.ChoicesFieldListFilter.title django.contrib.admin.filters.FieldListFilter.list_separator django.contrib.admin.filters.FieldListFilter.title django.contrib.admin.filters.RelatedFieldListFilter.field_admin_ordering -django.contrib.admin.filters.RelatedFieldListFilter.title -django.contrib.admin.filters.RelatedOnlyFieldListFilter.title django.contrib.admin.forms.AdminAuthenticationForm django.contrib.admin.forms.AdminPasswordChangeForm django.contrib.admin.helpers.ActionForm From 1a079e50e97a3e8f565825e72a38cd4dd4b2f47a Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Thu, 14 Mar 2024 00:32:29 +0000 Subject: [PATCH 2/4] Fix allowlist items for `django.contrib.admin.filters` --- django-stubs/contrib/admin/filters.pyi | 7 ++++++- scripts/stubtest/allowlist_todo.txt | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/django-stubs/contrib/admin/filters.pyi b/django-stubs/contrib/admin/filters.pyi index 4ff405733..91d03aadb 100644 --- a/django-stubs/contrib/admin/filters.pyi +++ b/django-stubs/contrib/admin/filters.pyi @@ -1,5 +1,5 @@ from collections.abc import Callable, Iterable, Iterator -from typing import Any +from typing import Any, ClassVar from django.contrib.admin.options import ModelAdmin from django.db.models.base import Model @@ -7,6 +7,7 @@ from django.db.models.fields import Field from django.db.models.fields.related import RelatedField from django.db.models.query import QuerySet from django.http.request import HttpRequest +from django.utils.datastructures import _ListOrTuple from django.utils.functional import _StrOrPromise class ListFilter: @@ -28,6 +29,7 @@ class SimpleListFilter(ListFilter): def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[Any, str]] | None: ... class FieldListFilter(ListFilter): + list_separator: ClassVar[str] field: Field field_path: str title: _StrOrPromise @@ -63,6 +65,9 @@ class RelatedFieldListFilter(FieldListFilter): empty_value_display: Any @property def include_empty_choice(self) -> bool: ... + def field_admin_ordering( + self, field: RelatedField, request: HttpRequest, model_admin: ModelAdmin + ) -> _ListOrTuple[str]: ... def field_choices( self, field: RelatedField, request: HttpRequest, model_admin: ModelAdmin ) -> list[tuple[str, str]]: ... diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index ac8dcb551..73b5189c8 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -10,13 +10,11 @@ django.conf.STATICFILES_STORAGE_DEPRECATED_MSG django.conf.global_settings.gettext_noop django.conf.urls.IncludedURLConf django.conf.urls.url -django.contrib.admin.FieldListFilter.list_separator django.contrib.admin.FieldListFilter.title django.contrib.admin.ModelAdmin django.contrib.admin.ModelAdmin.log_addition django.contrib.admin.ModelAdmin.log_change django.contrib.admin.ModelAdmin.log_deletion -django.contrib.admin.RelatedFieldListFilter.field_admin_ordering django.contrib.admin.StackedInline django.contrib.admin.TabularInline django.contrib.admin.action @@ -24,9 +22,7 @@ django.contrib.admin.apps.AdminConfig.default django.contrib.admin.decorators.action django.contrib.admin.decorators.display django.contrib.admin.display -django.contrib.admin.filters.FieldListFilter.list_separator django.contrib.admin.filters.FieldListFilter.title -django.contrib.admin.filters.RelatedFieldListFilter.field_admin_ordering django.contrib.admin.forms.AdminAuthenticationForm django.contrib.admin.forms.AdminPasswordChangeForm django.contrib.admin.helpers.ActionForm From 5c6b4b1632e78ef3a2a4bcb236afda53ce294224 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Thu, 14 Mar 2024 00:34:11 +0000 Subject: [PATCH 3/4] Improve typing further in `django.contrib.admin.filters` --- django-stubs/contrib/admin/filters.pyi | 62 +++++++++++++++----------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/django-stubs/contrib/admin/filters.pyi b/django-stubs/contrib/admin/filters.pyi index 91d03aadb..64a688833 100644 --- a/django-stubs/contrib/admin/filters.pyi +++ b/django-stubs/contrib/admin/filters.pyi @@ -1,7 +1,9 @@ from collections.abc import Callable, Iterable, Iterator +from datetime import date, datetime from typing import Any, ClassVar from django.contrib.admin.options import ModelAdmin +from django.contrib.admin.views.main import ChangeList from django.db.models.base import Model from django.db.models.fields import Field from django.db.models.fields.related import RelatedField @@ -9,24 +11,32 @@ from django.db.models.query import QuerySet from django.http.request import HttpRequest from django.utils.datastructures import _ListOrTuple from django.utils.functional import _StrOrPromise +from django.utils.safestring import SafeString +from typing_extensions import TypedDict + +class _ListFilterChoices(TypedDict): + selected: bool + query_string: str + display: _StrOrPromise class ListFilter: title: _StrOrPromise | None template: str - used_parameters: Any + request: HttpRequest + used_parameters: dict[str, bool | datetime | str] def __init__( self, request: HttpRequest, params: dict[str, str], model: type[Model], model_admin: ModelAdmin ) -> None: ... def has_output(self) -> bool: ... - def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... + def choices(self, changelist: ChangeList) -> Iterator[_ListFilterChoices]: ... def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None: ... def expected_parameters(self) -> list[str | None]: ... class SimpleListFilter(ListFilter): parameter_name: str | None - lookup_choices: Any + lookup_choices: list[tuple[str, _StrOrPromise]] def value(self) -> str | None: ... - def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[Any, str]] | None: ... + def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[str, _StrOrPromise]] | None: ... class FieldListFilter(ListFilter): list_separator: ClassVar[str] @@ -43,7 +53,9 @@ class FieldListFilter(ListFilter): field_path: str, ) -> None: ... @classmethod - def register(cls, test: Callable, list_filter_class: type[FieldListFilter], take_priority: bool = ...) -> None: ... + def register( + cls, test: Callable[[Field], Any], list_filter_class: type[FieldListFilter], take_priority: bool = ... + ) -> None: ... @classmethod def create( cls, @@ -58,11 +70,11 @@ class FieldListFilter(ListFilter): class RelatedFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_kwarg_isnull: str - lookup_val: Any - lookup_val_isnull: Any - lookup_choices: Any - lookup_title: str - empty_value_display: Any + lookup_val: str | None + lookup_val_isnull: str | None + lookup_choices: list[tuple[str, _StrOrPromise]] + lookup_title: _StrOrPromise + empty_value_display: SafeString @property def include_empty_choice(self) -> bool: ... def field_admin_ordering( @@ -70,38 +82,38 @@ class RelatedFieldListFilter(FieldListFilter): ) -> _ListOrTuple[str]: ... def field_choices( self, field: RelatedField, request: HttpRequest, model_admin: ModelAdmin - ) -> list[tuple[str, str]]: ... + ) -> list[tuple[str, _StrOrPromise]]: ... class BooleanFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_kwarg2: str - lookup_val: Any - lookup_val2: Any + lookup_val: str | None + lookup_val2: str | None class ChoicesFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_kwarg_isnull: str - lookup_val: Any - lookup_val_isnull: Any + lookup_val: str | None + lookup_val_isnull: str | None class DateFieldListFilter(FieldListFilter): - field_generic: Any - date_params: Any - lookup_kwarg_since: Any - lookup_kwarg_until: Any - links: Any - lookup_kwarg_isnull: Any + field_generic: str + date_params: dict[str, str] + lookup_kwarg_since: str + lookup_kwarg_until: str + links: tuple[tuple[_StrOrPromise, dict[str, bool | date | datetime]], ...] + lookup_kwarg_isnull: str class AllValuesFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_kwarg_isnull: str - lookup_val: Any - lookup_val_isnull: Any - empty_value_display: str + lookup_val: str | None + lookup_val_isnull: str | None + empty_value_display: SafeString lookup_choices: QuerySet class RelatedOnlyFieldListFilter(RelatedFieldListFilter): ... class EmptyFieldListFilter(FieldListFilter): lookup_kwarg: str - lookup_val: Any + lookup_val: str | None From 941a975b5880e24b8d3a367d775afa620158c077 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Wed, 6 Mar 2024 09:10:29 +0000 Subject: [PATCH 4/4] 5.0: Update `django.contrib.admin` --- django-stubs/contrib/admin/__init__.pyi | 1 + django-stubs/contrib/admin/exceptions.pyi | 2 ++ django-stubs/contrib/admin/filters.pyi | 11 +++++++-- django-stubs/contrib/admin/options.pyi | 1 + django-stubs/contrib/admin/sites.pyi | 3 --- django-stubs/contrib/admin/utils.pyi | 7 +++++- django-stubs/contrib/admin/views/main.pyi | 2 +- scripts/stubtest/allowlist_todo_django50.txt | 24 -------------------- 8 files changed, 20 insertions(+), 31 deletions(-) diff --git a/django-stubs/contrib/admin/__init__.pyi b/django-stubs/contrib/admin/__init__.pyi index 4badf3b4f..a7cded02e 100644 --- a/django-stubs/contrib/admin/__init__.pyi +++ b/django-stubs/contrib/admin/__init__.pyi @@ -15,6 +15,7 @@ from .filters import SimpleListFilter as SimpleListFilter from .options import HORIZONTAL as HORIZONTAL from .options import VERTICAL as VERTICAL from .options import ModelAdmin as ModelAdmin +from .options import ShowFacets as ShowFacets from .options import StackedInline as StackedInline from .options import TabularInline as TabularInline from .sites import AdminSite as AdminSite diff --git a/django-stubs/contrib/admin/exceptions.pyi b/django-stubs/contrib/admin/exceptions.pyi index cc76ad482..22e128764 100644 --- a/django-stubs/contrib/admin/exceptions.pyi +++ b/django-stubs/contrib/admin/exceptions.pyi @@ -2,3 +2,5 @@ from django.core.exceptions import SuspiciousOperation class DisallowedModelAdminLookup(SuspiciousOperation): ... class DisallowedModelAdminToField(SuspiciousOperation): ... +class AlreadyRegistered(Exception): ... +class NotRegistered(Exception): ... diff --git a/django-stubs/contrib/admin/filters.pyi b/django-stubs/contrib/admin/filters.pyi index 64a688833..6502ae210 100644 --- a/django-stubs/contrib/admin/filters.pyi +++ b/django-stubs/contrib/admin/filters.pyi @@ -4,10 +4,12 @@ from typing import Any, ClassVar from django.contrib.admin.options import ModelAdmin from django.contrib.admin.views.main import ChangeList +from django.db.models.aggregates import Count from django.db.models.base import Model from django.db.models.fields import Field from django.db.models.fields.related import RelatedField from django.db.models.query import QuerySet +from django.db.models.query_utils import Q from django.http.request import HttpRequest from django.utils.datastructures import _ListOrTuple from django.utils.functional import _StrOrPromise @@ -32,13 +34,17 @@ class ListFilter: def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None: ... def expected_parameters(self) -> list[str | None]: ... -class SimpleListFilter(ListFilter): +class FacetsMixin: + def get_facet_counts(self, pk_attname: str, filtered_qs: QuerySet[Model]) -> dict[str, Count]: ... + def get_facet_queryset(self, changelist: ChangeList) -> dict[str, int]: ... + +class SimpleListFilter(FacetsMixin, ListFilter): parameter_name: str | None lookup_choices: list[tuple[str, _StrOrPromise]] def value(self) -> str | None: ... def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Iterable[tuple[str, _StrOrPromise]] | None: ... -class FieldListFilter(ListFilter): +class FieldListFilter(FacetsMixin, ListFilter): list_separator: ClassVar[str] field: Field field_path: str @@ -117,3 +123,4 @@ class RelatedOnlyFieldListFilter(RelatedFieldListFilter): ... class EmptyFieldListFilter(FieldListFilter): lookup_kwarg: str lookup_val: str | None + def get_lookup_condition(self) -> Q: ... diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index a9be3de1f..096d840d9 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -38,6 +38,7 @@ from typing_extensions import TypeAlias, TypedDict IS_POPUP_VAR: str TO_FIELD_VAR: str +IS_FACETS_VAR: str HORIZONTAL: Literal[1] VERTICAL: Literal[2] diff --git a/django-stubs/contrib/admin/sites.pyi b/django-stubs/contrib/admin/sites.pyi index 271a6bbd6..e81049ec2 100644 --- a/django-stubs/contrib/admin/sites.pyi +++ b/django-stubs/contrib/admin/sites.pyi @@ -28,9 +28,6 @@ else: _ViewType = TypeVar("_ViewType", bound=Callable[..., HttpResponse]) _ActionCallback: TypeAlias = Callable[[ModelAdmin, HttpRequest, QuerySet], TemplateResponse | None] -class AlreadyRegistered(Exception): ... -class NotRegistered(Exception): ... - class AdminSite: site_title: _StrOrPromise site_header: _StrOrPromise diff --git a/django-stubs/contrib/admin/utils.pyi b/django-stubs/contrib/admin/utils.pyi index a82fb5655..521f8a9da 100644 --- a/django-stubs/contrib/admin/utils.pyi +++ b/django-stubs/contrib/admin/utils.pyi @@ -13,6 +13,7 @@ from django.db.models.deletion import Collector from django.db.models.fields import Field, reverse_related from django.db.models.options import Options from django.db.models.query import QuerySet +from django.db.models.query_utils import Q from django.forms.forms import BaseForm from django.forms.formsets import BaseFormSet from django.http.request import HttpRequest @@ -24,7 +25,11 @@ _T = TypeVar("_T") class FieldIsAForeignKeyColumnName(Exception): ... def lookup_spawns_duplicates(opts: Options, lookup_path: str) -> bool: ... -def prepare_lookup_value(key: str, value: datetime.datetime | str) -> bool | datetime.datetime | str: ... +def get_last_value_from_parameters(parameters: dict[str, list[str] | str], key: str) -> str | None: ... +def prepare_lookup_value( + key: str, value: list[bool | datetime.datetime | str] | datetime.datetime | str, separator: str +) -> list[bool | datetime.datetime | str] | bool | datetime.datetime | str: ... +def build_q_object_from_lookup_parameters(parameters: dict[str, list[str]]) -> Q: ... def quote(s: int | str | UUID) -> str: ... def unquote(s: str) -> str: ... def flatten(fields: Any) -> list[Callable | str]: ... diff --git a/django-stubs/contrib/admin/views/main.pyi b/django-stubs/contrib/admin/views/main.pyi index 81570f74a..63ff29cea 100644 --- a/django-stubs/contrib/admin/views/main.pyi +++ b/django-stubs/contrib/admin/views/main.pyi @@ -77,7 +77,7 @@ class ChangeList: def get_ordering_field(self, field_name: Callable | str) -> Expression | str | None: ... def get_ordering(self, request: HttpRequest, queryset: QuerySet) -> list[Expression | str]: ... def get_ordering_field_columns(self) -> dict[int, Literal["desc", "asc"]]: ... - def get_queryset(self, request: HttpRequest) -> QuerySet: ... + def get_queryset(self, request: HttpRequest, exclude_parameters: list[str | None] | None = ...) -> QuerySet: ... filter_specs: list[ListFilter] has_filters: bool has_active_filters: bool diff --git a/scripts/stubtest/allowlist_todo_django50.txt b/scripts/stubtest/allowlist_todo_django50.txt index b0a884779..db1e9591e 100644 --- a/scripts/stubtest/allowlist_todo_django50.txt +++ b/scripts/stubtest/allowlist_todo_django50.txt @@ -2,30 +2,6 @@ # Only discrepancies that appeared after Django 4.2 -> 5.0 update. # Unsorted: there are real problems and things we can really ignore. -django.contrib.admin.AllValuesFieldListFilter.get_facet_counts -django.contrib.admin.BooleanFieldListFilter.get_facet_counts -django.contrib.admin.ChoicesFieldListFilter.get_facet_counts -django.contrib.admin.DateFieldListFilter.get_facet_counts -django.contrib.admin.EmptyFieldListFilter.get_facet_counts -django.contrib.admin.EmptyFieldListFilter.get_lookup_condition -django.contrib.admin.RelatedFieldListFilter.get_facet_counts -django.contrib.admin.ShowFacets -django.contrib.admin.SimpleListFilter.get_facet_counts -django.contrib.admin.exceptions.AlreadyRegistered -django.contrib.admin.exceptions.NotRegistered -django.contrib.admin.filters.AllValuesFieldListFilter.get_facet_counts -django.contrib.admin.filters.BooleanFieldListFilter.get_facet_counts -django.contrib.admin.filters.ChoicesFieldListFilter.get_facet_counts -django.contrib.admin.filters.DateFieldListFilter.get_facet_counts -django.contrib.admin.filters.EmptyFieldListFilter.get_facet_counts -django.contrib.admin.filters.EmptyFieldListFilter.get_lookup_condition -django.contrib.admin.filters.FacetsMixin -django.contrib.admin.filters.RelatedFieldListFilter.get_facet_counts -django.contrib.admin.filters.SimpleListFilter.get_facet_counts -django.contrib.admin.options.IS_FACETS_VAR -django.contrib.admin.utils.build_q_object_from_lookup_parameters -django.contrib.admin.utils.get_last_value_from_parameters -django.contrib.admin.views.main.ChangeList.get_queryset django.contrib.contenttypes.fields.GenericForeignKey.get_content_type django.contrib.contenttypes.fields.GenericForeignKey.get_prefetch_querysets django.contrib.contenttypes.prefetch