diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index b03aa2e28d..23234da282 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -4,6 +4,7 @@ from typing import ( Callable, Dict, Generic, + Iterable, Iterator, List, Mapping, @@ -16,8 +17,9 @@ from typing import ( Union, ) + from django import forms -from django.contrib.admin.filters import ListFilter +from django.contrib.admin.filters import ListFilter, FieldListFilter from django.contrib.admin.models import LogEntry from django.contrib.admin.sites import AdminSite from django.contrib.admin.views.main import ChangeList @@ -25,19 +27,19 @@ from django.contrib.auth.forms import AdminPasswordChangeForm from django.contrib.contenttypes.models import ContentType from django.core.checks.messages import CheckMessage from django.core.paginator import Paginator -from django.db import models from django.db.models.base import Model from django.db.models.fields import Field from django.db.models.fields.related import ForeignKey, ManyToManyField, RelatedField from django.db.models.options import Options from django.db.models.query import QuerySet -from django.forms.fields import TypedChoiceField +from django.forms.fields import Field as FormField, TypedChoiceField from django.forms.forms import BaseForm +from django.forms.formsets import BaseFormSet from django.forms.models import BaseInlineFormSet, ModelChoiceField, ModelMultipleChoiceField from django.forms.widgets import Media from django.http.request import HttpRequest -from django.http.response import HttpResponse, HttpResponseBase, HttpResponseRedirect, JsonResponse -from django.template.response import TemplateResponse +from django.http.response import HttpResponse, HttpResponseRedirect, JsonResponse +from django.template.response import _TemplateForResponseT from django.urls.resolvers import URLPattern from django.utils.safestring import SafeString from django.utils.datastructures import _ListOrTuple @@ -62,18 +64,26 @@ class IncorrectLookupParameters(Exception): ... FORMFIELD_FOR_DBFIELD_DEFAULTS: Any csrf_protect_m: Any +_FieldGroups = Sequence[Union[str, Sequence[str]]] + class _OptionalFieldOpts(TypedDict, total=False): classes: Sequence[str] description: str class _FieldOpts(_OptionalFieldOpts, total=True): - fields: Sequence[Union[str, Sequence[str]]] + fields: _FieldGroups # Workaround for mypy issue, a Sequence type should be preferred here. # https://github.com/python/mypy/issues/8921 # _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]] -_T = TypeVar("_T") _FieldsetSpec = _ListOrTuple[Tuple[Optional[str], _FieldOpts]] +_ListFilterT = Union[ + Type[ListFilter], + Field, + str, + Tuple[Union[Field, str], Type[FieldListFilter]], + List[Union[Field, str, Type[FieldListFilter]]], +] # Generic type specifically for models, for use in BaseModelAdmin and subclasses # https://github.com/typeddjango/django-stubs/issues/482 @@ -82,62 +92,70 @@ _ModelT = TypeVar("_ModelT", bound=Model) class BaseModelAdmin(Generic[_ModelT]): autocomplete_fields: Sequence[str] = ... raw_id_fields: Sequence[str] = ... - fields: Sequence[Union[str, Sequence[str]]] = ... - exclude: Sequence[str] = ... - fieldsets: _FieldsetSpec = ... - form: Type[BaseForm] = ... + fields: Optional[_FieldGroups] = ... + exclude: Optional[Sequence[str]] = ... + fieldsets: Optional[_FieldsetSpec] = ... + form: Type[forms.ModelForm[_ModelT]] = ... filter_vertical: Sequence[str] = ... filter_horizontal: Sequence[str] = ... radio_fields: Mapping[str, _Direction] = ... - prepopulated_fields: Mapping[str, Sequence[str]] = ... + prepopulated_fields: Dict[str, Sequence[str]] = ... formfield_overrides: Mapping[Type[Field], Mapping[str, Any]] = ... - readonly_fields: Sequence[Union[str, Callable[[_ModelT], Any]]] = ... - ordering: Sequence[str] = ... - sortable_by: Sequence[str] = ... - view_on_site: bool = ... + readonly_fields: Sequence[str] = ... + ordering: Optional[Sequence[str]] = ... + sortable_by: Optional[_ListOrTuple[str]] = ... + view_on_site: Union[bool, Callable[[_ModelT], str]] = ... show_full_result_count: bool = ... checks_class: Any = ... + model: Type[_ModelT] + opts: Options[_ModelT] + admin_site: AdminSite + def __init__(self) -> None: ... def check(self, **kwargs: Any) -> List[CheckMessage]: ... def formfield_for_dbfield( - self, db_field: models.Field, request: Optional[HttpRequest], **kwargs: Any - ) -> Optional[forms.Field]: ... + self, db_field: Field, request: HttpRequest, **kwargs: Any + ) -> Optional[FormField]: ... def formfield_for_choice_field( - self, db_field: Field, request: Optional[HttpRequest], **kwargs: Any + self, db_field: Field, request: HttpRequest, **kwargs: Any ) -> TypedChoiceField: ... def get_field_queryset( - self, db: None, db_field: RelatedField, request: Optional[HttpRequest] + self, db: Optional[str], db_field: RelatedField, request: HttpRequest ) -> Optional[QuerySet]: ... def formfield_for_foreignkey( - self, db_field: ForeignKey, request: Optional[HttpRequest], **kwargs: Any - ) -> Optional[ModelChoiceField]: ... + self, db_field: ForeignKey, request: HttpRequest, **kwargs: Any + ) -> ModelChoiceField: ... def formfield_for_manytomany( - self, db_field: ManyToManyField, request: Optional[HttpRequest], **kwargs: Any - ) -> ModelMultipleChoiceField: ... - def get_autocomplete_fields(self, request: HttpRequest) -> Tuple: ... + self, db_field: ManyToManyField, request: HttpRequest, **kwargs: Any + ) -> Optional[ModelMultipleChoiceField]: ... + def get_autocomplete_fields(self, request: HttpRequest) -> Sequence[str]: ... def get_view_on_site_url(self, obj: Optional[_ModelT] = ...) -> Optional[str]: ... - def get_empty_value_display(self) -> SafeText: ... - def get_exclude(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Any: ... - def get_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Sequence[Union[Callable, str]]: ... + def get_empty_value_display(self) -> SafeString: ... + def get_exclude(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Optional[Sequence[str]]: ... + def get_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> _FieldGroups: ... def get_fieldsets( self, request: HttpRequest, obj: Optional[_ModelT] = ... - ) -> List[Tuple[Optional[str], Dict[str, Any]]]: ... - def get_ordering(self, request: HttpRequest) -> Union[List[str], Tuple]: ... - def get_readonly_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Union[List[str], Tuple]: ... - def get_prepopulated_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Dict[str, Tuple[str]]: ... - def get_queryset(self, request: HttpRequest) -> QuerySet: ... - def get_sortable_by(self, request: HttpRequest) -> Union[List[Callable], List[str], Tuple]: ... + ) -> _FieldsetSpec: ... + def get_inlines(self, request: HttpRequest, obj: Optional[_ModelT]) -> List[Type[InlineModelAdmin]]: ... + def get_ordering(self, request: HttpRequest) -> Sequence[str]: ... + def get_readonly_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Sequence[str]: ... + def get_prepopulated_fields(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> Dict[str, Sequence[str]]: ... + def get_queryset(self, request: HttpRequest) -> QuerySet[_ModelT]: ... + def get_sortable_by(self, request: HttpRequest) -> Sequence[str]: ... def lookup_allowed(self, lookup: str, value: str) -> bool: ... def to_field_allowed(self, request: HttpRequest, to_field: str) -> bool: ... def has_add_permission(self, request: HttpRequest) -> bool: ... def has_change_permission(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> bool: ... def has_delete_permission(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> bool: ... def has_view_permission(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> bool: ... + def has_view_or_change_permission(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> bool: ... def has_module_permission(self, request: HttpRequest) -> bool: ... +_DisplayT = _ListOrTuple[Union[str, Callable[[_ModelT], str]]] + class ModelAdmin(BaseModelAdmin[_ModelT]): - list_display: Sequence[Union[str, Callable[[_ModelT], Any]]] = ... - list_display_links: Optional[Sequence[Union[str, Callable]]] = ... - list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ... + list_display: _DisplayT = ... + list_display_links: _DisplayT = ... + list_filter: _ListOrTuple[_ListFilterT] = ... list_select_related: Union[bool, Sequence[str]] = ... list_per_page: int = ... list_max_show_all: int = ... @@ -150,23 +168,22 @@ class ModelAdmin(BaseModelAdmin[_ModelT]): paginator: Type = ... preserve_filters: bool = ... inlines: Sequence[Type[InlineModelAdmin]] = ... - add_form_template: str = ... - change_form_template: str = ... - change_list_template: str = ... - delete_confirmation_template: str = ... - delete_selected_confirmation_template: str = ... - object_history_template: str = ... - popup_response_template: str = ... + add_form_template: Optional[_TemplateForResponseT] = ... + change_form_template: Optional[_TemplateForResponseT] = ... + change_list_template: Optional[_TemplateForResponseT] = ... + delete_confirmation_template: Optional[_TemplateForResponseT] = ... + delete_selected_confirmation_template: Optional[_TemplateForResponseT] = ... + object_history_template: Optional[_TemplateForResponseT] = ... + popup_response_template: Optional[_TemplateForResponseT] = ... actions: Optional[Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]]] = ... action_form: Any = ... actions_on_top: bool = ... actions_on_bottom: bool = ... actions_selection_counter: bool = ... model: Type[_ModelT] = ... - opts: Options = ... + opts: Options[_ModelT] = ... admin_site: AdminSite = ... - def __init__(self, model: Type[_ModelT], admin_site: Optional[AdminSite]) -> None: ... - def get_inlines(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> List[Type[InlineModelAdmin]]: ... + def __init__(self, model: Type[_ModelT], admin_site: AdminSite) -> None: ... def get_inline_instances(self, request: HttpRequest, obj: Optional[_ModelT] = ...) -> List[InlineModelAdmin]: ... def get_urls(self) -> List[URLPattern]: ... @property @@ -174,11 +191,11 @@ class ModelAdmin(BaseModelAdmin[_ModelT]): @property def media(self) -> Media: ... def get_model_perms(self, request: HttpRequest) -> Dict[str, bool]: ... - def get_form(self, request: Any, obj: Optional[_ModelT] = ..., change: bool = ..., **kwargs: Any): ... + def get_form(self, request: Any, obj: Optional[_ModelT] = ..., change: bool = ..., **kwargs: Any) -> Type[forms.ModelForm[_ModelT]]: ... def get_changelist(self, request: HttpRequest, **kwargs: Any) -> Type[ChangeList]: ... def get_changelist_instance(self, request: HttpRequest) -> ChangeList: ... def get_object( - self, request: HttpRequest, object_id: str, from_field: Optional[Union[Callable[..., Any], str]] = ... + self, request: HttpRequest, object_id: str, from_field: Optional[str] = ... ) -> Optional[_ModelT]: ... def get_changelist_form(self, request: Any, **kwargs: Any): ... def get_changelist_formset(self, request: Any, **kwargs: Any): ... @@ -199,20 +216,20 @@ class ModelAdmin(BaseModelAdmin[_ModelT]): def get_action_choices( self, request: HttpRequest, default_choices: List[Tuple[str, str]] = ... ) -> List[Tuple[str, str]]: ... - def get_action(self, action: Union[Callable, str]) -> Tuple[Callable, str, str]: ... - def get_list_display(self, request: HttpRequest) -> Sequence[str]: ... - def get_list_display_links(self, request: HttpRequest, list_display: Sequence[str]) -> Optional[Sequence[str]]: ... - def get_list_filter(self, request: HttpRequest) -> Sequence[str]: ... - def get_list_select_related(self, request: HttpRequest) -> Sequence[str]: ... - def get_search_fields(self, request: HttpRequest) -> List[str]: ... + def get_action(self, action: Union[Callable, str]) -> Optional[Tuple[Callable[..., str], str, str]]: ... + def get_list_display(self, request: HttpRequest) -> _DisplayT: ... + def get_list_display_links(self, request: HttpRequest, list_display: _DisplayT) -> _DisplayT: ... + def get_list_filter(self, request: HttpRequest) -> Sequence[_ListFilterT]: ... + def get_list_select_related(self, request: HttpRequest) -> Union[bool, Sequence[str]]: ... + def get_search_fields(self, request: HttpRequest) -> Sequence[str]: ... def get_search_results( self, request: HttpRequest, queryset: QuerySet, search_term: str - ) -> Tuple[QuerySet, bool]: ... + ) -> Tuple[QuerySet[_ModelT], bool]: ... def get_preserved_filters(self, request: HttpRequest) -> str: ... def _get_edited_object_pks(self, request: HttpRequest, prefix: str) -> List[str]: ... - def _get_list_editable_queryset(self, request: HttpRequest, prefix: str) -> QuerySet: ... + def _get_list_editable_queryset(self, request: HttpRequest, prefix: str) -> QuerySet[_ModelT]: ... def construct_change_message( - self, request: HttpRequest, form: AdminPasswordChangeForm, formsets: None, add: bool = ... + self, request: HttpRequest, form: AdminPasswordChangeForm, formsets: Iterable[BaseFormSet], add: bool = ... ) -> List[Dict[str, Dict[str, List[str]]]]: ... def message_user( self, @@ -243,21 +260,21 @@ class ModelAdmin(BaseModelAdmin[_ModelT]): def response_change(self, request: HttpRequest, obj: _ModelT) -> HttpResponse: ... def response_post_save_add(self, request: HttpRequest, obj: _ModelT) -> HttpResponseRedirect: ... def response_post_save_change(self, request: HttpRequest, obj: _ModelT) -> HttpResponseRedirect: ... - def response_action(self, request: HttpRequest, queryset: QuerySet) -> Optional[HttpResponseBase]: ... + # Probably FileResponse cannot come from ModelAdmin views + def response_action(self, request: HttpRequest, queryset: QuerySet) -> Optional[HttpResponse]: ... def response_delete(self, request: HttpRequest, obj_display: str, obj_id: int) -> HttpResponse: ... def render_delete_form(self, request: Any, context: Any): ... def get_inline_formsets( self, request: HttpRequest, formsets: List[Any], inline_instances: List[Any], obj: Optional[_ModelT] = ... ) -> List[Any]: ... - def get_changeform_initial_data(self, request: HttpRequest) -> Dict[str, str]: ... + def get_changeform_initial_data(self, request: HttpRequest) -> Dict[str, Union[str, List[str]]]: ... def changeform_view( self, request: HttpRequest, object_id: Optional[str] = ..., form_url: str = ..., extra_context: Optional[Dict[str, Any]] = ..., - ) -> Any: ... - def autocomplete_view(self, request: HttpRequest) -> JsonResponse: ... + ) -> HttpResponse: ... def add_view( self, request: HttpRequest, form_url: str = ..., extra_context: Optional[Dict[str, Any]] = ... ) -> HttpResponse: ... @@ -266,21 +283,24 @@ class ModelAdmin(BaseModelAdmin[_ModelT]): ) -> HttpResponse: ... def changelist_view( self, request: HttpRequest, extra_context: Optional[Dict[str, Any]] = ... - ) -> TemplateResponse: ... + ) -> HttpResponse: ... def get_deleted_objects( - self, objs: QuerySet, request: HttpRequest - ) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ... + self, objs: Union[Sequence[_ModelT], QuerySet[_ModelT]], request: HttpRequest + ) -> Tuple[List[Model], Dict[str, int], Set[str], List[str]]: ... def delete_view( self, request: HttpRequest, object_id: str, extra_context: Optional[Dict[str, Any]] = ... - ) -> Any: ... + ) -> HttpResponse: ... def history_view( self, request: HttpRequest, object_id: str, extra_context: Optional[Dict[str, Any]] = ... ) -> HttpResponse: ... -class InlineModelAdmin(BaseModelAdmin[_ModelT]): - model: Type[_ModelT] = ... - fk_name: str = ... - formset: Type[BaseInlineFormSet] = ... +_ChildModelT = TypeVar("_ChildModelT", bound=Model) +_ParentModelT = TypeVar("_ParentModelT", bound=Model) + +class InlineModelAdmin(Generic[_ChildModelT, _ParentModelT], BaseModelAdmin[_ChildModelT]): + model: Type[_ChildModelT] = ... + fk_name: Optional[str] = ... + formset: Type[BaseInlineFormSet[_ChildModelT, forms.ModelForm[_ChildModelT]]] = ... extra: int = ... min_num: Optional[int] = ... max_num: Optional[int] = ... @@ -291,16 +311,26 @@ class InlineModelAdmin(BaseModelAdmin[_ModelT]): show_change_link: bool = ... classes: Optional[Sequence[str]] = ... admin_site: AdminSite = ... - parent_model: Any = ... - opts: Any = ... - has_registered_model: Any = ... - def __init__(self, parent_model: Union[Type[_ModelT], _ModelT], admin_site: AdminSite) -> None: ... + parent_model: Type[_ParentModelT] = ... + opts: Options[_ChildModelT] = ... + has_registered_model: bool = ... + def __init__(self, parent_model: Type[_ParentModelT], admin_site: AdminSite) -> None: ... @property def media(self) -> Media: ... - def get_extra(self, request: HttpRequest, obj: Optional[_ModelT] = ..., **kwargs: Any) -> int: ... - def get_min_num(self, request: HttpRequest, obj: Optional[_ModelT] = ..., **kwargs: Any) -> Optional[int]: ... - def get_max_num(self, request: HttpRequest, obj: Optional[_ModelT] = ..., **kwargs: Any) -> Optional[int]: ... - def get_formset(self, request: Any, obj: Optional[_ModelT] = ..., **kwargs: Any): ... + def get_extra(self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any) -> int: ... + def get_min_num(self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any) -> Optional[int]: ... + def get_max_num(self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any) -> Optional[int]: ... + def get_formset( + self, request: HttpRequest, obj: Optional[_ParentModelT] = ..., **kwargs: Any + ) -> Type[BaseInlineFormSet[_ChildModelT, forms.ModelForm[_ChildModelT]]]: ... + def get_queryset(self, request: HttpRequest) -> QuerySet[_ChildModelT]: ... + def has_add_permission(self, request: HttpRequest, obj: Optional[_ParentModelT]) -> bool: ... # type: ignore + def has_change_permission(self, request: HttpRequest, obj: Optional[_ParentModelT] = ...) -> bool: ... # type: ignore + def has_delete_permission(self, request: HttpRequest, obj: Optional[_ParentModelT] = ...) -> bool: ... # type: ignore + def has_view_permission(self, request: HttpRequest, obj: Optional[_ParentModelT] = ...) -> bool: ... # type: ignore -class StackedInline(InlineModelAdmin[_ModelT]): ... -class TabularInline(InlineModelAdmin[_ModelT]): ... +class StackedInline(InlineModelAdmin[_ChildModelT, _ParentModelT]): + template: str = ... + +class TabularInline(InlineModelAdmin[_ChildModelT, _ParentModelT]): + template: str = ... diff --git a/django-stubs/contrib/admin/utils.pyi b/django-stubs/contrib/admin/utils.pyi index 464a360319..5a0e5b7acc 100644 --- a/django-stubs/contrib/admin/utils.pyi +++ b/django-stubs/contrib/admin/utils.pyi @@ -1,5 +1,9 @@ -from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, Union +import datetime +import sys +from typing import ( + Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, + Tuple, Type, Union, overload +) from uuid import UUID from django.contrib.admin.options import BaseModelAdmin @@ -8,23 +12,29 @@ from django.contrib.auth.forms import AdminPasswordChangeForm from django.db.models.base import Model from django.db.models.deletion import Collector from django.db.models.fields import Field, reverse_related -from django.db.models.fields.reverse_related import ManyToOneRel from django.db.models.options import Options from django.db.models.query import QuerySet from django.forms.forms import BaseForm +from django.forms.formsets import BaseFormSet from django.http.request import HttpRequest +from django.utils.datastructures import _IndexableCollection + +if sys.version_info < (3, 8): + from typing_extensions import Literal +else: + from typing import Literal class FieldIsAForeignKeyColumnName(Exception): ... def lookup_needs_distinct(opts: Options, lookup_path: str) -> bool: ... -def prepare_lookup_value(key: str, value: Union[datetime, str]) -> Union[bool, datetime, str]: ... +def prepare_lookup_value(key: str, value: Union[datetime.datetime, str]) -> Union[bool, datetime.datetime, str]: ... def quote(s: Union[int, str, UUID]) -> str: ... def unquote(s: str) -> str: ... def flatten(fields: Any) -> List[Union[Callable, str]]: ... def flatten_fieldsets(fieldsets: Any) -> List[Union[Callable, str]]: ... def get_deleted_objects( objs: Union[Sequence[Optional[Model]], QuerySet[Model]], request: HttpRequest, admin_site: AdminSite -) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ... +) -> Tuple[List[Model], Dict[str, int], Set[str], List[str]]: ... class NestedObjects(Collector): data: Dict[str, Any] @@ -37,21 +47,38 @@ class NestedObjects(Collector): model_objs: Any = ... def __init__(self, *args: Any, **kwargs: Any) -> None: ... def add_edge(self, source: Optional[Model], target: Model) -> None: ... - def related_objects(self, related: ManyToOneRel, objs: Sequence[Optional[Model]]) -> QuerySet: ... + def related_objects( + self, + related_model: Type[Model], + related_fields: Iterable[Field], + objs: _IndexableCollection[Model] + ) -> QuerySet[Model]: ... def nested(self, format_callback: Callable = ...) -> List[Any]: ... + def can_fast_delete(self, *args: Any, **kwargs: Any) -> bool: ... -def model_format_dict(obj: Any): ... +def model_format_dict(obj: Union[Model, Type[Model], QuerySet, Options[Model]]): ... def model_ngettext(obj: Union[Options, QuerySet], n: Optional[int] = ...) -> str: ... def lookup_field( - name: Union[Callable, str], obj: Model, model_admin: BaseModelAdmin = ... -) -> Tuple[Optional[Field], Any, Any]: ... + name: Union[Callable, str], obj: Model, model_admin: Optional[BaseModelAdmin] = ... +) -> Tuple[Optional[Field], Optional[str], Any]: ... + +@overload +def label_for_field( # type: ignore + name: Union[Callable, str], + model: Type[Model], + model_admin: Optional[BaseModelAdmin] = ..., + return_attr: Literal[True] = ..., + form: Optional[BaseForm] = ..., +) -> Tuple[str, Union[Callable, str, None]]: ... +@overload def label_for_field( name: Union[Callable, str], model: Type[Model], model_admin: Optional[BaseModelAdmin] = ..., - return_attr: bool = ..., + return_attr: Literal[False] = ..., form: Optional[BaseForm] = ..., -) -> Union[Tuple[Optional[str], Union[Callable, Type[str]]], str]: ... +) -> str: ... + def help_text_for_field(name: str, model: Type[Model]) -> str: ... def display_for_field(value: Any, field: Field, empty_value_display: str) -> str: ... def display_for_value(value: Any, empty_value_display: str, boolean: bool = ...) -> str: ... @@ -62,5 +89,5 @@ def get_model_from_relation(field: Union[Field, reverse_related.ForeignObjectRel def reverse_field_path(model: Type[Model], path: str) -> Tuple[Type[Model], str]: ... def get_fields_from_path(model: Type[Model], path: str) -> List[Field]: ... def construct_change_message( - form: AdminPasswordChangeForm, formsets: None, add: bool + form: AdminPasswordChangeForm, formsets: Iterable[BaseFormSet], add: bool ) -> List[Dict[str, Dict[str, List[str]]]]: ... diff --git a/django-stubs/contrib/admin/views/main.pyi b/django-stubs/contrib/admin/views/main.pyi index 778e4a2e72..78e8d573a1 100644 --- a/django-stubs/contrib/admin/views/main.pyi +++ b/django-stubs/contrib/admin/views/main.pyi @@ -1,13 +1,16 @@ import sys -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import ( + Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union +) -from django.contrib.admin.filters import ListFilter, SimpleListFilter +from django.contrib.admin.filters import ListFilter from django.contrib.admin.options import IS_POPUP_VAR as IS_POPUP_VAR # noqa: F401 from django.contrib.admin.options import TO_FIELD_VAR as TO_FIELD_VAR from django.contrib.admin.options import ModelAdmin +from django.contrib.admin.options import _ListFilterT, _DisplayT from django.db.models.base import Model -from django.db.models.expressions import Combinable, CombinedExpression, OrderBy +from django.db.models.expressions import Expression from django.db.models.options import Options from django.db.models.query import QuerySet from django.forms.formsets import BaseFormSet @@ -24,71 +27,77 @@ ORDER_TYPE_VAR: str PAGE_VAR: str SEARCH_VAR: str ERROR_FLAG: str -IGNORED_PARAMS: Any +IGNORED_PARAMS: Tuple[str, ...] class ChangeList: model: Type[Model] = ... opts: Options = ... lookup_opts: Options = ... root_queryset: QuerySet = ... - list_display: List[str] = ... - list_display_links: List[str] = ... - list_filter: Tuple = ... - date_hierarchy: None = ... - search_fields: Tuple = ... - list_select_related: bool = ... + list_display: _DisplayT = ... + list_display_links: _DisplayT = ... + list_filter: Sequence[_ListFilterT] = ... + date_hierarchy: Any = ... + search_fields: Sequence[str] = ... + list_select_related: Union[bool, Sequence[str]] = ... list_per_page: int = ... list_max_show_all: int = ... model_admin: ModelAdmin = ... preserved_filters: str = ... - sortable_by: Tuple[str] = ... + sortable_by: Optional[Sequence[str]] = ... page_num: int = ... show_all: bool = ... is_popup: bool = ... - to_field: None = ... - params: Dict[Any, Any] = ... - list_editable: Tuple = ... + to_field: Any = ... + params: Dict[str, Any] = ... + list_editable: Sequence[str] = ... query: str = ... queryset: Any = ... - title: Any = ... - pk_attname: Any = ... + title: str = ... + pk_attname: str = ... formset: Optional[BaseFormSet] def __init__( self, request: HttpRequest, model: Type[Model], - list_display: Union[List[Union[Callable, str]], Tuple[str]], - list_display_links: Optional[Union[List[Callable], List[str], Tuple[str]]], - list_filter: Union[List[Type[SimpleListFilter]], List[str], Tuple], + list_display: _DisplayT, + list_display_links: _DisplayT, + list_filter: Sequence[_ListFilterT], date_hierarchy: Optional[str], - search_fields: Union[List[str], Tuple], - list_select_related: Union[Tuple, bool], + search_fields: Sequence[str], + list_select_related: Union[bool, Sequence[str]], list_per_page: int, list_max_show_all: int, - list_editable: Union[List[str], Tuple], + list_editable: Sequence[str], model_admin: ModelAdmin, - sortable_by: Union[List[Callable], List[str], Tuple], + sortable_by: Optional[Sequence[str]], ) -> None: ... - def get_filters_params(self, params: None = ...) -> Dict[str, str]: ... + def get_filters_params(self, params: Optional[Dict[str, Any]] = ...) -> Dict[str, Any]: ... def get_filters( self, request: HttpRequest ) -> Tuple[List[ListFilter], bool, Dict[str, Union[bool, str]], bool, bool]: ... def get_query_string( - self, new_params: Optional[Dict[str, None]] = ..., remove: Optional[List[str]] = ... + self, + new_params: Optional[Dict[str, Any]] = ..., + remove: Optional[Iterable[str]] = ... ) -> str: ... - result_count: Any = ... - show_full_result_count: Any = ... - show_admin_actions: Any = ... - full_result_count: Any = ... + result_count: int = ... + show_full_result_count: bool = ... + show_admin_actions: bool = ... + full_result_count: Optional[int] = ... result_list: Any = ... - can_show_all: Any = ... - multi_page: Any = ... + can_show_all: bool = ... + multi_page: bool = ... paginator: Any = ... def get_results(self, request: HttpRequest) -> None: ... - def get_ordering_field(self, field_name: Union[Callable, str]) -> Optional[Union[CombinedExpression, str]]: ... + def get_ordering_field(self, field_name: Union[Callable, str]) -> Optional[Union[Expression, str]]: ... def get_ordering(self, request: HttpRequest, queryset: QuerySet) -> List[Union[Expression, str]]: ... def get_ordering_field_columns(self) -> Dict[int, Literal['desc', 'asc']]: ... def get_queryset(self, request: HttpRequest) -> QuerySet: ... + filter_specs: List[ListFilter] + has_filters: bool + has_active_filters: bool + clear_all_filters_qs: str def apply_select_related(self, qs: QuerySet) -> QuerySet: ... def has_related_field_in_list_display(self) -> bool: ... def url_for_result(self, result: Model) -> str: ... diff --git a/tests/typecheck/contrib/admin/test_options.yml b/tests/typecheck/contrib/admin/test_options.yml index 1d5bcdfc37..86a0aee5d3 100644 --- a/tests/typecheck/contrib/admin/test_options.yml +++ b/tests/typecheck/contrib/admin/test_options.yml @@ -3,7 +3,7 @@ - case: test_full_admin main: | from django.contrib import admin - from django.forms import Form, Textarea + from django.forms import Form, ModelForm, Textarea from django.db import models from django.core.paginator import Paginator from django.contrib.admin.sites import AdminSite @@ -30,7 +30,7 @@ (None, {"fields": ["a", "b"]}), ("group", {"fields": ("c",), "classes": ("a",), "description": "foo"}), ] - form = Form + form = ModelForm filter_vertical = ("fields",) filter_horizontal = ("plenty", "of", "fields") radio_fields = { @@ -48,7 +48,7 @@ # ModelAdmin list_display = ("pk",) list_display_links = ("str",) - list_filter = ("str", admin.SimpleListFilter, ("str", admin.SimpleListFilter)) + list_filter = ("str", admin.SimpleListFilter, ("str", admin.FieldListFilter)) list_select_related = True list_per_page = 1 list_max_show_all = 2