From c925100fd66319dbbf2d6bac61ca2ed7fd09629c Mon Sep 17 00:00:00 2001 From: Sayyid Hamid Mahdavi Date: Thu, 23 Oct 2025 17:25:15 +0330 Subject: [PATCH] some cleanups --- .../0007_remove_application_balance.py | 17 ++ apps/users/forms.py | 8 +- ..._user_balance_remove_user_iban_and_more.py | 25 +++ apps/users/views.py | 5 +- apps/wallet/filters.py | 37 ++++- ...2_remove_transaction__payee_id_and_more.py | 29 ++++ apps/wallet/serializers.py | 4 + apps/wallet/urls.py | 38 ++--- apps/wallet/views/application.py | 148 ++++++++++++++---- apps/wallet/views/user.py | 26 +++ 10 files changed, 269 insertions(+), 68 deletions(-) create mode 100644 apps/gooyal_oauth2/migrations/0007_remove_application_balance.py create mode 100644 apps/users/migrations/0007_remove_user_balance_remove_user_iban_and_more.py create mode 100644 apps/wallet/migrations/0012_remove_transaction__payee_id_and_more.py diff --git a/apps/gooyal_oauth2/migrations/0007_remove_application_balance.py b/apps/gooyal_oauth2/migrations/0007_remove_application_balance.py new file mode 100644 index 0000000..278182e --- /dev/null +++ b/apps/gooyal_oauth2/migrations/0007_remove_application_balance.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1.7 on 2025-10-23 09:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gooyal_oauth2', '0006_accesstoken_token_checksum_refreshtoken_token_family_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='application', + name='balance', + ), + ] diff --git a/apps/users/forms.py b/apps/users/forms.py index 409ef59..f16e601 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -21,7 +21,7 @@ class OAUTHLoginRequestForm(forms.ModelForm): fields = ['agreement'] -class AccountUpdateForm(forms.ModelForm): - class Meta: - model = UserModel - fields = ['iban'] \ No newline at end of file +# class AccountUpdateForm(forms.ModelForm): +# class Meta: +# model = UserModel +# fields = ['iban'] \ No newline at end of file diff --git a/apps/users/migrations/0007_remove_user_balance_remove_user_iban_and_more.py b/apps/users/migrations/0007_remove_user_balance_remove_user_iban_and_more.py new file mode 100644 index 0000000..7f57a63 --- /dev/null +++ b/apps/users/migrations/0007_remove_user_balance_remove_user_iban_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.7 on 2025-10-23 09:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_user_balance'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='balance', + ), + migrations.RemoveField( + model_name='user', + name='iban', + ), + migrations.RemoveField( + model_name='user', + name='iban_verified', + ), + ] diff --git a/apps/users/views.py b/apps/users/views.py index 1b770ae..ecb34dc 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -13,7 +13,8 @@ from rest_framework import generics from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements from apps.gooyal_oauth2.validators import OAuth2Validator -from apps.users.forms import OAUTHLoginRequestForm, AccountUpdateForm +from apps.users.forms import OAUTHLoginRequestForm +# from apps.users.forms import AccountUpdateForm from apps.users.models import User, OAuthCode UserModel = get_user_model() @@ -95,7 +96,7 @@ class AccountDetailView(DetailView): class AccountUpdateView(UpdateView): model = User template_name = 'users/account_update.html' - form_class = AccountUpdateForm + # form_class = AccountUpdateForm def get_success_url(self): messages.success(self.request, 'عملیات انجام شد.') diff --git a/apps/wallet/filters.py b/apps/wallet/filters.py index c12c41e..7908c0f 100644 --- a/apps/wallet/filters.py +++ b/apps/wallet/filters.py @@ -1,15 +1,46 @@ import django_filters +from django.db import models from django.db.models import Q +from .constans import TypeChoices +from .models import Transaction, Wallet -from .models import Transaction + +class CategoryUUIDFilter(django_filters.BaseInFilter, django_filters.UUIDFilter): + pass + + +class UserTransactionCategoryFilter(django_filters.BaseInFilter, django_filters.UUIDFilter): + def filter(self, queryset, value): + request = self.parent.request + user = request.user + + return queryset.filter( + Q(payer_wallet__owner_uuid=user.pk, + payer_wallet__owner_type=TypeChoices.USER, + payer_wallet__category_id__in=value, + ) | + Q(payee_wallet__owner_uuid=user.pk, + payee_wallet__owner_type=TypeChoices.USER, + payee_wallet__category_id__in=value, + )) class UserTransactionFilter(django_filters.FilterSet): - payer_id = django_filters.UUIDFilter(field_name='payer_wallet__owner_uuid', lookup_expr='iexact') - payee_id = django_filters.UUIDFilter(field_name='payee_wallet__owner_uuid', lookup_expr='iexact') + payer = django_filters.UUIDFilter(field_name='payer_wallet__owner_uuid', lookup_expr='iexact') + payee = django_filters.UUIDFilter(field_name='payee_wallet__owner_uuid', lookup_expr='iexact') + wallets = UserTransactionCategoryFilter(field_name='payee_wallet__category', required=True, lookup_expr='in') class Meta: model = Transaction fields = { } + + +class WalletFilter(django_filters.FilterSet): + wallets = CategoryUUIDFilter(field_name='category', required=True, lookup_expr='in') + + class Meta: + model = Wallet + fields = { + } diff --git a/apps/wallet/migrations/0012_remove_transaction__payee_id_and_more.py b/apps/wallet/migrations/0012_remove_transaction__payee_id_and_more.py new file mode 100644 index 0000000..ab426b5 --- /dev/null +++ b/apps/wallet/migrations/0012_remove_transaction__payee_id_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1.7 on 2025-10-23 09:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wallet', '0011_category_currency'), + ] + + operations = [ + migrations.RemoveField( + model_name='transaction', + name='_payee_id', + ), + migrations.RemoveField( + model_name='transaction', + name='_payee_type', + ), + migrations.RemoveField( + model_name='transaction', + name='_payer_id', + ), + migrations.RemoveField( + model_name='transaction', + name='_payer_type', + ), + ] diff --git a/apps/wallet/serializers.py b/apps/wallet/serializers.py index a538245..9535997 100755 --- a/apps/wallet/serializers.py +++ b/apps/wallet/serializers.py @@ -160,6 +160,10 @@ class TransferVerifySerializer(serializers.ModelSerializer): #### Application +class WalletSerializer(serializers.ModelSerializer): + class Meta: + model = Wallet + fields = ['balance', 'category'] class BalanceSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/wallet/urls.py b/apps/wallet/urls.py index 7dd4b07..44f00b8 100644 --- a/apps/wallet/urls.py +++ b/apps/wallet/urls.py @@ -8,37 +8,23 @@ app_name = "wallet" user_urlpatterns = [ path('transactions/', user_views.TransactionListView.as_view(), name='user_transaction_list_page'), path('transactions/', user_views.TransactionDetailView.as_view(), name='user_transaction_detail_page'), - path('api/wallet/balance', user_views.CategoryMyWalletBalanceView.as_view(), name='user_wallet_balance_api'), - path('api/transactions/', user_views.CategoryTransactionListView.as_view(), name='transaction_list_api'), + # path('api/wallet/balance', user_views.CategoryMyWalletBalanceView.as_view(), name='user_wallet_balance_api'), + path('api/transactions/', user_views.TransactionListAPIView.as_view(), name='transaction_list_api'), - path('api/user//balance', user_views.CategoryMyWalletBalanceView.as_view(), name='user_wallet_balance_api'), + # path('api/user//balance', user_views.CategoryMyWalletBalanceView.as_view(), name='user_wallet_balance_api'), path('api/user//transactions/', user_views.CategoryTransactionListView.as_view(), name='transaction_list_api'), - - - # path('api/transactions/', TransactionDetail.as_view(), name='transaction_detail_api'), - # path('api/transactions//pay', TransactionPay.as_view(), name='transaction_pay_api'), - # path('api/transactions//receipt', TransactionReceipt.as_view(), name='transaction_receipt_api'), - # # - # path('api/deposit/', DepositSubmit.as_view(), name='deposit_submit_api'), - # path('api/deposit//verify', DepositVerify.as_view(), name='deposit_verify_api'), - # # - # path('api/withdraw/', WithdrawSubmit.as_view(), name='withdraw_submit_api'), - # path('api/withdraw//verify', WithdrawVerify.as_view(), name='withdraw_verify_api'), - - # path('api/get_balance/', application_views.WalletBalanceView.as_view(), name='wallet_balance'), - ] application_urlpatterns = [ - path('api/application/balance', application_views.BalanceAPIView.as_view(), name='application_wallet_balance_api'), - path('api/application/user_balance/', application_views.UserBalanceAPIView.as_view(), name='application_user_wallet_balance_api'), - path('api/application/', application_views.TransactionListAPIView.as_view(), name='application_transaction_list_api'), - path('api/application//', application_views.TransactionDetailAPIView.as_view(), name='application_transaction_detail'), + path('api/application/wallets', application_views.WalletListAPIView.as_view(), name='application_wallet_balance_api'), + path('api/application/user//wallets/', application_views.UserWalletListAPIView.as_view(), name='application_user_wallet_api'), + + # path('api/application/', application_views.TransactionListAPIView.as_view(), name='application_transaction_list_api'), + # path('api/application//', application_views.TransactionDetailAPIView.as_view(), name='application_transaction_detail'), - - path('api/application//balance', application_views.BalanceAPIView.as_view(), name='application_wallet_balance_api'), - path('api/application//user//balance', application_views.UserBalanceAPIView.as_view(), name='application_user_wallet_balance_api'), + # path('api/application//wallet', application_views.BalanceAPIView.as_view(), name='application_wallet_balance_api'), + # path('api/application//user//wallet', application_views.UserBalanceAPIView.as_view(), name='application_user_wallet_balance_api'), path('api/application//deposit/', application_views.DepositSubmitAPIView.as_view(), name='application_deposit_submit_api'), path('api/application//deposit//verify', application_views.DepositVerifyAPIView.as_view(), name='application_deposit_verify_api'), @@ -46,8 +32,8 @@ application_urlpatterns = [ path('api/application//withdraw/', application_views.WithdrawSubmitAPIView.as_view(), name='application_withdraw_submit_api'), path('api/application//withdraw//verify', application_views.WithdrawVerifyAPIView.as_view(), name='application_withdraw_verify_api'), - path('api/application//', application_views.TransactionListAPIView.as_view(), name='application_transaction_list_api'), - path('api/application///', application_views.TransactionDetailAPIView.as_view(), name='application_transaction_detail'), + path('api/application//', application_views.CategoryTransactionListAPIView.as_view(), name='application_transaction_list_api'), + path('api/application///', application_views.CategoryTransactionDetailAPIView.as_view(), name='application_transaction_detail'), ] diff --git a/apps/wallet/views/application.py b/apps/wallet/views/application.py index 8e909f7..6e7c246 100755 --- a/apps/wallet/views/application.py +++ b/apps/wallet/views/application.py @@ -1,6 +1,7 @@ import logging from django.db.models import Q +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.exceptions import APIException @@ -10,9 +11,10 @@ from rest_framework.response import Response from apps.users.models import User from apps.wallet.constans import TypeChoices, StateChoices +from apps.wallet.filters import WalletFilter, UserTransactionFilter from apps.wallet.models import Transaction, Category, Wallet from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \ - TransactionSerializer, BalanceSerializer + TransactionSerializer, BalanceSerializer, WalletSerializer from utils.exceptions import UnprocessableEntity from django.utils.translation import gettext_lazy as _ @@ -31,12 +33,14 @@ def get_wallet_category(request: Request): pass -class BalanceAPIView(generics.RetrieveAPIView): +class WalletListAPIView(generics.ListAPIView): ''' API for getting specified user or application balance. ''' - permission_classes = [TokenHasScope] serializer_class = BalanceSerializer + filter_backends = (DjangoFilterBackend,) + filterset_class = WalletFilter + permission_classes = [IsAuthenticatedOrTokenHasScope] required_scopes = ['wallet.application:get_balance'] def get_object(self): @@ -51,62 +55,140 @@ class BalanceAPIView(generics.RetrieveAPIView): category=category) return wallet + def get_queryset(self): + category_uuid = self.kwargs.get('category_uuid') + if category_uuid: + category = get_object_or_404(Category.objects.all(), uuid=category_uuid) + else: + category = None + application = get_application(self.request) + wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, + category=category) + return Wallet.objects.filter(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION).all() -class UserBalanceAPIView(generics.RetrieveAPIView): + +class UserWalletListAPIView(generics.ListAPIView): ''' - API for getting specified user or application balance. + API for getting specified user wallet. ''' + serializer_class = WalletSerializer + filter_backends = (DjangoFilterBackend,) + filterset_class = WalletFilter permission_classes = [IsAuthenticatedOrTokenHasScope] - serializer_class = BalanceSerializer required_scopes = ['wallet.application:get_user_balance'] - def get_object(self): + queryset = Wallet.objects.all() + + def get_queryset(self): + qs = super().get_queryset() + category_uuid = self.kwargs.get('category_uuid') + user_uuid = self.kwargs.get('user_uuid') + if category_uuid: + category = get_object_or_404(Category.objects.all(), uuid=category_uuid) + else: + category = None + user, created = User.objects.get_or_create(uuid=self.kwargs.get('uuid')) + wallet, created = Wallet.objects.get_or_create(owner_uuid=user_uuid, owner_type=TypeChoices.USER, + category=category) + return qs.filter(owner_uuid=user_uuid, owner_type=TypeChoices.USER).all() + + +class TransactionDetailAPIView(generics.RetrieveAPIView): + serializer_class = TransactionSerializer + lookup_field = 'uuid' + filter_backends = (DjangoFilterBackend,) + filterset_class = WalletFilter + permission_classes = [TokenHasScope] + required_scopes = ['wallet.application:get_transaction_detail'] + queryset = Transaction.objects.all() + + def get_queryset(self): + qs = super().get_queryset() + + application = get_application(self.request) + return Transaction.objects.filter(Q(application=application) | + Q(payer_wallet__owner_uuid=application.pk, + payer_wallet__owner_type=TypeChoices.APPLICATION, + ) | + Q(payee_wallet__owner_uuid=application.pk, + payee_wallet__owner_type=TypeChoices.APPLICATION, + ) + ).all() + + +class TransactionListAPIView(generics.ListAPIView): + serializer_class = TransactionSerializer + lookup_field = 'uuid' + filter_backends = (DjangoFilterBackend,) + filterset_class = UserTransactionFilter + permission_classes = [IsAuthenticatedOrTokenHasScope] + required_scopes = ['wallet.application:get_transaction_list'] + queryset = Transaction.objects.all() + + def get_queryset(self): + qs = super().get_queryset() + + application = get_application(self.request) + return qs.filter(Q(application=application) | + Q(payer_wallet__owner_uuid=application.pk, + payer_wallet__owner_type=TypeChoices.APPLICATION) | + Q(payee_wallet__owner_uuid=application.pk, + payee_wallet__owner_type=TypeChoices.APPLICATION, + ) + ).all() + +class CategoryTransactionDetailAPIView(generics.RetrieveAPIView): + permission_classes = [TokenHasScope] + serializer_class = TransactionSerializer + lookup_field = 'uuid' + required_scopes = ['wallet.application:get_transaction_detail'] + queryset = Transaction.objects.all() + + def get_queryset(self): + qs = super().get_queryset() + category_uuid = self.kwargs.get('category_uuid') if category_uuid: category = get_object_or_404(Category.objects.all(), uuid=category_uuid) else: category = None - user_uuid = self.kwargs.get('uuid') - user, created = User.objects.get_or_create(uuid=user_uuid) - application = get_application(self.request) - wallet, created = Wallet.objects.get_or_create(owner_uuid=user_uuid, owner_type=TypeChoices.USER, - category=category) - return wallet - - -class TransactionDetailAPIView(generics.RetrieveAPIView): - permission_classes = [TokenHasScope] - serializer_class = TransactionSerializer - lookup_field = 'uuid' - required_scopes = ['wallet.application:get_transaction_detail'] - - def get_queryset(self): - # TODO: what about category application = get_application(self.request) return Transaction.objects.filter(Q(application=application) | Q(payer_wallet__owner_uuid=application.pk, - payer_wallet__owner_type=TypeChoices.APPLICATION) | + payer_wallet__owner_type=TypeChoices.APPLICATION, + payer_wallet__category=category) | Q(payee_wallet__owner_uuid=application.pk, - payee_wallet__owner_type=TypeChoices.APPLICATION) + payee_wallet__owner_type=TypeChoices.APPLICATION, + payee_wallet__category=category) ).all() -class TransactionListAPIView(generics.ListAPIView): +class CategoryTransactionListAPIView(generics.ListAPIView): permission_classes = [TokenHasScope] serializer_class = TransactionSerializer lookup_field = 'uuid' required_scopes = ['wallet.application:get_transaction_list'] + queryset = Transaction.objects.all() def get_queryset(self): - # TODO: what about category? + qs = super().get_queryset() + + category_uuid = self.kwargs.get('category_uuid') + if category_uuid: + category = get_object_or_404(Category.objects.all(), uuid=category_uuid) + else: + category = None + application = get_application(self.request) - return Transaction.objects.filter(Q(application=application) | - Q(payer_wallet__owner_uuid=application.pk, - payer_wallet__owner_type=TypeChoices.APPLICATION) | - Q(payee_wallet__owner_uuid=application.pk, - payee_wallet__owner_type=TypeChoices.APPLICATION) - ).all() + return qs.filter(Q(application=application) | + Q(payer_wallet__owner_uuid=application.pk, + payer_wallet__owner_type=TypeChoices.APPLICATION, + payer_wallet__category=category) | + Q(payee_wallet__owner_uuid=application.pk, + payee_wallet__owner_type=TypeChoices.APPLICATION, + payee_wallet__category=category) + ).all() # from application to another entity diff --git a/apps/wallet/views/user.py b/apps/wallet/views/user.py index 79b2c36..753e710 100755 --- a/apps/wallet/views/user.py +++ b/apps/wallet/views/user.py @@ -249,6 +249,32 @@ class CategoryMyWalletBalanceView(generics.RetrieveAPIView): # 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_wallet__owner_uuid=user.uuid) | + Q(payer_wallet__owner_uuid=user.uuid)).all() + + class CategoryTransactionListView(generics.ListAPIView): ''' GET: API for getting list of transactions for authenticated user