Browse Source

Admin panels are separated now!

master
Mohsen Taba 5 months ago
parent
commit
b2ac5f73f1
  1. 5
      apps/account/admin/location.py
  2. 299
      apps/account/admin/user.py
  3. 12
      apps/api/admin.py
  4. 14
      apps/article/admin.py
  5. 50
      apps/course/admin/course.py
  6. 4
      apps/dobodbi_calendar/admin.py
  7. 6
      apps/hadis/admin/category.py
  8. 20
      apps/hadis/admin/hadis.py
  9. 10
      apps/hadis/admin/reference.py
  10. 16
      apps/hadis/admin/transmitter.py
  11. 4
      apps/hadis/admin/version.py
  12. 12
      apps/library/admin.py
  13. 14
      apps/podcast/admin.py
  14. 12
      apps/video/admin.py
  15. 160
      config/settings/base.py
  16. 5
      config/urls.py
  17. 310
      utils/admin.py

5
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
# Register with both admin sites
project_admin_site.register(LocationHistory, LocationHistoryAdmin)
dovoodi_admin_site.register(LocationHistory, LocationHistoryAdmin)

299
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"),
{
(_("Basic Information"), {
"fields": ("gender", "avatar", "phone_number", "birthdate", 'info', 'skill', "password"),
"classes": ["tab"],
},
),
(
_('Country & City'), {
}),
(_('Country & City'), {
'fields': ('city', 'country'),
"classes": ["tab"],
}
),
(
_('Device Information'), {
'fields': ('device_id', 'device_os', 'fcm', 'language', ),
}),
(_('Device Information'), {
'fields': ('device_id', 'device_os', 'fcm', 'language',),
"classes": ["tab"],
}
),
(
_('Authentication'), {
}),
(_('Authentication'), {
'fields': ('display_auth_token',),
"classes": ["tab"],
}
),
(
_('Permissions'), {
}),
(_('Permissions'), {
'fields': ('user_type', 'is_active', 'is_staff', 'groups'),
"classes": ["tab"],
}
),
(
_('Important dates'), {
}),
(_('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('<code style="word-break: break-all;">{}</code>', 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,17 +193,11 @@ 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,
@ -252,40 +207,26 @@ class StudentUserAdmin(UserAdmin):
"height": 30,
"width": 36,
"borderless": True,
# "squared": 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(
'<span title="{}">{}</span>',
f"Born on {formatted_date}",
age
)
return format_html('<span title="{}">{}</span>', 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(
"""
<div class="flex flex-row gap-2 items-center">
@ -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,87 +251,36 @@ class StudentUserAdmin(UserAdmin):
}
def get_queryset(self, request):
"""
Optimize queries by prefetching related courses
"""
return (
super().get_queryset(request)
.prefetch_related(
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(
'<a href="/admin/course/course/{}/change/" class="leading-none">'
'<span class="material-symbols-outlined leading-none text-base-500">visibility</span>'
'</a>',
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,
@ -411,11 +296,8 @@ class ProfessorUserAdmin(UserAdmin):
@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")
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 "-"
# Register the ProfessorUserAdmin with the project admin site
project_admin_site.register(ProfessorUser, ProfessorUserAdmin)
# =========================================================
# 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
admin.site.unregister(TokenProxy)
# 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

12
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)

14
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)

50
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)

4
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)

6
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)
dovoodi_admin_site.register(HadisSect, HadisSectAdmin)
dovoodi_admin_site.register(HadisCategory, HadisCategoryAdmin)

20
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)
# 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)

10
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)
dovoodi_admin_site.register(BookReference, BookReferenceAdmin)
dovoodi_admin_site.register(BookAuthor, BookAuthorAdmin)
dovoodi_admin_site.register(BookAttribute, BookAttributeAdmin)
dovoodi_admin_site.register(BookReferenceImage, BookReferenceImageAdmin)

16
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)
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)

4
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)

12
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)

14
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)

12
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)

160
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
],

5
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')),

310
utils/admin.py

@ -1,42 +1,65 @@
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
# Unfold Imports
from unfold.sites import UnfoldAdminSite
# 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
# ---------------------------------------------------------
# 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):
class CustomLoginForm(AuthenticationForm):
password = forms.CharField(widget=forms.PasswordInput(render_value=True))
def __init__(self, request=None, *args, **kwargs):
@ -44,56 +67,72 @@ class LoginForm(AuthenticationForm):
# 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",
"metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
"footer": mark_safe(
f'<strong class="text-green-700 font-semibold dark:text-green-400">+{intcomma(f"{random.uniform(1, 9):.02f}")}%</strong>&nbsp;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",
"title": "Total Revenue",
"metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
"footer": mark_safe(
f'<strong class="text-green-700 font-semibold dark:text-green-400">+{intcomma(f"{random.uniform(1, 9):.02f}")}%</strong>&nbsp;progress from last week'
),
},
{
"title": "Product C Performance",
"metric": f"${intcomma(f'{random.uniform(1000, 9999):.02f}')}",
"footer": mark_safe(
f'<strong class="text-green-700 font-semibold dark:text-green-400">+{intcomma(f"{random.uniform(1, 9):.02f}")}%</strong>&nbsp;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),
"footer": mark_safe(f'<strong class="text-green-700 font-semibold dark:text-green-400">+{intcomma(f"{random.uniform(1, 9):.02f}")}%</strong>&nbsp;progress'),
"chart": json.dumps({"labels": [WEEKDAYS[day % 7] for day in range(1, 28)], "datasets": [{"data": average, "borderColor": "#9333ea"}]}),
},
],
"chart": json.dumps(
{
"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(
'<strong class="text-green-600 font-medium">+3.14%</strong>&nbsp;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(
'<strong class="text-green-600 font-medium">+3.14%</strong>&nbsp;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)",
}
],
}
),
},
{"label": "Revenue", "data": positive, "backgroundColor": "var(--color-primary-700)"},
],
}),
}
Loading…
Cancel
Save