diff --git a/service/urls.py b/service/urls.py index 0639fcf..8991c67 100644 --- a/service/urls.py +++ b/service/urls.py @@ -9,7 +9,6 @@ from service import views router = routers.DefaultRouter() router.register('', views.PlacementPointViewSet) - schema_view = get_schema_view( openapi.Info( title="Snippets API", @@ -25,16 +24,8 @@ schema_view = get_schema_view( ) urlpatterns = [ - path('placement_points/', - include([*router.urls, - url(r'ao_rayons',views.AOViewSet.as_view({'get': 'list'}),name='ao_and_rayons'), - url(r'update_status', views.update_status.as_view(), name='update_status'), - url(r'update_fact', views.update_fact.as_view(), name='update_fact'), - url(r'to_excel', views.get_excel.as_view(), name='to_excel'), - url(r'10k', views.get_top_10k.as_view(), name='top_10_k'), - url(r'address', views.search_address.as_view(), name='search_address') - ]), name='placement_points'), - path('raschet/', views.raschet.as_view(), name='ao_and_rayons'), + path('placement_points/', include([*router.urls]), name='placement_points'), + path('ao_rayons', views.AOViewSet.as_view({'get': 'list'}), name='ao_and_rayons'), url(r'load_csv/', views.refresh_placement_points.as_view(), name='upload_placement_points'), url(r'upload_ao_and_rayons/', views.load_ao_and_rayons.as_view(), name='upload_ao_and_rayons'), re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), diff --git a/service/views.py b/service/views.py index d5ed29a..6eccfeb 100644 --- a/service/views.py +++ b/service/views.py @@ -4,168 +4,16 @@ from io import BytesIO import pandas as pd from django.db.models import Q from django.http import HttpResponse -from rest_framework import permissions, status +from rest_framework import status from rest_framework import status as http_status from rest_framework.decorators import action -from rest_framework.generics import GenericAPIView from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import ReadOnlyModelViewSet from service import serializers, models, pagination from service import utils -from service.pagination import MyPagination -from service.serializers import PlacementPointSerializer -from service.utils import raschet as raschet_alg, load_data - - -def rename_result_dataset(dataframe, rename_dict, reverse=False): - if reverse: - return dataframe.rename(columns={v: k for k, v in rename_dict.items()}) - return dataframe.rename(columns=rename_dict) - - -class raschet(GenericAPIView): - permission_classes = [permissions.AllowAny] - - def post(self, request, format=None): - POINTS_RENAME_DICT = { - "point_id": "ID точки (для всех наборов одинаковые)", - "category": "Тип объекта", - "msk_ao": "Адм. округ", - "msk_rayon": "Адм. район", - "addr": "Адрес", - "name": "Название", - "model": "Модель на ML", - "rate": "Стандартная модель", - } - NET_RENAME_DICT = { - "cell_id": "ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", - "msk_ao": "Адм. округ", - "msk_rayon": "Адм. район", - "vuz": "ВУЗы и техникумы", - "model": "Модель на ML", - "rate": "Стандартная модель", - } - TIPES_DICT = { - "kiosk": "городской киоск", - "mfc": "МФЦ", - "library": "библиотека", - "dk": "дом культуры и клуб", - "sport": "спортивный объект", - } - RAYONS_RENAME_DICT = {1: 'район Филёвский Парк', 2: 'район Зюзино', 3: 'район Внуково', 4: 'Алтуфьевский район', - 5: 'Дмитровский район', 6: 'Можайский район', 7: 'район Ново-Переделкино', - 8: 'район Щукино', 9: 'Молжаниновский район', 10: 'район Митино', 11: 'район Куркино', - 12: 'район Аэропорт', 13: 'район Строгино', 14: 'район Крылатское', - 15: 'район Тёплый Стан', 16: 'район Солнцево', 57: 'Бутырский район', - 17: 'район Южное Тушино', 18: 'район Северное Тушино', 19: 'район Покровское-Стрешнево', - 20: 'район Хорошёво-Мнёвники', 21: 'район Очаково-Матвеевское', - 22: 'район Тропарёво-Никулино', 23: 'район Левобережный', 24: 'район Фили-Давыдково', - 25: 'район Ховрино', 26: 'Головинский район', 27: 'район Раменки', 28: 'Войковский район', - 29: 'район Западное Дегунино', 30: 'район Сокол', 31: 'район Проспект Вернадского', - 32: 'район Южное Бутово', 33: 'Обручевский район', 34: 'район Ясенево', - 35: 'район Дорогомилово', 36: 'район Коньково', 37: 'Хорошёвский район', - 38: 'район Северный', 39: 'район Коптево', 40: 'Ломоносовский район', 58: 'район Марфино', - 59: 'Тверской район', 41: 'Пресненский район', 74: 'район Нагатино-Садовники', - 42: 'Бескудниковский район', 43: 'Гагаринский район', 44: 'район Черёмушки', - 45: 'Тимирязевский район', 46: 'район Беговой', 47: 'район Хамовники', - 48: 'район Северное Бутово', 49: 'район Лианозово', 50: 'район Восточное Дегунино', - 51: 'Савёловский район', 52: 'Академический район', 53: 'район Чертаново Центральное', - 54: 'район Отрадное', 55: 'район Арбат', 56: 'район Чертаново Южное', - 60: 'район Чертаново Северное', 61: 'район Якиманка', 62: 'район Котловка', - 63: 'Останкинский район', 64: 'Донской район', 65: 'район Бибирево', - 66: 'район Марьина Роща', 67: 'Нагорный район', 68: 'Даниловский район', - 73: 'район Северное Медведково', 69: 'Мещанский район', 70: 'район Бирюлёво Западное', - 71: 'район Южное Медведково', 72: 'район Замоскворечье', 75: 'район Царицыно', - 76: 'район Москворечье-Сабурово', 77: 'район Свиблово', 78: 'район Нагатинский Затон', - 82: 'Таганский район', 79: 'Басманный район', 80: 'Красносельский район', - 81: 'район Ростокино', 83: 'Алексеевский район', 84: 'район Восточный', - 87: 'Бабушкинский район', 85: 'район Бирюлёво Восточное', 86: 'Южнопортовый район', - 88: 'район Сокольники', 89: 'Ярославский район', 90: 'район Богородское', - 91: 'район Метрогородок', 92: 'район Лефортово', 93: 'район Орехово-Борисово Северное', - 94: 'район Печатники', 95: 'Лосиноостровский район', 98: 'район Новогиреево', - 96: 'Нижегородский район', 99: 'район Марьино', 97: 'район Орехово-Борисово Южное', - 100: 'район Преображенское', 105: 'район Братеево', 101: 'район Соколиная Гора', - 102: 'район Перово', 103: 'район Люблино', 104: 'район Текстильщики', - 106: 'район Зябликово', 107: 'Рязанский район', 108: 'район Измайлово', - 109: 'район Северное Измайлово', 113: 'район Капотня', 110: 'район Кузьминки', - 111: 'район Гольяново', 112: 'район Вешняки', 114: 'район Выхино-Жулебино', - 115: 'район Восточное Измайлово', 118: 'район Новокосино', 116: 'район Ивановское', - 117: 'район Косино-Ухтомский', 119: 'район Некрасовка', 120: 'район Кунцево'} - AO_RENAME_DICT = {1: 'Западный административный округ', 2: 'Южный административный округ', - 3: 'Северный административный округ', 4: 'Юго-Западный административный округ', - 5: 'Северо-Западный административный округ', 6: 'Центральный административный округ', - 7: 'Северо-Восточный административный округ', 8: 'Юго-Восточный административный округ', - 9: 'Восточный административный округ'} - EXCLUDED_COLS = ['id', 'people', 'people2025', 'stops_ot', 'routes_ot', 'in_metro', 'out_metro', 'tc', 'empls', - 'walkers', 'schools', 'parking', 'pvz', 'gov_place', 'bike_park', 'products', 'nonprod', - 'service', 'vuz'] - df_points_, df_nets_ = raschet_alg(**request.data) - df_points_['category'] = df_points_['category'].map(TIPES_DICT) - df_points_['msk_ao'] = df_points_['msk_ao'].map(AO_RENAME_DICT) - df_points_['msk_rayon'] = df_points_['msk_rayon'].map(RAYONS_RENAME_DICT) - df_nets_['msk_ao'] = df_nets_['msk_ao'].map(AO_RENAME_DICT) - df_nets_['msk_rayon'] = df_nets_['msk_rayon'].map(RAYONS_RENAME_DICT) - df_points_ = df_points_.drop(columns=EXCLUDED_COLS) - df_nets_ = df_nets_.drop(columns=EXCLUDED_COLS) - - df_points = rename_result_dataset(df_points_, POINTS_RENAME_DICT) - if "Стандартная модель" in df_points.columns: - df_points = df_points[ - ["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", - "Тип объекта", "geom", "Модель на ML", "Стандартная модель"]] - else: - df_points = df_points[ - ["ID точки (для всех наборов одинаковые)", "Адм. округ", "Адм. район", "Адрес", "Название", - "Тип объекта", "geom", "Модель на ML", ]] - - df_nets = rename_result_dataset(df_nets_, NET_RENAME_DICT) - if "Стандартная модель" in df_nets.columns: - df_nets = df_nets[ - ["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", - "Модель на ML", "Стандартная модель"]] - else: - df_nets = df_nets[ - ["ID ячейки (для всех трёх сеток - разные, нумерация сквозная)", "Адм. округ", "Адм. район", "geom", - "Модель на ML"]] - - with BytesIO() as b: - # Use the StringIO object as the filehandle. - writer = pd.ExcelWriter(b, engine='xlsxwriter') - if df_points is not None: - df_points.to_excel(writer, sheet_name='Точки', index=False) - worksheet = writer.sheets['Точки'] # pull worksheet object - for idx, col in enumerate(df_points): # loop through all columns - if col == 'geom': - continue - series = df_points[col] - max_len = max(( - series.astype(str).map(len).max(), # len of largest item - len(str(series.name)) # len of column name/header - )) + 1 # adding a little extra space - worksheet.set_column(idx, idx, max_len) - if df_nets is not None: - df_nets.to_excel(writer, sheet_name='Полигоны', index=False) - worksheet = writer.sheets['Полигоны'] # pull worksheet object - for idx, col in enumerate(df_nets): # loop through all columns - if col == 'geom': - continue - series = df_nets[col] - max_len = max(( - series.astype(str).map(len).max(), # len of largest item - len(str(series.name)) # len of column name/header - )) + 1 # adding a little extra space - worksheet.set_column(idx, idx, max_len) - writer.save() - # Set up the Http response. - filename = f'Выгрузка.xlsx' - response = HttpResponse( - b.getvalue(), - content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - ) - response['Content-Disposition'] = 'attachment; filename=%s' % filename - return response +from service.utils import load_data class RayonViewSet(ReadOnlyModelViewSet): @@ -292,106 +140,31 @@ class PlacementPointViewSet(ReadOnlyModelViewSet): } return Response(data, status=status.HTTP_200_OK) + @action(detail=False, methods=['get']) + def search_address(self, request): + address = self.request.GET.get('address') + qs = self.get_queryset() + qs = qs.filter(address__icontains=address) -class refresh_placement_points(APIView): - @staticmethod - def post(request): - warnings.filterwarnings('ignore') - file = request.FILES['file'] - load_data(file) - return Response(status=http_status.HTTP_200_OK) - - -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) - return Response(status=http_status.HTTP_200_OK) + 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) -class update_status(APIView): - def put(self, request): - qs = models.PlacementPoint.objects.all() + @action(detail=False, methods=['put']) + def update_status(self, request): + qs = self.get_queryset() new_status = self.request.GET.get('status') - location_ids = self.request.GET.get('location_ids[]') - prediction_first = self.request.GET.get('prediction_first[]') - prediction_current = self.request.GET.get('prediction_current[]') - plan_first = self.request.GET.get('plan_first[]') - plan_current = self.request.GET.get('plan_current[]') - categories = self.request.GET.get('categories[]') - status = self.request.GET.get('status[]') - delta = self.request.GET.get('delta[]') - fact = self.request.GET.get('fact[]') - age = self.request.GET.get('age[]') - included = self.request.GET.get('included[]') - excluded = self.request.GET.get('excluded[]') - rayons = self.request.GET.get('rayon[]') - aos = self.request.GET.get('ao[]') if not new_status: - return Response({'message': 'No status provided'}, status=http_status.HTTP_400_BAD_REQUEST) - if not any( - [location_ids, rayons, aos, prediction_first, plan_first, plan_current, prediction_current, categories, - status, - delta, fact, age]): - qs = models.PlacementPoint.objects.none() - if rayons: - rayons = list(rayons.split(',')) - qs = qs.filter(rayon_id__in=rayons) - if aos: - aos = list(aos.split(',')) - qs = qs.filter(okrug_id__in=aos) - if location_ids: - location_ids = list(location_ids.split(',')) - qs = qs.filter(location_id__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 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 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 age: - age = list(age.split(',')) - qs = qs.filter(age__range=age) - if excluded: - excluded = list(excluded.split(',')) - qs = qs.filter(~Q(location_id__in=excluded)) - if included: - inclded = list(included.split(',')) - qs2 = models.PlacementPoint.objects.filter(location_id__in=inclded).all() - qs = (qs | qs2).distinct() - if not any( - [location_ids, rayons, aos, prediction_first, plan_first, plan_current, prediction_current, categories, - status, - delta, fact, age, excluded, included]): - return Response({'message': 'Empty queryset'}, status=http_status.HTTP_200_OK) - + return Response({'message': 'No status'}, 400) qs.update(**{'status': new_status}) return Response({'message': 'status updated'}, status=http_status.HTTP_200_OK) - -class update_fact(APIView): - def put(self, request): + @action(detail=False, methods=['put']) + def update_fact(self, request): point_id = request.GET.get('location_id') fact = request.GET.get('fact') if not point_id or not fact or not fact.isdigit(): @@ -402,67 +175,9 @@ class update_fact(APIView): qs.update(**{'fact': fact}) return Response({'message': 'fact updated'}, status=http_status.HTTP_200_OK) - -class get_excel(APIView): - def get(self, request): - qs = models.PlacementPoint.objects.all() - location_ids = self.request.GET.get('location_ids[]') - prediction_first = self.request.GET.get('prediction_first[]') - prediction_current = self.request.GET.get('prediction_current[]') - categories = self.request.GET.get('categories[]') - status = self.request.GET.get('status[]') - delta = self.request.GET.get('delta[]') - fact = self.request.GET.get('fact[]') - age = self.request.GET.get('age[]') - 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[]') - rayons = self.request.GET.get('rayon[]') - aos = self.request.GET.get('ao[]') - if location_ids: - location_ids = list(location_ids.split(',')) - qs = qs.filter(location_id__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 age: - age = list(age.split(',')) - qs = qs.filter(age__range=age) - if plan_first: - plan_first = list(plan_first.split(',')) - qs = qs.filter(plan_first__range=plan_first) - if rayons: - rayons = list(rayons.split(',')) - qs = qs.filter(rayon_id__in=rayons) - if aos: - aos = list(aos.split(',')) - qs = qs.filter(okrug_id__in=aos) - if plan_current: - plan_current = list(plan_current.split(',')) - qs = qs.filter(plan_current__range=plan_current) - if excluded: - excluded = list(excluded.split(',')) - qs = qs.filter(~Q(location_id__in=excluded)) - if included: - inclded = list(included.split(',')) - qs2 = models.PlacementPoint.objects.filter(location_id__in=inclded).all() - qs = (qs | qs2).distinct() + @action(detail=False, methods=['get']) + def to_excel(self, request): + qs = self.get_queryset() data = pd.DataFrame(list(qs.values())) data['start_date'] = data['start_date'].dt.tz_localize(None) data['sample_trn'] = data['sample_trn'].astype(int) @@ -478,41 +193,31 @@ class get_excel(APIView): res['Content-Disposition'] = f'attachment; filename={filename}' return res - -class get_top_10k(APIView): - def get(self, request): - if models.PlacementPoint.objects.count()>10000: + @action(detail=False, methods=['get']) + def get_10k(self, request): + if models.PlacementPoint.objects.count() > 10000: qs = models.PlacementPoint.objects.order_by('-prediction_current').all()[10000] - return Response({'prediction_current':qs.prediction_current},200) - return Response({'prediction_current': models.PlacementPoint.objects.order_by('prediction_current').first().prediction_current}, 200) + return Response({'prediction_current': qs.prediction_current}, 200) + return Response( + {'prediction_current': models.PlacementPoint.objects.order_by( + 'prediction_current').first().prediction_current}, + 200) -class search_address(APIView): - pagination_class=MyPagination - queryset = models.PlacementPoint.objects.all() - serializer_class = PlacementPointSerializer - @property - def paginator(self): - if not hasattr(self, '_paginator'): - if self.pagination_class is None: - self._paginator = None - else: - self._paginator = self.pagination_class() - return self._paginator - def paginate_queryset(self, queryset): - if self.paginator is None: - return None - return self.paginator.paginate_queryset(queryset, self.request, view=self) +class refresh_placement_points(APIView): + @staticmethod + def post(request): + warnings.filterwarnings('ignore') + file = request.FILES['file'] + load_data(file) + return Response(status=http_status.HTTP_200_OK) - def get_paginated_response(self, data): - assert self.paginator is not None - return self.paginator.get_paginated_response(data) - def get(self, request): - address = self.request.GET.get('address') - qs = self.queryset.filter(address__icontains=address) - page = self.paginate_queryset(qs) - if page is not None: - serializer = self.serializer_class(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.serializer_class(qs, many=True) - return Response(serializer.data) \ No newline at end of file + +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) + return Response(status=http_status.HTTP_200_OK)