diff --git a/apps/account/admin/location.py b/apps/account/admin/location.py
index 8f2164e..c1740e3 100644
--- a/apps/account/admin/location.py
+++ b/apps/account/admin/location.py
@@ -9,7 +9,7 @@ from unfold.contrib.filters.admin import (
)
from apps.account.models import LocationHistory, User
-from utils.admin import project_admin_site
+from utils.admin import project_admin_site, dovoodi_admin_site
class LocationHistoryInline(TabularInline):
@@ -54,5 +54,6 @@ class LocationHistoryAdmin(ModelAdmin):
return instance.at_time.strftime("%Y-%m-%d %H:%M") if instance.at_time else "-"
-# Register with project admin site
-project_admin_site.register(LocationHistory, LocationHistoryAdmin)
\ No newline at end of file
+# Register with both admin sites
+project_admin_site.register(LocationHistory, LocationHistoryAdmin)
+dovoodi_admin_site.register(LocationHistory, LocationHistoryAdmin)
\ No newline at end of file
diff --git a/apps/account/admin/user.py b/apps/account/admin/user.py
index 5db816b..7a48833 100644
--- a/apps/account/admin/user.py
+++ b/apps/account/admin/user.py
@@ -1,43 +1,33 @@
+from django import forms
from django.contrib import admin
-from django.contrib.auth.forms import UserChangeForm, UsernameField
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
-
-from django.utils.translation import gettext_lazy as _
-from rest_framework.authtoken.models import TokenProxy
-from ajaxdatatable.admin import AjaxDatatable
-
-from apps.account.models import User, Notification, LocationHistory
-from django import forms
-from django.contrib import admin
-from django.urls import path, reverse
-from django.shortcuts import render, redirect
-from django.contrib import messages
-
-from apps.account.models import ClientUser, AdminUser, StudentUser, ProfessorUser
-from apps.course.models import Participant
-from phonenumber_field.formfields import PhoneNumberField
-from utils.admin import project_admin_site
-from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
-from unfold.admin import ModelAdmin, StackedInline, TabularInline
+from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.contrib.auth.models import Group
from django.db import models
+from django.utils.html import format_html
+from django.utils.translation import gettext_lazy as _
+from django.templatetags.static import static
+
+from rest_framework.authtoken.models import TokenProxy
+from unfold.admin import ModelAdmin, StackedInline
from unfold.contrib.forms.widgets import WysiwygWidget
-from unfold.decorators import action, display
+from unfold.decorators import display
+from unfold.forms import AdminPasswordChangeForm
from unfold.sections import TableSection
-from unfold.contrib.filters.admin import (
- AutocompleteSelectMultipleFilter,
- ChoicesDropdownFilter,
- MultipleRelatedDropdownFilter,
- RangeDateFilter,
- RangeDateTimeFilter,
- RangeNumericFilter,
- SingleNumericFilter,
- TextFilter,
-)
-from apps.account.admin.location import LocationHistoryInline
+from unfold.contrib.filters.admin import RangeDateTimeFilter
+
+# Import Models
+from apps.account.models import User, ClientUser, StudentUser, ProfessorUser, LocationHistory
+from apps.course.models import Participant
+# Import Admin Sites from utils
+from utils.admin import project_admin_site, dovoodi_admin_site
+from apps.account.admin.location import LocationHistoryInline
+# =========================================================
+# 1. Base User Admin (Logic Shared by all User types)
+# =========================================================
class UserAdmin(BaseUserAdmin, ModelAdmin):
form = UserChangeForm
@@ -45,13 +35,9 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
change_password_form = AdminPasswordChangeForm
compressed_fields = False
list_before_template = "account/user_list_section.html"
- list_display = (
- 'fullname', 'email', 'is_active', 'display_date_joined',
- )
+ list_display = ('fullname', 'email', 'is_active', 'display_date_joined',)
ordering = ("-id",)
- search_fields = (
- 'email', 'fullname', 'username',
- )
+ search_fields = ('email', 'fullname', 'username',)
list_filter = [
"is_active",
"is_staff",
@@ -59,10 +45,11 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
("date_joined", RangeDateTimeFilter),
]
inlines = [LocationHistoryInline]
+
add_fieldsets = (
(None, {
'classes': ('wide',),
- 'fields': ( ('fullname', 'email'), 'phone_number', 'birthdate', 'gender','avatar', 'skill', 'info'),
+ 'fields': (('fullname', 'email'), 'phone_number', 'birthdate', 'gender', 'avatar', 'skill', 'info'),
}),
(_('Location'), {
'fields': ('city', 'country'),
@@ -77,57 +64,41 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
'classes': ('collapse',),
}),
)
+
fieldsets = (
(None, {"fields": ("email", "fullname")}),
- (
- _("Basic Information"),
- {
- "fields": ("gender", "avatar", "phone_number", "birthdate", 'info', 'skill', "password"),
- "classes": ["tab"],
- },
- ),
- (
- _('Country & City'), {
- 'fields': ('city', 'country'),
- "classes": ["tab"],
- }
- ),
- (
- _('Device Information'), {
- 'fields': ('device_id', 'device_os', 'fcm', 'language', ),
- "classes": ["tab"],
- }
- ),
- (
- _('Authentication'), {
- 'fields': ('display_auth_token',),
- "classes": ["tab"],
- }
- ),
- (
- _('Permissions'), {
- 'fields': ('user_type', 'is_active', 'is_staff', 'groups'),
- "classes": ["tab"],
- }
- ),
- (
- _('Important dates'), {
- 'fields': ('last_login', 'date_joined', 'deleted_at'),
- "classes": ["tab"],
- }
- ),
+ (_("Basic Information"), {
+ "fields": ("gender", "avatar", "phone_number", "birthdate", 'info', 'skill', "password"),
+ "classes": ["tab"],
+ }),
+ (_('Country & City'), {
+ 'fields': ('city', 'country'),
+ "classes": ["tab"],
+ }),
+ (_('Device Information'), {
+ 'fields': ('device_id', 'device_os', 'fcm', 'language',),
+ "classes": ["tab"],
+ }),
+ (_('Authentication'), {
+ 'fields': ('display_auth_token',),
+ "classes": ["tab"],
+ }),
+ (_('Permissions'), {
+ 'fields': ('user_type', 'is_active', 'is_staff', 'groups'),
+ "classes": ["tab"],
+ }),
+ (_('Important dates'), {
+ 'fields': ('last_login', 'date_joined', 'deleted_at'),
+ "classes": ["tab"],
+ }),
)
+
formfield_overrides = {
- models.TextField: {
- "widget": WysiwygWidget,
- }
- }
- radio_fields = {
- "gender": admin.HORIZONTAL,
+ models.TextField: {"widget": WysiwygWidget}
}
+ radio_fields = {"gender": admin.HORIZONTAL}
readonly_fields = ["last_login", "date_joined", "display_auth_token"]
-
@display(description=_("Date Joined"))
def display_date_joined(self, instance: User):
return instance.date_joined.strftime("%Y-%m-%d %H:%M") if instance.date_joined else "-"
@@ -139,8 +110,6 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
@display(description=_("Authentication Token"))
def display_auth_token(self, instance: User):
from rest_framework.authtoken.models import Token
- from django.utils.html import format_html
-
try:
token, created = Token.objects.get_or_create(user=instance)
return format_html('{}', token.key)
@@ -151,11 +120,12 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
qs = super().get_queryset(request)
return qs.filter(email__isnull=False)
+# =========================================================
+# 2. Specific User Type Admins
+# =========================================================
+
class GuestUserAdmin(UserAdmin):
- list_display = (
- 'device_id', 'device_os', 'is_active', 'display_date_joined',
- )
- # Inherits fieldsets from UserAdmin, which now include the auth token
+ list_display = ('device_id', 'device_os', 'is_active', 'display_date_joined',)
def has_add_permission(self, request):
if '_popup' in request.GET and request.GET['_popup'] == '1':
@@ -171,20 +141,17 @@ class GuestUserAdmin(UserAdmin):
return instance.date_joined.strftime("%Y-%m-%d %H:%M") if instance.date_joined else "-"
-
-
-
-
class StudentParticipantInline(StackedInline):
- """نمایش دورههای شرکت کرده دانشآموز"""
+ """Inline to show courses a student has joined"""
model = Participant
extra = 0
- readonly_fields = ('course','joined_date', 'course_status', 'course_professor')
+ readonly_fields = ('course', 'joined_date', 'course_status', 'course_professor')
fields = ('course', 'course_status', 'course_professor', 'joined_date', 'is_active')
verbose_name = _('Course Participation')
verbose_name_plural = _('Course Participations')
- autocomplete_fields = ['course'] # اضافه کردن autocomplete برای course
+ autocomplete_fields = ['course']
tab = True
+
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('course', 'course__professor')
@@ -203,21 +170,15 @@ class StudentParticipantInline(StackedInline):
def has_add_permission(self, request, obj=None):
return True
-
def has_change_permission(self, request, obj=None):
return True
-
def has_delete_permission(self, request, obj=None):
return True
-
-
-
class StudentUserAdmin(UserAdmin):
- list_display = (
- 'display_header', 'email', 'gender', 'display_age', 'courses_count'
- )
+ list_display = ('display_header', 'email', 'gender', 'display_age', 'courses_count')
+
add_fieldsets = (
(None, {
'classes': ('wide',),
@@ -232,60 +193,40 @@ class StudentUserAdmin(UserAdmin):
'classes': ('collapse',),
}),
)
- # Ensure the fieldsets from UserAdmin are used, which now include the auth token
inlines = [StudentParticipantInline, LocationHistoryInline]
-
@display(description=_("Student"), header=True)
def display_header(self, instance: StudentUser):
- from django.templatetags.static import static
-
- # Get avatar image path - use user's avatar if available, otherwise use default
avatar_path = instance.avatar.url if instance.avatar else static("images/reading(1).png")
-
return [
instance.fullname,
None,
None,
{
- "path": avatar_path,
- "height": 30,
- "width": 36,
- "borderless": True,
- # "squared": True,
+ "path": avatar_path,
+ "height": 30,
+ "width": 36,
+ "borderless": True,
},
]
@display(description=_("Age"))
def display_age(self, instance: StudentUser):
- from django.utils.html import format_html
from datetime import date
-
if not instance.birthdate:
return "-"
-
today = date.today()
birthdate = instance.birthdate
age = today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day))
-
formatted_date = birthdate.strftime("%Y-%m-%d")
-
- return format_html(
- '{}',
- f"Born on {formatted_date}",
- age
- )
+ return format_html('{}', f"Born on {formatted_date}", age)
@display(description=_("Courses"), dropdown=True)
def courses_count(self, instance: StudentUser):
- from django.utils.html import format_html
-
total = instance.participated_courses.count()
items = []
-
for participant in instance.participated_courses.all():
course = participant.course
-
title = format_html(
"""
@@ -298,13 +239,8 @@ class StudentUserAdmin(UserAdmin):
course.title,
course.id
)
- items.append(
- {
- "title": title,
- }
- )
+ items.append({"title": title})
- # Display custom string if no records found
if total == 0:
return "-"
@@ -315,107 +251,53 @@ class StudentUserAdmin(UserAdmin):
}
def get_queryset(self, request):
- """
- Optimize queries by prefetching related courses
- """
- return (
- super().get_queryset(request)
- .prefetch_related(
- "participated_courses",
- "participated_courses__course",
- )
+ return super().get_queryset(request).prefetch_related(
+ "participated_courses",
+ "participated_courses__course",
)
-
-
-# Register with default admin site for filer app compatibility
-admin.site.register(User, UserAdmin)
-
-# Register with custom project admin site
-project_admin_site.register(User, UserAdmin)
-project_admin_site.register(ClientUser, GuestUserAdmin)
-project_admin_site.register(StudentUser, StudentUserAdmin)
-
-
-
-@admin.register(Group, site=project_admin_site)
-class GroupAdmin(BaseGroupAdmin, ModelAdmin):
- list_display = ('name', 'permissions_count')
- search_fields = ('name',)
- ordering = ('name',)
- filter_horizontal = ('permissions',)
-
- fieldsets = (
- (None, {'fields': ('name',)}),
- (_('Permissions'), {'fields': ('permissions',), 'classes': ['tab']}),
- )
-
- @display(description=_("Permissions"))
- def permissions_count(self, obj):
- count = obj.permissions.count()
- return f"{count} {_('permissions')}" if count > 0 else "-"
-
class CourseTableSection(TableSection):
verbose_name = _("Course Categories")
related_name = "courses"
height = 380
- fields = [
- "title",
- "status",
- "edit_link"
- ]
+ fields = ["title", "status", "edit_link"]
def edit_link(self, instance):
- from django.utils.html import format_html
-
return format_html(
'
'
'visibility'
'',
instance.id
)
-
edit_link.short_description = _("Edit")
-
class ProfessorUserAdmin(UserAdmin):
- list_display = (
- 'display_header', 'email', 'courses_count'
- )
+ list_display = ('display_header', 'email', 'courses_count')
list_sections = [CourseTableSection]
-
save_as = True
- # Inherits fieldsets from UserAdmin, which now include the auth token
@display(description=_("Professor"), header=True)
def display_header(self, instance: StudentUser):
- from django.templatetags.static import static
-
- # Get avatar image path - use user's avatar if available, otherwise use default
avatar_path = instance.avatar.url if instance.avatar else static("images/reading(1).png")
-
return [
instance.fullname,
None,
None,
{
- "path": avatar_path,
- "height": 30,
- "width": 50,
- "borderless": True,
- "squared": True,
+ "path": avatar_path,
+ "height": 30,
+ "width": 50,
+ "borderless": True,
+ "squared": True,
},
]
@display(description=_("Courses"), dropdown=True)
def courses_count(self, instance: ProfessorUser):
- from django.utils.html import format_html
-
total = instance.courses.count()
items = []
-
for course in instance.courses.all():
title = format_html(
"""
@@ -429,13 +311,8 @@ class ProfessorUserAdmin(UserAdmin):
course.title,
course.id
)
- items.append(
- {
- "title": title,
- }
- )
+ items.append({"title": title})
- # Display custom string if no records found
if total == 0:
return "-"
@@ -446,16 +323,56 @@ class ProfessorUserAdmin(UserAdmin):
}
def get_queryset(self, request):
- """
- Optimize queries by prefetching related courses
- """
- return (
- super().get_queryset(request)
- .prefetch_related("courses")
- )
+ return super().get_queryset(request).prefetch_related("courses")
-# Register the ProfessorUserAdmin with the project admin site
-project_admin_site.register(ProfessorUser, ProfessorUserAdmin)
+class GroupAdmin(BaseGroupAdmin, ModelAdmin):
+ list_display = ('name', 'permissions_count')
+ search_fields = ('name',)
+ ordering = ('name',)
+ filter_horizontal = ('permissions',)
-admin.site.unregister(TokenProxy)
+ fieldsets = (
+ (None, {'fields': ('name',)}),
+ (_('Permissions'), {'fields': ('permissions',), 'classes': ['tab']}),
+ )
+
+ @display(description=_("Permissions"))
+ def permissions_count(self, obj):
+ count = obj.permissions.count()
+ return f"{count} {_('permissions')}" if count > 0 else "-"
+
+
+# =========================================================
+# 3. Registrations (SAFE METHOD)
+# =========================================================
+
+# A. DEFAULT DJANGO ADMIN (SAFE REGISTRATION)
+# This is required because plugins like 'django-filer' expect User to be registered here.
+try:
+ admin.site.unregister(User)
+except admin.sites.NotRegistered:
+ pass
+
+try:
+ admin.site.register(User, UserAdmin)
+except admin.sites.AlreadyRegistered:
+ pass
+
+# B. PROJECT ADMIN SITE (Imam Javad)
+project_admin_site.register(User, UserAdmin)
+project_admin_site.register(ClientUser, GuestUserAdmin)
+project_admin_site.register(StudentUser, StudentUserAdmin)
+project_admin_site.register(ProfessorUser, ProfessorUserAdmin)
+project_admin_site.register(Group, GroupAdmin)
+
+# C. DOVOODI ADMIN SITE
+dovoodi_admin_site.register(User, UserAdmin)
+dovoodi_admin_site.register(ClientUser, GuestUserAdmin)
+dovoodi_admin_site.register(Group, GroupAdmin)
+
+# D. Unregister TokenProxy safely (Cleaner UI)
+try:
+ admin.site.unregister(TokenProxy)
+except admin.sites.NotRegistered:
+ pass
\ No newline at end of file
diff --git a/apps/api/admin.py b/apps/api/admin.py
index 5f0ec4b..6b94fbf 100644
--- a/apps/api/admin.py
+++ b/apps/api/admin.py
@@ -73,11 +73,11 @@ class ThumbnailOptionAdmin(ModelAdmin):
return super().changelist_view(request, extra_context=extra_context)
-from utils.admin import project_admin_site
+from utils.admin import project_admin_site, dovoodi_admin_site
project_admin_site.register(ThumbnailOption, ThumbnailOptionAdmin)
+dovoodi_admin_site.register(ThumbnailOption, ThumbnailOptionAdmin)
-@admin.register(Comment, site=project_admin_site)
class CommentAdmin(ModelAdmin):
list_display = [
'user_fullname',
@@ -90,7 +90,6 @@ class CommentAdmin(ModelAdmin):
ordering = ['order', '-created_at']
-@admin.register(AppVersion, site=project_admin_site)
class AppVersionAdmin(ModelAdmin):
list_display = [
'version',
@@ -101,3 +100,10 @@ class AppVersionAdmin(ModelAdmin):
search_fields = ['version', 'description']
list_filter = ['app_type', 'is_active', 'created_at']
ordering = ['-created_at']
+
+
+# Register models with both admin sites
+project_admin_site.register(Comment, CommentAdmin)
+project_admin_site.register(AppVersion, AppVersionAdmin)
+dovoodi_admin_site.register(Comment, CommentAdmin)
+dovoodi_admin_site.register(AppVersion, AppVersionAdmin)
diff --git a/apps/article/admin.py b/apps/article/admin.py
index 6f472fd..6c3c11d 100755
--- a/apps/article/admin.py
+++ b/apps/article/admin.py
@@ -13,7 +13,7 @@ from unfold.decorators import display, action
from django import forms
from django.urls import path, reverse_lazy
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from unfold.sections import TableSection
from apps.article.models import (
@@ -310,9 +310,9 @@ class ContentPartAdmin(ModelAdmin):
# Register models with admin site
-project_admin_site.register(ArticleCategory, ArticleCategoryAdmin)
-project_admin_site.register(PinnedArticleCollection, PinnedArticleCollectionAdmin)
-project_admin_site.register(MiddleArticleCollection, MiddleArticleCollectionAdmin)
-project_admin_site.register(Article, ArticleAdmin)
-project_admin_site.register(ArticleContent, ArticleContentAdmin)
-project_admin_site.register(ContentPart, ContentPartAdmin)
+dovoodi_admin_site.register(ArticleCategory, ArticleCategoryAdmin)
+dovoodi_admin_site.register(PinnedArticleCollection, PinnedArticleCollectionAdmin)
+dovoodi_admin_site.register(MiddleArticleCollection, MiddleArticleCollectionAdmin)
+dovoodi_admin_site.register(Article, ArticleAdmin)
+dovoodi_admin_site.register(ArticleContent, ArticleContentAdmin)
+dovoodi_admin_site.register(ContentPart, ContentPartAdmin)
diff --git a/apps/course/admin/course.py b/apps/course/admin/course.py
index afaaaf8..f7ff313 100644
--- a/apps/course/admin/course.py
+++ b/apps/course/admin/course.py
@@ -33,7 +33,7 @@ from .professor_base import DirectCourseAdmin, CourseRelatedAdmin, AttachmentGlo
from unfold.contrib.forms.widgets import ArrayWidget
from django.contrib.postgres.fields import ArrayField
-from utils.admin import project_admin_site
+from utils.admin import project_admin_site ,dovoodi_admin_site
from utils.json_editor_field import JsonEditorWidget
from apps.course.models import Course, Glossary, Attachment, CourseCategory, Participant, CourseGlossary, CourseAttachment
from apps.course.models.lesson import Lesson, CourseLesson
@@ -545,14 +545,18 @@ class CourseAttachmentAdmin(CourseRelatedAdmin):
return obj.attachment.file_size
-# Register with both admin sites for autocomplete support
from django.contrib import admin as django_admin
-django_admin.site.register(Course, CourseAdmin)
-django_admin.site.register(CourseCategory, CourseCategoryAdmin)
-django_admin.site.register(Glossary, GlossaryAdmin)
-django_admin.site.register(Attachment, AttachmentAdmin)
-
-# Register with the project admin site
+try:
+ django_admin.site.register(Course, CourseAdmin)
+ django_admin.site.register(CourseCategory, CourseCategoryAdmin)
+ django_admin.site.register(Glossary, GlossaryAdmin)
+ django_admin.site.register(Attachment, AttachmentAdmin)
+except admin.sites.AlreadyRegistered:
+ pass
+
+# =========================================================
+# 2. REGISTER TO PROJECT ADMIN (The Full Admin Panel)
+# =========================================================
project_admin_site.register(Course, CourseAdmin)
project_admin_site.register(CourseCategory, CourseCategoryAdmin)
project_admin_site.register(Glossary, GlossaryAdmin)
@@ -561,4 +565,32 @@ project_admin_site.register(Attachment, AttachmentAdmin)
project_admin_site.register(CourseAttachment, CourseAttachmentAdmin)
project_admin_site.register(Participant, ParticipantAdmin)
-# مدلهای ProfessorUser و StudentUser قبلاً در admin های مربوطه ثبت شدهاند
+
+# =========================================================
+# 3. REGISTER TO DOVOODI ADMIN (The "Ghost" Fix)
+# =========================================================
+
+# IMPORTANT: Do NOT inherit from CourseAdmin. Inherit from ModelAdmin directly.
+# This prevents dragging in 'inlines' and 'autocomplete_fields' that cause errors.
+
+class HiddenCourseAdmin(ModelAdmin):
+ # We only need search_fields so the Autocomplete box works
+ search_fields = ('title', 'id')
+
+ # No inlines
+ # No autocomplete_fields
+ # No fieldsets
+
+ # Hide from the Menu
+ def has_module_permission(self, request):
+ return False
+
+ # Disable all permissions
+ def has_add_permission(self, request):
+ return False
+ def has_change_permission(self, request, obj=None):
+ return False
+ def has_delete_permission(self, request, obj=None):
+ return False
+
+dovoodi_admin_site.register(Course, HiddenCourseAdmin)
\ No newline at end of file
diff --git a/apps/dobodbi_calendar/admin.py b/apps/dobodbi_calendar/admin.py
index eafc931..35165fd 100644
--- a/apps/dobodbi_calendar/admin.py
+++ b/apps/dobodbi_calendar/admin.py
@@ -19,7 +19,7 @@ from unfold.contrib.filters.admin import (
)
from apps.dobodbi_calendar.models import CalendarOccasions
from utils.json_editor_field import JsonEditorWidget
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from utils.schema import get_calender_dates_schema
@@ -132,4 +132,4 @@ class CalendarOccasionsAdmin(ModelAdmin):
return queryset, use_distinct
-project_admin_site.register(CalendarOccasions, CalendarOccasionsAdmin)
+dovoodi_admin_site.register(CalendarOccasions, CalendarOccasionsAdmin)
diff --git a/apps/hadis/admin/category.py b/apps/hadis/admin/category.py
index e30f48d..01c74f2 100644
--- a/apps/hadis/admin/category.py
+++ b/apps/hadis/admin/category.py
@@ -8,7 +8,7 @@ from mptt.admin import DraggableMPTTAdmin
from utils.json_editor_field import JsonEditorWidget
import json
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from ..models import HadisSect, HadisCategory
@@ -217,5 +217,5 @@ class HadisCategoryAdmin(DraggableMPTTAdmin, ModelAdmin):
# Register models with the custom admin site
-project_admin_site.register(HadisSect, HadisSectAdmin)
-project_admin_site.register(HadisCategory, HadisCategoryAdmin)
\ No newline at end of file
+dovoodi_admin_site.register(HadisSect, HadisSectAdmin)
+dovoodi_admin_site.register(HadisCategory, HadisCategoryAdmin)
\ No newline at end of file
diff --git a/apps/hadis/admin/hadis.py b/apps/hadis/admin/hadis.py
index 5cecd33..4acc62f 100644
--- a/apps/hadis/admin/hadis.py
+++ b/apps/hadis/admin/hadis.py
@@ -7,7 +7,7 @@ from unfold.decorators import display, action
from utils.json_editor_field import JsonEditorWidget
import json
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site,dovoodi_admin_site
from ..models import (
Hadis, HadisReference, HadisTag, HadisStatus, ReferenceImage,
HadisCollection, HadisInCollection, HadisCorrection
@@ -378,12 +378,12 @@ class HadisCorrectionAdmin(ModelAdmin):
)
-# Register models with the custom admin site
-project_admin_site.register(HadisTag, HadisTagAdmin)
-project_admin_site.register(HadisStatus, HadisStatusAdmin)
-project_admin_site.register(Hadis, HadisAdmin)
-project_admin_site.register(HadisReference, HadisReferenceAdmin)
-project_admin_site.register(ReferenceImage, ReferenceImageAdmin)
-project_admin_site.register(HadisCollection, HadisCollectionAdmin)
-project_admin_site.register(HadisInCollection, HadisInCollectionAdmin)
-project_admin_site.register(HadisCorrection, HadisCorrectionAdmin)
\ No newline at end of file
+# Register models with dovoodi admin site
+dovoodi_admin_site.register(HadisTag, HadisTagAdmin)
+dovoodi_admin_site.register(HadisStatus, HadisStatusAdmin)
+dovoodi_admin_site.register(Hadis, HadisAdmin)
+dovoodi_admin_site.register(HadisReference, HadisReferenceAdmin)
+dovoodi_admin_site.register(ReferenceImage, ReferenceImageAdmin)
+dovoodi_admin_site.register(HadisCollection, HadisCollectionAdmin)
+dovoodi_admin_site.register(HadisInCollection, HadisInCollectionAdmin)
+dovoodi_admin_site.register(HadisCorrection, HadisCorrectionAdmin)
diff --git a/apps/hadis/admin/reference.py b/apps/hadis/admin/reference.py
index 5175cf8..ef6f2db 100644
--- a/apps/hadis/admin/reference.py
+++ b/apps/hadis/admin/reference.py
@@ -7,7 +7,7 @@ from utils.json_editor_field import JsonEditorWidget
import json
# Import your custom admin site
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
# Import your models
from ..models import (
@@ -383,7 +383,7 @@ class BookAttributeAdmin(ModelAdmin):
# 3. Registration
# -----------------------------------------------------------------------------
-project_admin_site.register(BookReference, BookReferenceAdmin)
-project_admin_site.register(BookAuthor, BookAuthorAdmin)
-project_admin_site.register(BookAttribute, BookAttributeAdmin)
-project_admin_site.register(BookReferenceImage, BookReferenceImageAdmin)
\ No newline at end of file
+dovoodi_admin_site.register(BookReference, BookReferenceAdmin)
+dovoodi_admin_site.register(BookAuthor, BookAuthorAdmin)
+dovoodi_admin_site.register(BookAttribute, BookAttributeAdmin)
+dovoodi_admin_site.register(BookReferenceImage, BookReferenceImageAdmin)
\ No newline at end of file
diff --git a/apps/hadis/admin/transmitter.py b/apps/hadis/admin/transmitter.py
index 0872d6e..05bcee4 100644
--- a/apps/hadis/admin/transmitter.py
+++ b/apps/hadis/admin/transmitter.py
@@ -7,7 +7,7 @@ from unfold.contrib.forms.widgets import WysiwygWidget
from utils.json_editor_field import JsonEditorWidget
import json
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from ..models import (
Transmitters, HadisTransmitter, NarratorLayer, TransmitterReliability,
OpinionStatus, TransmitterOpinion, TransmitterOriginalText
@@ -522,10 +522,10 @@ class TransmitterOriginalTextAdmin(ModelAdmin):
# Register models with the custom admin site
-project_admin_site.register(Transmitters, TransmittersAdmin)
-project_admin_site.register(HadisTransmitter, HadisTransmitterAdmin)
-project_admin_site.register(NarratorLayer, NarratorLayerAdmin)
-project_admin_site.register(TransmitterReliability, TransmitterReliabilityAdmin)
-project_admin_site.register(OpinionStatus, OpinionStatusAdmin)
-project_admin_site.register(TransmitterOpinion, TransmitterOpinionAdmin)
-project_admin_site.register(TransmitterOriginalText, TransmitterOriginalTextAdmin)
\ No newline at end of file
+dovoodi_admin_site.register(Transmitters, TransmittersAdmin)
+dovoodi_admin_site.register(HadisTransmitter, HadisTransmitterAdmin)
+dovoodi_admin_site.register(NarratorLayer, NarratorLayerAdmin)
+dovoodi_admin_site.register(TransmitterReliability, TransmitterReliabilityAdmin)
+dovoodi_admin_site.register(OpinionStatus, OpinionStatusAdmin)
+dovoodi_admin_site.register(TransmitterOpinion, TransmitterOpinionAdmin)
+dovoodi_admin_site.register(TransmitterOriginalText, TransmitterOriginalTextAdmin)
\ No newline at end of file
diff --git a/apps/hadis/admin/version.py b/apps/hadis/admin/version.py
index f33a956..270892b 100644
--- a/apps/hadis/admin/version.py
+++ b/apps/hadis/admin/version.py
@@ -2,7 +2,7 @@ from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from ..models import ContentRelease
@@ -26,4 +26,4 @@ class ContentReleaseAdmin(ModelAdmin):
# Register model with the custom admin site
-project_admin_site.register(ContentRelease, ContentReleaseAdmin)
+dovoodi_admin_site.register(ContentRelease, ContentReleaseAdmin)
diff --git a/apps/library/admin.py b/apps/library/admin.py
index 87407b3..db85697 100644
--- a/apps/library/admin.py
+++ b/apps/library/admin.py
@@ -8,7 +8,7 @@ from django.contrib.admin import SimpleListFilter
from unfold.decorators import display, action
from django import forms
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from apps.library.models import *
@@ -17,7 +17,7 @@ class BookCollectionAdmin(ModelAdmin):
list_filter = ('status', 'display_position')
search_fields = ('title',)
-project_admin_site.register(BookCollection, BookCollectionAdmin)
+dovoodi_admin_site.register(BookCollection, BookCollectionAdmin)
class BookAdminForm(forms.ModelForm):
class Meta:
@@ -243,8 +243,8 @@ class CategoryAdmin(ModelAdmin):
# Register models with the custom admin site
-project_admin_site.register(Book, BookAdmin)
-project_admin_site.register(PinnedBookCollection, PinnedBookCollectionAdmin)
-project_admin_site.register(MiddleBookCollection, MiddleBookCollectionAdmin)
-project_admin_site.register(Category, CategoryAdmin)
+dovoodi_admin_site.register(Book, BookAdmin)
+dovoodi_admin_site.register(PinnedBookCollection, PinnedBookCollectionAdmin)
+dovoodi_admin_site.register(MiddleBookCollection, MiddleBookCollectionAdmin)
+dovoodi_admin_site.register(Category, CategoryAdmin)
diff --git a/apps/podcast/admin.py b/apps/podcast/admin.py
index bee3eb6..669e2dc 100755
--- a/apps/podcast/admin.py
+++ b/apps/podcast/admin.py
@@ -10,7 +10,7 @@ from unfold.widgets import UnfoldAdminSelectWidget
from unfold.decorators import display, action
from django import forms
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from unfold.sections import TableSection
from apps.podcast.models import *
@@ -338,9 +338,9 @@ class UserPlaylistAdmin(ModelAdmin):
)
-project_admin_site.register(PodcastCategory, PodcastCategoryAdmin)
-project_admin_site.register(Podcast, PodcastAdmin)
-project_admin_site.register(PinnedPodcastCollection, PinnedPodcastCollectionAdmin)
-project_admin_site.register(MiddlePodcastCollection, MiddlePodcastCollectionAdmin)
-project_admin_site.register(PodcastPlaylist, PodcastPlaylistAdmin)
-project_admin_site.register(UserPlaylist, UserPlaylistAdmin)
+dovoodi_admin_site.register(PodcastCategory, PodcastCategoryAdmin)
+dovoodi_admin_site.register(Podcast, PodcastAdmin)
+dovoodi_admin_site.register(PinnedPodcastCollection, PinnedPodcastCollectionAdmin)
+dovoodi_admin_site.register(MiddlePodcastCollection, MiddlePodcastCollectionAdmin)
+dovoodi_admin_site.register(PodcastPlaylist, PodcastPlaylistAdmin)
+dovoodi_admin_site.register(UserPlaylist, UserPlaylistAdmin)
diff --git a/apps/video/admin.py b/apps/video/admin.py
index c05d181..b22bff5 100755
--- a/apps/video/admin.py
+++ b/apps/video/admin.py
@@ -11,7 +11,7 @@ from unfold.widgets import UnfoldAdminSelectWidget
from unfold.decorators import display, action
from django import forms
-from utils.admin import project_admin_site
+from utils.admin import dovoodi_admin_site
from unfold.sections import TableSection
from apps.video.models import *
@@ -331,8 +331,8 @@ class VideoPlaylistAdmin(ModelAdmin):
super().save_formset(request, form, formset, change)
-project_admin_site.register(VideoCategory, VideoCategoryAdmin)
-project_admin_site.register(Video, VideoAdmin)
-project_admin_site.register(PinnedVideoCollection, PinnedVideoCollectionAdmin)
-project_admin_site.register(MiddleVideoCollection, MiddleVideoCollectionAdmin)
-project_admin_site.register(VideoPlaylist, VideoPlaylistAdmin)
+dovoodi_admin_site.register(VideoCategory, VideoCategoryAdmin)
+dovoodi_admin_site.register(Video, VideoAdmin)
+dovoodi_admin_site.register(PinnedVideoCollection, PinnedVideoCollectionAdmin)
+dovoodi_admin_site.register(MiddleVideoCollection, MiddleVideoCollectionAdmin)
+dovoodi_admin_site.register(VideoPlaylist, VideoPlaylistAdmin)
diff --git a/config/settings/base.py b/config/settings/base.py
index 3dd91ae..0eba77c 100755
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -302,7 +302,7 @@ FILE_UPLOAD_HANDLERS = [
######################################################################
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
LOGIN_URL = "admin:login"
-LOGIN_REDIRECT_URL = reverse_lazy("admin:index")
+LOGIN_REDIRECT_URL = reverse_lazy("admin.index")
# STORAGES = {
# "default": {
# "BACKEND": "django.core.files.storage.FileSystemStorage",
@@ -314,6 +314,8 @@ LOGIN_REDIRECT_URL = reverse_lazy("admin:index")
######################################################################
# Unfold
######################################################################
+from utils.admin import admin_url_generator
+
UNFOLD = {
"SITE_TITLE": _("Imam Jawad Admin"),
"SITE_HEADER": _("Imam Jawad Admin"),
@@ -403,13 +405,13 @@ UNFOLD = {
{
"title": _("Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:video_pinnedvideocollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_pinnedvideocollection_changelist"),
"active": lambda request: "video/pinnedvideocollection" in request.path and "library/middlevideocollection" not in request.path,
},
{
"title": _("Middle Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:video_middlevideocollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_middlevideocollection_changelist"),
"active": lambda request: "video/middlevideocollection" in request.path,
},
],
@@ -421,13 +423,13 @@ UNFOLD = {
{
"title": _("Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:library_pinnedbookcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "library_pinnedbookcollection_changelist"),
"active": lambda request: "library/pinnedbookcollection" in request.path and "library/middlebookcollection" not in request.path,
},
{
"title": _("Middle Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:library_middlebookcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "library_middlebookcollection_changelist"),
"active": lambda request: "library/middlebookcollection" in request.path,
},
@@ -440,13 +442,13 @@ UNFOLD = {
{
"title": _("Pinned Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:article_pinnedarticlecollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_pinnedarticlecollection_changelist"),
"active": lambda request: "article/pinnedarticlecollection" in request.path and "article/middlearticlecollection" not in request.path,
},
{
"title": _("Regular Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:article_middlearticlecollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_middlearticlecollection_changelist"),
"active": lambda request: "article/middlearticlecollection" in request.path,
},
],
@@ -458,10 +460,10 @@ UNFOLD = {
{
"title": _("Users"),
"icon": "sports_motorsports",
- "link": reverse_lazy("admin:account_user_changelist"),
- "active": lambda request: request.path
- == reverse_lazy("admin:account_user_changelist")
- and "email__isnull" not in request.GET,
+ "link": lambda request: admin_url_generator(request, "account_user_changelist"),
+ # "active": lambda request: request.path
+ # == lambda request: admin_url_generator(request, "account_user_changelist")
+ # and "email__isnull" not in request.GET,
},
{
"title": _("Guest Users"),
@@ -478,7 +480,7 @@ UNFOLD = {
{
"title": _("Groups"),
"icon": "shield",
- "link": reverse_lazy("admin:auth_group_changelist"),
+ "link": lambda request: admin_url_generator(request, "auth_group_changelist"),
},
],
},
@@ -494,26 +496,26 @@ UNFOLD = {
{
"title": _("Courses"),
"icon": "school",
- "link": reverse_lazy("admin:course_course_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_course_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_course_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_course_changelist"))),
},
{
"title": _("Course Lessons"),
"icon": "menu_book",
- "link": reverse_lazy("admin:course_courselesson_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_courselesson_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_courselesson_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_courselesson_changelist"))),
},
{
"title": _("Course Attachments"),
"icon": "attach_file",
- "link": reverse_lazy("admin:course_courseattachment_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_courseattachment_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_courseattachment_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_courseattachment_changelist"))),
},
{
"title": _("Course Glossary"),
"icon": "book",
- "link": reverse_lazy("admin:course_courseglossary_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_courseglossary_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_courseglossary_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_courseglossary_changelist"))),
},
],
@@ -529,20 +531,20 @@ UNFOLD = {
{
"title": _("Course Onlines"),
"icon": "video_call",
- "link": reverse_lazy("admin:course_courselivesession_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_courselivesession_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_courselivesession_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_courselivesession_changelist"))),
},
{
"title": _("Session Users"),
"icon": "groups",
- "link": reverse_lazy("admin:course_livesessionuser_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_livesessionuser_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_livesessionuser_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_livesessionuser_changelist"))),
},
{
"title": _("Session Recordings"),
"icon": "play_circle",
- "link": reverse_lazy("admin:course_livesessionrecording_changelist"),
- "active": lambda request: request.path.startswith(str(reverse_lazy("admin:course_livesessionrecording_changelist"))),
+ "link": lambda request: admin_url_generator(request, "course_livesessionrecording_changelist"),
+ "active": lambda request: request.path.startswith(str(lambda request: admin_url_generator(request, "course_livesessionrecording_changelist"))),
},
],
},
@@ -553,13 +555,13 @@ UNFOLD = {
{
"title": _("Pinned Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:podcast_pinnedpodcastcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_pinnedpodcastcollection_changelist"),
"active": lambda request: "podcast/pinnedpodcastcollection" in request.path and "podcast/middlepodcastcollection" not in request.path,
},
{
"title": _("Regular Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:podcast_middlepodcastcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_middlepodcastcollection_changelist"),
"active": lambda request: "podcast/middlepodcastcollection" in request.path,
},
],
@@ -577,7 +579,7 @@ UNFOLD = {
{
"title": _("Dashboard"),
"icon": "dashboard",
- "link": reverse_lazy("admin:index"),
+ "link": lambda request: admin_url_generator(request, "index"),
},
],
},
@@ -587,7 +589,7 @@ UNFOLD = {
{
"title": _("Authentication"),
"icon": "shield",
- "link": reverse_lazy("admin:auth_group_changelist"),
+ "link": lambda request: admin_url_generator(request, "auth_group_changelist"),
"permission": lambda request: request.user.is_staff,
},
],
@@ -598,7 +600,7 @@ UNFOLD = {
{
"title": _("Users"),
"icon": "person",
- "link": reverse_lazy("admin:account_user_changelist"),
+ "link": lambda request: admin_url_generator(request, "account_user_changelist"),
"permission": lambda request: request.user.is_staff,
},
],
@@ -609,7 +611,7 @@ UNFOLD = {
{
"title": _("Students"),
"icon": "school",
- "link": reverse_lazy("admin:account_studentuser_changelist"),
+ "link": lambda request: admin_url_generator(request, "account_studentuser_changelist"),
"permission": lambda request: request.user.is_staff,
},
@@ -621,7 +623,7 @@ UNFOLD = {
{
"title": _("Professors"),
"icon": "person_book",
- "link": reverse_lazy("admin:account_professoruser_changelist"),
+ "link": lambda request: admin_url_generator(request, "account_professoruser_changelist"),
"permission": lambda request: request.user.is_staff,
},
@@ -633,7 +635,7 @@ UNFOLD = {
{
"title": _("Calender"),
"icon": "calendar_today",
- "link": reverse_lazy("admin:dobodbi_calendar_calendaroccasions_changelist"),
+ "link": lambda request: admin_url_generator(request, "dobodbi_calendar_calendaroccasions_changelist"),
"permission": lambda request: request.user.is_staff,
},
],
@@ -646,47 +648,47 @@ UNFOLD = {
{
"title": _("Categories"),
"icon": "category",
- "link": reverse_lazy("admin:course_coursecategory_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_coursecategory_changelist"),
},
{
"title": _("Courses"),
"icon": "school",
- "link": reverse_lazy("admin:course_course_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_course_changelist"),
},
{
"title": _("Lessons"),
"icon": "menu_book",
- "link": reverse_lazy("admin:course_lesson_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_lesson_changelist"),
},
{
"title": _("Attachments"),
"icon": "attach_file",
- "link": reverse_lazy("admin:course_attachment_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_attachment_changelist"),
},
{
"title": _("Glossary"),
"icon": "book",
- "link": reverse_lazy("admin:course_glossary_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_glossary_changelist"),
},
{
"title": _("Live Sessions"),
"icon": "video_call",
- "link": reverse_lazy("admin:course_courselivesession_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_courselivesession_changelist"),
},
{
"title": _("Session Users"),
"icon": "groups",
- "link": reverse_lazy("admin:course_livesessionuser_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_livesessionuser_changelist"),
},
{
"title": _("Session Recordings"),
"icon": "play_circle",
- "link": reverse_lazy("admin:course_livesessionrecording_changelist"),
+ "link": lambda request: admin_url_generator(request, "course_livesessionrecording_changelist"),
},
{
"title": _("Certificates"),
"icon": "workspace_premium",
- "link": reverse_lazy("admin:certificate_certificate_changelist"),
+ "link": lambda request: admin_url_generator(request, "certificate_certificate_changelist"),
},
]
},
@@ -698,12 +700,12 @@ UNFOLD = {
{
"title": _("Quizzes"),
"icon": "quiz",
- "link": reverse_lazy("admin:quiz_quiz_changelist"),
+ "link": lambda request: admin_url_generator(request, "quiz_quiz_changelist"),
},
{
"title": _("Quiz Participants"),
"icon": "group",
- "link": reverse_lazy("admin:quiz_quizparticipant_changelist"),
+ "link": lambda request: admin_url_generator(request, "quiz_quizparticipant_changelist"),
},
]
},
@@ -715,7 +717,7 @@ UNFOLD = {
{
"title": _("Transactions"),
"icon": "payments",
- "link": reverse_lazy("admin:transaction_transactionparticipant_changelist"),
+ "link": lambda request: admin_url_generator(request, "transaction_transactionparticipant_changelist"),
},
]
},
@@ -727,17 +729,17 @@ UNFOLD = {
{
"title": _("Books"),
"icon": "menu_book",
- "link": reverse_lazy("admin:library_book_changelist"),
+ "link": lambda request: admin_url_generator(request, "library_book_changelist"),
},
{
"title": _("Categories"),
"icon": "category",
- "link": reverse_lazy("admin:library_category_changelist"),
+ "link": lambda request: admin_url_generator(request, "library_category_changelist"),
},
{
"title": _("Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:library_pinnedbookcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "library_pinnedbookcollection_changelist"),
},
]
},
@@ -749,22 +751,22 @@ UNFOLD = {
{
"title": _("Videos"),
"icon": "live_tv",
- "link": reverse_lazy("admin:video_video_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_video_changelist"),
},
{
"title": _("Categories"),
"icon": "category",
- "link": reverse_lazy("admin:video_videocategory_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_videocategory_changelist"),
},
{
"title": _("Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:video_pinnedvideocollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_pinnedvideocollection_changelist"),
},
{
"title": _("Playlists"),
"icon": "playlist_play",
- "link": reverse_lazy("admin:video_videoplaylist_changelist"),
+ "link": lambda request: admin_url_generator(request, "video_videoplaylist_changelist"),
# "active": lambda request: "video/videoplaylist" in request.path,
},
@@ -778,12 +780,12 @@ UNFOLD = {
{
"title": _("Comments"),
"icon": "comment",
- "link": reverse_lazy("admin:api_comment_changelist"),
+ "link": lambda request: admin_url_generator(request, "api_comment_changelist"),
},
{
"title": _("Blogs"),
"icon": "article",
- "link": reverse_lazy("admin:blog_blog_changelist"),
+ "link": lambda request: admin_url_generator(request, "blog_blog_changelist"),
},
]
},
@@ -793,7 +795,7 @@ UNFOLD = {
{
"title": _("App Versions"),
"icon": "system_update",
- "link": reverse_lazy("admin:api_appversion_changelist"),
+ "link": lambda request: admin_url_generator(request, "api_appversion_changelist"),
},
],
},
@@ -805,27 +807,27 @@ UNFOLD = {
{
"title": _("Articles"),
"icon": "article",
- "link": reverse_lazy("admin:article_article_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_article_changelist"),
},
{
"title": _("Categories"),
"icon": "category",
- "link": reverse_lazy("admin:article_articlecategory_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_articlecategory_changelist"),
},
{
"title": _("Pinned Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:article_pinnedarticlecollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_pinnedarticlecollection_changelist"),
},
{
"title": _("Regular Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:article_middlearticlecollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_middlearticlecollection_changelist"),
},
{
"title": _("Article Contents"),
"icon": "text_snippet",
- "link": reverse_lazy("admin:article_articlecontent_changelist"),
+ "link": lambda request: admin_url_generator(request, "article_articlecontent_changelist"),
},
]
},
@@ -837,32 +839,32 @@ UNFOLD = {
{
"title": _("Podcasts"),
"icon": "headset",
- "link": reverse_lazy("admin:podcast_podcast_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_podcast_changelist"),
},
{
"title": _("Categories"),
"icon": "category",
- "link": reverse_lazy("admin:podcast_podcastcategory_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_podcastcategory_changelist"),
},
{
"title": _("Pinned Collections"),
"icon": "collections_bookmark",
- "link": reverse_lazy("admin:podcast_pinnedpodcastcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_pinnedpodcastcollection_changelist"),
},
{
"title": _("Regular Collections"),
"icon": "view_module",
- "link": reverse_lazy("admin:podcast_middlepodcastcollection_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_middlepodcastcollection_changelist"),
},
{
"title": _("Playlists"),
"icon": "playlist_play",
- "link": reverse_lazy("admin:podcast_podcastplaylist_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_podcastplaylist_changelist"),
},
{
"title": _("User Playlists"),
"icon": "person_add",
- "link": reverse_lazy("admin:podcast_userplaylist_changelist"),
+ "link": lambda request: admin_url_generator(request, "podcast_userplaylist_changelist"),
},
]
},
@@ -874,17 +876,17 @@ UNFOLD = {
{
"title": _("Chat Rooms"),
"icon": "forum",
- "link": reverse_lazy("admin:chat_roommessage_changelist"),
+ "link": lambda request: admin_url_generator(request, "chat_roommessage_changelist"),
},
# {
# "title": _("Chat Messages"),
# "icon": "chat",
- # "link": reverse_lazy("admin:apps_chat_chatmessage_changelist"),
+ # "link": lambda request: admin_url_generator(request, "apps_chat_chatmessage_changelist"),
# },
# {
# "title": _("Read Status"),
# "icon": "mark_chat_read",
- # "link": reverse_lazy("admin:apps_chat_messagereadstatus_changelist"),
+ # "link": lambda request: admin_url_generator(request, "apps_chat_messagereadstatus_changelist"),
# },
]
},
@@ -896,42 +898,42 @@ UNFOLD = {
{
"title": _("Hadis Sects"),
"icon": "account_tree",
- "link": reverse_lazy("admin:hadis_hadissect_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadissect_changelist"),
},
{
"title": _("Hadis Categories"),
"icon": "category",
- "link": reverse_lazy("admin:hadis_hadiscategory_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadiscategory_changelist"),
},
{
"title": _("Hadis"),
"icon": "format_quote",
- "link": reverse_lazy("admin:hadis_hadis_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadis_changelist"),
},
{
"title": _("Hadis References"),
"icon": "link",
- "link": reverse_lazy("admin:hadis_hadisreference_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadisreference_changelist"),
},
{
"title": _("Hadis Tags"),
"icon": "label",
- "link": reverse_lazy("admin:hadis_hadistag_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadistag_changelist"),
},
{
"title": _("Hadis Status"),
"icon": "flag",
- "link": reverse_lazy("admin:hadis_hadisstatus_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadisstatus_changelist"),
},
{
"title": _("Transmitters"),
"icon": "person",
- "link": reverse_lazy("admin:hadis_transmitters_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_transmitters_changelist"),
},
{
"title": _("Hadis Transmitters"),
"icon": "group",
- "link": reverse_lazy("admin:hadis_hadistransmitter_changelist"),
+ "link": lambda request: admin_url_generator(request, "hadis_hadistransmitter_changelist"),
},
]
},
@@ -941,7 +943,7 @@ UNFOLD = {
{
"title": _("Global Preferences"),
"icon": "settings",
- "link": reverse_lazy("admin:dynamic_preferences_globalpreferencemodel_changelist"),
+ "link": lambda request: admin_url_generator(request, "dynamic_preferences_globalpreferencemodel_changelist"),
},
# You can add more preference sections here
],
diff --git a/config/urls.py b/config/urls.py
index 36633a3..30802f4 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -29,7 +29,7 @@ from rest_framework.response import Response
from utils import absolute_url
-from utils.admin import project_admin_site, HomeView
+from utils.admin import project_admin_site, HomeView ,dovoodi_admin_site
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
@@ -119,7 +119,8 @@ swagger_urlpatterns = [
]
urlpatterns+= i18n_patterns(
- path("admin/", project_admin_site.urls),
+ path("imam-javad/admin/", project_admin_site.urls),
+ path("dovoodi/admin/", dovoodi_admin_site.urls),
path('docs/', CustomAPIDocumentationView.as_view(), name='docs-index'),
*swagger_urlpatterns,
path('admin/filer/', include('filer.urls')),
diff --git a/utils/admin.py b/utils/admin.py
index c5049ce..24043ba 100644
--- a/utils/admin.py
+++ b/utils/admin.py
@@ -1,99 +1,138 @@
-
-
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
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
-from django.views.generic import RedirectView, TemplateView
+from django.views.generic import RedirectView
-# Import Filer admin classes
-from filer.admin.fileadmin import FileAdmin
-from filer.admin.folderadmin import FolderAdmin
-from filer.admin.imageadmin import ImageAdmin
-from filer.models import File, Folder, Image
+# Unfold Imports
+from unfold.sites import UnfoldAdminSite
+
+# ---------------------------------------------------------
+# 1. Helper Functions
+# ---------------------------------------------------------
+
+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 based on the URL path
+ if request.path.startswith('/en/dovoodi/') or request.path.startswith('/dovoodi/'):
+ namespace = 'dovoodi_admin'
+ else:
+ # Default to the main admin
+ namespace = 'imam_javad_admin'
+
+ # 2. Construct the view name (e.g., "imam_javad_admin:account_user_changelist")
+ full_view_name = f"{namespace}:{url_name}"
+
+ # 3. Resolve the URL
+ try:
+ return reverse(full_view_name)
+ except Exception:
+ # If the model isn't registered in this specific admin, return a dead link
+ # to prevent the whole page from crashing.
+ return "#"
def dashboard_callback(request, context):
context.update(random_data())
return context
-import json
-import random
-from functools import lru_cache
-
-from django.contrib.humanize.templatetags.humanize import intcomma
-from django.utils.safestring import mark_safe
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import RedirectView, TemplateView
-from unfold.views import UnfoldModelAdminViewMixin
+def variables(request):
+ return {"plausible_domain": getattr(settings, 'PLAUSIBLE_DOMAIN', '')}
+# ---------------------------------------------------------
+# 2. Custom Login Form
+# ---------------------------------------------------------
-from unfold.sites import UnfoldAdminSite
-from django import forms
-from django.conf import settings
-from unfold.forms import AuthenticationForm
+class LoginForm:
+ """Lazy login form to avoid circular imports during settings loading"""
+ @staticmethod
+ def get_form():
+ # Import AuthenticationForm only when needed
+ from django.contrib.auth.forms import AuthenticationForm
-class LoginForm(AuthenticationForm):
- password = forms.CharField(widget=forms.PasswordInput(render_value=True))
+ 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"
+ 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):
- login_form = LoginForm
-
-
-project_admin_site = FormulaAdminSite()
-
-# # Register Filer models with the admin site
-# project_admin_site.register(Folder, FolderAdmin)
-# project_admin_site.register(File, FileAdmin)
-# project_admin_site.register(Image, ImageAdmin)
-
+ """Main Admin for Imam Javad"""
+ site_header = "Imam Javad Admin"
+ site_title = "Imam Javad"
+ index_title = "System Administration"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # Set login form after initialization to avoid circular import
+ self.login_form = LoginForm.get_form()
+
+class DovoodiAdminSite(UnfoldAdminSite):
+ """Secondary Admin for Dovoodi"""
+ site_header = "Dovoodi Admin"
+ site_title = "Dovoodi"
+ index_title = "System Administration"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # Set login form after initialization to avoid circular import
+ self.login_form = LoginForm.get_form()
+
+# 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):
+ # Initialize with minimal setup to avoid circular imports
+ self._site_class = site_class
+ self._name = name
+ self._real_instance = None
+ # Don't call super().__init__() here to avoid circular imports
+
+ def _get_real_instance(self):
+ if self._real_instance is None:
+ self._real_instance = self._site_class(name=self._name)
+ return self._real_instance
+
+ def __getattr__(self, name):
+ return getattr(self._get_real_instance(), name)
+
+ def __call__(self, *args, **kwargs):
+ return self._get_real_instance()(*args, **kwargs)
+
+# Create placeholder instances
+project_admin_site = AdminSitePlaceholder(FormulaAdminSite, 'imam_javad_admin')
+dovoodi_admin_site = AdminSitePlaceholder(DovoodiAdminSite, 'dovoodi_admin')
class HomeView(RedirectView):
- pattern_name = "admin:index"
-
-
-def variables(request):
- return {"plausible_domain": settings.PLAUSIBLE_DOMAIN}
-
-# class MyClassBasedView(UnfoldModelAdminViewMixin, TemplateView):
-# title = "Custom Title" # required: custom page header title
-# # required: tuple of permissions
-# permission_required = (
-# "formula.view_driver",
-# "formula.add_driver",
-# "formula.change_driver",
-# "formula.delete_driver",
-# )
-# template_name = "formula/driver_custom_page.html"
-
-
-def dashboard_callback(request, context):
- context.update(random_data())
- return context
+ pattern_name = "imam_javad_admin:index"
+# ---------------------------------------------------------
+# 4. Dummy Data for Dashboard Charts
+# ---------------------------------------------------------
@lru_cache
def random_data():
- WEEKDAYS = [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- ]
-
+ 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]
@@ -106,153 +145,18 @@ def random_data():
{"title": _("Analytics"), "link": "#"},
{"title": _("Settings"), "link": "#"},
],
- "filters": [
- {"title": _("All"), "link": "#", "active": True},
- {
- "title": _("New"),
- "link": "#",
- },
- ],
"kpi": [
{
- "title": "Product A Performance",
+ "title": "Total Revenue",
"metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "footer": mark_safe(
- f'
+{intcomma(f"{random.uniform(1, 9):.02f}")}% progress from last week'
- ),
- "chart": json.dumps(
- {
- "labels": [WEEKDAYS[day % 7] for day in range(1, 28)],
- "datasets": [{"data": average, "borderColor": "#9333ea"}],
- }
- ),
- },
- {
- "title": "Product B Performance",
- "metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "footer": mark_safe(
- f'
+{intcomma(f"{random.uniform(1, 9):.02f}")}% progress from last week'
- ),
- },
- {
- "title": "Product C Performance",
- "metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "footer": mark_safe(
- f'
+{intcomma(f"{random.uniform(1, 9):.02f}")}% progress from last week'
- ),
- },
- ],
- "progress": [
- {
- "title": "🦆 Social marketing e-book",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🦍 Freelancing tasks",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🐋 Development coaching",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🦑 Product consulting",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🐨 Other income",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🐶 Course sales",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🐻❄️ Ads revenue",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🦩 Customer Retention Rate",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🦊 Marketing ROI",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- {
- "title": "🦁 Affiliate partnerships",
- "description": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
- "value": random.randint(10, 90),
- },
- ],
- "chart": json.dumps(
- {
- "labels": [WEEKDAYS[day % 7] for day in range(1, 28)],
- "datasets": [
- {
- "label": "Example 1",
- "type": "line",
- "data": average,
- "borderColor": "var(--color-primary-500)",
- },
- {
- "label": "Example 2",
- "data": positive,
- "backgroundColor": "var(--color-primary-700)",
- },
- {
- "label": "Example 3",
- "data": negative,
- "backgroundColor": "var(--color-primary-300)",
- },
- ],
- }
- ),
- "performance": [
- {
- "title": _("Last week revenue"),
- "metric": "$1,234.56",
- "footer": mark_safe(
- '
+3.14% progress from last week'
- ),
- "chart": json.dumps(
- {
- "labels": [WEEKDAYS[day % 7] for day in range(1, 28)],
- "datasets": [
- {
- "data": performance_positive,
- "borderColor": "var(--color-primary-700)",
- }
- ],
- }
- ),
- },
- {
- "title": _("Last week expenses"),
- "metric": "$1,234.56",
- "footer": mark_safe(
- '
+3.14% progress from last week'
- ),
- "chart": json.dumps(
- {
- "labels": [WEEKDAYS[day % 7] for day in range(1, 28)],
- "datasets": [
- {
- "data": performance_negative,
- "borderColor": "var(--color-primary-300)",
- }
- ],
- }
- ),
+ "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)"},
+ ],
+ }),
+ }
\ No newline at end of file