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.
532 lines
21 KiB
532 lines
21 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.views.decorators.http import require_POST
|
|
|
|
# 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.
|
|
Checks if '/dovoodi/' exists anywhere in the path to handle i18n prefixes
|
|
(e.g., /en/dovoodi/admin, /fa/dovoodi/admin).
|
|
"""
|
|
return '/dovoodi/' in request.path
|
|
|
|
def is_main_panel(request):
|
|
"""Returns True if the user is accessing the Main (Imam Javad) admin panel."""
|
|
return not is_dovoodi_panel(request)
|
|
|
|
def admin_url_generator(request, url_name):
|
|
"""
|
|
Dynamically generates admin URLs based on the current active panel.
|
|
Usage in settings.py: lambda request: admin_url_generator(request, "app_model_changelist")
|
|
"""
|
|
# Ensure admin sites are created and URLs are registered
|
|
_ = project_admin_site.urls # Access URLs to ensure site is created
|
|
_ = dovoodi_admin_site.urls # Access URLs to ensure site is created
|
|
|
|
# 1. Determine the current namespace
|
|
if is_dovoodi_panel(request):
|
|
namespace = 'dovoodi_admin'
|
|
else:
|
|
namespace = 'imam_javad_admin'
|
|
|
|
# 2. Construct the view name
|
|
full_view_name = f"{namespace}:{url_name}"
|
|
|
|
# 3. Try Django URL reversal
|
|
try:
|
|
return reverse(full_view_name)
|
|
except Exception:
|
|
return "#"
|
|
|
|
def dashboard_callback(request, context):
|
|
context.update(random_data())
|
|
return context
|
|
|
|
def variables(request):
|
|
return {"plausible_domain": getattr(settings, 'PLAUSIBLE_DOMAIN', '')}
|
|
|
|
# Toggle sidebar view for Unfold compatibility
|
|
@require_POST
|
|
def toggle_sidebar(request):
|
|
"""Toggle sidebar state for Unfold admin interface"""
|
|
# This is a simple view that just returns success
|
|
# The actual sidebar state is handled client-side
|
|
return JsonResponse({'status': 'success'})
|
|
|
|
# ---------------------------------------------------------
|
|
# 2. Custom Login Form
|
|
# ---------------------------------------------------------
|
|
|
|
class LoginForm:
|
|
"""Lazy login form to avoid circular imports during settings loading"""
|
|
|
|
@staticmethod
|
|
def get_form():
|
|
# Import AuthenticationForm only when needed
|
|
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)
|
|
# Change the label of the username field to "Email"
|
|
self.fields["username"].label = "Email"
|
|
|
|
return CustomLoginForm
|
|
|
|
# ---------------------------------------------------------
|
|
# 3. Admin Site Definitions
|
|
# ---------------------------------------------------------
|
|
|
|
class FormulaAdminSite(UnfoldAdminSite):
|
|
"""Main Admin for Imam Jawad"""
|
|
site_header = "Imam Jawad Admin"
|
|
site_title = "Imam Jawad Admin"
|
|
index_title = "System Administration"
|
|
site_subheader = "Imam Jawad School"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Set login form after initialization to avoid circular import
|
|
self.login_form = LoginForm.get_form()
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
"""Override to ensure form is properly initialized"""
|
|
form = super().get_form(request, obj, **kwargs)
|
|
return form
|
|
|
|
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):
|
|
"""Override colors for Imam Javad admin panel with green theme"""
|
|
from unfold.utils import hex_to_rgb
|
|
|
|
if key != "COLORS":
|
|
return super()._get_colors(key, *args)
|
|
|
|
# پالت رنگی سبز برای امام جواد
|
|
imam_javad_colors = {
|
|
"base": {
|
|
"50": "249 250 251",
|
|
"100": "243 244 246",
|
|
"200": "229 231 235",
|
|
"300": "209 213 219",
|
|
"400": "156 163 175",
|
|
"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": "234 253 243",
|
|
"100": "208 251 232",
|
|
"200": "167 247 216",
|
|
"300": "110 240 189",
|
|
"400": "37 213 152",
|
|
"500": "37 208 118", # #25D076 - سبز اصلی
|
|
"600": "29 166 94",
|
|
"700": "25 136 80",
|
|
"800": "22 108 66",
|
|
"900": "20 89 57",
|
|
"950": "10 53 34",
|
|
},
|
|
"secondary": {
|
|
"50": "240 253 250",
|
|
"100": "204 251 241",
|
|
"200": "153 246 228",
|
|
"300": "94 234 212",
|
|
"400": "45 212 191",
|
|
"500": "1 53 59", # #01353B - پسزمینه تیره
|
|
"600": "1 43 48",
|
|
"700": "1 36 40",
|
|
"800": "1 30 34",
|
|
"900": "0 26 29",
|
|
"950": "0 13 15",
|
|
},
|
|
"font": {
|
|
"subtle-light": "var(--color-base-500)",
|
|
"subtle-dark": "var(--color-base-400)",
|
|
"default-light": "var(--color-secondary-500)",
|
|
"default-dark": "var(--color-base-300)",
|
|
"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)
|
|
# Set login form after initialization to avoid circular import
|
|
self.login_form = LoginForm.get_form()
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
"""Override to ensure form is properly initialized"""
|
|
form = super().get_form(request, obj, **kwargs)
|
|
return form
|
|
|
|
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):
|
|
"""Override colors for Dovoodi admin panel with blue/teal theme matching frontend"""
|
|
from unfold.utils import hex_to_rgb
|
|
|
|
if key != "COLORS":
|
|
return super()._get_colors(key, *args)
|
|
|
|
# پالت رنگی آبی-تیره برای داوودی (مطابق با فرانت)
|
|
dovoodi_colors = {
|
|
"base": {
|
|
# استفاده از Wormy scale برای base
|
|
"50": "252 251 250", # #FCFBFA
|
|
"100": "246 245 244", # #F6F5F4
|
|
"200": "240 236 233", # #F0ECE9
|
|
"300": "229 220 211", # #E5DCD3
|
|
"400": "191 174 157", # #BFAE9D
|
|
"500": "107 114 128",
|
|
"600": "75 85 99",
|
|
"700": "55 65 81",
|
|
"800": "31 41 55",
|
|
"900": "17 24 39", # #111827
|
|
"950": "3 7 18",
|
|
},
|
|
"primary": {
|
|
# استفاده از رنگ آبی اصلی فرانت
|
|
"50": "240 244 255", # #F0F4FF
|
|
"100": "224 231 255", # #E0E7FF
|
|
"200": "199 210 254", # #C7D2FE
|
|
"300": "165 180 252", # #A5B4FC
|
|
"400": "129 140 248", # #818CF8
|
|
"500": "99 102 241", # #6366F1
|
|
"600": "81 114 225", # #5172E1 - رنگ اصلی فرانت
|
|
"700": "59 89 196", # #3B59C4
|
|
"800": "45 68 145", # #2D4491
|
|
"900": "30 41 91",
|
|
"950": "15 20 45",
|
|
},
|
|
"secondary": {
|
|
# استفاده از Second scale فرانت (تیره سبز-آبی)
|
|
"50": "210 215 215", # #D2D7D7
|
|
"100": "151 163 164", # #97A3A4
|
|
"200": "108 125 127", # #6C7D7F
|
|
"300": "44 69 72", # #2C4548
|
|
"400": "1 31 34", # #011F22
|
|
"500": "1 22 24", # #011618 - پسزمینه اصلی
|
|
"600": "1 19 21", # #011315
|
|
"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
|
|
|
|
# Simple admin site placeholders that will be replaced after Django setup
|
|
class AdminSitePlaceholder(UnfoldAdminSite):
|
|
"""Placeholder that behaves like an admin site until Django is fully loaded"""
|
|
|
|
def __init__(self, site_class, name):
|
|
# 1. Store config for lazy loading
|
|
self._site_class = site_class
|
|
self._name = name
|
|
self._real_instance = None
|
|
self._registry = {} # Store registrations until real instance is created
|
|
|
|
# 2. THE FIX: Copy visual attributes immediately so Templates see them!
|
|
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:
|
|
# Force creation of real admin site instance for proper CSS loading
|
|
self._real_instance = self._site_class(name=self._name)
|
|
# Copy critical attributes immediately for template access
|
|
self.login_form = self._real_instance.login_form
|
|
self.login_template = self._real_instance.login_template
|
|
# Copy any other attributes that templates might need
|
|
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))
|
|
|
|
# Copy any existing registrations from the placeholder to the real instance
|
|
if hasattr(self, '_registry'):
|
|
for model, admin_class in self._registry.items():
|
|
self._real_instance.register(model, admin_class)
|
|
|
|
# Replace the global reference with the real instance
|
|
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):
|
|
# Delegate all attribute access to the real instance for proper CSS and template loading
|
|
return getattr(self._get_real_instance(), name)
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
"""Delegate get_form to the real admin site instance"""
|
|
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):
|
|
"""Store registrations in placeholder until real instance is created"""
|
|
if isinstance(model_or_iterable, (list, tuple)):
|
|
for model in model_or_iterable:
|
|
self.register(model, admin_class, **options)
|
|
else:
|
|
model = model_or_iterable
|
|
if model in self._registry:
|
|
# If already registered, update the admin class
|
|
self._registry[model] = admin_class
|
|
else:
|
|
self._registry[model] = admin_class
|
|
|
|
# Also register with the real instance if it exists
|
|
if self._real_instance is not None:
|
|
self._real_instance.register(model, admin_class, **options)
|
|
|
|
# Create lazy-loading admin site instances that properly inherit from AdminSite
|
|
class LazyAdminSite(UnfoldAdminSite):
|
|
def __init__(self, site_class, name):
|
|
# Don't call super().__init__() to avoid creating the real instance yet
|
|
self._site_class = site_class
|
|
self._name = name
|
|
self._instance = None
|
|
# Set basic attributes that Django expects for isinstance checks
|
|
self.name = name
|
|
|
|
def _force_init(self):
|
|
"""Force initialization immediately"""
|
|
if self._instance is None:
|
|
self._instance = self._site_class(name=self._name)
|
|
# Copy all attributes to this instance
|
|
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):
|
|
"""Ensure the real instance exists"""
|
|
if self._instance is None:
|
|
self._instance = self._site_class(name=self._name)
|
|
# Copy essential attributes to this lazy wrapper for compatibility
|
|
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):
|
|
"""Ensure URLs are accessed to create the instance"""
|
|
return self._get_instance().urls
|
|
|
|
def get_urls(self):
|
|
"""Delegate get_urls to ensure proper URL registration"""
|
|
return self._get_instance().get_urls()
|
|
|
|
def register(self, model_or_iterable, admin_class=None, **options):
|
|
"""Register models with the real admin site instance"""
|
|
self._ensure_instance()
|
|
return self._instance.register(model_or_iterable, admin_class, **options)
|
|
|
|
# Create lazy admin site instances
|
|
project_admin_site = LazyAdminSite(FormulaAdminSite, 'imam_javad_admin')
|
|
dovoodi_admin_site = LazyAdminSite(DovoodiAdminSite, 'dovoodi_admin')
|
|
|
|
# from django.contrib.admin.sites import AdminSite
|
|
|
|
# class LazyAdminSite(AdminSite):
|
|
# """Lazy wrapper that initializes the real admin site on first access"""
|
|
|
|
# def __init__(self, site_class, name):
|
|
# # Don't call super().__init__() - avoid app registry checks at import time
|
|
# self._site_class = site_class
|
|
# self._name = name
|
|
# self._instance = None
|
|
|
|
# def _get_instance(self):
|
|
# """Initialize the real site on first access (after Django is ready)"""
|
|
# if self._instance is None:
|
|
# self._instance = self._site_class(name=self._name)
|
|
# return self._instance
|
|
|
|
# def __getattr__(self, name):
|
|
# """Delegate all attribute access to the real instance"""
|
|
# if name.startswith('_'):
|
|
# raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
# return getattr(self._get_instance(), name)
|
|
|
|
# @property
|
|
# def urls(self):
|
|
# """Required for Django URL routing"""
|
|
# return self._get_instance().urls
|
|
|
|
# @property
|
|
# def media(self):
|
|
# """Expose the media from the real admin instance"""
|
|
# return self._get_instance().media
|
|
|
|
# @property
|
|
# def form_class(self):
|
|
# """Expose form class"""
|
|
# return self._get_instance().form_class
|
|
|
|
# def has_permission(self, request):
|
|
# """Check if user has admin permission"""
|
|
# return self._get_instance().has_permission(request)
|
|
|
|
# def each_context(self, request):
|
|
# """Return context for admin templates"""
|
|
# return self._get_instance().each_context(request)
|
|
|
|
# def get_urls(self):
|
|
# """Get admin URLs"""
|
|
# return self._get_instance().get_urls()
|
|
|
|
# def register(self, model, admin_class=None, **options):
|
|
# """Register a model with the admin site"""
|
|
# return self._get_instance().register(model, admin_class, **options)
|
|
|
|
|
|
# # Create lazy instances (NO initialization at import time!)
|
|
# project_admin_site = LazyAdminSite(FormulaAdminSite, 'imam_javad_admin')
|
|
# dovoodi_admin_site = LazyAdminSite(DovoodiAdminSite, 'dovoodi_admin')
|
|
|
|
# Function to replace placeholders with real instances when Django is ready
|
|
def replace_placeholders_with_real_sites():
|
|
global project_admin_site, dovoodi_admin_site
|
|
if isinstance(project_admin_site, AdminSitePlaceholder):
|
|
project_admin_site = FormulaAdminSite(name='imam_javad_admin')
|
|
if isinstance(dovoodi_admin_site, AdminSitePlaceholder):
|
|
dovoodi_admin_site = DovoodiAdminSite(name='dovoodi_admin')
|
|
|
|
# The placeholders will be replaced with real instances when first accessed
|
|
# This ensures proper CSS loading for admin templates
|
|
|
|
class HomeView(RedirectView):
|
|
def get_redirect_url(self, *args, **kwargs):
|
|
host = self.request.get_host()
|
|
|
|
# دریافت زبان فعلی (پیشفرض: en)
|
|
language = get_language() or 'en'
|
|
|
|
# دامنههای داوودی
|
|
dovoodi_domains = ['dovodi.newhorizonco.uk', 'dovoodi.newhorizonco.uk']
|
|
|
|
# تصمیمگیری بر اساس دامنه و برگرداندن URL با prefix زبانی
|
|
if any(domain in host for domain in dovoodi_domains):
|
|
return f'/{language}/dovoodi/admin/'
|
|
else:
|
|
return f'/{language}/imam-javad/admin/'
|
|
|
|
# ---------------------------------------------------------
|
|
# 4. Dummy Data for Dashboard Charts
|
|
# ---------------------------------------------------------
|
|
|
|
@lru_cache
|
|
def random_data():
|
|
WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
|
|
|
# Generate some fake data
|
|
positive = [[1, random.randrange(8, 28)] for i in range(1, 28)]
|
|
negative = [[-1, -random.randrange(8, 28)] for i in range(1, 28)]
|
|
average = [r[1] - random.randint(3, 5) for r in positive]
|
|
performance_positive = [[1, random.randrange(8, 28)] for i in range(1, 28)]
|
|
performance_negative = [[-1, -random.randrange(8, 28)] for i in range(1, 28)]
|
|
|
|
return {
|
|
"navigation": [
|
|
{"title": _("Dashboard"), "link": "/", "active": True},
|
|
{"title": _("Analytics"), "link": "#"},
|
|
{"title": _("Settings"), "link": "#"},
|
|
],
|
|
"kpi": [
|
|
{
|
|
"title": "Total Revenue",
|
|
"metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
|
|
"footer": mark_safe(f'<strong class="text-green-700 font-semibold dark:text-green-400">+{intcomma(f"{random.uniform(1, 9):.02f}")}%</strong> progress'),
|
|
"chart": json.dumps({"labels": [WEEKDAYS[day % 7] for day in range(1, 28)], "datasets": [{"data": average, "borderColor": "#9333ea"}]}),
|
|
},
|
|
],
|
|
"chart": json.dumps({
|
|
"labels": [WEEKDAYS[day % 7] for day in range(1, 28)],
|
|
"datasets": [
|
|
{"label": "Revenue", "data": positive, "backgroundColor": "var(--color-primary-700)"},
|
|
],
|
|
}),
|
|
}
|