Browse Source

admin panel fieldsets fixed.

master
Mohsen Taba 5 months ago
parent
commit
7125d65a39
  1. 37
      apps/account/management/commands/test_guest_token.py
  2. 4
      apps/hadis/admin/transmitter.py
  3. 48
      config/debug_authentication.py
  4. 2
      config/settings/base.py
  5. 0
      templates/admin/includes/fieldset.html.backup
  6. 200
      utils/admin.py
  7. 14
      utils/permissions.py

37
apps/account/management/commands/test_guest_token.py

@ -0,0 +1,37 @@
from django.core.management.base import BaseCommand
from rest_framework.test import APIClient
from apps.account.models import User # Your user model
import json
class Command(BaseCommand):
def handle(self, *args, **options):
client = APIClient()
# Step 1: Create guest token
print("\n📝 Step 1: Creating guest token...")
response = client.post('/api/account/web/guest/',
data=json.dumps({"timezone": "UTC", "user_agent": "test"}),
content_type='application/json'
)
print(f"Status: {response.status_code}")
if response.status_code == 200:
print(f"Response: {response.json()}")
token = response.json()['token']
else:
print(f"Error Response: {response.content.decode('utf-8')}")
print("❌ Failed to create token!")
return
print(f"✅ Token created: {token[:20]}...")
# Step 2: Test authentication with token
print("\n🔐 Step 2: Testing token authentication...")
client.credentials(HTTP_AUTHORIZATION=f'Token {token}')
response = client.get('/api/library/books/')
print(f"Status: {response.status_code}")
if response.status_code == 200:
print(f"Response: {response.json()}")
print("✅ Token authentication works!")
else:
print(f"Error Response: {response.content.decode('utf-8')}")
print("❌ Token authentication failed!")

4
apps/hadis/admin/transmitter.py

@ -48,11 +48,11 @@ class TransmittersAdmin(ModelAdmin):
}),
(_('Additional Information'), {
'fields': ('description',),
'classes': ('collapse',)
# 'classes': ('collapse',)
}),
(_('Timestamps'), {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
# 'classes': ('collapse',)
}),
)
@display(description=_('Full Name'), ordering='full_name')

48
config/debug_authentication.py

@ -0,0 +1,48 @@
from rest_framework.authentication import TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
import logging
logger = logging.getLogger(__name__)
class DebugTokenAuthentication(TokenAuthentication):
"""
Extended TokenAuthentication with detailed logging for debugging
"""
def authenticate(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
logger.info(f"🔍 AUTH DEBUG - Header: {auth_header}")
# Check if header exists
if not auth_header:
logger.warning("🔴 AUTH DEBUG - No Authorization header found")
return None
# Extract token
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != 'token':
logger.warning(f"🔴 AUTH DEBUG - Invalid header format: {parts}")
return None
token_key = parts[1]
logger.info(f"🔍 AUTH DEBUG - Token key extracted: {token_key[:10]}...")
try:
# Try to get token from database
from rest_framework.authtoken.models import Token
token = Token.objects.select_related('user').get(key=token_key)
logger.info(f"✅ AUTH DEBUG - Token found in DB")
logger.info(f"✅ AUTH DEBUG - User: {token.user}")
logger.info(f"✅ AUTH DEBUG - User ID: {token.user.id}")
logger.info(f"✅ AUTH DEBUG - User is_active: {token.user.is_active}")
logger.info(f"✅ AUTH DEBUG - User is_authenticated: {token.user.is_authenticated}")
if not token.user.is_active:
logger.error("🔴 AUTH DEBUG - User is not active")
raise AuthenticationFailed('User inactive or deleted.')
logger.info("✅ AUTH DEBUG - Authentication SUCCESSFUL")
return (token.user, token)
except Exception as e:
logger.error(f"🔴 AUTH DEBUG - Token lookup failed: {str(e)}")
raise AuthenticationFailed('Invalid token.')

2
config/settings/base.py

@ -422,7 +422,7 @@ UNFOLD = {
{
"title": _("Guest Users"),
"icon": "sports_motorsports",
"link": lambda request: f"{reverse_lazy('admin:account_user_changelist')}?email__isnull=true",
"link": lambda request: f"{admin_url_generator(request, 'account_user_changelist')}?email__isnull=true",
},
],
},

0
templates/admin/includes/fieldset.html → templates/admin/includes/fieldset.html.backup

200
utils/admin.py

@ -5,11 +5,13 @@ 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
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
@ -35,17 +37,20 @@ 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")
"""
# 1. Determine the current namespace using the robust check
# 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:
# Default to the main admin
namespace = 'imam_javad_admin'
# 2. Construct the view name
full_view_name = f"{namespace}:{url_name}"
# 3. Resolve the URL
# 3. Try Django URL reversal
try:
return reverse(full_view_name)
except Exception:
@ -58,6 +63,14 @@ def dashboard_callback(request, 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
# ---------------------------------------------------------
@ -96,6 +109,18 @@ class FormulaAdminSite(UnfoldAdminSite):
# 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
@ -168,6 +193,18 @@ class DovoodiAdminSite(UnfoldAdminSite):
# 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
@ -240,6 +277,7 @@ class AdminSitePlaceholder(UnfoldAdminSite):
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')
@ -258,12 +296,28 @@ class AdminSitePlaceholder(UnfoldAdminSite):
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)
@ -277,9 +331,141 @@ class AdminSitePlaceholder(UnfoldAdminSite):
def each_context(self, request):
return self._get_real_instance().each_context(request)
# Create placeholder instances that will be replaced with real instances when Django is ready
project_admin_site = AdminSitePlaceholder(FormulaAdminSite, 'imam_javad_admin')
dovoodi_admin_site = AdminSitePlaceholder(DovoodiAdminSite, 'dovoodi_admin')
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():

14
utils/permissions.py

@ -0,0 +1,14 @@
from rest_framework.permissions import BasePermission
class IsTokenAuthenticatedOrAnonymous(BasePermission):
"""
Allow access to token-authenticated users OR anonymous users.
Useful for guest token endpoints.
"""
def has_permission(self, request, view):
# Allow if user is authenticated (including token users)
if request.user and request.user.is_authenticated:
return True
# Allow anonymous access
return True
Loading…
Cancel
Save