Skip to content

Commit eb70238

Browse files
authored
Improves edit.py and its forms (#648)
* Improves edit.py and its forms * Adds tests
1 parent acfe0ce commit eb70238

File tree

5 files changed

+55
-18
lines changed

5 files changed

+55
-18
lines changed

dev-requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
wheel
2+
black==21.6b0
23
requests==2.24.0
34
coreapi==2.3.3
45
gitpython==3.1.9
56
pre-commit==2.7.1
67
pytest==6.1.1
7-
pytest-mypy-plugins==1.6.1
8+
pytest-mypy-plugins==1.7.0
89
psycopg2-binary
910
types-toml==0.1.1
1011
-e ./django_stubs_ext

django-stubs/contrib/postgres/indexes.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ from django.db.models.query_utils import Q
55
from django.db.models import Index, Func
66
from django.db.models.expressions import BaseExpression, Combinable
77

8-
98
class PostgresIndex(Index): ...
109

1110
class BrinIndex(PostgresIndex):

django-stubs/forms/models.pyi

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from typing import (
55
Collection,
66
Dict,
77
Iterator,
8+
Generic,
89
List,
910
Mapping,
1011
MutableMapping,
@@ -78,8 +79,8 @@ class ModelFormOptions:
7879

7980
class ModelFormMetaclass(DeclarativeFieldsMetaclass): ...
8081

81-
class BaseModelForm(BaseForm):
82-
instance: Any = ...
82+
class BaseModelForm(Generic[_M], BaseForm):
83+
instance: _M
8384
def __init__(
8485
self,
8586
data: Optional[Mapping[str, Any]] = ...,
@@ -95,10 +96,10 @@ class BaseModelForm(BaseForm):
9596
renderer: Any = ...,
9697
) -> None: ...
9798
def validate_unique(self) -> None: ...
98-
save_m2m: Any = ...
99-
def save(self, commit: bool = ...) -> Any: ...
99+
def save(self, commit: bool = ...) -> _M: ...
100+
def save_m2m(self) -> None: ...
100101

101-
class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
102+
class ModelForm(BaseModelForm[_M], metaclass=ModelFormMetaclass):
102103
base_fields: ClassVar[Dict[str, Field]] = ...
103104

104105
def modelform_factory(

django-stubs/views/generic/edit.pyi

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ from django.forms.models import BaseModelForm
55
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
66
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
77
from typing_extensions import Literal
8+
from django.db import models
89

910
from django.http import HttpRequest, HttpResponse
1011

1112
_FormT = TypeVar("_FormT", bound=BaseForm)
13+
_ModelFormT = TypeVar("_ModelFormT", bound=BaseModelForm)
14+
_T = TypeVar("_T", bound=models.Model)
1215

13-
class AbstractFormMixin(ContextMixin):
16+
class AbstractFormMixin(Generic[_FormT], ContextMixin):
1417
initial: Dict[str, Any] = ...
15-
form_class: Optional[Type[BaseForm]] = ...
18+
form_class: Optional[Type[_FormT]] = ...
1619
success_url: Optional[Union[str, Callable[..., Any]]] = ...
1720
prefix: Optional[str] = ...
1821
def get_initial(self) -> Dict[str, Any]: ...
@@ -26,12 +29,12 @@ class FormMixin(Generic[_FormT], AbstractFormMixin):
2629
def form_valid(self, form: _FormT) -> HttpResponse: ...
2730
def form_invalid(self, form: _FormT) -> HttpResponse: ...
2831

29-
class ModelFormMixin(AbstractFormMixin, SingleObjectMixin):
32+
class ModelFormMixin(Generic[_T, _ModelFormT], AbstractFormMixin, SingleObjectMixin[_T]):
3033
fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ...
31-
def get_form_class(self) -> Type[BaseModelForm]: ...
32-
def get_form(self, form_class: Optional[Type[BaseModelForm]] = ...) -> BaseModelForm: ...
33-
def form_valid(self, form: BaseModelForm) -> HttpResponse: ...
34-
def form_invalid(self, form: BaseModelForm) -> HttpResponse: ...
34+
def get_form_class(self) -> Type[_ModelFormT]: ...
35+
def get_form(self, form_class: Optional[Type[_ModelFormT]] = ...) -> BaseModelForm: ...
36+
def form_valid(self, form: _ModelFormT) -> HttpResponse: ...
37+
def form_invalid(self, form: _ModelFormT) -> HttpResponse: ...
3538

3639
class ProcessFormView(View):
3740
def get(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ...
@@ -40,10 +43,10 @@ class ProcessFormView(View):
4043

4144
class BaseFormView(FormMixin[_FormT], ProcessFormView): ...
4245
class FormView(TemplateResponseMixin, BaseFormView[_FormT]): ...
43-
class BaseCreateView(ModelFormMixin, ProcessFormView): ...
44-
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): ...
45-
class BaseUpdateView(ModelFormMixin, ProcessFormView): ...
46-
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView): ...
46+
class BaseCreateView(ModelFormMixin[_T, _ModelFormT], ProcessFormView): ...
47+
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView[_T, _ModelFormT]): ...
48+
class BaseUpdateView(ModelFormMixin[_T, _ModelFormT], ProcessFormView): ...
49+
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView[_T, _ModelFormT]): ...
4750

4851
class DeletionMixin:
4952
success_url: Optional[str] = ...

tests/typecheck/views/generic/test_edit.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"""Ensure that form can have type AuthenticationForm."""
1111
form.get_user()
1212
return HttpResponseRedirect(self.get_success_url())
13+
14+
1315
- case: dispatch_http_response
1416
main: |
1517
from django.http import HttpResponse
@@ -19,6 +21,8 @@
1921
def dispatch(self, request, *args, **kwargs) -> HttpResponse:
2022
response: HttpResponse
2123
return response
24+
25+
2226
- case: dispatch_streaming_http_response
2327
main: |
2428
from django.http import StreamingHttpResponse
@@ -28,3 +32,32 @@
2832
def dispatch(self, request, *args, **kwargs) -> StreamingHttpResponse:
2933
response: StreamingHttpResponse
3034
return response
35+
36+
37+
- case: generic_form_views
38+
main: |
39+
from django.views.generic.edit import CreateView, UpdateView
40+
from django import forms
41+
from myapp.models import Article
42+
43+
class ArticleModelForm(forms.ModelForm[Article]):
44+
class Meta:
45+
model = Article
46+
47+
class MyCreateView(CreateView[Article, ArticleModelForm]):
48+
def some(self) -> None:
49+
reveal_type(self.get_form_class()) # N: Revealed type is "Type[main.ArticleModelForm*]"
50+
51+
class MyUpdateView(UpdateView[Article, ArticleModelForm]):
52+
def some(self) -> None:
53+
reveal_type(self.get_form_class()) # N: Revealed type is "Type[main.ArticleModelForm*]"
54+
installed_apps:
55+
- myapp
56+
files:
57+
- path: myapp/__init__.py
58+
- path: myapp/models.py
59+
content: |
60+
from django.db import models
61+
62+
class Article(models.Model):
63+
pass

0 commit comments

Comments
 (0)