From 95a7e14ada82fd43eafed7218a28ba0554c81e69 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 7 Aug 2024 11:16:40 -0300 Subject: [PATCH] feat: dev mode admin + refactor api init (#7628) * feat: style admin site in dev mode * refactor: eliminate base_site.html * fix: remove debug * fix: commit missing __init__.py * refactor: make method static; fix tests * refactor: move api init to AppConfig.ready() Avoids interacting with the app registry before it's ready. --- ietf/admin/__init__.py | 0 ietf/admin/apps.py | 6 +++++ ietf/admin/sites.py | 15 ++++++++++++ ietf/api/__init__.py | 43 ++++++++++++++-------------------- ietf/api/__init__.pyi | 1 + ietf/api/apps.py | 15 ++++++++++++ ietf/api/urls.py | 1 + ietf/settings.py | 2 +- ietf/templates/admin/base.html | 27 +++++++++++++++++++++ ietf/urls.py | 2 -- ietf/utils/tests.py | 3 ++- 11 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 ietf/admin/__init__.py create mode 100644 ietf/admin/apps.py create mode 100644 ietf/admin/sites.py create mode 100644 ietf/api/apps.py create mode 100644 ietf/templates/admin/base.html diff --git a/ietf/admin/__init__.py b/ietf/admin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ietf/admin/apps.py b/ietf/admin/apps.py new file mode 100644 index 0000000000..20b762cfec --- /dev/null +++ b/ietf/admin/apps.py @@ -0,0 +1,6 @@ +# Copyright The IETF Trust 2024, All Rights Reserved +from django.contrib.admin import apps as admin_apps + + +class AdminConfig(admin_apps.AdminConfig): + default_site = "ietf.admin.sites.AdminSite" diff --git a/ietf/admin/sites.py b/ietf/admin/sites.py new file mode 100644 index 0000000000..69cb62ae20 --- /dev/null +++ b/ietf/admin/sites.py @@ -0,0 +1,15 @@ +# Copyright The IETF Trust 2024, All Rights Reserved +from django.contrib.admin import AdminSite as _AdminSite +from django.conf import settings +from django.utils.safestring import mark_safe + + +class AdminSite(_AdminSite): + site_title = "Datatracker admin" + + @staticmethod + def site_header(): + if settings.SERVER_MODE == "production": + return "Datatracker administration" + else: + return mark_safe('Datatracker administration δ') diff --git a/ietf/api/__init__.py b/ietf/api/__init__.py index 54b4b7424b..81f370121f 100644 --- a/ietf/api/__init__.py +++ b/ietf/api/__init__.py @@ -7,8 +7,10 @@ from urllib.parse import urlencode -from django.conf import settings +from django.apps import apps as django_apps from django.core.exceptions import ObjectDoesNotExist +from django.utils.module_loading import autodiscover_modules + import debug # pyflakes:ignore @@ -19,40 +21,29 @@ from tastypie.exceptions import ApiFieldError from tastypie.fields import ApiField + _api_list = [] -for _app in settings.INSTALLED_APPS: - _module_dict = globals() - if '.' in _app: - _root, _name = _app.split('.', 1) - if _root == 'ietf': - if not '.' in _name: - _api = Api(api_name=_name) - _module_dict[_name] = _api - _api_list.append((_name, _api)) + +def populate_api_list(): + for app_config in django_apps.get_app_configs(): + _module_dict = globals() + if '.' in app_config.name: + _root, _name = app_config.name.split('.', 1) + if _root == 'ietf': + if not '.' in _name: + _api = Api(api_name=_name) + _module_dict[_name] = _api + _api_list.append((_name, _api)) def autodiscover(): """ Auto-discover INSTALLED_APPS resources.py modules and fail silently when - not present. This forces an import on them to register any admin bits they + not present. This forces an import on them to register any resources they may want. """ + autodiscover_modules("resources") - from importlib import import_module - from django.conf import settings - from django.utils.module_loading import module_has_submodule - - for app in settings.INSTALLED_APPS: - mod = import_module(app) - # Attempt to import the app's admin module. - try: - import_module('%s.resources' % (app, )) - except: - # Decide whether to bubble up this error. If the app just - # doesn't have an admin module, we can ignore the error - # attempting to import it, otherwise we want it to bubble up. - if module_has_submodule(mod, "resources"): - raise class ModelResource(tastypie.resources.ModelResource): def generate_cache_key(self, *args, **kwargs): diff --git a/ietf/api/__init__.pyi b/ietf/api/__init__.pyi index 63d9bc513b..ededea90a7 100644 --- a/ietf/api/__init__.pyi +++ b/ietf/api/__init__.pyi @@ -30,4 +30,5 @@ class Serializer(): ... class ToOneField(tastypie.fields.ToOneField): ... class TimedeltaField(tastypie.fields.ApiField): ... +def populate_api_list() -> None: ... def autodiscover() -> None: ... diff --git a/ietf/api/apps.py b/ietf/api/apps.py new file mode 100644 index 0000000000..7eca094a62 --- /dev/null +++ b/ietf/api/apps.py @@ -0,0 +1,15 @@ +from django.apps import AppConfig +from . import populate_api_list + + +class ApiConfig(AppConfig): + name = "ietf.api" + + def ready(self): + """Hook to do init after the app registry is fully populated + + Importing models or accessing the app registry is ok here, but do not + interact with the database. See + https://docs.djangoproject.com/en/4.2/ref/applications/#django.apps.AppConfig.ready + """ + populate_api_list() diff --git a/ietf/api/urls.py b/ietf/api/urls.py index fb2184a3f0..3c0fb872c9 100644 --- a/ietf/api/urls.py +++ b/ietf/api/urls.py @@ -11,6 +11,7 @@ from ietf.submit import views as submit_views from ietf.utils.urls import url + api.autodiscover() urlpatterns = [ diff --git a/ietf/settings.py b/ietf/settings.py index 13b7506673..7572b15213 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -436,7 +436,7 @@ def skip_unreadable_post(record): INSTALLED_APPS = [ # Django apps - 'django.contrib.admin', + 'ietf.admin', # replaces django.contrib.admin 'django.contrib.admindocs', 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/ietf/templates/admin/base.html b/ietf/templates/admin/base.html new file mode 100644 index 0000000000..9ca7377a54 --- /dev/null +++ b/ietf/templates/admin/base.html @@ -0,0 +1,27 @@ +{% extends 'admin/base.html' %} +{% load static %} +{% block extrastyle %}{{ block.super }} + {% if server_mode and server_mode != "production" %} + + {% endif %} +{% endblock %} diff --git a/ietf/urls.py b/ietf/urls.py index 4b29a3aa81..90b161b530 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -20,8 +20,6 @@ from ietf.utils.urls import url -admin.autodiscover() - # sometimes, this code gets called more than once, which is an # that seems impossible to work around. try: diff --git a/ietf/utils/tests.py b/ietf/utils/tests.py index 476c257a38..d435583e89 100644 --- a/ietf/utils/tests.py +++ b/ietf/utils/tests.py @@ -36,6 +36,7 @@ import debug # pyflakes:ignore +from ietf.admin.sites import AdminSite from ietf.person.name import name_parts, unidecode_name from ietf.submit.tests import submission_file from ietf.utils.draft import PlaintextDraft, getmeta @@ -325,7 +326,7 @@ def test_all_model_admins_exist(self): User.objects.create_superuser('admin', 'admin@example.org', 'admin+password') self.client.login(username='admin', password='admin+password') rtop = self.client.get("/admin/") - self.assertContains(rtop, 'Django administration') + self.assertContains(rtop, AdminSite.site_header()) for name in self.apps: app_name = self.apps[name] self.assertContains(rtop, name)