Files
consumocuidado-server/products/views.py
2021-02-26 11:46:40 +00:00

254 lines
10 KiB
Python

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
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
"""
try:
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=',')
counter = 0
# get company linked to user
company_qs = Company.objects.filter(creator=request.user)
if company_qs:
company = company_qs.first()
else:
return Response({"errors":{"details": "Your user has no company to add products to"}})
# create historysync instance
history = HistorySync.objects.create(company=company, sync_date=datetime.datetime.now(), quantity=len(decoded_file))
for row in csv_reader:
if '' in (row['nombre-producto'], row['descripcion'], row['precio'], row['categoria']):
logging.error(f"Required data missing: {row}")
continue
try:
# download image references in csv
if row['imagen'].strip() != '':
image_url = row['imagen'].strip()
response = requests.get(image_url, stream=True)
if response.status_code == 200:
path = f"{setting.BASE_DIR}media/{row['nombre-producto'].strip()}.{image.url.split('/')[-1]}"
logging.info(f"Saving product image to: {path}")
new_image = open(path, 'wb')
for chunk in response:
new_image.write(chunk)
new_image.close()
else:
logging.warning(f"Image URL did not work: {image_url}")
new_image = None
else:
new_image = None
# TODO: if tags is empty, auto-generate tags
# assemble instance data
product_data = {
'id': None if row['id'].strip()=='' else row['id'].strip(),
'company': Company.objects.filter(creator=request.user).first(),
'name': row['nombre-producto'].strip(),
'description': row['descripcion'].strip(),
'image': new_image,
'url': row['url'].strip(),
'price': row['precio'].strip(),
'shipping_cost': row['gastos-envio'].strip(),
'shipping_terms': row['cond-envio'].strip(),
'discount': row['descuento'].strip(),
'stock': row['stock'].strip(),
'tags': row['tags'].strip(),
'category': row['categoria'].strip(),
'identifiers': row['identificadores'].strip(),
'history': history,
'creator': request.user,
}
Product.objects.create(**product_data)
logging.info(f"Created Product: {product_data}")
counter += 1
except Exception as e:
logging.error(f"Could not parse {row}")
return Response()
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"}})
elif q is '':
# return everything
serializer = SearchResultSerializer(Product.objects.all(), many=True)
products = serializer.data
# filters = extract_search_filters(products)
return Response(data={"filters": [], "count": len(products), "products": products})
try:
# we collect our results here
result_set = set()
# values for response
prices = {
'min': None,
'max': None,
}
# 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 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)
# TODO: add search for entire phrase ???
# extract filters from result_set
filters = extract_search_filters(result_set)
result_list = list(result_set)
if order == 'newest':
# order results by created
ordered_products = sorted(result_list, key= lambda x:x.created, reverse=True)
elif order == 'oldest':
# order results by created
ordered_products = sorted(result_list, key= lambda x:x.created, reverse=False)
else:
# order results by RANK
ordered_products = sorted(result_list, key= lambda rank:rank.rank, reverse=True)
# extract max and min price values
serializer = SearchResultSerializer(ordered_products, many=True)
product_results = [dict(i) for i in serializer.data]
total_results = len(product_results)
# RESULTS PAGINATION
if limit is not None and offset is not None:
limit = int(limit)
offset = int(offset)
product_results = product_results[offset:(limit+offset)]
elif limit is not None:
limit = int(limit)
product_results = product_results[:limit]
return Response(data={"filters": filters, "count": total_results, "products": product_results, 'prices': prices})
except Exception as e:
return Response({"errors": {"details": str(e)}}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)