Browse Source

feat(account, course): add enrolled courses count & improve admin

- Added enrolled courses count to StudentUser admin
- Improved StudentUser admin queryset
- Added StudentParticipantInline to StudentUser admin
- Added is_active field to Participant model
- Improved Course admin registration
master
mortezaei 10 months ago
parent
commit
7911bd0a9d
  1. 15
      apps/account/admin/student.py
  2. 40
      apps/account/admin/user.py
  3. 2
      apps/account/models/user.py
  4. 7
      apps/course/admin/course.py
  5. 8
      apps/course/admin/lesson.py
  6. 33
      apps/course/admin/participant.py
  7. 18
      apps/course/migrations/0006_participant_is_active.py
  8. 1
      apps/course/models/participant.py
  9. 2
      apps/quiz/serializers/quiz.py

15
apps/account/admin/student.py

@ -4,6 +4,7 @@ from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import TokenProxy from rest_framework.authtoken.models import TokenProxy
from ajaxdatatable.admin import AjaxDatatable from ajaxdatatable.admin import AjaxDatatable
from unfold.admin import TabularInline, StackedInline
from django.contrib import admin from django.contrib import admin
from apps.account.models import User from apps.account.models import User
@ -15,12 +16,10 @@ from django.contrib import messages
from apps.account.models import StudentUser, User from apps.account.models import StudentUser, User
@admin.register(StudentUser) @admin.register(StudentUser)
class StudentUserAdmin(UserAdmin, AjaxDatatable): class StudentUserAdmin(UserAdmin, AjaxDatatable):
list_display = ( list_display = (
'device_id', 'email', 'fullname', 'user_type','last_login', 'date_joined',
'device_id', 'email', 'fullname', 'user_type', 'enrolled_courses_count', 'last_login', 'date_joined',
) )
ordering = 'last_login', ordering = 'last_login',
readonly_fields = ('date_joined',) readonly_fields = ('date_joined',)
@ -52,12 +51,18 @@ class StudentUserAdmin(UserAdmin, AjaxDatatable):
@admin.display(description='Phone Number') @admin.display(description='Phone Number')
def _phone_number(self, obj): def _phone_number(self, obj):
return obj.phone_number return obj.phone_number
@admin.display(description=_('Enrolled Courses'))
def enrolled_courses_count(self, obj):
"""نمایش تعداد دوره‌های شرکت کرده"""
count = obj.participated_courses.filter(is_active=True).count()
return f"{count} دوره"
def get_queryset(self, request): def get_queryset(self, request):
# محدود کردن نمایش فقط دانش‌آموزان
# محدود کردن نمایش فقط دانش‌آموزان و بهینه‌سازی query
qs = super().get_queryset(request) qs = super().get_queryset(request)
return qs.filter(user_type=User.UserType.STUDENT)
return qs.filter(user_type=User.UserType.STUDENT).prefetch_related('participated_courses')
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):

40
apps/account/admin/user.py

@ -15,6 +15,7 @@ from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from apps.account.models import ClientUser, AdminUser, StudentUser, ProfessorUser from apps.account.models import ClientUser, AdminUser, StudentUser, ProfessorUser
from apps.course.models import Participant
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from utils.admin import project_admin_site from utils.admin import project_admin_site
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
@ -174,6 +175,44 @@ class GuestUserAdmin(UserAdmin):
class StudentParticipantInline(StackedInline):
"""نمایش دوره‌های شرکت کرده دانش‌آموز"""
model = Participant
extra = 0
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
tab = True
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('course', 'course__professor')
@admin.display(description=_('Course Status'))
def course_status(self, obj):
if obj.course:
return obj.course.get_status_display()
return '-'
@admin.display(description=_('Professor'))
def course_professor(self, obj):
if obj.course and obj.course.professor:
return obj.course.professor.fullname or obj.course.professor.email
return '-'
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): class StudentUserAdmin(UserAdmin):
list_display = ( list_display = (
@ -194,6 +233,7 @@ class StudentUserAdmin(UserAdmin):
}), }),
) )
# Ensure the fieldsets from UserAdmin are used, which now include the auth token # Ensure the fieldsets from UserAdmin are used, which now include the auth token
inlines = [StudentParticipantInline, LocationHistoryInline]
@display(description=_("Student"), header=True) @display(description=_("Student"), header=True)

