Compare commits
43 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fba0cf3b64 | |||
| 2fae79eb04 | |||
| 11327db537 | |||
| 1d555cd031 | |||
| fb4c79c5c6 | |||
| 5b278bddd2 | |||
| 8328814dca | |||
| 5d897b0917 | |||
| 5eff037d10 | |||
|
|
d9b7dbe1b2 | ||
|
|
e6eafbd6dc | ||
| 2d8e0da6fa | |||
| 7b1e9376b4 | |||
| 615a568294 | |||
| a8b32a16f4 | |||
|
|
0a179bf350 | ||
| e27fe27bf0 | |||
| e3413e33ab | |||
| 7e378e5b54 | |||
|
|
e5f22d1659 | ||
| 8acf7bc6c1 | |||
| fd244950e1 | |||
|
|
cc5b0568a9 | ||
|
|
a5639b5426 | ||
|
|
2caae02a3c | ||
| 53cc3f67ee | |||
| fb00595d23 | |||
|
|
1d961efb69 | ||
|
|
15fe0d4531 | ||
|
|
e045ffd9b1 | ||
|
|
27db599e8c | ||
|
|
95df80f593 | ||
|
|
1e3a69ece1 | ||
|
|
ff3cbcf162 | ||
|
|
7e8e35c746 | ||
|
|
b8e0cd09fc | ||
|
|
c24b52e341 | ||
| 132a3afcbf | |||
|
|
4393e3878a | ||
|
|
bd3438528d | ||
|
|
dae2a2e518 | ||
|
|
9183b1aa78 | ||
|
|
a35bd6b202 |
60 changed files with 1614 additions and 203 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ utils/delme.py
|
||||||
*.pyc
|
*.pyc
|
||||||
.env
|
.env
|
||||||
/venv/
|
/venv/
|
||||||
|
/.venv/
|
||||||
|
|
|
||||||
15
Dockerfile
15
Dockerfile
|
|
@ -1,9 +1,12 @@
|
||||||
FROM debian:12
|
FROM base:debian13
|
||||||
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
|
||||||
COPY requirements.txt /app/requirements.txt
|
#RUN apt install binutils libproj-dev gdal-bin -y
|
||||||
RUN pip3 install --break-system-packages -r requirements.txt
|
#COPY requirements.txt /app/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"]
|
||||||
|
|
|
||||||
8
Dockerfile.build
Normal file
8
Dockerfile.build
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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"]
|
||||||
8
Dockerfile.run
Normal file
8
Dockerfile.run
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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"]
|
||||||
18
apps/core/migrations/0002_alter_charge_state.py
Normal file
18
apps/core/migrations/0002_alter_charge_state.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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,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):
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,111 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Transaction, Account, Wallet
|
from .models import Transaction, Account, Wallet
|
||||||
|
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', 'amount', 'created_at', 'updated_at', 'payer', 'payer_type', 'payee', 'payee_type', 'state', 'application']
|
list_display = ['uuid', 'application', 'splited_amount', 'jalali_datetime', 'payer', 'payer_type', 'payee', 'payee_type', 'state', 'amount', 'created_at', 'updated_at']
|
||||||
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_account__owner_uuid', 'payee_account__owner_uuid', 'application__uuid']
|
||||||
list_filter = ['state']
|
list_filter = ['application', 'state', ('created_at', JalaliDateTimeRangeFilter), ('updated_at', JalaliDateTimeRangeFilter),]
|
||||||
|
actions = [export_excel]
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
css = {'all': ('admin/css/django_jalali.min.css',)}
|
||||||
|
js = ('admin/js/django_jalali.min.js',)
|
||||||
|
|
||||||
class AccountAdmin(admin.ModelAdmin):
|
class AccountAdmin(admin.ModelAdmin):
|
||||||
list_display = ['uuid', 'owner_uuid', 'wallet', 'owner_type', 'balance',]
|
list_display = ['uuid', 'owner_uuid', 'wallet', 'owner_type', 'balance', 'get_name']
|
||||||
search_fields = ['uuid', 'owner_uuid']
|
search_fields = ['uuid', 'owner_uuid']
|
||||||
list_filter = ['wallet__label', 'owner_type']
|
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(Account, AccountAdmin)
|
||||||
|
|
|
||||||
46
apps/wallet/admin_filters.py
Normal file
46
apps/wallet/admin_filters.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
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,6 +10,8 @@ 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):
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class UserTransactionFilter(django_filters.FilterSet):
|
||||||
|
|
||||||
|
|
||||||
class AccountFilter(django_filters.FilterSet):
|
class AccountFilter(django_filters.FilterSet):
|
||||||
wallet = WalletUUIDFilter(field_name='wallet', required=True, lookup_expr='in')
|
wallets = WalletUUIDFilter(field_name='wallet', required=True, lookup_expr='in')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/wallet/migrations/0018_alter_transaction_state.py
Normal file
18
apps/wallet/migrations/0018_alter_transaction_state.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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,11 +1,16 @@
|
||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
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
|
||||||
|
|
@ -20,6 +25,7 @@ from ..gooyal_oauth2.models import Application
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
||||||
|
|
||||||
|
|
@ -27,10 +33,11 @@ class CurrencyChoices(models.TextChoices):
|
||||||
class AccountQuerySet(models.QuerySet):
|
class AccountQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseModel):
|
class Account(BaseModel):
|
||||||
owner_uuid = models.UUIDField(db_index=True, null=False)
|
owner_uuid = models.UUIDField(db_index=True, null=False)
|
||||||
wallet = models.ForeignKey('Wallet', 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
|
# TODO: unique together wallet and owner_uuid
|
||||||
|
|
@ -46,10 +53,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 WalletQuerySet(models.QuerySet):
|
class WalletQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Wallet(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)
|
||||||
|
|
@ -57,6 +70,9 @@ class Wallet(BaseModel):
|
||||||
|
|
||||||
objects = WalletQuerySet.as_manager()
|
objects = WalletQuerySet.as_manager()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.label
|
||||||
|
|
||||||
|
|
||||||
# TODO: move DRF functionalities to view or serializers
|
# TODO: move DRF functionalities to view or serializers
|
||||||
# class TransactionManager(models.Manager):
|
# class TransactionManager(models.Manager):
|
||||||
|
|
@ -92,6 +108,39 @@ class TransactionQuerySet(models.QuerySet):
|
||||||
def available(self):
|
def available(self):
|
||||||
return self.select_related('payer_account', 'payee_account')
|
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)])
|
||||||
|
|
@ -100,13 +149,15 @@ class Transaction(BaseModel):
|
||||||
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_account = models.ForeignKey(Account, on_delete=models.PROTECT, null=True,
|
||||||
related_name='payer_account_transactions',
|
related_name='payer_account_transactions',
|
||||||
db_index=True)
|
db_index=True)
|
||||||
payee_account = models.ForeignKey(Account, on_delete=models.PROTECT, null=True,
|
payee_account = models.ForeignKey(Account, on_delete=models.PROTECT, null=True,
|
||||||
related_name='payee_account_transactions',
|
related_name='payee_account_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'}
|
||||||
|
|
||||||
|
|
@ -114,6 +165,13 @@ 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:
|
||||||
|
|
@ -123,6 +181,27 @@ 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_account.get_owner()
|
||||||
|
|
||||||
|
|
@ -153,18 +232,26 @@ class Transaction(BaseModel):
|
||||||
def payee_type(self):
|
def payee_type(self):
|
||||||
return self.payee_account.owner_type
|
return self.payee_account.owner_type
|
||||||
|
|
||||||
def change_state(self, from_state, to_state, error_message=None, **data):
|
def change_state(self, from_states: list | tuple, to_state, same_ok=False, raise_exception=True,
|
||||||
if self.state == from_state:
|
error_message=None,
|
||||||
updated = Transaction.objects.filter(pk=self.pk, state=from_state).update(
|
data: None | dict = None):
|
||||||
|
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
|
||||||
else:
|
return self
|
||||||
raise APIException(error_message)
|
|
||||||
else:
|
if raise_exception:
|
||||||
raise APIException(error_message)
|
raise Exception(error_message)
|
||||||
|
|
||||||
def withdraw_from_payer_balance(self):
|
def withdraw_from_payer_balance(self):
|
||||||
logger.debug('start withdraw_from_payer_balance')
|
logger.debug('start withdraw_from_payer_balance')
|
||||||
|
|
@ -173,8 +260,23 @@ class Transaction(BaseModel):
|
||||||
if self.amount > self.payer_account.balance and not payer_is_allowed_negative_balance:
|
if self.amount > self.payer_account.balance and not payer_is_allowed_negative_balance:
|
||||||
logger.error('not enough balance')
|
logger.error('not enough balance')
|
||||||
raise UnprocessableEntity('not enough balance')
|
raise UnprocessableEntity('not enough balance')
|
||||||
logger.info(f'payer_account balance {self.payee_account} -> {self.payer_account.balance} - {self.amount}')
|
|
||||||
self.payer_account.balance = F('balance') - self.amount
|
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:
|
||||||
|
updated = Account.objects.filter(pk=self.payer_account.pk).update(balance=F('balance') - self.amount)
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
self.payer_account.refresh_from_db()
|
||||||
|
logger.info(f'payer_account balance {self.payee_account} -> {self.payer_account.balance} - {self.amount}')
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise UnprocessableEntity('not enough balance')
|
||||||
|
|
||||||
|
def rollback_withdraw_from_payer_balance(self):
|
||||||
|
logger.debug('start rollback withdraw_from_payer_balance')
|
||||||
|
logger.info(f'payer_account balance {self.payer_account} -> {self.amount}')
|
||||||
|
self.payer_account.balance = F('balance') + self.amount
|
||||||
self.payer_account.save(update_fields=['balance'])
|
self.payer_account.save(update_fields=['balance'])
|
||||||
|
|
||||||
def deposit_to_payee_balance(self):
|
def deposit_to_payee_balance(self):
|
||||||
|
|
@ -183,6 +285,12 @@ class Transaction(BaseModel):
|
||||||
self.payee_account.balance = F('balance') + self.amount
|
self.payee_account.balance = F('balance') + self.amount
|
||||||
self.payee_account.save(update_fields=['balance'])
|
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
|
# 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):
|
||||||
|
|
@ -206,20 +314,24 @@ 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',
|
||||||
payer_id=payer_id,
|
data={
|
||||||
payer_type=payer_type
|
"payer_id": payer_id,
|
||||||
|
"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',
|
||||||
payer_id=payer_id,
|
data={
|
||||||
payer_type=payer_type
|
"payer_id": payer_id,
|
||||||
|
"payer_type": payer_type
|
||||||
|
}
|
||||||
)
|
)
|
||||||
self.deposit_to_payee_balance()
|
self.deposit_to_payee_balance()
|
||||||
|
|
||||||
|
|
@ -233,7 +345,7 @@ class Transaction(BaseModel):
|
||||||
|
|
||||||
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'
|
||||||
)
|
)
|
||||||
|
|
@ -248,18 +360,18 @@ class Transaction(BaseModel):
|
||||||
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_account.wallet.currency == self.payee_account.wallet.currency:
|
||||||
self.change_state(from_state=self.state, to_state=StateChoices.FAILED)
|
self.change_state([self.state], StateChoices.FAILED)
|
||||||
logger.info(f'payer({self.payer_account.wallet.currency}) and payee({self.payee_account.wallet.currency}) account currency not equal')
|
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 account 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})')
|
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.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')
|
||||||
)
|
)
|
||||||
|
|
@ -272,12 +384,46 @@ class Transaction(BaseModel):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.deposit_to_payee_balance()
|
self.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_account.owner_uuid == entity.uuid:
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -337,3 +483,29 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -223,3 +223,20 @@ 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']
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ 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',
|
||||||
|
|
@ -93,9 +94,10 @@ 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.application_access_token.token)
|
auth = self._create_authorization_header(self.payer_access_token.token)
|
||||||
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
wallets = [self.wallet_rial_uuid.hex, self.wallet_reward_uuid.hex]
|
||||||
response = self.client.get(reverse('wallet:user_wallet_account_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
response = self.client.get(reverse('wallet:user_wallet_account_api'), query_params={'wallet': ','.join(wallets)}, HTTP_AUTHORIZATION=auth)
|
||||||
|
print(response.json())
|
||||||
self.assertGreater(response.json()['count'], 1)
|
self.assertGreater(response.json()['count'], 1)
|
||||||
from_wallet = response.json()['results'][0]
|
from_wallet = response.json()['results'][0]
|
||||||
self.assertEqual(from_wallet['balance'], 300)
|
self.assertEqual(from_wallet['balance'], 300)
|
||||||
|
|
@ -239,6 +241,7 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
}
|
}
|
||||||
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet),
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet),
|
||||||
data=data, HTTP_AUTHORIZATION=auth)
|
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):
|
||||||
|
|
@ -303,6 +306,7 @@ class ApplicationApiFlowsTests(APITestCase):
|
||||||
'payer_wallet': self.wallet_rial_uuid,
|
'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)
|
response = self.client.post(reverse('wallet:application_deposit_submit_api', kwargs=kwargs_payer_wallet), 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_payer_wallet), data=data, HTTP_AUTHORIZATION=auth)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
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/wallets/', user_views.WalletAccountListAPIView.as_view(), name='user_wallet_account_api'),
|
||||||
path('api/transactions/', user_views.TransactionListAPIView.as_view(), name='user_transaction_list_api'),
|
|
||||||
|
|
||||||
# path('api/<wallet>/balance', user_views.WalletMyAccountBalanceView.as_view(), name='user_wallet_balance_api'),
|
# 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'),
|
# path('api/<wallet>/transactions/', user_views.WalletTransactionListView.as_view(), name='transaction_list_api'),
|
||||||
|
|
@ -24,13 +29,15 @@ application_urlpatterns = [
|
||||||
|
|
||||||
path('api/application/<payer_wallet>/deposit/', application_views.DepositSubmitAPIView.as_view(), name='application_deposit_submit_api'),
|
path('api/application/<payer_wallet>/deposit/', application_views.DepositSubmitAPIView.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/<payer_wallet>/deposit/<uuid>/verify', application_views.DepositVerifyAPIView.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/<payee_wallet>/withdraw/', application_views.WithdrawSubmitAPIView.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/<payee_wallet>/withdraw/<uuid>/verify', application_views.WithdrawVerifyAPIView.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/wallets/<wallet>/', application_views.WalletTransactionListAPIView.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/wallets/<wallet>/<uuid>/', application_views.WalletTransactionDetailAPIView.as_view(), name='application_transaction_detail'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = user_urlpatterns + application_urlpatterns
|
urlpatterns += user_urlpatterns + application_urlpatterns
|
||||||
|
|
@ -259,7 +259,33 @@ class DepositVerifyAPIView(generics.RetrieveAPIView):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance.state == StateChoices.PENDING:
|
if instance.state == StateChoices.PENDING:
|
||||||
instance.verify()
|
instance.verify()
|
||||||
logger.warning(f"Transaction was retrieved for {instance.uuid}: {instance.state}")
|
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)
|
||||||
|
|
||||||
|
|
@ -359,3 +385,25 @@ class WithdrawVerifyAPIView(generics.RetrieveAPIView):
|
||||||
|
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class WithdrawRollbackAPIView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [TokenHasScope]
|
||||||
|
serializer_class = ApplicationWithdrawSerializer
|
||||||
|
lookup_field = 'uuid'
|
||||||
|
required_scopes = ['wallet.application.withdraw:rollback']
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
application = get_application(self.request)
|
||||||
|
payee_wallet_uuid = self.kwargs.get('payee_wallet')
|
||||||
|
wallet = get_object_or_404(Wallet.objects.all(), uuid=payee_wallet_uuid)
|
||||||
|
payee_account = Account.objects.get(owner_uuid=application.uuid, owner_type=TypeChoices.APPLICATION, wallet=wallet)
|
||||||
|
return Transaction.objects.filter(application=application, payee_account=payee_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)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,29 @@
|
||||||
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
|
from rest_framework import generics, permissions, mixins
|
||||||
|
from rest_framework.decorators import action
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.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, Wallet, Account
|
||||||
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
|
from apps.wallet.serializers import TransactionSerializer, DepositSerializer, WithdrawSerializer, \
|
||||||
AccountSerializer
|
AccountSerializer, TransactionGroupedSerializer
|
||||||
from ..constans import StateChoices, TypeChoices
|
from ..constans import StateChoices, TypeChoices
|
||||||
from ..filters import AccountFilter, UserTransactionFilter
|
from ..filters import AccountFilter, UserTransactionFilter
|
||||||
from ..forms import TransactionActionForm
|
from ..forms import TransactionActionForm
|
||||||
|
|
@ -57,7 +65,7 @@ class TransactionList(generics.ListAPIView):
|
||||||
required_alternate_scopes = {
|
required_alternate_scopes = {
|
||||||
"GET": [['wallet.user:transaction_list']],
|
"GET": [['wallet.user:transaction_list']],
|
||||||
# "POST": [['wallet.invoice:create']],
|
# "POST": [['wallet.invoice:create']],
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = UserTransactionFilter
|
filterset_class = UserTransactionFilter
|
||||||
|
|
@ -66,7 +74,6 @@ 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
|
||||||
|
|
@ -166,6 +173,7 @@ class WithdrawSubmit(generics.CreateAPIView):
|
||||||
payee = self.request.auth.application.user
|
payee = self.request.auth.application.user
|
||||||
serializer.save(payee=payee, application=get_application(self.request))
|
serializer.save(payee=payee, application=get_application(self.request))
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactor
|
# TODO: refactor
|
||||||
class WithdrawVerify(generics.RetrieveAPIView):
|
class WithdrawVerify(generics.RetrieveAPIView):
|
||||||
permission_classes = [TokenHasScope]
|
permission_classes = [TokenHasScope]
|
||||||
|
|
@ -226,7 +234,6 @@ class TransactionDetailView(UpdateView):
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WalletAccountListAPIView(generics.ListAPIView):
|
class WalletAccountListAPIView(generics.ListAPIView):
|
||||||
'''
|
'''
|
||||||
API for getting authenticated user balance.
|
API for getting authenticated user balance.
|
||||||
|
|
@ -238,7 +245,7 @@ class WalletAccountListAPIView(generics.ListAPIView):
|
||||||
required_alternate_scopes = {
|
required_alternate_scopes = {
|
||||||
"GET": [['wallet.user:transaction_list']],
|
"GET": [['wallet.user:transaction_list']],
|
||||||
# "POST": [['wallet.invoice:create']],
|
# "POST": [['wallet.invoice:create']],
|
||||||
}
|
}
|
||||||
queryset = Account.objects.all()
|
queryset = Account.objects.all()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
@ -267,7 +274,7 @@ class TransactionListAPIView(generics.ListAPIView):
|
||||||
required_alternate_scopes = {
|
required_alternate_scopes = {
|
||||||
"GET": [['wallet.user:transaction_list']],
|
"GET": [['wallet.user:transaction_list']],
|
||||||
# "POST": [['wallet.invoice:create']],
|
# "POST": [['wallet.invoice:create']],
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = UserTransactionFilter
|
filterset_class = UserTransactionFilter
|
||||||
|
|
@ -293,7 +300,7 @@ class WalletTransactionListView(generics.ListAPIView):
|
||||||
required_alternate_scopes = {
|
required_alternate_scopes = {
|
||||||
"GET": [['wallet.user:transaction_list']],
|
"GET": [['wallet.user:transaction_list']],
|
||||||
# "POST": [['wallet.invoice:create']],
|
# "POST": [['wallet.invoice:create']],
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = UserTransactionFilter
|
filterset_class = UserTransactionFilter
|
||||||
|
|
@ -306,3 +313,60 @@ class WalletTransactionListView(generics.ListAPIView):
|
||||||
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
|
return Transaction.objects.filter(Q(payee_account=account) | Q(payer_account=account)).all()
|
||||||
|
|
||||||
|
|
||||||
|
class UserTransactionViewSet(mixins.ListModelMixin,
|
||||||
|
# mixins.CreateModelMixin,
|
||||||
|
# mixins.RetrieveModelMixin,
|
||||||
|
# mixins.UpdateModelMixin,
|
||||||
|
# mixins.DestroyModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
serializer_class = TransactionSerializer
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"GET": [['wallet.user:transaction_list']],
|
||||||
|
# "POST": [['wallet.invoice:create']],
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_backends = (DjangoFilterBackend,)
|
||||||
|
filterset_class = UserTransactionFilter
|
||||||
|
queryset = Transaction.objects.all()
|
||||||
|
|
||||||
|
# TODO: redundant
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
return Transaction.objects.filter(Q(payee_account__owner_uuid=user.uuid) |
|
||||||
|
Q(payer_account__owner_uuid=user.uuid)).all()
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains endpoint functions for accessing the API"""
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains endpoint functions for accessing the API"""
|
||||||
151
gooyal_accounts_client/api/users/users_api_inquiry_create.py
Normal file
151
gooyal_accounts_client/api/users/users_api_inquiry_create.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
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,7 +2,10 @@
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -10,11 +13,15 @@ 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",
|
||||||
|
|
@ -22,4 +29,5 @@ __all__ = (
|
||||||
"PublicUser",
|
"PublicUser",
|
||||||
"RequestOTP",
|
"RequestOTP",
|
||||||
"RequestOTT",
|
"RequestOTT",
|
||||||
|
"UserInquiry",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
|
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")
|
||||||
|
|
@ -24,6 +30,10 @@ 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]):
|
||||||
|
|
@ -42,6 +52,10 @@ 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
|
||||||
|
|
@ -81,6 +95,40 @@ 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
|
||||||
|
|
@ -133,6 +181,14 @@ 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:
|
||||||
|
|
@ -147,8 +203,8 @@ class Account:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
uuid = UUID(d.pop("uuid"))
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
email = d.pop("email")
|
email = d.pop("email")
|
||||||
|
|
@ -190,6 +246,82 @@ 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
|
||||||
|
|
@ -247,6 +379,10 @@ 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,4 +1,5 @@
|
||||||
from typing import Any, TypeVar
|
from collections.abc import Mapping
|
||||||
|
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
|
||||||
|
|
@ -13,6 +14,7 @@ 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):
|
||||||
|
|
@ -20,6 +22,7 @@ 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
|
||||||
|
|
@ -30,6 +33,9 @@ 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)
|
||||||
|
|
@ -42,6 +48,7 @@ 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,
|
||||||
|
|
@ -51,12 +58,19 @@ class Application:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
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"))
|
||||||
|
|
@ -66,6 +80,7 @@ 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,
|
||||||
|
|
|
||||||
8
gooyal_accounts_client/models/blank_enum.py
Normal file
8
gooyal_accounts_client/models/blank_enum.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class BlankEnum(str, Enum):
|
||||||
|
VALUE_0 = ""
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
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 +44,8 @@ class ChangePassword:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
old_password = d.pop("old_password")
|
old_password = d.pop("old_password")
|
||||||
|
|
||||||
new_password = d.pop("new_password")
|
new_password = d.pop("new_password")
|
||||||
|
|
|
||||||
14
gooyal_accounts_client/models/education_degree_enum.py
Normal file
14
gooyal_accounts_client/models/education_degree_enum.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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)
|
||||||
9
gooyal_accounts_client/models/gender_enum.py
Normal file
9
gooyal_accounts_client/models/gender_enum.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class GenderEnum(str, Enum):
|
||||||
|
F = "f"
|
||||||
|
M = "m"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
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
|
||||||
|
|
@ -64,10 +65,10 @@ class PaginatedApplicationList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
from ..models.application import Application
|
from ..models.application import Application
|
||||||
|
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
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
|
||||||
|
|
@ -64,10 +65,10 @@ class PaginatedPublicUserList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
from ..models.public_user import PublicUser
|
from ..models.public_user import PublicUser
|
||||||
|
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
|
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")
|
||||||
|
|
@ -21,6 +27,10 @@ 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]):
|
||||||
|
|
@ -39,6 +49,10 @@ 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
|
||||||
|
|
@ -76,6 +90,40 @@ 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
|
||||||
|
|
@ -135,6 +183,14 @@ 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:
|
||||||
|
|
@ -155,8 +211,8 @@ class PatchedAccount:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
_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):
|
||||||
|
|
@ -192,6 +248,82 @@ 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
|
||||||
|
|
@ -259,6 +391,10 @@ 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,3 +1,4 @@
|
||||||
|
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
|
||||||
|
|
@ -42,8 +43,8 @@ class PatchedChangePassword:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
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,3 +1,4 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -58,8 +59,8 @@ class PublicUser:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
|
|
||||||
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,4 +1,5 @@
|
||||||
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
|
||||||
|
|
@ -46,8 +47,8 @@ class RequestOTP:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
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,4 +1,5 @@
|
||||||
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 +48,8 @@ class RequestOTT:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
d = src_dict.copy()
|
d = dict(src_dict)
|
||||||
application = d.pop("application")
|
application = d.pop("application")
|
||||||
|
|
||||||
def _parse_ott(data: object) -> Union[None, str]:
|
def _parse_ott(data: object) -> Union[None, str]:
|
||||||
|
|
|
||||||
68
gooyal_accounts_client/models/user_inquiry.py
Normal file
68
gooyal_accounts_client/models/user_inquiry.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
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
|
||||||
50
log/accounts.log
Normal file
50
log/accounts.log
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
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
|
||||||
0
log/errors.log
Normal file
0
log/errors.log
Normal file
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import os
|
import os
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wallet.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
||||||
|
|
||||||
from utils.broker import get_rpc_broker_consumer
|
from utils.broker import get_rpc_broker_consumer
|
||||||
|
|
||||||
0
main/other_settings/__init__.py
Normal file
0
main/other_settings/__init__.py
Normal file
141
main/other_settings/logging.py
Normal file
141
main/other_settings/logging.py
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
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,
|
||||||
|
# },
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
@ -16,7 +16,6 @@ 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/
|
||||||
|
|
||||||
|
|
@ -38,6 +37,7 @@ 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,7 +47,8 @@ INSTALLED_APPS = [
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'crispy_bootstrap5',
|
'crispy_bootstrap5',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'jalali_date',
|
'rangefilter',
|
||||||
|
# 'jalali_date',
|
||||||
|
|
||||||
# local apps
|
# local apps
|
||||||
'apps.core',
|
'apps.core',
|
||||||
|
|
@ -70,7 +71,6 @@ 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,8 +126,7 @@ 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 = [
|
||||||
{
|
{
|
||||||
|
|
@ -150,7 +149,7 @@ AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
WSGI_APPLICATION = 'wallet.wsgi.application'
|
WSGI_APPLICATION = 'main.wsgi.application'
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
@ -191,7 +190,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
|
@ -209,7 +207,6 @@ 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/
|
||||||
|
|
||||||
|
|
@ -224,17 +221,13 @@ if DEBUG == True:
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'static',
|
||||||
)
|
)
|
||||||
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 = '/'
|
||||||
|
|
||||||
|
|
@ -243,7 +236,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']
|
CSRF_TRUSTED_ORIGINS = ['http://*.gooyal.com', 'https://*.gooyal.com', 'https://*.gooyal.ir']
|
||||||
|
|
||||||
JALALI_DATE_DEFAULTS = {
|
JALALI_DATE_DEFAULTS = {
|
||||||
'Strftime': {
|
'Strftime': {
|
||||||
|
|
@ -264,40 +257,6 @@ CACHES = {
|
||||||
|
|
||||||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
||||||
|
|
||||||
|
from main.other_settings.logging import LOGGING
|
||||||
|
|
||||||
LOGGING = {
|
ALLOWED_NEGATIVE_BALANCE_APPLICATIONS = config('ALLOWED_NEGATIVE_BALANCE_APPLICATIONS', cast=lambda v: [s.strip() for s in v.split(',')])
|
||||||
'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': 1, # 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": "wallet"}, # 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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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', 'wallet.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wallet.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.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,6 +18,9 @@ django-jalali-date
|
||||||
django-betterforms
|
django-betterforms
|
||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
crispy_bootstrap5
|
crispy_bootstrap5
|
||||||
psycopg[binary]
|
psycopg[binary,pool]
|
||||||
django-redis
|
django-redis
|
||||||
httpx
|
httpx
|
||||||
|
packaging==25.0
|
||||||
|
xlsxwriter
|
||||||
|
django-admin-rangefilter
|
||||||
133
requirements.txt
133
requirements.txt
|
|
@ -1,37 +1,35 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.11
|
# This file is autogenerated by pip-compile with Python 3.13
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile
|
# pip-compile --cert=None --client-cert=None --index-url=None --pip-args=None
|
||||||
#
|
#
|
||||||
amqp==5.3.1
|
amqp==5.3.1
|
||||||
# via kombu
|
# via kombu
|
||||||
anyio==4.8.0
|
anyio==4.12.1
|
||||||
# via httpx
|
# via httpx
|
||||||
asgiref==3.8.1
|
asgiref==3.11.1
|
||||||
# via
|
# via
|
||||||
# django
|
# django
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
async-timeout==5.0.1
|
attrs==25.4.0
|
||||||
# via redis
|
|
||||||
attrs==25.2.0
|
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
billiard==4.2.1
|
billiard==4.2.4
|
||||||
# via celery
|
# via celery
|
||||||
celery==5.4.0
|
celery==5.6.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
certifi==2025.1.31
|
certifi==2026.1.4
|
||||||
# via
|
# via
|
||||||
# httpcore
|
# httpcore
|
||||||
# httpx
|
# httpx
|
||||||
# requests
|
# requests
|
||||||
cffi==1.17.1
|
cffi==2.0.0
|
||||||
# via cryptography
|
# via cryptography
|
||||||
charset-normalizer==3.4.1
|
charset-normalizer==3.4.4
|
||||||
# via requests
|
# via requests
|
||||||
click==8.1.8
|
click==8.3.1
|
||||||
# via
|
# via
|
||||||
# celery
|
# celery
|
||||||
# click-didyoumean
|
# click-didyoumean
|
||||||
|
|
@ -39,15 +37,15 @@ click==8.1.8
|
||||||
# click-repl
|
# click-repl
|
||||||
click-didyoumean==0.3.1
|
click-didyoumean==0.3.1
|
||||||
# via celery
|
# via celery
|
||||||
click-plugins==1.1.1
|
click-plugins==1.1.1.2
|
||||||
# via celery
|
# via celery
|
||||||
click-repl==0.3.0
|
click-repl==0.3.0
|
||||||
# via celery
|
# via celery
|
||||||
crispy-bootstrap5==2024.10
|
crispy-bootstrap5==2025.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
cryptography==44.0.2
|
cryptography==46.0.4
|
||||||
# via jwcrypto
|
# via jwcrypto
|
||||||
django==5.1.7
|
django==6.0.2
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
|
|
@ -62,39 +60,39 @@ django==5.1.7
|
||||||
# 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.7.0
|
django-cors-headers==4.9.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-crispy-forms==2.3
|
django-crispy-forms==2.5
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
django-filter==25.1
|
django-filter==25.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-jalali-date==1.1.3
|
django-jalali-date==2.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-oauth-toolkit==3.0.1
|
django-oauth-toolkit==3.2.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==5.4.0
|
django-redis==6.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.16.1
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
drf-spectacular==0.28.0
|
drf-spectacular==0.29.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
gevent==24.11.1
|
gevent==25.9.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
greenlet==3.1.1
|
greenlet==3.3.1
|
||||||
# via gevent
|
# via gevent
|
||||||
gunicorn==23.0.0
|
gunicorn==25.0.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
h11==0.14.0
|
h11==0.16.0
|
||||||
# via httpcore
|
# via httpcore
|
||||||
httpcore==1.0.7
|
httpcore==1.0.9
|
||||||
# via httpx
|
# via httpx
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.10
|
idna==3.11
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
# httpx
|
# httpx
|
||||||
|
|
@ -105,81 +103,82 @@ 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.23.0
|
jsonschema==4.26.0
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
jsonschema-specifications==2024.10.1
|
jsonschema-specifications==2025.9.1
|
||||||
# via jsonschema
|
# via jsonschema
|
||||||
jwcrypto==1.5.6
|
jwcrypto==1.5.6
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
kombu==5.4.2
|
kombu==5.6.2
|
||||||
# via celery
|
# via celery
|
||||||
markdown==3.7
|
markdown==3.10.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
oauthlib==3.2.2
|
oauthlib==3.3.1
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
packaging==24.2
|
packaging==25.0
|
||||||
# via gunicorn
|
# via
|
||||||
pillow==11.1.0
|
# -r requirements.in
|
||||||
|
# gunicorn
|
||||||
|
# kombu
|
||||||
|
pillow==12.1.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
prompt-toolkit==3.0.50
|
prompt-toolkit==3.0.52
|
||||||
# via click-repl
|
# via click-repl
|
||||||
psycopg[binary]==3.2.6
|
psycopg[binary,pool]==3.3.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
psycopg-binary==3.2.6
|
psycopg-binary==3.3.2
|
||||||
# via psycopg
|
# via psycopg
|
||||||
pycparser==2.22
|
psycopg-pool==3.3.0
|
||||||
|
# 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.2
|
pyyaml==6.0.3
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
redis==5.2.1
|
redis==7.1.0
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-redis
|
# django-redis
|
||||||
referencing==0.36.2
|
referencing==0.37.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# jsonschema-specifications
|
# jsonschema-specifications
|
||||||
requests==2.32.3
|
requests==2.32.5
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
rpds-py==0.23.1
|
rpds-py==0.30.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
# via python-dateutil
|
# via python-dateutil
|
||||||
sniffio==1.3.1
|
sqlparse==0.5.5
|
||||||
# via anyio
|
|
||||||
sqlparse==0.5.3
|
|
||||||
# via django
|
# via django
|
||||||
typing-extensions==4.12.2
|
typing-extensions==4.15.0
|
||||||
# via
|
# via
|
||||||
# anyio
|
|
||||||
# jwcrypto
|
# jwcrypto
|
||||||
# psycopg
|
# psycopg-pool
|
||||||
# referencing
|
tzdata==2025.3
|
||||||
tzdata==2025.1
|
# via kombu
|
||||||
# via
|
tzlocal==5.3.1
|
||||||
# celery
|
# via celery
|
||||||
# kombu
|
uritemplate==4.2.0
|
||||||
uritemplate==4.1.1
|
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
urllib3==2.3.0
|
urllib3==2.6.3
|
||||||
# via requests
|
# via requests
|
||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
# via
|
# via
|
||||||
# amqp
|
# amqp
|
||||||
# celery
|
# celery
|
||||||
# kombu
|
# kombu
|
||||||
wcwidth==0.2.13
|
wcwidth==0.6.0
|
||||||
# via prompt-toolkit
|
# via prompt-toolkit
|
||||||
zope-event==5.0
|
zope-event==6.1
|
||||||
# via gevent
|
# via gevent
|
||||||
zope-interface==7.2
|
zope-interface==8.2
|
||||||
# via gevent
|
# via gevent
|
||||||
|
xlsxwriter==3.2.3
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# via -r requirements.in
|
||||||
# setuptools
|
django-admin-rangefilter==0.13.5
|
||||||
|
|
|
||||||
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 wallet.wsgi:application --bind 0.0.0.0:8000 -w 4
|
gunicorn main.wsgi:application --bind 0.0.0.0:8000 -w 4
|
||||||
2
static/admin/css/django_jalali.min.css
vendored
Executable file
2
static/admin/css/django_jalali.min.css
vendored
Executable file
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
|
|
@ -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,7 +8,8 @@ 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.types import Response
|
from gooyal_accounts_client.models import Account
|
||||||
|
from gooyal_accounts_client.api.users import (users_api_users_details_retrieve)
|
||||||
|
|
||||||
|
|
||||||
def login_as_client_credentials():
|
def login_as_client_credentials():
|
||||||
|
|
@ -60,6 +61,13 @@ 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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue