"""Tag views.""" from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from rest_framework import generics, mixins, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.serializers import ValidationError from location.models import WineRegion from product.models import ProductType from search_indexes import views as search_views from tag import filters, models, serializers class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): pagination_class = None permission_classes = (permissions.AllowAny,) serializer_class = serializers.TagBaseSerializer filterset_class = filters.TagsFilterSet queryset = models.Tag.objects.all() def get_queryset(self): result_tags_ids = models.ChosenTagSettings.objects \ .by_country_code(self.request.country_code) \ .values('tag_id') return models.Tag.objects \ .filter(id__in=result_tags_ids) \ .order_by_priority() def list(self, request, *args, **kwargs): # TMP TODO remove it later # Временный хардкод для демонстрации > 15 ноября, потом удалить! queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) result_list = serializer.data if request.query_params.get('type') and (settings.ESTABLISHMENT_CHOSEN_TAGS or settings.NEWS_CHOSEN_TAGS): ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get( 'type') == 'establishment' else settings.NEWS_CHOSEN_TAGS result_list = sorted(result_list, key=lambda x: ordered_list.index(x['index_name'])) return Response(result_list) # User`s views & viewsets class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" filterset_class = filters.TagCategoryFilterSet pagination_class = None permission_classes = (permissions.AllowAny,) queryset = models.TagCategory.objects.with_tags().with_base_related(). \ distinct() serializer_class = serializers.TagCategoryBaseSerializer # User`s views & viewsets class FiltersTagCategoryViewSet(TagCategoryViewSet): """ViewSet for TagCategory model.""" serializer_class = serializers.FiltersTagCategoryBaseSerializer index_name_to_order = { 'open_now': 9, 'works_noon': 8, 'works_evening': 7, 'pop': 6, 'category': 5, 'toque_number': 4, 'cuisine': 3, 'moment': 2, 'service': 1, } def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset().exclude(public=False)) serializer = self.get_serializer(queryset, many=True) result_list = serializer.data query_params = request.query_params params_type = query_params.get('type') if query_params.get('establishment_type'): params_type = query_params.get('establishment_type') elif query_params.get('product_type'): params_type = query_params.get('product_type') week_days = tuple(map(_, ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))) short_week_days = tuple(map(_, ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"))) flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') filter_flags = {flag_name: False for flag_name in flags} additional_flags = [] if params_type == 'restaurant': additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now'] elif params_type in ['winery', 'wine']: additional_flags += ['wine_region'] elif params_type == 'artisan': additional_flags += ['works_now', 'works_at_weekday'] for flag_name in additional_flags: filter_flags[flag_name] = True if request.query_params.get('product_type') == ProductType.WINE: wine_region_id = query_params.get('wine_region_id__in') if str(wine_region_id).isdigit(): queryset = WineRegion.objects.filter(id=int(wine_region_id)) else: queryset = WineRegion.objects.all() wine_regions = { "index_name": "wine_region", "label_translated": "Wine region", "param_name": "wine_region_id__in", "filters": [{ "id": obj.id, "index_name": obj.name.lower().replace(' ', '_'), "label_translated": obj.name } for obj in queryset] } result_list.append(wine_regions) for item in result_list: if 'filters' in item: item['filters'].sort(key=lambda x: x.get('label_translated')) if filter_flags['toque_number']: toques = { "index_name": "toque_number", "label_translated": "Toques", "param_name": "toque_number__in", 'type': 'toque', "filters": [{ "id": toque_id, "index_name": "toque_%d" % toque_id, "label_translated": "Toque %d" % toque_id } for toque_id in range(6)] } result_list.append(toques) if filter_flags['works_noon']: works_noon = { "index_name": "works_noon", "label_translated": "Open noon", "param_name": "works_noon__in", 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), "label_translated": short_week_days[weekday], } for weekday in range(7)] } result_list.append(works_noon) if filter_flags['works_evening']: works_evening = { "index_name": "works_evening", "label_translated": "Open evening", "param_name": "works_evening__in", 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), "label_translated": short_week_days[weekday], } for weekday in range(7)] } result_list.append(works_evening) if filter_flags['works_now']: works_now = { "index_name": "open_now", "label_translated": "Open now", "param_name": "open_now", "type": 'bool', } result_list.append(works_now) if filter_flags['works_at_weekday']: works_at_weekday = { "index_name": "works_at_weekday", "label_translated": "Works at weekday", "param_name": "works_at_weekday__in", 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), "label_translated": short_week_days[weekday], } for weekday in range(7)] } result_list.append(works_at_weekday) search_view_class = self.define_search_view_by_request(request) facets = search_view_class.as_view({'get': 'list'})(self.mutate_request(self.request)).data['facets'] result_list = self.remove_empty_filters(result_list, facets) tag_category = list(filter(lambda x: x.get('index_name') == 'tag', result_list)) result_list = [category for category in result_list if category.get('index_name') != 'tag'] if len(tag_category): tag_category = list(filter(lambda x: x.get('index_name') == 'pop', tag_category[0]['filters'])) if len(tag_category): # we have Pop tag in our results tag_category = tag_category[0] tag_category['param_name'] = 'tags_id__in' result_list.append(tag_category) result_list.sort(key=lambda x: self.index_name_to_order.get(x.get('index_name'), 0), reverse=True) return Response(result_list) @staticmethod def mutate_request(request): """Remove all filtering get params and remove s_ from the rest of them""" request.GET._mutable = True for name in request.query_params.copy().keys(): value = request.query_params.pop(name) if name.startswith('s_'): request.query_params[name[2:]] = value[0] request.GET._mutable = False return request._request @staticmethod def define_search_view_by_request(request): request.GET._mutable = True if request.query_params.get('items'): items = request.query_params.pop('items')[0] else: raise ValidationError({'detail': _('Missing required "items" parameter')}) item_to_class = { 'news': search_views.NewsDocumentViewSet, 'establishments': search_views.EstablishmentDocumentViewSet, 'products': search_views.ProductDocumentViewSet, } klass = item_to_class.get(items) if klass is None: raise ValidationError({'detail': _('news/establishments/products')}) request.GET._mutable = False return klass @staticmethod def remove_empty_filters(filters, facets): # parse facets if facets.get('_filter_tag'): tags_to_preserve = list(map(lambda el: el['key'], facets['_filter_tag']['tag']['buckets'])) if facets.get('_filter_wine_colors'): wine_colors_to_preserve = list( map(lambda el: el['key'], facets['_filter_wine_colors']['wine_colors']['buckets'])) if facets.get('_filter_wine_region_id'): wine_regions_to_preserve = list( map(lambda el: el['key'], facets['_filter_wine_region_id']['wine_region_id']['buckets'])) if facets.get('_filter_toque_number'): toque_numbers = list(map(lambda el: el['key'], facets['_filter_toque_number']['toque_number']['buckets'])) if facets.get('_filter_works_noon'): works_noon = list(map(lambda el: el['key'], facets['_filter_works_noon']['works_noon']['buckets'])) if facets.get('_filter_works_evening'): works_evening = list(map(lambda el: el['key'], facets['_filter_works_evening']['works_evening']['buckets'])) if facets.get('_filter_works_at_weekday'): works_at_weekday = list( map(lambda el: el['key'], facets['_filter_works_at_weekday']['works_at_weekday']['buckets'])) if facets.get('_filter_works_now'): works_now = list(map(lambda el: el['key'], facets['_filter_works_now']['works_now']['buckets'])) # remove empty filters for category in filters: param_name = category.get('param_name') if param_name == 'tags_id__in': category['filters'] = list(filter(lambda tag: tag['id'] in tags_to_preserve, category['filters'])) elif param_name == 'wine_colors_id__in': category['filters'] = list( filter(lambda tag: tag['id'] in wine_colors_to_preserve, category['filters'])) elif param_name == 'wine_region_id__in': category['filters'] = list( filter(lambda tag: tag['id'] in wine_regions_to_preserve, category['filters'])) elif param_name == 'toque_number__in': category['filters'] = list(filter(lambda tag: tag['id'] in toque_numbers, category['filters'])) elif param_name == 'works_noon__in': category['filters'] = list(filter(lambda tag: tag['id'] in works_noon, category['filters'])) elif param_name == 'works_evening__in': category['filters'] = list(filter(lambda tag: tag['id'] in works_evening, category['filters'])) elif param_name == 'works_at_weekday__in': category['filters'] = list(filter(lambda tag: tag['id'] in works_at_weekday, category['filters'])) return filters # BackOffice user`s views & viewsets class BindObjectMixin: """Bind object mixin.""" def get_serializer_class(self): if self.action == 'bind_object': return self.bind_object_serializer_class elif self.action == 'chosen': return self.chosen_serializer_class return self.serializer_class def perform_binding(self, serializer): raise NotImplemented def perform_unbinding(self, serializer): raise NotImplemented @action(methods=['post', 'delete'], detail=True, url_path='bind-object') def bind_object(self, request, pk=None): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) if request.method == 'POST': self.perform_binding(serializer) return Response(serializer.data, status=status.HTTP_201_CREATED) elif request.method == 'DELETE': self.perform_unbinding(serializer) return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=['post', 'delete'], detail=True, url_path='chosen') def chosen(self, request, pk=None): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) if request.method == 'POST': self.perform_binding(serializer) return Response(serializer.data, status=status.HTTP_201_CREATED) elif request.method == 'DELETE': self.perform_unbinding(serializer) return Response(status=status.HTTP_204_NO_CONTENT) class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, BindObjectMixin, viewsets.GenericViewSet): """List/create tag view.""" pagination_class = None permission_classes = (permissions.IsAuthenticated,) queryset = models.Tag.objects.all() serializer_class = serializers.TagBackOfficeSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer chosen_serializer_class = serializers.ChosenTagBindObjectSerializer def perform_binding(self, serializer): data = serializer.validated_data tag = data.pop('tag') obj_type = data.get('type') related_object = data.get('related_object') # for compatible exist code if self.action == 'chosen': obj_type = ContentType.objects.get_for_model(models.ChosenTag) models.ChosenTag.objects.update_or_create( tag=tag, content_type=obj_type, object_id=related_object.id, defaults={ "content_object": related_object, "site": self.request.user.last_country }, ) if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: tag.establishments.add(related_object) elif obj_type == self.bind_object_serializer_class.NEWS: tag.news.add(related_object) def perform_unbinding(self, serializer): data = serializer.validated_data tag = data.pop('tag') obj_type = data.get('type') related_object = data.get('related_object') # for compatible exist code if self.action == 'chosen': related_object.chosen_tags.filter(tag=tag).delete() if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: tag.establishments.remove(related_object) elif obj_type == self.bind_object_serializer_class.NEWS: tag.news.remove(related_object) class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.RetrieveModelMixin, BindObjectMixin, TagCategoryViewSet): """ViewSet for TagCategory model for BackOffice users.""" permission_classes = (permissions.IsAuthenticated,) queryset = TagCategoryViewSet.queryset.with_extended_related() serializer_class = serializers.TagCategoryBackOfficeDetailSerializer bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer def perform_binding(self, serializer): data = serializer.validated_data tag_category = data.pop('tag_category') obj_type = data.get('type') related_object = data.get('related_object') if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: tag_category.establishment_types.add(related_object) elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: tag_category.news_types.add(related_object) def perform_unbinding(self, serializer): data = serializer.validated_data tag_category = data.pop('tag_category') obj_type = data.get('type') related_object = data.get('related_object') if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: tag_category.establishment_types.remove(related_object) elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: tag_category.news_types.remove(related_object)