+ statistics by clients
+ cdek_tracking + checklist create API + some permissions * Cleanup
This commit is contained in:
parent
369dbf31ae
commit
4511a65b7f
|
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import display
|
||||||
|
|
||||||
from .models import Category, Checklist, GlobalSettings, PaymentMethod, PromoCode, User, Image
|
from .models import Category, Checklist, GlobalSettings, PaymentMethod, Promocode, User, Image
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
|
|
@ -21,8 +22,12 @@ class ImageAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(Checklist)
|
@admin.register(Checklist)
|
||||||
class ChecklistAdmin(admin.ModelAdmin):
|
class ChecklistAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'date', 'price_rub', 'commission_rub')
|
list_display = ('id', 'brand', 'model', 'price_rub', 'commission_rub', 'date', 'status_display')
|
||||||
filter_horizontal = ('images',)
|
ordering = ('-status_updated_at', '-created_at')
|
||||||
|
|
||||||
|
@display(description='Статус')
|
||||||
|
def status_display(self, obj: Checklist):
|
||||||
|
return obj.get_status_display()
|
||||||
|
|
||||||
def date(self, obj: Checklist):
|
def date(self, obj: Checklist):
|
||||||
return obj.status_updated_at or obj.created_at
|
return obj.status_updated_at or obj.created_at
|
||||||
|
|
@ -41,7 +46,7 @@ class PaymentMethodAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'slug')
|
list_display = ('name', 'slug')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(PromoCode)
|
@admin.register(Promocode)
|
||||||
class PromoCodeAdmin(admin.ModelAdmin):
|
class PromoCodeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'discount', 'free_delivery', 'no_comission')
|
list_display = ('name', 'discount', 'free_delivery', 'no_comission')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
|
|
||||||
class CRMException(APIException):
|
class CRMException(APIException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
|
||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None):
|
||||||
if detail is None:
|
if detail is None:
|
||||||
detail = self.default_detail
|
detail = self.default_detail
|
||||||
|
|
|
||||||
|
|
@ -130,12 +130,15 @@ class User(AbstractUser):
|
||||||
return concat_not_null_values(self.last_name, self.first_name, self.middle_name)
|
return concat_not_null_values(self.last_name, self.first_name, self.middle_name)
|
||||||
|
|
||||||
|
|
||||||
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.DecimalField('Скидка', max_digits=10, decimal_places=2,)
|
||||||
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
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Промокод'
|
verbose_name = 'Промокод'
|
||||||
verbose_name_plural = 'Промокоды'
|
verbose_name_plural = 'Промокоды'
|
||||||
|
|
@ -155,9 +158,11 @@ class PaymentMethod(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
@cleanup.select
|
||||||
class Image(models.Model):
|
class Image(models.Model):
|
||||||
image = models.ImageField(upload_to='checklist_images')
|
image = models.ImageField(upload_to='checklist_images')
|
||||||
is_preview = models.BooleanField(default=False)
|
is_preview = models.BooleanField(default=False)
|
||||||
|
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE, related_name='images')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Изображение'
|
verbose_name = 'Изображение'
|
||||||
|
|
@ -168,6 +173,8 @@ class Image(models.Model):
|
||||||
|
|
||||||
|
|
||||||
def generate_checklist_id():
|
def generate_checklist_id():
|
||||||
|
""" Generate unique id for Checklist """
|
||||||
|
|
||||||
all_ids = Checklist.objects.all().values_list('id', flat=True)
|
all_ids = Checklist.objects.all().values_list('id', flat=True)
|
||||||
allowed_chars = string.ascii_letters + string.digits
|
allowed_chars = string.ascii_letters + string.digits
|
||||||
|
|
||||||
|
|
@ -226,18 +233,6 @@ class Checklist(models.Model):
|
||||||
(COMPLETED, 'Завершен'),
|
(COMPLETED, 'Завершен'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Payment types
|
|
||||||
class PaymentType:
|
|
||||||
ALFA = "alfa"
|
|
||||||
TINKOFF = "tink"
|
|
||||||
RAIFFEISEN = "raif"
|
|
||||||
|
|
||||||
CHOICES = (
|
|
||||||
(ALFA, 'Альфа-Банк'),
|
|
||||||
(TINKOFF, 'Тинькофф Банк'),
|
|
||||||
(RAIFFEISEN, 'Райффайзен Банк'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delivery
|
# Delivery
|
||||||
class DeliveryType:
|
class DeliveryType:
|
||||||
PICKUP = "pickup"
|
PICKUP = "pickup"
|
||||||
|
|
@ -249,7 +244,7 @@ class Checklist(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
status_updated_at = models.DateTimeField('Дата обновления статуса заказа')
|
status_updated_at = models.DateTimeField('Дата обновления статуса заказа', auto_now_add=True)
|
||||||
id = models.CharField(primary_key=True, max_length=settings.CHECKLIST_ID_LENGTH,
|
id = models.CharField(primary_key=True, max_length=settings.CHECKLIST_ID_LENGTH,
|
||||||
default=generate_checklist_id, editable=False)
|
default=generate_checklist_id, editable=False)
|
||||||
status = models.CharField('Статус заказа', max_length=15, choices=Status.CHOICES, default=Status.NEW)
|
status = models.CharField('Статус заказа', max_length=15, choices=Status.CHOICES, default=Status.NEW)
|
||||||
|
|
@ -263,14 +258,12 @@ class Checklist(models.Model):
|
||||||
model = models.CharField('Модель', max_length=100, null=True, blank=True)
|
model = models.CharField('Модель', max_length=100, null=True, blank=True)
|
||||||
size = models.CharField('Размер', max_length=30, null=True, blank=True)
|
size = models.CharField('Размер', max_length=30, null=True, blank=True)
|
||||||
|
|
||||||
images = models.ManyToManyField('Image', verbose_name='Картинки', blank=True)
|
|
||||||
|
|
||||||
# curencycurency2
|
# curencycurency2
|
||||||
price_yuan = models.DecimalField('Цена в юанях', max_digits=10, decimal_places=2, default=0)
|
price_yuan = models.DecimalField('Цена в юанях', max_digits=10, decimal_places=2, default=0)
|
||||||
# TODO: replace by parser
|
# TODO: replace real_price by parser
|
||||||
real_price = models.DecimalField('Реальная цена', max_digits=10, decimal_places=2, null=True, blank=True)
|
real_price = models.DecimalField('Реальная цена', max_digits=10, decimal_places=2, null=True, blank=True)
|
||||||
|
|
||||||
# TODO: choose from PromoCode table
|
# TODO: choose from Promocode table
|
||||||
# promo
|
# promo
|
||||||
promocode = models.CharField('Промокод', max_length=100, null=True, blank=True)
|
promocode = models.CharField('Промокод', max_length=100, null=True, blank=True)
|
||||||
comment = models.CharField('Комментарий', max_length=200, null=True, blank=True)
|
comment = models.CharField('Комментарий', max_length=200, null=True, blank=True)
|
||||||
|
|
@ -296,7 +289,8 @@ class Checklist(models.Model):
|
||||||
|
|
||||||
delivery = models.CharField('Тип доставки', max_length=10, choices=DeliveryType.CHOICES, null=True, blank=True)
|
delivery = models.CharField('Тип доставки', max_length=10, choices=DeliveryType.CHOICES, null=True, blank=True)
|
||||||
# trackid
|
# trackid
|
||||||
track_number = models.CharField('Трек-номер', max_length=100, null=True, blank=True)
|
poizon_tracking = models.CharField('Трек-номер Poizon', max_length=100, null=True, blank=True)
|
||||||
|
cdek_tracking = models.CharField('Трек-номер СДЭК', max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
objects = ChecklistQuerySet.as_manager()
|
objects = ChecklistQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
@ -359,9 +353,9 @@ class Checklist(models.Model):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.id:
|
if self.id:
|
||||||
old_obj = Checklist.objects.get(id=self.id)
|
old_obj = Checklist.objects.filter(id=self.id).first()
|
||||||
# If status was updated, update status_updated_at field
|
# If status was updated, update status_updated_at field
|
||||||
if self.status != old_obj.status:
|
if old_obj and self.status != old_obj.status:
|
||||||
self.status_updated_at = timezone.now()
|
self.status_updated_at = timezone.now()
|
||||||
|
|
||||||
# Create preview image
|
# Create preview image
|
||||||
|
|
@ -375,11 +369,10 @@ class Checklist(models.Model):
|
||||||
preview.save(image_io, format='JPEG')
|
preview.save(image_io, format='JPEG')
|
||||||
|
|
||||||
# Create Image model and save it
|
# Create Image model and save it
|
||||||
image_obj = Image(is_preview=True)
|
image_obj = Image(is_preview=True, checklist_id=self.id)
|
||||||
image_obj.image.save(name=f'{self.id}_preview.jpg',
|
image_obj.image.save(name=f'{self.id}_preview.jpg',
|
||||||
content=ContentFile(image_io.getvalue()),
|
content=ContentFile(image_io.getvalue()),
|
||||||
save=True)
|
save=True)
|
||||||
self.images.add(image_obj)
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
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.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
|
||||||
|
|
||||||
|
|
||||||
class LoginSerializer(serializers.Serializer):
|
class LoginSerializer(serializers.Serializer):
|
||||||
|
|
@ -44,12 +48,14 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class ImageSerializer(serializers.ModelSerializer):
|
class ImageSerializer(serializers.ModelSerializer):
|
||||||
|
image = Base64ImageField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Image
|
model = Image
|
||||||
fields = ('image', )
|
fields = ('image',)
|
||||||
|
|
||||||
|
|
||||||
class OrderImageListSerializer(serializers.ListSerializer):
|
class ChecklistImageListSerializer(serializers.ListSerializer):
|
||||||
child = ImageSerializer()
|
child = ImageSerializer()
|
||||||
|
|
||||||
def to_representation(self, data):
|
def to_representation(self, data):
|
||||||
|
|
@ -59,44 +65,54 @@ class OrderImageListSerializer(serializers.ListSerializer):
|
||||||
|
|
||||||
class ChecklistSerializer(serializers.ModelSerializer):
|
class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.CharField(read_only=True)
|
id = serializers.CharField(read_only=True)
|
||||||
managerid = serializers.CharField(source='manager.manager_id', required=False, allow_null=True)
|
managerid = serializers.CharField(source='manager.manager_id', read_only=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 = serializers.SlugRelatedField(slug_field='slug', queryset=Category.objects.all())
|
||||||
image = OrderImageListSerializer(source='main_images')
|
|
||||||
previewimage = serializers.ImageField(source='preview_image_url')
|
|
||||||
|
|
||||||
# TODO: choose from PromoCode table
|
# image = Base64ImageField(source='images', many=True, queryset=Image.objects.all())
|
||||||
|
previewimage = serializers.ImageField(source='preview_image_url', read_only=True)
|
||||||
|
|
||||||
|
# TODO: choose from Promocode table
|
||||||
promo = serializers.CharField(source='promocode', required=False)
|
promo = serializers.CharField(source='promocode', required=False)
|
||||||
|
|
||||||
currency = serializers.SerializerMethodField('get_yuan_rate')
|
currency = serializers.SerializerMethodField('get_yuan_rate')
|
||||||
curencycurency2 = serializers.DecimalField(source='price_yuan', max_digits=10, decimal_places=2)
|
curencycurency2 = serializers.DecimalField(source='price_yuan', max_digits=10, decimal_places=2)
|
||||||
currency3 = serializers.DecimalField(source='price_rub', max_digits=10, decimal_places=2, read_only=True)
|
currency3 = serializers.DecimalField(source='price_rub', read_only=True, max_digits=10, decimal_places=2)
|
||||||
chinadelivery = serializers.SerializerMethodField('get_delivery_price_CN')
|
chinadelivery = serializers.SerializerMethodField('get_delivery_price_CN', read_only=True)
|
||||||
chinadelivery2 = serializers.DecimalField(source='delivery_price_CN_RU', max_digits=10, decimal_places=2, read_only=True)
|
chinadelivery2 = serializers.DecimalField(source='delivery_price_CN_RU', read_only=True, max_digits=10,
|
||||||
fullprice = serializers.DecimalField(source='full_price', max_digits=10, decimal_places=2)
|
decimal_places=2)
|
||||||
realprice = serializers.DecimalField(source='real_price', max_digits=10, decimal_places=2)
|
fullprice = serializers.DecimalField(source='full_price', read_only=True, max_digits=10, decimal_places=2)
|
||||||
comission = serializers.SerializerMethodField('get_comission')
|
realprice = serializers.DecimalField(source='real_price', required=False, allow_null=True, max_digits=10,
|
||||||
|
decimal_places=2)
|
||||||
|
comission = serializers.SerializerMethodField('get_comission', read_only=True)
|
||||||
|
|
||||||
buyername = serializers.CharField(source='buyer_name')
|
buyername = serializers.CharField(source='buyer_name', required=False, allow_null=True)
|
||||||
buyerphone = serializers.CharField(source='buyer_phone')
|
buyerphone = serializers.CharField(source='buyer_phone', required=False, allow_null=True)
|
||||||
tg = serializers.CharField(source='buyer_telegram')
|
tg = serializers.CharField(source='buyer_telegram', required=False, allow_null=True)
|
||||||
|
|
||||||
receivername = serializers.CharField(source='receiver_name')
|
receivername = serializers.CharField(source='receiver_name', required=False, allow_null=True)
|
||||||
reveiverphone = serializers.CharField(source='receiver_phone')
|
reveiverphone = serializers.CharField(source='receiver_phone', required=False, allow_null=True)
|
||||||
|
|
||||||
paymenttype = serializers.SlugRelatedField(source='payment_method', slug_field='slug', queryset=PaymentMethod.objects.all())
|
paymenttype = serializers.SlugRelatedField(source='payment_method', slug_field='slug',
|
||||||
paymentproovement = serializers.ImageField(source='payment_proof')
|
queryset=PaymentMethod.objects.all(),
|
||||||
checkphoto = serializers.ImageField(source='cheque_photo')
|
required=False, allow_null=True)
|
||||||
trackid = serializers.CharField(source='track_number')
|
paymentproovement = serializers.ImageField(source='payment_proof', required=False, allow_null=True)
|
||||||
delivery = serializers.CharField(source='get_delivery_display')
|
checkphoto = serializers.ImageField(source='cheque_photo', required=False, allow_null=True)
|
||||||
|
trackid = serializers.CharField(source='poizon_tracking', required=False, allow_null=True)
|
||||||
|
cdek_tracking = serializers.CharField(required=False, allow_null=True)
|
||||||
|
delivery = serializers.CharField(source='get_delivery_display', required=False, allow_null=True)
|
||||||
|
|
||||||
startDate = serializers.DateTimeField(source='created_at')
|
startDate = serializers.DateTimeField(source='created_at', read_only=True)
|
||||||
currentDate = serializers.DateTimeField(source='status_updated_at')
|
currentDate = serializers.DateTimeField(source='status_updated_at', read_only=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_yuan_rate(obj: Checklist):
|
def get_yuan_rate(obj: Checklist):
|
||||||
return GlobalSettings.load().yuan_rate
|
return GlobalSettings.load().yuan_rate
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_image(obj: Checklist):
|
||||||
|
return obj.images.all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_delivery_price_CN(obj: Checklist):
|
def get_delivery_price_CN(obj: Checklist):
|
||||||
return GlobalSettings.load().delivery_price_CN
|
return GlobalSettings.load().delivery_price_CN
|
||||||
|
|
@ -110,7 +126,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
fields = ('id', 'status', 'managerid', 'link',
|
fields = ('id', 'status', 'managerid', 'link',
|
||||||
'category', 'subcategory',
|
'category', 'subcategory',
|
||||||
'brand', 'model', 'size',
|
'brand', 'model', 'size',
|
||||||
'image',
|
# 'image',
|
||||||
'previewimage',
|
'previewimage',
|
||||||
'currency', 'curencycurency2', 'currency3', 'chinadelivery', 'chinadelivery2', 'comission',
|
'currency', 'curencycurency2', 'currency3', 'chinadelivery', 'chinadelivery2', 'comission',
|
||||||
'promo',
|
'promo',
|
||||||
|
|
@ -119,7 +135,7 @@ class ChecklistSerializer(serializers.ModelSerializer):
|
||||||
'buyername', 'buyerphone', 'tg',
|
'buyername', 'buyerphone', 'tg',
|
||||||
'receivername', 'reveiverphone',
|
'receivername', 'reveiverphone',
|
||||||
'paymenttype', 'paymentproovement', 'checkphoto',
|
'paymenttype', 'paymentproovement', 'checkphoto',
|
||||||
'trackid', 'delivery',
|
'trackid', 'cdek_tracking', 'delivery',
|
||||||
'startDate', 'currentDate',
|
'startDate', 'currentDate',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -171,5 +187,5 @@ class PromocodeSerializer(serializers.ModelSerializer):
|
||||||
nocomission = serializers.BooleanField(source='no_comission')
|
nocomission = serializers.BooleanField(source='no_comission')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PromoCode
|
model = Promocode
|
||||||
fields = ('name', 'discount', 'freedelivery', 'nocomission')
|
fields = ('name', 'discount', 'freedelivery', 'nocomission')
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ router = DefaultRouter()
|
||||||
|
|
||||||
# FIXME: renamed
|
# FIXME: renamed
|
||||||
router.register(r'statistics', views.StatisticsAPI, basename='statistics')
|
router.register(r'statistics', views.StatisticsAPI, basename='statistics')
|
||||||
|
router.register(r'cdek', views.CDEKAPI, basename='cdek')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("login/", views.LoginAPI.as_view()),
|
path("login/", views.LoginAPI.as_view()),
|
||||||
|
|
|
||||||
118
store/views.py
118
store/views.py
|
|
@ -1,20 +1,23 @@
|
||||||
import calendar
|
import calendar
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict, defaultdict
|
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.db.models import F, Count, Q, Sum
|
from django.db.models import F, Count, Q, Sum, Value, Subquery
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
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
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from cdek.api import CDEKClient
|
||||||
from store.exceptions import CRMException
|
from store.exceptions import CRMException
|
||||||
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, PromoCode
|
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode
|
||||||
from store.serializers import (UserSerializer, LoginSerializer, ChecklistSerializer, GlobalSettingsYuanRateSerializer,
|
from store.serializers import (UserSerializer, LoginSerializer, ChecklistSerializer, GlobalSettingsYuanRateSerializer,
|
||||||
CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer,
|
CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer,
|
||||||
PromocodeSerializer, GlobalSettingsPickupSerializer)
|
PromocodeSerializer, GlobalSettingsPickupSerializer)
|
||||||
|
from utils.permissions import ReadOnly
|
||||||
|
|
||||||
|
|
||||||
class UserAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
class UserAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
||||||
|
|
@ -53,8 +56,9 @@ class LoginAPI(generics.GenericAPIView):
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class ChecklistAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
class ChecklistAPI(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
|
||||||
serializer_class = ChecklistSerializer
|
serializer_class = ChecklistSerializer
|
||||||
|
permission_classes = [IsAuthenticated | ReadOnly]
|
||||||
lookup_field = 'id'
|
lookup_field = 'id'
|
||||||
filterset_fields = ['status', ]
|
filterset_fields = ['status', ]
|
||||||
search_fields = ['id', 'track_id', 'buyer_phone', 'full_price']
|
search_fields = ['id', 'track_id', 'buyer_phone', 'full_price']
|
||||||
|
|
@ -76,6 +80,9 @@ class ChecklistAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.Ge
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
return self.create(request, *args, **kwargs)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if 'id' in kwargs:
|
if 'id' in kwargs:
|
||||||
return self.retrieve(request, *args, **kwargs)
|
return self.retrieve(request, *args, **kwargs)
|
||||||
|
|
@ -129,8 +136,7 @@ class CategoryAPI(generics.GenericAPIView):
|
||||||
'prices': GlobalSettingsPriceSerializer(global_settings).data,
|
'prices': GlobalSettingsPriceSerializer(global_settings).data,
|
||||||
})
|
})
|
||||||
|
|
||||||
# FIXME: use PATCH method for updates
|
def patch(self, request, *args, **kwargs):
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
data = json.loads(request.body)
|
data = json.loads(request.body)
|
||||||
if not all(k in data for k in ("category", "chinarush")):
|
if not all(k in data for k in ("category", "chinarush")):
|
||||||
raise CRMException('category and chinarush is required')
|
raise CRMException('category and chinarush is required')
|
||||||
|
|
@ -146,8 +152,7 @@ class CategoryAPI(generics.GenericAPIView):
|
||||||
class PricesAPI(generics.GenericAPIView):
|
class PricesAPI(generics.GenericAPIView):
|
||||||
serializer_class = GlobalSettingsPriceSerializer
|
serializer_class = GlobalSettingsPriceSerializer
|
||||||
|
|
||||||
# FIXME: use PATCH method for updates
|
def patch(self, request, *args, **kwargs):
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
data = json.loads(request.body)
|
data = json.loads(request.body)
|
||||||
|
|
||||||
instance = GlobalSettings.load()
|
instance = GlobalSettings.load()
|
||||||
|
|
@ -160,6 +165,7 @@ class PricesAPI(generics.GenericAPIView):
|
||||||
|
|
||||||
class PickupAPI(generics.GenericAPIView):
|
class PickupAPI(generics.GenericAPIView):
|
||||||
serializer_class = GlobalSettingsPickupSerializer
|
serializer_class = GlobalSettingsPickupSerializer
|
||||||
|
permission_classes = [IsAuthenticated | ReadOnly]
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return GlobalSettings.load()
|
return GlobalSettings.load()
|
||||||
|
|
@ -193,8 +199,7 @@ class PaymentMethodsAPI(generics.GenericAPIView):
|
||||||
|
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
# FIXME: use PATCH method for updates
|
def patch(self, request, *args, **kwargs):
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
data = json.loads(request.body)
|
data = json.loads(request.body)
|
||||||
if 'type' not in data:
|
if 'type' not in data:
|
||||||
raise CRMException('type is required')
|
raise CRMException('type is required')
|
||||||
|
|
@ -212,7 +217,7 @@ class PromoCodeAPI(mixins.CreateModelMixin, generics.GenericAPIView):
|
||||||
lookup_field = 'name'
|
lookup_field = 'name'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return PromoCode.objects.all()
|
return Promocode.objects.all()
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
|
|
@ -245,8 +250,6 @@ class StatisticsAPI(viewsets.GenericViewSet):
|
||||||
global_settings = GlobalSettings.load()
|
global_settings = GlobalSettings.load()
|
||||||
yuan_rate = global_settings.yuan_rate
|
yuan_rate = global_settings.yuan_rate
|
||||||
|
|
||||||
completed_filter = Q(status=Checklist.Status.COMPLETED)
|
|
||||||
|
|
||||||
# Prepare query to collect the stats
|
# Prepare query to collect the stats
|
||||||
qs = self.get_queryset() \
|
qs = self.get_queryset() \
|
||||||
.annotate_price_rub() \
|
.annotate_price_rub() \
|
||||||
|
|
@ -307,51 +310,72 @@ class StatisticsAPI(viewsets.GenericViewSet):
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
# TODO: implement stats_by_clients
|
|
||||||
@action(url_path='clients', detail=False, methods=['get'])
|
@action(url_path='clients', detail=False, methods=['get'])
|
||||||
def stats_by_clients(self, request, *args, **kwargs):
|
def stats_by_clients(self, request, *args, **kwargs):
|
||||||
def _create_stats(data: dict):
|
options = {
|
||||||
return {
|
"moreone": 1,
|
||||||
"moreone": data.get('moreone', 0),
|
"moretwo": 2,
|
||||||
"moretwo": data.get('moretwo', 0),
|
"morethree": 3,
|
||||||
"morethree": data.get('morethree', 0),
|
"morefour": 4,
|
||||||
"morefour": data.get('morefour', 0),
|
"morefive": 5,
|
||||||
"morefive": data.get('morefive', 0),
|
"moreten": 10,
|
||||||
"moreten": data.get('moreten', 0),
|
"moretwentyfive": 25,
|
||||||
"moretwentyfive": data.get('moretwentyfive', 0),
|
"morefifty": 50,
|
||||||
"morefifty": data.get('morefifty', 0),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def _filter_for_count(count):
|
def _create_empty_stats():
|
||||||
return Count('buyer_phone',
|
return {k: set() for k in options.keys()}
|
||||||
filter=Q(buyer_phone__in=Checklist.objects.values('buyer_phone')
|
|
||||||
.annotate(total_orders=Count('id'))
|
|
||||||
.filter(total_orders__gt=count)
|
|
||||||
.values('buyer_phone')
|
|
||||||
))
|
|
||||||
|
|
||||||
qs = self.get_queryset() \
|
qs = self.get_queryset() \
|
||||||
.values('month') \
|
.filter(buyer_phone__isnull=False) \
|
||||||
.annotate(
|
.values('month', 'buyer_phone') \
|
||||||
moreone=_filter_for_count(1),
|
.annotate(order_count=Count('id')) \
|
||||||
moretwo=_filter_for_count(2),
|
.filter(order_count__gt=1) \
|
||||||
morethree=_filter_for_count(3),
|
.order_by('month')
|
||||||
morefour=_filter_for_count(4),
|
|
||||||
morefive=_filter_for_count(5),
|
|
||||||
moreten=_filter_for_count(10),
|
|
||||||
moretwentyfive=_filter_for_count(25),
|
|
||||||
morefifty=_filter_for_count(50),
|
|
||||||
)
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
# Add empty stats
|
# Add empty stats
|
||||||
for i in range(1, 13):
|
for i in range(1, 13):
|
||||||
month = calendar.month_name[i]
|
month_name = calendar.month_name[i]
|
||||||
result[month] = _create_stats(dict())
|
result[month_name] = _create_empty_stats()
|
||||||
|
|
||||||
# Add actual stats
|
# Add actual stats
|
||||||
for stat in qs:
|
for stat in qs:
|
||||||
month = calendar.month_name[stat['month']]
|
month_name = calendar.month_name[stat['month']]
|
||||||
result[month] = _create_stats(stat)
|
|
||||||
|
for key, size in reversed(options.items()):
|
||||||
|
if stat['order_count'] > size:
|
||||||
|
result[month_name][key].add(stat['buyer_phone'])
|
||||||
|
break
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
|
class CDEKAPI(viewsets.GenericViewSet):
|
||||||
|
client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
|
||||||
|
|
||||||
|
@action(url_path='orders', detail=False, methods=['get'])
|
||||||
|
def get_order_info(self, request, *args, **kwargs):
|
||||||
|
im_number = request.query_params.get('im_number')
|
||||||
|
if not im_number:
|
||||||
|
raise CRMException('im_number is required')
|
||||||
|
|
||||||
|
r = self.client.get_order_info(im_number)
|
||||||
|
return Response(r.json())
|
||||||
|
|
||||||
|
@action(url_path='orders', detail=False, methods=['post'])
|
||||||
|
def create_order(self, request, *args, **kwargs):
|
||||||
|
order_data = request.data
|
||||||
|
if not order_data:
|
||||||
|
raise CRMException('json data is required')
|
||||||
|
|
||||||
|
r = self.client.create_order(order_data)
|
||||||
|
return Response(r.json())
|
||||||
|
|
||||||
|
@action(url_path='calculator/tariff', detail=False, methods=['post'])
|
||||||
|
def calculate_tariff(self, request, *args, **kwargs):
|
||||||
|
data = request.data
|
||||||
|
if not data:
|
||||||
|
raise CRMException('json data is required')
|
||||||
|
r = self.client.calculate_tariff(data)
|
||||||
|
return Response(r.json())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
|
from rest_framework.permissions import BasePermission, SAFE_METHODS
|
||||||
|
|
||||||
|
|
||||||
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
||||||
def enforce_csrf(self, request):
|
def enforce_csrf(self, request):
|
||||||
# To not perform the csrf check previously happening
|
# To not perform the csrf check previously happening
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class ReadOnly(BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return request.method in SAFE_METHODS
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user