Skip to content

Commit 350a803

Browse files
committed
Improve stubs for flatten and flatten_fieldsets
These functions are implemented in a generic way that doesn't generally care about the content. The existing return type of `Callable | str` makes these awkward to use. I've changed the use of `Sequence` in the `_FieldGroups` alias to use `_ListOrTuple`. This is another case where `ModelAdmin.fields` and the `"fields"` item in fieldsets are presumed by Django to be a `list` or `tuple`, e.g. there are some system checks that ensure this. I've also made `_FieldGroups` and `_FieldsetSpec` generic so that these aliases can be reused by `flatten()` and `flatten_fieldsets()`. Because of this, the typing of these functions will also now work nicely if used with `.list_display`, `.readonly_fields`, etc. See https://github.com/django/django/blob/afbb8c709d40e77b3f71c152d363c5ad95ceec2d/django/contrib/admin/utils.py#L102-L120
1 parent 19e63f2 commit 350a803

File tree

2 files changed

+12
-14
lines changed

2 files changed

+12
-14
lines changed

django-stubs/contrib/admin/options.pyi

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,19 @@ class IncorrectLookupParameters(Exception): ...
5656
FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
5757
csrf_protect_m: Any
5858

59-
_FieldGroups: TypeAlias = Sequence[str | Sequence[str]]
59+
_FieldT = TypeVar("_FieldT")
60+
_FieldGroups: TypeAlias = _ListOrTuple[_FieldT | _ListOrTuple[_FieldT]]
6061

6162
@type_check_only
6263
class _OptionalFieldOpts(TypedDict, total=False):
6364
classes: Sequence[str]
6465
description: _StrOrPromise
6566

6667
@type_check_only
67-
class _FieldOpts(_OptionalFieldOpts, total=True):
68-
fields: _FieldGroups
68+
class _FieldOpts(_OptionalFieldOpts, Generic[_FieldT], total=True):
69+
fields: _FieldGroups[_FieldT]
6970

70-
# Workaround for mypy issue, a Sequence type should be preferred here.
71-
# https://github.com/python/mypy/issues/8921
72-
# _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]]
73-
_FieldsetSpec: TypeAlias = _ListOrTuple[tuple[_StrOrPromise | None, _FieldOpts]]
71+
_FieldsetSpec: TypeAlias = _ListOrTuple[tuple[_StrOrPromise | None, _FieldOpts[_FieldT]]]
7472
_ListFilterT: TypeAlias = (
7573
type[ListFilter]
7674
| Field[Any, Any]
@@ -89,9 +87,9 @@ _DisplayT: TypeAlias = _ListOrTuple[str | Callable[[_ModelT], str | bool]]
8987
class BaseModelAdmin(Generic[_ModelT]):
9088
autocomplete_fields: ClassVar[_ListOrTuple[str]]
9189
raw_id_fields: ClassVar[_ListOrTuple[str]]
92-
fields: ClassVar[_FieldGroups | None]
90+
fields: ClassVar[_FieldGroups[str] | None]
9391
exclude: ClassVar[_ListOrTuple[str] | None]
94-
fieldsets: ClassVar[_FieldsetSpec | None]
92+
fieldsets: ClassVar[_FieldsetSpec[str] | None]
9593
form: type[forms.ModelForm[_ModelT]]
9694
filter_vertical: ClassVar[_ListOrTuple[str]]
9795
filter_horizontal: ClassVar[_ListOrTuple[str]]
@@ -123,8 +121,8 @@ class BaseModelAdmin(Generic[_ModelT]):
123121
def get_view_on_site_url(self, obj: _ModelT | None = ...) -> str | None: ...
124122
def get_empty_value_display(self) -> SafeString: ...
125123
def get_exclude(self, request: HttpRequest, obj: _ModelT | None = ...) -> _ListOrTuple[str] | None: ...
126-
def get_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> _FieldGroups: ...
127-
def get_fieldsets(self, request: HttpRequest, obj: _ModelT | None = ...) -> _FieldsetSpec: ...
124+
def get_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> _FieldGroups[str]: ...
125+
def get_fieldsets(self, request: HttpRequest, obj: _ModelT | None = ...) -> _FieldsetSpec[str]: ...
128126
def get_inlines(self, request: HttpRequest, obj: _ModelT | None) -> list[type[InlineModelAdmin]]: ...
129127
def get_ordering(self, request: HttpRequest) -> _ListOrTuple[str]: ...
130128
def get_readonly_fields(self, request: HttpRequest, obj: _ModelT | None = ...) -> _ListOrTuple[str]: ...

django-stubs/contrib/admin/utils.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from typing import Any, Literal, TypeVar, overload, type_check_only
55
from uuid import UUID
66

77
from _typeshed import Unused
8-
from django.contrib.admin.options import BaseModelAdmin
8+
from django.contrib.admin.options import BaseModelAdmin, _FieldGroups, _FieldsetSpec
99
from django.contrib.admin.sites import AdminSite
1010
from django.db.models.base import Model
1111
from django.db.models.deletion import Collector
@@ -31,8 +31,8 @@ def prepare_lookup_value(
3131
def build_q_object_from_lookup_parameters(parameters: dict[str, list[str]]) -> Q: ...
3232
def quote(s: int | str | UUID) -> str: ...
3333
def unquote(s: str) -> str: ...
34-
def flatten(fields: Any) -> list[Callable | str]: ...
35-
def flatten_fieldsets(fieldsets: Any) -> list[Callable | str]: ...
34+
def flatten(fields: _FieldGroups[_T]) -> list[_T]: ...
35+
def flatten_fieldsets(fieldsets: _FieldsetSpec[_T]) -> list[_T]: ...
3636
def get_deleted_objects(
3737
objs: Sequence[Model | None] | QuerySet[Model], request: HttpRequest, admin_site: AdminSite
3838
) -> tuple[list[str], dict[str, int], set[str], list[str]]: ...

0 commit comments

Comments
 (0)