56 Commits

Author SHA1 Message Date
sobolevn
645ee97e78 Release 1.6.0 2020-10-01 10:57:05 +03:00
William Schwartz
5bc3759ea2 Template Loader.get_template_sources yield Origins (#476)
django.template.loaders.base.Loader.get_template_sources should yield Origins:
https://docs.djangoproject.com/en/3.1/ref/templates/api/#django.template.loaders.base.Loader.get_template_sources

Currently it's returning none. Objects that yield should be marked as iterables.
https://mypy.readthedocs.io/en/stable/kinds_of_types.html#generators
2020-09-30 01:21:38 +03:00
henribru
534a028ea2 Use AbstractBaseUser where possible (#475)
Fixes #431
2020-09-29 22:04:20 +03:00
Nicolas Delaby
87856754ea psycopg2-binary is faster to install (#473) 2020-09-29 12:50:11 +03:00
Szymon Pyżalski
2f7fac2eaf Allowed to use decimal in combinables (#454)
Co-authored-by: Szymon Pyżalski <spyzalski@egnyte.com>
2020-09-21 15:00:32 +03:00
Marti Raudsepp
5ff99fd047 Check arguments to django.urls.{path,re_path} functions (#464)
These functions were added in Django 2.0, but the stubs have been
incomplete since commit 9a68263257

The implementation is almost identical to `url()` from
`conf/urls/__init__.pyi`
2020-09-17 18:08:26 +03:00
Marti Raudsepp
f77ebcd22c Add db.models.constants stub module (#462) 2020-09-17 18:07:03 +03:00
Alexander Viklund
34b126e3da Fix wrong type for parse_until in template parser (#460) 2020-09-12 01:58:09 +03:00
Jonathan Moss
6e5f5f2cdb Allows FileField storage to be a Callable (#453)
* Allows FileField storage to be a Callable

The `storage` parameter of `FileField` (and by extension `ImageField`)
is not limited to just taking an instance of `Storage`. It can also take
a no-args callable that returns an instance of `Storage`.

* correcting linting issue in forms.pyi
2020-08-28 10:14:57 +03:00
James Perretta
95252cde60 Add "one_to_many", "one_to_one", "many_to_many," and "many_to_one" attributes to Field base class (#449)
* Add "_to_many" and "_to_one" bool attributes to Field base class.

* Remove unused import from test case.
2020-08-24 23:09:15 +03:00
Zach Waggoner
6ef2cf0331 Add force_color parameter to django.core.management.color.color_style (#448) 2020-08-21 12:41:51 +03:00
Alexander Viklund
9f3b95841b return type for translation.get_language should not be Optional (#446) 2020-08-18 12:41:52 +03:00
Federico Bond
e764b1cf4c Add route argument to ResolverMatch constructor (#442) 2020-08-12 15:36:01 +03:00
Šarūnas Nejus
8a64d87917 Output a more clear configuration error (#421)
* Output a more clear configuration error

* Performance related improvements in config read handling

* Check python 3.6 with travis

* Revert the .travis.yml py36 inclusion

* Added tests for input error handling

* Thanks isort, isorted it out

* Make exit() function a bit more aesthetic

* Single quote -> double quote docstrings

* Whitespace removed
2020-08-09 11:27:21 +03:00
Daniel Hillier
60f3f9dd9f Change select_for_update's 'of' type to Sequence[str] (#440) 2020-08-08 20:21:55 +03:00
Mantas Zimnickas
ca10ee9242 Fix staff_member_required annotations (#436)
* Fix staff_member_required annotations

`redirect_field_name` can be `None`.

* Reformat code with black
2020-07-31 10:35:35 +03:00
Sigurd Ljødal
f651f27ddf Fix type of update_fields parameter to Model.save (#437)
Sending in a single string leads to unexpected behavior and the method
accepts any iterable, not just sequences, of strings.

Technically sending in a single string is still allowed because a string
is an iterable of strings, but this way the intention of that argument
is clearer.
2020-07-31 10:14:35 +03:00
Kacper
3915aa0639 AttributeError exception in build_unannotated_method_args (#429)
* AttributeError exception in build_unannotated_method_args

* assigning [] to arguments if AttributeError

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-07-21 15:31:19 +03:00
Nate Clark
97ec2ee43b Allow Field.widget to be a class or instance (#426)
According to source below, a Field's widget is allowed to be a class or instance of Widget.

156a2138db/django/forms/fields.py (L63-L66)
2020-07-16 00:51:29 +03:00
Daniel Hillier
19c73a106d admin: Allow ModelAdmin.actions to contain str (#425)
Strings are allowed in ModelAdmin.actions when they refer to a method on
the ModelAdmin subclass.
2020-07-14 10:43:50 +03:00
Kacper
92ef5d9d95 additional defer() to offset the effect of using the type before decl… (#424)
* additional defer() to offset the effect of using the type before declaring

* linter fix

* linter fix

* linter fix

* test case

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-07-13 16:31:13 +03:00
Daniel Hillier
f16d1b8cb6 Improve *HttpResponse.getvalue() types (#422)
* Change returned type of StreamingHttpResponse.getvalue() to bytes

* Add HttpResponse.getvalue() definition
2020-07-11 15:57:35 +03:00
Alexander Viklund
c3cdc1c2d5 Allow more types for args to Paginator.get_page (#418) 2020-07-08 22:53:38 +03:00
Alexander Viklund
3704d0ab98 Don't change type of HttpRequest.user if type has been changed by subclassing (#415)
* Don't change type of HttpRequest.user if type has been changed by subclassing

* Asserts for typing

* Add tests

* Add description of HttpRequest subclassing to README

* Dummy to rebuild travis
2020-07-07 12:52:21 +03:00
Kacper
b1d619edb2 Bumped required Mypy version to newest in requirements (#414)
* pytest-mypy-plugins package newer version

* Revert "pytest-mypy-plugins package newer version"

This reverts commit 871347a86577a5dad867bc751689bbc06d2bcae0.

* update tests ignores for django

* Revert "update tests ignores for django"

This reverts commit 93fc66e311af62cf8cf5b79a72ab723bf3cf060a.

* changed required mypy version to the newest

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-07-06 10:34:17 +03:00
Akseli Nelander
e680326c72 Fix partition signature (#413) 2020-07-01 15:35:35 +03:00
Étienne
574a87e68c explicitly mark value and label types on choices enum (#406)
In addition to the meta properties added
to the Choices enums, value and label are
set for each choice.
This commit explicitly types those instead of the
tuple value and non-extant label types
2020-06-22 11:08:09 +03:00
coiax
82ae1751ed 🐑 Update update_session_auth_hash() stub (#401)
The update_session_auth_hash() stub doesn't match the documented and
actual type that the function expects.

---

Resolves #400.
2020-06-16 19:19:11 +03:00
Ceesjan Luiten
69042783b1 Allow form.save() in CreateView / UpdateView (#374) 2020-06-12 21:12:56 +03:00
Kacper
391bbc59d5 WIP Fix ci/cd (#396)
* pytest-mypy-plugins package newer version

* Revert "pytest-mypy-plugins package newer version"

This reverts commit 871347a86577a5dad867bc751689bbc06d2bcae0.

* update tests ignores for django

* Revert "update tests ignores for django"

This reverts commit 93fc66e311af62cf8cf5b79a72ab723bf3cf060a.

* lets see what happen :)

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-11 23:05:59 +03:00
Michael
28c76df3b2 Fix compatibility table in readme (#395) 2020-06-10 16:03:00 +03:00
Nikita Sobolev
3d2534ea8d Closes #392 2020-06-08 10:55:30 +03:00
Kacper
54f5f63e71 Issue 379 (#391)
* pytest-mypy-plugins package newer version

* Revert "pytest-mypy-plugins package newer version"

This reverts commit 871347a86577a5dad867bc751689bbc06d2bcae0.

* update tests ignores for django

* Revert "update tests ignores for django"

This reverts commit 93fc66e311af62cf8cf5b79a72ab723bf3cf060a.

* tests for python 3.8 WIP - initial commit

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 14:28:28 +03:00
Kacper
4c5723d368 WIP Issue 388 (#390)
* updated mypy dependency

* update readme

* readme update v2

* pytest-mypy-plugins newer version

* updated pytest_mypy_plugins name

* update ignored errors for typechecking django test suite

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-06 11:35:51 +03:00
Alexander Viklund
7e0e43135d BaseForm.prefix may be None (#389) 2020-06-04 21:24:42 +03:00
Kacper
e05b84e32d Issue 378 (#387)
* proper redirect return type annotations made with Literal

* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Add __init__ to OrderedSet (#381)

Issue 382 (#384)

* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

Fix parameter types for assertJSONEqual/NotEqual (#385)

Add get_supported_language_variant (#386)

Issue 309 (#383)

* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>

* fix formatting and unused import

* reformatted again

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 23:46:30 +03:00
Alexander Viklund
71751d3795 Issue 309 (#383)
* added tags for user models

* type test for HttpRequest.user

* test for User and AnonymousUser tags

* httrequest test fix

* checking python version fix for readibility

* Rewrite version check for readability

* Annotate is_authenticated/is_anonymous with Literal-type

* Add auth in INSTALLED_APPS in test

* Fix wrong type assertion in test

* Fix misconception of how branch-testing works

* Remove user from WSGIRequest

* Change HttpRequest-transformer to set user-type to include AnonymousUser

* Add check for anonymous_user_info=None to appease mypy

* Isort transformers/request

* Remove trailing whitespace

* Remove unused import

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 20:29:19 +03:00
Alexander Viklund
25f92e8e56 Add get_supported_language_variant (#386) 2020-06-03 20:04:19 +03:00
Alexander Viklund
28d47c7e93 Fix parameter types for assertJSONEqual/NotEqual (#385) 2020-06-03 20:03:59 +03:00
Kacper
197cb4058e Issue 382 (#384)
* WIP fix, pushed for testing

* added _ prefix for internal types

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-03 12:58:03 +03:00
Alexander Viklund
dac2b31fb2 Add __init__ to OrderedSet (#381) 2020-06-02 13:56:11 +03:00
Kacper
8d2600136a Issue 355 (#376)
* Mapping instead of Dict type annotation for context in render() with test

* removed Union and Context

* typo

Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-06-01 19:41:57 +03:00
Pavel Savchenko
570772f973 Revert "Allow template render to string helper functions to accept Context (#372)" (#377)
This reverts commit 64cbb0f70e.
2020-06-01 14:34:21 +03:00
Anton Agestam
d5c1bfb12a Increase accuracy of ModelAdmin types (#375)
* Increase accuracy of ModelAdmin types

* Add comment for regression test
2020-05-30 00:24:47 +03:00
Pavel Savchenko
64cbb0f70e Allow template render to string helper functions to accept Context (#372)
* Add failing test case for render_to_string

* Allow Context objects in template render functions
2020-05-23 13:47:39 +03:00
Ville Skyttä
6f5a39625e Add release notes project URL (#365)
Background info at https://github.com/pypa/warehouse/pull/7882
2020-05-05 18:38:11 +03:00
Anthony Ricaud
bf604a0398 Add BaseForm._html_output (#364) 2020-04-30 14:36:55 +03:00
Ashish Bansal (mrphantom)
92c8dfc93f Fix wrong BaseCache typings (#363)
django.core.cache.backends.base.BaseCache were invalid. This commit
fixes them in accordance with [0].

[0] 447980e72a/django/core/cache/backends/base.py (L108)
2020-04-24 12:44:55 +03:00
Jorgen Phillips
c10c55052c Add add_to_class method to Model (#361)
* Add add_to_class method to Model

* Fix @classmethod decorator
2020-04-24 01:11:16 +03:00
Sergey Tikhonov
96914e466b Annotate AdminForm.__init__ (#359) 2020-04-16 18:22:21 +03:00
Onyeka Aghanenu
90ed7f332d Fix Modelform auto_id to accept Boolean type. (#356) 2020-04-13 19:04:18 +03:00
Kacper
a801501151 stub for _build_app_dict (#351)
Co-authored-by: Kacper Szmigiel <szmigielkacper@gmai.com>
2020-04-04 20:38:42 +03:00
Sid Mitra
8ea59985df Add RESTRICT and RestrictedError to django.db.models.deletion (#345)
* Add RESTRICT and RestrictedError to django.db.models.deletion

* Add black fomatting changes
2020-04-03 17:02:34 +03:00
Ceesjan Luiten
2964ed53d7 Improve type of BaseForms.files (#350)
This is valid Django, which failed to pass with the old type-defintion:

    class MyForm(Form):
      myparam = MultiFileField(...)

      self.clean(self, *args, **kwargs):
        self.files.getlist('myparam', [])
2020-03-31 10:16:02 +03:00
Stevan Milic
1b9176f994 Add state attribute to Model (#347) (#348)
Add from_db method to Model

Co-authored-by: Stevan Milic <stevan.milic@tradecore.com>
2020-03-30 10:35:40 +03:00
Nikita Sobolev
54d0d018c6 Minor type improvements after linting (#343) 2020-03-19 18:04:51 +03:00
60 changed files with 737 additions and 180 deletions

View File

@@ -4,10 +4,19 @@ dist: xenial
sudo: required
jobs:
include:
- name: Run plugin test suite with python 3.8
python: 3.8
script: 'pytest'
- name: Run plugin test suite with python 3.7
python: 3.7
script: 'pytest'
- name: Typecheck Django 3.0 test suite with python 3.8
python: 3.8
script: |
python ./scripts/typecheck_tests.py --django_version=3.0
- name: Typecheck Django 3.0 test suite with python 3.7
python: 3.7
script: |

View File

@@ -47,7 +47,10 @@ We rely on different `django` and `mypy` versions:
| django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- |
| 1.3.0 | 0.750 | 2.2.x | ^3.6
| 1.6.0 | 0.780 | 2.2.x \|\| 3.x | ^3.6
| 1.5.0 | 0.770 | 2.2.x \|\| 3.x | ^3.6
| 1.4.0 | 0.760 | 2.2.x \|\| 3.x | ^3.6
| 1.3.0 | 0.750 | 2.2.x \|\| 3.x | ^3.6
| 1.2.0 | 0.730 | 2.2.x | ^3.6
| 1.1.0 | 0.720 | 2.2.x | ^3.6
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6
@@ -87,19 +90,24 @@ You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, thi
Currently we [are working](https://github.com/django/django/pull/12405) on providing `__class_getitem__` to the classes where we need them.
### How can I use HttpRequest with custom user model?
### How can I create a HttpRequest that's guaranteed to have an authenticated user?
You can subclass standard request like so:
Django's built in `HttpRequest` has the attribute `user` that resolves to the type
```python
Union[User, AnonymousUser]
```
where `User` is the user model specified by the `AUTH_USER_MODEL` setting.
If you want a `HttpRequest` that you can type-annotate with where you know that the user is authenticated you can subclass the normal `HttpRequest` class like so:
```python
from django.http import HttpRequest
from my_user_app.models import MyUser
class MyRequest(HttpRequest):
class AuthenticatedHttpRequest(HttpRequest):
user: MyUser
```
And then use `MyRequest` instead of standard `HttpRequest` inside your project.
And then use `AuthenticatedHttpRequest` instead of the standard `HttpRequest` for when you know that the user is authenticated. For example in views using the `@login_required` decorator.
## Related projects

View File

@@ -1,6 +1,6 @@
black
pytest-mypy-plugins==1.2.0
psycopg2
pytest-mypy-plugins==1.3.0
psycopg2-binary
flake8==3.7.9
flake8-pyi==19.3.0
isort==4.3.21

View File

@@ -1,7 +1,7 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, Iterable
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.forms.boundfield import BoundField
from django.forms.forms import BaseForm
from django.forms.utils import ErrorDict
from django.forms.widgets import Media, Widget
from django.utils.safestring import SafeText
@@ -23,7 +23,7 @@ class AdminForm:
readonly_fields: Any = ...
def __init__(
self,
form: AdminPasswordChangeForm,
form: BaseForm,
fieldsets: List[Tuple[None, Dict[str, List[str]]]],
prepopulated_fields: Dict[Any, Any],
readonly_fields: Optional[Iterable[Any]] = ...,

View File

@@ -1,5 +1,9 @@
from collections import OrderedDict
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union, Mapping, TypeVar
from django.forms.forms import BaseForm
from django.forms.formsets import BaseFormSet
from typing_extensions import Literal, TypedDict
from django.contrib.admin.filters import ListFilter
from django.contrib.admin.models import LogEntry
@@ -26,8 +30,10 @@ from django.db.models.fields import Field
IS_POPUP_VAR: str
TO_FIELD_VAR: str
HORIZONTAL: Any
VERTICAL: Any
HORIZONTAL: Literal[1] = ...
VERTICAL: Literal[2] = ...
_Direction = Union[Literal[1], Literal[2]]
def get_content_type_for_model(obj: Union[Type[Model], Model]) -> ContentType: ...
def get_ul_class(radio_style: int) -> str: ...
@@ -37,21 +43,35 @@ class IncorrectLookupParameters(Exception): ...
FORMFIELD_FOR_DBFIELD_DEFAULTS: Any
csrf_protect_m: Any
class _OptionalFieldOpts(TypedDict, total=False):
classes: Sequence[str]
description: str
class _FieldOpts(_OptionalFieldOpts, total=True):
fields: Sequence[Union[str, Sequence[str]]]
# Workaround for mypy issue, a Sequence type should be preferred here.
# https://github.com/python/mypy/issues/8921
# _FieldsetSpec = Sequence[Tuple[Optional[str], _FieldOpts]]
_T = TypeVar("_T")
_ListOrTuple = Union[Tuple[_T, ...], List[_T]]
_FieldsetSpec = _ListOrTuple[Tuple[Optional[str], _FieldOpts]]
class BaseModelAdmin:
autocomplete_fields: Any = ...
raw_id_fields: Any = ...
fields: Any = ...
exclude: Any = ...
fieldsets: Any = ...
form: Any = ...
filter_vertical: Any = ...
filter_horizontal: Any = ...
radio_fields: Any = ...
prepopulated_fields: Any = ...
formfield_overrides: Any = ...
readonly_fields: Any = ...
ordering: Any = ...
sortable_by: Any = ...
autocomplete_fields: Sequence[str] = ...
raw_id_fields: Sequence[str] = ...
fields: Sequence[Union[str, Sequence[str]]] = ...
exclude: Sequence[str] = ...
fieldsets: _FieldsetSpec = ...
form: Type[BaseForm] = ...
filter_vertical: Sequence[str] = ...
filter_horizontal: Sequence[str] = ...
radio_fields: Mapping[str, _Direction] = ...
prepopulated_fields: Mapping[str, Sequence[str]] = ...
formfield_overrides: Mapping[Type[Field], Mapping[str, Any]] = ...
readonly_fields: Sequence[Union[str, Callable[[Model], Any]]] = ...
ordering: Sequence[str] = ...
sortable_by: Sequence[str] = ...
view_on_site: bool = ...
show_full_result_count: bool = ...
checks_class: Any = ...
@@ -93,7 +113,7 @@ class BaseModelAdmin:
def has_module_permission(self, request: HttpRequest) -> bool: ...
class ModelAdmin(BaseModelAdmin):
list_display: Sequence[Union[str, Callable]] = ...
list_display: Sequence[Union[str, Callable[[Model], Any]]] = ...
list_display_links: Optional[Sequence[Union[str, Callable]]] = ...
list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ...
list_select_related: Union[bool, Sequence[str]] = ...
@@ -101,21 +121,21 @@ class ModelAdmin(BaseModelAdmin):
list_max_show_all: int = ...
list_editable: Sequence[str] = ...
search_fields: Sequence[str] = ...
date_hierarchy: Optional[Any] = ...
date_hierarchy: Optional[str] = ...
save_as: bool = ...
save_as_continue: bool = ...
save_on_top: bool = ...
paginator: Any = ...
paginator: Type = ...
preserve_filters: bool = ...
inlines: Sequence[Type[InlineModelAdmin]] = ...
add_form_template: Any = ...
change_form_template: Any = ...
change_list_template: Any = ...
delete_confirmation_template: Any = ...
delete_selected_confirmation_template: Any = ...
object_history_template: Any = ...
popup_response_template: Any = ...
actions: Any = ...
add_form_template: str = ...
change_form_template: str = ...
change_list_template: str = ...
delete_confirmation_template: str = ...
delete_selected_confirmation_template: str = ...
object_history_template: str = ...
popup_response_template: str = ...
actions: Sequence[Union[Callable[[ModelAdmin, HttpRequest, QuerySet], None], str]] = ...
action_form: Any = ...
actions_on_top: bool = ...
actions_on_bottom: bool = ...
@@ -227,9 +247,9 @@ class ModelAdmin(BaseModelAdmin):
def history_view(self, request: HttpRequest, object_id: str, extra_context: None = ...) -> HttpResponse: ...
class InlineModelAdmin(BaseModelAdmin):
model: Any = ...
fk_name: Any = ...
formset: Any = ...
model: Type[Model] = ...
fk_name: str = ...
formset: BaseFormSet = ...
extra: int = ...
min_num: Optional[int] = ...
max_num: Optional[int] = ...
@@ -238,8 +258,8 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural: Optional[str] = ...
can_delete: bool = ...
show_change_link: bool = ...
classes: Any = ...
admin_site: Any = ...
classes: Optional[Sequence[str]] = ...
admin_site: AdminSite = ...
parent_model: Any = ...
opts: Any = ...
has_registered_model: Any = ...

View File

@@ -63,6 +63,7 @@ class AdminSite:
def i18n_javascript(self, request: WSGIRequest, extra_context: Optional[Dict[Any, Any]] = ...) -> HttpResponse: ...
def logout(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
def login(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> HttpResponse: ...
def _build_app_dict(self, request: WSGIRequest, label: Optional[str] = ...) -> Dict[str, Any]: ...
def get_app_list(self, request: WSGIRequest) -> List[Any]: ...
def index(self, request: WSGIRequest, extra_context: Optional[Dict[str, Any]] = ...) -> TemplateResponse: ...
def app_index(

View File

@@ -1,7 +1,11 @@
from typing import Callable, TypeVar, overload
from typing import Callable, Optional, TypeVar, overload
_C = TypeVar("_C", bound=Callable)
@overload
def staff_member_required(view_func: _C = ..., redirect_field_name: str = ..., login_url: str = ...) -> _C: ...
def staff_member_required(
view_func: _C = ..., redirect_field_name: Optional[str] = ..., login_url: str = ...
) -> _C: ...
@overload
def staff_member_required(view_func: None = ..., redirect_field_name: str = ..., login_url: str = ...) -> Callable: ...
def staff_member_required(
view_func: None = ..., redirect_field_name: Optional[str] = ..., login_url: str = ...
) -> Callable: ...

View File

@@ -2,7 +2,7 @@ from typing import Any, List, Optional, Type, Union
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AbstractUser, AnonymousUser
from django.contrib.auth.models import AnonymousUser
from django.core.handlers.wsgi import WSGIRequest
from django.db.models.base import Model
from django.db.models.options import Options
@@ -29,6 +29,6 @@ def logout(request: HttpRequest) -> None: ...
def get_user_model() -> Type[Model]: ...
def get_user(request: HttpRequest) -> Union[AbstractBaseUser, AnonymousUser]: ...
def get_permission_codename(action: str, opts: Options) -> str: ...
def update_session_auth_hash(request: WSGIRequest, user: AbstractUser) -> None: ...
def update_session_auth_hash(request: HttpRequest, user: AbstractBaseUser) -> None: ...
default_app_config: str

View File

@@ -1,9 +1,15 @@
import sys
from typing import Any, Optional, Tuple, List, overload, TypeVar
from django.db.models.base import Model
from django.db import models
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_T = TypeVar("_T", bound=Model)
class BaseUserManager(models.Manager[_T]):
@@ -20,9 +26,9 @@ class AbstractBaseUser(models.Model):
def get_username(self) -> str: ...
def natural_key(self) -> Tuple[str]: ...
@property
def is_anonymous(self) -> bool: ...
def is_anonymous(self) -> Literal[False]: ...
@property
def is_authenticated(self) -> bool: ...
def is_authenticated(self) -> Literal[True]: ...
def set_password(self, raw_password: Optional[str]) -> None: ...
def check_password(self, raw_password: str) -> bool: ...
def set_unusable_password(self) -> None: ...

View File

@@ -1,7 +1,7 @@
from typing import Any, Dict, Iterator, Optional
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AbstractUser, User
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
@@ -86,6 +86,6 @@ class AdminPasswordChangeForm(forms.Form):
password1: Any = ...
password2: Any = ...
user: User = ...
def __init__(self, user: AbstractUser, *args: Any, **kwargs: Any) -> None: ...
def __init__(self, user: AbstractBaseUser, *args: Any, **kwargs: Any) -> None: ...
def clean_password2(self) -> str: ...
def save(self, commit: bool = ...) -> AbstractUser: ...
def save(self, commit: bool = ...) -> AbstractBaseUser: ...

View File

@@ -1,3 +1,4 @@
import sys
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar, Union
from django.contrib.auth.backends import ModelBackend
@@ -9,6 +10,11 @@ from django.db.models.manager import EmptyManager
from django.db import models
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
_AnyUser = Union[Model, "AnonymousUser"]
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
@@ -105,7 +111,7 @@ class AnonymousUser:
def has_perms(self, perm_list: Collection[str], obj: Optional[_AnyUser] = ...) -> bool: ...
def has_module_perms(self, module: str) -> bool: ...
@property
def is_anonymous(self) -> bool: ...
def is_anonymous(self) -> Literal[True]: ...
@property
def is_authenticated(self) -> bool: ...
def is_authenticated(self) -> Literal[False]: ...
def get_username(self) -> str: ...

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, Protocol
from django.contrib.sites.models import Site
from django.contrib.sites.requests import RequestSite
@@ -13,10 +13,19 @@ class SitemapNotFound(Exception): ...
def ping_google(sitemap_url: Optional[str] = ..., ping_url: str = ...) -> None: ...
class _SupportsLen(Protocol):
def __len__(self) -> int: ...
class _SupportsCount(Protocol):
def count(self) -> int: ...
class _SupportsOrdered(Protocol):
ordered: bool = ...
class Sitemap:
limit: int = ...
protocol: Optional[str] = ...
def items(self) -> List[Any]: ...
def items(self) -> Union[_SupportsLen, _SupportsCount, _SupportsOrdered]: ...
def location(self, obj: Model) -> str: ...
@property
def paginator(self) -> Paginator: ...

View File

@@ -19,16 +19,16 @@ class BaseCache:
def __init__(self, params: Dict[str, Any]) -> None: ...
def get_backend_timeout(self, timeout: Any = ...) -> Optional[float]: ...
def make_key(self, key: Any, version: Optional[Any] = ...) -> str: ...
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def add(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
def get(self, key: Any, default: Optional[Any] = ..., version: Optional[Any] = ...) -> Any: ...
def set(self, key: Any, value: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> None: ...
def touch(self, key: Any, timeout: Any = ..., version: Optional[Any] = ...) -> bool: ...
def delete(self, key: Any, version: Optional[Any] = ...) -> None: ...
def get_many(self, keys: List[str], version: Optional[int] = ...) -> Dict[str, Union[int, str]]: ...
def get_or_set(
self, key: Any, default: Optional[Any], timeout: Any = ..., version: Optional[int] = ...
) -> Optional[Any]: ...
def has_key(self, key: Any, version: Optional[Any] = ...): ...
def has_key(self, key: Any, version: Optional[Any] = ...) -> bool: ...
def incr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
def decr(self, key: str, delta: int = ..., version: Optional[int] = ...) -> int: ...
def __contains__(self, key: str) -> bool: ...

View File

@@ -15,14 +15,14 @@ class SkipFile(UploadFileException): ...
class StopFutureHandlers(UploadFileException): ...
class FileUploadHandler:
chunk_size = ... # type: int
file_name = ... # type: Optional[str]
content_type = ... # type: Optional[str]
content_length = ... # type: Optional[int]
charset = ... # type: Optional[str]
content_type_extra = ... # type: Optional[Dict[str, str]]
request = ... # type: Optional[HttpRequest]
field_name = ... # type: str
chunk_size: int = ...
file_name: Optional[str] = ...
content_type: Optional[str] = ...
content_length: Optional[int] = ...
charset: Optional[str] = ...
content_type_extra: Optional[Dict[str, str]] = ...
request: Optional[HttpRequest] = ...
field_name: str = ...
def __init__(self, request: Optional[HttpRequest] = ...) -> None: ...
def handle_raw_input(
self,

View File

@@ -1,12 +1,10 @@
from io import BytesIO
from typing import Any, Callable, Dict, Optional, Union
from django.contrib.auth.models import AbstractUser
from django.contrib.sessions.backends.base import SessionBase
from django.http.response import HttpResponse
from django.core.handlers import base
from django.http import HttpRequest
from django.http.response import HttpResponse
_Stream = Union[BytesIO, str]
_WSGIEnviron = Dict[str, Any]
@@ -22,7 +20,6 @@ class LimitedStream:
class WSGIRequest(HttpRequest):
environ: _WSGIEnviron = ...
user: AbstractUser
session: SessionBase
encoding: Any = ...
def __init__(self, environ: _WSGIEnviron) -> None: ...

View File

@@ -22,4 +22,4 @@ class Style:
def make_style(config_string: str = ...) -> Style: ...
def no_style() -> Style: ...
def color_style() -> Style: ...
def color_style(force_color: bool = ...) -> Style: ...

View File

@@ -8,13 +8,13 @@ class InvalidPage(Exception): ...
class PageNotAnInteger(InvalidPage): ...
class EmptyPage(InvalidPage): ...
class SupportsLen(Protocol):
class _SupportsLen(Protocol):
def __len__(self) -> int: ...
class SupportsCount(Protocol):
class _SupportsCount(Protocol):
def count(self) -> int: ...
class SupportsOrdered(Protocol):
class _SupportsOrdered(Protocol):
ordered: bool = ...
class Paginator:
@@ -24,13 +24,13 @@ class Paginator:
allow_empty_first_page: bool = ...
def __init__(
self,
object_list: Union[SupportsLen, SupportsCount, SupportsOrdered],
object_list: Union[_SupportsLen, _SupportsCount, _SupportsOrdered],
per_page: Union[int, str],
orphans: int = ...,
allow_empty_first_page: bool = ...,
) -> None: ...
def validate_number(self, number: Optional[Union[float, str]]) -> int: ...
def get_page(self, number: Optional[int]) -> Page: ...
def validate_number(self, number: Optional[Union[int, float, str]]) -> int: ...
def get_page(self, number: Optional[Union[int, float, str]]) -> Page: ...
def page(self, number: Union[int, str]) -> Page: ...
@property
def count(self) -> int: ...

View File

@@ -68,6 +68,9 @@ from .deletion import (
DO_NOTHING as DO_NOTHING,
PROTECT as PROTECT,
SET as SET,
RESTRICT as RESTRICT,
ProtectedError as ProtectedError,
RestrictedError as RestrictedError,
)
from .query import (

View File

@@ -1,4 +1,4 @@
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Collection
from typing import Any, Callable, Collection, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar, Union
from django.core.checks.messages import CheckMessage
from django.core.exceptions import ValidationError
@@ -7,6 +7,13 @@ from django.db.models.options import Options
_Self = TypeVar("_Self", bound="Model")
class ModelStateFieldsCacheDescriptor: ...
class ModelState:
db: Optional[str] = ...
adding: bool = ...
fields_cache: ModelStateFieldsCacheDescriptor = ...
class ModelBase(type): ...
class Model(metaclass=ModelBase):
@@ -17,7 +24,12 @@ class Model(metaclass=ModelBase):
_default_manager: BaseManager[Model]
objects: BaseManager[Any]
pk: Any = ...
_state: ModelState
def __init__(self: _Self, *args, **kwargs) -> None: ...
@classmethod
def add_to_class(cls, name: str, value: Any): ...
@classmethod
def from_db(cls, db: Optional[str], field_names: Collection[str], values: Collection[Any]) -> _Self: ...
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
def full_clean(self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = ...) -> None: ...
def clean(self) -> None: ...
@@ -31,7 +43,7 @@ class Model(metaclass=ModelBase):
force_insert: bool = ...,
force_update: bool = ...,
using: Optional[str] = ...,
update_fields: Optional[Union[Sequence[str], str]] = ...,
update_fields: Optional[Iterable[str]] = ...,
) -> None: ...
def save_base(
self,
@@ -39,17 +51,10 @@ class Model(metaclass=ModelBase):
force_insert: bool = ...,
force_update: bool = ...,
using: Optional[str] = ...,
update_fields: Optional[Union[Sequence[str], str]] = ...,
update_fields: Optional[Iterable[str]] = ...,
): ...
def refresh_from_db(self: _Self, using: Optional[str] = ..., fields: Optional[List[str]] = ...) -> None: ...
def get_deferred_fields(self) -> Set[str]: ...
@classmethod
def check(cls, **kwargs: Any) -> List[CheckMessage]: ...
def __getstate__(self) -> dict: ...
class ModelStateFieldsCacheDescriptor: ...
class ModelState:
db: None = ...
adding: bool = ...
fields_cache: ModelStateFieldsCacheDescriptor = ...

View File

@@ -0,0 +1 @@
LOOKUP_SEP: str = ...

View File

@@ -11,10 +11,12 @@ def SET_NULL(collector, field, sub_objs, using): ...
def SET_DEFAULT(collector, field, sub_objs, using): ...
def DO_NOTHING(collector, field, sub_objs, using): ...
def PROTECT(collector, field, sub_objs, using): ...
def RESTRICT(collector, field, sub_objs, using): ...
def SET(value: Any) -> Callable: ...
def get_candidate_relations_to_delete(opts: Options) -> Iterable[Field]: ...
class ProtectedError(IntegrityError): ...
class RestrictedError(IntegrityError): ...
class Collector:
def __init__(self, using: str) -> None: ...

View File

@@ -10,6 +10,10 @@ class ChoicesMeta(enum.EnumMeta):
class Choices(enum.Enum, metaclass=ChoicesMeta):
def __str__(self): ...
@property
def label(self) -> str: ...
@property
def value(self) -> Any: ...
# fake
class _IntegerChoicesMeta(ChoicesMeta):
@@ -18,7 +22,9 @@ class _IntegerChoicesMeta(ChoicesMeta):
labels: List[str] = ...
values: List[int] = ...
class IntegerChoices(int, Choices, metaclass=_IntegerChoicesMeta): ...
class IntegerChoices(int, Choices, metaclass=_IntegerChoicesMeta):
@property
def value(self) -> int: ...
# fake
class _TextChoicesMeta(ChoicesMeta):
@@ -27,4 +33,6 @@ class _TextChoicesMeta(ChoicesMeta):
labels: List[str] = ...
values: List[str] = ...
class TextChoices(str, Choices, metaclass=_TextChoicesMeta): ...
class TextChoices(str, Choices, metaclass=_TextChoicesMeta):
@property
def value(self) -> str: ...

View File

@@ -1,4 +1,5 @@
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, Iterable
from django.db.models.lookups import Lookup
@@ -15,6 +16,8 @@ class SQLiteNumericMixin:
_Self = TypeVar("_Self")
_Numeric = Union[float, Decimal]
class Combinable:
ADD: str = ...
SUB: str = ...
@@ -27,25 +30,25 @@ class Combinable:
BITLEFTSHIFT: str = ...
BITRIGHTSHIFT: str = ...
def __neg__(self: _Self) -> _Self: ...
def __add__(self: _Self, other: Optional[Union[timedelta, Combinable, float, str]]) -> _Self: ...
def __sub__(self: _Self, other: Union[timedelta, Combinable, float]) -> _Self: ...
def __mul__(self: _Self, other: Union[timedelta, Combinable, float]) -> _Self: ...
def __truediv__(self: _Self, other: Union[Combinable, float]) -> _Self: ...
def __itruediv__(self: _Self, other: Union[Combinable, float]) -> _Self: ...
def __add__(self: _Self, other: Optional[Union[timedelta, Combinable, _Numeric, str]]) -> _Self: ...
def __sub__(self: _Self, other: Union[timedelta, Combinable, _Numeric]) -> _Self: ...
def __mul__(self: _Self, other: Union[timedelta, Combinable, _Numeric]) -> _Self: ...
def __truediv__(self: _Self, other: Union[Combinable, _Numeric]) -> _Self: ...
def __itruediv__(self: _Self, other: Union[Combinable, _Numeric]) -> _Self: ...
def __mod__(self: _Self, other: Union[int, Combinable]) -> _Self: ...
def __pow__(self: _Self, other: Union[float, Combinable]) -> _Self: ...
def __pow__(self: _Self, other: Union[_Numeric, Combinable]) -> _Self: ...
def __and__(self: _Self, other: Combinable) -> _Self: ...
def bitand(self: _Self, other: int) -> _Self: ...
def bitleftshift(self: _Self, other: int) -> _Self: ...
def bitrightshift(self: _Self, other: int) -> _Self: ...
def __or__(self: _Self, other: Combinable) -> _Self: ...
def bitor(self: _Self, other: int) -> _Self: ...
def __radd__(self, other: Optional[Union[datetime, float, Combinable]]) -> Combinable: ...
def __rsub__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rmul__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rtruediv__(self, other: Union[float, Combinable]) -> Combinable: ...
def __radd__(self, other: Optional[Union[datetime, _Numeric, Combinable]]) -> Combinable: ...
def __rsub__(self, other: Union[_Numeric, Combinable]) -> Combinable: ...
def __rmul__(self, other: Union[_Numeric, Combinable]) -> Combinable: ...
def __rtruediv__(self, other: Union[_Numeric, Combinable]) -> Combinable: ...
def __rmod__(self, other: Union[int, Combinable]) -> Combinable: ...
def __rpow__(self, other: Union[float, Combinable]) -> Combinable: ...
def __rpow__(self, other: Union[_Numeric, Combinable]) -> Combinable: ...
def __rand__(self, other: Any) -> Combinable: ...
def __ror__(self, other: Any) -> Combinable: ...

View File

@@ -56,6 +56,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
remote_field: Field
is_relation: bool
related_model: Optional[Type[Model]]
one_to_many: Optional[bool] = ...
one_to_one: Optional[bool] = ...
many_to_many: Optional[bool] = ...
many_to_one: Optional[bool] = ...
max_length: int
model: Type[Model]
name: str

View File

@@ -39,7 +39,7 @@ class FileField(Field):
def __init__(
self,
upload_to: Union[str, Callable, Path] = ...,
storage: Optional[Storage] = ...,
storage: Optional[Union[Storage, Callable[[], Storage]]] = ...,
verbose_name: Optional[Union[str, bytes]] = ...,
name: Optional[str] = ...,
max_length: Optional[int] = ...,

View File

@@ -96,7 +96,7 @@ class _BaseQuerySet(Generic[_T], Sized):
def union(self: _QS, *other_qs: Any, all: bool = ...) -> _QS: ...
def intersection(self: _QS, *other_qs: Any) -> _QS: ...
def difference(self: _QS, *other_qs: Any) -> _QS: ...
def select_for_update(self: _QS, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> _QS: ...
def select_for_update(self: _QS, nowait: bool = ..., skip_locked: bool = ..., of: Sequence[str] = ...) -> _QS: ...
def select_related(self: _QS, *fields: Any) -> _QS: ...
def prefetch_related(self: _QS, *lookups: Any) -> _QS: ...
# TODO: return type

View File

@@ -14,7 +14,7 @@ class Field:
initial: Any
label: Optional[str]
required: bool
widget: Type[Widget] = ...
widget: Union[Type[Widget], Widget] = ...
hidden_widget: Any = ...
default_validators: Any = ...
default_error_messages: Any = ...

View File

@@ -1,11 +1,13 @@
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Type, Union
from django.core.exceptions import ValidationError as ValidationError
from django.core.files import uploadedfile
from django.forms.boundfield import BoundField
from django.forms.fields import Field
from django.forms.renderers import BaseRenderer
from django.forms.utils import ErrorDict, ErrorList
from django.forms.widgets import Media, MediaDefiningClass
from django.utils.datastructures import MultiValueDict
from django.utils.safestring import SafeText
class DeclarativeFieldsMetaclass(MediaDefiningClass): ...
@@ -18,11 +20,11 @@ class BaseForm:
use_required_attribute: bool = ...
is_bound: bool = ...
data: Dict[str, Any] = ...
files: Optional[Dict[str, Any]] = ...
auto_id: str = ...
files: MultiValueDict[str, uploadedfile.UploadedFile] = ...
auto_id: Union[bool, str] = ...
initial: Dict[str, Any] = ...
error_class: Type[ErrorList] = ...
prefix: str = ...
prefix: Optional[str] = ...
label_suffix: str = ...
empty_permitted: bool = ...
fields: Dict[str, Any] = ...
@@ -67,6 +69,14 @@ class BaseForm:
def hidden_fields(self): ...
def visible_fields(self): ...
def get_initial_for_field(self, field: Field, field_name: str) -> Any: ...
def _html_output(
self,
normal_row: str,
error_row: str,
row_ender: str,
help_text_html: str,
errors_on_separate_row: bool,
) -> SafeText: ...
class Form(BaseForm):
base_fields: Dict[str, Field]

View File

@@ -17,6 +17,7 @@ from typing import (
)
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.backends.base import SessionBase
from django.contrib.sites.models import Site
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
@@ -51,7 +52,7 @@ class HttpRequest(BytesIO):
resolver_match: ResolverMatch = ...
content_type: Optional[str] = ...
content_params: Optional[Dict[str, str]] = ...
user: AbstractBaseUser
user: Union[AbstractBaseUser, AnonymousUser]
site: Site
session: SessionBase
encoding: Optional[str] = ...

View File

@@ -83,12 +83,13 @@ class HttpResponse(HttpResponseBase):
context: Context
resolver_match: ResolverMatch
def json(self) -> Any: ...
def getvalue(self) -> bytes: ...
class StreamingHttpResponse(HttpResponseBase):
content: Any
streaming_content: Iterator[Any]
def __init__(self, streaming_content: Iterable[Any] = ..., *args: Any, **kwargs: Any) -> None: ...
def getvalue(self) -> Any: ...
def getvalue(self) -> bytes: ...
class FileResponse(StreamingHttpResponse):
client: Client

View File

@@ -1,4 +1,5 @@
from typing import Any, Callable, Dict, List, Optional, Protocol, Sequence, Type, TypeVar, Union
import sys
from typing import Any, Callable, List, Mapping, Optional, overload, Protocol, Sequence, Type, TypeVar, Union
from django.db.models.base import Model
from django.http.response import (
@@ -10,9 +11,14 @@ from django.http.response import (
from django.db.models import Manager, QuerySet
from django.http import HttpRequest
if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal
def render_to_response(
template_name: Union[str, Sequence[str]],
context: Optional[Dict[str, Any]] = ...,
context: Optional[Mapping[str, Any]] = ...,
content_type: Optional[str] = ...,
status: Optional[int] = ...,
using: Optional[str] = ...,
@@ -20,7 +26,7 @@ def render_to_response(
def render(
request: HttpRequest,
template_name: Union[str, Sequence[str]],
context: Optional[Dict[str, Any]] = ...,
context: Optional[Mapping[str, Any]] = ...,
content_type: Optional[str] = ...,
status: Optional[int] = ...,
using: Optional[str] = ...,
@@ -28,6 +34,15 @@ def render(
class SupportsGetAbsoluteUrl(Protocol): ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[True], **kwargs: Any
) -> HttpResponsePermanentRedirect: ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: Literal[False], **kwargs: Any
) -> HttpResponseRedirect: ...
@overload
def redirect(
to: Union[Callable, str, SupportsGetAbsoluteUrl], *args: Any, permanent: bool = ..., **kwargs: Any
) -> Union[HttpResponseRedirect, HttpResponsePermanentRedirect]: ...

View File

@@ -109,7 +109,7 @@ class Parser:
builtins: Optional[List[Library]] = ...,
origin: Optional[Origin] = ...,
) -> None: ...
def parse(self, parse_until: Optional[Tuple[str]] = ...) -> NodeList: ...
def parse(self, parse_until: Optional[Tuple[str, ...]] = ...) -> NodeList: ...
def skip_past(self, endtag: str) -> None: ...
def extend_nodelist(self, nodelist: NodeList, node: Node, token: Token) -> None: ...
def error(self, token: Token, e: Union[Exception, str]) -> Exception: ...

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Optional, Dict
from typing import Any, List, Iterable, Optional, Dict
from django.template.base import Origin, Template
from django.template.engine import Engine
@@ -8,5 +8,5 @@ class Loader:
get_template_cache: Dict[str, Any] = ...
def __init__(self, engine: Engine) -> None: ...
def get_template(self, template_name: str, skip: Optional[List[Origin]] = ...) -> Template: ...
def get_template_sources(self, template_name: str) -> None: ...
def get_template_sources(self, template_name: str) -> Iterable[Origin]: ...
def reset(self) -> None: ...

View File

@@ -2,7 +2,7 @@ from io import BytesIO
from types import TracebackType
from typing import Any, Dict, List, Optional, Pattern, Tuple, Type, Union
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.sessions.backends.base import SessionBase
from django.core.handlers.base import BaseHandler
from django.http.cookie import SimpleCookie
@@ -126,7 +126,7 @@ class Client(RequestFactory):
@property
def session(self) -> SessionBase: ...
def login(self, **credentials: Any) -> bool: ...
def force_login(self, user: AbstractUser, backend: Optional[str] = ...) -> None: ...
def force_login(self, user: AbstractBaseUser, backend: Optional[str] = ...) -> None: ...
def logout(self) -> None: ...
def conditional_content_removal(request: HttpRequest, response: HttpResponseBase) -> HttpResponse: ...

View File

@@ -126,9 +126,17 @@ class SimpleTestCase(unittest.TestCase):
self, needle: str, haystack: SafeText, count: Optional[int] = ..., msg_prefix: str = ...
) -> None: ...
def assertJSONEqual(
self, raw: str, expected_data: Union[Dict[str, str], bool, str], msg: Optional[str] = ...
self,
raw: str,
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
msg: Optional[str] = ...,
) -> None: ...
def assertJSONNotEqual(
self,
raw: str,
expected_data: Union[Dict[str, Any], List[Any], str, int, float, bool, None],
msg: Optional[str] = ...,
) -> None: ...
def assertJSONNotEqual(self, raw: str, expected_data: str, msg: Optional[str] = ...) -> None: ...
def assertXMLEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...
def assertXMLNotEqual(self, xml1: str, xml2: str, msg: Optional[str] = ...) -> None: ...

View File

@@ -1,8 +1,31 @@
from typing import Any, List, Optional, Tuple
from typing import Any, List, Optional, Tuple, overload, Callable, Dict, Union
from .resolvers import URLResolver
from .resolvers import URLResolver, URLPattern
from ..conf.urls import IncludedURLConf
from ..http.response import HttpResponseBase
def include(arg: Any, namespace: Optional[str] = ...) -> Tuple[List[URLResolver], Optional[str], Optional[str]]: ...
path: Any
re_path: Any
# path()
@overload
def path(
route: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLPattern: ...
@overload
def path(route: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ...
@overload
def path(
route: str, view: List[Union[URLResolver, str]], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLResolver: ...
# re_path()
@overload
def re_path(
route: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLPattern: ...
@overload
def re_path(route: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ...
@overload
def re_path(
route: str, view: List[Union[URLResolver, str]], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLResolver: ...

View File

@@ -22,6 +22,7 @@ class ResolverMatch:
url_name: Optional[str] = ...,
app_names: Optional[List[Optional[str]]] = ...,
namespaces: Optional[List[Optional[str]]] = ...,
route: Optional[str] = ...,
) -> None: ...
def __getitem__(self, index: int) -> Any: ...
# for tuple unpacking

View File

@@ -22,6 +22,7 @@ _V = TypeVar("_V")
class OrderedSet(MutableSet[_K]):
dict: Dict[_K, None] = ...
def __init__(self, iterable: Optional[Iterable[_K]] = ...) -> None: ...
def __contains__(self, item: object) -> bool: ...
def __iter__(self) -> Iterator[_K]: ...
def __len__(self) -> int: ...

View File

@@ -56,4 +56,8 @@ class SimpleLazyObject(LazyObject):
def __copy__(self) -> List[int]: ...
def __deepcopy__(self, memo: Dict[Any, Any]) -> List[int]: ...
def partition(predicate: Callable, values: List[Model]) -> Tuple[List[Model], List[Model]]: ...
_PartitionMember = TypeVar("_PartitionMember")
def partition(
predicate: Callable, values: List[_PartitionMember]
) -> Tuple[List[_PartitionMember], List[_PartitionMember]]: ...

View File

@@ -13,6 +13,6 @@ NOCOLOR_PALETTE: str
DARK_PALETTE: str
LIGHT_PALETTE: str
PALETTES: Any
DEFAULT_PALETTE = DARK_PALETTE
DEFAULT_PALETTE: str = ...
def parse_color_setting(config_string: str) -> Optional[Dict[str, Dict[str, Union[Tuple[str], str]]]]: ...

View File

@@ -57,7 +57,7 @@ class override(ContextDecorator):
def __enter__(self) -> None: ...
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
def get_language() -> Optional[str]: ...
def get_language() -> str: ...
def get_language_from_path(path: str) -> Optional[str]: ...
def get_language_bidi() -> bool: ...
def check_for_language(lang_code: Optional[str]) -> bool: ...
@@ -66,6 +66,7 @@ def to_locale(language: str) -> str: ...
def get_language_from_request(request: WSGIRequest, check_path: bool = ...) -> str: ...
def templatize(src: str, **kwargs: Any) -> str: ...
def deactivate_all() -> None: ...
def get_supported_language_variant(lang_code: str, strict: bool = ...) -> str: ...
def get_language_info(lang_code: str) -> Any: ...
from . import trans_real as trans_real

View File

@@ -1,28 +1,35 @@
from typing import Any, Callable, Dict, Optional, Sequence, Type, Union
from django.forms.forms import BaseForm
from django.forms.models import BaseModelForm
from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
from django.views.generic.detail import BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin
from typing_extensions import Literal
from django.http import HttpRequest, HttpResponse
class FormMixin(ContextMixin):
class AbstractFormMixin(ContextMixin):
initial: Dict[str, Any] = ...
form_class: Optional[Type[BaseForm]] = ...
success_url: Optional[Union[str, Callable[..., Any]]] = ...
prefix: Optional[str] = ...
def get_initial(self) -> Dict[str, Any]: ...
def get_prefix(self) -> Optional[str]: ...
def get_form_class(self) -> Type[BaseForm]: ...
def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ...
def get_form_kwargs(self) -> Dict[str, Any]: ...
def get_success_url(self) -> str: ...
class FormMixin(AbstractFormMixin):
def get_form_class(self) -> Type[BaseForm]: ...
def get_form(self, form_class: Optional[Type[BaseForm]] = ...) -> BaseForm: ...
def form_valid(self, form: BaseForm) -> HttpResponse: ...
def form_invalid(self, form: BaseForm) -> HttpResponse: ...
class ModelFormMixin(FormMixin, SingleObjectMixin):
class ModelFormMixin(AbstractFormMixin, SingleObjectMixin):
fields: Optional[Union[Sequence[str], Literal["__all__"]]] = ...
def get_form_class(self) -> Type[BaseModelForm]: ...
def get_form(self, form_class: Optional[Type[BaseModelForm]] = ...) -> BaseModelForm: ...
def form_valid(self, form: BaseModelForm) -> HttpResponse: ...
def form_invalid(self, form: BaseModelForm) -> HttpResponse: ...
class ProcessFormView(View):
def get(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: ...

View File

@@ -10,3 +10,5 @@ select = F401, Y
max_line_length = 120
per-file-ignores =
*__init__.pyi: F401
base_user.pyi: Y003
models.pyi: Y003

View File

@@ -311,7 +311,11 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
def build_unannotated_method_args(method_node: FuncDef) -> Tuple[List[Argument], MypyType]:
prepared_arguments = []
for argument in method_node.arguments[1:]:
try:
arguments = method_node.arguments[1:]
except AttributeError:
arguments = []
for argument in arguments:
argument.type_annotation = AnyType(TypeOfAny.unannotated)
prepared_arguments.append(argument)
return_type = AnyType(TypeOfAny.unannotated)
@@ -343,6 +347,7 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
arguments = []
bound_return_type = semanal_api.anal_type(method_type.ret_type,
allow_placeholder=True)
assert bound_return_type is not None
if isinstance(bound_return_type, PlaceholderNode):
@@ -352,6 +357,10 @@ def copy_method_to_another_class(ctx: ClassDefContext, self_type: Instance,
method_type.arg_types[1:],
method_node.arguments[1:]):
bound_arg_type = semanal_api.anal_type(arg_type, allow_placeholder=True)
if bound_arg_type is None and not semanal_api.final_iteration:
semanal_api.defer()
return
assert bound_arg_type is not None
if isinstance(bound_arg_type, PlaceholderNode):

View File

@@ -1,9 +1,8 @@
import configparser
from functools import partial
from typing import Callable, Dict, List, Optional, Tuple
from typing import Callable, Dict, List, NoReturn, Optional, Tuple, cast
from django.db.models.fields.related import RelatedField
from mypy.errors import Errors
from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options
from mypy.plugin import (
@@ -52,25 +51,40 @@ def add_new_manager_base(ctx: ClassDefContext) -> None:
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
errors = Errors()
if config_file_path is None:
errors.report(0, None, "'django_settings_module' is not set: no mypy config file specified")
errors.raise_error()
def exit(error_type: int) -> NoReturn:
"""Using mypy's argument parser, raise `SystemExit` to fail hard if validation fails.
Considering that the plugin's startup duration is around double as long as mypy's, this aims to
import and construct objects only when that's required - which happens once and terminates the
run. Considering that most of the runs are successful, there's no need for this to linger in the
global scope.
"""
from mypy.main import CapturableArgumentParser
usage = """(config)
...
[mypy.plugins.django_stubs]
django_settings_module: str (required)
...
""".replace("\n" + 8 * " ", "\n")
handler = CapturableArgumentParser(prog='(django-stubs) mypy', usage=usage)
messages = {1: 'mypy config file is not specified or found',
2: 'no section [mypy.plugins.django-stubs]',
3: 'the setting is not provided'}
handler.error("'django_settings_module' is not set: " + messages[error_type])
parser = configparser.ConfigParser()
parser.read(config_file_path) # type: ignore
try:
parser.read_file(open(cast(str, config_file_path), 'r'), source=config_file_path)
except (IsADirectoryError, OSError):
exit(1)
if not parser.has_section('mypy.plugins.django-stubs'):
errors.report(0, None, "'django_settings_module' is not set: no section [mypy.plugins.django-stubs]",
file=config_file_path)
errors.raise_error()
if not parser.has_option('mypy.plugins.django-stubs', 'django_settings_module'):
errors.report(0, None, "'django_settings_module' is not set: setting is not provided",
file=config_file_path)
errors.raise_error()
django_settings_module = parser.get('mypy.plugins.django-stubs', 'django_settings_module').strip('\'"')
return django_settings_module
section = 'mypy.plugins.django-stubs'
if not parser.has_section(section):
exit(2)
settings = parser.get(section, 'django_settings_module', fallback=None) or exit(3)
return cast(str, settings).strip('\'"')
class NewSemanalDjangoPlugin(Plugin):

View File

@@ -1,16 +1,34 @@
from mypy.plugin import AttributeContext
from mypy.types import Instance
from mypy.types import Type as MypyType
from mypy.types import UnionType
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
auth_user_model = django_context.settings.AUTH_USER_MODEL
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
if model_info is None:
# Imported here because django isn't properly loaded yet when module is loaded
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
abstract_base_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AbstractBaseUser)
anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
# This shouldn't be able to happen, as we managed to import the models above.
assert abstract_base_user_info is not None
assert anonymous_user_info is not None
if ctx.default_attr_type != UnionType([Instance(abstract_base_user_info, []), Instance(anonymous_user_info, [])]):
# Type has been changed from the default in django-stubs.
# I.e. HttpRequest has been subclassed and user-type overridden, so let's leave it as is.
return ctx.default_attr_type
return Instance(model_info, [])
auth_user_model = django_context.settings.AUTH_USER_MODEL
user_cls = django_context.apps_registry.get_model(auth_user_model)
user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
if user_info is None:
return ctx.default_attr_type
return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])])

View File

@@ -1,5 +1,7 @@
[pytest]
testpaths = ./test-data
testpaths =
./test-plugin
./test-data
addopts =
--tb=native
-s

View File

@@ -185,6 +185,8 @@ IGNORED_ERRORS = {
],
'files': [
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
'Argument 1 to "TextIOWrapper" has incompatible type "File"; expected "BinaryIO"',
'Incompatible types in assignment (expression has type "BinaryIO", variable has type "File")',
],
'filtered_relation': [
'has no attribute "name"',
@@ -378,6 +380,7 @@ IGNORED_ERRORS = {
'responses': [
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"',
'"FileLike" has no attribute "closed"',
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "BinaryIO"',
],
'reverse_lookup': [
"Cannot resolve keyword 'choice' into field"
@@ -448,9 +451,11 @@ IGNORED_ERRORS = {
'urlpatterns': [
'"object" not callable',
'"None" not callable',
'Argument 2 to "path" has incompatible type "Callable[[Any], None]"',
'Incompatible return value type (got "None", expected "HttpResponseBase")',
],
'urlpatterns_reverse': [
'List or tuple expected as variable arguments',
'No overload variant of "path" matches argument types "str", "None"',
'No overload variant of "zip" matches argument types "Any", "object"',
'Argument 1 to "get_callable" has incompatible type "int"'
],

View File

@@ -1,5 +1,5 @@
from pytest_mypy.collect import File
from pytest_mypy.item import YamlTestItem
from pytest_mypy_plugins.collect import File
from pytest_mypy_plugins.item import YamlTestItem
def django_plugin_hook(test_item: YamlTestItem) -> None:

View File

@@ -14,8 +14,8 @@ from scripts.enabled_test_modules import (
)
DJANGO_COMMIT_REFS: Dict[str, Tuple[str, str]] = {
'2.2': ('stable/2.2.x', '996be04c3ceb456754d9d527d4d708f30727f07e'),
'3.0': ('stable/3.0.x', 'd9f1792c7649e9f946f4a3a35a76bddf5a412b8b')
'2.2': ('stable/2.2.x', '8093aaa8ff9dd7386a069c6eb49fcc1c5980c033'),
'3.0': ('stable/3.0.x', '44da7abda848f05caaed74f6a749038c87dedfda')
}
PROJECT_DIRECTORY = Path(__file__).parent.parent
DJANGO_SOURCE_DIRECTORY = PROJECT_DIRECTORY / 'django-sources' # type: Path

View File

@@ -9,7 +9,7 @@ def find_stub_files(name: str) -> List[str]:
result = []
for root, dirs, files in os.walk(name):
for file in files:
if file.endswith('.pyi'):
if file.endswith(".pyi"):
if os.path.sep in root:
sub_root = root.split(os.path.sep, 1)[-1]
file = os.path.join(sub_root, file)
@@ -17,34 +17,42 @@ def find_stub_files(name: str) -> List[str]:
return result
with open('README.md', 'r') as f:
with open("README.md", "r") as f:
readme = f.read()
dependencies = [
'mypy>=0.770,<0.780',
'typing-extensions',
'django',
"mypy>=0.782,<0.790",
"typing-extensions",
"django",
]
setup(
name="django-stubs",
version="1.5.0",
description='Mypy stubs for Django',
version="1.6.0",
description="Mypy stubs for Django",
long_description=readme,
long_description_content_type='text/markdown',
license='MIT',
long_description_content_type="text/markdown",
license="MIT",
url="https://github.com/typeddjango/django-stubs",
author="Maksim Kurnikov",
author_email="maxim.kurnikov@gmail.com",
py_modules=[],
python_requires='>=3.6',
python_requires=">=3.6",
install_requires=dependencies,
packages=['django-stubs', *find_packages(exclude=['scripts'])],
package_data={'django-stubs': find_stub_files('django-stubs')},
packages=["django-stubs", *find_packages(exclude=["scripts"])],
package_data={"django-stubs": find_stub_files("django-stubs")},
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7'
]
"Development Status :: 3 - Alpha",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Framework :: Django",
"Framework :: Django :: 2.2",
"Framework :: Django :: 3.0",
"Typing :: Typed",
],
project_urls={
"Release notes": "https://github.com/typeddjango/django-stubs/releases",
},
)

View File

@@ -0,0 +1,130 @@
# "Happy path" test for model admin, trying to cover as many valid
# configurations as possible.
- case: test_full_admin
main: |
from django.contrib import admin
from django.forms import Form, Textarea
from django.db import models
from django.core.paginator import Paginator
from django.contrib.admin.sites import AdminSite
from django.db.models.options import Options
from django.http.request import HttpRequest
from django.db.models.query import QuerySet
def an_action(modeladmin: admin.ModelAdmin, request: HttpRequest, queryset: QuerySet) -> None:
pass
class A(admin.ModelAdmin):
# BaseModelAdmin
autocomplete_fields = ("strs",)
raw_id_fields = ["strs"]
fields = (
"a field",
["a", "list of", "fields"],
)
exclude = ("a", "b")
fieldsets = [
(None, {"fields": ["a", "b"]}),
("group", {"fields": ("c",), "classes": ("a",), "description": "foo"}),
]
form = Form
filter_vertical = ("fields",)
filter_horizontal = ("plenty", "of", "fields")
radio_fields = {
"some_field": admin.VERTICAL,
"another_field": admin.HORIZONTAL,
}
prepopulated_fields = {"slug": ("title",)}
formfield_overrides = {models.TextField: {"widget": Textarea}}
readonly_fields = ("date_modified",)
ordering = ("-pk", "date_modified")
sortable_by = ["pk"]
view_on_site = True
show_full_result_count = False
# ModelAdmin
list_display = ("pk",)
list_display_links = ("str",)
list_filter = ("str", admin.SimpleListFilter, ("str", admin.SimpleListFilter))
list_select_related = True
list_per_page = 1
list_max_show_all = 2
list_editable = ("a", "b")
search_fields = ("c", "d")
date_hirearchy = "f"
save_as = False
save_as_continue = True
save_on_top = False
paginator = Paginator
presserve_filters = False
inlines = (admin.TabularInline, admin.StackedInline)
add_form_template = "template"
change_form_template = "template"
change_list_template = "template"
delete_confirmation_template = "template"
delete_selected_confirmation_template = "template"
object_history_template = "template"
popup_response_template = "template"
actions = (an_action, "a_method_action")
actions_on_top = True
actions_on_bottom = False
actions_selection_counter = True
admin_site = AdminSite()
def a_method_action(self, request, queryset):
pass
# This test is here to make sure we're not running into a mypy issue which is
# worked around using a somewhat complicated _ListOrTuple union type. Once the
# issue is solved upstream this test should pass even with the workaround
# replaced by a simpler Sequence type.
# https://github.com/python/mypy/issues/8921
- case: test_fieldset_workaround_regression
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('name',),
}),
)
- case: errors_on_omitting_fields_from_fieldset_opts
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
fieldsets = [ # type: ignore
(None, {}), # E: Key 'fields' missing for TypedDict "_FieldOpts"
]
- case: errors_on_invalid_radio_fields
main: |
from django.contrib import admin
class A(admin.ModelAdmin):
radio_fields = {"some_field": 0} # E: Dict entry 0 has incompatible type "str": "Literal[0]"; expected "str": "Union[Literal[1], Literal[2]]"
class B(admin.ModelAdmin):
radio_fields = {1: admin.VERTICAL} # E: Dict entry 0 has incompatible type "int": "Literal[2]"; expected "str": "Union[Literal[1], Literal[2]]"
- case: errors_for_invalid_formfield_overrides
main: |
from django.contrib import admin
from django.forms import Textarea
class A(admin.ModelAdmin):
formfield_overrides = {
"not a field": { # E: Dict entry 0 has incompatible type "str": "Dict[str, Any]"; expected "Type[Field[Any, Any]]": "Mapping[str, Any]"
"widget": Textarea
}
}
- case: errors_for_invalid_action_signature
main: |
from django.contrib import admin
from django.http.request import HttpRequest
from django.db.models.query import QuerySet
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[[ModelAdmin, HttpRequest, QuerySet[Any]], None], str]"

View File

@@ -0,0 +1,26 @@
- case: field_to_many_and_to_one_attrs_bool_or_none_in_field_base_class
main: |
from django.db.models import Field
field: Field
my_bool: bool
my_bool = field.one_to_many
my_bool = field.one_to_one
my_bool = field.many_to_many
my_bool = field.many_to_one
# Narrowing the types should give us bool
assert field.one_to_many is not None
my_bool = field.one_to_many
assert field.one_to_one is not None
my_bool = field.one_to_one
assert field.many_to_many is not None
my_bool = field.many_to_many
assert field.many_to_one is not None
my_bool = field.many_to_one
out: |
main:6: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:7: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:8: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")
main:9: error: Incompatible types in assignment (expression has type "Optional[bool]", variable has type "bool")

View File

@@ -335,3 +335,25 @@
objects = MyManager()
class ChildUser(models.Model):
objects = MyManager()
- case: custom_manager_annotate_method_before_type_declaration
main: |
from myapp.models import ModelA, ModelB, ManagerA
reveal_type(ModelA.objects) # N: Revealed type is 'myapp.models.ModelA_ManagerA1[myapp.models.ModelA]'
reveal_type(ModelA.objects.do_something) # N: Revealed type is 'def (other_obj: myapp.models.ModelB) -> builtins.str'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class ManagerA(models.Manager):
def do_something(self, other_obj: "ModelB") -> str:
return 'test'
class ModelA(models.Model):
title = models.TextField()
objects = ManagerA()
class ModelB(models.Model):
movie = models.TextField()

View File

@@ -10,6 +10,12 @@
reveal_type(User().is_active) # N: Revealed type is 'builtins.bool*'
reveal_type(User().date_joined) # N: Revealed type is 'datetime.datetime*'
reveal_type(User().last_login) # N: Revealed type is 'Union[datetime.datetime, None]'
reveal_type(User().is_authenticated) # N: Revealed type is 'Literal[True]'
reveal_type(User().is_anonymous) # N: Revealed type is 'Literal[False]'
from django.contrib.auth.models import AnonymousUser
reveal_type(AnonymousUser().is_authenticated) # N: Revealed type is 'Literal[False]'
reveal_type(AnonymousUser().is_anonymous) # N: Revealed type is 'Literal[True]'
from django.contrib.auth.models import Permission
reveal_type(Permission().name) # N: Revealed type is 'builtins.str*'

View File

@@ -0,0 +1,14 @@
- case: state_attribute_has_a_type_of_model_state
main: |
from myapp.models import MyUser
user = MyUser(pk=1)
reveal_type(user._state) # N: Revealed type is 'django.db.models.base.ModelState'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass

View File

@@ -46,6 +46,18 @@
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
- case: updateview_form_valid_has_form_save
main: |
from django import forms
from django.views.generic.edit import UpdateView
class MyForm(forms.ModelForm):
pass
class MyView(UpdateView):
form_class = MyForm
def form_valid(self, form: forms.BaseModelForm):
reveal_type(form.save) # N: Revealed type is 'def (commit: builtins.bool =) -> Any'
- case: successmessagemixin_compatible_with_formmixin
main: |
from django.views.generic.edit import FormMixin

View File

@@ -2,11 +2,11 @@
disable_cache: true
main: |
from django.http.request import HttpRequest
reveal_type(HttpRequest().user) # N: Revealed type is 'myapp.models.MyUser'
reveal_type(HttpRequest().user) # N: Revealed type is 'Union[myapp.models.MyUser, django.contrib.auth.models.AnonymousUser]'
# check that other fields work ok
reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
AUTH_USER_MODEL='myapp.MyUser'
files:
- path: myapp/__init__.py
@@ -14,4 +14,41 @@
content: |
from django.db import models
class MyUser(models.Model):
pass
pass
- case: request_object_user_can_be_descriminated
disable_cache: true
main: |
from django.http.request import HttpRequest
request = HttpRequest()
reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
if not request.user.is_anonymous:
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
if request.user.is_authenticated:
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
- case: subclass_request_not_changed_user_type
disable_cache: true
main: |
from django.http.request import HttpRequest
class MyRequest(HttpRequest):
foo: int # Just do something
request = MyRequest()
reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
- case: subclass_request_changed_user_type
disable_cache: true
main: |
from django.http.request import HttpRequest
from django.contrib.auth.models import User
class MyRequest(HttpRequest):
user: User # Override the type of user
request = MyRequest()
reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')

View File

@@ -36,3 +36,24 @@
from django.db import models
class MyUser(models.Model):
pass
- case: check_render_function_arguments_annotations
main: |
from typing import Any
from typing_extensions import TypedDict
from django.shortcuts import render
from django.http.request import HttpRequest
TestContext = TypedDict("TestContext", {"user": Any})
test_context: TestContext = {"user": "test"}
reveal_type(test_context) # N: Revealed type is 'TypedDict('main.TestContext', {'user': Any})'
reveal_type(render(HttpRequest(), '', test_context)) # N: Revealed type is 'django.http.response.HttpResponse'
- case: check_redirect_return_annotation
main: |
from django.shortcuts import redirect
reveal_type(redirect(to = '', permanent = True)) # N: Revealed type is 'django.http.response.HttpResponsePermanentRedirect'
reveal_type(redirect(to = '', permanent = False)) # N: Revealed type is 'django.http.response.HttpResponseRedirect'
var = True
reveal_type(redirect(to = '', permanent = var)) # N: Revealed type is 'Union[django.http.response.HttpResponseRedirect, django.http.response.HttpResponsePermanentRedirect]'

View File

@@ -0,0 +1,73 @@
import tempfile
import typing
import pytest
from mypy_django_plugin.main import extract_django_settings_module
TEMPLATE = """usage: (config)
...
[mypy.plugins.django_stubs]
django_settings_module: str (required)
...
(django-stubs) mypy: error: 'django_settings_module' is not set: {}
"""
@pytest.mark.parametrize(
'config_file_contents,message_part',
[
pytest.param(
None,
'mypy config file is not specified or found',
id='missing-file',
),
pytest.param(
['[not-really-django-stubs]'],
'no section [mypy.plugins.django-stubs]',
id='missing-section',
),
pytest.param(
['[mypy.plugins.django-stubs]',
'\tnot_django_not_settings_module = badbadmodule'],
'the setting is not provided',
id='missing-settings-module',
),
pytest.param(
['[mypy.plugins.django-stubs]'],
'the setting is not provided',
id='no-settings-given',
),
],
)
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
# type: (typing.Any, typing.List[str], str) -> None
"""Invalid configuration raises `SystemExit` with a precise error message."""
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
if not config_file_contents:
config_file.close()
else:
config_file.write('\n'.join(config_file_contents).expandtabs(4))
config_file.seek(0)
with pytest.raises(SystemExit, match='2'):
extract_django_settings_module(config_file.name)
error_message = TEMPLATE.format(message_part)
assert error_message == capsys.readouterr().err
def test_correct_configuration() -> None:
"""Django settings module gets extracted given valid configuration."""
config_file_contents = [
'[mypy.plugins.django-stubs]',
'\tsome_other_setting = setting',
'\tdjango_settings_module = my.module',
]
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
config_file.write('\n'.join(config_file_contents).expandtabs(4))
config_file.seek(0)
extracted = extract_django_settings_module(config_file.name)
assert extracted == 'my.module'