diff --git a/README.md b/README.md index 7c2ba1d..a44efe4 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Endpoint: `/api/v1/product_search/` Query parameters: - - `query_string`: text from the search input box + - `q`: text from the search input box Response format: diff --git a/core/management/commands/addtaxonomy.py b/core/management/commands/addtaxonomy.py index 569c4ac..040fe32 100644 --- a/core/management/commands/addtaxonomy.py +++ b/core/management/commands/addtaxonomy.py @@ -3,7 +3,7 @@ import logging from django.core.management.base import BaseCommand from django.conf import settings -from core.models import TreeTag +from products.models import CategoryTag from products.models import Product @@ -15,7 +15,7 @@ class Command(BaseCommand): print(self.help) print("Deleting existing instances") - TreeTag.objects.all().delete() + CategoryTag.objects.all().delete() file_path = settings.BASE_DIR + '/../datasets/' + settings.TAXONOMY_FILE counter = 0 @@ -23,11 +23,11 @@ class Command(BaseCommand): print(f"Reading from {settings.TAXONOMY_FILE}") for line in data_file.readlines(): try: - tag = Product.tags.tag_model.objects.create(name=line) + tag = Product.category.tag_model.objects.create(name=line) counter += 1 print('.', end='') logging.debug(f"{tag} created from {line}") except Exception as e: logging.error(f"{type(e)} while creating tags from {settings.TAXONOMY_FILE}") - print(f"\nAdded {counter} Tag objects to Product.tags") + print(f"\nAdded {counter} Tag objects to Product.category") print('Shutting down\n') diff --git a/core/management/commands/extractparenttags.py b/core/management/commands/extractparenttags.py index 7e14657..2ffec50 100644 --- a/core/management/commands/extractparenttags.py +++ b/core/management/commands/extractparenttags.py @@ -3,19 +3,19 @@ import logging from django.core.management.base import BaseCommand from django.conf import settings -from core.models import TreeTag +from products.models import CategoryTag class Command(BaseCommand): - help = 'Extract top level tags' + help = 'Extract top level catefory tags' def handle(self, *args, **kwargs): # get all instances - tags = TreeTag.objects.all() + tags = CategoryTag.objects.all() top_tags = [] - print("Extracting top-level tags from TreeTag instances") + print("Extracting top-level tags from CategoryTag instances") # extract tags with no ancestor for tag in tags: if not tag.get_ancestors(): diff --git a/core/models.py b/core/models.py index 5e42300..3c49a74 100644 --- a/core/models.py +++ b/core/models.py @@ -73,10 +73,3 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): verbose_name = 'Usuario' verbose_name_plural = 'Usuarios' - -class TreeTag(TagTreeModel): - class TagMeta: - initial = "" - force_lowercase = True - max_count=20 - # autocomplete_view = 'myapp.views.hobbies_autocomplete' diff --git a/history/models.py b/history/models.py index e102cb0..5296478 100644 --- a/history/models.py +++ b/history/models.py @@ -9,9 +9,9 @@ class HistorySync(models.Model): company = models.ForeignKey('companies.Company', on_delete=models.DO_NOTHING, null=True) rss_url = models.URLField('URL del feed', null=True, blank=True) - sync_date = models.DateTimeField('Fecha de lanzamiento', null=True) + sync_date = models.DateTimeField('Fecha de lanzamiento', null=True, blank=True) result = models.TextField('Resultado', null=True, blank=True) - quantity = models.PositiveIntegerField('Productos importados', null=True) + quantity = models.PositiveIntegerField('Productos importados', null=True, blank=True) # internal created = models.DateTimeField('date of creation', auto_now_add=True) diff --git a/products/admin.py b/products/admin.py index 306b15d..af39311 100644 --- a/products/admin.py +++ b/products/admin.py @@ -4,4 +4,7 @@ from . import models # Register your models here. -admin.site.register(models.Product) \ No newline at end of file +admin.site.register(models.Product) +admin.site.register(models.TreeTag) +admin.site.register(models.CategoryTag) +admin.site.register(models.AttributeTag) diff --git a/products/models.py b/products/models.py index 84c7060..81f5146 100644 --- a/products/models.py +++ b/products/models.py @@ -2,11 +2,34 @@ from django.contrib.gis.db import models from tagulous.models import SingleTagField, TagField, TagTreeModel -from core.models import TreeTag from companies.models import Company # Create your models here. +class TreeTag(TagTreeModel): + class TagMeta: + initial = "" + force_lowercase = True + max_count=20 + # autocomplete_view = 'myapp.views.hobbies_autocomplete' + + +class CategoryTag(TagTreeModel): + class TagMeta: + initial = "" + force_lowercase = True + max_count=20 + # autocomplete_view = 'myapp.views.hobbies_autocomplete' + + +class AttributeTag(TagTreeModel): + class TagMeta: + initial = "" + force_lowercase = True + max_count=20 + # autocomplete_view = 'myapp.views.hobbies_autocomplete' + + class Product(models.Model): @@ -33,10 +56,10 @@ class Product(models.Model): sourcing_date = models.DateTimeField('Fecha de importación original de producto', null=True, blank=True) update_date = models.DateTimeField('Fecha de actualización de producto', null=True, blank=True) discount = models.DecimalField('Descuento', max_digits=5, decimal_places=2, null=True, blank=True) - stock = models.PositiveIntegerField('Stock', null=True) + stock = models.PositiveIntegerField('Stock', null=True, blank=True) tags = TagField(to=TreeTag) - category = SingleTagField(blank=True, null=True) # main tag category - attributes = TagField(to=TreeTag, related_name='product_attributes') + category = SingleTagField(to=CategoryTag, null=True, blank=True) # main tag category + attributes = TagField(to=AttributeTag, related_name='product_attributes') identifiers = models.TextField('Identificador único de producto', null=True, blank=True) # internal diff --git a/products/tests.py b/products/tests.py index bbe2151..5929277 100644 --- a/products/tests.py +++ b/products/tests.py @@ -212,11 +212,11 @@ class ProductViewSetTest(APITestCase): # Assert access is granted self.assertEqual(response.status_code, status.HTTP_200_OK) - # TODO: assert correct order - previous_date = datetime.datetime.now() - for instance in payload: - self.assertTrue(datetime.datetime.fromisoformat(instance['created'][:-1]) < previous_date) - previous_date = datetime.datetime.fromisoformat(instance['created'][:-1]) + # assert correct order + dates = [d['created'][:-1] for d in payload] + for i in range(len(dates)-1): + # first instance should be most recent + self.assertTrue(datetime.datetime.fromisoformat(dates[i]) > datetime.datetime.fromisoformat(dates[i+1])) # authenticated user def test_auth_user_can_list_instances(self): @@ -321,7 +321,7 @@ class ProductViewSetTest(APITestCase): 'discount': '0.05', 'stock': 22, 'tags': ['tag1x, tag2x'], - 'category': 'MayorTagCategory2', + 'category': 'mayortagcategory2', 'attributes': ['color/blue', 'size/m'], 'identifiers': '34rf34f43c43', } @@ -531,9 +531,9 @@ class ProductSearchTest(TestCase): self.factory(tags="azules"), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") - url = f"{self.endpoint}?query_string={query_string}" + url = f"{self.endpoint}?q={q}" # send in request response = self.client.get(url) @@ -565,10 +565,10 @@ class ProductSearchTest(TestCase): self.factory(tags="azules"), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") limit = 2 - url = f"{self.endpoint}?query_string={query_string}&limit=2" + url = f"{self.endpoint}?q={q}&limit=2" # send in request response = self.client.get(url) @@ -592,9 +592,9 @@ class ProductSearchTest(TestCase): self.factory(tags="lunares/rojos", description="zapatos", shipping_cost=0.00), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # shipping_cost=true - url = f"{self.endpoint}?query_string={query_string}&shipping_cost=true" + url = f"{self.endpoint}?q={q}&shipping_cost=true" # send in request response = self.client.get(url) # check response @@ -613,10 +613,10 @@ class ProductSearchTest(TestCase): self.factory(tags="azules", shipping_cost=10.00), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # shipping_cost=false - url = f"{self.endpoint}?query_string={query_string}&shipping_cost=false" + url = f"{self.endpoint}?q={q}&shipping_cost=false" # send in request response = self.client.get(url) # check response @@ -636,9 +636,9 @@ class ProductSearchTest(TestCase): self.factory(tags="lunares/rojos", category='zapatos', description="zapatos verdes", discount=None), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # discount=true - url = f"{self.endpoint}?query_string={query_string}&discount=true" + url = f"{self.endpoint}?q={q}&discount=true" # send in request response = self.client.get(url) # check response @@ -658,9 +658,9 @@ class ProductSearchTest(TestCase): self.factory(tags="azules", discount=9.00), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # discount=true - url = f"{self.endpoint}?query_string={query_string}&discount=false" + url = f"{self.endpoint}?q={q}&discount=false" # send in request response = self.client.get(url) # check response @@ -680,9 +680,8 @@ class ProductSearchTest(TestCase): self.factory(tags="azules"), ] - query_string = quote("zapatos rojos") # discount=true - url = f"{self.endpoint}?query_string=" + url = f"{self.endpoint}?q=" # send in request response = self.client.get(url) # check response @@ -702,9 +701,9 @@ class ProductSearchTest(TestCase): self.factory(tags="zapatos/azules", category="deporte", description='rojos', discount=12.00), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # discount=true - url = f"{self.endpoint}?query_string={query_string}&category=ropa" + url = f"{self.endpoint}?q={q}&category=ropa" # send in request response = self.client.get(url) # check response @@ -724,9 +723,9 @@ class ProductSearchTest(TestCase): self.factory(tags="zapatos/azules", category="deporte", description='rojos', discount=12.00), ] - query_string = quote("zapatos rojos") + q = quote("zapatos rojos") # discount=true - url = f"{self.endpoint}?query_string={query_string}&tags=deporte" + url = f"{self.endpoint}?q={q}&tags=deporte" # send in request response = self.client.get(url) # check response @@ -746,8 +745,8 @@ class ProductSearchTest(TestCase): self.factory(tags="lunares/rojos", category='zapatos', description="zapatos verdes", price=None), ] price_min = 5.00 - query_string = quote("zapatos rojos") - url = f"{self.endpoint}?query_string={query_string}&price_min={price_min}" + q = quote("zapatos rojos") + url = f"{self.endpoint}?q={q}&price_min={price_min}" # send in request response = self.client.get(url) @@ -770,8 +769,8 @@ class ProductSearchTest(TestCase): self.factory(tags="lunares/rojos", category='zapatos', description="zapatos verdes", price=100.00), ] price_max = 50.00 - query_string = quote("zapatos rojos") - url = f"{self.endpoint}?query_string={query_string}&price_max={price_max}" + q = quote("zapatos rojos") + url = f"{self.endpoint}?q={q}&price_max={price_max}" # send in request response = self.client.get(url) diff --git a/products/views.py b/products/views.py index a695710..192ca78 100644 --- a/products/views.py +++ b/products/views.py @@ -28,7 +28,7 @@ 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 +from utils.tag_filters import ProductTagFilter, ProductOrderFilter logging.basicConfig( @@ -40,21 +40,11 @@ logging.basicConfig( class ProductViewSet(viewsets.ModelViewSet): - # queryset = Product.objects.all() + queryset = Product.objects.all().order_by('-created') serializer_class = ProductSerializer permission_classes = [IsAuthenticatedOrReadOnly, IsCreator] filter_backends = [DjangoFilterBackend, OrderingFilter] filterset_class = ProductTagFilter - filterset_fields = ['name', 'tags', 'category', 'attributes', 'company', 'created'] - # TODO: figure out why cant use filterset for company filter - def get_queryset(self): - try: - company_id = self.request.GET.get('company') - company_id = int(company_id) - queryset = Product.objects.filter(company__id=company_id) - except: - queryset = Product.objects.all() - return queryset def perform_create(self, serializer): serializer.save(creator=self.request.user) @@ -159,7 +149,7 @@ def product_search(request): Takes a string of data, return relevant products Params: - - query_string: used for search [MANDATORY] + - q: used for search [MANDATORY] - limit: max number of returned instances [OPTIONAL] - offset: where to start counting results [OPTIONAL] - shipping_cost: true/false @@ -168,7 +158,7 @@ def product_search(request): - tags: string """ # capture query params - query_string = request.GET.get('query_string', None) + 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) @@ -192,9 +182,9 @@ def product_search(request): price_min = request.GET.get('price_min', None) price_max = request.GET.get('price_max', None) - if query_string is None: + if q is None: return Response({"errors": {"details": "No query string to parse"}}) - elif query_string is '': + elif q is '': # return everything serializer = ProductSerializer(Product.objects.all(), many=True) products = serializer.data @@ -205,7 +195,7 @@ def product_search(request): result_set = set() # split query string into single words - chunks = query_string.split(' ') + chunks = q.split(' ') for chunk in chunks: product_set = find_related_products_v6(chunk, shipping_cost, discount, category, tags, price_min, price_max) # add to result set diff --git a/stats/models.py b/stats/models.py index 1de6942..0b86ec8 100644 --- a/stats/models.py +++ b/stats/models.py @@ -17,11 +17,11 @@ class StatsLog(models.Model): action_object_object_id = models.CharField(max_length=255, blank=True, null=True) action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id') user = models.ForeignKey(User, on_delete=models.DO_NOTHING, null=True) - anonymous = models.BooleanField('Usuario no registrado', null=True) + anonymous = models.BooleanField('Usuario no registrado', null=True, blank=True) ip_address = models.GenericIPAddressField('IP usuario', null=True, blank=True) geo = models.PointField('Ubicación aproximada', null=True, blank=True ) - contact = models.BooleanField('Empresa contactada', null=True) - shop = models.BooleanField('Redirigido por botón "Comprar"', null=True) + contact = models.BooleanField('Empresa contactada', null=True, blank=True) + shop = models.BooleanField('Redirigido por botón "Comprar"', null=True, blank=True) # internal created = models.DateTimeField('date of creation', auto_now_add=True) diff --git a/utils/tag_filters.py b/utils/tag_filters.py index 3cc8502..eec4d5a 100644 --- a/utils/tag_filters.py +++ b/utils/tag_filters.py @@ -1,5 +1,7 @@ import django_filters +from rest_framework.filters import BaseFilterBackend + from companies.models import Company from products.models import Product @@ -26,10 +28,19 @@ class ProductTagFilter(django_filters.FilterSet): class Meta: model = Product - fields = ['name', 'tags', 'category', 'attributes'] + fields = ['name', 'tags', 'category', 'attributes', 'company', 'created',] def tag_filter(self, queryset, name, value): return queryset.filter(**{ name: value, }) + +class ProductOrderFilter(BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + order_field = request.GET.get('order', None) + if order_field is not None: + return queryset.order_by(order_field) + else: + return queryset + diff --git a/utils/woocommerce.py b/utils/woocommerce.py index 36f1cd1..f125e80 100644 --- a/utils/woocommerce.py +++ b/utils/woocommerce.py @@ -53,6 +53,7 @@ def create_imported_product(info, company, history, user): 'creator': user.id if user is not None else None, 'history': history.id, 'url': info['permalink'], + 'stock': info['stock_quantity'], } # parse the product info for key in info: @@ -89,7 +90,7 @@ def create_imported_product(info, company, history, user): return new else: logging.error(f"{serializer.errors}") - return [] + return None def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): @@ -144,11 +145,19 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): counter = 0 for product in products: + # check if exists + if product['sku'] and Product.objects.filter(sku=product['sku']).exists(): + # product already registered + logging.info(f"Product with sku [{product['sku']}] already registered") + print(f"Product with sku [{product['sku']}] already registered") + # TODO: UPDATE instance?? + continue new = create_imported_product(product, company, history, user) - new_products.append(new) - counter += 1 - logging.info(f"Product '{new.name}' created") - print(f"Product '{new.name}' created") + if new is not None: + new_products.append(new) + counter += 1 + logging.info(f"Product '{new.name}' created") + print(f"Product '{new.name}' created") # update history.quantity history.quantity = counter