+ 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:
Phil Zhitnikov 2023-08-18 16:22:32 +04:00
parent e370a2097a
commit 3a6b06d223
10 changed files with 292 additions and 118 deletions

View File

@ -2,6 +2,7 @@
Django==4.2.2 Django==4.2.2
django-cleanup==8.0.0 django-cleanup==8.0.0
django-filter==23.2 django-filter==23.2
django-mptt==0.14.0
djangorestframework==3.14.0 djangorestframework==3.14.0
django-cors-headers==4.1.0 django-cors-headers==4.1.0
djoser==2.2.0 djoser==2.2.0

View File

@ -1,5 +1,8 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import display 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 from .models import Category, Checklist, GlobalSettings, PaymentMethod, Promocode, User, Image
@ -10,8 +13,8 @@ class UserAdmin(admin.ModelAdmin):
@admin.register(Category) @admin.register(Category)
class CategoryAdmin(admin.ModelAdmin): class CategoryAdmin(MPTTModelAdmin):
list_display = ('slug', 'name') list_display = ('name', 'delivery_price_CN_RU', 'commission')
ordering = ('id',) ordering = ('id',)

View File

@ -3,19 +3,104 @@ from tqdm import tqdm
from store.models import Category, PaymentMethod from store.models import Category, PaymentMethod
category_names = { category_names = {
"shoes": "Обувь", "Обувь": [
"outerwear": "Верхняя одежда", "Кроссовки",
"underwear": "Нижнее белье", "Кеды",
"bags": "Сумки", "Пляжная обувь",
"cosmetics": "Косметика", "Туфли",
"accessories": "Аксессуары", "Босоножки",
"technics": "Техника", "Сандвли",
"watches": "Часы", "Лоаферы",
"toys": "Игрушки", "Мокасины",
"home": "Товары для дома", "Ботинки",
"foodndrinks": "Еда и напитки", "Полуботинки",
"different": "Другое", "Сапоги",
"Домашняя обувь",
"Другое",
],
"Верхняя одежда": [
"Куртка летняя",
"Куртка зимняя",
"Худи",
"Толстовка",
"Футболка",
"Майка",
"Лонгслив",
"Рубашка",
"Платье",
"Жилетка",
"Свитер",
"Топ",
"Другое",
],
"Нижнее белье": [
"Брюки",
"Штаны",
"Джинсы",
"Леггинсы",
"Шорты",
"Юбка",
"Колготки",
"Другое",
],
"Сумки": [
"Женская сумка",
"Мужская сумка",
"Рюкзак",
"Кошелек",
"Визитница",
"Клатч",
"Шоппер",
"Другое",
],
"Косметика": [
"Для лица",
"Для тела",
"Для губ",
"Для ресниц",
"Для волос",
"Для бровей",
"Для рук",
"Для ног",
"Другое",
],
"Аксессуары": [
"Шарф",
"Шапка",
"Кепка",
"Очки",
"Украшения",
"Перчатки",
"Носки",
"Нижнее белье",
"Другое",
],
"Техника": [
"Телефон",
"Планшет",
"Ноутбук",
"Приставка",
"Фен",
"Скайлер",
"Пылесос",
"Увлажнитель",
"Бытовая техника",
"Другое",
],
"Часы": ["Механические", "Электронные"],
"Игрушки": ["Lego", "Фигурка", "Мягкая", "Кукла", "Набор", "Другое"],
"Товары для дома": ["Полотенце", "Ковер", "Набор", "Косметичка", "Другое"],
"Еда и напитки": ["Еда", "Напиток"],
"Другое": [],
} }
payment_methods = { payment_methods = {
@ -41,8 +126,10 @@ class Command(BaseCommand):
help = ''' Create root categories ''' help = ''' Create root categories '''
def create_categories(self): def create_categories(self):
for slug, name in tqdm(category_names.items(), desc="Creating categories"): for cat_name, subcat_names in tqdm(category_names.items(), desc="Creating categories"):
Category.objects.get_or_create(slug=slug, defaults={"name": name}) 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): def create_payment_types(self):
for slug, data in tqdm(payment_methods.items(), desc="Creating payment methods"): for slug, data in tqdm(payment_methods.items(), desc="Creating payment methods"):

View 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',
),
]

View 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='Категория'),
),
]

View File

