+ time_to_buy & buy_time_remaining fields
+ Category: hierarchy, removed slugs & added comission per category * Promocode value in rubles instead of percentage * Cleanup in GlobalSettings routes
This commit is contained in:
parent
e370a2097a
commit
3a6b06d223
|
|
@ -2,6 +2,7 @@
|
|||
Django==4.2.2
|
||||
django-cleanup==8.0.0
|
||||
django-filter==23.2
|
||||
django-mptt==0.14.0
|
||||
djangorestframework==3.14.0
|
||||
django-cors-headers==4.1.0
|
||||
djoser==2.2.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin import display
|
||||
from mptt.admin import MPTTModelAdmin
|
||||
|
||||
from .models import Category, Checklist, GlobalSettings, PaymentMethod, Promocode, User, Image, Client
|
||||
|
||||
from .models import Category, Checklist, GlobalSettings, PaymentMethod, Promocode, User, Image
|
||||
|
||||
|
|
@ -10,8 +13,8 @@ class UserAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('slug', 'name')
|
||||
class CategoryAdmin(MPTTModelAdmin):
|
||||
list_display = ('name', 'delivery_price_CN_RU', 'commission')
|
||||
ordering = ('id',)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,19 +3,104 @@ from tqdm import tqdm
|
|||
|
||||
from store.models import Category, PaymentMethod
|
||||
|
||||
|
||||
category_names = {
|
||||
"shoes": "Обувь",
|
||||
"outerwear": "Верхняя одежда",
|
||||
"underwear": "Нижнее белье",
|
||||
"bags": "Сумки",
|
||||
"cosmetics": "Косметика",
|
||||
"accessories": "Аксессуары",
|
||||
"technics": "Техника",
|
||||
"watches": "Часы",
|
||||
"toys": "Игрушки",
|
||||
"home": "Товары для дома",
|
||||
"foodndrinks": "Еда и напитки",
|
||||
"different": "Другое",
|
||||
"Обувь": [
|
||||
"Кроссовки",
|
||||
"Кеды",
|
||||
"Пляжная обувь",
|
||||
"Туфли",
|
||||
"Босоножки",
|
||||
"Сандвли",
|
||||
"Лоаферы",
|
||||
"Мокасины",
|
||||
"Ботинки",
|
||||
"Полуботинки",
|
||||
"Сапоги",
|
||||
"Домашняя обувь",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Верхняя одежда": [
|
||||
"Куртка летняя",
|
||||
"Куртка зимняя",
|
||||
"Худи",
|
||||
"Толстовка",
|
||||
"Футболка",
|
||||
"Майка",
|
||||
"Лонгслив",
|
||||
"Рубашка",
|
||||
"Платье",
|
||||
"Жилетка",
|
||||
"Свитер",
|
||||
"Топ",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Нижнее белье": [
|
||||
"Брюки",
|
||||
"Штаны",
|
||||
"Джинсы",
|
||||
"Леггинсы",
|
||||
"Шорты",
|
||||
"Юбка",
|
||||
"Колготки",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Сумки": [
|
||||
"Женская сумка",
|
||||
"Мужская сумка",
|
||||
"Рюкзак",
|
||||
"Кошелек",
|
||||
"Визитница",
|
||||
"Клатч",
|
||||
"Шоппер",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Косметика": [
|
||||
"Для лица",
|
||||
"Для тела",
|
||||
"Для губ",
|
||||
"Для ресниц",
|
||||
"Для волос",
|
||||
"Для бровей",
|
||||
"Для рук",
|
||||
"Для ног",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Аксессуары": [
|
||||
"Шарф",
|
||||
"Шапка",
|
||||
"Кепка",
|
||||
"Очки",
|
||||
"Украшения",
|
||||
"Перчатки",
|
||||
"Носки",
|
||||
"Нижнее белье",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Техника": [
|
||||
"Телефон",
|
||||
"Планшет",
|
||||
"Ноутбук",
|
||||
"Приставка",
|
||||
"Фен",
|
||||
"Скайлер",
|
||||
"Пылесос",
|
||||
"Увлажнитель",
|
||||
"Бытовая техника",
|
||||
"Другое",
|
||||
],
|
||||
|
||||
"Часы": ["Механические", "Электронные"],
|
||||
"Игрушки": ["Lego", "Фигурка", "Мягкая", "Кукла", "Набор", "Другое"],
|
||||
"Товары для дома": ["Полотенце", "Ковер", "Набор", "Косметичка", "Другое"],
|
||||
"Еда и напитки": ["Еда", "Напиток"],
|
||||
"Другое": [],
|
||||
}
|
||||
|
||||
payment_methods = {
|
||||
|
|
@ -41,8 +126,10 @@ class Command(BaseCommand):
|
|||
help = ''' Create root categories '''
|
||||
|
||||
def create_categories(self):
|
||||
for slug, name in tqdm(category_names.items(), desc="Creating categories"):
|
||||
Category.objects.get_or_create(slug=slug, defaults={"name": name})
|
||||
for cat_name, subcat_names in tqdm(category_names.items(), desc="Creating categories"):
|
||||
category, _ = Category.objects.get_or_create(name=cat_name, parent=None)
|
||||
for subcat_name in subcat_names:
|
||||
Category.objects.get_or_create(name=subcat_name, parent_id=category.id)
|
||||
|
||||
def create_payment_types(self):
|
||||
for slug, data in tqdm(payment_methods.items(), desc="Creating payment methods"):
|
||||
|
|
|
|||
35
store/migrations/0041_remove_checklist_category_and_more.py
Normal file
35
store/migrations/0041_remove_checklist_category_and_more.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 4.2.2 on 2023-08-17 23:19
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('store', '0040_alter_paymentmethod_cardnumber_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='checklist',
|
||||
name='category',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='checklist',
|
||||
name='subcategory',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='globalsettings',
|
||||
name='time_to_buy',
|
||||
field=models.DurationField(default=datetime.timedelta(seconds=10800), help_text="Через N времени заказ переходит из статуса 'Новый' в 'Черновик'", verbose_name='Время на покупку'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='promocode',
|
||||
name='discount',
|
||||
field=models.PositiveIntegerField(verbose_name='Скидка в рублях'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Category',
|
||||
),
|
||||
]
|
||||
39
store/migrations/0042_category_checklist_category.py
Normal file
39
store/migrations/0042_category_checklist_category.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Generated by Django 4.2.2 on 2023-08-17 23:20
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('store', '0041_remove_checklist_category_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, verbose_name='Название')),
|
||||
('delivery_price_CN_RU', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Цена доставки Китай-РФ')),
|
||||
('commission', models.DecimalField(decimal_places=2, default=0, max_digits=10, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)], verbose_name='Дополнительная комиссия, %')),
|
||||
('lft', models.PositiveIntegerField(editable=False)),
|
||||
('rght', models.PositiveIntegerField(editable=False)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||
('level', models.PositiveIntegerField(editable=False)),
|
||||
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='store.category', verbose_name='Родительская категория')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Категория',
|
||||
'verbose_name_plural': 'Категории',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='checklist',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='store.category', verbose_name='Категория'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import math
|
||||
import posixpath
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import random
|
||||
import string
|
||||
from io import BytesIO
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.admin import display
|
||||
|
|
@ -18,6 +19,8 @@ from django.db.models.lookups import GreaterThan
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_cleanup import cleanup
|
||||
from mptt.fields import TreeForeignKey
|
||||
from mptt.models import MPTTModel
|
||||
|
||||
from store.utils import create_preview, concat_not_null_values
|
||||
from utils.cache import InMemoryCache
|
||||
|
|
@ -30,6 +33,9 @@ class GlobalSettings(models.Model):
|
|||
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)
|
||||
pickup_address = models.CharField('Адрес пункта самовывоза', max_length=200, blank=True, null=True)
|
||||
time_to_buy = models.DurationField('Время на покупку',
|
||||
help_text="Через N времени заказ переходит из статуса 'Новый' в 'Черновик'",
|
||||
default=timedelta(hours=3))
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Глобальные настройки'
|
||||
|
|
@ -57,14 +63,21 @@ class GlobalSettings(models.Model):
|
|||
return obj
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
class Category(MPTTModel):
|
||||
name = models.CharField('Название', max_length=20)
|
||||
slug = models.SlugField('Идентификатор', unique=True)
|
||||
parent = TreeForeignKey('self', verbose_name='Родительская категория', on_delete=models.SET_NULL, blank=True, null=True, related_name='children', db_index=True)
|
||||
delivery_price_CN_RU = models.DecimalField('Цена доставки Китай-РФ', max_digits=10, decimal_places=2, default=0) # Chinadelivery2
|
||||
commission = models.DecimalField('Дополнительная комиссия, %',
|
||||
max_digits=10, decimal_places=2, default=0,
|
||||
validators=[MinValueValidator(0), MaxValueValidator(100)])
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class MPTTMeta:
|
||||
order_insertion_by = ['id']
|
||||
parent_attr = 'parent'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Категория'
|
||||
verbose_name_plural = 'Категории'
|
||||
|
|
@ -138,8 +151,7 @@ class PromocodeQuerySet(models.QuerySet):
|
|||
|
||||
class Promocode(models.Model):
|
||||
name = models.CharField('Название', max_length=100, unique=True)
|
||||
discount = models.DecimalField('Скидка', max_digits=10, decimal_places=2,
|
||||
validators=[MinValueValidator(0), MaxValueValidator(100)])
|
||||
discount = models.PositiveIntegerField('Скидка в рублях')
|
||||
free_delivery = models.BooleanField('Бесплатная доставка', default=False) # freedelivery
|
||||
no_comission = models.BooleanField('Без комиссии', default=False) # nocomission
|
||||
is_active = models.BooleanField('Активен', default=True)
|
||||
|
|
@ -291,7 +303,6 @@ class Checklist(models.Model):
|
|||
manager = models.ForeignKey('User', verbose_name='Менеджер', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
product_link = models.URLField('Ссылка на товар', null=True, blank=True)
|
||||
category = models.ForeignKey('Category', verbose_name="Категория", blank=True, null=True, on_delete=models.SET_NULL)
|
||||
subcategory = models.CharField('Подкатегория', max_length=20, blank=True, null=True)
|
||||
|
||||
brand = models.CharField('Бренд', max_length=100, null=True, blank=True)
|
||||
model = models.CharField('Модель', max_length=100, null=True, blank=True)
|
||||
|
|
@ -341,6 +352,16 @@ class Checklist(models.Model):
|
|||
verbose_name = 'Заказ'
|
||||
verbose_name_plural = 'Заказы'
|
||||
|
||||
@property
|
||||
def buy_time_remaining(self) -> Optional[timedelta]:
|
||||
if self.status != Checklist.Status.NEW:
|
||||
return None
|
||||
|
||||
time_to_buy = GlobalSettings.load().time_to_buy
|
||||
diff = max(timedelta(), timezone.now() - self.status_updated_at)
|
||||
result = max(timedelta(), time_to_buy - diff)
|
||||
return result
|
||||
|
||||
@property
|
||||
def price_rub(self) -> int:
|
||||
# Prefer annotated field
|
||||
|
|
@ -361,7 +382,7 @@ class Checklist(models.Model):
|
|||
# and intentionally don't check if promocode is active here.
|
||||
# It's also good for archive orders.
|
||||
promocode = self.promocode
|
||||
price -= promocode.discount * self.price_rub / 100
|
||||
price -= min(self.price_rub, promocode.discount)
|
||||
|
||||
free_delivery = promocode.free_delivery
|
||||
no_comission = promocode.no_comission
|
||||
|
|
@ -372,6 +393,11 @@ class Checklist(models.Model):
|
|||
if not no_comission:
|
||||
price += self.commission_rub
|
||||
|
||||
# Add commission of bottom-most category
|
||||
category = self.category.get_ancestors(ascending=True, include_self=True).first()
|
||||
category_commission = getattr(category, 'commission', 0)
|
||||
price += category_commission * self.price_rub / 100
|
||||
|
||||
return max(0, math.ceil(price))
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from django.contrib.auth import authenticate
|
||||
from drf_extra_fields.fields import Base64ImageField
|
||||
from rest_framework import serializers
|
||||
|
||||
from store.exceptions import CRMException, InvalidCredentialsException
|
||||
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode, Image
|
||||
from store.utils import get_primary_key_related_model
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -39,12 +38,36 @@ class ImageListSerializer(serializers.ListSerializer):
|
|||
return [image['image'] for image in images]
|
||||
|
||||
|
||||
class CategoryChecklistSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
chinarush = serializers.DecimalField(source='delivery_price_CN_RU', max_digits=10, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ('id', 'name', 'chinarush', 'commission')
|
||||
|
||||
|
||||
class CategoryFullSerializer(CategorySerializer):
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj):
|
||||
return CategoryFullSerializer(obj.get_children(), many=True).data
|
||||
|
||||
class Meta:
|
||||
model = CategorySerializer.Meta.model
|
||||
fields = CategorySerializer.Meta.fields + ('children',)
|
||||
|
||||
|
||||
class ChecklistSerializer(serializers.ModelSerializer):
|
||||
id = serializers.CharField(read_only=True)
|
||||
managerid = serializers.PrimaryKeyRelatedField(source='manager_id', read_only=True, allow_null=True)
|
||||
link = serializers.URLField(source='product_link', required=False)
|
||||
category = serializers.SlugRelatedField(slug_field='slug', queryset=Category.objects.all(),
|
||||
required=False, allow_null=True)
|
||||
category = get_primary_key_related_model(CategoryChecklistSerializer, required=False, allow_null=True)
|
||||
|
||||
image = ImageListSerializer(source='main_images', required=False)
|
||||
previewimage = serializers.ImageField(source='preview_image_url', read_only=True)
|
||||
|
|
@ -156,7 +179,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Checklist
|
||||
fields = ('id', 'status', 'managerid', 'link',
|
||||
'category', 'subcategory',
|
||||
'category',
|
||||
'brand', 'model', 'size',
|
||||
'image',
|
||||
'previewimage',
|
||||
|
|
@ -168,7 +191,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
|||
'receivername', 'reveiverphone',
|
||||
'split', 'paymenttype', 'paymentprovement', 'checkphoto',
|
||||
'trackid', 'cdek_tracking', 'cdek_barcode_pdf', 'delivery', 'delivery_display',
|
||||
'startDate', 'currentDate',
|
||||
'startDate', 'currentDate', 'buy_time_remaining'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -183,38 +206,15 @@ class AnonymousUserChecklistSerializer(ChecklistSerializer):
|
|||
'recievername', 'recieverphone', 'tg'})
|
||||
|
||||
|
||||
class GlobalSettingsYuanRateSerializer(serializers.ModelSerializer):
|
||||
class GlobalSettingsSerializer(serializers.ModelSerializer):
|
||||
currency = serializers.DecimalField(source='yuan_rate', max_digits=10, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
model = GlobalSettings
|
||||
fields = ('currency',)
|
||||
|
||||
|
||||
class GlobalSettingsPickupSerializer(serializers.ModelSerializer):
|
||||
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 = ('pickup',)
|
||||
|
||||
|
||||
class GlobalSettingsPriceSerializer(serializers.ModelSerializer):
|
||||
commission = serializers.DecimalField(source='commission_rub', max_digits=10, decimal_places=2)
|
||||
chinadelivery = serializers.DecimalField(source='delivery_price_CN', max_digits=10, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
model = GlobalSettings
|
||||
fields = ('commission', 'chinadelivery')
|
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
category = serializers.CharField(source='slug')
|
||||
chinarush = serializers.DecimalField(source='delivery_price_CN_RU', max_digits=10, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ('category', 'chinarush')
|
||||
fields = ('currency', 'commission', 'chinadelivery', 'pickup', 'time_to_buy')
|
||||
|
||||
|
||||
class PaymentMethodSerializer(serializers.ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ urlpatterns = [
|
|||
path("checklist/", views.ChecklistAPI.as_view()),
|
||||
path("checklist/<str:id>", views.ChecklistAPI.as_view()),
|
||||
|
||||
path("currency/", views.YuanRateAPI.as_view()),
|
||||
path("category/", views.CategoryAPI.as_view()),
|
||||
path("pickup/", views.PickupAPI.as_view()),
|
||||
path("category/price/", views.PricesAPI.as_view()),
|
||||
path("category/<int:id>", views.CategoryAPI.as_view()),
|
||||
|
||||
path("payment/", views.PaymentMethodsAPI.as_view()),
|
||||
path("settings/", views.GlobalSettingsAPI.as_view()),
|
||||
|
||||
path("promo/", views.PromoCodeAPI.as_view()),
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import textwrap
|
||||
from typing import Tuple
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError
|
||||
|
||||
from poizonstore.settings import BASE_DIR
|
||||
|
|
@ -125,3 +126,30 @@ def create_preview(source_image: str, size=None, price_rub=None, title_lines=Non
|
|||
|
||||
def concat_not_null_values(*values, separator=' '):
|
||||
return separator.join([v for v in values if v is not None])
|
||||
|
||||
|
||||
def get_primary_key_related_model(model_class, **kwargs):
|
||||
"""
|
||||
Info: https://stackoverflow.com/a/43742949
|
||||
Nested serializers are a mess.
|
||||
This lets us accept ids when saving / updating instead of nested objects.
|
||||
Representation would be into an object (depending on model_class).
|
||||
"""
|
||||
class PrimaryKeyNestedMixin(model_class):
|
||||
default_error_messages = {
|
||||
'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
|
||||
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
|
||||
}
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
return model_class.Meta.model.objects.get(pk=data)
|
||||
except model_class.Meta.model.DoesNotExist:
|
||||
self.fail('does_not_exist', pk_value=data)
|
||||
except (TypeError, ValueError):
|
||||
self.fail('incorrect_type', data_type=type(data).__name__)
|
||||
|
||||
def to_representation(self, data):
|
||||
return model_class.to_representation(self, data)
|
||||
|
||||
return PrimaryKeyNestedMixin(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import calendar
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login
|
||||
from django.db.models import F, Count, Sum
|
||||
from django.utils import timezone
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import generics, permissions, mixins, status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
|
|
@ -15,9 +15,9 @@ from rest_framework.response import Response
|
|||
from cdek.api import CDEKClient
|
||||
from store.exceptions import CRMException
|
||||
from store.models import Checklist, GlobalSettings, Category, PaymentMethod, Promocode
|
||||
from store.serializers import (ChecklistSerializer, GlobalSettingsYuanRateSerializer,
|
||||
CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer,
|
||||
PromocodeSerializer, GlobalSettingsPickupSerializer, AnonymousUserChecklistSerializer)
|
||||
from store.serializers import (ChecklistSerializer, CategorySerializer, CategoryFullSerializer,
|
||||
PaymentMethodSerializer, GlobalSettingsSerializer,
|
||||
PromocodeSerializer, AnonymousUserChecklistSerializer)
|
||||
from utils.permissions import ReadOnly
|
||||
|
||||
|
||||
|
|
@ -63,10 +63,9 @@ class ChecklistAPI(mixins.ListModelMixin,
|
|||
def get_object(self):
|
||||
obj: Checklist = super().get_object()
|
||||
|
||||
# 3 hours maximum in 'neworder' status -> move to drafts
|
||||
if obj.status == Checklist.Status.NEW:
|
||||
diff_hours = (timezone.now() - obj.status_updated_at).seconds / 3600
|
||||
if diff_hours > 3:
|
||||
# N time maximum in 'neworder' status -> move to drafts
|
||||
if obj.status == Checklist.Status.NEW and obj.buy_time_remaining is not None:
|
||||
if obj.buy_time_remaining <= timedelta():
|
||||
obj.status = Checklist.Status.DRAFT
|
||||
obj.save()
|
||||
|
||||
|
|
@ -95,67 +94,23 @@ class ChecklistAPI(mixins.ListModelMixin,
|
|||
return self.destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
class YuanRateAPI(generics.GenericAPIView):
|
||||
serializer_class = GlobalSettingsYuanRateSerializer
|
||||
|
||||
def get_object(self):
|
||||
return GlobalSettings.load()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
yuan_rate = GlobalSettings.load().yuan_rate
|
||||
return Response(data={'currency': yuan_rate})
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class CategoryAPI(generics.GenericAPIView):
|
||||
class CategoryAPI(mixins.ListModelMixin, mixins.UpdateModelMixin, generics.GenericAPIView):
|
||||
serializer_class = CategorySerializer
|
||||
lookup_field = 'id'
|
||||
|
||||
def get_queryset(self):
|
||||
return Category.objects.all()
|
||||
return Category.objects.root_nodes()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
categories_qs = self.get_queryset()
|
||||
global_settings = GlobalSettings.load()
|
||||
|
||||
return Response({
|
||||
'categories': CategorySerializer(categories_qs, many=True).data,
|
||||
'prices': GlobalSettingsPriceSerializer(global_settings).data,
|
||||
})
|
||||
return Response(CategoryFullSerializer(categories_qs, many=True).data)
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
data = request.data
|
||||
if not all(k in data for k in ("category", "chinarush")):
|
||||
raise CRMException('category and chinarush is required')
|
||||
|
||||
instance = get_object_or_404(self.get_queryset(), slug=data['category'])
|
||||
serializer = self.get_serializer(instance, data=data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return Response(serializer.data)
|
||||
return self.partial_update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PricesAPI(generics.GenericAPIView):
|
||||
serializer_class = GlobalSettingsPriceSerializer
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
instance = GlobalSettings.load()
|
||||
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class PickupAPI(DisablePermissionsMixin):
|
||||
serializer_class = GlobalSettingsPickupSerializer
|
||||
class GlobalSettingsAPI(generics.GenericAPIView):
|
||||
serializer_class = GlobalSettingsSerializer
|
||||
permission_classes = [IsAuthenticated | ReadOnly]
|
||||
|
||||
def get_object(self):
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user