Skip to content

Commit 3959bba

Browse files
authored
Fallback to inspect.getmodule for models not exported in the app's models module (#66)
1 parent 35a984b commit 3959bba

File tree

5 files changed

+40
-2
lines changed

5 files changed

+40
-2
lines changed

src/django_autotyping/stubbing/django_context/django_context.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

3+
import inspect
34
from collections import defaultdict
5+
from types import ModuleType
46

57
from django.apps.registry import Apps
68
from django.conf import LazySettings
@@ -25,12 +27,26 @@ def __init__(self, apps: Apps, settings: LazySettings) -> None:
2527

2628
@staticmethod
2729
def _get_model_alias(model: ModelType) -> str:
28-
"""Return an alias of the model, by converting the app label to PascalCase and joining
30+
"""Return an alias of the model.
31+
32+
The alias is constructed by converting the app label to PascalCase and joining
2933
the app label to the model name.
3034
"""
3135
app_label = to_pascal(model._meta.app_label)
3236
return f"{app_label}{model.__name__}"
3337

38+
@staticmethod
39+
def _get_model_module(model: ModelType) -> ModuleType:
40+
"""Return the module object where the model class is exported or defined.
41+
42+
Use the models module of the app where the model is defined, and fallback
43+
to the actual module of the model class in case the model is not exported
44+
or present in the models module.
45+
"""
46+
if model._meta.app_config.models_module is not None:
47+
return model._meta.app_config.models_module
48+
return inspect.getmodule(model) # type: ignore
49+
3450
@property
3551
def models(self) -> list[ModelType]:
3652
"""All the defined models. Abstract models are not included."""
@@ -45,7 +61,7 @@ def model_imports(self) -> list[ImportItem]:
4561

4662
return [
4763
ImportItem(
48-
module_name=model._meta.app_config.models_module.__name__,
64+
module_name=self._get_model_module(model).__name__,
4965
obj_name=model.__name__,
5066
alias=self._get_model_alias(model) if self.is_duplicate(model) else None,
5167
)

tests/stubstestproj/appwithoutmodelsmodule/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AppwithoutmodelsmoduleConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "stubstestproj.appwithoutmodelsmodule"
7+
8+
def ready(self) -> None:
9+
# See https://github.com/Viicos/django-autotyping/issues/59 for more context:
10+
from .extra_models import ExtraModel
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.db import models
2+
3+
4+
class ExtraModel(models.Model):
5+
# `ModelBase.__new__` registers every model to the default `Apps` class,
6+
# even if the model is not defined/exported in the `AppConfig`'s models module.
7+
# This can lead to cases where `AppConfig.models_module` is `None` because
8+
# no models module exists, however models are still registered under this
9+
# specific `AppConfig`.
10+
# See https://github.com/Viicos/django-autotyping/issues/59 as an example.
11+
pass

tests/stubstestproj/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"stubstestproj.accounts",
3333
"stubstestproj.firstapp",
3434
"stubstestproj.secondapp",
35+
"stubstestproj.appwithoutmodelsmodule",
3536
]
3637

3738
MIDDLEWARE = [

0 commit comments

Comments
 (0)