Browse Source

pref add transaction

master
alireza 1 year ago
parent
commit
cafbd22e8c
  1. 15
      apps/course/serializers/course.py
  2. 19
      apps/transaction/admin.py
  3. 27
      apps/transaction/migrations/0002_remove_transactionparticipant_is_paid_and_more.py
  4. 20
      apps/transaction/models.py
  5. 2
      apps/transaction/serializers.py
  6. 1
      apps/transaction/urls.py
  7. 65
      apps/transaction/views.py

15
apps/course/serializers/course.py

@ -71,7 +71,7 @@ class CourseDetailSerializer(serializers.ModelSerializer):
lessons_count = serializers.SerializerMethodField() lessons_count = serializers.SerializerMethodField()
last_lesson_id = serializers.SerializerMethodField() last_lesson_id = serializers.SerializerMethodField()
room_id = serializers.SerializerMethodField() room_id = serializers.SerializerMethodField()
user_transaction_status = serializers.SerializerMethodField()
class Meta: class Meta:
model = Course model = Course
fields = [ fields = [
@ -103,6 +103,7 @@ class CourseDetailSerializer(serializers.ModelSerializer):
'features', 'features',
'last_lesson_id', 'last_lesson_id',
'room_id', 'room_id',
'user_transaction_status'
] ]
def get_room_id(self, obj): def get_room_id(self, obj):
@ -111,6 +112,18 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return room_message.id return room_message.id
return None return None
def get_user_transaction_status(self, obj):
from apps.transaction.models import TransactionParticipant
if student := self._get_authenticated_user():
latest_transaction = TransactionParticipant.objects.filter(
user=student,
course=obj,
is_deleted=False
).order_by('-created_at').first()
if latest_transaction:
return latest_transaction.status
return None
def get_last_lesson_id(self, obj): def get_last_lesson_id(self, obj):
request = self.context.get('request') request = self.context.get('request')
if request and request.user.is_authenticated: if request and request.user.is_authenticated:

19
apps/transaction/admin.py

@ -22,7 +22,7 @@ class ParticipantInfoInline(StackedInline):
@admin.register(TransactionParticipant) @admin.register(TransactionParticipant)
class TransactionParticipantAdmin(ModelAdmin): class TransactionParticipantAdmin(ModelAdmin):
list_display = ('user', 'course', 'payment_status', 'price_display', 'created_at', 'updated_at') list_display = ('user', 'course', 'payment_status', 'price_display', 'created_at', 'updated_at')
list_filter = ('is_paid', 'course', 'created_at')
list_filter = ('status', 'course', 'created_at')
search_fields = ('user__email', 'course__title') search_fields = ('user__email', 'course__title')
readonly_fields = [ 'created_at', 'updated_at'] readonly_fields = [ 'created_at', 'updated_at']
inlines = [ParticipantInfoInline] inlines = [ParticipantInfoInline]
@ -32,7 +32,7 @@ class TransactionParticipantAdmin(ModelAdmin):
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ('user', 'course', 'is_paid', 'price')
'fields': ('user', 'course', 'status', 'price')
}), }),
(_('Timestamps'), { (_('Timestamps'), {
'fields': ('created_at', 'updated_at'), 'fields': ('created_at', 'updated_at'),
@ -40,19 +40,20 @@ class TransactionParticipantAdmin(ModelAdmin):
}), }),
) )
@display(description=_("Payment Status"), ordering="is_paid")
@display(description=_("Payment Status"), ordering="status")
def payment_status(self, obj): def payment_status(self, obj):
if obj.is_paid:
if obj.status == 'success':
return format_html('<span class="unfold-badge unfold-badge--success">Paid</span>') return format_html('<span class="unfold-badge unfold-badge--success">Paid</span>')
return format_html('<span class="unfold-badge unfold-badge--warning">Unpaid</span>')
elif obj.status == 'failed':
return format_html('<span class="unfold-badge unfold-badge--danger">Failed</span>')
return format_html('<span class="unfold-badge unfold-badge--warning">Pending</span>')
@display(description=_("Price"), ordering="price") @display(description=_("Price"), ordering="price")
def price_display(self, obj): def price_display(self, obj):
return format_html('<span class="unfold-badge unfold-badge--info">${}</span>', obj.price) return format_html('<span class="unfold-badge unfold-badge--info">${}</span>', obj.price)
def get_queryset(self, request): def get_queryset(self, request):
queryset = super().get_queryset(request)
# Add any custom queryset modifications here if needed
return queryset
# Filter out deleted transactions
return super().get_queryset(request).filter(is_deleted=False)
project_admin_site.register(TransactionParticipant, TransactionParticipantAdmin) project_admin_site.register(TransactionParticipant, TransactionParticipantAdmin)

27
apps/transaction/migrations/0002_remove_transactionparticipant_is_paid_and_more.py

@ -0,0 +1,27 @@
# Generated by Django 5.1.8 on 2025-04-07 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transaction', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='transactionparticipant',
name='is_paid',
),
migrations.AddField(
model_name='transactionparticipant',
name='is_deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='transactionparticipant',
name='status',
field=models.CharField(choices=[('pending', 'Pending'), ('success', 'Success'), ('failed', 'Failed')], default='pending', max_length=20, verbose_name='Transaction Status'),
),
]

20
apps/transaction/models.py

