diff --git a/django-stubs/contrib/admin/filters.pyi b/django-stubs/contrib/admin/filters.pyi index e3c627104..21beb7279 100644 --- a/django-stubs/contrib/admin/filters.pyi +++ b/django-stubs/contrib/admin/filters.pyi @@ -121,3 +121,8 @@ class RelatedOnlyFieldListFilter(RelatedFieldListFilter): lookup_val_isnull: None title: str used_parameters: Dict[Any, Any] + +class EmptyFieldListFilter(FieldListFilter): + lookup_kwarg: str + lookup_val: Any + def choices(self, changelist: Any) -> Iterator[dict[str, Any]]: ... diff --git a/django-stubs/db/models/indexes.pyi b/django-stubs/db/models/indexes.pyi index 9f0ab5336..929188a35 100644 --- a/django-stubs/db/models/indexes.pyi +++ b/django-stubs/db/models/indexes.pyi @@ -3,6 +3,7 @@ from typing import Any, List, Optional, Sequence, Tuple, Type from django.db.backends.base.schema import BaseDatabaseSchemaEditor from django.db.backends.ddl_references import Statement from django.db.models.base import Model +from django.db.models.expressions import BaseExpression, Combinable from django.db.models.query_utils import Q class Index: @@ -15,15 +16,19 @@ class Index: db_tablespace: Optional[str] = ... opclasses: Sequence[str] = ... condition: Optional[Q] = ... + expressions: Sequence[BaseExpression | Combinable] = ... + def __init__( self, - *, + *expressions: BaseExpression | Combinable | str, fields: Sequence[str] = ..., name: Optional[str] = ..., db_tablespace: Optional[str] = ..., opclasses: Sequence[str] = ..., condition: Optional[Q] = ... ) -> None: ... + @property + def contains_expressions(self) -> bool: ... def check_name(self) -> List[str]: ... def create_sql( self, diff --git a/django-stubs/urls/conf.pyi b/django-stubs/urls/conf.pyi index 3e0e32438..2d221c5b9 100644 --- a/django-stubs/urls/conf.pyi +++ b/django-stubs/urls/conf.pyi @@ -14,7 +14,9 @@ from ..conf.urls import IncludedURLConf from ..http.response import HttpResponseBase from .resolvers import URLPattern, URLResolver -_ResponseType = Union[HttpResponseBase, Coroutine[Any, Any, HttpResponseBase], Coroutine[Any, Any, None]] +_ResponseType = Union[ + HttpResponseBase, Coroutine[Any, Any, HttpResponseBase], Coroutine[Any, Any, None] +] def include( arg: Any, namespace: Optional[str] = ... diff --git a/django-stubs/utils/html.pyi b/django-stubs/utils/html.pyi index bf3b9c8b6..4c268349d 100644 --- a/django-stubs/utils/html.pyi +++ b/django-stubs/utils/html.pyi @@ -17,7 +17,9 @@ def json_script(value: Any, element_id: str) -> SafeText: ... def conditional_escape(text: Any) -> str: ... def format_html(format_string: str, *args: Any, **kwargs: Any) -> SafeText: ... def format_html_join( - sep: str, format_string: str, args_generator: Union[Iterable[Any], Iterable[Tuple[str]]] + sep: str, + format_string: str, + args_generator: Union[Iterable[Any], Iterable[Tuple[str]]], ) -> SafeText: ... def linebreaks(value: Any, autoescape: bool = ...) -> str: ... diff --git a/tests/trout/admin.py b/tests/trout/admin.py new file mode 100644 index 000000000..f231ec9ae --- /dev/null +++ b/tests/trout/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin + +from .models import IndexModel + + +@admin.register(IndexModel) +class IndexModelAdmin(admin.ModelAdmin[IndexModel]): + list_display = [ + "pub_date", "title", "author", "height", "weight", + ] + list_filter = [ + ("author", admin.filters.EmptyFieldListFilter), + "pub_date", + ] + + diff --git a/tests/trout/models.py b/tests/trout/models.py index 696e5abd7..e79cd1b04 100644 --- a/tests/trout/models.py +++ b/tests/trout/models.py @@ -1,3 +1,6 @@ +# Disable because pyright is not able to understand the Meta nested class override in models. +# pyright: reportIncompatibleVariableOverride=false + import sys from collections import namedtuple from datetime import time, timedelta @@ -21,6 +24,7 @@ from django.core.cache import cache from django.db import connection, connections, models from django.db.backends.utils import CursorWrapper +from django.db.models.functions import Lower, Round from django.db.models.manager import ManyToManyRelatedManager from django.http.request import HttpRequest from django.http.response import HttpResponse @@ -1030,3 +1034,24 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: AuthUser.objects.create_superuser(username="foo", email=None, password=None) + + +class IndexModel(models.Model): + title = models.TextField() + author = models.ForeignKey[Optional[User]](User, on_delete=models.CASCADE, null=True) + pub_date = models.DateTimeField() + height = models.IntegerField() + weight = models.IntegerField() + + class Meta: + # from: https://docs.djangoproject.com/en/3.2/ref/models/indexes/#expressions + indexes = [ + models.Index( + Lower("title").desc(), "pub_date", name="lower_title_date_idx" + ), + models.Index( + models.F("height") * models.F("weight"), + Round("weight"), + name="calc_idx", + ), + ]