【解決】sphinx-build時に「doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.」と言われる

起きたこと

djangoのソース(docstring)をsphinx-buildすると以下のエラーが起こりました。
 
 

WARNING: autodoc: failed to import module 'views' from module 'blogs'; the following exception was raised:
Traceback (most recent call last):
  File "/usr/local/python/lib/python3.8/site-packages/sphinx/ext/autodoc/importer.py", line 32, in import_module
    return importlib.import_module(modname)
  File "/usr/local/python/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/opt/myblog/blogs/views.py", line 3, in <module>
    from .models import *
  File "/opt/myblog/blogs/models.py", line 8, in <module>
    class TopicsTr(models.Model):
  File "/usr/local/python/lib/python3.8/site-packages/django/db/models/base.py", line 112, in __new__
    raise RuntimeError(
RuntimeError: Model class blogs.models.TopicsTr doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

 
 
 

Djangosphinx-buildする場合

 
前提として、conf.pyに以下を記載します。
 
 

import os
import sys
sys.path.insert(0, '/path/to/source')

import django
from django.conf import settings

settings.configure()
django.setup()

 
 
 

エラーの原因

 
エラーメッセージを見ると
 

RuntimeError: Model class blogs.models.TopicsTr doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

「blogs.models.TopicsTr」というモデルクラスのapp_labelがINSTALLED_APPSの中に定義されてない、といったところでしょうか。
ちなみにsettings.pyとmodels.pyは以下のようになっています。
 
 
settings.py

:

INSTALLED_APPS = [
    'blogs',
    'myauth',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    'widget_tweaks',
]

:

 
 
 
models.py

class TopicsTr(models.Model):

    id              = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title           = models.CharField(max_length=128, default='no title', null=False)
    created_at      = models.DateTimeField(default=timezone.now, null=False)
    last_update     = models.DateTimeField(default=timezone.now, null=False)
    isdraft         = models.BooleanField(default=False, null=False)
    thumbnail       = models.BigIntegerField(default=0, null=False)
    text            = models.TextField(null=True)
    likes           = models.IntegerField(default=0, null=False)

 
 
 
「blogs.models.TopicsTr」のパッケージ名であるblogsはDjangoアプリケーション名としてINSTALLED_APPSに定義されているのに、
なぜ認識されないのでしょうか。

ちなみにマニュアルには以下のように記載されています。

If a model is defined outside of an application in INSTALLED_APPS, it must declare which app it belongs to:

app_label = 'myapp'

If you want to represent a model with the format app_label.object_name or app_label.model_name you can use model._meta.label or model._meta.label_lower respectively.

Model Meta options | Django ドキュメント | Django
 
 
INSTALLED_APPSのアプリケーション以外でモデルを宣言する場合は
明示的にapp_labelを定義しないといけませんよー。と。
 
 
今回はblogsというアプリの中にあるモデルなので上記には該当しないはずなのですが・・・。
エラーメッセージから見るにそうとは認識されていないようです。
裏を返せばapp_labelを宣言してしまえば解決するのでは?
 
 
ということでmodels.pyを以下のように変更
 
models.py

class TopicsTr(models.Model):

+   class Meta:
+       abstract = True  # specify this model as an Abstract Model    
+       app_label = 'blogs'

    id              = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title           = models.CharField(max_length=128, default='no title', null=False)
    created_at      = models.DateTimeField(default=timezone.now, null=False)
    last_update     = models.DateTimeField(default=timezone.now, null=False)
    isdraft         = models.BooleanField(default=False, null=False)
    thumbnail       = models.BigIntegerField(default=0, null=False)
    text            = models.TextField(null=True)
    likes           = models.IntegerField(default=0, null=False)

 
前述のマニュアルにもあるとおり、app_labelはMetaクラス内で宣言します。
abstractを有効にするのも忘れずに。
 
これでINSTALLED_APPSのアプリに所属することを明示的に宣言しました。
sphinx-buildしてみます。
 
 

# sphinx-build -a -b html ./docs ./docs/html
Running Sphinx v3.1.1
loading translations [en]... done
loading pickled environment... done
building [mo]: all of 0 po files
building [html]: all source files
updating environment: 0 added, 1 changed, 0 removed
reading sources... [100%] blogs                                                                                                                                                
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] modules                                                                                                                                               
generating indices...  genindex py-modindexdone
highlighting module code... [100%] functools                                                                                                                                   
writing additional pages...  searchdone
copying static files... ... done
copying extra files... done
dumping search index in English (code: en)... done
dumping object inventory... done
build succeeded, 3 warnings.

The HTML pages are in docs/html.

 
 
うまくいきました。うーん。。。
ちなみにdjnago自体はapp_labelを宣言しなくても正常に動きます。
そりゃそうですよね、blogsアプリの中でモデル定義してるんだから。
 
 
ちなみにできたドキュメントはこんな感じです。

f:id:fclout:20200630094506p:plain


見にくいです。もっとちゃんとdocstring書かないとだめですね。
スタイルも工夫したいと思います。

結論

models.pyでapp_labelを明示的に宣言する。