From 4ffa8f149c95ba6695eb21bf974f57f55dcc3f71 Mon Sep 17 00:00:00 2001 From: alireza Date: Sat, 14 Dec 2024 08:44:56 +0330 Subject: [PATCH] feat: app certificate --- apps/certificate/__init__.py | 0 apps/certificate/admin.py | 13 ++++++++ apps/certificate/apps.py | 6 ++++ apps/certificate/migrations/0001_initial.py | 31 +++++++++++++++++ apps/certificate/migrations/__init__.py | 0 apps/certificate/models.py | 30 +++++++++++++++++ apps/certificate/serializers.py | 37 +++++++++++++++++++++ apps/certificate/tests.py | 3 ++ apps/certificate/urls.py | 11 ++++++ apps/certificate/views.py | 24 +++++++++++++ config/settings/base.py | 1 + config/urls.py | 2 ++ 12 files changed, 158 insertions(+) create mode 100644 apps/certificate/__init__.py create mode 100644 apps/certificate/admin.py create mode 100644 apps/certificate/apps.py create mode 100644 apps/certificate/migrations/0001_initial.py create mode 100644 apps/certificate/migrations/__init__.py create mode 100644 apps/certificate/models.py create mode 100644 apps/certificate/serializers.py create mode 100644 apps/certificate/tests.py create mode 100644 apps/certificate/urls.py create mode 100644 apps/certificate/views.py diff --git a/apps/certificate/__init__.py b/apps/certificate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/certificate/admin.py b/apps/certificate/admin.py new file mode 100644 index 0000000..8183b52 --- /dev/null +++ b/apps/certificate/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from apps.certificate.models import Certificate + + + + +@admin.register(Certificate) +class CertificateAdmin(admin.ModelAdmin): + list_display = ['student', 'course', 'status', 'created_at'] + list_filter = ['status', 'created_at'] + search_fields = ['user__username', 'course__title'] + readonly_fields = ['created_at', 'updated_at'] diff --git a/apps/certificate/apps.py b/apps/certificate/apps.py new file mode 100644 index 0000000..6a8b903 --- /dev/null +++ b/apps/certificate/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CertificateConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.certificate' diff --git a/apps/certificate/migrations/0001_initial.py b/apps/certificate/migrations/0001_initial.py new file mode 100644 index 0000000..8ef3e6c --- /dev/null +++ b/apps/certificate/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.7 on 2024-12-14 08:35 + +from django.db import migrations, models +import django.db.models.deletion +import filer.fields.file + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('course', '0005_participant_unread_messages_count'), + ('account', '0005_user_city'), + ('filer', '0015_auto_20241214_0835'), + ] + + operations = [ + migrations.CreateModel( + name='Certificate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('pending', 'در حال بررسی'), ('approved', 'تایید شده'), ('canceled', 'لغو شده')], default='pending', max_length=10)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('certificate_file', filer.fields.file.FilerFileField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='filer.file', verbose_name='certificate_file')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_certificates', to='course.course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='certificates', to='account.studentuser')), + ], + ), + ] diff --git a/apps/certificate/migrations/__init__.py b/apps/certificate/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/certificate/models.py b/apps/certificate/models.py new file mode 100644 index 0000000..87ad053 --- /dev/null +++ b/apps/certificate/models.py @@ -0,0 +1,30 @@ +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.account.models import StudentUser + + + +class Certificate(models.Model): + STATUS_CHOICES = [ + ('pending', _('pending')), + ('approved', _('approved')), + ('canceled', _('canceled')), + ] + + student = models.ForeignKey(StudentUser, on_delete=models.CASCADE, related_name='certificates') + course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_certificates') + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending') + certificate_file = FilerFileField( + related_name='+', on_delete=models.PROTECT, null=True, blank=True, verbose_name=_('certificate_file') + ) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"Certificate {self.student.fullname} - {self.course.title}" + + \ No newline at end of file diff --git a/apps/certificate/serializers.py b/apps/certificate/serializers.py new file mode 100644 index 0000000..7801a69 --- /dev/null +++ b/apps/certificate/serializers.py @@ -0,0 +1,37 @@ + + +from rest_framework import serializers +from apps.certificate.models import Certificate + + + +class CertificateSerializer(serializers.ModelSerializer): + course = serializers.SerializerMethodField() + + class Meta: + model = Certificate + fields = ['id', 'student', 'course', 'status', 'created_at', 'updated_at',] + read_only_fields = ['id', 'student', 'status', 'created_at', 'updated_at',] + + def get_course(self, obj): + return obj.course.title + + + + +class CertificateRequestSerializer(serializers.ModelSerializer): + class Meta: + model = Certificate + fields = ['id', 'course'] + read_only_fields = ['id'] + + def create(self, validated_data): + user = self.context['request'].user + course = validated_data['course'] + + if Certificate.objects.filter(student=user, course=course, status__in=['pending', 'approved']).exists(): + raise serializers.ValidationError({"course": "Passwords do not match."}) + return Certificate.objects.create(student=user, course=course) + + + diff --git a/apps/certificate/tests.py b/apps/certificate/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/certificate/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/certificate/urls.py b/apps/certificate/urls.py new file mode 100644 index 0000000..9b80b74 --- /dev/null +++ b/apps/certificate/urls.py @@ -0,0 +1,11 @@ + + +from django.urls import path +from .views import CertificateRequestView, UserCertificatesListView + + + +urlpatterns = [ + path('request/', CertificateRequestView.as_view(), name='certificate-request'), + path('my-certificates/', UserCertificatesListView.as_view(), name='user-certificates'), +] \ No newline at end of file diff --git a/apps/certificate/views.py b/apps/certificate/views.py new file mode 100644 index 0000000..4e12f21 --- /dev/null +++ b/apps/certificate/views.py @@ -0,0 +1,24 @@ +from rest_framework import generics, permissions + +from apps.certificate.models import Certificate +from apps.certificate.serializers import CertificateRequestSerializer, CertificateSerializer + + + +class CertificateRequestView(generics.CreateAPIView): + queryset = Certificate.objects.all() + serializer_class = CertificateRequestSerializer + permission_classes = [permissions.IsAuthenticated] + + def perform_create(self, serializer): + serializer.save(student=self.request.user) + + + +class UserCertificatesListView(generics.ListAPIView): + serializer_class = CertificateSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Certificate.objects.filter(student=self.request.user).order_by('-created_at') + \ No newline at end of file diff --git a/config/settings/base.py b/config/settings/base.py index 3df04fa..4852f52 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -46,6 +46,7 @@ LOCAL_APPS = [ 'apps.chat.apps.ChatConfig', 'apps.quiz.apps.QuizConfig', 'apps.transaction.apps.TransactionConfig', + 'apps.certificate.apps.CertificateConfig', 'dynamic_preferences', ] diff --git a/config/urls.py b/config/urls.py index 3a10fba..759e195 100644 --- a/config/urls.py +++ b/config/urls.py @@ -38,6 +38,8 @@ api_patterns = [ path('courses/', include('apps.course.urls')), path('quiz/', include('apps.quiz.urls')), path('transaction/', include('apps.transaction.urls')), + path('certificates/', include('apps.certificate.urls')), + path('settings/', include('dynamic_preferences.urls')), path('upload-tmp-media/', UploadTmpMedia.as_view()),