123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- import copy
- import logging
- from typing import Dict, Any
- from django.conf import settings
- from django.templatetags.static import static
- from .utils import get_admin_url, get_model_meta
- logger = logging.getLogger(__name__)
- DEFAULT_SETTINGS: Dict[str, Any] = {
- # title of the window (Will default to current_admin_site.site_title)
- "site_title": None,
- # Title on the login screen (19 chars max) (will default to current_admin_site.site_header)
- "site_header": None,
- # Title on the brand (19 chars max) (will default to current_admin_site.site_header)
- "site_brand": None,
- # Relative path to logo for your site, used for brand on top left (must be present in static files)
- "site_logo": "vendor/adminlte/img/AdminLTELogo.png",
- # Relative path to logo for your site, used for login logo (must be present in static files. Defaults to site_logo)
- "login_logo": None,
- # Logo to use for login form in dark themes (must be present in static files. Defaults to login_logo)
- "login_logo_dark": None,
- # CSS classes that are applied to the logo
- "site_logo_classes": "img-circle",
- # Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px)
- "site_icon": None,
- # Welcome text on the login screen
- "welcome_sign": "Welcome",
- # Copyright on the footer
- "copyright": "",
- # The model admin to search from the search bar, search bar omitted if excluded
- "search_model": None,
- # Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
- "user_avatar": None,
- ############
- # Top Menu #
- ############
- # Links to put along the nav bar
- "topmenu_links": [],
- #############
- # User Menu #
- #############
- # Additional links to include in the user menu on the top right ('app' url type is not allowed)
- "usermenu_links": [],
- #############
- # Side Menu #
- #############
- # Whether to display the side menu
- "show_sidebar": True,
- # Whether to aut expand the menu
- "navigation_expanded": True,
- # Hide these apps when generating side menu e.g (auth)
- "hide_apps": [],
- # Hide these models when generating side menu (e.g auth.user)
- "hide_models": [],
- # List of apps to base side menu ordering off of
- "order_with_respect_to": [],
- # Custom links to append to side menu app groups, keyed on app name
- "custom_links": {},
- # Custom icons for side menu apps/models See the link below
- # https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,
- # 5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2
- # for the full list of 5.13.0 free icon classes
- "icons": {"auth": "fas fa-users-cog", "auth.user": "fas fa-user", "auth.Group": "fas fa-users"},
- # Icons that are used when one is not manually specified
- "default_icon_parents": "fas fa-chevron-circle-right",
- "default_icon_children": "fas fa-circle",
- #################
- # Related Modal #
- #################
- # Activate Bootstrap modal
- "related_modal_active": False,
- #############
- # UI Tweaks #
- #############
- # Relative paths to custom CSS/JS scripts (must be present in static files)
- "custom_css": None,
- "custom_js": None,
- # Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
- "use_google_fonts_cdn": True,
- # Whether to show the UI customizer on the sidebar
- "show_ui_builder": False,
- ###############
- # Change view #
- ###############
- # Render out the change view as a single form, or in tabs, current options are
- # - single
- # - horizontal_tabs (default)
- # - vertical_tabs
- # - collapsible
- # - carousel
- "changeform_format": "horizontal_tabs",
- # override change forms on a per modeladmin basis
- "changeform_format_overrides": {},
- # Add a language dropdown into the admin
- "language_chooser": False,
- }
- #######################################
- # Currently available UI tweaks #
- # Use the UI builder to generate this #
- #######################################
- DEFAULT_UI_TWEAKS: Dict[str, Any] = {
- # Small text on the top navbar
- "navbar_small_text": False,
- # Small text on the footer
- "footer_small_text": False,
- # Small text everywhere
- "body_small_text": False,
- # Small text on the brand/logo
- "brand_small_text": False,
- # brand/logo background colour
- "brand_colour": False,
- # Link colour
- "accent": "accent-primary",
- # topmenu colour
- "navbar": "navbar-white navbar-light",
- # topmenu border
- "no_navbar_border": False,
- # Make the top navbar sticky, keeping it in view as you scroll
- "navbar_fixed": False,
- # Whether to constrain the page to a box (leaving big margins at the side)
- "layout_boxed": False,
- # Make the footer sticky, keeping it in view all the time
- "footer_fixed": False,
- # Make the sidebar sticky, keeping it in view as you scroll
- "sidebar_fixed": False,
- # sidemenu colour
- "sidebar": "sidebar-dark-primary",
- # sidemenu small text
- "sidebar_nav_small_text": False,
- # Disable expanding on hover of collapsed sidebar
- "sidebar_disable_expand": False,
- # Indent child menu items on sidebar
- "sidebar_nav_child_indent": False,
- # Use a compact sidebar
- "sidebar_nav_compact_style": False,
- # Use the AdminLTE2 style sidebar
- "sidebar_nav_legacy_style": False,
- # Use a flat style sidebar
- "sidebar_nav_flat_style": False,
- # Bootstrap theme to use (default, or from bootswatch, see THEMES below)
- "theme": "default",
- # Theme to use instead if the user has opted for dark mode (e.g darkly/cyborg/slate/solar/superhero)
- "dark_mode_theme": None,
- # The classes/styles to use with buttons
- "button_classes": {
- "primary": "btn-primary",
- "secondary": "btn-secondary",
- "info": "btn-info",
- "warning": "btn-warning",
- "danger": "btn-danger",
- "success": "btn-success",
- },
- }
- THEMES = {
- # light themes
- "default": "vendor/bootswatch/default/bootstrap.min.css",
- "cerulean": "vendor/bootswatch/cerulean/bootstrap.min.css",
- "cosmo": "vendor/bootswatch/cosmo/bootstrap.min.css",
- "flatly": "vendor/bootswatch/flatly/bootstrap.min.css",
- "journal": "vendor/bootswatch/journal/bootstrap.min.css",
- "litera": "vendor/bootswatch/litera/bootstrap.min.css",
- "lumen": "vendor/bootswatch/lumen/bootstrap.min.css",
- "lux": "vendor/bootswatch/lux/bootstrap.min.css",
- "materia": "vendor/bootswatch/materia/bootstrap.min.css",
- "minty": "vendor/bootswatch/minty/bootstrap.min.css",
- "pulse": "vendor/bootswatch/pulse/bootstrap.min.css",
- "sandstone": "vendor/bootswatch/sandstone/bootstrap.min.css",
- "simplex": "vendor/bootswatch/simplex/bootstrap.min.css",
- "sketchy": "vendor/bootswatch/sketchy/bootstrap.min.css",
- "spacelab": "vendor/bootswatch/spacelab/bootstrap.min.css",
- "united": "vendor/bootswatch/united/bootstrap.min.css",
- "yeti": "vendor/bootswatch/yeti/bootstrap.min.css",
- # dark themes
- "darkly": "vendor/bootswatch/darkly/bootstrap.min.css",
- "cyborg": "vendor/bootswatch/cyborg/bootstrap.min.css",
- "slate": "vendor/bootswatch/slate/bootstrap.min.css",
- "solar": "vendor/bootswatch/solar/bootstrap.min.css",
- "superhero": "vendor/bootswatch/superhero/bootstrap.min.css",
- }
- DARK_THEMES = ("darkly", "cyborg", "slate", "solar", "superhero")
- CHANGEFORM_TEMPLATES = {
- "single": "jazzmin/includes/single.html",
- "carousel": "jazzmin/includes/carousel.html",
- "collapsible": "jazzmin/includes/collapsible.html",
- "horizontal_tabs": "jazzmin/includes/horizontal_tabs.html",
- "vertical_tabs": "jazzmin/includes/vertical_tabs.html",
- }
- def get_search_model_string(search_model: str) -> str:
- """
- Get a search model string for reversing an admin url.
- Ensure the model name is lower cased but remain the app name untouched.
- """
- app, model_name = search_model.split(".")
- return "{app}.{model_name}".format(app=app, model_name=model_name.lower())
- def get_settings() -> Dict:
- jazzmin_settings = copy.deepcopy(DEFAULT_SETTINGS)
- user_settings = {x: y for x, y in getattr(settings, "JAZZMIN_SETTINGS", {}).items() if y is not None}
- jazzmin_settings.update(user_settings)
- # Extract search model configuration from search_model setting
- if jazzmin_settings["search_model"]:
- if not isinstance(jazzmin_settings["search_model"], list):
- jazzmin_settings["search_model"] = [jazzmin_settings["search_model"]]
- jazzmin_settings["search_models_parsed"] = []
- for search_model in jazzmin_settings["search_model"]:
- jazzmin_search_model = {}
- jazzmin_search_model["search_url"] = get_admin_url(get_search_model_string(search_model))
- model_meta = get_model_meta(search_model)
- if model_meta:
- jazzmin_search_model["search_name"] = model_meta.verbose_name_plural.title()
- else:
- jazzmin_search_model["search_name"] = search_model.split(".")[-1] + "s"
- jazzmin_settings["search_models_parsed"].append(jazzmin_search_model)
- # Deal with single strings in hide_apps/hide_models and make sure we lower case 'em
- if type(jazzmin_settings["hide_apps"]) == str:
- jazzmin_settings["hide_apps"] = [jazzmin_settings["hide_apps"]]
- jazzmin_settings["hide_apps"] = [x.lower() for x in jazzmin_settings["hide_apps"]]
- if type(jazzmin_settings["hide_models"]) == str:
- jazzmin_settings["hide_models"] = [jazzmin_settings["hide_models"]]
- jazzmin_settings["hide_models"] = [x.lower() for x in jazzmin_settings["hide_models"]]
- # Ensure icon model names and classes are lower case
- jazzmin_settings["icons"] = {x.lower(): y.lower() for x, y in jazzmin_settings.get("icons", {}).items()}
- # Default the site icon using the site logo
- jazzmin_settings["site_icon"] = jazzmin_settings["site_icon"] or jazzmin_settings["site_logo"]
- # Default the login logo using the site logo
- jazzmin_settings["login_logo"] = jazzmin_settings["login_logo"] or jazzmin_settings["site_logo"]
- # Default the login logo dark using the login logo
- jazzmin_settings["login_logo_dark"] = jazzmin_settings["login_logo_dark"] or jazzmin_settings["login_logo"]
- # ensure all model names are lower cased
- jazzmin_settings["changeform_format_overrides"] = {
- x.lower(): y.lower() for x, y in jazzmin_settings.get("changeform_format_overrides", {}).items()
- }
- return jazzmin_settings
- def get_ui_tweaks() -> Dict:
- raw_tweaks = copy.deepcopy(DEFAULT_UI_TWEAKS)
- raw_tweaks.update(getattr(settings, "JAZZMIN_UI_TWEAKS", {}))
- tweaks = {x: y for x, y in raw_tweaks.items() if y not in (None, "", False)}
- # These options dont work well together
- if tweaks.get("layout_boxed"):
- tweaks.pop("navbar_fixed", None)
- tweaks.pop("footer_fixed", None)
- bool_map = {
- "navbar_small_text": "text-sm",
- "footer_small_text": "text-sm",
- "body_small_text": "text-sm",
- "brand_small_text": "text-sm",
- "sidebar_nav_small_text": "text-sm",
- "no_navbar_border": "border-bottom-0",
- "sidebar_disable_expand": "sidebar-no-expand",
- "sidebar_nav_child_indent": "nav-child-indent",
- "sidebar_nav_compact_style": "nav-compact",
- "sidebar_nav_legacy_style": "nav-legacy",
- "sidebar_nav_flat_style": "nav-flat",
- "layout_boxed": "layout-boxed",
- "sidebar_fixed": "layout-fixed",
- "navbar_fixed": "layout-navbar-fixed",
- "footer_fixed": "layout-footer-fixed",
- "actions_sticky_top": "sticky-top",
- }
- for key, value in bool_map.items():
- if key in tweaks:
- tweaks[key] = value
- def classes(*args: str) -> str:
- return " ".join([tweaks.get(arg, "") for arg in args]).strip()
- theme = tweaks["theme"]
- if theme not in THEMES:
- logger.warning("{} not found in {}, using default".format(theme, THEMES.keys()))
- theme = "default"
- dark_mode_theme = tweaks.get("dark_mode_theme", None)
- if dark_mode_theme and dark_mode_theme not in DARK_THEMES:
- logger.warning("{} is not a dark theme, using darkly".format(dark_mode_theme))
- dark_mode_theme = "darkly"
- theme_body_classes = " theme-{}".format(theme)
- if theme in DARK_THEMES:
- theme_body_classes += " dark-mode"
- ret = {
- "raw": raw_tweaks,
- "theme": {"name": theme, "src": static(THEMES[theme])},
- "sidebar_classes": classes("sidebar", "sidebar_disable_expand"),
- "navbar_classes": classes("navbar", "no_navbar_border", "navbar_small_text"),
- "body_classes": classes(
- "accent", "body_small_text", "navbar_fixed", "footer_fixed", "sidebar_fixed", "layout_boxed"
- )
- + theme_body_classes,
- "actions_classes": classes("actions_sticky_top"),
- "sidebar_list_classes": classes(
- "sidebar_nav_small_text",
- "sidebar_nav_flat_style",
- "sidebar_nav_legacy_style",
- "sidebar_nav_child_indent",
- "sidebar_nav_compact_style",
- ),
- "brand_classes": classes("brand_small_text", "brand_colour"),
- "footer_classes": classes("footer_small_text"),
- "button_classes": tweaks["button_classes"],
- }
- if dark_mode_theme:
- ret["dark_mode_theme"] = {"name": dark_mode_theme, "src": static(THEMES[dark_mode_theme])}
- return ret
|