Skip to content

Commit

Permalink
Rename BaseModelMeta to TypedModelMeta and document it (#1456)
Browse files Browse the repository at this point in the history
  • Loading branch information
intgr committed Apr 27, 2023
1 parent 3e81d11 commit feccb05
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 25 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
17 changes: 3 additions & 14 deletions django_stubs_ext/django_stubs_ext/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,4 +42,4 @@ class Meta(BaseModelMeta):
verbose_name_plural: ClassVar[StrOrPromise]

else:
BaseModelMeta = object
TypedModelMeta = object
4 changes: 2 additions & 2 deletions tests/typecheck/managers/test_managers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@
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]):
pass
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']()
Expand Down
14 changes: 7 additions & 7 deletions tests/typecheck/models/test_meta_options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,22 @@
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=[])
- case: base_model_meta_incompatible_types
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
4 changes: 2 additions & 2 deletions tests/typecheck/models/test_proxy_models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit feccb05

Please sign in to comment.