kwork-poizonstore/store/views.py

383 lines
13 KiB
Python

import calendar
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
from rest_framework.filters import SearchFilter
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
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 utils.permissions import ReadOnly
class DisablePermissionsMixin(generics.GenericAPIView):
def get_permissions(self):
if settings.DISABLE_PERMISSIONS:
return [permissions.AllowAny()]
return super().get_permissions()
class ChecklistAPI(mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
DisablePermissionsMixin):
serializer_class = ChecklistSerializer
lookup_field = 'id'
filterset_fields = ['status', ]
filter_backends = [DjangoFilterBackend, SearchFilter]
search_fields = ['id', 'poizon_tracking', 'buyer_phone']
# TODO: search by full_price
def get_serializer_class(self):
if self.request.user.is_authenticated or settings.DISABLE_PERMISSIONS:
return ChecklistSerializer
# Anonymous users can edit only a certain set of fields
return AnonymousUserChecklistSerializer
def get_permissions(self):
if self.request.method in ('GET', 'PATCH'):
return [permissions.AllowAny()]
return super().get_permissions()
def get_queryset(self):
return Checklist.objects.all().with_base_related() \
.annotate_price_rub().annotate_commission_rub() \
.default_ordering()
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:
obj.status = Checklist.Status.DRAFT
obj.save()
return obj
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if 'id' in kwargs:
return self.retrieve(request, *args, **kwargs)
if not request.user.is_authenticated and not settings.DISABLE_PERMISSIONS:
# Anonymous users can't list checklists
return Response([])
return self.list(request, *args, **kwargs)
def perform_update(self, serializer):
serializer.save()
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
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):
serializer_class = CategorySerializer
def get_queryset(self):
return Category.objects.all()
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,
})
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)
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
permission_classes = [IsAuthenticated | ReadOnly]
def get_object(self):
return GlobalSettings.load()
def get(self, request, *args, **kwargs):
instance = self.get_object()
return Response(self.get_serializer(instance).data)
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 PaymentMethodsAPI(generics.GenericAPIView):
serializer_class = PaymentMethodSerializer
permission_classes = [IsAuthenticated | ReadOnly]
def get_queryset(self):
return PaymentMethod.objects.all()
def get(self, request, *args, **kwargs):
qs = self.get_queryset()
data = {}
for obj in qs:
data[obj.slug] = self.get_serializer(obj).data
return Response(data)
def patch(self, request, *args, **kwargs):
data = request.data
if 'type' not in data:
raise CRMException('type is required')
instance = get_object_or_404(self.get_queryset(), slug=data['type'])
serializer = self.get_serializer(instance, data=data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
class PromoCodeAPI(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = PromocodeSerializer
lookup_field = 'name'
def get_queryset(self):
return Promocode.objects.all()
def get(self, request, *args, **kwargs):
qs = self.get_queryset()
return Response(
{'promo': self.get_serializer(qs, many=True).data}
)
def post(self, request, *args, **kwargs):
self.create(request, *args, **kwargs)
return self.get(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
data = request.data
if 'name' not in data:
raise CRMException('name is required')
instance: Promocode = get_object_or_404(self.get_queryset(), name=data['name'])
instance.is_active = False
instance.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class StatisticsAPI(viewsets.GenericViewSet):
def get_queryset(self):
return Checklist.objects.all() \
.filter(status=Checklist.Status.COMPLETED) \
.annotate(month=F('status_updated_at__month'))
@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
# Prepare query to collect the stats
qs = self.get_queryset() \
.annotate_price_rub() \
.annotate_commission_rub() \
.values('month') \
.annotate(total=Count('id'),
total_completed=Count('id'),
total_bought_yuan=Sum('real_price', default=0),
total_bought_rub=F('total_bought_yuan') * yuan_rate,
total_commission_rub=Sum('_commission_rub', default=0)
)
def _create_stats(data: dict):
return {
"CountOrders": data.get('total', 0),
"CountComplete": data.get('total_completed', 0),
"SumCommission": data.get('total_commission_rub', 0),
"SumOrders1": data.get('total_bought_yuan', 0),
"SumOrders2": data.get('total_bought_rub', 0),
}
result = {}
# Add empty stats
for i in range(1, 13):
month_name = calendar.month_name[i]
result[month_name] = _create_stats(dict())
# Add actual stats
for stat in qs:
month_name = calendar.month_name[stat['month']]
result[month_name] = _create_stats(stat)
return Response(result)
@action(url_path='categories', detail=False, methods=['get'])
def stats_by_categories(self, request, *args, **kwargs):
all_categories = Category.objects.values_list('slug', flat=True)
categories_dict = {c: 0 for c in all_categories}
qs = self.get_queryset() \
.select_related('category') \
.values('month', 'category__slug') \
.annotate(total_orders=Count('id'))
result = {}
# Add empty stats
for i in range(1, 13):
month = calendar.month_name[i]
result[month] = categories_dict.copy()
# Add actual stats
for stat in qs:
month = calendar.month_name[stat['month']]
category_slug = stat['category__slug']
total_orders = stat['total_orders']
result[month][category_slug] = total_orders
return Response(result)
@action(url_path='clients', detail=False, methods=['get'])
def stats_by_clients(self, request, *args, **kwargs):
options = {
"moreone": 1,
"moretwo": 2,
"morethree": 3,
"morefour": 4,
"morefive": 5,
"moreten": 10,
"moretwentyfive": 25,
"morefifty": 50,
}
def _create_empty_stats():
return {k: set() for k in options.keys()}
qs = self.get_queryset() \
.filter(buyer_phone__isnull=False) \
.values('month', 'buyer_phone') \
.annotate(order_count=Count('id')) \
.filter(order_count__gt=1) \
.order_by('month')
result = {}
# Add empty stats
for i in range(1, 13):
month_name = calendar.month_name[i]
result[month_name] = _create_empty_stats()
# Add actual stats
for stat in qs:
month_name = calendar.month_name[stat['month']]
for key, size in reversed(options.items()):
if stat['order_count'] > size:
result[month_name][key].add(stat['buyer_phone'])
break
return Response(result)
class CDEKAPI(viewsets.GenericViewSet):
client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
permission_classes = [permissions.AllowAny]
@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())
@get_order_info.mapping.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())
@get_order_info.mapping.patch
def edit_order(self, request, *args, **kwargs):
order_data = request.data
if not order_data:
raise CRMException('json data is required')
r = self.client.edit_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())