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', need_time=False) 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) print(obj) obj.pop('matching_status') obj.pop('id') ao = obj.pop('district') rayon = obj.pop('area') obj['district']=models.AO.objects.get(id=ao) obj['area'] = models.Rayon.objects.get(id=rayon) obj['age_day'] = 1 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