Skip to content

Commit 1b67f7a

Browse files
committed
Add model-admin check
1 parent 7ff9f8f commit 1b67f7a

File tree

10 files changed

+103
-26
lines changed

10 files changed

+103
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
### Unreleased
44

5+
### 0.4.0
6+
57
- Add infra for rest framework serializers checks
6-
- Add checks drf-model-serializer-extra-kwargs, drf-model-serializer-meta-attribute
8+
- Add checks
9+
- drf-model-serializer-extra-kwargs
10+
- drf-model-serializer-meta-attribute
11+
- model-admin
712

813
### 0.3.0
914

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class MyModel(models.Model):
4949
- **extra-checks-config** - settings.EXTRA_CHECKS is valid config for django-extra-checks (always enabled).
5050
- **model-attribute** - Each Model in the project must have all attributes from `attrs` setting specified.
5151
- **model-meta-attribute** - Each Model.Meta in the project must have all attributes from `attrs` setting specified.
52+
- **model-admin** - Each model must be registered in admin.
5253
- **field-file-upload-to** - FileField/ImageField must have non empty `upload_to` argument.
5354
- **field-verbose-name** - All model's fields must have verbose name.
5455
- **field-verbose-name-gettext** - verbose_name must use gettext.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = django-extra-checks
3-
version = 0.3.0
3+
version = 0.4.0
44
author = Konstantin Alekseev
55
author_email = [email protected]
66
description = Collection of useful checks for Django Checks Framework

src/extra_checks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class CheckId(str, enum.Enum):
66
X001 = "extra-checks-config"
77
X010 = "model-attribute"
88
X011 = "model-meta-attribute"
9+
X012 = "model-admin"
910
X050 = "field-verbose-name"
1011
X051 = "field-verbose-name-gettext"
1112
X052 = "field-verbose-name-gettext-case"

src/extra_checks/checks/model_checks.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import django.core.checks
66
from django import forms
7+
from django.apps import apps
8+
from django.contrib.admin.sites import all_sites
79
from django.db import models
810
from django.db.models.options import DEFAULT_NAMES as META_ATTRS
911

