Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow any function for admin's display and action decorators #2210

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions django-stubs/contrib/admin/decorators.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,41 @@ from typing import Any, TypeVar, overload

from django.contrib.admin import ModelAdmin
from django.contrib.admin.sites import AdminSite
from django.db.models import QuerySet
from django.db.models.base import Model
from django.db.models.expressions import BaseExpression, Combinable
from django.http import HttpRequest, HttpResponseBase
from django.utils.functional import _StrOrPromise
from typing_extensions import TypeAlias

_Model = TypeVar("_Model", bound=Model)
_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin)
_Request = TypeVar("_Request", bound=HttpRequest)
_QuerySet = TypeVar("_QuerySet", bound=QuerySet)
# This is deliberately different from _DisplayT defined in contrib.admin.options
_DisplayCallable: TypeAlias = Callable[[_ModelAdmin, _Model], Any] | Callable[[_Model], Any]
_DisplayCallableT = TypeVar("_DisplayCallableT", bound=_DisplayCallable)
_ActionReturn = TypeVar("_ActionReturn", bound=HttpResponseBase | None)
_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin[Any])
_F = TypeVar("_F", bound=Callable[..., Any])

@overload
def action(
function: Callable[[_ModelAdmin, _Request, _QuerySet], _ActionReturn],
function: _F,
permissions: Sequence[str] | None = ...,
description: _StrOrPromise | None = ...,
) -> Callable[[_ModelAdmin, _Request, _QuerySet], _ActionReturn]: ...
) -> _F: ...
@overload
def action(
*,
permissions: Sequence[str] | None = ...,
description: _StrOrPromise | None = ...,
) -> Callable[
[Callable[[_ModelAdmin, _Request, _QuerySet], _ActionReturn]],
Callable[[_ModelAdmin, _Request, _QuerySet], _ActionReturn],
]: ...
) -> Callable[[_F], _F]: ...
@overload
def display(
function: _DisplayCallableT,
function: _F,
boolean: bool | None = ...,
ordering: str | Combinable | BaseExpression | None = ...,
description: _StrOrPromise | None = ...,
empty_value: str | None = ...,
) -> _DisplayCallableT: ...
) -> _F: ...
@overload
def display(
*,
boolean: bool | None = ...,
ordering: str | Combinable | BaseExpression | None = ...,
description: _StrOrPromise | None = ...,
empty_value: str | None = ...,
) -> Callable[[_DisplayCallableT], _DisplayCallableT]: ...
) -> Callable[[_F], _F]: ...
def register(
*models: type[Model], site: AdminSite | None = ...
) -> Callable[[type[_ModelAdmin]], type[_ModelAdmin]]: ...
6 changes: 3 additions & 3 deletions django-stubs/contrib/admin/options.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ from django.urls.resolvers import URLPattern
from django.utils.datastructures import _ListOrTuple
from django.utils.functional import _StrOrPromise
from django.utils.safestring import SafeString
from typing_extensions import TypeAlias, TypedDict
from typing_extensions import Self, TypeAlias, TypedDict

IS_POPUP_VAR: str
TO_FIELD_VAR: str
Expand Down Expand Up @@ -139,7 +139,7 @@ class BaseModelAdmin(Generic[_ModelT]):
@property
def view_on_site(self) -> Callable[[_ModelT], str] | bool: ...

_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin)
_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin[Any])
_ActionCallable: TypeAlias = Callable[[_ModelAdmin, HttpRequest, QuerySet[_ModelT]], HttpResponseBase | None]

class ModelAdmin(BaseModelAdmin[_ModelT]):
Expand Down Expand Up @@ -167,7 +167,7 @@ class ModelAdmin(BaseModelAdmin[_ModelT]):
delete_selected_confirmation_template: _TemplateForResponseT | None
object_history_template: _TemplateForResponseT | None
popup_response_template: _TemplateForResponseT | None
actions: Sequence[_ActionCallable[Any, _ModelT] | str] | None
actions: Sequence[_ActionCallable[Self, _ModelT] | str] | None
action_form: Any
actions_on_top: bool
actions_on_bottom: bool
Expand Down
20 changes: 4 additions & 16 deletions tests/typecheck/contrib/admin/test_decorators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,6 @@
@admin.action
def freestanding_action_file_response(modeladmin: "MyModelAdmin", request: HttpRequest, queryset: QuerySet[MyModel]) -> FileResponse: ...

