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