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 rest_framework.authtoken.models import TokenProxy
from ajaxdatatable.admin import AjaxDatatable
from unfold.admin import TabularInline, StackedInline
from django.contrib import admin
from apps.account.models import User
@ -15,12 +16,10 @@ from django.contrib import messages
from apps.account.models import StudentUser, User
@admin.register(StudentUser)
class StudentUserAdmin(UserAdmin, AjaxDatatable):
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',
readonly_fields = ('date_joined',)
@ -52,12 +51,18 @@ class StudentUserAdmin(UserAdmin, AjaxDatatable):
@admin.display(description='Phone Number')
def _phone_number(self, obj):
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):
# محدود کردن نمایش فقط دانش‌آموزان
# محدود کردن نمایش فقط دانش‌آموزان و بهینه‌سازی query
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):

40
apps/account/admin/user.py

@ -15,6 +15,7 @@ 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
@ -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):
list_display = (
@ -194,6 +233,7 @@ class StudentUserAdmin(UserAdmin):
}),
)
# Ensure the fieldsets from UserAdmin are used, which now include the auth token
inlines = [StudentParticipantInline, LocationHistoryInline]
@display(description=_("Student"), header=True)

2
apps/account/models/user.py

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

7
apps/course/admin/course.py

@ -526,6 +526,13 @@ 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
project_admin_site.register(Course, CourseAdmin)
project_admin_site.register(CourseCategory, CourseCategoryAdmin)

8
apps/course/admin/lesson.py

@ -134,9 +134,11 @@ class LessonCompletionAdmin(ModelAdmin):
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
project_admin_site.register(Lesson, LessonAdmin)
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,
related_name='participants'
)
is_active = models.BooleanField(default=True)
joined_date = models.DateTimeField(auto_now_add=True)
unread_messages_count = models.IntegerField(default=0)

2
apps/quiz/serializers/quiz.py

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