Compare commits
1 commit
master
...
old-wallet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4efb4ab664 |
75 changed files with 911 additions and 2368 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,4 +5,3 @@ utils/delme.py
|
||||||
*.pyc
|
*.pyc
|
||||||
.env
|
.env
|
||||||
/venv/
|
/venv/
|
||||||
/.venv/
|
|
||||||
|
|
|
||||||
15
Dockerfile
15
Dockerfile
|
|
@ -1,12 +1,9 @@
|
||||||
FROM base:debian13
|
FROM debian:12
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
#RUN date
|
RUN date
|
||||||
#RUN apt update
|
RUN apt update
|
||||||
#RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
||||||
#RUN apt install binutils libproj-dev gdal-bin -y
|
COPY requirements.txt /app/requirements.txt
|
||||||
#COPY requirements.txt /app/requirements.txt
|
RUN pip3 install --break-system-packages -r requirements.txt
|
||||||
#RUN pip3 install --break-system-packages -r requirements.txt
|
|
||||||
#RUN pip3 install --break-system-packages django-admin-rangefilter==0.13.5
|
|
||||||
#RUN pip3 install --break-system-packages setuptools
|
|
||||||
#ENTRYPOINT ["./run.sh"]
|
#ENTRYPOINT ["./run.sh"]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
FROM debian:13
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
|
||||||
WORKDIR /app
|
|
||||||
RUN apt update
|
|
||||||
RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip nano htop iputils-ping -y
|
|
||||||
COPY requirements.txt /app/requirements.txt
|
|
||||||
RUN pip3 install --break-system-packages -r requirements.txt
|
|
||||||
#ENTRYPOINT ["./run.sh"]
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
FROM wallet:local_build
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
|
||||||
WORKDIR /app
|
|
||||||
#RUN apt update
|
|
||||||
#RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip nano htop iputils-ping -y
|
|
||||||
COPY requirements.txt /app/requirements.txt
|
|
||||||
RUN pip3 install --break-system-packages -r requirements.txt
|
|
||||||
#ENTRYPOINT ["./run.sh"]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 6.0 on 2026-02-07 05:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='charge',
|
|
||||||
name='state',
|
|
||||||
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure'), (8, 'ROLLED_BACK')], default=1),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -5,6 +5,6 @@ app_name = "core"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', HomeView.as_view(), name='home'),
|
path('', HomeView.as_view(), name='home'),
|
||||||
# path('charge/<pk>/', ChargeView.as_view(), name='charge'),
|
path('charge/<pk>/', ChargeView.as_view(), name='charge'),
|
||||||
# path('api/charge_create', ChargeRequestApiView.as_view(), name='charge_create_api'),
|
path('api/charge_create', ChargeRequestApiView.as_view(), name='charge_create_api'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ from django.contrib.admin.widgets import AdminDateWidget
|
||||||
from django.forms.fields import DateField, SplitDateTimeField, TimeField, DateTimeField
|
from django.forms.fields import DateField, SplitDateTimeField, TimeField, DateTimeField
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from datetime import date as datetime_date, datetime as datetime_datetime
|
from datetime import date as datetime_date, datetime as datetime_datetime
|
||||||
# from jdatetime import GregorianToJalali, datetime as jalali_datetime
|
from jdatetime import GregorianToJalali, datetime as jalali_datetime
|
||||||
# import jdatetime
|
import jdatetime
|
||||||
|
|
||||||
class JalaliDateField(DateField):
|
class JalaliDateField(DateField):
|
||||||
def __init__(self, *, input_formats=None, **kwargs):
|
def __init__(self, *, input_formats=None, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class ChargeRequestApiView(generics.CreateAPIView):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
serializer.save(user = user)
|
serializer.save(user = user)
|
||||||
|
|
||||||
# TODO: refactor
|
|
||||||
class ChargeView(UpdateView):
|
class ChargeView(UpdateView):
|
||||||
template_name = 'core/charge.html'
|
template_name = 'core/charge.html'
|
||||||
form_class = ChargeForm
|
form_class = ChargeForm
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# 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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -22,6 +22,7 @@ class AccessToken(AbstractAccessToken):
|
||||||
class Application(AbstractApplication):
|
class Application(AbstractApplication):
|
||||||
id = None
|
id = None
|
||||||
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
|
||||||
|
balance = models.BigIntegerField(_('balance'), default=0)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = False
|
abstract = False
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class OAUTHLoginRequestForm(forms.ModelForm):
|
||||||
fields = ['agreement']
|
fields = ['agreement']
|
||||||
|
|
||||||
|
|
||||||
# class AccountUpdateForm(forms.ModelForm):
|
class AccountUpdateForm(forms.ModelForm):
|
||||||
# class Meta:
|
class Meta:
|
||||||
# model = UserModel
|
model = UserModel
|
||||||
# fields = ['iban']
|
fields = ['iban']
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# 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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -62,6 +62,10 @@ class User(AbstractUser):
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
last_checkout_request = models.DateTimeField(_('otp expire'), max_length=30, blank=True, null=True)
|
last_checkout_request = models.DateTimeField(_('otp expire'), max_length=30, blank=True, null=True)
|
||||||
|
balance = models.PositiveBigIntegerField(_('balance'), default=0)
|
||||||
|
|
||||||
|
iban = models.CharField(null=True, max_length=30, blank=True)
|
||||||
|
iban_verified = models.BooleanField(null=True)
|
||||||
|
|
||||||
updated_at = models.DateTimeField(_('last update'), max_length=30, blank=True, null=True, auto_now=True)
|
updated_at = models.DateTimeField(_('last update'), max_length=30, blank=True, null=True, auto_now=True)
|
||||||
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
|
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ from rest_framework import generics
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
from apps.gooyal_oauth2.validators import OAuth2Validator
|
from apps.gooyal_oauth2.validators import OAuth2Validator
|
||||||
from apps.users.forms import OAUTHLoginRequestForm
|
from apps.users.forms import OAUTHLoginRequestForm, AccountUpdateForm
|
||||||
# from apps.users.forms import AccountUpdateForm
|
|
||||||
from apps.users.models import User, OAuthCode
|
from apps.users.models import User, OAuthCode
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
|
|
@ -96,7 +95,7 @@ class AccountDetailView(DetailView):
|
||||||
class AccountUpdateView(UpdateView):
|
class AccountUpdateView(UpdateView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'users/account_update.html'
|
template_name = 'users/account_update.html'
|
||||||
# form_class = AccountUpdateForm
|
form_class = AccountUpdateForm
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.success(self.request, 'عملیات انجام شد.')
|
messages.success(self.request, 'عملیات انجام شد.')
|
||||||
|
|
|
||||||
|
|
@ -1,112 +1,11 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Transaction, Account, Wallet
|
from .models import Transaction, Wallet, Category
|
||||||
from django.http import HttpResponse
|
|
||||||
import io
|
|
||||||
import xlsxwriter
|
|
||||||
import jdatetime
|
|
||||||
from django.utils import timezone
|
|
||||||
from .admin_filters import JalaliDateTimeRangeFilter
|
|
||||||
# from utils.accounts_client import get_user_detailed_info
|
|
||||||
|
|
||||||
@admin.action(description="export excel")
|
|
||||||
def export_excel(modeladmin, request, queryset):
|
|
||||||
output = io.BytesIO()
|
|
||||||
workbook = xlsxwriter.Workbook(output)
|
|
||||||
worksheet = workbook.add_worksheet()
|
|
||||||
|
|
||||||
for col, header in enumerate(['uuid',
|
|
||||||
'application',
|
|
||||||
'amount',
|
|
||||||
'jalali_datetime',
|
|
||||||
'payer',
|
|
||||||
'payer_type',
|
|
||||||
'payee',
|
|
||||||
'payee_type',
|
|
||||||
'state',
|
|
||||||
'created_at',
|
|
||||||
'updated_at',
|
|
||||||
'details',
|
|
||||||
]):
|
|
||||||
worksheet.write(0, col, header)
|
|
||||||
|
|
||||||
for row, tranaction in enumerate(queryset, 1):
|
|
||||||
worksheet.write(row, 0, str(tranaction.uuid))
|
|
||||||
worksheet.write(row, 1, str(tranaction.application))
|
|
||||||
worksheet.write(row, 2, tranaction.amount)
|
|
||||||
worksheet.write(row, 3, str(tranaction.jalali_datetime))
|
|
||||||
worksheet.write(row, 4, str(tranaction.payer))
|
|
||||||
worksheet.write(row, 5, str(tranaction.payer_type))
|
|
||||||
worksheet.write(row, 6, str(tranaction.payee))
|
|
||||||
worksheet.write(row, 7, str(tranaction.payee_type))
|
|
||||||
worksheet.write(row, 8, str(tranaction.state))
|
|
||||||
worksheet.write(row, 9, str(tranaction.created_at))
|
|
||||||
worksheet.write(row, 10, str(tranaction.updated_at))
|
|
||||||
worksheet.write(row, 11, str(tranaction.details))
|
|
||||||
workbook.close()
|
|
||||||
|
|
||||||
# create a response
|
|
||||||
response = HttpResponse(content_type='application/vnd.ms-excel')
|
|
||||||
|
|
||||||
# tell the browser what the file is named
|
|
||||||
file_name = jdatetime.date.fromgregorian(date=timezone.now(), locale='fa_IR').strftime("%Y/%m/%d")
|
|
||||||
response['Content-Disposition'] = f'attachment;filename="{file_name}.xlsx"'
|
|
||||||
|
|
||||||
# put the spreadsheet data into the response
|
|
||||||
response.write(output.getvalue())
|
|
||||||
|
|
||||||
# return the response
|
|
||||||
return response
|
|
||||||
|
|
||||||
@admin.action(description="export excel")
|
|
||||||
def export_accounts_as_excel(modeladmin, request, queryset):
|
|
||||||
output = io.BytesIO()
|
|
||||||
workbook = xlsxwriter.Workbook(output)
|
|
||||||
worksheet = workbook.add_worksheet()
|
|
||||||
for col, header in enumerate(['uuid',
|
|
||||||
'owner_uuid',
|
|
||||||
'owner_type',
|
|
||||||
'wallet',
|
|
||||||
'balance',
|
|
||||||
]):
|
|
||||||
worksheet.write(0, col, header)
|
|
||||||
|
|
||||||
for row, account in enumerate(queryset, 1):
|
|
||||||
worksheet.write(row, 0, str(account.uuid))
|
|
||||||
worksheet.write(row, 1, str(account.owner_uuid))
|
|
||||||
worksheet.write(row, 2, account.owner_type)
|
|
||||||
worksheet.write(row, 3, str(account.wallet))
|
|
||||||
worksheet.write(row, 4, str(account.balance))
|
|
||||||
workbook.close()
|
|
||||||
|
|
||||||
# create a response
|
|
||||||
response = HttpResponse(content_type='application/vnd.ms-excel')
|
|
||||||
|
|
||||||
# tell the browser what the file is named
|
|
||||||
file_name = jdatetime.date.fromgregorian(date=timezone.now(), locale='fa_IR').strftime("%Y/%m/%d")
|
|
||||||
response['Content-Disposition'] = f'attachment;filename="{file_name}.xlsx"'
|
|
||||||
|
|
||||||
# put the spreadsheet data into the response
|
|
||||||
response.write(output.getvalue())
|
|
||||||
|
|
||||||
# return the response
|
|
||||||
return response
|
|
||||||
|
|
||||||
class TransactionAdmin(admin.ModelAdmin):
|
class TransactionAdmin(admin.ModelAdmin):
|
||||||
list_display = ['uuid', 'application', 'splited_amount', 'jalali_datetime', 'payer', 'payer_type', 'payee', 'payee_type', 'state', 'amount', 'created_at', 'updated_at']
|
list_display = ['uuid', 'amount', 'created_at', 'updated_at', 'payer', 'payer_type', 'payee', 'payee_type', 'state', 'application']
|
||||||
search_fields = ['uuid', 'created_at', 'updated_at', 'payer_account__owner_uuid', 'payee_account__owner_uuid', 'application__uuid']
|
search_fields = ['uuid', 'created_at', 'updated_at', 'payer', 'payee', 'application__uuid']
|
||||||
list_filter = ['application', 'state', ('created_at', JalaliDateTimeRangeFilter), ('updated_at', JalaliDateTimeRangeFilter),]
|
list_filter = ['state']
|
||||||
actions = [export_excel]
|
|
||||||
|
|
||||||
class Media:
|
|
||||||
css = {'all': ('admin/css/django_jalali.min.css',)}
|
|
||||||
js = ('admin/js/django_jalali.min.js',)
|
|
||||||
|
|
||||||
class AccountAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['uuid', 'owner_uuid', 'wallet', 'owner_type', 'balance', 'get_name']
|
|
||||||
search_fields = ['uuid', 'owner_uuid']
|
|
||||||
list_filter = ['wallet__label', 'owner_type']
|
|
||||||
actions = [export_accounts_as_excel]
|
|
||||||
|
|
||||||
admin.site.register(Transaction, TransactionAdmin)
|
admin.site.register(Transaction, TransactionAdmin)
|
||||||
admin.site.register(Account, AccountAdmin)
|
|
||||||
admin.site.register(Wallet)
|
admin.site.register(Wallet)
|
||||||
|
admin.site.register(Category)
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from django.contrib.admin.widgets import AdminSplitDateTime
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from rangefilter.filters import DateTimeRangeFilter
|
|
||||||
|
|
||||||
from jalali_date.fields import SplitJalaliDateTimeField
|
|
||||||
from jalali_date.widgets import AdminSplitJalaliDateTime as _AdminSplitJalaliDateTime
|
|
||||||
|
|
||||||
|
|
||||||
class AdminSplitJalaliDateTime(_AdminSplitJalaliDateTime):
|
|
||||||
# jalali_date's own template lives under the (unused, see below) 'jalali_date'
|
|
||||||
# app, which we deliberately don't register in INSTALLED_APPS: its
|
|
||||||
# templatetags module imports the stdlib `distutils`, removed in Python 3.12+,
|
|
||||||
# and Django auto-imports every installed app's templatetags at startup.
|
|
||||||
# Django's stock split-datetime template renders subwidgets generically, so
|
|
||||||
# it works fine here without needing that app registered.
|
|
||||||
template_name = AdminSplitDateTime.template_name
|
|
||||||
|
|
||||||
|
|
||||||
class JalaliDateTimeRangeFilter(DateTimeRangeFilter):
|
|
||||||
def _get_form_fields(self):
|
|
||||||
return OrderedDict(
|
|
||||||
(
|
|
||||||
(
|
|
||||||
self.lookup_kwarg_gte,
|
|
||||||
SplitJalaliDateTimeField(
|
|
||||||
label='',
|
|
||||||
widget=AdminSplitJalaliDateTime(attrs={'placeholder': _('From date')}),
|
|
||||||
localize=True,
|
|
||||||
required=False,
|
|
||||||
initial=self.default_gte,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
self.lookup_kwarg_lte,
|
|
||||||
SplitJalaliDateTimeField(
|
|
||||||
label='',
|
|
||||||
widget=AdminSplitJalaliDateTime(attrs={'placeholder': _('To date')}),
|
|
||||||
localize=True,
|
|
||||||
required=False,
|
|
||||||
initial=self.default_lte,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
@ -10,8 +10,6 @@ class StateChoices(IntegerChoices):
|
||||||
SUCCESS = 5 , _('success')
|
SUCCESS = 5 , _('success')
|
||||||
FAILED = 6 , _('failed')
|
FAILED = 6 , _('failed')
|
||||||
EXPECTED_FAILURE = 7 , _('expected_failure') # no exact data available but guessed to be failed
|
EXPECTED_FAILURE = 7 , _('expected_failure') # no exact data available but guessed to be failed
|
||||||
ROLLED_BACK = 8 , _('ROLLED_BACK')
|
|
||||||
CANCELED = 9, _('CANCELED')
|
|
||||||
|
|
||||||
|
|
||||||
class TypeChoices(IntegerChoices):
|
class TypeChoices(IntegerChoices):
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,15 @@
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from .constans import TypeChoices
|
|
||||||
from .models import Transaction, Account
|
|
||||||
|
|
||||||
|
from .models import Transaction
|
||||||
class WalletUUIDFilter(django_filters.BaseInFilter, django_filters.UUIDFilter):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserTransactionWalletFilter(django_filters.BaseInFilter, django_filters.UUIDFilter):
|
|
||||||
def filter(self, queryset, value):
|
|
||||||
request = self.parent.request
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
return queryset.filter(
|
|
||||||
Q(payer_account__owner_uuid=user.pk,
|
|
||||||
payer_account__owner_type=TypeChoices.USER,
|
|
||||||
payer_account__wallet_id__in=value,
|
|
||||||
) |
|
|
||||||
Q(payee_account__owner_uuid=user.pk,
|
|
||||||
payee_account__owner_type=TypeChoices.USER,
|
|
||||||
payee_account__wallet_id__in=value,
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class UserTransactionFilter(django_filters.FilterSet):
|
class UserTransactionFilter(django_filters.FilterSet):
|
||||||
payer = django_filters.UUIDFilter(field_name='payer_account__owner_uuid', lookup_expr='iexact')
|
payer_id = django_filters.UUIDFilter(field_name='payer_wallet__owner_uuid', lookup_expr='iexact')
|
||||||
payee = django_filters.UUIDFilter(field_name='payee_account__owner_uuid', lookup_expr='iexact')
|
payee_id = django_filters.UUIDFilter(field_name='payee_wallet__owner_uuid', lookup_expr='iexact')
|
||||||
wallets = UserTransactionWalletFilter(field_name='payee_account__wallet', required=True, lookup_expr='in')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
fields = {
|
fields = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AccountFilter(django_filters.FilterSet):
|
|
||||||
wallets = WalletUUIDFilter(field_name='wallet', required=True, lookup_expr='in')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Account
|
|
||||||
fields = {
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# 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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-10-25 10:41
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0012_remove_transaction__payee_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='Wallet',
|
|
||||||
new_name='Account',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-10-25 10:42
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0013_rename_wallet_account_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='transaction',
|
|
||||||
old_name='payee_wallet',
|
|
||||||
new_name='payee_account',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='transaction',
|
|
||||||
old_name='payer_wallet',
|
|
||||||
new_name='payer_account',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-10-25 10:44
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0014_remove_transaction_payee_wallet_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='transaction',
|
|
||||||
name='payee_account',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payee_account_transactions', to='wallet.account'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='transaction',
|
|
||||||
name='payer_account',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payer_account_transactions', to='wallet.account'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-10-25 11:35
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0015_alter_transaction_payee_account_and_more'),
|
|
||||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='Category',
|
|
||||||
new_name='Wallet',
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='account',
|
|
||||||
old_name='category',
|
|
||||||
new_name='wallet',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Generated by Django 6.0 on 2026-02-07 05:57
|
|
||||||
|
|
||||||
import django.contrib.postgres.indexes
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0016_rename_category_wallet_and_more'),
|
|
||||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='transaction',
|
|
||||||
name='reference',
|
|
||||||
field=models.UUIDField(db_index=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='transaction',
|
|
||||||
name='state',
|
|
||||||
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure'), (8, 'ROLLED_BACK')], default=1),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='transaction',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['details'], name='wallet_transaction_de_a7ff79aa'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2026-02-14 11:48
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wallet', '0017_transaction_reference_alter_transaction_state_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='transaction',
|
|
||||||
name='state',
|
|
||||||
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure'), (8, 'ROLLED_BACK'), (9, 'CANCELED')], default=1),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
import logging
|
|
||||||
from functools import cached_property
|
|
||||||
|
|
||||||
from django.contrib.postgres.indexes import GinIndex
|
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import jdatetime
|
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from oauth2_provider import settings as oauth2_settings
|
from oauth2_provider import settings as oauth2_settings
|
||||||
|
|
@ -23,27 +15,21 @@ from .constans import StateChoices, TypeChoices
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from ..gooyal_oauth2.models import Application
|
from ..gooyal_oauth2.models import Application
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CurrencyChoices(models.TextChoices):
|
class CurrencyChoices(models.TextChoices):
|
||||||
IRANIAN_RIAL = '166ab7ce33b34799950d45035a9997d6', _('IRANIAN RIAL') # uuid without dash
|
IRANIAN_RIAL = '166ab7ce33b34799950d45035a9997d6', _('IRANIAN RIAL') # uuid without dash
|
||||||
|
|
||||||
|
|
||||||
class AccountQuerySet(models.QuerySet):
|
class WalletQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class Wallet(BaseModel):
|
||||||
class Account(BaseModel):
|
|
||||||
owner_uuid = models.UUIDField(db_index=True, null=False)
|
owner_uuid = models.UUIDField(db_index=True, null=False)
|
||||||
|
category = models.ForeignKey('Category', on_delete=models.PROTECT, null=True)
|
||||||
owner_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
owner_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
||||||
wallet = models.ForeignKey('Wallet', on_delete=models.PROTECT, null=True)
|
|
||||||
balance = models.BigIntegerField(_('balance'), default=0)
|
balance = models.BigIntegerField(_('balance'), default=0)
|
||||||
|
|
||||||
# TODO: unique together wallet and owner_uuid
|
objects = WalletQuerySet.as_manager()
|
||||||
# TODO: performance check for performance issue of unique together
|
|
||||||
|
|
||||||
objects = AccountQuerySet.as_manager()
|
|
||||||
|
|
||||||
def get_owner(self):
|
def get_owner(self):
|
||||||
if self.owner_type == TypeChoices.APPLICATION:
|
if self.owner_type == TypeChoices.APPLICATION:
|
||||||
|
|
@ -53,25 +39,16 @@ class Account(BaseModel):
|
||||||
|
|
||||||
return owner
|
return owner
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
if self.owner_type == TypeChoices.APPLICATION:
|
|
||||||
owner: Application = Application.objects.get(pk=self.owner_uuid)
|
|
||||||
return owner.name
|
|
||||||
|
|
||||||
|
class CategoryQuerySet(models.QuerySet):
|
||||||
class WalletQuerySet(models.QuerySet):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class Category(BaseModel):
|
||||||
class Wallet(BaseModel):
|
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, null=True)
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, null=True)
|
||||||
label = models.CharField(_('label'), max_length=255)
|
label = models.CharField(_('label'), max_length=255)
|
||||||
currency = models.UUIDField(choices=CurrencyChoices.choices, db_index=True, default=CurrencyChoices.IRANIAN_RIAL)
|
currency = models.UUIDField(choices=CurrencyChoices.choices, db_index=True, default=CurrencyChoices.IRANIAN_RIAL)
|
||||||
|
|
||||||
objects = WalletQuerySet.as_manager()
|
objects = CategoryQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.label
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: move DRF functionalities to view or serializers
|
# TODO: move DRF functionalities to view or serializers
|
||||||
|
|
@ -105,59 +82,27 @@ class Wallet(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class TransactionQuerySet(models.QuerySet):
|
class TransactionQuerySet(models.QuerySet):
|
||||||
def available(self):
|
pass
|
||||||
return self.select_related('payer_account', 'payee_account')
|
|
||||||
|
|
||||||
def group_by_reference(self, reference):
|
|
||||||
from collections import defaultdict
|
|
||||||
from django.db.models import Count, Sum
|
|
||||||
|
|
||||||
# 'id': id,
|
|
||||||
# 'appid': appid,
|
|
||||||
# 'message': message,
|
|
||||||
# 'title': title,
|
|
||||||
# 'priority': priority,
|
|
||||||
# 'date': date.toIso8601String(),
|
|
||||||
|
|
||||||
Transaction.objects.values('reference').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
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(BaseModel):
|
class Transaction(BaseModel):
|
||||||
amount = models.PositiveBigIntegerField(_('amount'), blank=False, validators=[MinValueValidator(1)])
|
amount = models.PositiveBigIntegerField(_('amount'), blank=False, validators=[MinValueValidator(1)])
|
||||||
delay = models.PositiveIntegerField(default=0, blank=True) # number of days for delayed payments
|
delay = models.PositiveIntegerField(default=0, blank=True) # number of days for delayed payments
|
||||||
|
_payer_id = models.UUIDField(blank=True, null=True)
|
||||||
|
_payer_type = models.IntegerField(choices=TypeChoices.choices, blank=True, null=True)
|
||||||
|
_payee_id = models.UUIDField(null=True)
|
||||||
|
_payee_type = models.IntegerField(choices=TypeChoices.choices, blank=True, null=True)
|
||||||
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
related_name='transactions', null=True)
|
related_name='transactions', null=True)
|
||||||
payer_account = models.ForeignKey(Account, on_delete=models.PROTECT, null=True,
|
payer_wallet = models.ForeignKey(Wallet, on_delete=models.PROTECT, null=True,
|
||||||
related_name='payer_account_transactions',
|
related_name='payer_wallet_transactions',
|
||||||
db_index=True)
|
db_index=True)
|
||||||
payee_account = models.ForeignKey(Account, on_delete=models.PROTECT, null=True,
|
payee_wallet = models.ForeignKey(Wallet, on_delete=models.PROTECT, null=True,
|
||||||
related_name='payee_account_transactions',
|
related_name='payee_wallet_transactions',
|
||||||
db_index=True)
|
db_index=True)
|
||||||
details = models.JSONField(null=True, blank=True, default=dict)
|
details = models.JSONField(null=True, blank=True, default=dict)
|
||||||
|
|
||||||
reference = models.UUIDField(db_index=True, null=True)
|
|
||||||
|
|
||||||
# detail = {'payer_name', 'payee_name', 'description', 'application_name', 'payer_avatar_url', 'payee_avatar_url',
|
# detail = {'payer_name', 'payee_name', 'description', 'application_name', 'payer_avatar_url', 'payee_avatar_url',
|
||||||
# 'reference_id', 'application_details_url'}
|
# 'reference_id', 'application_details_url'}
|
||||||
|
|
||||||
|
|
@ -165,13 +110,6 @@ class Transaction(BaseModel):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-created_at']
|
ordering = ['-created_at']
|
||||||
indexes = [
|
|
||||||
GinIndex(
|
|
||||||
fields=['details'],
|
|
||||||
# hashlib.md5("wallet_transaction_details_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='wallet_transaction_de_a7ff79aa'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -181,117 +119,62 @@ class Transaction(BaseModel):
|
||||||
|
|
||||||
# unique_together = ('application', 'detail_id',)
|
# unique_together = ('application', 'detail_id',)
|
||||||
|
|
||||||
@property
|
|
||||||
def jalali_datetime(self):
|
|
||||||
def to_jalali(value):
|
|
||||||
if value:
|
|
||||||
return jdatetime.datetime.fromgregorian(date=value, locale='fa_IR').strftime("%Y/%m/%d-%H:%M:%S")
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return to_jalali(self.created_at)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def splited_amount(self):
|
|
||||||
def amount_split_3digit(value):
|
|
||||||
text = str(value)[::-1]
|
|
||||||
return ','.join([l[::-1] for l in [text[i:i + 3] for i in range(0, len(text), 3)][::-1]])
|
|
||||||
|
|
||||||
if self.amount:
|
|
||||||
amount_str = amount_split_3digit(str(self.amount))
|
|
||||||
return amount_str
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_payer(self):
|
def get_payer(self):
|
||||||
return self.payer_account.get_owner()
|
return self.payer_wallet.get_owner()
|
||||||
|
|
||||||
def get_payee(self):
|
def get_payee(self):
|
||||||
return self.payee_account.get_owner()
|
return self.payee_wallet.get_owner()
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payer_wallet(self):
|
def payer_wallet_category(self):
|
||||||
return self.payer_account and self.payer_account.wallet_id
|
return self.payer_wallet and self.payer_wallet.category_id
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payee_wallet(self):
|
def payee_wallet_category(self):
|
||||||
return self.payee_account and self.payee_account.wallet_id
|
return self.payee_wallet and self.payee_wallet.category_id
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payer(self):
|
def payer(self):
|
||||||
return self.payer_account.owner_uuid
|
return self.payer_wallet.owner_uuid
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payer_type(self):
|
def payer_type(self):
|
||||||
return self.payer_account.owner_type
|
return self.payer_wallet.owner_type
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payee(self):
|
def payee(self):
|
||||||
return self.payee_account.owner_uuid
|
return self.payee_wallet.owner_uuid
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def payee_type(self):
|
def payee_type(self):
|
||||||
return self.payee_account.owner_type
|
return self.payee_wallet.owner_type
|
||||||
|
|
||||||
def change_state(self, from_states: list | tuple, to_state, same_ok=False, raise_exception=True,
|
def change_state(self, from_state, to_state, error_message=None, **data):
|
||||||
error_message=None,
|
if self.state == from_state:
|
||||||
data: None | dict = None):
|
updated = Transaction.objects.filter(pk=self.pk, state=from_state).update(
|
||||||
data = data or {}
|
|
||||||
data['updated_at'] = timezone.now()
|
|
||||||
|
|
||||||
if same_ok and self.state == to_state:
|
|
||||||
return self
|
|
||||||
|
|
||||||
if self.state in from_states:
|
|
||||||
updated = self.__class__.objects.filter(pk=self.pk, state__in=from_states).update(
|
|
||||||
state=to_state,
|
state=to_state,
|
||||||
**data
|
**data
|
||||||
)
|
)
|
||||||
if updated: # double check in db level
|
if updated: # double check in db level
|
||||||
self.state = to_state
|
self.state = to_state
|
||||||
return self
|
|
||||||
|
|
||||||
if raise_exception:
|
|
||||||
raise Exception(error_message)
|
|
||||||
|
|
||||||
def withdraw_from_payer_balance(self):
|
|
||||||
logger.debug('start withdraw_from_payer_balance')
|
|
||||||
payer_is_allowed_negative_balance = self.payer_account.owner_type == TypeChoices.APPLICATION.value and str(
|
|
||||||
self.payer_account.owner_uuid) in settings.ALLOWED_NEGATIVE_BALANCE_APPLICATIONS
|
|
||||||
if self.amount > self.payer_account.balance and not payer_is_allowed_negative_balance:
|
|
||||||
logger.error('not enough balance')
|
|
||||||
raise UnprocessableEntity('not enough balance')
|
|
||||||
|
|
||||||
if not payer_is_allowed_negative_balance:
|
|
||||||
updated = Account.objects.filter(pk=self.payer_account.pk, balance__gte=self.amount).update(balance=F('balance') - self.amount)
|
|
||||||
else:
|
else:
|
||||||
updated = Account.objects.filter(pk=self.payer_account.pk).update(balance=F('balance') - self.amount)
|
raise APIException(error_message)
|
||||||
|
|
||||||
if updated:
|
|
||||||
self.payer_account.refresh_from_db()
|
|
||||||
logger.info(f'payer_account balance {self.payee_account} -> {self.payer_account.balance} - {self.amount}')
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
raise APIException(error_message)
|
||||||
|
|
||||||
|
######################## category
|
||||||
|
def category_withdraw_from_payer_balance(self):
|
||||||
|
payer_is_allowed_negative_balance = self.payer_wallet.owner_type == TypeChoices.APPLICATION.value and str(
|
||||||
|
self.payer_wallet.owner_uuid) in settings.ALLOWED_NEGATIVE_BALANCE_APPLICATIONS
|
||||||
|
if self.amount > self.payer_wallet.balance and not payer_is_allowed_negative_balance:
|
||||||
raise UnprocessableEntity('not enough balance')
|
raise UnprocessableEntity('not enough balance')
|
||||||
|
self.payer_wallet.balance = F('balance') - self.amount
|
||||||
|
self.payer_wallet.save(update_fields=['balance'])
|
||||||
|
|
||||||
def rollback_withdraw_from_payer_balance(self):
|
def category_deposit_to_payee_balance(self):
|
||||||
logger.debug('start rollback withdraw_from_payer_balance')
|
self.payee_wallet.balance = F('balance') + self.amount
|
||||||
logger.info(f'payer_account balance {self.payer_account} -> {self.amount}')
|
self.payee_wallet.save(update_fields=['balance'])
|
||||||
self.payer_account.balance = F('balance') + self.amount
|
|
||||||
self.payer_account.save(update_fields=['balance'])
|
|
||||||
|
|
||||||
def deposit_to_payee_balance(self):
|
|
||||||
logger.debug('start deposit_to_payee_balance')
|
|
||||||
logger.info(f'payee_account balance {self.payee_account} -> {self.payer_account.balance} + {self.amount}')
|
|
||||||
self.payee_account.balance = F('balance') + self.amount
|
|
||||||
self.payee_account.save(update_fields=['balance'])
|
|
||||||
|
|
||||||
def rollback_deposit_to_payee_balance(self):
|
|
||||||
logger.debug('start rollback deposit_to_payee_balance')
|
|
||||||
logger.info(f'payee_account balance {self.payee_account} -> {self.amount}')
|
|
||||||
self.payee_account.balance = F('balance') - self.amount
|
|
||||||
self.payee_account.save(update_fields=['balance'])
|
|
||||||
|
|
||||||
# TODO: refactor
|
|
||||||
def pay(self, payer_id=None, payer_type=None):
|
def pay(self, payer_id=None, payer_type=None):
|
||||||
if not self.payer_id and not (payer_id and payer_type):
|
if not self.payer_id and not (payer_id and payer_type):
|
||||||
raise APIException('payer identification error')
|
raise APIException('payer identification error')
|
||||||
|
|
@ -314,120 +197,79 @@ class Transaction(BaseModel):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if self.delay:
|
if self.delay:
|
||||||
self.change_state(
|
self.change_state(
|
||||||
[StateChoices.CREATED],
|
StateChoices.CREATED,
|
||||||
StateChoices.DELAYED,
|
StateChoices.DELAYED,
|
||||||
error_message='this transaction is not ready to pay',
|
error_message='this transaction is not ready to pay',
|
||||||
data={
|
payer_id=payer_id,
|
||||||
"payer_id": payer_id,
|
payer_type=payer_type
|
||||||
"payer_type": payer_type
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.change_state(
|
self.change_state(
|
||||||
[StateChoices.CREATED],
|
StateChoices.CREATED,
|
||||||
StateChoices.SUCCESS,
|
StateChoices.SUCCESS,
|
||||||
error_message='this transaction is not ready to pay',
|
error_message='this transaction is not ready to pay',
|
||||||
data={
|
payer_id=payer_id,
|
||||||
"payer_id": payer_id,
|
payer_type=payer_type
|
||||||
"payer_type": payer_type
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
self.deposit_to_payee_balance()
|
self.category_deposit_to_payee_balance()
|
||||||
|
|
||||||
self.withdraw_from_payer_balance()
|
self.category_withdraw_from_payer_balance()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# TODO: refactor
|
|
||||||
def receipt(self):
|
def receipt(self):
|
||||||
if not self.state == StateChoices.DELAYED:
|
if not self.state == StateChoices.DELAYED:
|
||||||
raise APIException('this transaction is not delayed')
|
raise APIException('this transaction is not delayed')
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.change_state(
|
self.change_state(
|
||||||
[StateChoices.DELAYED],
|
StateChoices.DELAYED,
|
||||||
StateChoices.SUCCESS,
|
StateChoices.SUCCESS,
|
||||||
error_message='this transaction is not delayed'
|
error_message='this transaction is not delayed'
|
||||||
)
|
)
|
||||||
self.deposit_to_payee_balance()
|
self.category_deposit_to_payee_balance()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def submit(self):
|
def category_submit(self):
|
||||||
if not self.payer_account:
|
if not self.payer_wallet:
|
||||||
raise APIException('payer identification error')
|
raise APIException('payer identification error')
|
||||||
|
|
||||||
if not self.payee_account:
|
if not self.payee_wallet:
|
||||||
raise APIException('payee identification error')
|
raise APIException('payee identification error')
|
||||||
|
|
||||||
if not self.payer_account.wallet.currency == self.payee_account.wallet.currency:
|
if not self.payer_wallet.category == self.payee_wallet.category:
|
||||||
self.change_state([self.state], StateChoices.FAILED)
|
self.change_state(from_state=self.state, to_state=StateChoices.FAILED)
|
||||||
logger.info(f'payer({self.payer_account.wallet.currency}) and payee({self.payee_account.wallet.currency}) account currency not equal')
|
raise APIException('payer and payee wallet currency not equal')
|
||||||
raise APIException('payer and payee account currency not equal')
|
|
||||||
|
|
||||||
if not self.state == StateChoices.CREATED:
|
if not self.state == StateChoices.CREATED:
|
||||||
logger.debug(f'this transaction is not ready to pay(UUID:{self.uuid}, state:{self.state})')
|
|
||||||
raise APIException('this transaction is not ready to pay')
|
raise APIException('this transaction is not ready to pay')
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.withdraw_from_payer_balance()
|
self.category_withdraw_from_payer_balance()
|
||||||
self.change_state(
|
self.change_state(
|
||||||
[StateChoices.CREATED],
|
StateChoices.CREATED,
|
||||||
StateChoices.PENDING,
|
StateChoices.PENDING,
|
||||||
error_message=_('this transaction is not ready to pay')
|
error_message=_('this transaction is not ready to pay')
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def verify(self):
|
def category_verify(self):
|
||||||
if not self.state == StateChoices.PENDING:
|
if not self.state == StateChoices.PENDING:
|
||||||
raise APIException('this transaction is not pending')
|
raise APIException('this transaction is not pending')
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.deposit_to_payee_balance()
|
self.category_deposit_to_payee_balance()
|
||||||
self.change_state(
|
self.change_state(
|
||||||
[StateChoices.PENDING],
|
StateChoices.PENDING,
|
||||||
StateChoices.SUCCESS,
|
StateChoices.SUCCESS,
|
||||||
error_message=_('this transaction is not pending')
|
error_message=_('this transaction is not pending')
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if self.state not in [StateChoices.PENDING, StateChoices.DELAYED, StateChoices.INCOMPLETE]:
|
|
||||||
raise APIException('this transaction is not pending')
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
self.rollback_withdraw_from_payer_balance()
|
|
||||||
self.change_state(
|
|
||||||
[StateChoices.PENDING, StateChoices.DELAYED, StateChoices.INCOMPLETE],
|
|
||||||
StateChoices.CANCELED,
|
|
||||||
error_message=_('TRANSACTION_STATE_NOT_SUITABLE_FOR_CANCELING')
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def rollback(self):
|
|
||||||
if self.state not in [StateChoices.PENDING, StateChoices.SUCCESS, StateChoices.DELAYED, StateChoices.INCOMPLETE]:
|
|
||||||
raise APIException('this transaction is not pending')
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
self.rollback_withdraw_from_payer_balance()
|
|
||||||
if self.state == StateChoices.SUCCESS:
|
|
||||||
self.rollback_deposit_to_payee_balance()
|
|
||||||
self.change_state(
|
|
||||||
[StateChoices.SUCCESS],
|
|
||||||
StateChoices.ROLLED_BACK,
|
|
||||||
error_message=_('TRANSACTION_STATE_NOT_SUITABLE_FOR_ROLLBACK')
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.change_state(
|
|
||||||
[StateChoices.PENDING, StateChoices.DELAYED, StateChoices.INCOMPLETE],
|
|
||||||
StateChoices.CANCELED,
|
|
||||||
error_message=_('TRANSACTION_STATE_NOT_SUITABLE_FOR_CANCEL')
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get_role(self, entity):
|
def get_role(self, entity):
|
||||||
if self.payer_account.owner_uuid == entity.uuid:
|
if self.payer_wallet.owner_uuid == entity.uuid:
|
||||||
return 1
|
return 1
|
||||||
elif self.payee_account.owner_uuid == entity.uuid:
|
elif self.payee_wallet.owner_uuid == entity.uuid:
|
||||||
return 2
|
return 2
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -439,27 +281,27 @@ class Transaction(BaseModel):
|
||||||
# detail = {'payer_name', 'payee_name', 'application_name', 'payer_avatar_url', 'payee_avatar_url', 'application_avatar_url'}
|
# detail = {'payer_name', 'payee_name', 'application_name', 'payer_avatar_url', 'payee_avatar_url', 'application_avatar_url'}
|
||||||
is_updated = False
|
is_updated = False
|
||||||
if not self.details.get('payer_name'):
|
if not self.details.get('payer_name'):
|
||||||
logger.info('update payer')
|
print('update payer')
|
||||||
if self.payer_account.owner_type == TypeChoices.USER:
|
if self.payer_wallet.owner_type == TypeChoices.USER:
|
||||||
payer_details: PublicUser = get_user_details(self.payer_account.owner_uuid)
|
payer_details: PublicUser = get_user_details(self.payer_wallet.owner_uuid)
|
||||||
self.details['payer_name'] = payer_details.first_name + payer_details.last_name
|
self.details['payer_name'] = payer_details.first_name + payer_details.last_name
|
||||||
self.details['payer_avatar_url'] = payer_details.avatar
|
self.details['payer_avatar_url'] = payer_details.avatar
|
||||||
else:
|
else:
|
||||||
payer_details = get_application_details(self.payer)
|
payer_details = get_application_details(self.payer_id)
|
||||||
self.details['payer_name'] = payer_details.name
|
self.details['payer_name'] = payer_details.name
|
||||||
# TODO: get avatar from details
|
# TODO: get avatar from details
|
||||||
is_updated = True
|
is_updated = True
|
||||||
logger.debug("payer_details:", payer_details)
|
print("payer_details:", payer_details)
|
||||||
logger.info("is_updated:", is_updated)
|
print("is_updated:", is_updated)
|
||||||
|
|
||||||
if not self.details.get('payee_name'):
|
if not self.details.get('payee_name'):
|
||||||
logger.info('update payee')
|
print('update payee')
|
||||||
if self.payee_account.owner_type == TypeChoices.USER:
|
if self.payee_wallet.owner_type == TypeChoices.USER:
|
||||||
payee_details: PublicUser = get_user_details(self.payee_account.owner_uuid)
|
payee_details: PublicUser = get_user_details(self.payee_wallet.owner_uuid)
|
||||||
self.details['payee_name'] = payee_details.first_name + payee_details.last_name
|
self.details['payee_name'] = payee_details.first_name + payee_details.last_name
|
||||||
self.details['payee_avatar_url'] = payee_details.avatar
|
self.details['payee_avatar_url'] = payee_details.avatar
|
||||||
else:
|
else:
|
||||||
payee_details = get_application_details(self.payee_account.owner_uuid)
|
payee_details = get_application_details(self.payee_id)
|
||||||
self.details['payee_name'] = payee_details.name
|
self.details['payee_name'] = payee_details.name
|
||||||
# TODO: get avatar from details
|
# TODO: get avatar from details
|
||||||
|
|
||||||
|
|
@ -483,29 +325,3 @@ class Transaction(BaseModel):
|
||||||
if is_updated:
|
if is_updated:
|
||||||
print('update details')
|
print('update details')
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
# TODO: bulk
|
|
||||||
# class PaymentRequest(BaseModel):
|
|
||||||
# state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
|
||||||
# application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
|
||||||
# related_name='transactions', null=True)
|
|
||||||
#
|
|
||||||
# payer_uuid = models.UUIDField(db_index=True, null=False)
|
|
||||||
# payer_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
|
||||||
#
|
|
||||||
# payee_uuid = models.UUIDField(db_index=True, null=False)
|
|
||||||
# payee_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
|
||||||
#
|
|
||||||
# details = models.JSONField(null=True, blank=True, default=dict)
|
|
||||||
#
|
|
||||||
# def submit(self):
|
|
||||||
# with transaction.atomic():
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
# def verify(self):
|
|
||||||
# with transaction.atomic():
|
|
||||||
# pass
|
|
||||||
#
|
|
||||||
# def rollback(self):
|
|
||||||
# with transaction.atomic():
|
|
||||||
# pass
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from rest_framework import serializers
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
from .constans import TypeChoices
|
from .constans import TypeChoices
|
||||||
from .models import Transaction, Account
|
from .models import Transaction, Wallet
|
||||||
from ..gooyal_oauth2.models import Application
|
from ..gooyal_oauth2.models import Application
|
||||||
from ..users.models import User
|
from ..users.models import User
|
||||||
|
|
||||||
|
|
@ -13,8 +13,8 @@ from ..users.models import User
|
||||||
class TransactionSerializer(serializers.ModelSerializer):
|
class TransactionSerializer(serializers.ModelSerializer):
|
||||||
payee = serializers.UUIDField(read_only=True)
|
payee = serializers.UUIDField(read_only=True)
|
||||||
payee_type = serializers.IntegerField(read_only=True)
|
payee_type = serializers.IntegerField(read_only=True)
|
||||||
# payee_wallet = serializers.UUIDField(source='payee_wallet', read_only=True)
|
payee_wallet = serializers.UUIDField(source='payee_wallet_category', read_only=True)
|
||||||
# payer_wallet = serializers.UUIDField(source='payer_wallet', read_only=True)
|
payer_wallet = serializers.UUIDField(source='payer_wallet_category', read_only=True)
|
||||||
role = serializers.SerializerMethodField()
|
role = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -74,7 +74,7 @@ class DepositSerializer(serializers.ModelSerializer):
|
||||||
with atomic():
|
with atomic():
|
||||||
try:
|
try:
|
||||||
transaction = Transaction.objects.create_transaction(**validated_data)
|
transaction = Transaction.objects.create_transaction(**validated_data)
|
||||||
transaction.submit()
|
transaction.category_submit()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
raise APIException('duplicate uuid')
|
raise APIException('duplicate uuid')
|
||||||
|
|
||||||
|
|
@ -104,14 +104,14 @@ class WithdrawSerializer(serializers.ModelSerializer):
|
||||||
with atomic():
|
with atomic():
|
||||||
try:
|
try:
|
||||||
transaction = Transaction.objects.create_transaction(**validated_data)
|
transaction = Transaction.objects.create_transaction(**validated_data)
|
||||||
transaction.submit()
|
transaction.category_submit()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
raise APIException('duplicate uuid')
|
raise APIException('duplicate uuid')
|
||||||
|
|
||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
|
|
||||||
class AccountBalanceSerializer(serializers.ModelSerializer):
|
class WalletBalanceSerializer(serializers.ModelSerializer):
|
||||||
instance_id = serializers.UUIDField(required=True)
|
instance_id = serializers.UUIDField(required=True)
|
||||||
instance_type = serializers.ChoiceField(choices=TypeChoices.choices, required=True)
|
instance_type = serializers.ChoiceField(choices=TypeChoices.choices, required=True)
|
||||||
|
|
||||||
|
|
@ -160,18 +160,18 @@ class TransferVerifySerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
#### Application
|
#### Application
|
||||||
class AccountSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Account
|
|
||||||
fields = ['balance', 'wallet']
|
|
||||||
|
|
||||||
|
class BalanceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Wallet
|
||||||
|
fields = ['balance', 'category']
|
||||||
|
|
||||||
class ApplicationDepositSerializer(serializers.ModelSerializer):
|
class ApplicationDepositSerializer(serializers.ModelSerializer):
|
||||||
uuid = serializers.UUIDField(required=False, validators=[])
|
uuid = serializers.UUIDField(required=False, validators=[])
|
||||||
payee = serializers.UUIDField(required=True)
|
payee = serializers.UUIDField(required=True)
|
||||||
payee_type = serializers.IntegerField(required=True)
|
payee_type = serializers.IntegerField(required=True)
|
||||||
payee_wallet = serializers.UUIDField(required=True)
|
payee_wallet = serializers.UUIDField(source='payee_wallet_category', required=False) # TODO: required True
|
||||||
payer_wallet = serializers.UUIDField(read_only=True)
|
payer_wallet = serializers.UUIDField(source='payer_wallet_category', read_only=True)
|
||||||
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
||||||
# TODO: details as fields
|
# TODO: details as fields
|
||||||
|
|
||||||
|
|
@ -193,15 +193,15 @@ class ApplicationDepositSerializer(serializers.ModelSerializer):
|
||||||
'details',
|
'details',
|
||||||
)
|
)
|
||||||
|
|
||||||
read_only_fields = ['updated_at', 'state', 'created_at', 'payer', 'payer_type', 'payer_wallet']
|
read_only_fields = ['updated_at', 'state', 'created_at', 'payer_id', 'payer_type', 'payer_wallet']
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
||||||
uuid = serializers.UUIDField(required=False, validators=[])
|
uuid = serializers.UUIDField(required=False, validators=[])
|
||||||
payer = serializers.UUIDField(required=True)
|
payer = serializers.UUIDField(required=True)
|
||||||
payer_type = serializers.IntegerField(required=True)
|
payer_type = serializers.IntegerField(required=True)
|
||||||
payee_wallet = serializers.UUIDField(read_only=True)
|
payer_wallet = serializers.UUIDField(source='payer_wallet_category',required=False) # TODO: required True
|
||||||
payer_wallet = serializers.UUIDField(required=True)
|
payee_wallet = serializers.UUIDField(source='payee_wallet_category', read_only=True) # TODO: required True
|
||||||
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
amount = serializers.IntegerField(required=True, validators=[MinValueValidator(1)])
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -223,20 +223,3 @@ class ApplicationWithdrawSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
read_only_fields = ['updated_at', 'state', 'created_at', 'payee', 'payee_type', 'payee_wallet']
|
read_only_fields = ['updated_at', 'state', 'created_at', 'payee', 'payee_type', 'payee_wallet']
|
||||||
|
|
||||||
|
|
||||||
class TransactionGroupedSerializer(serializers.Serializer):
|
|
||||||
reference = serializers.UUIDField(source='details__reference_id')
|
|
||||||
application = serializers.UUIDField()
|
|
||||||
total_amount = serializers.FloatField()
|
|
||||||
total_count = serializers.IntegerField()
|
|
||||||
state = serializers.IntegerField()
|
|
||||||
description = serializers.CharField()
|
|
||||||
created_at = serializers.DateTimeField()
|
|
||||||
role = serializers.IntegerField()
|
|
||||||
|
|
||||||
transactions = TransactionSerializer(many=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = ('reference', 'application', 'total_amount', 'total_count', 'transactions', 'state', 'description', 'created_at', 'role')
|
|
||||||
read_only_fields = ['reference', 'application', 'total_amount', 'total_count', 'transactions', 'state', 'description', 'created_at', 'role']
|
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,22 @@ from rest_framework.test import APITestCase, override_settings
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.wallet.constans import TypeChoices
|
from apps.wallet.constans import TypeChoices
|
||||||
from apps.wallet.models import Account
|
|
||||||
from apps.wallet.models import Wallet
|
from apps.wallet.models import Wallet
|
||||||
|
from apps.wallet.models import Category
|
||||||
|
|
||||||
AccessToken = get_access_token_model()
|
AccessToken = get_access_token_model()
|
||||||
Application = get_application_model()
|
Application = get_application_model()
|
||||||
|
|
||||||
application_uuid_hex = 'b14e8b86-8f4a-44d9-b29d-badceb470053'
|
|
||||||
|
|
||||||
class ApplicationApiFlowsTests(APITestCase):
|
class ApplicationApiFlowsTests(APITestCase):
|
||||||
client_id = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
client_id = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
||||||
application_uuid = uuid.UUID(application_uuid_hex)
|
application_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470053')
|
||||||
application_access_token = '14naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
application_access_token = '14naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||||
payer_access_token = '24naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
payer_access_token = '24naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||||
payer_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470051')
|
payer_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470051')
|
||||||
payee_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470052')
|
payee_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470052')
|
||||||
# wallet_rial_uuid = uuid.UUID('114e8b86-8f4a-44d9-b29d-badceb470051')
|
wallet_category1_uuid = uuid.UUID('114e8b86-8f4a-44d9-b29d-badceb470051')
|
||||||
wallet_rial_uuid = uuid.UUID('114e8b86-8f4a-44d9-b29d-badceb470051')
|
wallet_category2_uuid = uuid.UUID('214e8b86-8f4a-44d9-b29d-badceb470051')
|
||||||
wallet_reward_uuid = uuid.UUID('214e8b86-8f4a-44d9-b29d-badceb470051')
|
|
||||||
# application, _ = Application.objects.get_or_create(client_id=client_id, uuid=application_uuid)
|
# application, _ = Application.objects.get_or_create(client_id=client_id, uuid=application_uuid)
|
||||||
|
|
||||||
scopes = [
|
scopes = [
|
||||||
|
|
@ -35,7 +33,6 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
'wallet.deposit:submit',
|
'wallet.deposit:submit',
|
||||||
'wallet.wallet:get_balance',
|
'wallet.wallet:get_balance',
|
||||||
'wallet.application:get_user_balance',
|
'wallet.application:get_user_balance',
|
||||||
'wallet.user:transaction_list',
|
|
||||||
|
|
||||||
'wallet.application.withdraw:submit',
|
'wallet.application.withdraw:submit',
|
||||||
'wallet.application.withdraw:verify',
|
'wallet.application.withdraw:verify',
|
||||||
|
|
@ -44,9 +41,9 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.application_user, _ = User.objects.get_or_create(pk=self.application_uuid)
|
self.application_user, _ = User.objects.get_or_create(pk=self.application_uuid)
|
||||||
self.payer_user, _ = User.objects.get_or_create(pk=self.payer_uuid)
|
self.payer_user, _ = User.objects.get_or_create(pk=self.payer_uuid)
|
||||||
self.application_account, _ = Account.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300)
|
self.application_wallet, _ = Wallet.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300)
|
||||||
self.payee_account, _ = Account.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER)
|
self.payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER)
|
||||||
self.payer_account, _ = Account.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300)
|
self.payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300)
|
||||||
expire_datetime = timezone.now() + timedelta(seconds=3600)
|
expire_datetime = timezone.now() + timedelta(seconds=3600)
|
||||||
expire_datetime.isoformat()
|
expire_datetime.isoformat()
|
||||||
|
|
||||||
|
|
@ -78,14 +75,14 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
client_id=self.client_id,
|
client_id=self.client_id,
|
||||||
uuid=self.application_uuid,
|
uuid=self.application_uuid,
|
||||||
)
|
)
|
||||||
self.wallet_rial, _ = Wallet.objects.get_or_create(application=self.application, uuid=self.wallet_rial_uuid)
|
self.wallet_category1, _ = Category.objects.get_or_create(application=self.application, uuid=self.wallet_category1_uuid)
|
||||||
self.wallet_reward, _ = Wallet.objects.get_or_create(application=self.application, uuid=self.wallet_reward_uuid)
|
self.wallet_category2, _ = Category.objects.get_or_create(application=self.application, uuid=self.wallet_category2_uuid)
|
||||||
self.application_account1, _ = Account.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300, wallet=self.wallet_rial)
|
self.application_wallet1, _ = Wallet.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300, category=self.wallet_category1)
|
||||||
self.application_account2, _ = Account.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300, wallet=self.wallet_reward)
|
self.application_wallet2, _ = Wallet.objects.get_or_create(owner_uuid=self.application_uuid, owner_type=TypeChoices.APPLICATION, balance=300, category=self.wallet_category2)
|
||||||
self.payee_account1, _ = Account.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER, wallet=self.wallet_rial)
|
self.payee_wallet1, _ = Wallet.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER, category=self.wallet_category1)
|
||||||
self.payee_account2, _ = Account.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER, wallet=self.wallet_reward)
|
self.payee_wallet2, _ = Wallet.objects.get_or_create(owner_uuid=self.payee_uuid, owner_type=TypeChoices.USER, category=self.wallet_category2)
|
||||||
self.payer_account1, _ = Account.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300, wallet=self.wallet_rial)
|
self.payer_wallet1, _ = Wallet.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300, category=self.wallet_category1)
|
||||||
self.payer_account2, _ = Account.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300, wallet=self.wallet_reward)
|
self.payer_wallet2, _ = Wallet.objects.get_or_create(owner_uuid=self.payer_uuid, owner_type=TypeChoices.USER, balance=300, category=self.wallet_category2)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
@ -94,13 +91,9 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
return "Bearer {0}".format(token)
|
return "Bearer {0}".format(token)
|
||||||
|
|
||||||
def test_authentication_allow(self):
|
def test_authentication_allow(self):
|
||||||
auth = self._create_authorization_header(self.payer_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:user_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:user_wallet_account_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
print(response.json())
|
|
||||||
self.assertGreater(response.json()['count'], 1)
|
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
|
|
||||||
|
|
||||||
def test_authentication_expired_token(self):
|
def test_authentication_expired_token(self):
|
||||||
|
|
@ -117,7 +110,7 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
auth = self._create_authorization_header(expired_access_token.token)
|
auth = self._create_authorization_header(expired_access_token.token)
|
||||||
response = self.client.get(reverse('wallet:user_wallet_account_api'), HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:user_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
# def test_authentication_disallow(self):
|
# def test_authentication_disallow(self):
|
||||||
|
|
@ -129,313 +122,396 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payee_uuid,
|
'uuid': self.payee_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
self.assertEqual(to_wallet['balance'], 0)
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 100,
|
'amount': 100,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER,
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'payee_wallet': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
kwargs_category_uuid_payee_uuid = {
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid_payee_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
|
|
||||||
kwargs_verify = {
|
params = {
|
||||||
'uuid': json_response['uuid'],
|
'uuid': json_response['uuid'],
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_deposit_verify_api', kwargs=kwargs_verify), HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_deposit_verify_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.json()['state'], 5)
|
self.assertEqual(response.json()['state'], 5)
|
||||||
|
|
||||||
# Desired results
|
# Desired results
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid_payee_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
from_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 200)
|
||||||
self.assertEqual(from_wallet['balance'], 200)
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payee_uuid,
|
'uuid': self.payee_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 100)
|
||||||
self.assertEqual(to_wallet['balance'], 100)
|
|
||||||
|
def test_deposit_flow_category_success(self):
|
||||||
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
|
kwargs_category_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
|
kwargs_category_uuid_payee_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
|
'uuid': self.payee_uuid,
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=kwargs_category_uuid_payee_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
|
|
||||||
|
# Deposit process
|
||||||
|
data = {
|
||||||
|
'amount': 100,
|
||||||
|
'payee': self.payee_uuid,
|
||||||
|
'payee_type': TypeChoices.USER,
|
||||||
|
'payee_wallet': self.wallet_category1_uuid
|
||||||
|
}
|
||||||
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
json_response = response.json()
|
||||||
|
|
||||||
|
kwargs_category_uuid_transaction_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
|
'uuid': json_response['uuid'],
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_deposit_verify_api', kwargs=kwargs_category_uuid_transaction_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()['state'], 5)
|
||||||
|
|
||||||
|
# Desired results
|
||||||
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 200)
|
||||||
|
|
||||||
|
kwargs_category_uuid_payee_uuid = {
|
||||||
|
'category_uuid': self.wallet_category2_uuid,
|
||||||
|
'uuid': self.payee_uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=kwargs_category_uuid_payee_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
|
|
||||||
def test_deposit_flow_insufficient_balance(self):
|
def test_deposit_flow_insufficient_balance(self):
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
self.assertContains(response, 'balance')
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payee_uuid,
|
'uuid': self.payee_uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
kwargs_category_uuid_payee_uuid = {
|
||||||
self.assertGreater(response.json()['count'], 1)
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
to_wallet = response.json()['results'][0]
|
}
|
||||||
self.assertEqual(to_wallet['balance'], 0)
|
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 500,
|
'amount': 500,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER,
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'payee_wallet': self.wallet_category1_uuid
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid_payee_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet),
|
|
||||||
data=data, HTTP_AUTHORIZATION=auth)
|
|
||||||
self.assertEqual(response.status_code, 422)
|
self.assertEqual(response.status_code, 422)
|
||||||
|
|
||||||
@override_settings(ALLOWED_NEGATIVE_BALANCE_APPLICATIONS=[application_uuid_hex])
|
@override_settings(ALLOWED_NEGATIVE_BALANCE_APPLICATIONS=['b14e8b86-8f4a-44d9-b29d-badceb470053'])
|
||||||
def test_deposit_flow_skip_insufficient_balance_for_allowed_negative_balance_applications(self):
|
def test_deposit_flow_skip_insufficient_balance_for_allowed_negative_balance_applications(self):
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payee_uuid,
|
'uuid': self.payee_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
self.assertEqual(to_wallet['balance'], 0)
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 500,
|
'amount': 500,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER,
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'payee_wallet': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
kwargs_category_uuid_payee_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet),
|
|
||||||
data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid_payee_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
print(response.json())
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
def test_deposit_flow_zero_amount(self):
|
def test_deposit_flow_zero_amount(self):
|
||||||
# TODO: wallet
|
# TODO: category
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 0,
|
'amount': 0,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
response = self.client.post(reverse('wallet:application_deposit_submit_api'), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_deposit_flow_negative_amount(self):
|
def test_deposit_flow_negative_amount(self):
|
||||||
# TODO: wallet
|
# TODO: category
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': -100,
|
'amount': -100,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
response = self.client.post(reverse('wallet:application_deposit_submit_api'), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_deposit_flow_prevent_double_spending(self):
|
def test_deposit_flow_prevent_double_spending(self):
|
||||||
|
# TODO: check this test
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
auth = self._create_authorization_header(self.application_access_token.token)
|
||||||
|
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payee_uuid,
|
'uuid': self.payee_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 0)
|
||||||
self.assertEqual(to_wallet['balance'], 0)
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
|
'uuid': "b14e8b86-8f4a-44d9-b29d-badceb470053",
|
||||||
'amount': 200,
|
'amount': 200,
|
||||||
'payee': self.payee_uuid,
|
'payee': self.payee_uuid,
|
||||||
'payee_type': TypeChoices.USER,
|
'payee_type': TypeChoices.USER,
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'payee_wallet': self.wallet_category1_uuid,
|
||||||
|
|
||||||
}
|
}
|
||||||
kwargs_payer_wallet = {
|
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
kwargs_category_uuid_payee_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid_payee_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
print(response.json())
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_category_uuid_payee_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 422)
|
# self.assertEqual(response.status_code, 422)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
def test_withdraw_flow_success(self):
|
def test_withdraw_flow_success(self):
|
||||||
auth = self._create_authorization_header(self.payer_access_token.token)
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
|
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api'), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payer_uuid,
|
'uuid': self.payer_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
self.assertEqual(to_wallet['balance'], 300)
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 100,
|
'amount': 100,
|
||||||
'payer': self.payer_uuid,
|
'payer': self.payer_uuid,
|
||||||
'payer_type': TypeChoices.USER,
|
'payer_type': TypeChoices.USER,
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
'payer_wallet': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
kwargs_payee_wallet = {
|
kwargs_category_uuid = {
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_payee_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_category_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
|
|
||||||
kwargs_payee_wallet_uuid = {
|
params = {
|
||||||
'uuid': json_response['uuid'],
|
'uuid': json_response['uuid'],
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_withdraw_verify_api', kwargs=kwargs_payee_wallet_uuid), HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_withdraw_verify_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.json()['state'], 5)
|
self.assertEqual(response.json()['state'], 5)
|
||||||
|
|
||||||
# Desired results
|
# Desired results
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 400)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 400)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payer_uuid,
|
'uuid': self.payer_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 200)
|
||||||
self.assertEqual(to_wallet['balance'], 200)
|
|
||||||
|
def test_withdraw_flow_category_success(self):
|
||||||
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
|
|
||||||
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
|
kwargs_category_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
|
kwargs_category_uuid_payer_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
|
'uuid': self.payer_uuid,
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=kwargs_category_uuid_payer_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
|
|
||||||
|
# Deposit process
|
||||||
|
data = {
|
||||||
|
'amount': 100,
|
||||||
|
'payer': self.payer_uuid,
|
||||||
|
'payer_type': TypeChoices.USER,
|
||||||
|
'payer_wallet': self.wallet_category1_uuid,
|
||||||
|
}
|
||||||
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_category_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
json_response = response.json()
|
||||||
|
|
||||||
|
kwargs_category_uuid_transaction_uuid = {
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
|
'uuid': json_response['uuid'],
|
||||||
|
}
|
||||||
|
response = self.client.get(reverse('wallet:application_withdraw_verify_api', kwargs=kwargs_category_uuid_transaction_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()['state'], 5)
|
||||||
|
|
||||||
|
# Desired results
|
||||||
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 400)
|
||||||
|
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=kwargs_category_uuid_payer_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 200)
|
||||||
|
|
||||||
|
kwargs_category_uuid_payer_uuid = {
|
||||||
|
'category_uuid': self.wallet_category2_uuid,
|
||||||
|
'uuid': self.payer_uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=kwargs_category_uuid_payer_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
|
self.assertContains(response, 'balance')
|
||||||
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
|
|
||||||
def test_withdraw_flow_zero_amount(self):
|
def test_withdraw_flow_zero_amount(self):
|
||||||
auth = self._create_authorization_header(self.payer_access_token.token)
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
|
|
||||||
kwargs_payee_wallet = {
|
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 0,
|
'amount': 0,
|
||||||
'payer': self.payer_uuid,
|
'payer': self.payer_uuid,
|
||||||
'payer_type': TypeChoices.USER,
|
'payer_type': TypeChoices.USER,
|
||||||
'payer_account': self.wallet_rial_uuid,
|
'payer_wallet': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_payee_wallet),
|
kwargs_category_uuid = {
|
||||||
data=data, HTTP_AUTHORIZATION=auth)
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
|
}
|
||||||
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_category_uuid), data=data,
|
||||||
|
HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_withdraw_flow_negative_amount(self):
|
def test_withdraw_flow_negative_amount(self):
|
||||||
auth = self._create_authorization_header(self.payer_access_token.token)
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
kwargs_payee_wallet = {
|
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
|
||||||
}
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': -500,
|
'amount': -500,
|
||||||
'payer': self.payer_uuid,
|
'payer': self.payer_uuid,
|
||||||
'payer_type': TypeChoices.USER,
|
'payer_type': TypeChoices.USER
|
||||||
'payer_account': self.wallet_rial_uuid,
|
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_payee_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api'), data=data,
|
||||||
|
HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_withdraw_flow_prevent_double_spending(self):
|
def test_withdraw_flow_prevent_double_spending(self):
|
||||||
auth = self._create_authorization_header(self.payer_access_token.token)
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
|
|
||||||
kwargs_wallet_uuid = {
|
kwargs_category_uuid = {
|
||||||
'wallet_uuid': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
# Checking wallets initials values(application balance = 300 and payee balance = zero)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
response = self.client.get(reverse('wallet:application_wallet_balance_api', kwargs=kwargs_category_uuid), HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.get(reverse('wallet:application_account_list_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
self.assertContains(response, 'balance')
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
from_wallet = response.json()['results'][0]
|
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
|
||||||
params = {
|
params = {
|
||||||
'owner': self.payer_uuid,
|
'uuid': self.payer_uuid,
|
||||||
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
response = self.client.get(reverse('wallet:application_user_account_list_api', kwargs=params), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:application_user_wallet_balance_api', kwargs=params), HTTP_AUTHORIZATION=auth)
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertContains(response, 'balance')
|
||||||
to_wallet = response.json()['results'][0]
|
self.assertEqual(response.json()['balance'], 300)
|
||||||
self.assertEqual(to_wallet['balance'], 300)
|
|
||||||
|
|
||||||
# Deposit process
|
# Deposit process
|
||||||
data = {
|
data = {
|
||||||
'amount': 100,
|
'uuid': 'b14e8b86-8f4a-44d9-b29d-badceb470053',
|
||||||
|
'amount': 200,
|
||||||
'payer': self.payer_uuid,
|
'payer': self.payer_uuid,
|
||||||
'payer_type': TypeChoices.USER,
|
'payer_type': TypeChoices.USER,
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
'payer_wallet': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
kwargs_payee_wallet = {
|
kwargs_category_uuid = {
|
||||||
'payee_wallet': self.wallet_rial_uuid,
|
'category_uuid': self.wallet_category1_uuid,
|
||||||
}
|
}
|
||||||
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_category_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_payee_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_payee_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
response = self.client.post(reverse('wallet:application_withdraw_submit_api', kwargs=kwargs_category_uuid), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,63 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.routers import DefaultRouter
|
|
||||||
|
|
||||||
from .views import application as application_views
|
from .views import application as application_views
|
||||||
from .views import user as user_views
|
from .views import user as user_views
|
||||||
from .views.user import UserTransactionViewSet
|
|
||||||
|
|
||||||
app_name = "wallet"
|
app_name = "wallet"
|
||||||
router = DefaultRouter()
|
|
||||||
router.register('api/transactions', UserTransactionViewSet, basename='transactions')
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
|
||||||
|
|
||||||
user_urlpatterns = [
|
user_urlpatterns = [
|
||||||
path('transactions/', user_views.TransactionListView.as_view(), name='user_transaction_list_page'),
|
path('transactions/', user_views.TransactionListView.as_view(), name='user_transaction_list_page'),
|
||||||
path('transactions/<pk>', user_views.TransactionDetailView.as_view(), name='user_transaction_detail_page'),
|
path('transactions/<pk>', user_views.TransactionDetailView.as_view(), name='user_transaction_detail_page'),
|
||||||
path('api/wallets/', user_views.WalletAccountListAPIView.as_view(), name='user_wallet_account_api'),
|
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/user/<category_uuid>/balance', user_views.CategoryMyWalletBalanceView.as_view(), name='user_wallet_balance_api'),
|
||||||
|
path('api/user/<category_uuid>/transactions/', user_views.CategoryTransactionListView.as_view(), name='transaction_list_api'),
|
||||||
|
|
||||||
|
|
||||||
|
# path('api/transactions/<uuid>', TransactionDetail.as_view(), name='transaction_detail_api'),
|
||||||
|
# path('api/transactions/<uuid>/pay', TransactionPay.as_view(), name='transaction_pay_api'),
|
||||||
|
# path('api/transactions/<uuid>/receipt', TransactionReceipt.as_view(), name='transaction_receipt_api'),
|
||||||
|
# #
|
||||||
|
# path('api/deposit/', DepositSubmit.as_view(), name='deposit_submit_api'),
|
||||||
|
# path('api/deposit/<uuid>/verify', DepositVerify.as_view(), name='deposit_verify_api'),
|
||||||
|
# #
|
||||||
|
# path('api/withdraw/', WithdrawSubmit.as_view(), name='withdraw_submit_api'),
|
||||||
|
# path('api/withdraw/<uuid>/verify', WithdrawVerify.as_view(), name='withdraw_verify_api'),
|
||||||
|
|
||||||
|
# path('api/get_balance/', application_views.WalletBalanceView.as_view(), name='wallet_balance'),
|
||||||
|
|
||||||
# path('api/<wallet>/balance', user_views.WalletMyAccountBalanceView.as_view(), name='user_wallet_balance_api'),
|
|
||||||
# path('api/<wallet>/transactions/', user_views.WalletTransactionListView.as_view(), name='transaction_list_api'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
application_urlpatterns = [
|
application_urlpatterns = [
|
||||||
path('api/application/wallets/', application_views.AccountListAPIView.as_view(), name='application_account_list_api'),
|
path('api/application/balance', application_views.CategoryBalanceAPIView.as_view(), name='application_wallet_balance_api'),
|
||||||
path('api/application/user/wallets/<owner>/', application_views.UserAccountListAPIView.as_view(), name='application_user_account_list_api'),
|
|
||||||
|
|
||||||
path('api/application/transactions/', application_views.TransactionListAPIView.as_view(), name='application_transaction_list_api'),
|
path('api/application/user_balance/<uuid>', application_views.CategoryUserBalanceAPIView.as_view(), name='application_user_wallet_balance_api'),
|
||||||
path('api/application/transactions/<uuid>/', application_views.TransactionDetailAPIView.as_view(), name='application_transaction_detail_api'),
|
|
||||||
|
|
||||||
path('api/application/<payer_wallet>/deposit/', application_views.DepositSubmitAPIView.as_view(), name='application_deposit_submit_api'),
|
path('api/application/deposit/', application_views.CategoryDepositSubmitAPIView.as_view(), name='application_deposit_submit_api'),
|
||||||
path('api/application/<payer_wallet>/deposit/<uuid>/verify', application_views.DepositVerifyAPIView.as_view(), name='application_deposit_verify_api'),
|
path('api/application/deposit/<uuid>/verify', application_views.CategoryDepositVerifyAPIView.as_view(), name='application_deposit_verify_api'),
|
||||||
path('api/application/<payer_wallet>/deposit/<uuid>/rollback', application_views.DepositRollbackAPIView.as_view(), name='application_deposit_rollback_api'),
|
|
||||||
|
|
||||||
path('api/application/<payee_wallet>/withdraw/', application_views.WithdrawSubmitAPIView.as_view(), name='application_withdraw_submit_api'),
|
path('api/application/withdraw/', application_views.CategoryWithdrawSubmitAPIView.as_view(), name='application_withdraw_submit_api'),
|
||||||
path('api/application/<payee_wallet>/withdraw/<uuid>/verify', application_views.WithdrawVerifyAPIView.as_view(), name='application_withdraw_verify_api'),
|
path('api/application/withdraw/<uuid>/verify', application_views.CategoryWithdrawVerifyAPIView.as_view(), name='application_withdraw_verify_api'),
|
||||||
path('api/application/<payee_wallet>/withdraw/<uuid>/rollback', application_views.WithdrawRollbackAPIView.as_view(), name='application_withdraw_rollback_api'),
|
|
||||||
|
|
||||||
# path('api/application/wallets/<wallet>/', application_views.WalletTransactionListAPIView.as_view(), name='application_transaction_list_api'),
|
path('api/application/', application_views.CategoryTransactionListAPIView.as_view(), name='application_transaction_list_api'),
|
||||||
# path('api/application/wallets/<wallet>/<uuid>/', application_views.WalletTransactionDetailAPIView.as_view(), name='application_transaction_detail'),
|
path('api/application/<uuid>/', application_views.CategoryTransactionDetailAPIView.as_view(), name='application_transaction_detail'),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
path('api/application/<category_uuid>/balance', application_views.CategoryBalanceAPIView.as_view(), name='application_wallet_balance_api'),
|
||||||
|
|
||||||
|
path('api/application/<category_uuid>/user/<uuid>/balance', application_views.CategoryUserBalanceAPIView.as_view(), name='application_user_wallet_balance_api'),
|
||||||
|
|
||||||
|
path('api/application/<category_uuid>/deposit/', application_views.CategoryDepositSubmitAPIView.as_view(), name='application_deposit_submit_api'),
|
||||||
|
path('api/application/<category_uuid>/deposit/<uuid>/verify', application_views.CategoryDepositVerifyAPIView.as_view(), name='application_deposit_verify_api'),
|
||||||
|
|
||||||
|
path('api/application/<category_uuid>/withdraw/', application_views.CategoryWithdrawSubmitAPIView.as_view(), name='application_withdraw_submit_api'),
|
||||||
|
path('api/application/<category_uuid>/withdraw/<uuid>/verify', application_views.CategoryWithdrawVerifyAPIView.as_view(), name='application_withdraw_verify_api'),
|
||||||
|
|
||||||
|
path('api/application/<category_uuid>/', application_views.CategoryTransactionListAPIView.as_view(), name='application_transaction_list_api'),
|
||||||
|
path('api/application/<category_uuid>/<uuid>/', application_views.CategoryTransactionDetailAPIView.as_view(), name='application_transaction_detail'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += user_urlpatterns + application_urlpatterns
|
urlpatterns = user_urlpatterns + application_urlpatterns
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
||||||
from rest_framework import generics, permissions, mixins
|
from rest_framework import generics, permissions, mixins
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
import uuid
|
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.wallet.constans import TypeChoices, StateChoices
|
from apps.wallet.constans import TypeChoices, StateChoices
|
||||||
from apps.wallet.filters import AccountFilter, UserTransactionFilter
|
from apps.wallet.models import Transaction, Category, Wallet
|
||||||
from apps.wallet.models import Transaction, Wallet, Account
|
|
||||||
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
|
from apps.wallet.serializers import ApplicationDepositSerializer, ApplicationWithdrawSerializer, \
|
||||||
TransactionSerializer, AccountSerializer
|
TransactionSerializer, BalanceSerializer
|
||||||
from utils.exceptions import UnprocessableEntity
|
from utils.exceptions import UnprocessableEntity
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
@ -30,142 +27,63 @@ def get_application(request):
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
class AccountListAPIView(generics.ListAPIView):
|
def get_wallet_category(request: Request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceAPIView(generics.RetrieveAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting specified user or application balance.
|
API for getting specified user or application balance.
|
||||||
'''
|
'''
|
||||||
serializer_class = AccountSerializer
|
permission_classes = [TokenHasScope]
|
||||||
filter_backends = (DjangoFilterBackend,)
|
serializer_class = BalanceSerializer
|
||||||
filterset_class = AccountFilter
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
||||||
required_scopes = ['wallet.application:get_balance']
|
required_scopes = ['wallet.application:get_balance']
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
wallet_uuid = self.kwargs.get('wallet')
|
return get_application(self.request)
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
||||||
application = get_application(self.request)
|
|
||||||
account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
||||||
wallet=wallet)
|
|
||||||
return account
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
application = get_application(self.request)
|
|
||||||
return Account.objects.filter(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION).all()
|
|
||||||
|
|
||||||
|
|
||||||
class UserAccountListAPIView(generics.ListAPIView):
|
class UserBalanceAPIView(generics.RetrieveAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting specified user wallet.
|
API for getting specified user or application balance.
|
||||||
'''
|
'''
|
||||||
serializer_class = AccountSerializer
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
filterset_class = AccountFilter
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
serializer_class = BalanceSerializer
|
||||||
required_scopes = ['wallet.application:get_user_balance']
|
required_scopes = ['wallet.application:get_user_balance']
|
||||||
|
|
||||||
queryset = Account.objects.all()
|
def get_object(self):
|
||||||
|
user_uuid = self.kwargs.get('uuid')
|
||||||
|
user, created = User.objects.get_or_create(uuid=user_uuid)
|
||||||
|
# user = get_object_or_404(User.objects.all(), uuid=user_uuid)
|
||||||
|
|
||||||
def get_queryset(self):
|
return user
|
||||||
qs = super().get_queryset()
|
|
||||||
owner_uuid = self.kwargs.get('owner')
|
|
||||||
user, created = User.objects.get_or_create(uuid=self.kwargs.get('uuid'))
|
|
||||||
return qs.filter(owner_uuid=owner_uuid, owner_type=TypeChoices.USER).all()
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionDetailAPIView(generics.RetrieveAPIView):
|
class TransactionDetailAPIView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
serializer_class = TransactionSerializer
|
serializer_class = TransactionSerializer
|
||||||
lookup_field = 'uuid'
|
lookup_field = 'uuid'
|
||||||
filter_backends = (DjangoFilterBackend,)
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
||||||
required_scopes = ['wallet.application:get_transaction_detail']
|
required_scopes = ['wallet.application:get_transaction_detail']
|
||||||
queryset = Transaction.objects.all()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
|
||||||
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
return Transaction.objects.filter(Q(application=application) |
|
return Transaction.objects.filter(Q(application=application) |
|
||||||
Q(payer_account__owner_uuid=application.pk,
|
Q(payer_id=application.pk, payer_type=TypeChoices.APPLICATION.value) |
|
||||||
payer_account__owner_type=TypeChoices.APPLICATION,
|
Q(payee_id=application.pk, payee_type=TypeChoices.APPLICATION.value)
|
||||||
) |
|
|
||||||
Q(payee_account__owner_uuid=application.pk,
|
|
||||||
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
||||||
)
|
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
|
||||||
class TransactionListAPIView(generics.ListAPIView):
|
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_account__owner_uuid=application.pk,
|
|
||||||
payer_account__owner_type=TypeChoices.APPLICATION) |
|
|
||||||
Q(payee_account__owner_uuid=application.pk,
|
|
||||||
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
||||||
)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
class WalletTransactionDetailAPIView(generics.RetrieveAPIView):
|
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
serializer_class = TransactionSerializer
|
serializer_class = TransactionSerializer
|
||||||
lookup_field = 'uuid'
|
lookup_field = 'uuid'
|
||||||
required_scopes = ['wallet.application:get_transaction_detail']
|
required_scopes = ['wallet.application:get_transaction_list']
|
||||||
queryset = Transaction.objects.all()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
|
||||||
|
|
||||||
wallet_uuid = self.kwargs.get('wallet_uuid')
|
|
||||||
if wallet_uuid:
|
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
||||||
else:
|
|
||||||
wallet = None
|
|
||||||
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
return Transaction.objects.filter(Q(application=application) |
|
return Transaction.objects.filter(Q(application=application) |
|
||||||
Q(payer_account__owner_uuid=application.pk,
|
Q(payer_id=application.pk, payer_type=TypeChoices.APPLICATION.value) |
|
||||||
payer_account__owner_type=TypeChoices.APPLICATION,
|
Q(payee_id=application.pk, payee_type=TypeChoices.APPLICATION.value)
|
||||||
payer_account__wallet=wallet) |
|
|
||||||
Q(payee_account__owner_uuid=application.pk,
|
|
||||||
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
||||||
payee_account__wallet=wallet)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
|
|
||||||
class WalletTransactionListAPIView(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):
|
|
||||||
qs = super().get_queryset()
|
|
||||||
|
|
||||||
wallet_uuid = self.kwargs.get('wallet_uuid')
|
|
||||||
if wallet_uuid:
|
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
|
||||||
else:
|
|
||||||
wallet = None
|
|
||||||
|
|
||||||
application = get_application(self.request)
|
|
||||||
return qs.filter(Q(application=application) |
|
|
||||||
Q(payer_account__owner_uuid=application.pk,
|
|
||||||
payer_account__owner_type=TypeChoices.APPLICATION,
|
|
||||||
payer_account__wallet=wallet) |
|
|
||||||
Q(payee_account__owner_uuid=application.pk,
|
|
||||||
payee_account__owner_type=TypeChoices.APPLICATION,
|
|
||||||
payee_account__wallet=wallet)
|
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -177,66 +95,23 @@ class DepositSubmitAPIView(generics.CreateAPIView):
|
||||||
required_scopes = ['wallet.application.deposit:submit']
|
required_scopes = ['wallet.application.deposit:submit']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
payer_account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
return Transaction.objects.filter(application=application,
|
||||||
wallet=wallet)
|
payer_id=application.pk,
|
||||||
return Transaction.objects.filter(application=application, payer_account=payer_account).all()
|
payer_type=TypeChoices.APPLICATION.value).all()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# TODO: get_or_create instead of create if uuid given
|
# TODO: get_or_create instead of create if uuid given
|
||||||
# TODO: it is possible to merage create and submit in one step for improve performance
|
# TODO: it is possible to merage create and submit in one step for improve performance
|
||||||
logger.info("Starting perform_create for Transaction")
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
logger.debug(f"Application UUID: {application.uuid}")
|
instance = serializer.save(payer_id=application.pk,
|
||||||
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
payer_type=TypeChoices.APPLICATION.value,
|
||||||
if payer_wallet_uuid:
|
application=application)
|
||||||
payer_wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
try:
|
||||||
else:
|
instance.category_submit()
|
||||||
payer_wallet = None
|
except Exception as e:
|
||||||
payee_wallet_uuid = serializer.validated_data.pop('payee_wallet')
|
print(e)
|
||||||
payee_wallet_uuid = payee_wallet_uuid and str(payee_wallet_uuid)
|
APIException('Failed to submit deposit')
|
||||||
payee_wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
||||||
if not (payee_wallet and payee_wallet.currency == payer_wallet.currency):
|
|
||||||
raise UnprocessableEntity('payer and payee wallet currency not equal')
|
|
||||||
payee_id = serializer.validated_data.pop('payee')
|
|
||||||
payee_type = serializer.validated_data.pop('payee_type')
|
|
||||||
payer_account, _ = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
||||||
wallet=payer_wallet)
|
|
||||||
logger.debug(f"Payer account: {payer_account.uuid}")
|
|
||||||
payee_account, _ = Account.objects.get_or_create(owner_uuid=payee_id, owner_type=payee_type,
|
|
||||||
wallet=payee_wallet)
|
|
||||||
logger.debug(f"Payee account: {payee_account.uuid}")
|
|
||||||
uuid_param = serializer.validated_data.pop('uuid', uuid.uuid4()) # if uuid not provided generate new one to handle get_or_create
|
|
||||||
|
|
||||||
details = serializer.validated_data.get('details', {})
|
|
||||||
reference_id = details.get('reference_id', None)
|
|
||||||
logger.debug(f"Transaction UUID: {uuid_param}, Reference ID: {reference_id}")
|
|
||||||
transaction_params = {
|
|
||||||
'uuid': uuid_param,
|
|
||||||
'application': application,
|
|
||||||
'payer_account': payer_account,
|
|
||||||
'payee_account': payee_account,
|
|
||||||
'amount': serializer.validated_data.get('amount'),
|
|
||||||
}
|
|
||||||
# TODO: checking reference_id
|
|
||||||
# if reference_id:
|
|
||||||
# transaction_params['details__reference_id'] = reference_id
|
|
||||||
|
|
||||||
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
|
|
||||||
'details': details
|
|
||||||
})
|
|
||||||
serializer.instance = instance
|
|
||||||
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
|
|
||||||
if instance.state == StateChoices.PENDING:
|
|
||||||
return
|
|
||||||
|
|
||||||
if created or instance.state == StateChoices.CREATED:
|
|
||||||
instance.submit()
|
|
||||||
else:
|
|
||||||
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
|
|
||||||
raise UnprocessableEntity(_('Transaction was not in processable state!'))
|
|
||||||
|
|
||||||
|
|
||||||
class DepositVerifyAPIView(generics.RetrieveAPIView):
|
class DepositVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
|
@ -247,45 +122,15 @@ class DepositVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
return Transaction.objects.filter(application=application,
|
||||||
if payer_wallet_uuid:
|
payer_id=application.pk,
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
payer_type=TypeChoices.APPLICATION.value).all()
|
||||||
else:
|
|
||||||
wallet = None
|
|
||||||
account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
||||||
return Transaction.objects.filter(application=application, payer_account=account).all()
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance.state == StateChoices.PENDING:
|
if instance.state == StateChoices.PENDING:
|
||||||
instance.verify()
|
instance.category_verify()
|
||||||
logger.warning(f"Transaction was verified for {instance.uuid}: {instance.state}")
|
|
||||||
serializer = self.get_serializer(instance)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DepositRollbackAPIView(generics.RetrieveAPIView):
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
|
||||||
serializer_class = ApplicationDepositSerializer
|
|
||||||
lookup_field = 'uuid'
|
|
||||||
required_scopes = ['wallet.application.deposit:rollback']
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
application = get_application(self.request)
|
|
||||||
payer_wallet_uuid = self.kwargs.get('payer_wallet')
|
|
||||||
if payer_wallet_uuid:
|
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
||||||
else:
|
|
||||||
wallet = None
|
|
||||||
account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
|
||||||
return Transaction.objects.filter(application=application, payer_account=account).all()
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
|
|
||||||
instance.rollback()
|
|
||||||
logger.warning(f"Transaction was rolled back for {instance.uuid}: {instance.state}")
|
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
@ -297,72 +142,20 @@ class WithdrawSubmitAPIView(generics.CreateAPIView):
|
||||||
required_scopes = ['wallet.application.withdraw:submit']
|
required_scopes = ['wallet.application.withdraw:submit']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
payee_account, created = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
return Transaction.objects.filter(application=application,
|
||||||
wallet=wallet)
|
payee_id=application.pk,
|
||||||
return Transaction.objects.filter(application=application, payee_account=payee_account).all()
|
payee_type=TypeChoices.APPLICATION.value).all()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
# TODO: get_or_create instead of create if uuid given
|
# TODO: get_or_create instead of create if uuid given
|
||||||
# TODO: it is possible to merage create and submit in one step for improve performance
|
# TODO: it is possible to merage create and submit in one step for improve performance
|
||||||
logger.info("Starting perform_create for Transaction")
|
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
logger.debug(f"Application UUID: {application.uuid}")
|
instance = serializer.save(payee_id=application.pk,
|
||||||
|
payee_type=TypeChoices.APPLICATION.value,
|
||||||
|
application=application)
|
||||||
|
|
||||||
payer_wallet_uuid = serializer.validated_data.pop('payer_wallet')
|
instance.category_submit()
|
||||||
payer_wallet_uuid = payer_wallet_uuid and str(payer_wallet_uuid)
|
|
||||||
|
|
||||||
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
|
||||||
|
|
||||||
payee_wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
|
||||||
|
|
||||||
payer_wallet = get_object_or_404(Wallet.objects.all(), uuid=payer_wallet_uuid)
|
|
||||||
|
|
||||||
if not (payer_wallet and payer_wallet.currency == payee_wallet.currency):
|
|
||||||
raise UnprocessableEntity('payer and payee wallet currency not equal')
|
|
||||||
|
|
||||||
payer_id = serializer.validated_data.pop('payer')
|
|
||||||
payer_type = serializer.validated_data.pop('payer_type')
|
|
||||||
payee_account, _ = Account.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
|
||||||
wallet=payee_wallet)
|
|
||||||
logger.debug(f"Payee account: {payee_account.uuid}")
|
|
||||||
payer_account, _ = Account.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type,
|
|
||||||
wallet=payer_wallet)
|
|
||||||
logger.debug(f"Payer account: {payer_account.uuid}")
|
|
||||||
|
|
||||||
uuid_param = serializer.validated_data.pop('uuid', uuid.uuid4()) # if uuid not provided generate new one to handle get_or_create
|
|
||||||
|
|
||||||
details = serializer.validated_data.get('details', {})
|
|
||||||
reference_id = details.get('reference_id', None)
|
|
||||||
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
|
|
||||||
transaction_params = {
|
|
||||||
'uuid': uuid_param,
|
|
||||||
'application': application,
|
|
||||||
'payer_account': payer_account,
|
|
||||||
'payee_account': payee_account,
|
|
||||||
'amount': serializer.validated_data.get('amount'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: checking reference_id
|
|
||||||
# if reference_id:
|
|
||||||
# transaction_params['details__reference_id'] = reference_id
|
|
||||||
|
|
||||||
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
|
|
||||||
'details': details
|
|
||||||
})
|
|
||||||
|
|
||||||
serializer.instance = instance
|
|
||||||
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
|
|
||||||
if instance.state == StateChoices.PENDING:
|
|
||||||
return
|
|
||||||
|
|
||||||
if created or instance.state == StateChoices.CREATED:
|
|
||||||
instance.submit()
|
|
||||||
else:
|
|
||||||
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
|
|
||||||
raise UnprocessableEntity(_('Transaction was not in processable state!'))
|
|
||||||
|
|
||||||
|
|
||||||
class WithdrawVerifyAPIView(generics.RetrieveAPIView):
|
class WithdrawVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
|
@ -373,37 +166,311 @@ class WithdrawVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
return Transaction.objects.filter(application=application,
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
payee_id=application.pk,
|
||||||
payee_account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
payee_type=TypeChoices.APPLICATION.value).all()
|
||||||
return Transaction.objects.filter(application=application, payee_account=payee_account).all()
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance.state == StateChoices.PENDING:
|
if instance.state == StateChoices.PENDING:
|
||||||
instance.verify()
|
instance.category_verify()
|
||||||
|
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class WithdrawRollbackAPIView(generics.RetrieveAPIView):
|
class CategoryBalanceAPIView(generics.RetrieveAPIView):
|
||||||
|
'''
|
||||||
|
API for getting specified user or application balance.
|
||||||
|
'''
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
serializer_class = ApplicationWithdrawSerializer
|
serializer_class = BalanceSerializer
|
||||||
|
required_scopes = ['wallet.application:get_balance']
|
||||||
|
|
||||||
|
def get_object(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
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryUserBalanceAPIView(generics.RetrieveAPIView):
|
||||||
|
'''
|
||||||
|
API for getting specified user or application balance.
|
||||||
|
'''
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
serializer_class = BalanceSerializer
|
||||||
|
required_scopes = ['wallet.application:get_user_balance']
|
||||||
|
|
||||||
|
def get_object(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
|
||||||
|
|
||||||
|
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 CategoryTransactionDetailAPIView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
|
serializer_class = TransactionSerializer
|
||||||
lookup_field = 'uuid'
|
lookup_field = 'uuid'
|
||||||
required_scopes = ['wallet.application.withdraw:rollback']
|
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) |
|
||||||
|
Q(payee_wallet__owner_uuid=application.pk,
|
||||||
|
payee_wallet__owner_type=TypeChoices.APPLICATION)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryTransactionListAPIView(generics.ListAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
|
serializer_class = TransactionSerializer
|
||||||
|
lookup_field = 'uuid'
|
||||||
|
required_scopes = ['wallet.application:get_transaction_list']
|
||||||
|
|
||||||
|
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) |
|
||||||
|
Q(payee_wallet__owner_uuid=application.pk,
|
||||||
|
payee_wallet__owner_type=TypeChoices.APPLICATION)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
|
||||||
|
# from application to another entity
|
||||||
|
# TODO: better name
|
||||||
|
class CategoryDepositSubmitAPIView(generics.CreateAPIView):
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
serializer_class = ApplicationDepositSerializer
|
||||||
|
required_scopes = ['wallet.application.deposit:submit']
|
||||||
|
|
||||||
|
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 Transaction.objects.filter(application=application, payer_wallet=wallet).all()
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
# TODO: get_or_create instead of create if uuid given
|
||||||
|
# TODO: it is possible to merage create and submit in one step for improve performance
|
||||||
|
logger.info("Starting perform_create for Transaction")
|
||||||
|
application = get_application(self.request)
|
||||||
|
logger.debug(f"Application UUID: {application.uuid}")
|
||||||
|
|
||||||
|
payee_wallet_category_uuid = serializer.validated_data.pop('payee_wallet_category')
|
||||||
|
payee_wallet_category_uuid = payee_wallet_category_uuid and str(payee_wallet_category_uuid)
|
||||||
|
|
||||||
|
payer_wallet_category_uuid = self.kwargs.get('category_uuid')
|
||||||
|
|
||||||
|
if payer_wallet_category_uuid:
|
||||||
|
payer_wallet_category = get_object_or_404(Category.objects.all(), uuid=payer_wallet_category_uuid)
|
||||||
|
else:
|
||||||
|
payer_wallet_category = None
|
||||||
|
|
||||||
|
payee_wallet_category = get_object_or_404(Category.objects.all(), uuid=payee_wallet_category_uuid)
|
||||||
|
|
||||||
|
if not (payee_wallet_category and payee_wallet_category.currency == payer_wallet_category.currency):
|
||||||
|
raise UnprocessableEntity('payer and payee wallet currency not equal')
|
||||||
|
|
||||||
|
payee_id = serializer.validated_data.pop('payee')
|
||||||
|
payee_type = serializer.validated_data.pop('payee_type')
|
||||||
|
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
||||||
|
category=payer_wallet_category)
|
||||||
|
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
|
||||||
|
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payee_id, owner_type=payee_type,
|
||||||
|
category=payee_wallet_category)
|
||||||
|
logger.debug(f"Payee wallet: {payee_wallet.uuid}")
|
||||||
|
uuid = serializer.validated_data.pop('uuid', None)
|
||||||
|
|
||||||
|
details = serializer.validated_data.get('details', {})
|
||||||
|
reference_id = details.get('reference_id', None)
|
||||||
|
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
|
||||||
|
transaction_params = {
|
||||||
|
'application': application,
|
||||||
|
'payer_wallet': payer_wallet,
|
||||||
|
'payee_wallet': payee_wallet,
|
||||||
|
'amount': serializer.validated_data.get('amount'),
|
||||||
|
}
|
||||||
|
if uuid:
|
||||||
|
transaction_params['uuid'] = uuid
|
||||||
|
|
||||||
|
if reference_id:
|
||||||
|
transaction_params['details__reference_id'] = reference_id
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# logger.error('Missing UUID and Reference ID')
|
||||||
|
# raise UnprocessableEntity('Missing UUID or reference_id')
|
||||||
|
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
|
||||||
|
'details': details
|
||||||
|
})
|
||||||
|
serializer.instance = instance
|
||||||
|
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
|
||||||
|
if instance.state == StateChoices.PENDING:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: status code 200 if not created before
|
||||||
|
if created or instance.state == StateChoices.CREATED:
|
||||||
|
instance.category_submit()
|
||||||
|
else:
|
||||||
|
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
|
||||||
|
raise UnprocessableEntity(_('Transaction was not in processable state!'))
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryDepositVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
serializer_class = ApplicationDepositSerializer
|
||||||
|
lookup_field = 'uuid'
|
||||||
|
required_scopes = ['wallet.application.deposit:verify']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
application = get_application(self.request)
|
application = get_application(self.request)
|
||||||
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
category_uuid = self.kwargs.get('category_uuid')
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
if category_uuid:
|
||||||
payee_account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
|
||||||
return Transaction.objects.filter(application=application, payee_account=payee_account).all()
|
else:
|
||||||
|
category = None
|
||||||
|
wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, category=category)
|
||||||
|
return Transaction.objects.filter(application=application, payer_wallet=wallet).all()
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
|
if instance.state == StateChoices.PENDING:
|
||||||
instance.rollback()
|
instance.category_verify()
|
||||||
logger.warning(f"Transaction was rolled back for {instance.uuid}: {instance.state}")
|
logger.warning(f"Transaction was retrieved for {instance.uuid}: {instance.state}")
|
||||||
|
serializer = self.get_serializer(instance)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
# from another entity to application
|
||||||
|
class CategoryWithdrawSubmitAPIView(generics.CreateAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
|
serializer_class = ApplicationWithdrawSerializer
|
||||||
|
required_scopes = ['wallet.application.withdraw:submit']
|
||||||
|
|
||||||
|
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 Transaction.objects.filter(application=application, wallet=wallet).all()
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
# TODO: get_or_create instead of create if uuid given
|
||||||
|
# TODO: it is possible to merage create and submit in one step for improve performance
|
||||||
|
logger.info("Starting perform_create for Transaction")
|
||||||
|
application = get_application(self.request)
|
||||||
|
logger.debug(f"Application UUID: {application.uuid}")
|
||||||
|
|
||||||
|
payer_wallet_category_uuid = serializer.validated_data.pop('payer_wallet_category')
|
||||||
|
payer_wallet_category_uuid = payer_wallet_category_uuid and str(payer_wallet_category_uuid)
|
||||||
|
|
||||||
|
payee_wallet_category_uuid = self.kwargs.get('category_uuid')
|
||||||
|
|
||||||
|
if payee_wallet_category_uuid:
|
||||||
|
payee_wallet_category = get_object_or_404(Category.objects.all(), uuid=payee_wallet_category_uuid)
|
||||||
|
else:
|
||||||
|
payee_wallet_category = None
|
||||||
|
|
||||||
|
payer_wallet_category = get_object_or_404(Category.objects.all(), uuid=payer_wallet_category_uuid)
|
||||||
|
|
||||||
|
if not (payer_wallet_category and payer_wallet_category.currency == payee_wallet_category.currency):
|
||||||
|
raise UnprocessableEntity('payer and payee wallet currency not equal')
|
||||||
|
|
||||||
|
payer_id = serializer.validated_data.pop('payer')
|
||||||
|
payer_type = serializer.validated_data.pop('payer_type')
|
||||||
|
payee_wallet, _ = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION,
|
||||||
|
category=payee_wallet_category)
|
||||||
|
logger.debug(f"Payee wallet: {payee_wallet.uuid}")
|
||||||
|
payer_wallet, _ = Wallet.objects.get_or_create(owner_uuid=payer_id, owner_type=payer_type,
|
||||||
|
category=payer_wallet_category)
|
||||||
|
logger.debug(f"Payer wallet: {payer_wallet.uuid}")
|
||||||
|
|
||||||
|
uuid = serializer.validated_data.pop('uuid', None)
|
||||||
|
|
||||||
|
details = serializer.validated_data.get('details', {})
|
||||||
|
reference_id = details.get('reference_id', None)
|
||||||
|
logger.debug(f"Transaction UUID: {uuid}, Reference ID: {reference_id}")
|
||||||
|
transaction_params = {
|
||||||
|
'application': application,
|
||||||
|
'payer_wallet': payer_wallet,
|
||||||
|
'payee_wallet': payee_wallet,
|
||||||
|
'amount': serializer.validated_data.get('amount'),
|
||||||
|
}
|
||||||
|
if uuid:
|
||||||
|
transaction_params['uuid'] = uuid
|
||||||
|
|
||||||
|
if reference_id:
|
||||||
|
transaction_params['details__reference_id'] = reference_id
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# logger.error('Missing UUID and Reference ID')
|
||||||
|
# raise UnprocessableEntity('Missing UUID or reference_id')
|
||||||
|
instance, created = Transaction.objects.get_or_create(**transaction_params, defaults={
|
||||||
|
'details': details
|
||||||
|
})
|
||||||
|
|
||||||
|
serializer.instance = instance
|
||||||
|
logger.info(f"Transaction {'created' if created else 'retrieved'} successfully: {instance.uuid}")
|
||||||
|
if instance.state == StateChoices.PENDING:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: status code 200 if not created before
|
||||||
|
if created or instance.state == StateChoices.CREATED:
|
||||||
|
instance.category_submit()
|
||||||
|
else:
|
||||||
|
logger.warning(f"Transaction was not in processable state! for {instance.uuid}: {instance.state}")
|
||||||
|
raise UnprocessableEntity(_('Transaction was not in processable state!'))
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryWithdrawVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
|
serializer_class = ApplicationWithdrawSerializer
|
||||||
|
lookup_field = 'uuid'
|
||||||
|
required_scopes = ['wallet.application.withdraw:verify']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
application = get_application(self.request)
|
||||||
|
category_uuid = self.kwargs.get('category_uuid')
|
||||||
|
if category_uuid:
|
||||||
|
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
|
||||||
|
else:
|
||||||
|
category = None
|
||||||
|
wallet, created = Wallet.objects.get_or_create(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, category=category)
|
||||||
|
return Transaction.objects.filter(application=application, payee_wallet=wallet).all()
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
if instance.state == StateChoices.PENDING:
|
||||||
|
instance.category_verify()
|
||||||
|
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,23 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import DetailView, ListView, UpdateView
|
from django.views.generic import DetailView, ListView, UpdateView
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from drf_spectacular.utils import extend_schema
|
|
||||||
|
|
||||||
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
||||||
from rest_framework import generics, permissions, mixins
|
from rest_framework import generics, permissions
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
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
|
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
from apps.wallet.models import Transaction, Wallet, Account
|
from apps.wallet.models import Transaction, Category, Wallet
|
||||||
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
|
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
|
||||||
AccountSerializer, TransactionGroupedSerializer
|
BalanceSerializer
|
||||||
from ..constans import StateChoices, TypeChoices
|
from ..constans import StateChoices, TypeChoices
|
||||||
from ..filters import AccountFilter, UserTransactionFilter
|
from ..filters import UserTransactionFilter
|
||||||
from ..forms import TransactionActionForm
|
from ..forms import TransactionActionForm
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
@ -39,12 +31,12 @@ def get_application(request):
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
class UserAccountBalanceView(generics.RetrieveAPIView):
|
class MyWalletBalanceView(generics.RetrieveAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting authenticated user balance.
|
API for getting authenticated user balance.
|
||||||
'''
|
'''
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
serializer_class = AccountSerializer
|
serializer_class = BalanceSerializer
|
||||||
required_scopes = ['wallet.wallet:get_balance']
|
required_scopes = ['wallet.wallet:get_balance']
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
@ -74,6 +66,7 @@ class TransactionList(generics.ListAPIView):
|
||||||
user = self.request.user
|
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()
|
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:
|
# TODO:
|
||||||
# def perform_create(self, serializer):
|
# def perform_create(self, serializer):
|
||||||
# payer = self.request.user
|
# payer = self.request.user
|
||||||
|
|
@ -140,7 +133,6 @@ class DepositSubmit(generics.CreateAPIView):
|
||||||
serializer.save(payer=payer, application=get_application(self.request))
|
serializer.save(payer=payer, application=get_application(self.request))
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactore
|
|
||||||
class DepositVerify(generics.RetrieveAPIView):
|
class DepositVerify(generics.RetrieveAPIView):
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
serializer_class = DepositSerializer
|
serializer_class = DepositSerializer
|
||||||
|
|
@ -174,7 +166,6 @@ class WithdrawSubmit(generics.CreateAPIView):
|
||||||
serializer.save(payee=payee, application=get_application(self.request))
|
serializer.save(payee=payee, application=get_application(self.request))
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactor
|
|
||||||
class WithdrawVerify(generics.RetrieveAPIView):
|
class WithdrawVerify(generics.RetrieveAPIView):
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
serializer_class = WithdrawSerializer
|
serializer_class = WithdrawSerializer
|
||||||
|
|
@ -234,60 +225,30 @@ class TransactionDetailView(UpdateView):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class WalletAccountListAPIView(generics.ListAPIView):
|
|
||||||
|
class CategoryMyWalletBalanceView(generics.RetrieveAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting authenticated user balance.
|
API for getting authenticated user balance.
|
||||||
'''
|
'''
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
serializer_class = AccountSerializer
|
serializer_class = BalanceSerializer
|
||||||
filter_backends = (DjangoFilterBackend,)
|
required_scopes = ['wallet.wallet:get_balance']
|
||||||
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):
|
def get_object(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
wallet_uuid = self.kwargs.get('wallet')
|
category_uuid = self.kwargs.get('category_uuid')
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
if category_uuid:
|
||||||
wallet, created = Account.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER, wallet=wallet)
|
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
|
||||||
|
else:
|
||||||
|
category = None
|
||||||
|
|
||||||
|
wallet, created = Wallet.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER,
|
||||||
|
category=category)
|
||||||
return wallet
|
return wallet
|
||||||
|
|
||||||
|
|
||||||
# this is application create invoice view
|
# this is application create invoice view
|
||||||
class TransactionListAPIView(generics.ListAPIView):
|
class CategoryTransactionListView(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
|
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)
|
POST: API for creating transaction for transferring funds from authenticated user to application owner. (deprecated)
|
||||||
|
|
@ -307,66 +268,15 @@ class WalletTransactionListView(generics.ListAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
wallet_uuid = self.kwargs.get('wallet')
|
category_uuid = self.kwargs.get('category_uuid')
|
||||||
wallet = get_object_or_404(Wallet.objects.all(), uuid=wallet_uuid)
|
if category_uuid:
|
||||||
account, created = Account.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER, wallet=wallet)
|
category = get_object_or_404(Category.objects.all(), uuid=category_uuid)
|
||||||
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
|
else:
|
||||||
|
category = None
|
||||||
|
|
||||||
|
wallet, created = Wallet.objects.get_or_create(owner_uuid=user.uuid, owner_type=TypeChoices.USER,
|
||||||
|
category=category)
|
||||||
|
|
||||||
class UserTransactionViewSet(mixins.ListModelMixin,
|
return Transaction.objects.filter(Q(payee_wallet=wallet) |
|
||||||
# mixins.CreateModelMixin,
|
Q(payer_wallet=wallet)).all()
|
||||||
# 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()
|
|
||||||
|
|
||||||
@extend_schema(responses=TransactionGroupedSerializer(many=True))
|
|
||||||
@action(detail=False, methods=["GET"], serializer_class=TransactionGroupedSerializer)
|
|
||||||
def grouped(self, request, *args, **kwargs):
|
|
||||||
|
|
||||||
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'),
|
|
||||||
transactions=ArrayAgg('uuid'),
|
|
||||||
)
|
|
||||||
|
|
||||||
page = list(self.paginate_queryset(grouped_transaction_list))
|
|
||||||
|
|
||||||
transaction_uuid_list = []
|
|
||||||
|
|
||||||
for grouped_transaction in page:
|
|
||||||
transaction_uuid_list += grouped_transaction['transactions']
|
|
||||||
|
|
||||||
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:
|
|
||||||
grouped_transaction: dict
|
|
||||||
for i, transaction in enumerate(grouped_transaction['transactions']):
|
|
||||||
grouped_transaction.setdefault('description', transaction_dict[transaction].details.get('description', 'no-description'))
|
|
||||||
grouped_transaction.setdefault('state', transaction_dict[transaction].state)
|
|
||||||
grouped_transaction.setdefault('created_at', transaction_dict[transaction].created_at)
|
|
||||||
grouped_transaction.setdefault('role', transaction_dict[transaction].get_role(self.request.user))
|
|
||||||
grouped_transaction['transactions'][i] = transaction_dict[transaction]
|
|
||||||
|
|
||||||
serializer = self.get_serializer(page, many=True)
|
|
||||||
return self.get_paginated_response(serializer.data)
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.user_inquiry import UserInquiry
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": "/users/api/inquiry/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[UserInquiry]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = UserInquiry.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[UserInquiry]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Response[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[UserInquiry]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Optional[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
UserInquiry
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Response[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[UserInquiry]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Optional[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
UserInquiry
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.account import Account
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
uuid: UUID,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/users/api/users/{uuid}/details",
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Account]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Account.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Account]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
uuid=uuid,
|
|
||||||
client=client,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
uuid=uuid,
|
|
||||||
client=client,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
from .account import Account
|
from .account import Account
|
||||||
from .application import Application
|
from .application import Application
|
||||||
from .blank_enum import BlankEnum
|
|
||||||
from .change_password import ChangePassword
|
from .change_password import ChangePassword
|
||||||
from .education_degree_enum import EducationDegreeEnum
|
|
||||||
from .gender_enum import GenderEnum
|
|
||||||
from .paginated_application_list import PaginatedApplicationList
|
from .paginated_application_list import PaginatedApplicationList
|
||||||
from .paginated_public_user_list import PaginatedPublicUserList
|
from .paginated_public_user_list import PaginatedPublicUserList
|
||||||
from .patched_account import PatchedAccount
|
from .patched_account import PatchedAccount
|
||||||
|
|
@ -13,15 +10,11 @@ from .patched_change_password import PatchedChangePassword
|
||||||
from .public_user import PublicUser
|
from .public_user import PublicUser
|
||||||
from .request_otp import RequestOTP
|
from .request_otp import RequestOTP
|
||||||
from .request_ott import RequestOTT
|
from .request_ott import RequestOTT
|
||||||
from .user_inquiry import UserInquiry
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"Account",
|
"Account",
|
||||||
"Application",
|
"Application",
|
||||||
"BlankEnum",
|
|
||||||
"ChangePassword",
|
"ChangePassword",
|
||||||
"EducationDegreeEnum",
|
|
||||||
"GenderEnum",
|
|
||||||
"PaginatedApplicationList",
|
"PaginatedApplicationList",
|
||||||
"PaginatedPublicUserList",
|
"PaginatedPublicUserList",
|
||||||
"PatchedAccount",
|
"PatchedAccount",
|
||||||
|
|
@ -29,5 +22,4 @@ __all__ = (
|
||||||
"PublicUser",
|
"PublicUser",
|
||||||
"RequestOTP",
|
"RequestOTP",
|
||||||
"RequestOTT",
|
"RequestOTT",
|
||||||
"UserInquiry",
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
from typing import Any, TypeVar, Union, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
from attrs import field as _attrs_field
|
from attrs import field as _attrs_field
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
from ..models.blank_enum import BlankEnum
|
|
||||||
from ..models.education_degree_enum import EducationDegreeEnum
|
|
||||||
from ..models.gender_enum import GenderEnum
|
|
||||||
from ..types import UNSET, Unset
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
T = TypeVar("T", bound="Account")
|
T = TypeVar("T", bound="Account")
|
||||||
|
|
@ -30,10 +24,6 @@ class Account:
|
||||||
last_name (Union[Unset, str]):
|
last_name (Union[Unset, str]):
|
||||||
name (Union[Unset, str]):
|
name (Union[Unset, str]):
|
||||||
avatar (Union[None, Unset, str]):
|
avatar (Union[None, Unset, str]):
|
||||||
gender (Union[BlankEnum, GenderEnum, None, Unset]):
|
|
||||||
education_degree (Union[BlankEnum, EducationDegreeEnum, None, Unset]):
|
|
||||||
education_field (Union[None, Unset, str]):
|
|
||||||
birthdate (Union[None, Unset, datetime.date]):
|
|
||||||
iban (Union[None, Unset, str]):
|
iban (Union[None, Unset, str]):
|
||||||
province (Union[None, Unset, int]):
|
province (Union[None, Unset, int]):
|
||||||
city (Union[None, Unset, int]):
|
city (Union[None, Unset, int]):
|
||||||
|
|
@ -52,10 +42,6 @@ class Account:
|
||||||
last_name: Union[Unset, str] = UNSET
|
last_name: Union[Unset, str] = UNSET
|
||||||
name: Union[Unset, str] = UNSET
|
name: Union[Unset, str] = UNSET
|
||||||
avatar: Union[None, Unset, str] = UNSET
|
avatar: Union[None, Unset, str] = UNSET
|
||||||
gender: Union[BlankEnum, GenderEnum, None, Unset] = UNSET
|
|
||||||
education_degree: Union[BlankEnum, EducationDegreeEnum, None, Unset] = UNSET
|
|
||||||
education_field: Union[None, Unset, str] = UNSET
|
|
||||||
birthdate: Union[None, Unset, datetime.date] = UNSET
|
|
||||||
iban: Union[None, Unset, str] = UNSET
|
iban: Union[None, Unset, str] = UNSET
|
||||||
province: Union[None, Unset, int] = UNSET
|
province: Union[None, Unset, int] = UNSET
|
||||||
city: Union[None, Unset, int] = UNSET
|
city: Union[None, Unset, int] = UNSET
|
||||||
|
|
@ -95,40 +81,6 @@ class Account:
|
||||||
else:
|
else:
|
||||||
avatar = self.avatar
|
avatar = self.avatar
|
||||||
|
|
||||||
gender: Union[None, Unset, str]
|
|
||||||
if isinstance(self.gender, Unset):
|
|
||||||
gender = UNSET
|
|
||||||
elif isinstance(self.gender, GenderEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
elif isinstance(self.gender, BlankEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
else:
|
|
||||||
gender = self.gender
|
|
||||||
|
|
||||||
education_degree: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_degree, Unset):
|
|
||||||
education_degree = UNSET
|
|
||||||
elif isinstance(self.education_degree, EducationDegreeEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
elif isinstance(self.education_degree, BlankEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
else:
|
|
||||||
education_degree = self.education_degree
|
|
||||||
|
|
||||||
education_field: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_field, Unset):
|
|
||||||
education_field = UNSET
|
|
||||||
else:
|
|
||||||
education_field = self.education_field
|
|
||||||
|
|
||||||
birthdate: Union[None, Unset, str]
|
|
||||||
if isinstance(self.birthdate, Unset):
|
|
||||||
birthdate = UNSET
|
|
||||||
elif isinstance(self.birthdate, datetime.date):
|
|
||||||
birthdate = self.birthdate.isoformat()
|
|
||||||
else:
|
|
||||||
birthdate = self.birthdate
|
|
||||||
|
|
||||||
iban: Union[None, Unset, str]
|
iban: Union[None, Unset, str]
|
||||||
if isinstance(self.iban, Unset):
|
if isinstance(self.iban, Unset):
|
||||||
iban = UNSET
|
iban = UNSET
|
||||||
|
|
@ -181,14 +133,6 @@ class Account:
|
||||||
field_dict["name"] = name
|
field_dict["name"] = name
|
||||||
if avatar is not UNSET:
|
if avatar is not UNSET:
|
||||||
field_dict["avatar"] = avatar
|
field_dict["avatar"] = avatar
|
||||||
if gender is not UNSET:
|
|
||||||
field_dict["gender"] = gender
|
|
||||||
if education_degree is not UNSET:
|
|
||||||
field_dict["education_degree"] = education_degree
|
|
||||||
if education_field is not UNSET:
|
|
||||||
field_dict["education_field"] = education_field
|
|
||||||
if birthdate is not UNSET:
|
|
||||||
field_dict["birthdate"] = birthdate
|
|
||||||
if iban is not UNSET:
|
if iban is not UNSET:
|
||||||
field_dict["iban"] = iban
|
field_dict["iban"] = iban
|
||||||
if province is not UNSET:
|
if province is not UNSET:
|
||||||
|
|
@ -203,8 +147,8 @@ class Account:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
uuid = UUID(d.pop("uuid"))
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
email = d.pop("email")
|
email = d.pop("email")
|
||||||
|
|
@ -246,82 +190,6 @@ class Account:
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
||||||
|
|
||||||
def _parse_gender(data: object) -> Union[BlankEnum, GenderEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_0 = GenderEnum(data)
|
|
||||||
|
|
||||||
return gender_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return gender_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, GenderEnum, None, Unset], data)
|
|
||||||
|
|
||||||
gender = _parse_gender(d.pop("gender", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_degree(data: object) -> Union[BlankEnum, EducationDegreeEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_0 = EducationDegreeEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, EducationDegreeEnum, None, Unset], data)
|
|
||||||
|
|
||||||
education_degree = _parse_education_degree(d.pop("education_degree", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_field(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
education_field = _parse_education_field(d.pop("education_field", UNSET))
|
|
||||||
|
|
||||||
def _parse_birthdate(data: object) -> Union[None, Unset, datetime.date]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
birthdate_type_0 = isoparse(data).date()
|
|
||||||
|
|
||||||
return birthdate_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, Unset, datetime.date], data)
|
|
||||||
|
|
||||||
birthdate = _parse_birthdate(d.pop("birthdate", UNSET))
|
|
||||||
|
|
||||||
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
||||||
if data is None:
|
if data is None:
|
||||||
return data
|
return data
|
||||||
|
|
@ -379,10 +247,6 @@ class Account:
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
name=name,
|
name=name,
|
||||||
avatar=avatar,
|
avatar=avatar,
|
||||||
gender=gender,
|
|
||||||
education_degree=education_degree,
|
|
||||||
education_field=education_field,
|
|
||||||
birthdate=birthdate,
|
|
||||||
iban=iban,
|
iban=iban,
|
||||||
province=province,
|
province=province,
|
||||||
city=city,
|
city=city,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from collections.abc import Mapping
|
from typing import Any, TypeVar
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -14,7 +13,6 @@ class Application:
|
||||||
Attributes:
|
Attributes:
|
||||||
uuid (UUID):
|
uuid (UUID):
|
||||||
name (str):
|
name (str):
|
||||||
avatar (Union[None, str]):
|
|
||||||
client_id (str):
|
client_id (str):
|
||||||
user (UUID):
|
user (UUID):
|
||||||
allowed_scope (str):
|
allowed_scope (str):
|
||||||
|
|
@ -22,7 +20,6 @@ class Application:
|
||||||
|
|
||||||
uuid: UUID
|
uuid: UUID
|
||||||
name: str
|
name: str
|
||||||
avatar: Union[None, str]
|
|
||||||
client_id: str
|
client_id: str
|
||||||
user: UUID
|
user: UUID
|
||||||
allowed_scope: str
|
allowed_scope: str
|
||||||
|
|
@ -33,9 +30,6 @@ class Application:
|
||||||
|
|
||||||
name = self.name
|
name = self.name
|
||||||
|
|
||||||
avatar: Union[None, str]
|
|
||||||
avatar = self.avatar
|
|
||||||
|
|
||||||
client_id = self.client_id
|
client_id = self.client_id
|
||||||
|
|
||||||
user = str(self.user)
|
user = str(self.user)
|
||||||
|
|
@ -48,7 +42,6 @@ class Application:
|
||||||
{
|
{
|
||||||
"uuid": uuid,
|
"uuid": uuid,
|
||||||
"name": name,
|
"name": name,
|
||||||
"avatar": avatar,
|
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
"user": user,
|
"user": user,
|
||||||
"allowed_scope": allowed_scope,
|
"allowed_scope": allowed_scope,
|
||||||
|
|
@ -58,19 +51,12 @@ class Application:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
uuid = UUID(d.pop("uuid"))
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
name = d.pop("name")
|
name = d.pop("name")
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, str], data)
|
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar"))
|
|
||||||
|
|
||||||
client_id = d.pop("client_id")
|
client_id = d.pop("client_id")
|
||||||
|
|
||||||
user = UUID(d.pop("user"))
|
user = UUID(d.pop("user"))
|
||||||
|
|
@ -80,7 +66,6 @@ class Application:
|
||||||
application = cls(
|
application = cls(
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
name=name,
|
name=name,
|
||||||
avatar=avatar,
|
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
user=user,
|
user=user,
|
||||||
allowed_scope=allowed_scope,
|
allowed_scope=allowed_scope,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class BlankEnum(str, Enum):
|
|
||||||
VALUE_0 = ""
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union
|
from typing import Any, TypeVar, Union
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -44,8 +43,8 @@ class ChangePassword:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
old_password = d.pop("old_password")
|
old_password = d.pop("old_password")
|
||||||
|
|
||||||
new_password = d.pop("new_password")
|
new_password = d.pop("new_password")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class EducationDegreeEnum(str, Enum):
|
|
||||||
ASSOCIATE = "associate"
|
|
||||||
BACHELORS = "bachelors"
|
|
||||||
DOCTORATE = "doctorate"
|
|
||||||
HIGH = "high"
|
|
||||||
MASTERS = "masters"
|
|
||||||
MIDDLE = "middle"
|
|
||||||
PRIMARY = "primary"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class GenderEnum(str, Enum):
|
|
||||||
F = "f"
|
|
||||||
M = "m"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -65,10 +64,10 @@ class PaginatedApplicationList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
from ..models.application import Application
|
from ..models.application import Application
|
||||||
|
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -65,10 +64,10 @@ class PaginatedPublicUserList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
from ..models.public_user import PublicUser
|
from ..models.public_user import PublicUser
|
||||||
|
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
from typing import Any, TypeVar, Union, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
from attrs import field as _attrs_field
|
from attrs import field as _attrs_field
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
from ..models.blank_enum import BlankEnum
|
|
||||||
from ..models.education_degree_enum import EducationDegreeEnum
|
|
||||||
from ..models.gender_enum import GenderEnum
|
|
||||||
from ..types import UNSET, Unset
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
T = TypeVar("T", bound="PatchedAccount")
|
T = TypeVar("T", bound="PatchedAccount")
|
||||||
|
|
@ -27,10 +21,6 @@ class PatchedAccount:
|
||||||
last_name (Union[Unset, str]):
|
last_name (Union[Unset, str]):
|
||||||
name (Union[Unset, str]):
|
name (Union[Unset, str]):
|
||||||
avatar (Union[None, Unset, str]):
|
avatar (Union[None, Unset, str]):
|
||||||
gender (Union[BlankEnum, GenderEnum, None, Unset]):
|
|
||||||
education_degree (Union[BlankEnum, EducationDegreeEnum, None, Unset]):
|
|
||||||
education_field (Union[None, Unset, str]):
|
|
||||||
birthdate (Union[None, Unset, datetime.date]):
|
|
||||||
iban (Union[None, Unset, str]):
|
iban (Union[None, Unset, str]):
|
||||||
iban_verified (Union[None, Unset, bool]):
|
iban_verified (Union[None, Unset, bool]):
|
||||||
province (Union[None, Unset, int]):
|
province (Union[None, Unset, int]):
|
||||||
|
|
@ -49,10 +39,6 @@ class PatchedAccount:
|
||||||
last_name: Union[Unset, str] = UNSET
|
last_name: Union[Unset, str] = UNSET
|
||||||
name: Union[Unset, str] = UNSET
|
name: Union[Unset, str] = UNSET
|
||||||
avatar: Union[None, Unset, str] = UNSET
|
avatar: Union[None, Unset, str] = UNSET
|
||||||
gender: Union[BlankEnum, GenderEnum, None, Unset] = UNSET
|
|
||||||
education_degree: Union[BlankEnum, EducationDegreeEnum, None, Unset] = UNSET
|
|
||||||
education_field: Union[None, Unset, str] = UNSET
|
|
||||||
birthdate: Union[None, Unset, datetime.date] = UNSET
|
|
||||||
iban: Union[None, Unset, str] = UNSET
|
iban: Union[None, Unset, str] = UNSET
|
||||||
iban_verified: Union[None, Unset, bool] = UNSET
|
iban_verified: Union[None, Unset, bool] = UNSET
|
||||||
province: Union[None, Unset, int] = UNSET
|
province: Union[None, Unset, int] = UNSET
|
||||||
|
|
@ -90,40 +76,6 @@ class PatchedAccount:
|
||||||
else:
|
else:
|
||||||
avatar = self.avatar
|
avatar = self.avatar
|
||||||
|
|
||||||
gender: Union[None, Unset, str]
|
|
||||||
if isinstance(self.gender, Unset):
|
|
||||||
gender = UNSET
|
|
||||||
elif isinstance(self.gender, GenderEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
elif isinstance(self.gender, BlankEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
else:
|
|
||||||
gender = self.gender
|
|
||||||
|
|
||||||
education_degree: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_degree, Unset):
|
|
||||||
education_degree = UNSET
|
|
||||||
elif isinstance(self.education_degree, EducationDegreeEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
elif isinstance(self.education_degree, BlankEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
else:
|
|
||||||
education_degree = self.education_degree
|
|
||||||
|
|
||||||
education_field: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_field, Unset):
|
|
||||||
education_field = UNSET
|
|
||||||
else:
|
|
||||||
education_field = self.education_field
|
|
||||||
|
|
||||||
birthdate: Union[None, Unset, str]
|
|
||||||
if isinstance(self.birthdate, Unset):
|
|
||||||
birthdate = UNSET
|
|
||||||
elif isinstance(self.birthdate, datetime.date):
|
|
||||||
birthdate = self.birthdate.isoformat()
|
|
||||||
else:
|
|
||||||
birthdate = self.birthdate
|
|
||||||
|
|
||||||
iban: Union[None, Unset, str]
|
iban: Union[None, Unset, str]
|
||||||
if isinstance(self.iban, Unset):
|
if isinstance(self.iban, Unset):
|
||||||
iban = UNSET
|
iban = UNSET
|
||||||
|
|
@ -183,14 +135,6 @@ class PatchedAccount:
|
||||||
field_dict["name"] = name
|
field_dict["name"] = name
|
||||||
if avatar is not UNSET:
|
if avatar is not UNSET:
|
||||||
field_dict["avatar"] = avatar
|
field_dict["avatar"] = avatar
|
||||||
if gender is not UNSET:
|
|
||||||
field_dict["gender"] = gender
|
|
||||||
if education_degree is not UNSET:
|
|
||||||
field_dict["education_degree"] = education_degree
|
|
||||||
if education_field is not UNSET:
|
|
||||||
field_dict["education_field"] = education_field
|
|
||||||
if birthdate is not UNSET:
|
|
||||||
field_dict["birthdate"] = birthdate
|
|
||||||
if iban is not UNSET:
|
if iban is not UNSET:
|
||||||
field_dict["iban"] = iban
|
field_dict["iban"] = iban
|
||||||
if iban_verified is not UNSET:
|
if iban_verified is not UNSET:
|
||||||
|
|
@ -211,8 +155,8 @@ class PatchedAccount:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
_uuid = d.pop("uuid", UNSET)
|
_uuid = d.pop("uuid", UNSET)
|
||||||
uuid: Union[Unset, UUID]
|
uuid: Union[Unset, UUID]
|
||||||
if isinstance(_uuid, Unset):
|
if isinstance(_uuid, Unset):
|
||||||
|
|
@ -248,82 +192,6 @@ class PatchedAccount:
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
||||||
|
|
||||||
def _parse_gender(data: object) -> Union[BlankEnum, GenderEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_0 = GenderEnum(data)
|
|
||||||
|
|
||||||
return gender_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return gender_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, GenderEnum, None, Unset], data)
|
|
||||||
|
|
||||||
gender = _parse_gender(d.pop("gender", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_degree(data: object) -> Union[BlankEnum, EducationDegreeEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_0 = EducationDegreeEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, EducationDegreeEnum, None, Unset], data)
|
|
||||||
|
|
||||||
education_degree = _parse_education_degree(d.pop("education_degree", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_field(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
education_field = _parse_education_field(d.pop("education_field", UNSET))
|
|
||||||
|
|
||||||
def _parse_birthdate(data: object) -> Union[None, Unset, datetime.date]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
birthdate_type_0 = isoparse(data).date()
|
|
||||||
|
|
||||||
return birthdate_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, Unset, datetime.date], data)
|
|
||||||
|
|
||||||
birthdate = _parse_birthdate(d.pop("birthdate", UNSET))
|
|
||||||
|
|
||||||
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
||||||
if data is None:
|
if data is None:
|
||||||
return data
|
return data
|
||||||
|
|
@ -391,10 +259,6 @@ class PatchedAccount:
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
name=name,
|
name=name,
|
||||||
avatar=avatar,
|
avatar=avatar,
|
||||||
gender=gender,
|
|
||||||
education_degree=education_degree,
|
|
||||||
education_field=education_field,
|
|
||||||
birthdate=birthdate,
|
|
||||||
iban=iban,
|
iban=iban,
|
||||||
iban_verified=iban_verified,
|
iban_verified=iban_verified,
|
||||||
province=province,
|
province=province,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union
|
from typing import Any, TypeVar, Union
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -43,8 +42,8 @@ class PatchedChangePassword:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
old_password = d.pop("old_password", UNSET)
|
old_password = d.pop("old_password", UNSET)
|
||||||
|
|
||||||
old_password_field = d.pop("old_password_field", UNSET)
|
old_password_field = d.pop("old_password_field", UNSET)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
from typing import Any, TypeVar, Union, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
@ -59,8 +58,8 @@ class PublicUser:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, str]:
|
def _parse_avatar(data: object) -> Union[None, str]:
|
||||||
if data is None:
|
if data is None:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import datetime
|
import datetime
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -47,8 +46,8 @@ class RequestOTP:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
phone_number = d.pop("phone_number")
|
phone_number = d.pop("phone_number")
|
||||||
|
|
||||||
def _parse_otp_expire(data: object) -> Union[None, datetime.datetime]:
|
def _parse_otp_expire(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import datetime
|
import datetime
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -48,8 +47,8 @@ class RequestOTT:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
application = d.pop("application")
|
application = d.pop("application")
|
||||||
|
|
||||||
def _parse_ott(data: object) -> Union[None, str]:
|
def _parse_ott(data: object) -> Union[None, str]:
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="UserInquiry")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class UserInquiry:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
uuid (UUID):
|
|
||||||
phone_number (str):
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid: UUID
|
|
||||||
phone_number: str
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
uuid = str(self.uuid)
|
|
||||||
|
|
||||||
phone_number = self.phone_number
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"uuid": uuid,
|
|
||||||
"phone_number": phone_number,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
uuid = UUID(d.pop("uuid"))
|
|
||||||
|
|
||||||
phone_number = d.pop("phone_number")
|
|
||||||
|
|
||||||
user_inquiry = cls(
|
|
||||||
uuid=uuid,
|
|
||||||
phone_number=phone_number,
|
|
||||||
)
|
|
||||||
|
|
||||||
user_inquiry.additional_properties = d
|
|
||||||
return user_inquiry
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
2026-08-01 15:27:52,276 [INFO] django.server log 98318 6145355776 "GET /admin/login/ HTTP/1.1" 200 4068
|
|
||||||
2026-08-01 15:28:16,343 [INFO] django.server log 98318 6145355776 "GET /admin/login/ HTTP/1.1" 200 4068
|
|
||||||
2026-08-01 15:28:16,620 [INFO] django.server log 98318 6145355776 "POST /admin/login/ HTTP/1.1" 302 0
|
|
||||||
2026-08-01 15:28:16,695 [INFO] django.server log 98318 6145355776 "GET /admin/wallet/transaction/ HTTP/1.1" 200 30332
|
|
||||||
2026-08-01 15:29:03,893 [INFO] django.server log 98318 6162182144 "GET /admin/login/ HTTP/1.1" 200 4068
|
|
||||||
2026-08-01 15:29:03,994 [INFO] django.server log 98318 6145355776 "GET /static/admin/css/login.css HTTP/1.1" 200 958
|
|
||||||
2026-08-01 15:29:03,995 [INFO] django.server log 98318 6179008512 "GET /static/admin/css/dark_mode.css HTTP/1.1" 200 2682
|
|
||||||
2026-08-01 15:29:03,995 [INFO] django.server log 98318 6195834880 "GET /static/admin/css/nav_sidebar.css HTTP/1.1" 200 2810
|
|
||||||
2026-08-01 15:29:03,997 [INFO] django.server log 98318 6229487616 "GET /static/admin/js/theme.js HTTP/1.1" 200 1943
|
|
||||||
2026-08-01 15:29:03,997 [INFO] django.server log 98318 6212661248 "GET /static/admin/css/responsive.css HTTP/1.1" 200 17905
|
|
||||||
2026-08-01 15:29:03,997 [INFO] django.server log 98318 6162182144 "GET /static/admin/css/base.css HTTP/1.1" 200 21544
|
|
||||||
2026-08-01 15:29:03,998 [INFO] django.server log 98318 6229487616 "GET /static/admin/js/nav_sidebar.js HTTP/1.1" 200 3063
|
|
||||||
2026-08-01 15:29:04,565 [WARNING] django.server log 98318 6229487616 "GET /favicon.ico HTTP/1.1" 404 3758
|
|
||||||
2026-08-01 15:32:02,114 [INFO] django.server log 98318 6145355776 "GET /admin/ HTTP/1.1" 302 0
|
|
||||||
2026-08-01 15:32:02,125 [INFO] django.server log 98318 6145355776 "GET /admin/login/?next=/admin/ HTTP/1.1" 200 4081
|
|
||||||
2026-08-01 15:32:02,263 [INFO] django.server log 98318 6162182144 "GET /static/admin/css/dark_mode.css HTTP/1.1" 200 2682
|
|
||||||
2026-08-01 15:32:02,266 [INFO] django.server log 98318 6179008512 "GET /static/admin/css/nav_sidebar.css HTTP/1.1" 200 2810
|
|
||||||
2026-08-01 15:32:02,266 [INFO] django.server log 98318 6145355776 "GET /static/admin/css/base.css HTTP/1.1" 200 21544
|
|
||||||
2026-08-01 15:32:02,267 [INFO] django.server log 98318 6212661248 "GET /static/admin/js/theme.js HTTP/1.1" 200 1943
|
|
||||||
2026-08-01 15:32:02,267 [INFO] django.server log 98318 6195834880 "GET /static/admin/css/login.css HTTP/1.1" 200 958
|
|
||||||
2026-08-01 15:32:02,268 [INFO] django.server log 98318 6229487616 "GET /static/admin/css/responsive.css HTTP/1.1" 200 17905
|
|
||||||
2026-08-01 15:32:02,276 [INFO] django.server log 98318 6229487616 "GET /static/admin/js/nav_sidebar.js HTTP/1.1" 200 3063
|
|
||||||
2026-08-01 15:32:02,403 [WARNING] django.server log 98318 6229487616 "GET /favicon.ico HTTP/1.1" 404 3758
|
|
||||||
2026-08-01 15:32:17,288 [INFO] django.server log 98318 6229487616 "POST /admin/login/?next=/admin/ HTTP/1.1" 302 0
|
|
||||||
2026-08-01 15:32:17,351 [INFO] django.server log 98318 6229487616 "GET /admin/ HTTP/1.1" 200 13562
|
|
||||||
2026-08-01 15:32:17,384 [INFO] django.server log 98318 6229487616 "GET /static/admin/css/dashboard.css HTTP/1.1" 200 441
|
|
||||||
2026-08-01 15:32:17,389 [INFO] django.server log 98318 6195834880 "GET /static/admin/img/icon-changelink.svg HTTP/1.1" 200 380
|
|
||||||
2026-08-01 15:32:17,389 [INFO] django.server log 98318 6229487616 "GET /static/admin/img/icon-addlink.svg HTTP/1.1" 200 331
|
|
||||||
2026-08-01 15:32:22,629 [INFO] django.server log 98318 6229487616 "GET /admin/wallet/transaction/ HTTP/1.1" 200 30332
|
|
||||||
2026-08-01 15:32:22,649 [INFO] django.server log 98318 6162182144 "GET /static/admin/js/jquery.init.js HTTP/1.1" 200 347
|
|
||||||
2026-08-01 15:32:22,649 [INFO] django.server log 98318 6229487616 "GET /static/admin/css/changelists.css HTTP/1.1" 200 6811
|
|
||||||
2026-08-01 15:32:22,649 [INFO] django.server log 98318 6179008512 "GET /static/admin/css/django_jalali.min.css HTTP/1.1" 200 8077
|
|
||||||
2026-08-01 15:32:22,650 [INFO] django.server log 98318 6212661248 "GET /static/admin/js/django_jalali.min.js HTTP/1.1" 200 19826
|
|
||||||
2026-08-01 15:32:22,657 [INFO] django.server log 98318 6212661248 "GET /static/admin/js/actions.js HTTP/1.1" 200 8067
|
|
||||||
2026-08-01 15:32:22,658 [INFO] django.server log 98318 6229487616 "GET /static/admin/css/widgets.css HTTP/1.1" 200 11800
|
|
||||||
2026-08-01 15:32:22,658 [INFO] django.server log 98318 6162182144 "GET /static/admin/js/core.js HTTP/1.1" 200 6208
|
|
||||||
2026-08-01 15:32:22,660 [INFO] django.server log 98318 6179008512 "GET /static/admin/js/admin/RelatedObjectLookups.js HTTP/1.1" 200 9042
|
|
||||||
2026-08-01 15:32:22,660 [INFO] django.server log 98318 6145355776 "GET /static/admin/js/vendor/jquery/jquery.js HTTP/1.1" 200 285312
|
|
||||||
2026-08-01 15:32:22,666 [INFO] django.server log 98318 6145355776 "GET /static/admin/js/urlify.js HTTP/1.1" 200 7887
|
|
||||||
2026-08-01 15:32:22,666 [INFO] django.server log 98318 6212661248 "GET /static/admin/js/prepopulate.js HTTP/1.1" 200 1531
|
|
||||||
2026-08-01 15:32:22,667 [INFO] django.server log 98318 6195834880 "GET /admin/jsi18n/ HTTP/1.1" 200 3342
|
|
||||||
2026-08-01 15:32:22,667 [INFO] django.server log 98318 6229487616 "GET /static/admin/img/search.svg HTTP/1.1" 200 458
|
|
||||||
2026-08-01 15:32:22,669 [INFO] django.server log 98318 6179008512 "GET /static/admin/js/vendor/xregexp/xregexp.js HTTP/1.1" 200 325171
|
|
||||||
2026-08-01 15:32:22,679 [INFO] django.server log 98318 6179008512 "GET /static/admin/js/filters.js HTTP/1.1" 200 978
|
|
||||||
2026-08-01 15:32:22,696 [INFO] django.server log 98318 6229487616 "GET /static/admin/img/icon-viewlink.svg HTTP/1.1" 200 581
|
|
||||||
2026-08-01 15:32:22,696 [INFO] django.server log 98318 6179008512 "GET /static/admin/img/tooltag-add.svg HTTP/1.1" 200 331
|
|
||||||
2026-08-01 15:32:22,698 [INFO] django.server log 98318 6179008512 "GET /static/admin/img/sorting-icons.svg HTTP/1.1" 200 1097
|
|
||||||
2026-08-01 15:32:22,719 [INFO] django.server log 98318 6179008512 "GET /static/admin/js/calendar.js HTTP/1.1" 200 9141
|
|
||||||
2026-08-01 15:32:22,720 [INFO] django.server log 98318 6229487616 "GET /static/admin/js/admin/DateTimeShortcuts.js HTTP/1.1" 200 19319
|
|
||||||
2026-08-01 15:32:22,736 [INFO] django.server log 98318 6229487616 "GET /static/admin/img/icon-clock.svg HTTP/1.1" 200 677
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
def get_settings(key):
|
|
||||||
from django.conf import settings
|
|
||||||
return getattr(settings, 'BASE_DIR')
|
|
||||||
|
|
||||||
BASE_DIR = get_settings('BASE_DIR')
|
|
||||||
|
|
||||||
LOG_DIR = BASE_DIR / 'log'
|
|
||||||
if not os.path.exists(LOG_DIR):
|
|
||||||
os.makedirs(LOG_DIR)
|
|
||||||
|
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'formatters': {
|
|
||||||
'verbose': {
|
|
||||||
'format': '{asctime} [{levelname}] {name} {module} {process:d} {thread:d} {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
'standard': {
|
|
||||||
'format': '{asctime} [{levelname}] {name}: {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
'simple': {
|
|
||||||
'format': '{levelname} {message}',
|
|
||||||
'style': '{',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'filters': {
|
|
||||||
'require_debug_true': {
|
|
||||||
'()': 'django.utils.log.RequireDebugTrue',
|
|
||||||
},
|
|
||||||
'require_debug_false': {
|
|
||||||
'()': 'django.utils.log.RequireDebugFalse',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
# هندلر برای نوشتن در فایل
|
|
||||||
'file': {
|
|
||||||
'level': 'INFO',
|
|
||||||
'class': 'logging.handlers.TimedRotatingFileHandler',
|
|
||||||
'filename': os.path.join(LOG_DIR, 'accounts.log'),
|
|
||||||
# 'maxBytes': 1024 * 1024 * 10, # 10 MB
|
|
||||||
'when': 'midnight',
|
|
||||||
'interval': 5,
|
|
||||||
'backupCount': 30,
|
|
||||||
'formatter': 'verbose',
|
|
||||||
'encoding': 'utf-8',
|
|
||||||
},
|
|
||||||
# هندلر برای خطاها در فایل جداگانه
|
|
||||||
'error_file': {
|
|
||||||
'level': 'ERROR',
|
|
||||||
'class': 'logging.handlers.TimedRotatingFileHandler',
|
|
||||||
'filename': os.path.join(LOG_DIR, 'errors.log'),
|
|
||||||
# 'maxBytes': 1024 * 1024 * 10,
|
|
||||||
'when': 'midnight',
|
|
||||||
'interval': 5,
|
|
||||||
'backupCount': 30,
|
|
||||||
'formatter': 'verbose',
|
|
||||||
'encoding': 'utf-8',
|
|
||||||
},
|
|
||||||
# هندلر برای کنسول (فقط در حالت DEBUG)
|
|
||||||
'console': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'filters': ['require_debug_true'],
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'simple',
|
|
||||||
},
|
|
||||||
# هندلر برای کنسول همیشه فعال (حتی در production)
|
|
||||||
'console_always': {
|
|
||||||
'level': 'INFO',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'standard',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
# لاگر ریشه
|
|
||||||
'': { # empty string = root logger
|
|
||||||
'handlers': ['console', 'file', 'error_file'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
# لاگر اختصاصی برای Django
|
|
||||||
'django': {
|
|
||||||
'handlers': ['console', 'file'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
# لاگر اختصاصی برای درخواستها
|
|
||||||
'django.request': {
|
|
||||||
'handlers': ['file', 'error_file', 'console'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
# # لاگر اختصاصی برای برنامه خودتان
|
|
||||||
# 'root': {
|
|
||||||
# 'handlers': ['console', 'file', 'error_file'],
|
|
||||||
# 'level': 'DEBUG',
|
|
||||||
# 'propagate': False,
|
|
||||||
# },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
# {
|
|
||||||
# 'version': 1,
|
|
||||||
# 'disable_existing_loggers': False,
|
|
||||||
# 'formatters': {
|
|
||||||
# 'loki': {
|
|
||||||
# 'class': 'utils.logs.LokiFormatter', # required
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
#
|
|
||||||
# 'handlers': {
|
|
||||||
# 'loki': {
|
|
||||||
# 'level': 'DEBUG', # Log level. Required
|
|
||||||
# 'class': 'utils.logs.LokiHandler', # Required
|
|
||||||
# 'formatter': 'loki', # Loki formatter. Required
|
|
||||||
# 'timeout': 2, # Post request timeout, default is 0.5. Optional
|
|
||||||
# 'url': f'{LOKI_BASE_PUBLIC_URL}/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
|
|
||||||
# # 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
|
|
||||||
# 'tags': {"app": "accounts"}, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
|
|
||||||
# 'mode': 'thread',
|
|
||||||
# # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
|
|
||||||
# },
|
|
||||||
# 'console': {
|
|
||||||
# 'level': 'DEBUG',
|
|
||||||
# 'class': 'logging.StreamHandler',
|
|
||||||
# # 'formatter': 'verbose',
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
# 'loggers': {
|
|
||||||
# '': {
|
|
||||||
# 'handlers': ['console', 'loki'],
|
|
||||||
# 'level': 'INFO',
|
|
||||||
# 'propagate': True,
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
# }
|
|
||||||
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wallet.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,6 @@ django-jalali-date
|
||||||
django-betterforms
|
django-betterforms
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
crispy_bootstrap5
|
crispy_bootstrap5
|
||||||
psycopg[binary,pool]
|
psycopg[binary]
|
||||||
django-redis
|
django-redis
|
||||||
httpx
|
httpx
|
||||||
packaging==25.0
|
|
||||||
xlsxwriter
|
|
||||||
django-admin-rangefilter
|
|
||||||
133
requirements.txt
133
requirements.txt
|
|
@ -1,35 +1,37 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --cert=None --client-cert=None --index-url=None --pip-args=None
|
# pip-compile
|
||||||
#
|
#
|
||||||
amqp==5.3.1
|
amqp==5.3.1
|
||||||
# via kombu
|
# via kombu
|
||||||
anyio==4.12.1
|
anyio==4.8.0
|
||||||
# via httpx
|
# via httpx
|
||||||
asgiref==3.11.1
|
asgiref==3.8.1
|
||||||
# via
|
# via
|
||||||
# django
|
# django
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
attrs==25.4.0
|
async-timeout==5.0.1
|
||||||
|
# via redis
|
||||||
|
attrs==25.2.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
billiard==4.2.4
|
billiard==4.2.1
|
||||||
# via celery
|
# via celery
|
||||||
celery==5.6.2
|
celery==5.4.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
certifi==2026.1.4
|
certifi==2025.1.31
|
||||||
# via
|
# via
|
||||||
# httpcore
|
# httpcore
|
||||||
# httpx
|
# httpx
|
||||||
# requests
|
# requests
|
||||||
cffi==2.0.0
|
cffi==1.17.1
|
||||||
# via cryptography
|
# via cryptography
|
||||||
charset-normalizer==3.4.4
|
charset-normalizer==3.4.1
|
||||||
# via requests
|
# via requests
|
||||||
click==8.3.1
|
click==8.1.8
|
||||||
# via
|
# via
|
||||||
# celery
|
# celery
|
||||||
# click-didyoumean
|
# click-didyoumean
|
||||||
|
|
@ -37,15 +39,15 @@ click==8.3.1
|
||||||
# click-repl
|
# click-repl
|
||||||
click-didyoumean==0.3.1
|
click-didyoumean==0.3.1
|
||||||
# via celery
|
# via celery
|
||||||
click-plugins==1.1.1.2
|
click-plugins==1.1.1
|
||||||
# via celery
|
# via celery
|
||||||
click-repl==0.3.0
|
click-repl==0.3.0
|
||||||
# via celery
|
# via celery
|
||||||
crispy-bootstrap5==2025.6
|
crispy-bootstrap5==2024.10
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
cryptography==46.0.4
|
cryptography==44.0.2
|
||||||
# via jwcrypto
|
# via jwcrypto
|
||||||
django==6.0.2
|
django==5.1.7
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
|
|
@ -60,39 +62,39 @@ django==6.0.2
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
django-betterforms==2.0.0
|
django-betterforms==2.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-cors-headers==4.9.0
|
django-cors-headers==4.7.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-crispy-forms==2.5
|
django-crispy-forms==2.3
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
django-filter==25.2
|
django-filter==25.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-jalali-date==2.0.0
|
django-jalali-date==1.1.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-oauth-toolkit==3.2.0
|
django-oauth-toolkit==3.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==6.0.0
|
django-redis==5.4.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
djangorestframework==3.16.1
|
djangorestframework==3.15.2
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
drf-spectacular==0.29.0
|
drf-spectacular==0.28.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
gevent==25.9.1
|
gevent==24.11.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
greenlet==3.3.1
|
greenlet==3.1.1
|
||||||
# via gevent
|
# via gevent
|
||||||
gunicorn==25.0.2
|
gunicorn==23.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
h11==0.16.0
|
h11==0.14.0
|
||||||
# via httpcore
|
# via httpcore
|
||||||
httpcore==1.0.9
|
httpcore==1.0.7
|
||||||
# via httpx
|
# via httpx
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.11
|
idna==3.10
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
# httpx
|
# httpx
|
||||||
|
|
@ -103,82 +105,81 @@ jalali-core==1.0.0
|
||||||
# via jdatetime
|
# via jdatetime
|
||||||
jdatetime==5.2.0
|
jdatetime==5.2.0
|
||||||
# via django-jalali-date
|
# via django-jalali-date
|
||||||
jsonschema==4.26.0
|
jsonschema==4.23.0
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
jsonschema-specifications==2025.9.1
|
jsonschema-specifications==2024.10.1
|
||||||
# via jsonschema
|
# via jsonschema
|
||||||
jwcrypto==1.5.6
|
jwcrypto==1.5.6
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
kombu==5.6.2
|
kombu==5.4.2
|
||||||
# via celery
|
# via celery
|
||||||
markdown==3.10.1
|
markdown==3.7
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
oauthlib==3.3.1
|
oauthlib==3.2.2
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
packaging==25.0
|
packaging==24.2
|
||||||
# via
|
# via gunicorn
|
||||||
# -r requirements.in
|
pillow==11.1.0
|
||||||
# gunicorn
|
|
||||||
# kombu
|
|
||||||
pillow==12.1.0
|
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
prompt-toolkit==3.0.52
|
prompt-toolkit==3.0.50
|
||||||
# via click-repl
|
# via click-repl
|
||||||
psycopg[binary,pool]==3.3.2
|
psycopg[binary]==3.2.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
psycopg-binary==3.3.2
|
psycopg-binary==3.2.6
|
||||||
# via psycopg
|
# via psycopg
|
||||||
psycopg-pool==3.3.0
|
pycparser==2.22
|
||||||
# via psycopg
|
|
||||||
pycparser==3.0
|
|
||||||
# via cffi
|
# via cffi
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
# via celery
|
# via celery
|
||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pyyaml==6.0.3
|
pyyaml==6.0.2
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
redis==7.1.0
|
redis==5.2.1
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-redis
|
# django-redis
|
||||||
referencing==0.37.0
|
referencing==0.36.2
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# jsonschema-specifications
|
# jsonschema-specifications
|
||||||
requests==2.32.5
|
requests==2.32.3
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
rpds-py==0.30.0
|
rpds-py==0.23.1
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
# via python-dateutil
|
# via python-dateutil
|
||||||
sqlparse==0.5.5
|
sniffio==1.3.1
|
||||||
|
# via anyio
|
||||||
|
sqlparse==0.5.3
|
||||||
# via django
|
# via django
|
||||||
typing-extensions==4.15.0
|
typing-extensions==4.12.2
|
||||||
# via
|
# via
|
||||||
|
# anyio
|
||||||
# jwcrypto
|
# jwcrypto
|
||||||
# psycopg-pool
|
# psycopg
|
||||||
tzdata==2025.3
|
# referencing
|
||||||
# via kombu
|
tzdata==2025.1
|
||||||
tzlocal==5.3.1
|
# via
|
||||||
# via celery
|
# celery
|
||||||
uritemplate==4.2.0
|
# kombu
|
||||||
|
uritemplate==4.1.1
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
urllib3==2.6.3
|
urllib3==2.3.0
|
||||||
# via requests
|
# via requests
|
||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
# via
|
# via
|
||||||
# amqp
|
# amqp
|
||||||
# celery
|
# celery
|
||||||
# kombu
|
# kombu
|
||||||
wcwidth==0.6.0
|
wcwidth==0.2.13
|
||||||
# via prompt-toolkit
|
# via prompt-toolkit
|
||||||
zope-event==6.1
|
zope-event==5.0
|
||||||
# via gevent
|
# via gevent
|
||||||
zope-interface==8.2
|
zope-interface==7.2
|
||||||
# via gevent
|
# via gevent
|
||||||
xlsxwriter==3.2.3
|
|
||||||
# via -r requirements.in
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
django-admin-rangefilter==0.13.5
|
# setuptools
|
||||||
|
|
|
||||||
2
run.sh
2
run.sh
|
|
@ -5,4 +5,4 @@ while ! nc -z $DB_HOST 5432 ; do
|
||||||
done
|
done
|
||||||
#python3 manage.py collectstatic --noinput
|
#python3 manage.py collectstatic --noinput
|
||||||
python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
gunicorn main.wsgi:application --bind 0.0.0.0:8000 -w 4
|
gunicorn wallet.wsgi:application --bind 0.0.0.0:8000 -w 4
|
||||||
2
static/admin/css/django_jalali.min.css
vendored
2
static/admin/css/django_jalali.min.css
vendored
File diff suppressed because one or more lines are too long
20
static/admin/js/django_jalali.min.js
vendored
20
static/admin/js/django_jalali.min.js
vendored
File diff suppressed because one or more lines are too long
2
static/admin/js/vendor/jquery/jquery.js
vendored
2
static/admin/js/vendor/jquery/jquery.js
vendored
|
|
@ -1031,7 +1031,7 @@ function createDisabledPseudo( disabled ) {
|
||||||
|
|
||||||
// Check for inherited disabledness on relevant non-disabled elements:
|
// Check for inherited disabledness on relevant non-disabled elements:
|
||||||
// * listed form-associated elements in a disabled fieldset
|
// * listed form-associated elements in a disabled fieldset
|
||||||
// https://html.spec.whatwg.org/multipage/forms.html#wallet-listed
|
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
|
||||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
||||||
// * option elements in a disabled optgroup
|
// * option elements in a disabled optgroup
|
||||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base_charge.html" %}
|
{% extends "base_charge.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{#{% load jalali_tags %}#}
|
{% load jalali_tags %}
|
||||||
|
|
||||||
{% block title %}افزایش موجودی{% endblock %}
|
{% block title %}افزایش موجودی{% endblock %}
|
||||||
{% block page_title %}افزایش موجودی{% endblock %}
|
{% block page_title %}افزایش موجودی{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{#{% load jalali_tags %}#}
|
{% load jalali_tags %}
|
||||||
|
|
||||||
{% block title %}wallet: Home{% endblock %}
|
{% block title %}wallet: Home{% endblock %}
|
||||||
{% block page_title %}wallet: Home{% endblock %}
|
{% block page_title %}wallet: Home{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{#{% load jalali_tags %}#}
|
{% load jalali_tags %}
|
||||||
<div>
|
<div>
|
||||||
<div class="card card-body mb-3">
|
<div class="card card-body mb-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{#{% load jalali_tags %}#}
|
{% load jalali_tags %}
|
||||||
<div>
|
<div>
|
||||||
<p class="fw-bold mb-1">اطلاعات آقا</p>
|
<p class="fw-bold mb-1">اطلاعات آقا</p>
|
||||||
<div class="card card-body mb-3">
|
<div class="card card-body mb-3">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{#{% load jalali_tags %}#}
|
{% load jalali_tags %}
|
||||||
|
|
||||||
{% block title %}user account{% endblock %}
|
{% block title %}user account{% endblock %}
|
||||||
{% block page_title %}user account{% endblock %}
|
{% block page_title %}user account{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ from gooyal_accounts_client import AuthenticatedClient
|
||||||
from gooyal_accounts_client.models import PublicUser, Application
|
from gooyal_accounts_client.models import PublicUser, Application
|
||||||
from gooyal_accounts_client.api.users import users_api_users_retrieve
|
from gooyal_accounts_client.api.users import users_api_users_retrieve
|
||||||
from gooyal_accounts_client.api.oauth2 import oauth2_apis_applications_retrieve
|
from gooyal_accounts_client.api.oauth2 import oauth2_apis_applications_retrieve
|
||||||
from gooyal_accounts_client.models import Account
|
from gooyal_accounts_client.types import Response
|
||||||
from gooyal_accounts_client.api.users import (users_api_users_details_retrieve)
|
|
||||||
|
|
||||||
|
|
||||||
def login_as_client_credentials():
|
def login_as_client_credentials():
|
||||||
|
|
@ -61,13 +60,6 @@ def get_user_details(uuid):
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_user_detailed_info(uuid):
|
|
||||||
client = get_client()
|
|
||||||
response: Account = users_api_users_details_retrieve.sync(uuid=uuid, client=client)
|
|
||||||
print(response)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_application_details(uuid):
|
def get_application_details(uuid):
|
||||||
client = get_client()
|
client = get_client()
|
||||||
data: Application = oauth2_apis_applications_retrieve.sync(client=client, id=uuid)
|
data: Application = oauth2_apis_applications_retrieve.sync(client=client, id=uuid)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class LokiHandler(logging.Handler):
|
||||||
try:
|
try:
|
||||||
payload = self.formatter.format(record)
|
payload = self.formatter.format(record)
|
||||||
|
|
||||||
self._push_message(
|
_push_message(
|
||||||
self._url, json=payload, timeout=self._timeout, auth=self._auth
|
self._url, json=payload, timeout=self._timeout, auth=self._auth
|
||||||
)
|
)
|
||||||
except requests.exceptions.ReadTimeout:
|
except requests.exceptions.ReadTimeout:
|
||||||
|
|
@ -41,6 +41,7 @@ class LokiHandler(logging.Handler):
|
||||||
self.formatter = fmt
|
self.formatter = fmt
|
||||||
|
|
||||||
def _push_message(self, *args, **kwargs):
|
def _push_message(self, *args, **kwargs):
|
||||||
|
print('hehe')
|
||||||
if self._mode == "sync":
|
if self._mode == "sync":
|
||||||
return _push_message(*args, **kwargs)
|
return _push_message(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import os
|
import os
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wallet.settings')
|
||||||
|
|
||||||
from utils.broker import get_rpc_broker_consumer
|
from utils.broker import get_rpc_broker_consumer
|
||||||
|
|
||||||
|
|
@ -16,6 +16,7 @@ from decouple import config
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
@ -37,7 +38,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.postgres',
|
|
||||||
|
|
||||||
# pip installed apps
|
# pip installed apps
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
|
|
@ -47,8 +47,7 @@ INSTALLED_APPS = [
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'crispy_bootstrap5',
|
'crispy_bootstrap5',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'rangefilter',
|
'jalali_date',
|
||||||
# 'jalali_date',
|
|
||||||
|
|
||||||
# local apps
|
# local apps
|
||||||
'apps.core',
|
'apps.core',
|
||||||
|
|
@ -71,6 +70,7 @@ MIDDLEWARE = [
|
||||||
'utils.exceptions.ErrorMiddleware',
|
'utils.exceptions.ErrorMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
OAUTH2_PROVIDER_APPLICATION_MODEL = "gooyal_oauth2.Application"
|
OAUTH2_PROVIDER_APPLICATION_MODEL = "gooyal_oauth2.Application"
|
||||||
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "gooyal_oauth2.AccessToken"
|
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "gooyal_oauth2.AccessToken"
|
||||||
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "gooyal_oauth2.IDToken"
|
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "gooyal_oauth2.IDToken"
|
||||||
|
|
@ -126,7 +126,8 @@ SPECTACULAR_SETTINGS = {
|
||||||
# https://spec.openapis.org/oas/v3.0.3#oauth-flows-object
|
# https://spec.openapis.org/oas/v3.0.3#oauth-flows-object
|
||||||
}
|
}
|
||||||
|
|
||||||
ROOT_URLCONF = 'main.urls'
|
|
||||||
|
ROOT_URLCONF = 'wallet.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
|
|
@ -149,7 +150,7 @@ AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
WSGI_APPLICATION = 'main.wsgi.application'
|
WSGI_APPLICATION = 'wallet.wsgi.application'
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
@ -190,6 +191,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
|
@ -207,6 +209,7 @@ LOCALE_PATHS = [
|
||||||
BASE_DIR / 'locale',
|
BASE_DIR / 'locale',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
|
@ -223,11 +226,15 @@ if DEBUG == True:
|
||||||
else:
|
else:
|
||||||
STATIC_ROOT = BASE_DIR / 'static'
|
STATIC_ROOT = BASE_DIR / 'static'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LOGIN_URL = '/users/login/request/'
|
LOGIN_URL = '/users/login/request/'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
||||||
|
|
@ -236,7 +243,7 @@ CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = 'users.User'
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
CSRF_TRUSTED_ORIGINS = ['http://*.gooyal.com', 'https://*.gooyal.com', 'https://*.gooyal.ir']
|
CSRF_TRUSTED_ORIGINS = ['http://*.gooyal.com', 'https://*.gooyal.com']
|
||||||
|
|
||||||
JALALI_DATE_DEFAULTS = {
|
JALALI_DATE_DEFAULTS = {
|
||||||
'Strftime': {
|
'Strftime': {
|
||||||
|
|
@ -255,8 +262,4 @@ CACHES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
|
|
||||||
from main.other_settings.logging import LOGGING
|
|
||||||
|
|
||||||
ALLOWED_NEGATIVE_BALANCE_APPLICATIONS = config('ALLOWED_NEGATIVE_BALANCE_APPLICATIONS', cast=lambda v: [s.strip() for s in v.split(',')])
|
ALLOWED_NEGATIVE_BALANCE_APPLICATIONS = config('ALLOWED_NEGATIVE_BALANCE_APPLICATIONS', cast=lambda v: [s.strip() for s in v.split(',')])
|
||||||
|
|
@ -11,6 +11,6 @@ import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wallet.settings')
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
Loading…
Add table
Reference in a new issue