Skip to content
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
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ when you will try to use `QuerySet[MyModel]`, `Manager[MyModel]` or some other D

This happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime.

1. You can go with [`django_stubs_ext`](https://github.com/typeddjango/django-stubs/tree/master/ext) helper, that patches all the types we use as Generic in django.
1. You can go with our [`django_stubs_ext`](https://github.com/typeddjango/django-stubs/tree/master/ext) helper, that patches all the types we use as Generic in django.

Install it:

Expand All @@ -42,7 +42,26 @@ This happens because these Django classes do not support [`__class_getitem__`](h

You can add extra types to patch with `django_stubs_ext.monkeypatch(extra_classes=[YourDesiredType])`

2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for type-checking and as a regular `str` in runtime.
**If you use generic symbols in `django.contrib.auth.forms`**, you will have to do the monkeypatching
manually in your first [`AppConfig.ready`](https://docs.djangoproject.com/en/stable/ref/applications/#django.apps.AppConfig.ready).
This is currently required because `django.contrib.auth.forms` cannot be imported until django is initialized.

```python
import django_stubs_ext
from django.apps import AppConfig

class ClientsConfig(AppConfig):
name = "clients"

def ready(self) -> None:
from django.contrib.auth.forms import SetPasswordMixin, SetUnusablePasswordMixin

# For Django version prior to 5.1, use `extra_classes=[SetPasswordForm, AdminPasswordChangeForm]` instead.
django_stubs_ext.monkeypatch(extra_classes=[SetPasswordMixin, SetUnusablePasswordMixin])
```


2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for `mypy` and as a regular `str` in runtime.


## usage
Expand Down
3 changes: 2 additions & 1 deletion django-stubs/contrib/admin/decorators.pyi
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from collections.abc import Callable, Sequence
from typing import Any, TypeVar, overload
from typing import Any, overload

from django.contrib.admin import ModelAdmin
from django.contrib.admin.sites import AdminSite
from django.db.models.base import Model
from django.db.models.expressions import BaseExpression, Combinable
from django.utils.functional import _StrOrPromise
from typing_extensions import TypeVar

_ModelAdmin = TypeVar("_ModelAdmin", bound=ModelAdmin[Any])
_F = TypeVar("_F", bound=Callable[..., Any])
Expand Down
6 changes: 3 additions & 3 deletions django-stubs/contrib/admin/forms.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, _UserType

class AdminAuthenticationForm(AuthenticationForm):
class AdminAuthenticationForm(AuthenticationForm[_UserType]):
required_css_class: str = ...

class AdminPasswordChangeForm(PasswordChangeForm):
class AdminPasswordChangeForm(PasswordChangeForm[_UserType]):
required_css_class: str = ...
4 changes: 2 additions & 2 deletions django-stubs/contrib/admin/helpers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from django import forms
from django.db.models.fields import AutoField
from django.forms.boundfield import BoundField
from django.forms.forms import BaseForm
from django.forms.utils import ErrorDict
from django.forms.utils import ErrorDict, ErrorList
from django.forms.widgets import Media, Widget
from django.utils.safestring import SafeText

Expand Down Expand Up @@ -157,5 +157,5 @@ class InlineFieldset(Fieldset):
formset: Any = ...
def __init__(self, formset: Any, *args: Any, **kwargs: Any) -> None: ...

class AdminErrorList(forms.utils.ErrorList):
class AdminErrorList(ErrorList):
def __init__(self, form: Any, inline_formsets: Any) -> None: ...
427 changes: 229 additions & 198 deletions django-stubs/contrib/admin/options.pyi

Large diffs are not rendered by default.

164 changes: 116 additions & 48 deletions django-stubs/contrib/auth/forms.pyi
Original file line number Diff line number Diff line change
@@ -1,49 +1,113 @@
from collections.abc import Iterator
from typing import Any
from collections.abc import Iterable
from logging import Logger
from typing import Any, ClassVar, Generic, TypeVar

from django import forms
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.exceptions import ValidationError
from django.core.handlers.wsgi import WSGIRequest
from django.db import models
from django.db.models.fields import _ErrorMessagesDict
from django.forms.widgets import Widget
from django.http.request import HttpRequest
from django.utils.functional import _StrOrPromise, cached_property
from typing_extensions import override

logger: Logger
UserModel: Any

_UserType = TypeVar("_UserType", bound=AbstractBaseUser)

class ReadOnlyPasswordHashWidget(forms.Widget):
template_name: str = ...
template_name: str
@override
def get_context(self, name: str, value: Any, attrs: dict[str, Any] | None) -> dict[str, Any]: ...

class ReadOnlyPasswordHashField(forms.Field):
widget: Any
def __init__(self, *args: Any, **kwargs: Any) -> None: ...

class UsernameField(forms.CharField): ...
class UsernameField(forms.CharField):
@override
def to_python(self, value: Any | None) -> Any | None: ...
@override
def widget_attrs(self, widget: Widget) -> dict[str, Any]: ...

class SetPasswordMixin(Generic[_UserType]):
error_messages: _ErrorMessagesDict

@staticmethod
def create_password_fields(
label1: _StrOrPromise = ..., label2: _StrOrPromise = ...
) -> tuple[forms.CharField, forms.CharField]: ...
def validate_passwords(
self,
password1_field_name: str = ...,
password2_field_name: str = ...,
) -> None: ...
def validate_password_for_user(self, user: _UserType, password_field_name: str = "password2") -> None: ...
def set_password_and_save(
self, user: _UserType, password_field_name: str = "password1", commit: bool = True
) -> _UserType: ...
def __class_getitem__(cls, *args: Any, **kwargs: Any) -> Any: ...

class SetUnusablePasswordMixin(Generic[_UserType]):
usable_password_help_text: _StrOrPromise

@staticmethod
def create_usable_password_field(help_text: _StrOrPromise = ...) -> forms.ChoiceField: ...
def validate_passwords(
self,
password1_field_name: str = ...,
password2_field_name: str = ...,
usable_password_field_name: str = ...,
) -> None: ...
def validate_password_for_user(self, user: _UserType, **kwargs: Any) -> None: ...
def set_password_and_save(self, user: _UserType, commit: bool = True, **kwargs: Any) -> _UserType: ...

class BaseUserCreationForm(forms.ModelForm, Generic[_UserType]):
error_messages: _ErrorMessagesDict
password1: forms.Field
password2: forms.Field

class Meta:
model: ClassVar[type[Any]]
fields: ClassVar[tuple[str, ...]]
field_classes: ClassVar[dict[str, type[forms.Field]]]

class UserCreationForm(forms.ModelForm):
error_messages: Any = ...
password1: Any = ...
password2: Any = ...
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def clean_password2(self) -> str: ...
@override
def save(self, commit: bool = ...) -> _UserType: ...

class UserCreationForm(BaseUserCreationForm[_UserType]):
def clean_username(self) -> str: ...

class UserChangeForm(forms.ModelForm):
password: Any = ...
password: forms.Field

class Meta:
model: ClassVar[type[Any]]
fields: ClassVar[str]
field_classes: ClassVar[dict[str, type[forms.Field]]]

def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def clean_password(self) -> str: ...

class AuthenticationForm(forms.Form):
username: Any = ...
password: Any = ...
error_messages: Any = ...
request: WSGIRequest = ...
user_cache: AbstractBaseUser | None = ...
username_field: Any = ...
def __init__(self, request: Any = ..., *args: Any, **kwargs: Any) -> None: ...
def confirm_login_allowed(self, user: AbstractBaseUser) -> None: ...
def get_user(self) -> User: ...

class AuthenticationForm(forms.Form, Generic[_UserType]):
username: forms.Field
password: forms.Field
error_messages: _ErrorMessagesDict
request: HttpRequest | None
user_cache: _UserType | None
username_field: models.Field[Any, Any]
def __init__(self, request: HttpRequest | None = ..., *args: Any, **kwargs: Any) -> None: ...
def confirm_login_allowed(self, user: _UserType) -> None: ...
def get_user(self) -> _UserType: ...
def get_invalid_login_error(self) -> ValidationError: ...
@override
def clean(self) -> dict[str, Any]: ...

class PasswordResetForm(forms.Form):
email: Any = ...
class PasswordResetForm(forms.Form, Generic[_UserType]):
email: forms.Field
def send_mail(
self,
subject_template_name: str,
Expand All @@ -53,7 +117,7 @@ class PasswordResetForm(forms.Form):
to_email: str,
html_email_template_name: str | None = ...,
) -> None: ...
def get_users(self, email: str) -> Iterator[Any]: ...
def get_users(self, email: str) -> Iterable[_UserType]: ...
def save(
self,
domain_override: str | None = ...,
Expand All @@ -62,30 +126,34 @@ class PasswordResetForm(forms.Form):
use_https: bool = ...,
token_generator: PasswordResetTokenGenerator = ...,
from_email: str | None = ...,
request: WSGIRequest | None = ...,
request: HttpRequest | None = ...,
html_email_template_name: str | None = ...,
extra_email_context: dict[str, str] | None = ...,
) -> None: ...

class SetPasswordForm(forms.Form):
error_messages: Any = ...
new_password1: Any = ...
new_password2: Any = ...
user: User = ...
def __init__(self, user: AbstractBaseUser | None, *args: Any, **kwargs: Any) -> None: ...
def clean_new_password2(self) -> str: ...
def save(self, commit: bool = ...) -> AbstractBaseUser: ...

class PasswordChangeForm(SetPasswordForm):
old_password: Any = ...
class SetPasswordForm(SetPasswordMixin[_UserType], forms.Form, Generic[_UserType]):
new_password1: forms.Field
new_password2: forms.Field
user: _UserType
def __init__(self, user: _UserType, *args: Any, **kwargs: Any) -> None: ...
def save(self, commit: bool = ...) -> _UserType: ...

class PasswordChangeForm(SetPasswordForm[_UserType]):
old_password: forms.Field
def clean_old_password(self) -> str: ...

class AdminPasswordChangeForm(forms.Form):
error_messages: Any = ...
required_css_class: str = ...
password1: Any = ...
password2: Any = ...
user: User = ...
def __init__(self, user: AbstractBaseUser, *args: Any, **kwargs: Any) -> None: ...
def clean_password2(self) -> str: ...
def save(self, commit: bool = ...) -> AbstractBaseUser: ...
class AdminPasswordChangeForm(forms.Form, Generic[_UserType]):
error_messages: _ErrorMessagesDict
required_css_class: str
usable_password_help_text: str
password1: forms.Field
password2: forms.Field
user: _UserType
def __init__(self, user: _UserType, *args: Any, **kwargs: Any) -> None: ...
def save(self, commit: bool = ...) -> _UserType: ...
@cached_property
@override
def changed_data(self) -> list[str]: ...

class AdminUserCreationForm(SetUnusablePasswordMixin[_UserType], UserCreationForm[_UserType]):
usable_password: forms.ChoiceField = ...
4 changes: 2 additions & 2 deletions django-stubs/contrib/auth/views.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import AuthenticationForm, _UserType
from django.http.request import HttpRequest
from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse
Expand All @@ -15,7 +15,7 @@ class SuccessURLAllowedHostsMixin:
success_url_allowed_hosts: Any = ...
def get_success_url_allowed_hosts(self) -> set[str]: ...

class LoginView(SuccessURLAllowedHostsMixin, FormView[AuthenticationForm]):
class LoginView(SuccessURLAllowedHostsMixin, FormView[AuthenticationForm[_UserType]]):
authentication_form: Any = ...
redirect_field_name: Any = ...
redirect_authenticated_user: bool = ...
Expand Down
17 changes: 12 additions & 5 deletions django-stubs/contrib/contenttypes/admin.pyi
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from typing import Any

from django.contrib.admin.checks import InlineModelAdminChecks
from django.contrib.admin.options import InlineModelAdmin
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
from django.db.models.base import Model

class GenericInlineModelAdminChecks:
class GenericInlineModelAdminChecks(InlineModelAdminChecks):
def _check_exclude_of_parent_model(self, obj: GenericInlineModelAdmin, parent_model: type[Model]) -> list[Any]: ...
def _check_relation(self, obj: GenericInlineModelAdmin, parent_model: type[Model]) -> list[Any]: ...

class GenericInlineModelAdmin(InlineModelAdmin[Any]):
template: str = ...
class GenericInlineModelAdmin(InlineModelAdmin):
ct_field: str
ct_fk_field: str
formset: type[BaseGenericInlineFormSet] # type: ignore[assignment]

class GenericStackedInline(GenericInlineModelAdmin): ...
class GenericTabularInline(GenericInlineModelAdmin): ...
class GenericStackedInline(GenericInlineModelAdmin):
template: str

class GenericTabularInline(GenericInlineModelAdmin):
template: str
17 changes: 9 additions & 8 deletions django-stubs/contrib/flatpages/admin.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Any
from typing import Any, ClassVar

import django.contrib.admin as admin
from django.contrib import admin
from django.contrib.flatpages.models import FlatPage

class FlatPageAdmin(admin.ModelAdmin[Any]):
form: Any = ...
fieldsets: Any = ...
list_display: Any = ...
list_filter: Any = ...
search_fields: Any = ...
class FlatPageAdmin(admin.ModelAdmin[FlatPage]):
form: Any
fieldsets: ClassVar[Any]
list_display: Any
list_filter: ClassVar[Any]
search_fields: ClassVar[Any]
4 changes: 1 addition & 3 deletions django-stubs/contrib/flatpages/sitemaps.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Any

from django.contrib.sitemaps import Sitemap

class FlatPageSitemap(Sitemap[Any]): ...
class FlatPageSitemap(Sitemap): ...
7 changes: 4 additions & 3 deletions django-stubs/contrib/gis/db/models/fields.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from collections.abc import Iterable
from typing import Any, NamedTuple, TypeVar

from django.db.models.fields import Field, _ErrorMessagesToOverride, _FieldChoices, _ValidatorCallable
from django.core.validators import _ValidatorCallable
from django.db.models.fields import Field, _ErrorMessagesMapping, _FieldChoices
from django.utils.functional import _StrOrPromise

# __set__ value type
Expand Down Expand Up @@ -42,7 +43,7 @@ class BaseSpatialField(Field[_ST, _GT]):
db_column: str | None = ...,
db_tablespace: str | None = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: _ErrorMessagesToOverride | None = ...,
error_messages: _ErrorMessagesMapping | None = ...,
) -> None: ...
def deconstruct(self) -> Any: ...
def db_type(self, connection: Any) -> Any: ...
Expand Down Expand Up @@ -91,7 +92,7 @@ class GeometryField(BaseSpatialField[Any, Any]):
db_column: str | None = ...,
db_tablespace: str | None = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: _ErrorMessagesToOverride | None = ...,
error_messages: _ErrorMessagesMapping | None = ...,
) -> None: ...
def deconstruct(self) -> Any: ...
def formfield(self, **kwargs: Any) -> Any: ...
Expand Down
1 change: 0 additions & 1 deletion django-stubs/contrib/gis/forms/fields.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import django.forms as forms
class GeometryField(forms.Field):
widget: Any = ...
geom_type: str = ...
default_error_messages: Any = ...
srid: Any = ...
def __init__(self, *, srid: Any | None = ..., geom_type: Any | None = ..., **kwargs: Any) -> None: ...
def to_python(self, value: Any) -> Any: ...
Expand Down
2 changes: 1 addition & 1 deletion django-stubs/contrib/gis/sitemaps/kml.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from typing import Any
from django.contrib.sitemaps import Sitemap
from typing_extensions import override

class KMLSitemap(Sitemap[Any]):
class KMLSitemap(Sitemap):
geo_format: str
locations: Any
def __init__(self, locations: Any | None = ...) -> None: ...
Expand Down
Loading
Loading