Browse Source

certificate and transactions based on course new schema

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

40
apps/certificate/admin.py

@ -1,4 +1,5 @@
from django.contrib import admin from django.contrib import admin
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.html import format_html from django.utils.html import format_html
@ -6,15 +7,40 @@ from unfold.admin import ModelAdmin
from unfold.decorators import display from unfold.decorators import display
from apps.certificate.models import Certificate 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 utils.admin import project_admin_site
from apps.course.admin.professor_base import CertificateBaseAdmin 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) @admin.register(Certificate)
class CertificateAdmin(CertificateBaseAdmin): class CertificateAdmin(CertificateBaseAdmin):
list_display = ['student', 'course', 'certificate_status', 'created_at'] list_display = ['student', 'course', 'certificate_status', 'created_at']
list_filter = ['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'] readonly_fields = ['created_at', 'updated_at']
autocomplete_fields = ['student',] autocomplete_fields = ['student',]
fieldsets = ( fieldsets = (
@ -42,4 +68,14 @@ class CertificateAdmin(CertificateBaseAdmin):
def get_queryset(self, request): def get_queryset(self, request):
queryset = super().get_queryset(request) queryset = super().get_queryset(request)
return queryset return queryset
project_admin_site.register(Certificate, CertificateAdmin)
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)

6
apps/certificate/models.py

@ -3,6 +3,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from filer.fields.file import FilerFileField from filer.fields.file import FilerFileField
from apps.course.models import Course from apps.course.models import Course
from apps.course.models.course import extract_text_from_json
from apps.account.models import StudentUser from apps.account.models import StudentUser
@ -23,6 +24,7 @@ class Certificate(models.Model):
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
def __str__(self): 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}"

31
apps/certificate/views.py

@ -2,12 +2,38 @@ from rest_framework import generics, permissions
from rest_framework.authentication import TokenAuthentication from rest_framework.authentication import TokenAuthentication
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi from drf_yasg import openapi
from django.db.models import Q
from apps.certificate.models import Certificate from apps.certificate.models import Certificate
from apps.certificate.serializers import CertificateRequestSerializer, CertificateSerializer 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 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): class CertificateRequestView(generics.CreateAPIView):
queryset = Certificate.objects.all() queryset = Certificate.objects.all()
@ -82,10 +108,11 @@ class AdminCertificateViewSet(ModelViewSet):
# Search query # Search query
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
if search_query: if search_query:
matching_course_ids = find_matching_course_ids(search_query)
queryset = queryset.filter( queryset = queryset.filter(
Q(student__fullname__icontains=search_query) | Q(student__fullname__icontains=search_query) |
Q(student__email__icontains=search_query) | Q(student__email__icontains=search_query) |
Q(course__title__icontains=search_query)
Q(course_id__in=matching_course_ids)
) )
# Filters # Filters
@ -115,4 +142,4 @@ class AdminCertificateViewSet(ModelViewSet):
return queryset.order_by('-created_at') return queryset.order_by('-created_at')

57
apps/quiz/admin/quiz.py

@ -1,4 +1,5 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from django.db.models import Q
from django.db.models import Count from django.db.models import Count
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.urls import reverse from django.urls import reverse
@ -9,6 +10,8 @@ from unfold.admin import ModelAdmin
from unfold.decorators import display, action from unfold.decorators import display, action
from apps.course.models import CourseLesson 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.models import Quiz
from apps.quiz.admin.question import QuestionAdminInline from apps.quiz.admin.question import QuestionAdminInline
from apps.quiz.admin.participant import QuizParticipantInline from apps.quiz.admin.participant import QuizParticipantInline
@ -16,7 +19,7 @@ from utils.admin import project_admin_site, admin_url_generator
class QuizAdmin(ModelAdmin): 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_display = ['title', 'description', 'lesson', 'each_question_timing', 'status_display', 'questions_display']
list_filter = ['each_question_timing', 'status'] list_filter = ['each_question_timing', 'status']
inlines = [QuestionAdminInline, QuizParticipantInline] inlines = [QuestionAdminInline, QuizParticipantInline]
@ -41,6 +44,43 @@ class QuizAdmin(ModelAdmin):
return queryset.filter(lesson__course__professor=request.user) return queryset.filter(lesson__course__professor=request.user)
return queryset.none() 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): def get_exclude(self, request, obj=None):
if not obj: # اگر obj وجود ندارد یعنی صفحه Add است if not obj: # اگر obj وجود ندارد یعنی صفحه Add است
@ -72,6 +112,19 @@ class QuizAdmin(ModelAdmin):
form.base_fields['lesson'].widget.can_add_related = False form.base_fields['lesson'].widget.can_add_related = False
return form 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') @display(description=_('Status'), ordering='status')
def status_display(self, obj): def status_display(self, obj):
@ -119,4 +172,4 @@ class QuizAdmin(ModelAdmin):
messages.error(request, _("Course not found for this quiz.")) messages.error(request, _("Course not found for this quiz."))
return redirect(request.META.get('HTTP_REFERER', '/')) return redirect(request.META.get('HTTP_REFERER', '/'))
project_admin_site.register(Quiz, QuizAdmin)
project_admin_site.register(Quiz, QuizAdmin)

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.quiz.models import Quiz, Question, QuizParticipant
from apps.course.models import Course, CourseLesson, Participant 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.models import User
from apps.account.permissions import IsPanelUser from apps.account.permissions import IsPanelUser
@ -28,6 +29,45 @@ def is_professor(request):
return getattr(request.user, 'user_type', None) == 'professor' 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): class AdminQuizViewSet(ModelViewSet):
""" """
Admin CRUD ViewSet for Quiz management. Admin CRUD ViewSet for Quiz management.
@ -52,9 +92,13 @@ class AdminQuizViewSet(ModelViewSet):
# Handle Search # Handle Search
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
if search_query: if search_query:
matching_course_ids = find_matching_course_ids(search_query)
matching_lesson_ids = find_matching_lesson_ids(search_query)
queryset = queryset.filter( queryset = queryset.filter(
Q(title__icontains=search_query) | 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 # 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.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.authentication import TokenAuthentication from rest_framework.authentication import TokenAuthentication
from django.db.models import Q
from apps.course.models import Participant, Course 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.models import TransactionParticipant, TransactionReceipt
from apps.transaction.serializers import ( from apps.transaction.serializers import (
TransactionParticipantSerializer, TransactionParticipantSerializer,
@ -26,6 +28,32 @@ from apps.transaction.doc import (
from utils.ip_helper import get_client_ip, get_country_code 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): class TransactionParticipantCreateView(generics.CreateAPIView):
queryset = TransactionParticipant.objects.all() queryset = TransactionParticipant.objects.all()
@ -118,9 +146,8 @@ class TransactionParticipantCreateView(generics.CreateAPIView):
course_slug = self.kwargs.get('slug') course_slug = self.kwargs.get('slug')
# 1. Retrieve Course # 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"}) raise AppAPIException({'message': "Course not found"})
participant_infos = request.data.get('participant_infos', []) participant_infos = request.data.get('participant_infos', [])
@ -450,10 +477,25 @@ class AdminTransactionViewSet(ModelViewSet):
# Search # Search
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
if search_query: 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( queryset = queryset.filter(
Q(user__fullname__icontains=search_query) | Q(user__fullname__icontains=search_query) |
Q(user__email__icontains=search_query) | Q(user__email__icontains=search_query) |
Q(course__title__icontains=search_query)
Q(course_id__in=matching_course_ids)
) )
# Status filter # Status filter

Loading…
Cancel
Save