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. 4
      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'), { (_('Additional Information'), {
'fields': ('description',), 'fields': ('description',),
'classes': ('collapse',)
# 'classes': ('collapse',)
}), }),
(_('Timestamps'), { (_('Timestamps'), {
'fields': ('created_at', 'updated_at'), 'fields': ('created_at', 'updated_at'),
'classes': ('collapse',)
# 'classes': ('collapse',)
}), }),
) )
@display(description=_('Full Name'), ordering='full_name') @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.')

4
config/settings/base.py

@ -55,7 +55,7 @@ LOCAL_APPS = [
'apps.bookmark.apps.BookmarkConfig', 'apps.bookmark.apps.BookmarkConfig',
'apps.article.apps.ArticleConfig', 'apps.article.apps.ArticleConfig',
'apps.dobodbi_calendar.apps.DobodbiCalendarConfig', 'apps.dobodbi_calendar.apps.DobodbiCalendarConfig',
'apps.blog.apps.BlogConfig',
'apps.blog.apps.BlogConfig',
'dynamic_preferences', 'dynamic_preferences',
] ]
@ -422,7 +422,7 @@ UNFOLD = {
{ {
"title": _("Guest Users"), "title": _("Guest Users"),
"icon": "sports_motorsports", "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 import forms
from django.conf import settings from django.conf import settings
from django.contrib.humanize.templatetags.humanize import intcomma 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.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.utils.translation import get_language from django.utils.translation import get_language
from django.http import JsonResponse
from django.views.decorators.http import require_POST
# Unfold Imports # Unfold Imports
from unfold.sites import UnfoldAdminSite 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. Dynamically generates admin URLs based on the current active panel.
Usage in settings.py: lambda request: admin_url_generator(request, "app_model_changelist") 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): if is_dovoodi_panel(request):
namespace = 'dovoodi_admin' namespace = 'dovoodi_admin'
else: else:
# Default to the main admin
namespace = 'imam_javad_admin' namespace = 'imam_javad_admin'
# 2. Construct the view name # 2. Construct the view name
full_view_name = f"{namespace}:{url_name}" full_view_name = f"{namespace}:{url_name}"
# 3. Resolve the URL
# 3. Try Django URL reversal
try: try:
return reverse(full_view_name) return reverse(full_view_name)
except Exception: except Exception:
@ -58,6 +63,14 @@ def dashboard_callback(request, context):
def variables(request): def variables(request):
return {"plausible_domain": getattr(settings, 'PLAUSIBLE_DOMAIN', '')} 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 # 2. Custom Login Form
# --------------------------------------------------------- # ---------------------------------------------------------
@ -95,6 +108,18 @@ class FormulaAdminSite(UnfoldAdminSite):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Set login form after initialization to avoid circular import # Set login form after initialization to avoid circular import
self.login_form = LoginForm.get_form() 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): def _get_colors(self, key, *args):
"""Override colors for Imam Javad admin panel with green theme""" """Override colors for Imam Javad admin panel with green theme"""
@ -167,6 +192,18 @@ class DovoodiAdminSite(UnfoldAdminSite):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Set login form after initialization to avoid circular import # Set login form after initialization to avoid circular import
self.login_form = LoginForm.get_form() 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): def _get_colors(self, key, *args):
"""Override colors for Dovoodi admin panel with blue/teal theme matching frontend""" """Override colors for Dovoodi admin panel with blue/teal theme matching frontend"""
@ -240,6 +277,7 @@ class AdminSitePlaceholder(UnfoldAdminSite):
self._site_class = site_class self._site_class = site_class
self._name = name self._name = name
self._real_instance = None self._real_instance = None
self._registry = {} # Store registrations until real instance is created
# 2. THE FIX: Copy visual attributes immediately so Templates see them! # 2. THE FIX: Copy visual attributes immediately so Templates see them!
self.site_header = getattr(site_class, 'site_header', 'Django Admin') 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']: for attr in ['site_header', 'site_title', 'index_title', 'site_subheader']:
if hasattr(self._real_instance, attr): if hasattr(self._real_instance, attr):
setattr(self, attr, getattr(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 return self._real_instance
def __getattr__(self, name): def __getattr__(self, name):
# Delegate all attribute access to the real instance for proper CSS and template loading # Delegate all attribute access to the real instance for proper CSS and template loading
return getattr(self._get_real_instance(), name) 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): def __call__(self, *args, **kwargs):
return self._get_real_instance()(*args, **kwargs) return self._get_real_instance()(*args, **kwargs)
@ -277,9 +331,141 @@ class AdminSitePlaceholder(UnfoldAdminSite):
def each_context(self, request): def each_context(self, request):
return self._get_real_instance().each_context(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 # Function to replace placeholders with real instances when Django is ready
def replace_placeholders_with_real_sites(): 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