@@ -62,10 +64,6 @@ def check_models(
6264
yield from check(field, field_ast=field_ast, model=model)
6365

6466

65-
class MetaAttrsForm(BaseCheckForm):
66-
attrs = forms.MultipleChoiceField(choices=[(o, o) for o in META_ATTRS])
67-
68-
6967
class CheckModel(BaseCheck):
7068
@abstractmethod
7169
def apply(
@@ -102,6 +100,10 @@ def apply(
102100
@registry.register(django.core.checks.Tags.models)
103101
class CheckModelMetaAttribute(CheckModel):
104102
Id = CheckId.X011
103+
104+
class MetaAttrsForm(BaseCheckForm):
105+
attrs = forms.MultipleChoiceField(choices=[(o, o) for o in META_ATTRS])
106+
105107
settings_form_class = MetaAttrsForm
106108

107109
def __init__(self, attrs: List[str], **kwargs: Any) -> None:
@@ -122,3 +124,33 @@ def apply(
122124
hint=f'Set "{attr}" attribute in Meta.',
123125
obj=model,
124126
)
127+
128+
129+
@registry.register(django.core.checks.Tags.models)
130+
class CheckModelAdmin(CheckModel):
131+
Id = CheckId.X012
132+
133+
class AdminForm(BaseCheckForm):
134+
def clean(self) -> dict:
135+
if not apps.is_installed("django.contrib.admin"):
136+
raise forms.ValidationError(
137+
"django.contrib.admin must be in INSTALLED_APPS."
138+
)
139+
return super().clean()
140+
141+
settings_form_class = AdminForm
142+
143+
def __init__(self, **kwargs: Any) -> None:
144+
super().__init__(**kwargs)
145+
self.models_with_admin = set()
146+
for admin_site in all_sites:
147+
for model_cls, admin_cls in admin_site._registry.items():
148+
self.models_with_admin.add(model_cls)
149+
for inline in admin_cls.inlines:
150+
self.models_with_admin.add(inline.model)
151+
152+
def apply(
153+
self, model: Type[models.Model], model_ast: ModelAST
154+
) -> Iterator[django.core.checks.CheckMessage]:
155+
if model not in self.models_with_admin:
156+
yield self.message("The model is not registered in admin.", obj=model)

src/extra_checks/checks/model_field_checks.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ def apply(
2121
raise NotImplementedError()
2222

2323

24-
class GettTextFuncForm(BaseCheckForm):
25-
gettext_func = forms.CharField(required=False)
26-
27-
2824
class GetTextMixin(BaseCheckMixin):
25+
class GettTextFuncForm(BaseCheckForm):
26+
gettext_func = forms.CharField(required=False)
27+
2928
settings_form_class = GettTextFuncForm
3029

3130
def __init__(self, gettext_func: str, **kwargs: Any) -> None:
@@ -167,16 +166,16 @@ def apply(
167166
)
168167

169168

170-
class CheckFieldForeignKeyIndexForm(BaseCheckForm):
171-
when = forms.ChoiceField(
172-
choices=[("unique_together", "unique_together"), ("always", "always")],
173-
required=False,
174-
)
175-
176-
177169
@registry.register(django.core.checks.Tags.models)
178170
class CheckFieldForeignKeyIndex(CheckModelField):
179171
Id = CheckId.X058
172+
173+
class CheckFieldForeignKeyIndexForm(BaseCheckForm):
174+
when = forms.ChoiceField(
175+
choices=[("unique_together", "unique_together"), ("always", "always")],
176+
required=False,
177+
)
178+
180179
settings_form_class = CheckFieldForeignKeyIndexForm
181180

182181
def __init__(self, when: str, **kwargs: Any) -> None:

tests/example/admin.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from django.contrib import admin
2+
3+
from .models import Article, Author
4+
5+
6+
class ArticleInline(admin.TabularInline):
7+
model = Article
8+
9+
10+
@admin.register(Author)
11+
class AuthorAdmin(admin.ModelAdmin):
12+
inlines = [
13+
ArticleInline,
14+
]

tests/example/models.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@
55
_ = gettext_lazy
66

77

8+
if True:
9+
# test that indentation doesn't break ast parser
10+
class Author(models.Model):
11+
first_name = models.CharField(max_length=100)
12+
last_name = models.CharField(max_length=100)
13+
14+
815
class Article(models.Model):
916
site = models.ForeignKey(Site, verbose_name="site", on_delete=models.CASCADE)
1017
title = models.CharField("article title", max_length=100)
1118
text = models.TextField(verbose_name="article text")
19+
author = models.ForeignKey(
20+
Author, related_name="articles", on_delete=models.CASCADE
21+
)
1222

1323
class Meta:
1424
verbose_name = "Site Article"
1525

1626

17-
if True:
18-
# test that indentation doesn't break ast parser
19-
class Author(models.Model):
20-
first_name = models.CharField(max_length=100)
21-
last_name = models.CharField(max_length=100)
22-
23-
2427
class NestedField(models.Field):
2528
"""Resembles fields like postgres ArrayField."""
2629

tests/settings.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
ALLOWED_HOSTS = ["*"]
44

5-
INSTALLED_APPS = ["django.contrib.sites", "tests.example", "extra_checks"]
5+
INSTALLED_APPS = [
6+
"django.contrib.sites",
7+
"django.contrib.admin",
8+
"django.contrib.auth",
9+
"django.contrib.contenttypes",
10+
"django.contrib.messages",
11+
"django.contrib.sessions",
12+
"tests.example",
13+
"extra_checks",
14+
]
615

716
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
817

tests/test_model_checks.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from extra_checks.checks import model_checks
4-
from tests.example.models import Article, Author
4+
from tests.example.models import Article, Author, ModelFieldTextNull
55

66

77
@pytest.fixture
@@ -67,3 +67,16 @@ def test_check_model_meta_attrs(test_case):
6767
messages = test_case.models(Author).run()
6868
assert len(messages) == 1
6969
assert messages[0].id == model_checks.CheckModelMetaAttribute.Id.name
70+
71+
72+
def test_admin_models(test_case):
73+
messages = (
74+
test_case.models(Article, Author)
75+
.settings({"checks": [model_checks.CheckModelAdmin.Id.value]})
76+
.check(model_checks.CheckModelAdmin)
77+
.run()
78+
)
79+
assert not messages
80+
messages = test_case.models(ModelFieldTextNull).run()
81+
assert len(messages) == 1
82+
assert messages[0].id == model_checks.CheckModelAdmin.Id.name

0 commit comments

Comments
 (0)