Browse Source

certificate and transactions based on course new schema

master
Mohsen Taba 2 months ago
parent
commit
66f44aba3e
  1. 38
      apps/certificate/admin.py
  2. 4
      apps/certificate/models.py
  3. 29
      apps/certificate/views.py
  4. 55
      apps/quiz/admin/quiz.py
  5. 46
      apps/quiz/views/admin.py
  6. 50
      apps/transaction/views.py

38
apps/certificate/admin.py

@ -1,4 +1,5 @@
from django.contrib import admin
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django.utils.html import format_html
@ -6,15 +7,40 @@ from unfold.admin import ModelAdmin
from unfold.decorators import display
from apps.certificate.models import Certificate
from apps.course.models import Course
from apps.course.models.course import extract_text_from_json
from utils.admin import project_admin_site
from apps.course.admin.professor_base import CertificateBaseAdmin
def find_matching_course_ids(search_term: str):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = list(
Course.objects.filter(
Q(title__icontains=search_term) | Q(slug__icontains=search_term)
).values_list("id", flat=True)
)
if matching_ids:
return matching_ids
for course in Course.objects.all().only("id", "title", "slug"):
title_text = extract_text_from_json(course.title).lower()
slug_text = extract_text_from_json(course.slug).lower()
if normalized in title_text or normalized in slug_text:
matching_ids.append(course.id)
return matching_ids
@admin.register(Certificate)
class CertificateAdmin(CertificateBaseAdmin):
list_display = ['student', 'course', 'certificate_status', 'created_at']
list_filter = ['status', 'created_at']
search_fields = ['id', 'student__username', 'student__email', 'course__title']
search_fields = ['id', 'student__username', 'student__email']
readonly_fields = ['created_at', 'updated_at']
autocomplete_fields = ['student',]
fieldsets = (
@ -42,4 +68,14 @@ class CertificateAdmin(CertificateBaseAdmin):
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
matching_course_ids = find_matching_course_ids(search_term)
if matching_course_ids:
queryset = queryset | self.model.objects.filter(course_id__in=matching_course_ids)
use_distinct = True
return queryset, use_distinct
project_admin_site.register(Certificate, CertificateAdmin)

4
apps/certificate/models.py

@ -3,6 +3,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from filer.fields.file import FilerFileField
from apps.course.models import Course
from apps.course.models.course import extract_text_from_json
from apps.account.models import StudentUser
@ -23,6 +24,7 @@ class Certificate(models.Model):
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Certificate {self.student.fullname} - {self.course.title}"
course_title = extract_text_from_json(self.course.title) if self.course_id else ""
return f"Certificate {self.student.fullname} - {course_title}"

29
apps/certificate/views.py

@ -2,12 +2,38 @@ from rest_framework import generics, permissions
from rest_framework.authentication import TokenAuthentication
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.db.models import Q
from apps.certificate.models import Certificate
from apps.certificate.serializers import CertificateRequestSerializer, CertificateSerializer
from apps.course.models import Course
from apps.course.models.course import extract_text_from_json
from utils.pagination import StandardResultsSetPagination
def find_matching_course_ids(search_term: str):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = list(
Course.objects.filter(
Q(title__icontains=search_term) | Q(slug__icontains=search_term)
).values_list("id", flat=True)
)
if matching_ids:
return matching_ids
for course in Course.objects.all().only("id", "title", "slug"):
title_text = extract_text_from_json(course.title).lower()
slug_text = extract_text_from_json(course.slug).lower()
if normalized in title_text or normalized in slug_text:
matching_ids.append(course.id)
return matching_ids
class CertificateRequestView(generics.CreateAPIView):
queryset = Certificate.objects.all()
@ -82,10 +108,11 @@ class AdminCertificateViewSet(ModelViewSet):
# Search query
search_query = self.request.query_params.get('search', None)
if search_query:
matching_course_ids = find_matching_course_ids(search_query)
queryset = queryset.filter(
Q(student__fullname__icontains=search_query) |
Q(student__email__icontains=search_query) |
Q(course__title__icontains=search_query)
Q(course_id__in=matching_course_ids)
)
# Filters

55
apps/quiz/admin/quiz.py

@ -1,4 +1,5 @@
from django.contrib import admin, messages
from django.db.models import Q
from django.db.models import Count
from django.utils.safestring import mark_safe
from django.urls import reverse
@ -9,6 +10,8 @@ from unfold.admin import ModelAdmin
from unfold.decorators import display, action
from apps.course.models import CourseLesson
from apps.course.models import Course
from apps.course.models.course import extract_text_from_json
from apps.quiz.models import Quiz
from apps.quiz.admin.question import QuestionAdminInline
from apps.quiz.admin.participant import QuizParticipantInline
@ -16,7 +19,7 @@ from utils.admin import project_admin_site, admin_url_generator
class QuizAdmin(ModelAdmin):
search_fields = ['title', 'lesson__title']
search_fields = ['title', 'description']
list_display = ['title', 'description', 'lesson', 'each_question_timing', 'status_display', 'questions_display']
list_filter = ['each_question_timing', 'status']
inlines = [QuestionAdminInline, QuizParticipantInline]
@ -42,6 +45,43 @@ class QuizAdmin(ModelAdmin):
return queryset.none()
def _find_matching_course_ids(self, search_term):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = list(
Course.objects.filter(
Q(title__icontains=search_term) | Q(slug__icontains=search_term)
).values_list("id", flat=True)
)
if matching_ids:
return matching_ids
for course in Course.objects.all().only("id", "title", "slug"):
title_text = extract_text_from_json(course.title).lower()
slug_text = extract_text_from_json(course.slug).lower()
if normalized in title_text or normalized in slug_text:
matching_ids.append(course.id)
return matching_ids
def _find_matching_lesson_ids(self, search_term):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = []
for lesson in CourseLesson.objects.select_related("lesson").all():
lesson_title = extract_text_from_json(
lesson.title or (lesson.lesson.title if lesson.lesson else [])
).lower()
if normalized in lesson_title:
matching_ids.append(lesson.id)
return matching_ids
def get_exclude(self, request, obj=None):
if not obj: # اگر obj وجود ندارد یعنی صفحه Add است
return ['course']
@ -73,6 +113,19 @@ class QuizAdmin(ModelAdmin):
return form
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
matching_course_ids = self._find_matching_course_ids(search_term)
matching_lesson_ids = self._find_matching_lesson_ids(search_term)
if matching_course_ids or matching_lesson_ids:
queryset = queryset | self.model.objects.filter(
Q(course_id__in=matching_course_ids) | Q(lesson_id__in=matching_lesson_ids)
)
use_distinct = True
return queryset, use_distinct
@display(description=_('Status'), ordering='status')
def status_display(self, obj):
if obj.status:

46
apps/quiz/views/admin.py

@ -10,6 +10,7 @@ from utils.pagination import StandardResultsSetPagination
from apps.quiz.models import Quiz, Question, QuizParticipant
from apps.course.models import Course, CourseLesson, Participant
from apps.course.models.course import extract_text_from_json
from apps.account.models import User
from apps.account.permissions import IsPanelUser
@ -28,6 +29,45 @@ def is_professor(request):
return getattr(request.user, 'user_type', None) == 'professor'
def find_matching_course_ids(search_term: str):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = list(
Course.objects.filter(
Q(title__icontains=search_term) | Q(slug__icontains=search_term)
).values_list("id", flat=True)
)
if matching_ids:
return matching_ids
for course in Course.objects.all().only("id", "title", "slug"):
title_text = extract_text_from_json(course.title).lower()
slug_text = extract_text_from_json(course.slug).lower()
if normalized in title_text or normalized in slug_text:
matching_ids.append(course.id)
return matching_ids
def find_matching_lesson_ids(search_term: str):
normalized = (search_term or "").strip().lower()
if not normalized:
return []
matching_ids = []
for lesson in CourseLesson.objects.select_related("lesson").all():
lesson_title = extract_text_from_json(
lesson.title or (lesson.lesson.title if lesson.lesson else [])
).lower()
if normalized in lesson_title:
matching_ids.append(lesson.id)
return matching_ids
class AdminQuizViewSet(ModelViewSet):
"""
Admin CRUD ViewSet for Quiz management.
@ -52,9 +92,13 @@ class AdminQuizViewSet(ModelViewSet):
# Handle Search
search_query = self.request.query_params.get('search', None)
if search_query:
matching_course_ids = find_matching_course_ids(search_query)
matching_lesson_ids = find_matching_lesson_ids(search_query)
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(description__icontains=search_query)
Q(description__icontains=search_query) |
Q(course_id__in=matching_course_ids) |
Q(lesson_id__in=matching_lesson_ids)
)
# Handle Course Filter

50
apps/transaction/views.py

@ -3,7 +3,9 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.authentication import TokenAuthentication
from django.db.models import Q
from apps.course.models import Participant, Course
from apps.course.models.course import extract_text_from_json
from apps.transaction.models import TransactionParticipant, TransactionReceipt
from apps.transaction.serializers import (
TransactionParticipantSerializer,
@ -26,6 +28,32 @@ from apps.transaction.doc import (
from utils.ip_helper import get_client_ip, get_country_code
def find_course_by_multilingual_identifier(identifier: str):
normalized = (identifier or "").strip()
if not normalized:
return None
course = Course.objects.filter(slug__contains=[{"title": normalized}]).first()
if course:
return course
course = Course.objects.filter(
Q(title__icontains=normalized) |
Q(slug__icontains=normalized)
).first()
if course:
return course
normalized_lower = normalized.lower()
for item in Course.objects.all().only("id", "title", "slug"):
title_text = extract_text_from_json(item.title).lower()
slug_text = extract_text_from_json(item.slug).lower()
if normalized_lower in title_text or normalized_lower in slug_text:
return item
return None
class TransactionParticipantCreateView(generics.CreateAPIView):
queryset = TransactionParticipant.objects.all()
@ -118,9 +146,8 @@ class TransactionParticipantCreateView(generics.CreateAPIView):
course_slug = self.kwargs.get('slug')
# 1. Retrieve Course
try:
course = Course.objects.get(slug=course_slug)
except Course.DoesNotExist:
course = find_course_by_multilingual_identifier(course_slug)
if not course:
raise AppAPIException({'message': "Course not found"})
participant_infos = request.data.get('participant_infos', [])
@ -450,10 +477,25 @@ class AdminTransactionViewSet(ModelViewSet):
# Search
search_query = self.request.query_params.get('search', None)
if search_query:
matching_course_ids = list(
Course.objects.filter(
Q(title__icontains=search_query) |
Q(slug__icontains=search_query)
).values_list("id", flat=True)
)
if not matching_course_ids:
normalized_search = search_query.lower().strip()
matching_course_ids = [
course.id
for course in Course.objects.all().only("id", "title", "slug")
if normalized_search in extract_text_from_json(course.title).lower()
or normalized_search in extract_text_from_json(course.slug).lower()
]
queryset = queryset.filter(
Q(user__fullname__icontains=search_query) |
Q(user__email__icontains=search_query) |
Q(course__title__icontains=search_query)
Q(course_id__in=matching_course_ids)
)
# Status filter

Loading…
Cancel
Save