@ -1,10 +1,11 @@
import math import math
import posixpath import posixpath
from datetime import timedelta
from decimal import Decimal from decimal import Decimal
import random import random
import string import string
from io import BytesIO from io import BytesIO
from typing import Optional
from django.conf import settings from django.conf import settings
from django.contrib.admin import display 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 import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_cleanup import cleanup 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 store.utils import create_preview, concat_not_null_values
from utils.cache import InMemoryCache 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) 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) commission_rub = models.DecimalField('Комиссия, руб', max_digits=10, decimal_places=2, default=0)
pickup_address = models.CharField('Адрес пункта самовывоза', max_length=200, blank=True, null=True) 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: class Meta:
verbose_name = 'Глобальные настройки' verbose_name = 'Глобальные настройки'
@ -57,14 +63,21 @@ class GlobalSettings(models.Model):
return obj return obj
class Category(models.Model): class Category(MPTTModel):
name = models.CharField('Название', max_length=20) 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 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): def __str__(self):
return self.name return self.name
class MPTTMeta:
order_insertion_by = ['id']
parent_attr = 'parent'
class Meta: class Meta:
verbose_name = 'Категория' verbose_name = 'Категория'
verbose_name_plural = 'Категории' verbose_name_plural = 'Категории'
@ -138,8 +151,7 @@ class PromocodeQuerySet(models.QuerySet):
class Promocode(models.Model): class Promocode(models.Model):
name = models.CharField('Название', max_length=100, unique=True) name = models.CharField('Название', max_length=100, unique=True)
discount = models.DecimalField('Скидка', max_digits=10, decimal_places=2, discount = models.PositiveIntegerField('Скидка в рублях')
validators=[MinValueValidator(0), MaxValueValidator(100)])
free_delivery = models.BooleanField('Бесплатная доставка', default=False) # freedelivery free_delivery = models.BooleanField('Бесплатная доставка', default=False) # freedelivery
no_comission = models.BooleanField('Без комиссии', default=False) # nocomission no_comission = models.BooleanField('Без комиссии', default=False) # nocomission
is_active = models.BooleanField('Активен', default=True) 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) manager = models.ForeignKey('User', verbose_name='Менеджер', on_delete=models.SET_NULL, blank=True, null=True)
product_link = models.URLField('Ссылка на товар', null=True, blank=True) product_link = models.URLField('Ссылка на товар', null=True, blank=True)
category = models.ForeignKey('Category', verbose_name="Категория", blank=True, null=True, on_delete=models.SET_NULL) 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) brand = models.CharField('Бренд', max_length=100, null=True, blank=True)
model = 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 = 'Заказ'
verbose_name_plural = 'Заказы' 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 @property
def price_rub(self) -> int: def price_rub(self) -> int:
# Prefer annotated field # Prefer annotated field
@ -361,7 +382,7 @@ class Checklist(models.Model):
# and intentionally don't check if promocode is active here. # and intentionally don't check if promocode is active here.
# It's also good for archive orders. # It's also good for archive orders.
promocode = self.promocode promocode = self.promocode
price -= promocode.discount * self.price_rub / 100 price -= min(self.price_rub, promocode.discount)
free_delivery = promocode.free_delivery free_delivery = promocode.free_delivery
no_comission = promocode.no_comission no_comission = promocode.no_comission
@ -372,6 +393,11 @@ class Checklist(models.Model):
if not no_comission: if not no_comission:
price += self.commission_rub 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)) return max(0, math.ceil(price))
@property @property

View File

@ -1,9 +1,8 @@
from django.contrib.auth import authenticate
from drf_extra_fields.fields import Base64ImageField from drf_extra_fields.fields import Base64ImageField
from rest_framework import serializers from rest_framework import serializers
from store.exceptions import CRMException, InvalidCredentialsException
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode, Image from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode, Image
from store.utils import get_primary_key_related_model
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
@ -39,12 +38,36 @@ class ImageListSerializer(serializers.ListSerializer):
return [image['image'] for image in images] 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): class ChecklistSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True) id = serializers.CharField(read_only=True)
managerid = serializers.PrimaryKeyRelatedField(source='manager_id', read_only=True, allow_null=True) managerid = serializers.PrimaryKeyRelatedField(source='manager_id', read_only=True, allow_null=True)
link = serializers.URLField(source='product_link', required=False) link = serializers.URLField(source='product_link', required=False)
category = serializers.SlugRelatedField(slug_field='slug', queryset=Category.objects.all(), category = get_primary_key_related_model(CategoryChecklistSerializer, required=False, allow_null=True)
required=False, allow_null=True)
image = ImageListSerializer(source='main_images', required=False) image = ImageListSerializer(source='main_images', required=False)
previewimage = serializers.ImageField(source='preview_image_url', read_only=True) previewimage = serializers.ImageField(source='preview_image_url', read_only=True)
@ -156,7 +179,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Checklist model = Checklist
fields = ('id', 'status', 'managerid', 'link', fields = ('id', 'status', 'managerid', 'link',
'category', 'subcategory', 'category',
'brand', 'model', 'size', 'brand', 'model', 'size',
'image', 'image',
'previewimage', 'previewimage',
@ -168,7 +191,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
'receivername', 'reveiverphone', 'receivername', 'reveiverphone',
'split', 'paymenttype', 'paymentprovement', 'checkphoto', 'split', 'paymenttype', 'paymentprovement', 'checkphoto',
'trackid', 'cdek_tracking', 'cdek_barcode_pdf', 'delivery', 'delivery_display', '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'}) 'recievername', 'recieverphone', 'tg'})
class GlobalSettingsYuanRateSerializer(serializers.ModelSerializer): class GlobalSettingsSerializer(serializers.ModelSerializer):
currency = serializers.DecimalField(source='yuan_rate', max_digits=10, decimal_places=2) currency = serializers.DecimalField(source='yuan_rate', max_digits=10, decimal_places=2)
chinadelivery = serializers.DecimalField(source='delivery_price_CN', max_digits=10, decimal_places=2)
class Meta: commission = serializers.DecimalField(source='commission_rub', max_digits=10, decimal_places=2)
model = GlobalSettings
fields = ('currency',)
class GlobalSettingsPickupSerializer(serializers.ModelSerializer):
pickup = serializers.CharField(source='pickup_address') pickup = serializers.CharField(source='pickup_address')
class Meta: class Meta:
model = GlobalSettings model = GlobalSettings
fields = ('pickup',) fields = ('currency', 'commission', 'chinadelivery', 'pickup', 'time_to_buy')
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')
class PaymentMethodSerializer(serializers.ModelSerializer): class PaymentMethodSerializer(serializers.ModelSerializer):

