营养校准、后台美化、静态走对象存储 CDN、标题可编辑、admin 拼音搜索

- 菜品营养按食物成分表+烹饪方式校准(calibrate_nutrition 命令)
- 后台接入 jazzmin 主题美化,移除 Google Fonts(模板覆盖)
- 前端/后台静态资源指向 qnycdn 对象存储 CDN(vite base + DJANGO_STATIC_URL)
- 菜单标题学校名称/周数可编辑,日期随周数计算
- admin 搜索支持拼音首字母/全拼(Dish.pinyin 索引,pypinyin)
- Dockerfile 使用清华 pip 镜像源
This commit is contained in:
root
2026-08-06 21:11:14 +08:00
parent 75f214dc40
commit 65b52b052f
16 changed files with 805 additions and 16 deletions
+3 -1
View File
@@ -1,7 +1,9 @@
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
PIP_NO_CACHE_DIR=1 \
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn
WORKDIR /app
+60 -3
View File
@@ -32,6 +32,7 @@ ALLOWED_HOSTS = [h.strip() for h in os.environ.get('DJANGO_ALLOWED_HOSTS', '*').
# Application definition
INSTALLED_APPS = [
'jazzmin',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -59,7 +60,7 @@ ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -119,8 +120,9 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = 'static/'
STATIC_URL = os.environ.get('DJANGO_STATIC_URL', '/xx/admin/')
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
CORS_ALLOWED_ORIGINS = [
'http://localhost:5173',
@@ -129,6 +131,7 @@ CORS_ALLOWED_ORIGINS = [
'http://xxcdn.mymoyu.top',
'https://xx.mymoyu.top',
'https://xxcdn.mymoyu.top',
'https://qnycdn.mymoyu.top',
]
REST_FRAMEWORK = {
@@ -139,5 +142,59 @@ REST_FRAMEWORK = {
CSRF_TRUSTED_ORIGINS = [h.strip() for h in os.environ.get(
'DJANGO_CSRF_TRUSTED_ORIGINS',
'http://xx.mymoyu.top,http://xxcdn.mymoyu.top,https://xx.mymoyu.top,https://xxcdn.mymoyu.top',
'http://xx.mymoyu.top,http://xxcdn.mymoyu.top,https://xx.mymoyu.top,https://xxcdn.mymoyu.top,https://qnycdn.mymoyu.top',
).split(',')]
JAZZMIN_SETTINGS = {
'site_title': '学校订餐菜单管理系统',
'site_header': '学校订餐菜单生成器',
'site_brand': '订餐菜单管理',
'welcome_sign': '欢迎使用学校订餐菜单管理系统',
'copyright': '上海外国语大学附属宝山双语学校',
'search_model': ['meals.Dish', 'auth.User'],
'show_sidebar': True,
'navigation_expanded': True,
'hide_apps': [],
'hide_models': [],
'icons': {
'meals.Dish': 'fas fa-utensils',
'auth.User': 'fas fa-user',
'auth.Group': 'fas fa-users',
},
'default_icon_parents': 'fas fa-chevron-circle-right',
'default_icon_children': 'fas fa-circle',
'show_ui_builder': False,
'changeform_format': 'horizontal_tabs',
'related_modal_active': True,
'custom_css': None,
'custom_js': None,
'theme': 'flatly',
'dark_mode_theme': 'darkly',
'header_classes': 'bg-primary',
'order_with_respect_to': ['meals', 'auth'],
}
JAZZMIN_UI_TWEAKS = {
'navbar_small_text': False,
'footer_small_text': False,
'body_small_text': False,
'brand_small_text': False,
'brand_colour': False,
'accent': 'accent-primary',
'navbar': 'navbar-white navbar-light',
'no_navbar_border': False,
'navbar_fixed': True,
'layout_boxed': False,
'footer_fixed': False,
'sidebar_fixed': True,
'sidebar': 'sidebar-dark-primary',
'sidebar_nav_small_text': False,
'sidebar_disable_expand': False,
'sidebar_nav_child_indent': False,
'sidebar_nav_compact_style': False,
'sidebar_nav_legacy_style': False,
'sidebar_nav_flat_style': False,
'theme': 'flatly',
'dark_mode_theme': 'darkly',
'button_classes': {'primary': 'btn-outline-primary', 'secondary': 'btn-outline-secondary', 'info': 'btn-outline-info', 'warning': 'btn-outline-warning', 'danger': 'btn-outline-danger', 'success': 'btn-outline-success'},
}
+31 -3
View File
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.utils.html import format_html, mark_safe
from .models import Dish
@@ -15,10 +16,10 @@ def deactivate_dishes(modeladmin, request, queryset):
@admin.register(Dish)
class DishAdmin(admin.ModelAdmin):
list_display = ('name', 'cuisine', 'dish_type', 'ingredient_detail', 'protein', 'fat', 'calorie', 'is_active', 'created_at')
list_display = ('name', 'cuisine', 'dish_type_display', 'ingredient_detail', 'protein', 'fat', 'calorie', 'nutri_badge', 'is_active', 'created_at')
list_filter = ('dish_type', 'cuisine', 'is_active')
search_fields = ('name', 'ingredient_detail', 'cuisine')
list_editable = ('cuisine', 'dish_type', 'ingredient_detail', 'protein', 'fat', 'calorie', 'is_active')
search_fields = ('name', 'ingredient_detail', 'cuisine', 'pinyin')
list_editable = ('cuisine', 'ingredient_detail', 'protein', 'fat', 'calorie', 'is_active')
list_per_page = 50
actions = [activate_dishes, deactivate_dishes]
fieldsets = (
@@ -26,3 +27,30 @@ class DishAdmin(admin.ModelAdmin):
('营养信息', {'fields': ('ingredient_detail', 'protein', 'fat', 'calorie')}),
('其他', {'fields': ('description',), 'classes': ('collapse',)}),
)
@admin.display(description='营养提示', ordering='calorie')
def nutri_badge(self, obj):
badges = []
if obj.dish_type == 'meat' and obj.fat >= 20:
badges.append('<span class="badge badge-danger">高脂</span>')
if obj.dish_type == 'meat' and obj.calorie >= 300:
badges.append('<span class="badge badge-warning">高热量</span>')
if obj.dish_type in ('meat', 'veg') and obj.protein >= 15:
badges.append('<span class="badge badge-success">高蛋白</span>')
if obj.dish_type == 'veg' and obj.fat <= 3 and obj.calorie <= 60:
badges.append('<span class="badge badge-info">清淡</span>')
return mark_safe('&nbsp;'.join(badges)) if badges else '—'
@admin.display(description='类型')
def dish_type_display(self, obj):
colors = {'meat': '#d9534f', 'veg': '#5cb85c', 'soup': '#5bc0de', 'staple': '#f0ad4e'}
return format_html(
'<span style="color:{};font-weight:600">{}</span>',
colors.get(obj.dish_type, '#999'),
dict(Dish.TYPE_CHOICES).get(obj.dish_type, obj.dish_type),
)
admin.site.site_header = '学校订餐菜单管理系统'
admin.site.site_title = '学校订餐菜单管理'
admin.site.index_title = '菜单管理后台'
@@ -0,0 +1,27 @@
from django.db import migrations, models
from pypinyin import Style, lazy_pinyin
def fill_pinyin(apps, schema_editor):
Dish = apps.get_model('meals', 'Dish')
for dish in Dish.objects.all():
full = ''.join(lazy_pinyin(dish.name))
initials = ''.join(lazy_pinyin(dish.name, style=Style.FIRST_LETTER))
dish.pinyin = f'{full} {initials}'
dish.save(update_fields=['pinyin'])
class Migration(migrations.Migration):
dependencies = [
('meals', '0004_dish_cuisine'),
]
operations = [
migrations.AddField(
model_name='dish',
name='pinyin',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='拼音索引'),
),
migrations.RunPython(fill_pinyin, migrations.RunPython.noop),
]
+10
View File
@@ -1,5 +1,7 @@
from django.db import models
from pypinyin import Style, lazy_pinyin
class Dish(models.Model):
TYPE_CHOICES = [
@@ -29,6 +31,7 @@ class Dish(models.Model):
calorie = models.IntegerField('热量(kcal)', default=0)
description = models.CharField('备注', max_length=200, blank=True)
is_active = models.BooleanField('启用', default=True)
pinyin = models.CharField('拼音索引', max_length=200, blank=True, editable=False)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
@@ -38,3 +41,10 @@ class Dish(models.Model):
def __str__(self):
return f'{self.get_dish_type_display()}: {self.name}'
def save(self, *args, **kwargs):
if self.name:
full = ''.join(lazy_pinyin(self.name))
initials = ''.join(lazy_pinyin(self.name, style=Style.FIRST_LETTER))
self.pinyin = f'{full} {initials}'
super().save(*args, **kwargs)
+19 -7
View File
@@ -223,23 +223,35 @@ def generate_week_menu(payload, menu_key='a'):
return week
def build_menu_title(label):
today = date.today()
week_no = today.isocalendar().week
monday = today - timedelta(days=today.weekday())
def build_menu_title(label, school_name=None, week_no=None):
school_name = school_name or SCHOOL_NAME
iso = date.today().isocalendar()
try:
week_no = int(week_no) if week_no is not None else iso.week
except (TypeError, ValueError):
week_no = iso.week
if not 1 <= week_no <= 53:
week_no = iso.week
try:
monday = date.fromisocalendar(iso.year, week_no, 1)
except ValueError:
monday = date.today() - timedelta(days=date.today().weekday())
friday = monday + timedelta(days=4)
date_range = f'{monday.month}.{monday.day}--{friday.month}.{friday.day}'
return f'{SCHOOL_NAME}[第{week_no}周]{date_range}营养{label}菜单'
return f'{school_name}[第{week_no}周]{date_range}营养{label}菜单'
@api_view(['POST'])
def generate_menu(request):
data = request.data
school = data.get('school_name') or SCHOOL_NAME
week_no = data.get('week_no')
menus = {}
for label in ('A', 'B'):
key = label.lower()
menus[key] = {
'title': build_menu_title(label),
'week': generate_week_menu(request.data, menu_key=key),
'title': build_menu_title(label, school, week_no),
'week': generate_week_menu(data, menu_key=key),
}
return Response(menus)
+2
View File
@@ -3,3 +3,5 @@ djangorestframework==3.17.1
django-cors-headers==4.9.0
openpyxl==3.1.5
gunicorn==23.0.0
django-jazzmin==3.0.5
pypinyin==0.54.0
+429
View File
@@ -0,0 +1,429 @@
{% load i18n static jazzmin admin_urls %}
{% get_current_language as LANGUAGE_CODE %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{% get_jazzmin_settings request as jazzmin_settings %}
{% get_jazzmin_ui_tweaks as jazzmin_ui %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<!-- Prevent admin panel being crawled by search engines -->
<meta name="robots" content="none, noarchive">
<script>
(function() {
var mode = localStorage.getItem('jazzmin-theme-mode') || '{{ jazzmin_ui.default_theme_mode|default:"light" }}';
if (mode === 'auto') {
mode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-bs-theme', mode);
})();
</script>
<title>{% block title %}{{ title }} | {{ jazzmin_settings.site_title }}{% endblock %}</title>
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="{% static "vendor/fontawesome-free/css/all.min.css" %}">
<!-- Bootstrap and adminLTE -->
<link rel="stylesheet" href="{% static "vendor/adminlte/css/adminlte.min.css" %}" id="adminlte-css">
<!-- Bootswatch theme -->
<link rel="stylesheet" {% if jazzmin_ui.theme.name != 'default' %}href="{{ jazzmin_ui.theme.src }}"{% endif %} id="jazzmin-theme" data-theme-base="{% static 'vendor/bootswatch' %}" />
{% if jazzmin_settings.show_theme_chooser %}
<script>
(function() {
var savedTheme = localStorage.getItem('jazzmin-theme');
if (savedTheme && savedTheme !== 'default') {
var link = document.getElementById('jazzmin-theme');
link.setAttribute('href', link.getAttribute('data-theme-base') + '/' + savedTheme + '/bootstrap.min.css');
}
})();
</script>
{% endif %}
<!-- Custom fixes for django -->
<link rel="stylesheet" href="{% static "jazzmin/css/main.css" %}">
{% if jazzmin_settings.custom_css %}
<!-- Custom CSS -->
<link rel="stylesheet" href="{% static jazzmin_settings.custom_css %}">
{% endif %}
<!-- favicons -->
<link rel="shortcut icon" href="{% static jazzmin_settings.site_icon %}" type="image/png">
<link rel="icon" href="{% static jazzmin_settings.site_icon %}" sizes="32x32" type="image/png">
{% if jazzmin_settings.use_google_fonts_cdn %}
<!-- Google Font: Source Sans Pro -->
{% endif %}
{% block extrastyle %} {% endblock %}
{% block extrahead %} {% endblock %}
</head>
<body class="layout-fixed{% if jazzmin_settings.show_sidebar %} sidebar-expand-lg sidebar-open{% else %} no-sidebar{% endif %} bg-body-tertiary {% sidebar_status request %} {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %} {{ jazzmin_ui.body_classes }}" data-admin-utc-offset="{% now "Z" %}">
<div class="app-wrapper">
{% if not is_popup %}
<nav class="app-header navbar navbar-expand bg-body {{ jazzmin_ui.navbar_classes }}" id="jazzy-navbar">
<div class="container-fluid">
<ul class="navbar-nav">
{% if jazzmin_settings.show_sidebar %}
<li class="nav-item">
<a class="nav-link" data-lte-toggle="sidebar" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
{% else %}
<li class="nav-item">
<a href="{% url 'admin:index' %}" class="brand-link">
<img src="{% static jazzmin_settings.site_logo %}" alt="{{ jazzmin_settings.site_header }} Logo" class="{{ jazzmin_settings.site_logo_classes }} brand-image" style="opacity: .8; margin: 0 0 0 5px;">
</a>
</li>
{% endif %}
{% get_top_menu user request.current_app|default:"admin" as top_menu %}
{% for link in top_menu %}
<li class="nav-item d-none d-sm-inline-block{% if link.children %} dropdown{% endif %}">
{% if link.children %}
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ link.name }}
</a>
<div class="dropdown-menu">
{% for child in link.children %}
<a class="dropdown-item" href="{{ child.url }}" {% if link.new_window %}target="_blank"{% endif %}>{{ child.name }}</a>
{% endfor %}
</div>
{% else %}
<a href="{{ link.url }}" class="nav-link" {% if link.new_window %}target="_blank"{% endif %}>{{ link.name }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% if jazzmin_settings.search_model %}
{% for search_model in jazzmin_settings.search_models_parsed %}
<form action="{{ search_model.search_url }}" method="GET" class="d-flex ms-2">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" name="q" type="search" placeholder="{% trans 'Search' %} {{ search_model.search_name }}..." aria-label="{% trans 'Search' %} {{ search_model.search_name }}...">
<button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</form>
{% endfor %}
{% endif %}
<ul class="navbar-nav ms-auto">
{% if jazzmin_settings.show_ui_builder %}
<li class="nav-item">
<a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#" role="button">
<i class="fas fa-th-large"></i>
</a>
</li>
{% endif %}
{% if 'django.contrib.admindocs'|app_is_installed %}
<li class="nav-item">
<a class="nav-link" href="{% url 'django-admindocs-docroot' %}" role="button">
<i class="fas fa-book"></i>
</a>
</li>
{% endif %}
{% if jazzmin_settings.show_theme_chooser %}
<li class="nav-item dropdown">
<a class="nav-link btn" data-bs-toggle="dropdown" href="#" title="{% trans 'Theme' %}">
<i class="fas fa-palette"></i>
</a>
<div class="dropdown-menu dropdown-menu-end" id="jazzy-theme-chooser">
<span class="dropdown-header">{% trans 'Theme' %}</span>
<div class="dropdown-divider"></div>
<div class="px-3 pb-2">
<select class="form-select form-select-sm" id="jazzmin-theme-select">
{% for theme_name in jazzmin_ui.theme_list %}
<option value="{{ theme_name }}" {% if theme_name == jazzmin_ui.theme.name %}selected{% endif %}>
{{ theme_name|title }}
</option>
{% endfor %}
</select>
</div>
<div class="dropdown-divider"></div>
<span class="dropdown-header">{% trans 'Color Scheme' %}</span>
<div class="dropdown-divider"></div>
<div class="px-3 pb-2">
<select class="form-select form-select-sm" id="jazzmin-mode-select">
<option value="light">{% trans 'Light' %}</option>
<option value="dark">{% trans 'Dark' %}</option>
<option value="auto">{% trans 'Auto' %}</option>
</select>
</div>
</div>
</li>
{% endif %}
{% if jazzmin_settings.language_chooser %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
<li class="nav-item dropdown">
<a class="nav-link btn" data-bs-toggle="dropdown" href="#" title="Choose language">
<i class="fas fa-globe" aria-hidden="true"></i>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end" id="jazzy-languagemenu">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path|remove_lang:LANGUAGE_CODE }}" />
{% for language in languages %}
<button
type="submit"
name="language"
value="{{ language.code }}"
class="dropdown-item {% if language.code == LANGUAGE_CODE %}active{% endif %}"
lang="{{ language.code }}"
>
{{ language.name_local|title }}
</button>
{% endfor %}
</form>
</div>
</li>
{% endif %}
<li class="nav-item dropdown">
<a class="nav-link btn" data-bs-toggle="dropdown" href="#" title="{{ request.user }}">
<i class="far fa-user" aria-hidden="true"></i>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end" id="jazzy-usermenu">
<span class="dropdown-header">{% trans 'Account' %}</span>
<div class="dropdown-divider"></div>
<a href="{% url 'admin:password_change' %}" class="dropdown-item">
<i class="fas fa-key me-2"></i> {% trans 'Change password' %}
</a>
<div class="dropdown-divider"></div>
<form id="logout-form" method="post" action="{% url 'admin:logout' %}">
{% csrf_token %}
<button type="submit" class="dropdown-item"><i class="fas fa-users me-2"></i> {% translate 'Log out' %}</button>
</form>
{% get_user_menu user request.current_app|default:"admin" as user_menu %}
{% for link in user_menu %}
<div class="dropdown-divider"></div>
<a href="{{ link.url }}" class="dropdown-item" {% if link.new_window %}target="_blank"{% endif %}>
<i class="{{ link.icon }} me-2"></i> {% trans link.name %}
</a>
{% endfor %}
<div class="dropdown-divider"></div>
{% if perms|can_view_self %}
<a href="{% jazzy_admin_url request.user request.current_app|default:"admin" %}" class="dropdown-item dropdown-footer">{% trans 'See Profile' %}</a>
{% endif %}
</div>
</li>
</ul>
</div>
</nav>
{% block sidebar %}
{% if jazzmin_settings.show_sidebar %}
{% get_side_menu as side_menu_list %}
<aside class="app-sidebar bg-body-secondary shadow {{ jazzmin_ui.sidebar_classes }}" id="jazzy-sidebar" data-bs-theme="dark">
<div class="sidebar-brand">
<a href="{% url 'admin:index' %}" class="brand-link {{ jazzmin_ui.brand_classes }}" id="jazzy-logo">
<img src="{% static jazzmin_settings.site_logo %}" alt="{{ jazzmin_settings.site_header }} Logo" class="{{ jazzmin_settings.site_logo_classes }} brand-image opacity-75 shadow">
<span class="brand-text fw-light">{{ jazzmin_settings.site_brand }}</span>
</a>
</div>
<div class="sidebar-wrapper">
<div class="user-panel d-flex">
<div class="image">
{% if jazzmin_settings|has_jazzmin_setting:"user_avatar" %}
<img src="{% get_user_avatar request.user %}" width="160px" class="img-circle elevation-2" alt="User Image">
{% else %}
<i class="fas fa-inverse user-profile fa-user-circle"></i>
{% endif %}
</div>
<div class="info">
{% if perms|can_view_self %}
<a href="{% jazzy_admin_url request.user request.current_app|default:"admin" %}" class="d-block">{{ request.user }}</a>
{% else %}
<span class="d-block opacity-75">{{ request.user }}</span>
{% endif %}
</div>
</div>
<nav class="mt-2">
<ul class="nav sidebar-menu flex-column {{ jazzmin_ui.sidebar_list_classes }}" data-lte-toggle="treeview" role="navigation" aria-label="Main navigation" data-accordion="false" id="jazzy-navigation">
<li class="nav-item">
<a href="{% url 'admin:index' %}" class="nav-link">
<i class="nav-icon fas fa-th-large"></i>
<p>{% trans 'Dashboard' %}</p>
</a>
</li>
{% if jazzmin_settings.navigation_expanded %}
{% for app in side_menu_list %}
<li class="nav-header">{{ app.name }}</li>
{% for model in app.models %}
<li class="nav-item">
{% if model.url %}
<a href="{{ model.url }}" class="nav-link">
<i class="nav-icon {{ model.icon }}"></i> <p>{{ model.name }}</p>
</a>
{% else %}
<span class="nav-link disabled">
<i class="nav-icon {{ model.icon }}"></i> <p>{{ model.name }}</p>
</span>
{% endif %}
</li>
{% endfor %}
{% endfor %}
{% else %}
{% for app in side_menu_list %}
<li class="nav-item has-treeview">
<a href="#" class="nav-link">
<i class="nav-icon {{ app.icon }}"></i>
<p>{{ app.name|truncatechars:21 }} <i class="nav-arrow fas fa-chevron-right" aria-hidden="true"></i></p>
</a>
<ul class="nav nav-treeview">
{% for model in app.models %}
<li class="nav-item">
<a href="{% if model.url %}{{ model.url }}{% else %}javascript:void(0){% endif %}" class="nav-link">
<i class="nav-icon {{ model.icon }}"></i>
<p>{{ model.name }}</p>
</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
{% endif %}
</ul>
</nav>
</div>
</aside>
{% endif %}
{% endblock %}
{% endif %}
<main class="app-main" {% if is_popup %}style="margin-left:0; padding-top: 20px;"{% endif %}>
{% block page_content %}
{% if not is_popup %}
<div class="app-content-header">
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<h3 class="mb-0">{% block content_title %}{% endblock %}</h3>
</div>
<div class="col-sm-6">
<div class="float-sm-end">{% block breadcrumbs %}{% endblock %}</div>
{% block page_actions %}{% endblock %}
</div>
</div>
</div>
</div>
{% endif %}
<div class="app-content">
<div class="container-fluid">
{% block messages %}
{% for message in messages %}
{% if message.tags == 'success' %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="icon fa fa-check"></i> {{ message|capfirst }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% elif message.tags == 'error' %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="icon fa fa-ban"></i> {{ message|capfirst }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% elif message.tags == 'warning' %}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="icon fa fa-exclamation-triangle"></i> {{ message|capfirst }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% elif message.tags == 'info' %}
<div class="alert alert-info alert-dismissible fade show" role="alert">
<i class="icon fa fa-info"></i> {{ message|capfirst }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
{% endfor %}
{% endblock messages %}
<div class="row">
{% block content %} {% endblock %}
</div>
</div>
</div>
{% endblock %}
</main>
{% block footer %}
{% if not is_popup %}
<footer class="app-footer {{ jazzmin_ui.footer_classes }}">
<div class="float-end d-none d-sm-inline">
<b>{% trans 'Jazzmin version' %}</b> {% get_jazzmin_version %}
</div>
{% autoescape off %}
<strong>{% trans 'Copyright' %} &copy; {% now 'Y' %} {{ jazzmin_settings.copyright }}.</strong> {% trans 'All rights reserved.' %}
{% endautoescape %}
</footer>
{% if jazzmin_settings.show_ui_builder %}
{% include 'jazzmin/includes/ui_builder_panel.html' %}
{% endif %}
{% endif %}
{% endblock %}
</div>
{% if jazzmin_settings.show_ui_builder %}
<div id="codeBox" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans 'UI Configuration' %}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{% trans 'Copy this info your settings file to persist these UI changes' %}</p>
<pre><code></code></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn {{ jazzmin_ui.button_classes.danger }}" data-bs-dismiss="modal">{% trans 'Close' %}</button>
</div>
</div>
</div>
</div>
{% endif %}
<!-- jQuery -->
<script src="{% static "admin/js/vendor/jquery/jquery.js" %}"></script>
<!-- Bootstrap 5 -->
<script src="{% static "vendor/bootstrap/js/bootstrap.bundle.min.js" %}"></script>
<!-- AdminLTE App -->
<script src="{% static "vendor/adminlte/js/adminlte.min.js" %}"></script>
<!-- Django customisations -->
<script src="{% static "jazzmin/js/main.js" %}"></script>
{% if jazzmin_settings.custom_js %}
<script src="{% static jazzmin_settings.custom_js %}"></script>
{% endif %}
{% if jazzmin_settings.show_ui_builder %}
<script>
window.ui_changes = {{ jazzmin_ui.raw|as_json|safe }};
</script>
<script src="{% static "jazzmin/js/ui-builder.js" %}"></script>
{% endif %}
{% block extrajs %}{% endblock %}
</body>
</html>
+90
View File
@@ -0,0 +1,90 @@
{% load i18n static jazzmin admin_urls %}
{% get_current_language as LANGUAGE_CODE %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{% get_jazzmin_settings request as jazzmin_settings %}
{% get_jazzmin_ui_tweaks as jazzmin_ui %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<script>
(function() {
var mode = localStorage.getItem('jazzmin-theme-mode') || '{{ jazzmin_ui.default_theme_mode|default:"light" }}';
if (mode === 'auto') {
mode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-bs-theme', mode);
})();
</script>
<title>{% block title %}{{ title }} | {{ jazzmin_settings.site_title }}{% endblock %}</title>
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="{% static "vendor/fontawesome-free/css/all.min.css" %}">
<!-- Bootstrap and adminLTE -->
<link rel="stylesheet" href="{% static "vendor/adminlte/css/adminlte.min.css" %}">
<!-- Bootswatch theme -->
{% if jazzmin_ui.theme.name != 'default' %}
<link rel="stylesheet" href="{{ jazzmin_ui.theme.src }}" id="jazzmin-theme" />
{% endif %}
<!-- Custom fixes for django -->
<link rel="stylesheet" href="{% static "jazzmin/css/main.css" %}">
{% if jazzmin_settings.custom_css %}
<!-- Custom CSS -->
<link rel="stylesheet" href="{% static jazzmin_settings.custom_css %}">
{% endif %}
<!-- favicons -->
<link rel="shortcut icon" href="{% static jazzmin_settings.site_icon %}" type="image/png">
<link rel="icon" href="{% static jazzmin_settings.site_icon %}" sizes="32x32" type="image/png">
{% if jazzmin_settings.use_google_fonts_cdn %}
<!-- Google Font: Source Sans Pro -->
{% endif %}
{% block extrastyle %} {% endblock %}
{% block extrahead %} {% endblock %}
</head>
<body class="login-page bg-body-secondary">
<div class="login-box">
<div class="login-logo">
{% if jazzmin_settings.login_logo_dark %}
<img class="logo-light" src="{% static jazzmin_settings.login_logo %}" alt="{{ jazzmin_settings.site_header }}">
<img class="logo-dark" src="{% static jazzmin_settings.login_logo_dark %}" alt="{{ jazzmin_settings.site_header }}">
{% elif jazzmin_settings.login_logo %}
<img src="{% static jazzmin_settings.login_logo %}" alt="{{ jazzmin_settings.site_header }}">
{% else %}
<a href="{% url 'admin:index' %}"><b>{{ jazzmin_settings.site_header }}</b></a>
{% endif %}
</div>
<div class="card">
<div class="card-body login-card-body">
{% block content %} {% endblock %}
</div>
</div>
</div>
<!-- jQuery -->
<script src="{% static "admin/js/vendor/jquery/jquery.js" %}"></script>
<!-- Bootstrap 5 -->
<script src="{% static 'vendor/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
<!-- AdminLTE App -->
<script src="{% static 'vendor/adminlte/js/adminlte.min.js' %}"></script>
{% if jazzmin_settings.custom_js %}
<script src="{% static jazzmin_settings.custom_js %}"></script>
{% endif %}
</body>
</html>