diff --git a/apps/account/admin/student.py b/apps/account/admin/student.py index abb857c..a7cc389 100644 --- a/apps/account/admin/student.py +++ b/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): diff --git a/apps/account/admin/user.py b/apps/account/admin/user.py index f97ee11..5db816b 100644 --- a/apps/account/admin/user.py +++ b/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) diff --git a/apps/account/models/user.py b/apps/account/models/user.py index cdd123e..4c73d6d 100644 --- a/apps/account/models/user.py +++ b/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() diff --git a/apps/course/admin/course.py b/apps/course/admin/course.py index fbdb5a1..81ed23e 100644 --- a/apps/course/admin/course.py +++ b/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) diff --git a/apps/course/admin/lesson.py b/apps/course/admin/lesson.py index e367bb1..032aa19 100644 --- a/apps/course/admin/lesson.py +++ b/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 قبلاً ثبت شده است \ No newline at end of file +project_admin_site.register(LessonCompletion, LessonCompletionAdmin) \ No newline at end of file diff --git a/apps/course/admin/participant.py b/apps/course/admin/participant.py index 7f96249..e69de29 100644 --- a/apps/course/admin/participant.py +++ b/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 - \ No newline at end of file diff --git a/apps/course/migrations/0006_participant_is_active.py b/apps/course/migrations/0006_participant_is_active.py new file mode 100644 index 0000000..6c83fd3 --- /dev/null +++ b/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), + ), + ] diff --git a/apps/course/models/participant.py b/apps/course/models/participant.py index 2c2abd8..03c8731 100644 --- a/apps/course/models/participant.py +++ b/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) diff --git a/apps/quiz/serializers/quiz.py b/apps/quiz/serializers/quiz.py index 3fdc8e9..99e2aeb 100644 --- a/apps/quiz/serializers/quiz.py +++ b/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