View File

@ -13,11 +13,11 @@ urlpatterns = [
path("checklist/", views.ChecklistAPI.as_view()), path("checklist/", views.ChecklistAPI.as_view()),
path("checklist/<str:id>", 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("category/", views.CategoryAPI.as_view()),
path("pickup/", views.PickupAPI.as_view()), path("category/<int:id>", views.CategoryAPI.as_view()),
path("category/price/", views.PricesAPI.as_view()),
path("payment/", views.PaymentMethodsAPI.as_view()), path("payment/", views.PaymentMethodsAPI.as_view()),
path("settings/", views.GlobalSettingsAPI.as_view()),
path("promo/", views.PromoCodeAPI.as_view()), path("promo/", views.PromoCodeAPI.as_view()),

View File

@ -2,6 +2,7 @@ import os
import textwrap import textwrap
from typing import Tuple from typing import Tuple
from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError
from poizonstore.settings import BASE_DIR 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=' '): def concat_not_null_values(*values, separator=' '):
return separator.join([v for v in values if v is not None]) 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)

View File

@ -1,9 +1,9 @@
import calendar import calendar
from datetime import timedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth import login from django.contrib.auth import login
from django.db.models import F, Count, Sum from django.db.models import F, Count, Sum
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions, mixins, status, viewsets from rest_framework import generics, permissions, mixins, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
@ -15,9 +15,9 @@ from rest_framework.response import Response
from cdek.api import CDEKClient from cdek.api import CDEKClient
from store.exceptions import CRMException from store.exceptions import CRMException
from store.models import Checklist, GlobalSettings, Category, PaymentMethod, Promocode from store.models import Checklist, GlobalSettings, Category, PaymentMethod, Promocode
from store.serializers import (ChecklistSerializer, GlobalSettingsYuanRateSerializer, from store.serializers import (ChecklistSerializer, CategorySerializer, CategoryFullSerializer,
CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer, PaymentMethodSerializer, GlobalSettingsSerializer,
PromocodeSerializer, GlobalSettingsPickupSerializer, AnonymousUserChecklistSerializer) PromocodeSerializer, AnonymousUserChecklistSerializer)
from utils.permissions import ReadOnly from utils.permissions import ReadOnly
@ -63,10 +63,9 @@ class ChecklistAPI(mixins.ListModelMixin,
def get_object(self): def get_object(self):
obj: Checklist = super().get_object() obj: Checklist = super().get_object()
# 3 hours maximum in 'neworder' status -> move to drafts # N time maximum in 'neworder' status -> move to drafts
if obj.status == Checklist.Status.NEW: if obj.status == Checklist.Status.NEW and obj.buy_time_remaining is not None:
diff_hours = (timezone.now() - obj.status_updated_at).seconds / 3600 if obj.buy_time_remaining <= timedelta():
if diff_hours > 3:
obj.status = Checklist.Status.DRAFT obj.status = Checklist.Status.DRAFT
obj.save() obj.save()
@ -95,67 +94,23 @@ class ChecklistAPI(mixins.ListModelMixin,
return self.destroy(request, *args, **kwargs) return self.destroy(request, *args, **kwargs)
class YuanRateAPI(generics.GenericAPIView): class CategoryAPI(mixins.ListModelMixin, mixins.UpdateModelMixin, 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):
serializer_class = CategorySerializer serializer_class = CategorySerializer
lookup_field = 'id'
def get_queryset(self): def get_queryset(self):
return Category.objects.all() return Category.objects.root_nodes()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
categories_qs = self.get_queryset() categories_qs = self.get_queryset()
global_settings = GlobalSettings.load() return Response(CategoryFullSerializer(categories_qs, many=True).data)
return Response({
'categories': CategorySerializer(categories_qs, many=True).data,
'prices': GlobalSettingsPriceSerializer(global_settings).data,
})
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
data = request.data return self.partial_update(request, *args, **kwargs)
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)
class PricesAPI(generics.GenericAPIView): class GlobalSettingsAPI(generics.GenericAPIView):
serializer_class = GlobalSettingsPriceSerializer serializer_class = GlobalSettingsSerializer
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
permission_classes = [IsAuthenticated | ReadOnly] permission_classes = [IsAuthenticated | ReadOnly]
def get_object(self): def get_object(self):