application gateway policy
This commit is contained in:
parent
82e35a98f7
commit
556cdaf20a
10 changed files with 356 additions and 95 deletions
|
|
@ -23,6 +23,14 @@ class BasePGModel(models.Model):
|
|||
|
||||
redirect_method = 'GET'
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_pg_page_url(self):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ from django.shortcuts import redirect, get_object_or_404
|
|||
from django.urls import path
|
||||
|
||||
from .constans import StateChoices
|
||||
from .models import PaymentRequest, PNA, Fake
|
||||
from .forms import ApplicationGatewayPolicyForm
|
||||
from .models import PaymentRequest, PNA, Fake, ApplicationGatewayPolicy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -112,6 +113,12 @@ class PaymentRequestAdmin(admin.ModelAdmin):
|
|||
class FakeAdmin(admin.ModelAdmin):
|
||||
list_display = [ 'created_at']
|
||||
|
||||
|
||||
class ApplicationGatewayPolicyAdmin(admin.ModelAdmin):
|
||||
form = ApplicationGatewayPolicyForm
|
||||
|
||||
|
||||
admin.site.register(PaymentRequest, PaymentRequestAdmin)
|
||||
admin.site.register(PNA, PNAAdmin)
|
||||
admin.site.register(Fake, FakeAdmin)
|
||||
admin.site.register(Fake, FakeAdmin)
|
||||
admin.site.register(ApplicationGatewayPolicy, ApplicationGatewayPolicyAdmin)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from django import forms
|
||||
|
||||
from .models import PaymentRequest
|
||||
from .models import PaymentRequest, GatewayChoise
|
||||
|
||||
|
||||
class PaymentRequestForm(forms.ModelForm):
|
||||
|
|
@ -8,3 +8,44 @@ class PaymentRequestForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = PaymentRequest
|
||||
fields = []
|
||||
|
||||
|
||||
class DelimitedListField(forms.MultipleChoiceField):
|
||||
"""
|
||||
Django form field that allows for the use of list widgets with a text field
|
||||
containing a delimited list.
|
||||
"""
|
||||
delimiter = ','
|
||||
|
||||
def __init__(self, delimiter=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.delimiter = delimiter or self.delimiter
|
||||
|
||||
def prepare_value(self, value):
|
||||
# If the value is already a list or tuple, just use it as-is
|
||||
if isinstance(value, (list, tuple)): return value
|
||||
# Otherwise, prepare the value by splitting on the delimiter, trimming
|
||||
# leading and trailing whitespace and excluding empty values
|
||||
return [p.strip() for p in value.split(self.delimiter) if p.strip()]
|
||||
|
||||
def clean(self, value):
|
||||
# Let the parent clean the value first, then join the result using the
|
||||
# specified delimiter
|
||||
return self.delimiter.join(super().clean(value))
|
||||
|
||||
|
||||
class ApplicationGatewayPolicyForm(forms.ModelForm):
|
||||
"""
|
||||
Form for creating or updating a restricted application.
|
||||
"""
|
||||
# allowed_scope is a space-delimited list, but we want to present
|
||||
# a selection of valid scopes with checkboxes
|
||||
preferred_gateways = DelimitedListField(
|
||||
label='preferred gateways',
|
||||
# The choices and initial values are callables, because the scopes might
|
||||
# not be available at import type, e.g. if coming from the database
|
||||
choices=GatewayChoise.choices,
|
||||
initial=GatewayChoise.choices,
|
||||
delimiter=' ',
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# Generated by Django 5.1.4 on 2026-04-22 19:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pg', '0009_remove_sep_sep_order_id_remove_sep_sep_originator_and_more'),
|
||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='paymentrequest',
|
||||
name='gateway',
|
||||
field=models.CharField(blank=True, choices=[('fake', 'fake ipg'), ('pna', 'pna ipg'), ('sep', 'sep ipg')], max_length=255),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ApplicationGatewayPolicy',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||
('preferred_gateways', models.CharField(blank=True, default='', max_length=100, null=True)),
|
||||
('policy', models.CharField(choices=[('random', 'random'), ('selectable', 'selectable')], default='selectable', max_length=20)),
|
||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL, unique=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -22,18 +22,22 @@ from utils.pna import PNAClient
|
|||
from utils.sep import SEPClient
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GatewayChoise(TextChoices):
|
||||
FAKE = "fake", _("fake")
|
||||
PNA = 'pna', _('pna')
|
||||
SEP = 'sep', _('sep')
|
||||
# FAKE = "fake", _("fake ipg")
|
||||
PNA = 'pna', _('pna ipg')
|
||||
SEP = 'sep', _('sep ipg')
|
||||
|
||||
|
||||
class GatewayPolicy(BasePGModel):
|
||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True, blank=True, unique=True)
|
||||
preference_gateway = models.JSONField(default=list, blank=True)
|
||||
def get_gateway_class(gateway_label):
|
||||
return {
|
||||
# "fake": Fake,
|
||||
"pna": PNA,
|
||||
"sep": SEP,
|
||||
}[gateway_label]
|
||||
|
||||
|
||||
# TODO: use generic relation
|
||||
|
|
@ -44,7 +48,7 @@ class PaymentRequest(models.Model):
|
|||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True, blank=True)
|
||||
|
||||
gateway = models.CharField(max_length=255, choices=GatewayChoise.choices)
|
||||
gateway = models.CharField(max_length=255, choices=GatewayChoise.choices, blank=True)
|
||||
|
||||
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
|
|
@ -83,38 +87,36 @@ class PaymentRequest(models.Model):
|
|||
self.commission = calculate_pna_commission(self.amount)
|
||||
self.save(update_fields=['commission'])
|
||||
|
||||
def get_policy(self):
|
||||
application_gateway, created = ApplicationGatewayPolicy.objects.get_or_create(application=self.application)
|
||||
return application_gateway
|
||||
|
||||
def get_preferred_gateways(self):
|
||||
application_policy = self.get_policy()
|
||||
return application_policy.get_preferred_gateways()
|
||||
|
||||
def get_preferred_gateway_classes(self):
|
||||
preferred_gateways = self.get_preferred_gateways()
|
||||
return [get_gateway_class(gateway_label) for gateway_label in preferred_gateways]
|
||||
|
||||
|
||||
def get_gateway_tools(self):
|
||||
if self.gateway == GatewayChoise.PNA:
|
||||
return PNA.objects.get(payment_request=self)
|
||||
|
||||
if self.gateway == GatewayChoise.SEP:
|
||||
return SEP.objects.get(payment_request=self)
|
||||
|
||||
if self.gateway == GatewayChoise.FAKE:
|
||||
return Fake.objects.get(payment_request=self)
|
||||
gateway_class = get_gateway_class(self.gateway)
|
||||
return gateway_class.objects.get(payment_request=self)
|
||||
|
||||
def set_gateway(self, data):
|
||||
preferred_gateways = self.get_preferred_gateways()
|
||||
for gateway_label in preferred_gateways:
|
||||
if gateway_label in data:
|
||||
self.gateway = gateway_label
|
||||
gateway_class = get_gateway_class(gateway_label)
|
||||
pg = gateway_class.objects.create(payment_request=self)
|
||||
pg.payment_request = self
|
||||
|
||||
# if 'fake' in data:
|
||||
# self.gateway = GatewayChoise.FAKE
|
||||
# fake = Fake.objects.create(payment_request=self)
|
||||
# fake.payment_request = self
|
||||
#
|
||||
self.save()
|
||||
|
||||
if 'pna' in data:
|
||||
self.gateway = GatewayChoise.PNA
|
||||
pg = PNA.objects.create(payment_request=self)
|
||||
pg.payment_request = self
|
||||
|
||||
elif 'sep' in data:
|
||||
self.gateway = GatewayChoise.SEP
|
||||
pg = SEP.objects.create(payment_request=self)
|
||||
pg.payment_request = self
|
||||
|
||||
else:
|
||||
raise Exception('Unknown gateway')
|
||||
|
||||
self.save()
|
||||
if not self.gateway:
|
||||
raise Exception('No gateway')
|
||||
|
||||
@property
|
||||
def reference_number(self):
|
||||
|
|
@ -181,7 +183,8 @@ class PaymentRequest(models.Model):
|
|||
raise Exception('Failed to get response uuid from wallet service')
|
||||
|
||||
if verify_response.state != 5:
|
||||
logger.error(f"[Deposit Verify] Wallet verification failed | payment_id={self.pk}, response={verify_response}")
|
||||
logger.error(
|
||||
f"[Deposit Verify] Wallet verification failed | payment_id={self.pk}, response={verify_response}")
|
||||
# to_pay_failed = PaymentRequest.objects.filter(
|
||||
# pk=self.pk,
|
||||
# state=StateChoices.WALLET_SUBMIT_SUCCESS.value
|
||||
|
|
@ -225,6 +228,8 @@ class PaymentRequest(models.Model):
|
|||
|
||||
|
||||
class PNA(BasePGModel):
|
||||
label = GatewayChoise.PNA.value
|
||||
image = f'image/{GatewayChoise.PNA.value}.png'
|
||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='pna')
|
||||
|
||||
details = models.JSONField(null=True, blank=True, default=dict)
|
||||
|
|
@ -248,7 +253,6 @@ class PNA(BasePGModel):
|
|||
|
||||
return to_jalali(self.created_at)
|
||||
|
||||
|
||||
def make_request(self):
|
||||
self.payment_request.change_state(StateChoices.CREATED, StateChoices.IPG_PENDING, 'can not change state')
|
||||
amount = self.payment_request.amount + self.payment_request.commission
|
||||
|
|
@ -271,7 +275,8 @@ class PNA(BasePGModel):
|
|||
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
|
||||
'error in pardakht novin gateway')
|
||||
except Exception as e:
|
||||
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED, 'error in pardakht novin gateway')
|
||||
self.payment_request.change_state(self.payment_request.state, StateChoices.FAILED,
|
||||
'error in pardakht novin gateway')
|
||||
|
||||
self.save()
|
||||
|
||||
|
|
@ -322,6 +327,9 @@ class PNA(BasePGModel):
|
|||
|
||||
|
||||
class SEP(BasePGModel):
|
||||
label = GatewayChoise.SEP.value
|
||||
image = f'image/{GatewayChoise.SEP.value}.png'
|
||||
|
||||
redirect_method = 'GET'
|
||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='sep')
|
||||
|
||||
|
|
@ -421,6 +429,9 @@ class SEP(BasePGModel):
|
|||
|
||||
|
||||
class Fake(BasePGModel):
|
||||
label = GatewayChoise.FAKE.value
|
||||
image = f'image/{GatewayChoise.FAKE.value}.png'
|
||||
|
||||
payment_request = models.OneToOneField(PaymentRequest, on_delete=models.PROTECT, related_name='+')
|
||||
|
||||
def make_request(self):
|
||||
|
|
@ -438,3 +449,17 @@ class Fake(BasePGModel):
|
|||
|
||||
def reverse_request(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ApplicationGatewayPolicy(BaseModel):
|
||||
class PolicyChoise(TextChoices):
|
||||
RANDOM = "random", _("random")
|
||||
SELECTABLE = 'selectable', _('selectable')
|
||||
|
||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True, blank=True, unique=True)
|
||||
preferred_gateways = models.CharField(max_length=100, null=True, blank=True, default='')
|
||||
policy = models.CharField(max_length=20, choices=PolicyChoise.choices, default=PolicyChoise.SELECTABLE)
|
||||
|
||||
def get_preferred_gateways(self):
|
||||
return [p.strip() for p in self.preferred_gateways.split() if p.strip()]
|
||||
|
|
|
|||
|
|
@ -74,16 +74,56 @@ class PaymentRequestView(UpdateView):
|
|||
slug_field = 'uuid'
|
||||
slug_url_kwarg = 'uuid'
|
||||
|
||||
# TODO: remove it in future
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# charge_request: PaymentRequest = self.get_object()
|
||||
# if charge_request.state == StateChoices.FAILED:
|
||||
# return render(request, 'pg/payment_failure.html', {
|
||||
# 'object': charge_request
|
||||
# })
|
||||
# redirect_url = tool.get_pg_page_url()
|
||||
# return HttpResponseRedirect(redirect_url)
|
||||
#
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
charge_request: PaymentRequest = self.get_object()
|
||||
# TODO: check more states
|
||||
# TODO: prevent double
|
||||
if charge_request.state == StateChoices.FAILED:
|
||||
return render(request, 'pg/payment_failure.html', {
|
||||
'object': charge_request
|
||||
})
|
||||
|
||||
# # gateway was setted in api call
|
||||
# # TODO: double check
|
||||
# if charge_request.gateway and not charge_request.commission:
|
||||
# charge_request.save_commission()
|
||||
# gateway_tool = charge_request.get_gateway_tools()
|
||||
# gateway_tool.make_request()
|
||||
# return HttpResponseRedirect(gateway_tool.redirect_url)
|
||||
|
||||
|
||||
application_policy = charge_request.get_policy()
|
||||
preferred_gateways = application_policy.get_preferred_gateways()
|
||||
if preferred_gateways:
|
||||
if application_policy.policy == application_policy.PolicyChoise.RANDOM:
|
||||
# TODO: move to model
|
||||
import random
|
||||
gateway = random.choices(preferred_gateways)
|
||||
charge_request.set_gateway(gateway)
|
||||
charge_request.save_commission()
|
||||
gateway_tool = charge_request.get_gateway_tools()
|
||||
gateway_tool.make_request()
|
||||
redirect_url = gateway_tool.get_pg_page_url()
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
elif len(preferred_gateways) == 1:
|
||||
gateway = preferred_gateways[0]
|
||||
charge_request.set_gateway(gateway)
|
||||
charge_request.save_commission()
|
||||
gateway_tool = charge_request.get_gateway_tools()
|
||||
gateway_tool.make_request()
|
||||
redirect_url = gateway_tool.get_pg_page_url()
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
else:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
return render(request, 'pg/payment_failure.html', {
|
||||
'object': charge_request
|
||||
})
|
||||
|
||||
# # TODO: remove it in future
|
||||
# def post(self, request, *args, **kwargs):
|
||||
# charge_request: PaymentRequest = self.get_object()
|
||||
|
|
|
|||
0
main/other_settings/__init__.py
Normal file
0
main/other_settings/__init__.py
Normal file
142
main/other_settings/logging.py
Normal file
142
main/other_settings/logging.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import os
|
||||
|
||||
def get_settings(key):
|
||||
from django.conf import settings
|
||||
return getattr(settings, 'BASE_DIR')
|
||||
|
||||
|
||||
def get_logging_setting():
|
||||
BASE_DIR = get_settings('BASE_DIR')
|
||||
|
||||
LOG_DIR = BASE_DIR / 'log'
|
||||
if not os.path.exists(LOG_DIR):
|
||||
os.makedirs(LOG_DIR)
|
||||
|
||||
return {
|
||||
'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,
|
||||
# },
|
||||
# },
|
||||
# }
|
||||
|
|
@ -257,40 +257,8 @@ CACHES = {
|
|||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'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": "ipg"}, # 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
from main.other_settings.logging import get_logging_setting
|
||||
LOGGING = get_logging_setting()
|
||||
|
||||
PNA_CORPORATION_PIN = config('PNA_CORPORATION_PIN')
|
||||
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL')
|
||||
|
|
|
|||
|
|
@ -22,17 +22,11 @@
|
|||
<h6 class="h6">پرداخت با:</h6>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
{# <div class="">#}
|
||||
<button type="submit" class="btn mx-auto center-block" name="pna">
|
||||
<img class="img-thumbnail" width="128" height="128" src="{% static "image/pna.png" %}"/>
|
||||
{% for gateway_tool in object.get_preferred_gateway_classes %}
|
||||
<button type="submit" class="btn mx-auto center-block" name="{{ gateway_tool.label }}">
|
||||
<img class="img-thumbnail" width="128" height="128" src="{% static gateway_tool.image %}"/>
|
||||
</button>
|
||||
{# <button type="submit" class="btn mx-auto center-block" name="sep">#}
|
||||
{# <img class="img-thumbnail" width="128" height="128" src="{% static "image/sep.png" %}"/>#}
|
||||
{# </button>#}
|
||||
{# <button type="submit" class="btn mx-auto center-block" name="fake">#}
|
||||
{# <img class="img-thumbnail" width="128" height="128" src="{% static "image/fake.png" %}"/>#}
|
||||
{# </button>#}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue