27 changed files with 635 additions and 28 deletions
-
26apps/account/manager.py
-
18apps/account/migrations/0005_user_city.py
-
1apps/account/models/user.py
-
2apps/account/serializers/user.py
-
2apps/chat/admin.py
-
2apps/course/admin/participant.py
-
5apps/course/models/participant.py
-
3apps/course/serializers/__init__.py
-
17apps/course/serializers/participant.py
-
1apps/course/urls.py
-
42apps/course/views/participant.py
-
0apps/transaction/__init__.py
-
37apps/transaction/admin.py
-
6apps/transaction/apps.py
-
44apps/transaction/migrations/0001_initial.py
-
0apps/transaction/migrations/__init__.py
-
50apps/transaction/models.py
-
44apps/transaction/serializers.py
-
3apps/transaction/tests.py
-
13apps/transaction/urls.py
-
77apps/transaction/views.py
-
3config/settings/base.py
-
3config/urls.py
-
166dynamic_preferences/dynamic_preferences_registry.py
-
17dynamic_preferences/serializers.py
-
20dynamic_preferences/urls.py
-
61dynamic_preferences/views.py
@ -0,0 +1,18 @@ |
|||
# Generated by Django 3.2.4 on 2024-11-30 23:53 |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('account', '0004_user_skill'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.AddField( |
|||
model_name='user', |
|||
name='city', |
|||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='City'), |
|||
), |
|||
] |
|||
@ -1,2 +1,3 @@ |
|||
from .course import * |
|||
from .lesson import * |
|||
from .lesson import * |
|||
from .participant import * |
|||
@ -0,0 +1,17 @@ |
|||
from rest_framework import serializers |
|||
|
|||
|
|||
from apps.course.models import Lesson, Participant, LessonCompletion |
|||
from apps.account.models import StudentUser, User |
|||
|
|||
|
|||
|
|||
|
|||
class ParticipantSerializer(serializers.ModelSerializer): |
|||
email = serializers.EmailField(required=True) |
|||
gender = serializers.ChoiceField(choices=User.GenderChoices.choices, required=True) |
|||
|
|||
|
|||
class Meta: |
|||
model = StudentUser |
|||
fields = ['fullname' , 'phone_number', 'gender', 'email', 'birthdate'] |
|||
@ -0,0 +1,37 @@ |
|||
from django.contrib import admin |
|||
|
|||
from apps.transaction.models import TransactionParticipant, ParticipantInfo |
|||
from django.utils.translation import gettext_lazy as _ |
|||
|
|||
|
|||
|
|||
class ParticipantInfoInline(admin.StackedInline): |
|||
model = ParticipantInfo |
|||
extra = 1 |
|||
fields = ['fullname', 'email', 'phone_number', 'gender', 'birthdate'] |
|||
# readonly_fields = ['email', 'phone_number'] |
|||
classes = ['collapse'] |
|||
|
|||
|
|||
|
|||
|
|||
@admin.register(TransactionParticipant) |
|||
class TransactionParticipantAdmin(admin.ModelAdmin): |
|||
list_display = ('user', 'course', 'is_paid', 'price', 'created_at', 'updated_at') |
|||
list_filter = ('is_paid', 'course', 'created_at') |
|||
search_fields = ('user__email', 'course__title') |
|||
readonly_fields = ['user', 'course', 'price', 'created_at', 'updated_at'] |
|||
inlines = [ParticipantInfoInline] |
|||
ordering = ('-created_at',) |
|||
list_filter = ('is_paid', 'course', 'created_at') |
|||
|
|||
fieldsets = ( |
|||
(None, { |
|||
'fields': ('user', 'course', 'is_paid', 'price') |
|||
}), |
|||
(_('Timestamps'), { |
|||
'fields': ('created_at', 'updated_at'), |
|||
'classes': ('collapse',) |
|||
}), |
|||
) |
|||
|
|||
@ -0,0 +1,6 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class TransactionConfig(AppConfig): |
|||
default_auto_field = 'django.db.models.BigAutoField' |
|||
name = 'apps.transaction' |
|||
@ -0,0 +1,44 @@ |
|||
# Generated by Django 3.2.4 on 2024-11-30 22:25 |
|||
|
|||
from django.conf import settings |
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
import phonenumber_field.modelfields |
|||
import utils.validators |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
initial = True |
|||
|
|||
dependencies = [ |
|||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|||
('course', '0005_participant_unread_messages_count'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='TransactionParticipant', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('is_paid', models.BooleanField(default=False, help_text='Indicates whether the payment has been completed or not', verbose_name='Payment Status')), |
|||
('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Transaction Price')), |
|||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), |
|||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), |
|||
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_transactions', to='course.course')), |
|||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to=settings.AUTH_USER_MODEL)), |
|||
], |
|||
), |
|||
migrations.CreateModel( |
|||
name='ParticipantInfo', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('fullname', models.CharField(help_text='Enter the full name of the user.', max_length=255, verbose_name='Full Name')), |
|||
('email', models.EmailField(help_text="Enter the user's email address.", max_length=254, unique=True, verbose_name='Email Address')), |
|||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None, unique=True, validators=[utils.validators.validate_possible_number], verbose_name='phone')), |
|||
('gender', models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female')], help_text="Select the user's gender.", max_length=20, null=True, verbose_name='Gender')), |
|||
('birthdate', models.DateField(blank=True, null=True, verbose_name='birthdate')), |
|||
('transaction_participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participant_infos', to='transaction.transactionparticipant', verbose_name='Transaction Participant')), |
|||
], |
|||
), |
|||
] |
|||
@ -0,0 +1,50 @@ |
|||
from django.db import models |
|||
|
|||
from django.utils.translation import gettext_lazy as _ |
|||
|
|||
from apps.account.models import StudentUser, User |
|||
from apps.course.models import Course |
|||
from phonenumber_field.modelfields import PhoneNumberField |
|||
from utils.validators import validate_possible_number |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
class TransactionParticipant(models.Model): |
|||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions') |
|||
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_transactions') |
|||
is_paid = models.BooleanField(default=False, verbose_name='Payment Status', help_text='Indicates whether the payment has been completed or not') |
|||
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='Transaction Price') |
|||
|
|||
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) |
|||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at")) |
|||
|
|||
|
|||
|
|||
|
|||
class ParticipantInfo(models.Model): |
|||
class GenderChoices(models.TextChoices): |
|||
MALE = 'male', 'Male' |
|||
FEMALE = 'female', 'Female' |
|||
|
|||
transaction_participant = models.ForeignKey( |
|||
TransactionParticipant, |
|||
on_delete=models.CASCADE, |
|||
related_name='participant_infos', |
|||
verbose_name="Transaction Participant" |
|||
) |
|||
fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the full name of the user.") |
|||
email = models.EmailField(unique=True, verbose_name="Email Address", help_text="Enter the user's email address.") |
|||
phone_number = PhoneNumberField(unique=True, validators=[validate_possible_number], null=True, blank=True, verbose_name=_('phone')) |
|||
gender = models.CharField( |
|||
max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender." |
|||
) |
|||
birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True) |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
@ -0,0 +1,44 @@ |
|||
|
|||
from rest_framework import serializers |
|||
|
|||
from apps.transaction.models import TransactionParticipant, ParticipantInfo |
|||
from apps.course.serializers import CourseDetailSerializer |
|||
|
|||
|
|||
|
|||
|
|||
class ParticipantInfoSerializer(serializers.ModelSerializer): |
|||
class Meta: |
|||
model = ParticipantInfo |
|||
fields = ['fullname', 'email', 'phone_number', 'gender', 'birthdate'] |
|||
|
|||
|
|||
class TransactionParticipantSerializer(serializers.ModelSerializer): |
|||
participant_infos = ParticipantInfoSerializer(many=True) |
|||
|
|||
class Meta: |
|||
model = TransactionParticipant |
|||
fields = ['participant_infos'] |
|||
|
|||
|
|||
def create(self, validated_data): |
|||
participant_infos_data = validated_data.pop('participant_infos', []) |
|||
transaction_participant = TransactionParticipant.objects.create(**validated_data) |
|||
|
|||
for participant_info_data in participant_infos_data: |
|||
ParticipantInfo.objects.create(transaction_participant=transaction_participant, **participant_info_data) |
|||
|
|||
return transaction_participant |
|||
|
|||
|
|||
|
|||
class TransactionListSerializer(serializers.ModelSerializer): |
|||
course = serializers.SerializerMethodField() |
|||
|
|||
class Meta: |
|||
model = TransactionParticipant |
|||
fields = ['course', 'is_paid', 'price', 'created_at', 'updated_at'] |
|||
|
|||
def get_course(self, obj): |
|||
return CourseDetailSerializer(obj.course, context=self.context).data |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
from django.test import TestCase |
|||
|
|||
# Create your tests here. |
|||
@ -0,0 +1,13 @@ |
|||
|
|||
from django.urls import path |
|||
|
|||
from . import views |
|||
|
|||
|
|||
|
|||
urlpatterns = [ |
|||
path('<slug:slug>/join/', views.TransactionParticipantCreateView.as_view(), name='transaction-participant-create'), |
|||
path('list/', views.TransactiontListView.as_view(), name='transaction-list'), |
|||
|
|||
|
|||
] |
|||
@ -0,0 +1,77 @@ |
|||
from rest_framework import generics, status |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.response import Response |
|||
|
|||
from apps.course.models import Participant, Course |
|||
from apps.transaction.models import TransactionParticipant |
|||
from apps.transaction.serializers import TransactionParticipantSerializer, TransactionListSerializer |
|||
from utils.exceptions import AppAPIException |
|||
from apps.account.models import User |
|||
|
|||
|
|||
|
|||
class TransactionParticipantCreateView(generics.CreateAPIView): |
|||
queryset = TransactionParticipant.objects.all() |
|||
serializer_class = TransactionParticipantSerializer |
|||
permission_classes = [IsAuthenticated] |
|||
|
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
user = request.user |
|||
course_slug = self.kwargs.get('slug') # Get the slug from the URL |
|||
try: |
|||
course = Course.objects.get(slug=course_slug) # Retrieve the Course object |
|||
except Course.DoesNotExist: |
|||
raise AppAPIException({'message': "Course not found"}) # Handle course not found |
|||
|
|||
participant_infos = request.data.get('participant_infos', []) |
|||
print(f'1---> {participant_infos}') |
|||
print(f'2---> {len(participant_infos)}') |
|||
|
|||
serializer = self.get_serializer(data=request.data) |
|||
serializer.is_valid(raise_exception=True) |
|||
|
|||
if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free): |
|||
participant = participant_infos[0] |
|||
if participant.get('email') != user.email: |
|||
raise AppAPIException({'message': "The email must be for the requesting user"}) |
|||
|
|||
if user.user_type != User.UserType.STUDENT: |
|||
user = User.objects.change_user_type(user, User.UserType.STUDENT) |
|||
|
|||
participant, created = Participant.objects.get_or_create( |
|||
student=user, |
|||
course=course |
|||
) |
|||
return Response({ |
|||
'message': 'Transaction Participant created successfully.', |
|||
'participant_id': participant.id, |
|||
'participant_infos': serializer.data['participant_infos'] |
|||
}, status=status.HTTP_201_CREATED) |
|||
|
|||
|
|||
|
|||
transaction_participant = serializer.save(user=user, course=course, price=course.final_price) |
|||
print(f'---> {type(transaction_participant)}/ {transaction_participant}') |
|||
return Response({ |
|||
'message': 'Transaction Participant created successfully.', |
|||
'transaction_id': transaction_participant.id, |
|||
'participant_infos': serializer.data['participant_infos'] |
|||
}, status=status.HTTP_201_CREATED) |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
class TransactiontListView(generics.ListAPIView): |
|||
queryset = TransactionParticipant.objects.all() # یا هر فیلتر که بخواهید اضافه کنید |
|||
serializer_class = TransactionListSerializer |
|||
permission_classes = [IsAuthenticated] # برای دسترسی کاربران احراز هویت شده |
|||
|
|||
|
|||
def get_queryset(self): |
|||
queryset = super().get_queryset() |
|||
queryset = queryset.filter(user=self.request.user) |
|||
return queryset |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue