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()
last_lesson_id = serializers.SerializerMethodField()
room_id = serializers.SerializerMethodField()
user_transaction_status = serializers.SerializerMethodField()
class Meta:
model = Course
fields = [
@ -103,6 +103,7 @@ class CourseDetailSerializer(serializers.ModelSerializer):
'features',
'last_lesson_id',
'room_id',
'user_transaction_status'
]
def get_room_id(self, obj):
@ -111,6 +112,18 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return room_message.id
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):
request = self.context.get('request')
if request and request.user.is_authenticated:

19
apps/transaction/admin.py

@ -22,7 +22,7 @@ class ParticipantInfoInline(StackedInline):
@admin.register(TransactionParticipant)
class TransactionParticipantAdmin(ModelAdmin):
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')
readonly_fields = [ 'created_at', 'updated_at']
inlines = [ParticipantInfoInline]
@ -32,7 +32,7 @@ class TransactionParticipantAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('user', 'course', 'is_paid', 'price')
'fields': ('user', 'course', 'status', 'price')
}),
(_('Timestamps'), {
'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):
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--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")
def price_display(self, obj):
return format_html('<span class="unfold-badge unfold-badge--info">${}</span>', obj.price)
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)

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 TransactionStatus(models.TextChoices):
PENDING = 'pending', _('Pending')
SUCCESS = 'success', _('Success')
FAILED = 'failed', _('Failed')
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')
# 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')
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"))
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):
@ -42,6 +53,9 @@ class ParticipantInfo(models.Model):
)
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:
model = TransactionParticipant
fields = ['course', 'is_paid', 'price', 'created_at', 'updated_at']
fields = ['course', 'status', 'price', 'created_at', 'updated_at']
def get_course(self, obj):
return CourseDetailSerializer(obj.course, context=self.context).data

1
apps/transaction/urls.py

@ -8,6 +8,7 @@ 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'),
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.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
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
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
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)
statis = TransactionParticipant.TransactionStatus.PENDING
if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free):
participant = participant_infos[0]
if participant.get('email') != user.email:
@ -43,15 +43,11 @@ class TransactionParticipantCreateView(generics.CreateAPIView):
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)
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}')
return Response({
'message': 'Transaction Participant created successfully.',
@ -74,4 +70,51 @@ class TransactiontListView(generics.ListAPIView):
def get_queryset(self):
queryset = super().get_queryset()
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