wallet/apps/wallet/views/user.py
Sayyid Hamid Mahdavi e045ffd9b1 grouped transactions
2026-02-07 13:21:01 +03:30

378 lines
14 KiB
Python
Executable file

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Q
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import DetailView, ListView, UpdateView
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
from rest_framework import generics, permissions, mixins
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet, GenericViewSet
from apps.wallet.models import Transaction
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Sum, Count, F, StringAgg
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.wallet.models import Transaction, Wallet, Account
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
AccountSerializer, TransactionGroupedSerializer
from ..constans import StateChoices, TypeChoices
from ..filters import AccountFilter, UserTransactionFilter
from ..forms import TransactionActionForm
from django.utils.translation import gettext_lazy as _
def get_application(request):
try:
application = request.auth.application
except:
application = None
return application
class UserAccountBalanceView(generics.RetrieveAPIView):
'''
API for getting authenticated user balance.
'''
permission_classes = [IsAuthenticatedOrTokenHasScope]
serializer_class = AccountSerializer
required_scopes = ['wallet.wallet:get_balance']
def get_object(self):
return self.request.user
# this is application create invoice view
class TransactionList(generics.ListAPIView):
'''
GET: API for getting list of transactions for authenticated user
POST: API for creating transaction for transferring funds from authenticated user to application owner. (deprecated)
args:
- user: authenticated user
'''
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = TransactionSerializer
required_alternate_scopes = {
"GET": [['wallet.user:transaction_list']],
# "POST": [['wallet.invoice:create']],
}
filter_backends = (DjangoFilterBackend,)
filterset_class = UserTransactionFilter
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payee_id=user.pk, payee_type=TypeChoices.USER.value) | Q(payer_id=user.pk, payer_type=TypeChoices.USER.value)).all()
# TODO:
# def perform_create(self, serializer):
# payer = self.request.user
# payee = self.request.auth.application_owner
# serializer.save(payer_id=payer.pk, payee=payee, application=get_application(self.request))
class TransactionDetail(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.transaction:retrieve']
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payee=user) | Q(payer=user) | Q(payer__isnull=True)).all()
class TransactionPay(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.invoice:pay']
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payer=user) | Q(payer__isnull=True)).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
instance.pay(payer=self.request.user)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class TransactionReceipt(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, IsAuthenticatedOrTokenHasScope]
serializer_class = TransactionSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.invoice:receipt']
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(payer=user).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
instance.receipt()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class DepositSubmit(generics.CreateAPIView):
permission_classes = [TokenHasScope]
serializer_class = DepositSerializer
required_scopes = ['wallet.deposit:submit']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application).all()
def perform_create(self, serializer):
payer = self.request.auth.application.user
serializer.save(payer=payer, application=get_application(self.request))
# TODO: refactore
class DepositVerify(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = DepositSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.deposit:verify']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.verify()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class WithdrawSubmit(generics.CreateAPIView):
permission_classes = [TokenHasScope]
serializer_class = WithdrawSerializer
required_scopes = ['wallet.withdraw:submit']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application).all()
def perform_create(self, serializer):
payee = self.request.auth.application.user
serializer.save(payee=payee, application=get_application(self.request))
# TODO: refactor
class WithdrawVerify(generics.RetrieveAPIView):
permission_classes = [TokenHasScope]
serializer_class = WithdrawSerializer
lookup_field = 'uuid'
required_scopes = ['wallet.withdraw:verify']
def get_queryset(self):
application = get_application(self.request)
return Transaction.objects.filter(application=application).all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
if instance.state == StateChoices.PENDING:
instance.verify()
serializer = self.get_serializer(instance)
return Response(serializer.data)
@method_decorator(login_required, name='dispatch')
class TransactionListView(ListView):
model = Transaction
template_name = 'wallet/transaction_list.html'
def get_queryset(self):
user = self.request.user
qs = super().get_queryset()
return qs.filter(Q(payee_id=user.pk) | Q(payer_id=user.pk)).all()
@method_decorator(login_required, name='dispatch')
class TransactionDetailView(UpdateView):
model = Transaction
form_class = TransactionActionForm
template_name = 'wallet/transaction_detail.html'
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
context_data['StateChoices'] = StateChoices
return context_data
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payee=user) | Q(payer=user) | Q(payer__isnull=True)).all()
def get_success_url(self):
return reverse('wallet:transaction_detail', kwargs={'pk': self.get_object().pk})
def form_valid(self, form):
transaction: Transaction = form.instance
if 'pay' in self.request.POST:
result = transaction.pay(payer=self.request.user)
messages.error(self.request, _('success.'))
if 'receipt' in self.request.POST:
result = transaction.receipt()
messages.error(self.request, _('success.'))
return super().form_valid(form)
class WalletAccountListAPIView(generics.ListAPIView):
'''
API for getting authenticated user balance.
'''
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = AccountSerializer
filter_backends = (DjangoFilterBackend,)
filterset_class = AccountFilter
required_alternate_scopes = {
"GET": [['wallet.user:transaction_list']],
# "POST": [['wallet.invoice:create']],
}
queryset = Account.objects.all()
def get_queryset(self):
user = self.request.user
return Account.objects.filter(owner_uuid=user.uuid).all()
def get_object(self):
user = self.request.user
wallet_uuid = self.kwargs.get('wallet')
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
wallet, created = Account.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER, wallet=wallet)
return wallet
# this is application create invoice view
class TransactionListAPIView(generics.ListAPIView):
'''
GET: API for getting list of transactions for authenticated user
POST: API for creating transaction for transferring funds from authenticated user to application owner. (deprecated)
args:
- user: authenticated user
'''
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = TransactionSerializer
required_alternate_scopes = {
"GET": [['wallet.user:transaction_list']],
# "POST": [['wallet.invoice:create']],
}
filter_backends = (DjangoFilterBackend,)
filterset_class = UserTransactionFilter
queryset = Transaction.objects.all()
# TODO: redundant
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) |
Q(payer_account__owner_uuid=user.uuid)).all()
class WalletTransactionListView(generics.ListAPIView):
'''
GET: API for getting list of transactions for authenticated user
POST: API for creating transaction for transferring funds from authenticated user to application owner. (deprecated)
args:
- user: authenticated user
'''
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = TransactionSerializer
required_alternate_scopes = {
"GET": [['wallet.user:transaction_list']],
# "POST": [['wallet.invoice:create']],
}
filter_backends = (DjangoFilterBackend,)
filterset_class = UserTransactionFilter
def get_queryset(self):
user = self.request.user
wallet_uuid = self.kwargs.get('wallet')
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
account, created = Account.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER, wallet=wallet)
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
class UserTransactionViewSet(mixins.ListModelMixin,
# mixins.CreateModelMixin,
# mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
GenericViewSet):
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
serializer_class = TransactionSerializer
required_alternate_scopes = {
"GET": [['wallet.user:transaction_list']],
# "POST": [['wallet.invoice:create']],
}
filter_backends = (DjangoFilterBackend,)
filterset_class = UserTransactionFilter
queryset = Transaction.objects.all()
# TODO: redundant
def get_queryset(self):
user = self.request.user
return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) |
Q(payer_account__owner_uuid=user.uuid)).all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@extend_schema(responses=TransactionGroupedSerializer(many=True))
@action(detail=False, methods=["GET"], serializer_class=TransactionGroupedSerializer)
def grouped(self):
queryset = self.filter_queryset(self.get_queryset())
grouped_transaction_list = queryset.values('details__reference_id', 'application').annotate(
total_amount=Sum('amount'),
total_count=Count('uuid'),
transaction_uuid_list=ArrayAgg('uuid'),
)
page = list(self.paginate_queryset(grouped_transaction_list))
transaction_uuid_list = []
for grouped_transaction in page:
transaction_uuid_list += grouped_transaction['transaction_uuid_list']
transaction_list = Transaction.objects.filter(uuid__in=transaction_uuid_list)
transaction_dict = {t.pk: t for t in transaction_list}
for grouped_transaction in page:
for i, transaction in enumerate(grouped_transaction['transaction_uuid_list']):
grouped_transaction['transaction_uuid_list'][i] = transaction_dict[transaction]
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)