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'+{intcomma(f"{random.uniform(1, 9):.02f}")}% 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)"}, ], }), }