374 lines
13 KiB
Python
Executable file
374 lines
13 KiB
Python
Executable file
from django.contrib import messages
|
||
from django.contrib.auth.decorators import login_required
|
||
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 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.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||
from apps.wallet.models import Transaction, Wallet, Account
|
||
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
|
||
AccountSerializer
|
||
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)
|
||
|
||
|
||
@action(detail=False, methods=["GET"])
|
||
def grouped(self):
|
||
from collections import defaultdict
|
||
from django.db.models import Count, Sum
|
||
|
||
# فقط ۲ کوئری بهینه
|
||
# کوئری ۱: آمار کلی (سبک)
|
||
stats = dict(Transaction.objects.values('refrence').annotate(
|
||
total=Sum('amount'),
|
||
count=Count('id')
|
||
).values_list('refrence', 'total', 'count'))
|
||
|
||
# کوئری ۲: همه تراکنشها
|
||
transactions = Transaction.objects.all().order_by('refrence', 'created_at')
|
||
|
||
# گروهبندی در پایتون
|
||
groups = defaultdict(list)
|
||
for t in transactions:
|
||
groups[t.refrence].append(t)
|
||
|
||
# ترکیب
|
||
result = []
|
||
for ref, items in groups.items():
|
||
result.append({
|
||
'refrence': ref,
|
||
'stats': {'total': stats.get(ref, 0), 'count': len(items)},
|
||
'transactions': items
|
||
})
|