@admin.action # E: Value of type variable "_ModelAdmin" of "action" cannot be "int" [type-var]
def freestanding_action_invalid_bare(modeladmin: int, request: HttpRequest, queryset: QuerySet[MyModel]) -> None: ...

@admin.action(description="Some text here", permissions=["test"]) # E: Value of type variable "_ModelAdmin" of function cannot be "int" [type-var]
def freestanding_action_invalid_fancy(modeladmin: int, request: HttpRequest, queryset: QuerySet[MyModel]) -> None: ...

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin[MyModel]):
actions = [freestanding_action_bare, freestanding_action_fancy, "method_action_bare", "method_action_fancy", freestanding_action_http_response, freestanding_action_file_response]
Expand All @@ -101,14 +95,8 @@
@admin.action(description="Some text here", permissions=["test"])
def method_action_file_response(self, request: HttpRequest, queryset: QuerySet[MyModel]) -> FileResponse: ...

@admin.action # E: Value of type variable "_QuerySet" of "action" cannot be "int" [type-var]
def method_action_invalid_bare(self, request: HttpRequest, queryset: int) -> None: ...

@admin.action(description="Some text here", permissions=["test"]) # E: Value of type variable "_QuerySet" of function cannot be "int" [type-var]
def method_action_invalid_fancy(self, request: HttpRequest, queryset: int) -> None: ...

def method(self) -> None:
reveal_type(self.method_action_bare) # N: Revealed type is "def (django.http.request.HttpRequest, django.db.models.query.QuerySet[main.MyModel, main.MyModel])"
reveal_type(self.method_action_fancy) # N: Revealed type is "def (django.http.request.HttpRequest, django.db.models.query.QuerySet[main.MyModel, main.MyModel])"
reveal_type(self.method_action_http_response) # N: Revealed type is "def (django.http.request.HttpRequest, django.db.models.query.QuerySet[main.MyModel, main.MyModel]) -> django.http.response.HttpResponse"
reveal_type(self.method_action_file_response) # N: Revealed type is "def (django.http.request.HttpRequest, django.db.models.query.QuerySet[main.MyModel, main.MyModel]) -> django.http.response.FileResponse"
reveal_type(self.method_action_bare) # N: Revealed type is "def (request: django.http.request.HttpRequest, queryset: django.db.models.query.QuerySet[main.MyModel, main.MyModel])"
reveal_type(self.method_action_fancy) # N: Revealed type is "def (request: django.http.request.HttpRequest, queryset: django.db.models.query.QuerySet[main.MyModel, main.MyModel])"
reveal_type(self.method_action_http_response) # N: Revealed type is "def (request: django.http.request.HttpRequest, queryset: django.db.models.query.QuerySet[main.MyModel, main.MyModel]) -> django.http.response.HttpResponse"
reveal_type(self.method_action_file_response) # N: Revealed type is "def (request: django.http.request.HttpRequest, queryset: django.db.models.query.QuerySet[main.MyModel, main.MyModel]) -> django.http.response.FileResponse"
8 changes: 5 additions & 3 deletions tests/typecheck/contrib/admin/test_options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,15 @@
main: |
from django.contrib import admin
from django.http.request import HttpRequest
from django.db.models.query import QuerySet
from django.db import models

class MyModel(models.Model): ...

def an_action(modeladmin: None) -> None:
pass

class A(admin.ModelAdmin):
actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Union[Callable[[Any, HttpRequest, QuerySet[Any, Any]], Optional[HttpResponseBase]], str]" [list-item]
class A(admin.ModelAdmin[MyModel]):
actions = [an_action] # E: List item 0 has incompatible type "Callable[[None], None]"; expected "Union[Callable[[A, HttpRequest, QuerySet[MyModel, MyModel]], Optional[HttpResponseBase]], str]" [list-item]
- case: errors_for_invalid_model_admin_generic
main: |
from django.contrib.admin import ModelAdmin
Expand Down
Loading