import logging import csv import datetime from django.db.models import Q from django.core import serializers # Create your views here. from rest_framework import status from rest_framework import viewsets from rest_framework.response import Response from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser, IsAuthenticated from rest_framework.decorators import api_view, permission_classes, action from rest_framework.filters import OrderingFilter from django_filters.rest_framework import DjangoFilterBackend import requests from products.models import Product from products.serializers import ProductSerializer, TagFilterSerializer, SearchResultSerializer from companies.models import Company from history.models import HistorySync from back_latienda.permissions import IsCreator from .utils import extract_search_filters, find_related_products_v3, find_related_products_v6, product_loader from utils.tag_serializers import TaggitSerializer from utils.tag_filters import ProductTagFilter, ProductOrderFilter logging.basicConfig( filename='logs/product-load.log', filemode='w', format='%(levelname)s:%(message)s', level=logging.INFO, ) class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all().order_by('-created') serializer_class = ProductSerializer permission_classes = [IsAuthenticatedOrReadOnly, IsCreator] filter_backends = [DjangoFilterBackend, OrderingFilter] filterset_class = ProductTagFilter def perform_create(self, serializer): serializer.save(creator=self.request.user) @action(detail=True, methods=['GET',]) def related(request): # TODO: find the most similar products return Response(data=[]) @api_view(['GET',]) @permission_classes([IsAuthenticated,]) def my_products(request): qs = Product.objects.filter(creator=request.user) product_serializer = ProductSerializer(qs, many=True) return Response(data=product_serializer.data) @api_view(['POST',]) @permission_classes([IsAuthenticated,]) def load_coop_products(request): """Read CSV file being received Parse it to create products for related Company Authenticated user must have a related company """ try: # check company linked to user if request.user.company is None: return Response({"errors":{"details": "Your user has no company to add products to"}}) csv_file = request.FILES['csv_file'] if csv_file.name.endswith('.csv') is not True: logging.error(f"File {csv_file.name} is not a CSV file") return Response({"errors":{"details": "File is not CSV type"}}) logging.info(f"Reading contents of {csv_file.name}") decoded_file = csv_file.read().decode('utf-8').splitlines() csv_reader = csv.DictReader(decoded_file, delimiter=',') count = product_loader(csv_reader, request.user) if count is None: return Response({"errors": {"details": "Authenticated user is not related to any company"}}, status=status.HTTP_406_NOT_ACCEPTABLE) return Response(f"{count} products registered for {request.user.company.company_name}") except Exception as e: return Response({"errors": {"details": str(type(e))}}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @api_view(['GET',]) # include allowed methods def product_search(request): """ Takes a string of data, return relevant products Params: - q: used for search [MANDATORY] - limit: max number of returned instances [OPTIONAL] - offset: where to start counting results [OPTIONAL] - shipping_cost: true/false - discount: true/false - category: string - tags: string - order: string (newest/oldest) - price_min: int - price_max: int In the response: - filters - count - products - price_min - price_max """ # capture query params q = request.GET.get('q', None) limit = request.GET.get('limit', None) offset = request.GET.get('offset', None) shipping_cost = request.GET.get('shipping_cost', None) if shipping_cost is not None: if shipping_cost == 'true': shipping_cost = True elif shipping_cost == 'false': shipping_cost = False else: shipping_cost = None discount = request.GET.get('discount', None) if discount is not None: if discount == 'true': discount = True elif discount == 'false': discount = False else: discount = None category = request.GET.get('category', None) tags = request.GET.get('tags', None) price_min = request.GET.get('price_min', None) price_max = request.GET.get('price_max', None) order = request.GET.get('order', '') if q is None: return Response({"errors": {"details": "No query string to parse"}}) try: # we collect our results here result_set = set() # values for response prices = { 'min': None, 'max': None, } if q == '': # filter entire queryset products_qs = Product.objects.all() if tags: products_qs = Product.objects.filter(tags=tags) if category: products_qs = Product.objects.filter(category=category) # serialize and list data serializer = ProductSerializer(products_qs, many=True) result_list = [dict(i) for i in serializer.data] else: # split query string into single words chunks = q.split(' ') for chunk in chunks: product_set, min_price, max_price = find_related_products_v6(chunk, shipping_cost, discount, category, tags, price_min, price_max) # update price values if product_set: if prices['min'] is None or min_price['price__min'] < prices['min']: prices['min'] = min_price['price__min'] if prices['max'] is None or max_price['price__max'] > prices['max']: prices['max'] = max_price['price__max'] # add to result set result_set.update(product_set) # serialize and list data serializer = SearchResultSerializer(product_set, many=True) result_list = [dict(i) for i in serializer.data] # extract filters from result_set filters = extract_search_filters(result_set) # order the results if order == 'newest': # order results by created result_list = sorted(result_list, key= lambda x:x['created'], reverse=True) elif order == 'oldest': # order results by created result_list = sorted(result_list, key= lambda x:x['created'], reverse=False) elif q != '': # order results by RANK result_list = sorted(result_list, key= lambda x:x['rank'], reverse=True) total_results = len(result_list) # RESULTS PAGINATION if limit is not None and offset is not None: limit = int(limit) offset = int(offset) result_list = result_list[offset:(limit+offset)] elif limit is not None: limit = int(limit) result_list = result_list[:limit] return Response(data={"filters": filters, "count": total_results, "products": result_list, 'prices': prices}) except Exception as e: return Response({"errors": {"details": str(e)}}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)