|
|
import warnings
|
|
|
from http import HTTPStatus
|
|
|
|
|
|
from django.db.models import Q
|
|
|
from django.http import HttpResponse
|
|
|
from django.http import JsonResponse
|
|
|
from rest_framework.decorators import action
|
|
|
from rest_framework.decorators import api_view
|
|
|
from rest_framework.decorators import permission_classes
|
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
from rest_framework.response import Response
|
|
|
from rest_framework.views import APIView
|
|
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
|
|
|
|
|
from postamates.settings import AGE_DAY_BORDER
|
|
|
from postamates.settings import EXCEL_EXPORT_FILENAME
|
|
|
from postamates.settings import JSON_EXPORT_FILENAME, STATUS_TASK_NAME
|
|
|
from service import models
|
|
|
from service import pagination
|
|
|
from service import serializers
|
|
|
from service import utils
|
|
|
from service.enums import PointStatus, MatchingStatus
|
|
|
from service.permissions import UserPermission
|
|
|
from service.service import PointService
|
|
|
from service.tasks import raschet, load_post_and_pvz, load_other_objects, load_data
|
|
|
from rest_framework.permissions import AllowAny
|
|
|
from django.shortcuts import redirect
|
|
|
from django.contrib import messages
|
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
|
from rest_framework import filters
|
|
|
from service.utils import CustomReadOnlyModelViewSet
|
|
|
from django.db.models import Min, Max
|
|
|
import os
|
|
|
from django.forms.models import model_to_dict
|
|
|
|
|
|
|
|
|
class AOViewSet(CustomReadOnlyModelViewSet):
|
|
|
serializer_class = serializers.AOSerializer
|
|
|
queryset = models.AO.objects
|
|
|
permission_classes = [AllowAny]
|
|
|
|
|
|
|
|
|
class PostAndPVZCategoryViewSet(CustomReadOnlyModelViewSet):
|
|
|
serializer_class = serializers.PostAndPVZCategorySerializer
|
|
|
queryset = models.Post_and_pvzCategory.objects
|
|
|
|
|
|
|
|
|
class OtherObjectsCategoryViewSet(CustomReadOnlyModelViewSet):
|
|
|
serializer_class = serializers.OtherObjectsCategorySerializer
|
|
|
queryset = models.OtherObjectsCategory.objects
|
|
|
|
|
|
|
|
|
class PlacementPointViewSet(ReadOnlyModelViewSet):
|
|
|
serializer_class = serializers.PlacementPointSerializer
|
|
|
queryset = models.PlacementPoint.objects
|
|
|
pagination_class = pagination.MyPagination
|
|
|
permission_classes = [UserPermission]
|
|
|
filter_backends = [DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter]
|
|
|
|
|
|
@property
|
|
|
def filterset_fields(self):
|
|
|
model_cls = self.queryset.model
|
|
|
fieldset = {
|
|
|
field.name: [
|
|
|
"exact",
|
|
|
"gt",
|
|
|
"gte",
|
|
|
"lt",
|
|
|
"lte",
|
|
|
"in",
|
|
|
"iexact",
|
|
|
"startswith",
|
|
|
"istartswith",
|
|
|
"endswith",
|
|
|
"iendswith",
|
|
|
"regex",
|
|
|
"iregex",
|
|
|
"isnull",
|
|
|
"contains",
|
|
|
"icontains",
|
|
|
]
|
|
|
for field in [
|
|
|
field
|
|
|
for field in model_cls._meta.get_fields()
|
|
|
if field.get_internal_type()
|
|
|
not in [
|
|
|
"JSONField",
|
|
|
"ForeignKey",
|
|
|
"ManyToManyField",
|
|
|
"OneToOneField",
|
|
|
"PointField"
|
|
|
]
|
|
|
]
|
|
|
}
|
|
|
|
|
|
# filters for model relations
|
|
|
for field in [
|
|
|
field
|
|
|
for field in model_cls._meta.get_fields()
|
|
|
if field.get_internal_type()
|
|
|
in [
|
|
|
"ForeignKey",
|
|
|
"ManyToManyField",
|
|
|
"OneToOneField",
|
|
|
]
|
|
|
]:
|
|
|
fieldset[field.name] = [
|
|
|
"exact",
|
|
|
"gt",
|
|
|
"gte",
|
|
|
"lt",
|
|
|
"lte",
|
|
|
]
|
|
|
|
|
|
return fieldset
|
|
|
|
|
|
def get_queryset(self):
|
|
|
qs = self.queryset.all().order_by('id')
|
|
|
location_ids = self.request.GET.get('location_ids[]')
|
|
|
prediction_first = self.request.GET.get('prediction_first[]')
|
|
|
prediction_current = self.request.GET.get('prediction_current[]')
|
|
|
age_day = self.request.GET.get('age_day[]')
|
|
|
categories = self.request.GET.get('categories[]')
|
|
|
status = self.request.GET.get('status[]')
|
|
|
delta = self.request.GET.get('delta[]')
|
|
|
fact = self.request.GET.get('fact[]')
|
|
|
included = self.request.GET.get('included[]')
|
|
|
excluded = self.request.GET.get('excluded[]')
|
|
|
plan_first = self.request.GET.get('plan_first[]')
|
|
|
plan_current = self.request.GET.get('plan_current[]')
|
|
|
delta_first = self.request.GET.get('delta_first[]')
|
|
|
delta_current = self.request.GET.get('delta_current[]')
|
|
|
rayons = self.request.GET.get('area[]')
|
|
|
aos = self.request.GET.get('district[]')
|
|
|
group_dists_lt = self.request.GET.getlist('dist_to_group__lt')
|
|
|
group_dists_gt = self.request.GET.getlist('dist_to_group__gt')
|
|
|
if location_ids:
|
|
|
location_ids = list(location_ids.split(','))
|
|
|
qs = qs.filter(pk__in=location_ids)
|
|
|
if prediction_first:
|
|
|
prediction_first = list(prediction_first.split(','))
|
|
|
qs = qs.filter(prediction_first__range=prediction_first)
|
|
|
if prediction_current:
|
|
|
prediction_current = list(prediction_current.split(','))
|
|
|
qs = qs.filter(prediction_current__range=prediction_current)
|
|
|
if categories:
|
|
|
categories = list(categories.split(','))
|
|
|
qs = qs.filter(category__in=categories)
|
|
|
if status:
|
|
|
status = list(status.split(','))
|
|
|
qs = qs.filter(status__in=status)
|
|
|
if delta:
|
|
|
delta = list(delta.split(','))
|
|
|
qs = qs.filter(delta__range=delta)
|
|
|
if fact:
|
|
|
fact = list(fact.split(','))
|
|
|
qs = qs.filter(fact__range=fact)
|
|
|
if plan_first:
|
|
|
plan_first = list(plan_first.split(','))
|
|
|
qs = qs.filter(plan_first__range=plan_first)
|
|
|
if plan_current:
|
|
|
plan_current = list(plan_current.split(','))
|
|
|
qs = qs.filter(plan_current__range=plan_current)
|
|
|
if age_day:
|
|
|
age_day = list(age_day.split(','))
|
|
|
qs = qs.filter(age_day__range=age_day)
|
|
|
if delta_current:
|
|
|
delta_current = list(delta_current.split(','))
|
|
|
qs = qs.filter(delta_current__range=delta_current)
|
|
|
if delta_first:
|
|
|
delta_first = list(delta_first.split(','))
|
|
|
qs = qs.filter(delta_first__range=delta_first)
|
|
|
if rayons:
|
|
|
rayons = list(rayons.split(','))
|
|
|
qs = qs.filter(area_id__in=rayons)
|
|
|
if aos:
|
|
|
aos = list(aos.split(','))
|
|
|
qs = qs.filter(district_id__in=aos)
|
|
|
if excluded:
|
|
|
excluded = list(excluded.split(','))
|
|
|
qs = qs.filter(~Q(pk__in=excluded))
|
|
|
if included:
|
|
|
inclded = list(included.split(','))
|
|
|
qs2 = self.queryset.filter(pk__in=inclded).all()
|
|
|
qs = (qs | qs2).distinct()
|
|
|
if group_dists_lt:
|
|
|
g_d = [list(g.split(',')) for g in group_dists_lt]
|
|
|
for group in g_d:
|
|
|
filtered_points = list(
|
|
|
models.PlacementPointPVZDistance.objects.filter(pvz_postamates_group__id=int(group[0]),
|
|
|
dist__lt=int(group[1])).values_list(
|
|
|
'placement_point__id', flat=True))
|
|
|
qs = qs.filter(id__in=filtered_points)
|
|
|
if group_dists_gt:
|
|
|
g_d = [list(g.split(',')) for g in group_dists_gt]
|
|
|
for group in g_d:
|
|
|
filtered_points = list(
|
|
|
models.PlacementPointPVZDistance.objects.filter(pvz_postamates_group__id=int(group[0]),
|
|
|
dist__gt=int(group[1])).values_list(
|
|
|
'placement_point__id', flat=True))
|
|
|
qs = qs.filter(id__in=filtered_points)
|
|
|
return qs
|
|
|
|
|
|
@action(methods=['get'], detail=False)
|
|
|
def get_filterset_fields(self, request, *args, **kwargs):
|
|
|
return Response(self.filterset_fields, status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def filters(self, request):
|
|
|
def get_filter_data():
|
|
|
qs = self.get_queryset()
|
|
|
keys = (
|
|
|
'age_day', 'prediction_first', 'prediction_current',
|
|
|
'plan_first', 'plan_current', 'fact', 'delta_first',
|
|
|
'delta_current', 'flat_cnt', 'year_bld', 'levels',
|
|
|
'doors', 'flats_cnt', 'popul_home', 'popul_job',
|
|
|
'other_post_cnt', 'target_post_cnt', 'yndxfood_cnt',
|
|
|
'yndxfood_sum', 'yndxfood_cnt_cst', 'rival_post_cnt', 'rival_pvz_cnt', 'tc_cnt', 'culture_cnt',
|
|
|
'mfc_cnt', 'public_stop_cnt'
|
|
|
, 'supermarket_cnt', 'target_dist', 'metro_dist'
|
|
|
)
|
|
|
temp_data = {
|
|
|
key: [
|
|
|
x for x in list(set(qs.values_list(key, flat=True))) if
|
|
|
x is not None
|
|
|
]
|
|
|
for key in keys
|
|
|
}
|
|
|
data = {
|
|
|
key: [
|
|
|
min(temp_data[key]), max(temp_data[key]),
|
|
|
] if temp_data[key] else [0, 100] for key in keys
|
|
|
}
|
|
|
data['dist_to_groups'] = [{'group_id': d['pvz_postamates_group'], 'dist': [d['min_dist'], d['max_dist']]}
|
|
|
for d in list(
|
|
|
models.PlacementPointPVZDistance.objects.values('pvz_postamates_group').annotate(
|
|
|
min_dist=Min('dist'), max_dist=Max('dist')))]
|
|
|
|
|
|
return data
|
|
|
|
|
|
data = get_filter_data()
|
|
|
|
|
|
return Response(data, status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def search_address(self, request):
|
|
|
address = self.request.GET.get('address')
|
|
|
qs = self.get_queryset()
|
|
|
if address:
|
|
|
qs = qs.filter(address__icontains=address)
|
|
|
qs = qs.distinct()
|
|
|
page = self.paginate_queryset(qs)
|
|
|
if page is not None:
|
|
|
serializer = self.get_serializer(page, many=True)
|
|
|
return self.get_paginated_response(serializer.data)
|
|
|
|
|
|
serializer = self.get_serializer(qs, many=True)
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
@action(detail=False, methods=['put'])
|
|
|
def update_status(self, request):
|
|
|
qs = self.get_queryset()
|
|
|
new_status = self.request.GET.get('status')
|
|
|
if not new_status or new_status not in [tag.name for tag in PointStatus]:
|
|
|
return Response({'message': 'No status'}, HTTPStatus.BAD_REQUEST)
|
|
|
PointService.update_points_in_radius(qs, new_status)
|
|
|
PointService.update_status(qs, new_status)
|
|
|
return Response(
|
|
|
{'message': 'status updated'},
|
|
|
status=HTTPStatus.OK,
|
|
|
)
|
|
|
|
|
|
@action(detail=False, methods=['put'])
|
|
|
def update_fact(self, request):
|
|
|
point_id = request.GET.get('postamat_id')
|
|
|
fact = request.GET.get('fact')
|
|
|
if not point_id or not fact or not fact.isdigit():
|
|
|
return Response(status=HTTPStatus.BAD_REQUEST)
|
|
|
qs = models.PlacementPoint.objects.filter(postamat_id=point_id)
|
|
|
if not qs:
|
|
|
return Response(status=HTTPStatus.NOT_FOUND)
|
|
|
for q in qs:
|
|
|
if q.age_day < AGE_DAY_BORDER:
|
|
|
qs.update(**{'fact': fact, 'fact_raw': fact})
|
|
|
else:
|
|
|
new_fact = fact // (q.age_day / AGE_DAY_BORDER)
|
|
|
qs.update(fact=new_fact, fact_raw=fact)
|
|
|
qs.update(**{'fact_raw': fact})
|
|
|
return Response({'message': 'fact updated'}, status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['put'])
|
|
|
def update_postamat_id(self, request):
|
|
|
postamat_id = request.GET.get('postamat_id')
|
|
|
point_id = request.GET.get('id')
|
|
|
if not point_id or not postamat_id:
|
|
|
return Response(status=HTTPStatus.BAD_REQUEST)
|
|
|
qs = PointService.get_point_by_id(point_id)
|
|
|
if not qs:
|
|
|
return Response(status=HTTPStatus.NOT_FOUND)
|
|
|
PointService().update_postamat_id(point_id, postamat_id)
|
|
|
return Response({'message': 'Postamat id updated'}, status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def to_excel(self, request):
|
|
|
qs = self.get_queryset()
|
|
|
serializer = self.serializer_class(qs, many=True)
|
|
|
filename = EXCEL_EXPORT_FILENAME
|
|
|
res = HttpResponse(
|
|
|
PointService.to_excel(serializer),
|
|
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
)
|
|
|
res['Content-Disposition'] = f'attachment; filename={filename}'
|
|
|
return res
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def to_json(self, request):
|
|
|
qs = self.get_queryset()
|
|
|
serializer = self.serializer_class(qs, many=True)
|
|
|
filename = JSON_EXPORT_FILENAME
|
|
|
response = HttpResponse(PointService.to_json(serializer), content_type='application/json')
|
|
|
response['Content-Disposition'] = f'attachment; filename={filename}'
|
|
|
return response
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def get_10k(self, request):
|
|
|
pred = PointService.get_first_10_k()
|
|
|
return Response({'prediction_current': pred}, status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def start(self, request):
|
|
|
raschet.delay()
|
|
|
return Response('Sucess', status=HTTPStatus.OK)
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def last_time_ml_run(self, request):
|
|
|
status = models.TaskStatus.objects.filter(task_name=STATUS_TASK_NAME).first()
|
|
|
st = None
|
|
|
if status:
|
|
|
st = status.status
|
|
|
return Response({'last_time': models.LastMLCall.objects.first().dt, 'task_status': st}, status=HTTPStatus.OK)
|
|
|
|
|
|
|
|
|
class PrePlacementPointViewSet(PlacementPointViewSet):
|
|
|
queryset = models.PrePlacementPoint.objects
|
|
|
serializer_class = serializers.PrePlacementPointSerializer
|
|
|
|
|
|
def get_queryset(self):
|
|
|
qs = super().get_queryset()
|
|
|
matching_status = self.request.GET.get('matching_status')
|
|
|
if matching_status:
|
|
|
qs = qs.filter(matching_status=matching_status)
|
|
|
return qs
|
|
|
|
|
|
@action(detail=False, methods=['post'])
|
|
|
def load_matching_file(self, request):
|
|
|
file = request.FILES['file'].file
|
|
|
file_bytes = file.read()
|
|
|
excel_base64 = base64.b64encode(file_bytes).decode()
|
|
|
obj = models.TempFiles.objects.create(data=excel_base64)
|
|
|
return Response(
|
|
|
{'id': obj.id},
|
|
|
status=HTTPStatus.OK,
|
|
|
)
|
|
|
|
|
|
@action(detail=False, methods=['post'])
|
|
|
def start_matching(self, request):
|
|
|
file_id = request.POST['id']
|
|
|
total, matched, problem = PointService().start_mathing(file_id)
|
|
|
PointService().make_enrichment()
|
|
|
# raschet('service_preplacementpoint')
|
|
|
return Response(
|
|
|
{'message': {'total': total, 'matched': matched, 'error': problem, 'unmatched': total - matched - problem}},
|
|
|
status=HTTPStatus.OK,
|
|
|
)
|
|
|
|
|
|
@action(detail=False, methods=['post'])
|
|
|
def move_points(self, request):
|
|
|
qs = self.get_queryset()
|
|
|
qs = qs.filter(matching_status=MatchingStatus.New.name)
|
|
|
for q in qs:
|
|
|
obj = model_to_dict(q)
|
|
|
obj.pop('matching_status')
|
|
|
obj.pop('id')
|
|
|
models.PlacementPoint.objects.create(**obj)
|
|
|
models.PrePlacementPoint.objects.all().delete()
|
|
|
return Response(status=HTTPStatus.OK, )
|
|
|
|
|
|
@action(detail=False, methods=['delete'])
|
|
|
def delete_points(self, request):
|
|
|
ids = request.POST['ids'].split(',')
|
|
|
PointService.delete_preplacement_points(ids)
|
|
|
return Response(status=HTTPStatus.OK, )
|
|
|
|
|
|
@action(detail=False, methods=['get'])
|
|
|
def download_template(self, request):
|
|
|
image_buffer = open('PrePlacementPoints.xlsx', "rb").read()
|
|
|
response = HttpResponse(image_buffer, content_type='xlsx')
|
|
|
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('preplacementpoints.xlsx')
|
|
|
return response
|
|
|
|
|
|
|
|
|
class refresh_placement_points(APIView):
|
|
|
@staticmethod
|
|
|
def post(request):
|
|
|
warnings.filterwarnings('ignore')
|
|
|
file = request.FILES['file'].file
|
|
|
file_bytes = file.read()
|
|
|
csv_base64 = base64.b64encode(file_bytes).decode()
|
|
|
obj = models.TempFiles.objects.create(data=csv_base64)
|
|
|
load_data.delay(obj.id)
|
|
|
messages.success(request, 'Файл точек успешно загружен')
|
|
|
return redirect('/admin')
|
|
|
|
|
|
|
|
|
class load_ao_and_rayons(APIView):
|
|
|
@staticmethod
|
|
|
def post(request):
|
|
|
warnings.filterwarnings('ignore')
|
|
|
file_ao = request.FILES['file_ao']
|
|
|
file_rayon = request.FILES['file_rayon']
|
|
|
utils.load_ao_and_rayons(file_ao, file_rayon)
|
|
|
messages.success(request, 'Файл АО и Районов успешно загружен')
|
|
|
return redirect('/admin')
|
|
|
|
|
|
|
|
|
import base64
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
def upload_post_and_pvz(request):
|
|
|
warnings.filterwarnings('ignore')
|
|
|
file_rivals = request.FILES['file_post_and_pvz'].file
|
|
|
file_bytes = file_rivals.read()
|
|
|
excel_base64 = base64.b64encode(file_bytes).decode()
|
|
|
obj = models.TempFiles.objects.create(data=excel_base64)
|
|
|
load_post_and_pvz.delay(obj.id)
|
|
|
messages.success(request, 'Загрузка ПВЗ и Постаматов началась. Отслеживайте выполнение в Статусе фоновых задач')
|
|
|
return redirect('/admin')
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
def upload_other_objects(request):
|
|
|
warnings.filterwarnings('ignore')
|
|
|
file = request.FILES['file_other_objects']
|
|
|
file_bytes = file.read()
|
|
|
excel_base64 = base64.b64encode(file_bytes).decode()
|
|
|
obj = models.TempFiles.objects.create(data=excel_base64)
|
|
|
load_other_objects.delay(obj.id)
|
|
|
messages.success(request, 'Загрузка Прочих объектов началась. Отслеживайте выполнение в Статусе фоновых задач')
|
|
|
return redirect('/admin')
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
def upload_dist(request):
|
|
|
warnings.filterwarnings('ignore')
|
|
|
file_dist = request.FILES['file_dist']
|
|
|
utils.load_dist(file_dist)
|
|
|
messages.success(request, 'Файл расстояний успешно загружен')
|
|
|
return redirect('/admin')
|
|
|
|
|
|
|
|
|
@api_view(['GET'])
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
def get_current_user(request):
|
|
|
return JsonResponse(
|
|
|
{'groups': [gr.name for gr in request.user.groups.all()]},
|
|
|
)
|
|
|
|
|
|
|
|
|
def download_pvz_template(self):
|
|
|
image_buffer = open('Постоматы и ПВЗ.xlsx', "rb").read()
|
|
|
response = HttpResponse(image_buffer, content_type='xlsx')
|
|
|
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('pvz_and_postomats.xlsx')
|
|
|
return response
|
|
|
|
|
|
|
|
|
def download_other_template(self):
|
|
|
image_buffer = open('Другие объекты.xlsx', "rb").read()
|
|
|
response = HttpResponse(image_buffer, content_type='xlsx')
|
|
|
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('other.xlsx')
|
|
|
return response
|