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.
This commit is contained in:
Jennifer Richards 2024-08-07 11:16:40 -03:00 committed by GitHub
parent b90820eb7a
commit 95a7e14ada
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 85 additions and 30 deletions

0
ietf/admin/__init__.py Normal file
View file

6
ietf/admin/apps.py Normal file
View file

@ -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"

15
ietf/admin/sites.py Normal file
View file

@ -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 <span class="text-danger">&delta;</span>')

View file

@ -7,8 +7,10 @@ import re
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.bundle import Bundle
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):

View file

@ -30,4 +30,5 @@ class Serializer(): ...
class ToOneField(tastypie.fields.ToOneField): ...
class TimedeltaField(tastypie.fields.ApiField): ...
def populate_api_list() -> None: ...
def autodiscover() -> None: ...

15
ietf/api/apps.py Normal file
View file

@ -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()

View file

@ -11,6 +11,7 @@ from ietf.meeting import views as meeting_views
from ietf.submit import views as submit_views
from ietf.utils.urls import url
api.autodiscover()
urlpatterns = [

View file

@ -436,7 +436,7 @@ STATICFILES_DIRS = (
INSTALLED_APPS = [
# Django apps
'django.contrib.admin',
'ietf.admin', # replaces django.contrib.admin
'django.contrib.admindocs',
'django.contrib.auth',
'django.contrib.contenttypes',

View file

@ -0,0 +1,27 @@
{% extends 'admin/base.html' %}
{% load static %}
{% block extrastyle %}{{ block.super }}
{% if server_mode and server_mode != "production" %}
<style>
{# grab colors that match bootstrap so we don't have to import the css #}
html, :root{
--bs-danger-bg-subtle: #F8D7DAFF;
--bs-danger-text-emphasis: #58151CFF;
--bs-danger: #DC3545FF;
--bs-secondary: #6C757DFF;
--bs-primary-text-emphasis: #052C65FF;
}
html[data-theme="light"], :root {
--primary: var(--bs-danger-bg-subtle);
--secondary: var(--bs-danger-bg-subtle);
--accent: var(--bs-danger-text-emphasis);
--primary-fg: var(--bs-primary-text-emphasis);
--link-fg: var(--bs-danger-text-emphasis);
--header-color: var(--bs-secondary);
--breadcrumbs-fg: var(--bs-secondary);
--breadcrumbs-link-fg: var(--link-fg);
}
span.text-danger { color: var(--bs-danger); }
</style>
{% endif %}
{% endblock %}

View file

@ -20,8 +20,6 @@ from ietf.liaisons.sitemaps import LiaisonMap
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:

View file

@ -36,6 +36,7 @@ from django.urls import reverse as urlreverse
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 @@ class AdminTestCase(TestCase):
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)