+ CurrencyAPIClient for fresh CNY rate

+ yuan_rate_last_updated, yuan_rate_commission fields in GlobalSettings
This commit is contained in:
Phil Zhitnikov 2023-11-23 02:16:53 +04:00
parent a4f8dfc27c
commit 2e79e579f7
6 changed files with 112 additions and 6 deletions

57
external_api/currency.py Normal file
View File

@ -0,0 +1,57 @@
from decimal import Decimal
from contextlib import suppress
from urllib.parse import urljoin
import requests
from django.conf import settings
class CurrencyAPIClient:
CONVERT_ENDPOINT = 'currency/convert'
MAX_RETRIES = 2
def __init__(self, api_key: str):
self.api_url = 'https://api.getgeoapi.com/v2/'
self.api_key = api_key
self.session = requests.Session()
def request(self, method, url, *args, **kwargs):
params = kwargs.pop('params', {})
params.update({"api_key": self.api_key})
joined_url = urljoin(self.api_url, url)
request = requests.Request(method, joined_url, params=params, *args, **kwargs)
retries = 0
while retries < self.MAX_RETRIES:
retries += 1
prepared = self.session.prepare_request(request)
r = self.session.send(prepared)
# TODO: handle/log errors
return r
def get_rate(self, currency1: str, currency2: str, amount=1):
params = {
'from': currency1,
'to': currency2,
'amount': amount,
'format': 'json'
}
r = self.request('GET', self.CONVERT_ENDPOINT, params=params)
if not r or r.json().get('status') == 'failed':
return None
with suppress(KeyError):
rate = r.json()['rates'][currency2.upper()]['rate']
return Decimal(rate)
return None
def get_cny_rate(self):
return self.get_rate('cny', 'rub')
client = CurrencyAPIClient(settings.CURRENCY_GETGEOIP_API_KEY)

View File

@ -29,6 +29,8 @@ CDEK_CLIENT_SECRET = 'lc2gmrmK5s1Kk6FhZbNqpQCaATQRlsOy'
POIZON_TOKEN = 'IRwNgBxb8YQ'
CURRENCY_GETGEOIP_API_KEY = '136a69df7e4f419c97783288068e5a2f1ef4ad6d'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(int(os.environ.get("DJANGO_DEBUG") or 0))
DISABLE_PERMISSIONS = False
@ -198,6 +200,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CHECKLIST_ID_LENGTH = 10
COMMISSION_OVER_150K = 1.1
YUAN_RATE_UPDATE_PERIOD_MINUTES = 60
# Logging
SENTRY_DSN = "https://96106e3f938badc86ecb2e502716e496@o4506163299418112.ingest.sentry.io/4506163300663296"

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.2 on 2023-11-22 22:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('store', '0047_alter_checklist_gift'),
]
operations = [
migrations.AddField(
model_name='globalsettings',
name='yuan_rate_commission',
field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Наценка на курс юаня, руб'),
),
migrations.AddField(
model_name='globalsettings',
name='yuan_rate_last_updated',
field=models.DateTimeField(default=None, null=True, verbose_name='Дата обновления курса CNY/RUB'),
),
]

View File

@ -23,6 +23,7 @@ from django_cleanup import cleanup
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
from external_api.currency import client as CurrencyAPIClient
from store.utils import create_preview, concat_not_null_values
from utils.cache import InMemoryCache
@ -30,6 +31,8 @@ from utils.cache import InMemoryCache
class GlobalSettings(models.Model):
# currency
yuan_rate = models.DecimalField('Курс CNY/RUB', max_digits=10, decimal_places=2, default=0)
yuan_rate_last_updated = models.DateTimeField('Дата обновления курса CNY/RUB', null=True, default=None)
yuan_rate_commission = models.DecimalField('Наценка на курс юаня, руб', max_digits=10, decimal_places=2, default=0)
# Chinadelivery
delivery_price_CN = models.DecimalField('Цена доставки по Китаю', max_digits=10, decimal_places=2, default=0)
commission_rub = models.DecimalField('Комиссия, руб', max_digits=10, decimal_places=2, default=0)
@ -63,6 +66,26 @@ class GlobalSettings(models.Model):
InMemoryCache.set('GlobalSettings', obj)
return obj
def get_yuan_rate(self):
""" Get rate either from CurrencyAPI or from DB (if fresh enough) """
if self.yuan_rate_last_updated is not None:
diff_minutes = (timezone.now() - self.yuan_rate_last_updated).total_seconds() / 60
else:
diff_minutes = None
if diff_minutes is None or diff_minutes > settings.YUAN_RATE_UPDATE_PERIOD_MINUTES:
# Get fresh rate from API
rate = CurrencyAPIClient.get_cny_rate()
if rate:
# Save rate in DB for future usage
self.yuan_rate = rate
self.yuan_rate_last_updated = timezone.now()
self.save()
return rate
# Default
return self.yuan_rate
class Category(MPTTModel):
name = models.CharField('Название', max_length=20)
@ -271,7 +294,7 @@ class ChecklistQuerySet(models.QuerySet):
return self.annotate(
_yuan_rate=Case(
When(price_snapshot_id__isnull=False, then=F('price_snapshot__yuan_rate')),
default=GlobalSettings.load().yuan_rate
default=GlobalSettings.load().get_yuan_rate()
),
_price_rub=Ceil(F('_yuan_rate') * F('price_yuan'))
)
@ -448,7 +471,7 @@ class Checklist(models.Model):
if self.price_snapshot_id:
yuan_rate = self.price_snapshot.yuan_rate
else:
yuan_rate = GlobalSettings.load().yuan_rate
yuan_rate = GlobalSettings.load().get_yuan_rate()
return math.ceil(yuan_rate * self.price_yuan)
@ -483,7 +506,7 @@ class Checklist(models.Model):
if self.price_snapshot_id:
return self.price_snapshot.yuan_rate
else:
return GlobalSettings.load().yuan_rate
return GlobalSettings.load().get_yuan_rate()
@property
def delivery_price_CN(self) -> Decimal:

View File

@ -206,14 +206,14 @@ class AnonymousUserChecklistSerializer(ChecklistSerializer):
class GlobalSettingsSerializer(serializers.ModelSerializer):
currency = serializers.DecimalField(source='yuan_rate', max_digits=10, decimal_places=2)
currency = serializers.DecimalField(source='get_yuan_rate', read_only=True, max_digits=10, decimal_places=2)
chinadelivery = serializers.DecimalField(source='delivery_price_CN', max_digits=10, decimal_places=2)
commission = serializers.DecimalField(source='commission_rub', max_digits=10, decimal_places=2)
pickup = serializers.CharField(source='pickup_address')
class Meta:
model = GlobalSettings
fields = ('currency', 'commission', 'chinadelivery', 'pickup', 'time_to_buy')
fields = ('currency', 'yuan_rate_commission', 'commission', 'chinadelivery', 'pickup', 'time_to_buy')
class PaymentMethodSerializer(serializers.ModelSerializer):

View File

@ -202,7 +202,7 @@ class StatisticsAPI(viewsets.GenericViewSet):
@action(url_path='orders', detail=False, methods=['get'])
def stats_by_orders(self, request, *args, **kwargs):
global_settings = GlobalSettings.load()
yuan_rate = global_settings.yuan_rate
yuan_rate = global_settings.get_yuan_rate()
# Prepare query to collect the stats
qs = self.get_queryset() \