diff --git a/README.md b/README.md index 1fff660b6..39e98b84b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,28 @@ We rely on different `django` and `mypy` versions: | 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 +## Features + +### Type checking of Model Meta attributes + +By inheriting from the `TypedModelMeta` class, you can ensure you're using correct types for +attributes: + +```python +from django.db import models +from django_stubs_ext.db.models import TypedModelMeta + +class MyModel(models.Model): + example = models.CharField(max_length=100) + + class Meta(TypedModelMeta): + ordering = ["example"] + constraints = [ + models.UniqueConstraint(fields=["example"], name="unique_example"), + ] +``` + + ## FAQ ### Is this an official Django project? diff --git a/django_stubs_ext/django_stubs_ext/db/models.py b/django_stubs_ext/django_stubs_ext/db/models.py index 060ef4f77..1efeae7af 100644 --- a/django_stubs_ext/django_stubs_ext/db/models.py +++ b/django_stubs_ext/django_stubs_ext/db/models.py @@ -8,23 +8,12 @@ from django_stubs_ext import StrOrPromise - class BaseModelMeta: + class TypedModelMeta: """ - Typed base class for Django Model `class Meta:` inner class. + Typed base class for Django Model `class Meta:` inner class. At runtime this is just an alias to `object`. Most attributes are the same as `django.db.models.options.Options`. Options has some additional attributes and some values are normalized by Django. - - Usage:: - - from django.db import models - from django_stubs_ext.db.models import BaseModelMeta - - class MyModel(models.Model): - example = models.CharField(max_length=100) - - class Meta(BaseModelMeta): - ordering = ["example"] """ abstract: ClassVar[bool] # default: False @@ -53,4 +42,4 @@ class Meta(BaseModelMeta): verbose_name_plural: ClassVar[StrOrPromise] else: - BaseModelMeta = object + TypedModelMeta = object diff --git a/tests/typecheck/managers/test_managers.yml b/tests/typecheck/managers/test_managers.yml index 107a5c1dc..1542aceef 100644 --- a/tests/typecheck/managers/test_managers.yml +++ b/tests/typecheck/managers/test_managers.yml @@ -110,7 +110,7 @@ content: | from typing import TypeVar from django.db import models - from django_stubs_ext.db.models import BaseModelMeta + from django_stubs_ext.db.models import TypedModelMeta _T = TypeVar('_T', bound=models.Model) class Manager1(models.Manager[_T]): @@ -118,7 +118,7 @@ class Manager2(models.Manager[_T]): pass class MyModel(models.Model): - class Meta(BaseModelMeta): + class Meta(TypedModelMeta): default_manager_name = 'm2' m1 = Manager1['MyModel']() m2 = Manager2['MyModel']() diff --git a/tests/typecheck/models/test_meta_options.yml b/tests/typecheck/models/test_meta_options.yml index 5667423ce..d63931920 100644 --- a/tests/typecheck/models/test_meta_options.yml +++ b/tests/typecheck/models/test_meta_options.yml @@ -49,9 +49,9 @@ content: | from django.db import models from django.contrib.postgres.fields import ArrayField - from django_stubs_ext.db.models import BaseModelMeta + from django_stubs_ext.db.models import TypedModelMeta class AbstractModel(models.Model): - class Meta(BaseModelMeta): + class Meta(TypedModelMeta): abstract = True class MyModel(AbstractModel): field = ArrayField(models.IntegerField(), default=[]) @@ -59,12 +59,12 @@ main: | from django.db import models from django.contrib.postgres.fields import ArrayField - from django_stubs_ext.db.models import BaseModelMeta + from django_stubs_ext.db.models import TypedModelMeta class MyModel(models.Model): example = models.CharField(max_length=100) - class Meta(BaseModelMeta): - abstract = 7 # E: Incompatible types in assignment (expression has type "int", base class "BaseModelMeta" defined the type as "bool") - verbose_name = ['test'] # E: Incompatible types in assignment (expression has type "List[str]", base class "BaseModelMeta" defined the type as "Union[str, _StrPromise]") - unique_together = {1: 2} # E: Incompatible types in assignment (expression has type "Dict[int, int]", base class "BaseModelMeta" defined the type as "Union[Sequence[Sequence[str]], Sequence[str]]") + class Meta(TypedModelMeta): + abstract = 7 # E: Incompatible types in assignment (expression has type "int", base class "TypedModelMeta" defined the type as "bool") + verbose_name = ['test'] # E: Incompatible types in assignment (expression has type "List[str]", base class "TypedModelMeta" defined the type as "Union[str, _StrPromise]") + unique_together = {1: 2} # E: Incompatible types in assignment (expression has type "Dict[int, int]", base class "TypedModelMeta" defined the type as "Union[Sequence[Sequence[str]], Sequence[str]]") unknown_attr = True # can't check this diff --git a/tests/typecheck/models/test_proxy_models.yml b/tests/typecheck/models/test_proxy_models.yml index 43ed2ea52..9357ade1e 100644 --- a/tests/typecheck/models/test_proxy_models.yml +++ b/tests/typecheck/models/test_proxy_models.yml @@ -12,12 +12,12 @@ - path: myapp/models.py content: | from django.db import models - from django_stubs_ext.db.models import BaseModelMeta + from django_stubs_ext.db.models import TypedModelMeta class Publisher(models.Model): pass class PublisherProxy(Publisher): - class Meta(BaseModelMeta): + class Meta(TypedModelMeta): proxy = True class Blog(models.Model): publisher = models.ForeignKey(to=PublisherProxy, on_delete=models.CASCADE)