@ -12,15 +12,26 @@ from utils.validators import validate_possible_number
class TransactionParticipant(models.Model): class TransactionParticipant(models.Model):
class TransactionStatus(models.TextChoices):
PENDING = 'pending', _('Pending')
SUCCESS = 'success', _('Success')
FAILED = 'failed', _('Failed')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions')
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='course_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')
# 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') price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='Transaction Price')
status = models.CharField(max_length=20, choices=TransactionStatus.choices, default=TransactionStatus.PENDING, verbose_name=_('Transaction Status'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created at"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at"))
is_deleted = models.BooleanField(default=False)
def __str__(self):
return f"{self.user.email} - {self.course.title} ({self.status})"
class ParticipantInfo(models.Model): class ParticipantInfo(models.Model):
@ -42,6 +53,9 @@ class ParticipantInfo(models.Model):
) )
birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True) birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True)
def __str__(self):
return f"{self.fullname} (Transaction: {self.transaction_participant.id}) - {self.email}"

2
apps/transaction/serializers.py

@ -42,7 +42,7 @@ class TransactionListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = TransactionParticipant model = TransactionParticipant
fields = ['course', 'is_paid', 'price', 'created_at', 'updated_at']
fields = ['course', 'status', 'price', 'created_at', 'updated_at']
def get_course(self, obj): def get_course(self, obj):
return CourseDetailSerializer(obj.course, context=self.context).data return CourseDetailSerializer(obj.course, context=self.context).data

1
apps/transaction/urls.py

@ -8,6 +8,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path('<slug:slug>/join/', views.TransactionParticipantCreateView.as_view(), name='transaction-participant-create'), path('<slug:slug>/join/', views.TransactionParticipantCreateView.as_view(), name='transaction-participant-create'),
path('list/', views.TransactiontListView.as_view(), name='transaction-list'), path('list/', views.TransactiontListView.as_view(), name='transaction-list'),
path('<int:pk>/delete/', views.SoftDeleteTransactionParticipantView.as_view(), name='soft-delete-transaction-participant'),
] ]

65
apps/transaction/views.py

@ -1,12 +1,14 @@
from rest_framework import generics, status from rest_framework import generics, status
from rest_framework.permissions import IsAuthenticated 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 apps.course.models import Participant, Course from apps.course.models import Participant, Course
from apps.transaction.models import TransactionParticipant from apps.transaction.models import TransactionParticipant
from apps.transaction.serializers import TransactionParticipantSerializer, TransactionListSerializer from apps.transaction.serializers import TransactionParticipantSerializer, TransactionListSerializer
from utils.exceptions import AppAPIException from utils.exceptions import AppAPIException
from apps.account.models import User from apps.account.models import User
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
@ -25,12 +27,10 @@ class TransactionParticipantCreateView(generics.CreateAPIView):
raise AppAPIException({'message': "Course not found"}) # Handle course not found raise AppAPIException({'message': "Course not found"}) # Handle course not found
participant_infos = request.data.get('participant_infos', []) 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 = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
statis = TransactionParticipant.TransactionStatus.PENDING
if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free): if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free):
participant = participant_infos[0] participant = participant_infos[0]
if participant.get('email') != user.email: if participant.get('email') != user.email:
@ -43,15 +43,11 @@ class TransactionParticipantCreateView(generics.CreateAPIView):
student=user, student=user,
course=course course=course
) )
return Response({
'message': 'Transaction Participant created successfully.',
'participant_id': participant.id,
'participant_infos': serializer.data['participant_infos']
}, status=status.HTTP_201_CREATED)
statis = TransactionParticipant.TransactionStatus.SUCCESS
transaction_participant = serializer.save(user=user, course=course, price=course.final_price)
transaction_participant = serializer.save(user=user, course=course, price=course.final_price, status=statis)
print(f'---> {type(transaction_participant)}/ {transaction_participant}') print(f'---> {type(transaction_participant)}/ {transaction_participant}')
return Response({ return Response({
'message': 'Transaction Participant created successfully.', 'message': 'Transaction Participant created successfully.',
@ -74,4 +70,51 @@ class TransactiontListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
queryset = queryset.filter(user=self.request.user) queryset = queryset.filter(user=self.request.user)
return queryset
return queryset
class SoftDeleteTransactionParticipantView(APIView):
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
operation_summary="Soft delete a transaction participant",
operation_description="Marks a transaction participant as deleted without removing it from the database",
manual_parameters=[
openapi.Parameter(
'id',
openapi.IN_PATH,
description="Transaction Participant ID",
type=openapi.TYPE_INTEGER,
required=True
)
],
responses={
200: openapi.Response(
description="Transaction participant successfully marked as deleted",
examples={
"application/json": {
"success": True,
"message": "Transaction participant successfully marked as deleted"
}
}
),
404: "Transaction participant not found",
403: "Permission denied"
}
)
def delete(self, request, pk):
try:
transaction = TransactionParticipant.objects.get(pk=pk)
if transaction.user == request.user:
transaction.is_deleted = True
transaction.save()
return Response({
"success": True,
"message": "Transaction participant successfully marked as deleted"
}, status=status.HTTP_200_OK)
else:
return AppAPIException({'message': "You don't have permission to delete this transaction"}, status_code=status.HTTP_403_FORBIDDEN)
except TransactionParticipant.DoesNotExist:
return AppAPIException({'message': "Transaction participant not found"})
Loading…
Cancel
Save