2
apps/account/models/user.py

@ -69,7 +69,7 @@ class User(AbstractUser):
def __str__(self): def __str__(self):
username = self.email or self.fullname or self.device_id username = self.email or self.fullname or self.device_id
return f"{username}-({self.user_type})"
return f"{username}"
def soft_delete(self): def soft_delete(self):
self.deleted_at = timezone.now() self.deleted_at = timezone.now()

7
apps/course/admin/course.py

@ -526,6 +526,13 @@ class CourseAttachmentAdmin(CourseRelatedAdmin):
return obj.attachment.file_size 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 # Register with the project admin site
project_admin_site.register(Course, CourseAdmin) project_admin_site.register(Course, CourseAdmin)
project_admin_site.register(CourseCategory, CourseCategoryAdmin) project_admin_site.register(CourseCategory, CourseCategoryAdmin)

8
apps/course/admin/lesson.py

@ -134,9 +134,11 @@ class LessonCompletionAdmin(ModelAdmin):
return [] return []
# Register with both admin sites for autocomplete support
from django.contrib import admin as django_admin
django_admin.site.register(Lesson, LessonAdmin)
# Register with the project admin site # Register with the project admin site
project_admin_site.register(Lesson, LessonAdmin) project_admin_site.register(Lesson, LessonAdmin)
project_admin_site.register(CourseLesson, CourseLessonAdmin) project_admin_site.register(CourseLesson, CourseLessonAdmin)
project_admin_site.register(LessonCompletion, LessonCompletionAdmin)
# Lesson قبلاً ثبت شده است
project_admin_site.register(LessonCompletion, LessonCompletionAdmin)

33
apps/course/admin/participant.py

@ -1,33 +0,0 @@
# from django.contrib import admin
# from apps.course.models import Participant
# from apps.account.models import StudentUser, User
# @admin.register(Participant)
# class ParticipantAdmin(admin.ModelAdmin):
# list_display = ('student', 'course', 'joined_date', 'unread_messages_count')
# search_fields = ('student__fullname', 'student__email', 'course__title')
# list_filter = ('course', 'joined_date')
# ordering = ('-joined_date',)
# autocomplete_fields = ['student',] # جستجوی پویا برای فیلد دانش‌آموز
# def get_readonly_fields(self, request, obj=None):
# """
# Make fields readonly if the object already exists.
# """
# if obj:
# return ['student', 'course', 'joined_date']
# return []
# def get_form(self, request, obj=None, **kwargs):
# form = super().get_form(request, obj, **kwargs)
# if obj is None: # Adding a new participant
# # محدود کردن انتخاب دانش‌آموزان به کاربرانی که از نوع StudentUser هستند
# # form.base_fields['student'].queryset = StudentUser.objects.filter(user_type=User.UserType.STUDENT)
# form.base_fields['student'].widget.can_add_related = True # فعال کردن دکمه اضافه کردن
# return form

18
apps/course/migrations/0006_participant_is_active.py

@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-08-07 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0005_add_database_indexes'),
]
operations = [
migrations.AddField(
model_name='participant',
name='is_active',
field=models.BooleanField(default=True),
),
]

1
apps/course/models/participant.py

@ -17,6 +17,7 @@ class Participant(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='participants' related_name='participants'
) )
is_active = models.BooleanField(default=True)
joined_date = models.DateTimeField(auto_now_add=True) joined_date = models.DateTimeField(auto_now_add=True)
unread_messages_count = models.IntegerField(default=0) unread_messages_count = models.IntegerField(default=0)

2
apps/quiz/serializers/quiz.py

@ -100,4 +100,4 @@ class QuizSerializer(serializers.ModelSerializer):
return False return False
participated = QuizParticipant.objects.filter(user=user, quiz=obj).exists() participated = QuizParticipant.objects.filter(user=user, quiz=obj).exists()
return not participated
return participated
Loading…
Cancel
Save