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:
parent
b90820eb7a
commit
95a7e14ada
0
ietf/admin/__init__.py
Normal file
0
ietf/admin/__init__.py
Normal file
6
ietf/admin/apps.py
Normal file
6
ietf/admin/apps.py
Normal 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
15
ietf/admin/sites.py
Normal 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">δ</span>')
|
|
@ -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):
|
||||
|
|
|
@ -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
15
ietf/api/apps.py
Normal 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()
|
|
@ -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 = [
|
||||
|
|
|
@ -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',
|
||||
|
|
27
ietf/templates/admin/base.html
Normal file
27
ietf/templates/admin/base.html
Normal 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 %}
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue