You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
17 KiB
489 lines
17 KiB
import json
|
|
import random
|
|
from functools import lru_cache
|
|
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.humanize.templatetags.humanize import intcomma
|
|
from django.urls import reverse, path
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.generic import RedirectView
|
|
from django.utils.translation import get_language
|
|
from django.http import JsonResponse
|
|
from django.utils.html import format_html
|
|
from django.views.decorators.http import require_POST
|
|
|
|
# --- AGGRESSIVE UNICODE PATCH FOR UNFOLD 0.64.1 TABS ---
|
|
import django.utils.text
|
|
from django.template.defaultfilters import register
|
|
|
|
_original_slugify = django.utils.text.slugify
|
|
|
|
def _unicode_slugify(value, allow_unicode=True):
|
|
# We forcefully pass allow_unicode=True to preserve Russian characters for tab IDs
|
|
return _original_slugify(value, allow_unicode=True)
|
|
|
|
django.utils.text.slugify = _unicode_slugify
|
|
|
|
# We must also override the template filter explicitly because Unfold calls it directly in 0.64.1
|
|
@register.filter(is_safe=True)
|
|
def slugify(value):
|
|
return _unicode_slugify(value)
|
|
# -------------------------------------------------------
|
|
|
|
# Unfold Imports
|
|
from unfold.sites import UnfoldAdminSite
|
|
|
|
# ---------------------------------------------------------
|
|
# 1. Helper Functions
|
|
# ---------------------------------------------------------
|
|
|
|
def is_dovoodi_panel(request):
|
|
"""
|
|
Returns True if the user is accessing the Dovoodi admin panel.
|
|
In standalone Dovoodi project, this is always True.
|
|
"""
|
|
return True
|
|
|
|
def is_main_panel(request):
|
|
"""Returns True if the user is accessing the Main (Imam Javad) admin panel."""
|
|
return False
|
|
|
|
def admin_url_generator(request, url_name):
|
|
"""
|
|
Dynamically generates admin URLs under the standard 'admin' namespace.
|
|
"""
|
|
try:
|
|
return reverse(f"admin:{url_name}")
|
|
except Exception:
|
|
return "#"
|
|
|
|
# FIXME: DEPENDENCY ON DELETED APP (certificate) — Commented out for Dovoodi project separation
|
|
def get_pending_certificates_badge(request):
|
|
"""Generates the integer for the sidebar badge (Dummy for Dovoodi)"""
|
|
# try:
|
|
# from apps.certificate.models import Certificate
|
|
# qs = Certificate.objects.filter(status='pending')
|
|
#
|
|
# if request.user.is_authenticated and not request.user.is_staff and not getattr(request.user, 'is_superuser', False):
|
|
# qs = qs.filter(course__professor=request.user)
|
|
#
|
|
# count = qs.count()
|
|
# return count if count > 0 else None
|
|
# except Exception as e:
|
|
# print(f"Badge Error: {e}")
|
|
# return None
|
|
return None
|
|
|
|
def variables(request):
|
|
return {"plausible_domain": getattr(settings, 'PLAUSIBLE_DOMAIN', '')}
|
|
|
|
@require_POST
|
|
def toggle_sidebar(request):
|
|
"""Toggle sidebar state for Unfold admin interface"""
|
|
return JsonResponse({'status': 'success'})
|
|
|
|
# ---------------------------------------------------------
|
|
# 2. Custom Login Form
|
|
# ---------------------------------------------------------
|
|
|
|
class LoginForm:
|
|
"""Lazy login form to avoid circular imports during settings loading"""
|
|
|
|
@staticmethod
|
|
def get_form():
|
|
from unfold.forms import AuthenticationForm
|
|
|
|
class CustomLoginForm(AuthenticationForm):
|
|
password = forms.CharField(widget=forms.PasswordInput(render_value=True))
|
|
|
|
def __init__(self, request=None, *args, **kwargs):
|
|
super().__init__(request, *args, **kwargs)
|
|
self.fields["username"].label = "Email"
|
|
|
|
return CustomLoginForm
|
|
|
|
# ---------------------------------------------------------
|
|
# 3. Admin Site Definitions
|
|
# ---------------------------------------------------------
|
|
|
|
class FormulaAdminSite(UnfoldAdminSite):
|
|
"""Main Admin for Imam Jawad"""
|
|
site_header = _("Imam Javad Admin")
|
|
site_title = _("Imam Javad Admin")
|
|
index_title = _("System Administration")
|
|
site_subheader = _("Imam Javad School")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.login_form = LoginForm.get_form()
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
form = super().get_form(request, obj, **kwargs)
|
|
return form
|
|
|
|
def each_context(self, request):
|
|
context = super().each_context(request)
|
|
context["site_dropdown"] = [
|
|
{
|
|
"title": _("Imam Javad Site"),
|
|
"link": "https://imamjavad.newhorizonco.uk/",
|
|
"icon": "diamond",
|
|
},
|
|
{
|
|
"title": _("Dovoodi Site"),
|
|
"link": "https://dovodi.newhorizonco.uk/",
|
|
"icon": "diamond",
|
|
},
|
|
{
|
|
"title": _("Dovoodi Admin"),
|
|
"link": "https://dovodi.newhorizonco.uk/admin/",
|
|
"icon": "diamond",
|
|
}
|
|
]
|
|
return context
|
|
|
|
def get_urls(self):
|
|
urls = super().get_urls()
|
|
custom_urls = [
|
|
path('toggle_sidebar/', toggle_sidebar, name='toggle_sidebar'),
|
|
]
|
|
return custom_urls + urls
|
|
|
|
def _get_colors(self, key, *args):
|
|
if key != "COLORS":
|
|
return super()._get_colors(key, *args)
|
|
|
|
imam_javad_colors = {
|
|
"base": {
|
|
# Soft Sage Grays (Neutral gray infused with 5% brand green)
|
|
# This makes the main background a very soft icy-green, letting the pure white cards pop!
|
|
"50": "246 250 248",
|
|
"100": "237 245 241",
|
|
"200": "218 232 225",
|
|
"300": "191 210 201",
|
|
"400": "150 173 162",
|
|
"500": "107 130 119",
|
|
"600": "76 97 87",
|
|
"700": "58 77 68",
|
|
"800": "38 51 45",
|
|
"900": "24 33 29",
|
|
"950": "10 15 13",
|
|
},
|
|
"primary": {
|
|
# Your Vibrant Green
|
|
"50": "236 253 243",
|
|
"100": "209 250 229",
|
|
"200": "167 247 216",
|
|
"300": "110 240 189",
|
|
"400": "37 213 152",
|
|
"500": "25 166 94",
|
|
"600": "20 136 80",
|
|
"700": "15 108 66",
|
|
"800": "12 89 57",
|
|
"900": "10 70 45",
|
|
"950": "5 35 20",
|
|
},
|
|
"secondary": {
|
|
# Your Deep Teal
|
|
"50": "240 253 250",
|
|
"100": "204 251 241",
|
|
"200": "153 246 228",
|
|
"300": "94 234 212",
|
|
"400": "45 212 191",
|
|
"500": "14 63 59",
|
|
"600": "11 49 46",
|
|
"700": "9 39 37",
|
|
"800": "7 29 27",
|
|
"900": "5 19 18",
|
|
"950": "3 10 9",
|
|
},
|
|
"font": {
|
|
"subtle-light": "var(--color-base-500)",
|
|
"subtle-dark": "var(--color-base-400)",
|
|
"default-light": "var(--color-base-700)",
|
|
"default-dark": "var(--color-base-200)",
|
|
"important-light": "var(--color-base-900)",
|
|
"important-dark": "255 255 255",
|
|
},
|
|
}
|
|
return imam_javad_colors
|
|
|
|
class DovoodiAdminSite(UnfoldAdminSite):
|
|
"""Secondary Admin for Dovoodi"""
|
|
site_header = _("Dovoodi Admin")
|
|
site_title = _("Dovoodi Admin")
|
|
index_title = _("System Administration")
|
|
site_subheader = _("Dovodbi Application")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.login_form = LoginForm.get_form()
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
form = super().get_form(request, obj, **kwargs)
|
|
return form
|
|
|
|
def each_context(self, request):
|
|
context = super().each_context(request)
|
|
context["site_dropdown"] = [
|
|
{
|
|
"title": _("Dovoodi Site"),
|
|
"link": "https://dovodi.newhorizonco.uk/",
|
|
"icon": "diamond",
|
|
},
|
|
{
|
|
"title": _("Imam Javad Site"),
|
|
"link": "https://imamjavad.newhorizonco.uk/",
|
|
"icon": "diamond",
|
|
},
|
|
{
|
|
"title": _("Imam Javad Admin"),
|
|
"link": "https://imamjavad.newhorizonco.uk/admin/",
|
|
"icon": "diamond",
|
|
}
|
|
]
|
|
return context
|
|
|
|
def get_urls(self):
|
|
urls = super().get_urls()
|
|
custom_urls = [
|
|
path('toggle_sidebar/', toggle_sidebar, name='toggle_sidebar'),
|
|
]
|
|
return custom_urls + urls
|
|
|
|
def _get_colors(self, key, *args):
|
|
if key != "COLORS":
|
|
return super()._get_colors(key, *args)
|
|
|
|
dovoodi_colors = {
|
|
"base": {
|
|
"50": "252 251 250",
|
|
"100": "246 245 244",
|
|
"200": "240 236 233",
|
|
"300": "229 220 211",
|
|
"400": "191 174 157",
|
|
"500": "107 114 128",
|
|
"600": "75 85 99",
|
|
"700": "55 65 81",
|
|
"800": "31 41 55",
|
|
"900": "17 24 39",
|
|
"950": "3 7 18",
|
|
},
|
|
"primary": {
|
|
"50": "240 244 255",
|
|
"100": "224 231 255",
|
|
"200": "199 210 254",
|
|
"300": "165 180 252",
|
|
"400": "129 140 248",
|
|
"500": "99 102 241",
|
|
"600": "81 114 225",
|
|
"700": "59 89 196",
|
|
"800": "45 68 145",
|
|
"900": "30 41 91",
|
|
"950": "15 20 45",
|
|
},
|
|
"secondary": {
|
|
"50": "210 215 215",
|
|
"100": "151 163 164",
|
|
"200": "108 125 127",
|
|
"300": "44 69 72",
|
|
"400": "1 31 34",
|
|
"500": "1 22 24",
|
|
"600": "1 19 21",
|
|
"700": "0 15 17",
|
|
"800": "0 12 14",
|
|
"900": "0 8 10",
|
|
"950": "0 4 5",
|
|
},
|
|
"font": {
|
|
"subtle-light": "var(--color-base-500)",
|
|
"subtle-dark": "var(--color-base-400)",
|
|
"default-light": "var(--color-secondary-400)",
|
|
"default-dark": "var(--color-base-200)",
|
|
"important-light": "var(--color-base-900)",
|
|
"important-dark": "255 255 255",
|
|
},
|
|
}
|
|
return dovoodi_colors
|
|
|
|
class AdminSitePlaceholder(UnfoldAdminSite):
|
|
"""Placeholder that behaves like an admin site until Django is fully loaded"""
|
|
|
|
def __init__(self, site_class, name):
|
|
self._site_class = site_class
|
|
self._name = name
|
|
self._real_instance = None
|
|
self._registry = {}
|
|
|
|
self.site_header = getattr(site_class, 'site_header', 'Django Admin')
|
|
self.site_title = getattr(site_class, 'site_title', 'Django Site')
|
|
self.index_title = getattr(site_class, 'index_title', 'Site Administration')
|
|
self.site_subheader = getattr(site_class, 'site_subheader', '')
|
|
|
|
def _get_real_instance(self):
|
|
if self._real_instance is None:
|
|
self._real_instance = self._site_class(name=self._name)
|
|
self.login_form = self._real_instance.login_form
|
|
self.login_template = self._real_instance.login_template
|
|
for attr in ['site_header', 'site_title', 'index_title', 'site_subheader']:
|
|
if hasattr(self._real_instance, attr):
|
|
setattr(self, attr, getattr(self._real_instance, attr))
|
|
|
|
if hasattr(self, '_registry'):
|
|
for model, admin_class in self._registry.items():
|
|
self._real_instance.register(model, admin_class)
|
|
|
|
import sys
|
|
current_module = sys.modules[__name__]
|
|
if hasattr(current_module, self._name):
|
|
setattr(current_module, self._name, self._real_instance)
|
|
|
|
return self._real_instance
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self._get_real_instance(), name)
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
return self._get_real_instance().get_form(request, obj, **kwargs)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return self._get_real_instance()(*args, **kwargs)
|
|
|
|
def get_urls(self):
|
|
return self._get_real_instance().get_urls()
|
|
|
|
@property
|
|
def urls(self):
|
|
return self._get_real_instance().urls
|
|
|
|
def each_context(self, request):
|
|
return self._get_real_instance().each_context(request)
|
|
|
|
def register(self, model_or_iterable, admin_class=None, **options):
|
|
if isinstance(model_or_iterable, (list, tuple)):
|
|
for model in model_or_iterable:
|
|
self.register(model, admin_class, **options)
|
|
else:
|
|
model = model_or_iterable
|
|
self._registry[model] = admin_class
|
|
|
|
if self._real_instance is not None:
|
|
if model in self._real_instance._registry:
|
|
return
|
|
self._real_instance.register(model, admin_class, **options)
|
|
|
|
|
|
class LazyAdminSite(UnfoldAdminSite):
|
|
def __init__(self, site_class, name):
|
|
self._site_class = site_class
|
|
self._name = name
|
|
self._instance = None
|
|
self.name = name
|
|
|
|
def _force_init(self):
|
|
if self._instance is None:
|
|
self._instance = self._site_class(name=self._name)
|
|
for attr in dir(self._instance):
|
|
if not attr.startswith('_') and attr not in ('register', 'unregister', 'is_registered'):
|
|
try:
|
|
setattr(self, attr, getattr(self._instance, attr))
|
|
except (AttributeError, TypeError):
|
|
pass
|
|
def _ensure_instance(self):
|
|
if self._instance is None:
|
|
self._instance = self._site_class(name=self._name)
|
|
essential_attrs = ['site_header', 'site_title', 'index_title', 'site_url', 'login_template']
|
|
for attr in essential_attrs:
|
|
if hasattr(self._instance, attr):
|
|
setattr(self, attr, getattr(self._instance, attr))
|
|
|
|
def _get_instance(self):
|
|
self._ensure_instance()
|
|
return self._instance
|
|
|
|
def __getattr__(self, name):
|
|
self._ensure_instance()
|
|
return getattr(self._instance, name)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return self._get_instance()(*args, **kwargs)
|
|
|
|
@property
|
|
def urls(self):
|
|
return self._get_instance().urls
|
|
|
|
def get_urls(self):
|
|
return self._get_instance().get_urls()
|
|
|
|
def register(self, model_or_iterable, admin_class=None, **options):
|
|
self._ensure_instance()
|
|
if isinstance(model_or_iterable, (list, tuple)):
|
|
models = model_or_iterable
|
|
else:
|
|
models = [model_or_iterable]
|
|
|
|
for model in models:
|
|
if model in self._instance._registry:
|
|
continue
|
|
self._instance.register(model, admin_class, **options)
|
|
|
|
|
|
# Unified Dovoodi admin site under the standard 'admin' namespace
|
|
dovoodi_admin_site = LazyAdminSite(DovoodiAdminSite, 'admin')
|
|
# Alias project_admin_site to dovoodi_admin_site for seamless backward compatibility
|
|
project_admin_site = dovoodi_admin_site
|
|
|
|
def replace_placeholders_with_real_sites():
|
|
global project_admin_site, dovoodi_admin_site
|
|
if isinstance(dovoodi_admin_site, AdminSitePlaceholder):
|
|
dovoodi_admin_site = DovoodiAdminSite(name='admin')
|
|
project_admin_site = dovoodi_admin_site
|
|
|
|
|
|
class HomeView(RedirectView):
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
language = get_language() or 'en'
|
|
return f'/{language}/admin/'
|
|
|
|
# ---------------------------------------------------------
|
|
# 4. Dynamic Custom Dashboard
|
|
# ---------------------------------------------------------
|
|
|
|
def dashboard_callback(request, context):
|
|
from django.apps import apps
|
|
|
|
if context is None:
|
|
context = {}
|
|
|
|
context.update({
|
|
"navigation": [{"title": _("Dashboard"), "link": "/", "active": True}],
|
|
"kpi": [],
|
|
"top_courses": [],
|
|
"tx_stats": {},
|
|
})
|
|
|
|
if not hasattr(request, "user") or not request.user.is_authenticated:
|
|
return context
|
|
|
|
# DOVOODI PANEL STATS ONLY
|
|
try:
|
|
Video = apps.get_model('video', 'Video')
|
|
Book = apps.get_model('library', 'Book')
|
|
Article = apps.get_model('article', 'Article')
|
|
Hadis = apps.get_model('hadis', 'Hadis')
|
|
Podcast = apps.get_model('podcast', 'Podcast')
|
|
|
|
total_multimedia = Video.objects.count() + Podcast.objects.count()
|
|
total_reading = Book.objects.count() + Article.objects.count()
|
|
|
|
context["kpi"] = [
|
|
{"title": _("Hadith Database"), "metric": f"{Hadis.objects.count():,}"},
|
|
{"title": _("Books & Articles"), "metric": f"{total_reading:,}"},
|
|
{"title": _("Multimedia"), "metric": f"{total_multimedia:,}"},
|
|
]
|
|
except Exception as e:
|
|
print(f"Dashboard KPI Error (Dovoodi Panel): {e}")
|
|
|
|
return context
|