From 2ecab694b9e8b69c56e106e7e7e7dfb483a725c2 Mon Sep 17 00:00:00 2001 From: Diego Calvo Date: Thu, 8 Apr 2021 15:12:05 +0200 Subject: [PATCH] Woocommerce and some fixes --- back_latienda/permissions.py | 23 +- back_latienda/urls.py | 1 + companies/views.py | 6 +- core/models.py | 2 +- geo/views.py | 2 +- history/views.py | 5 +- products/views.py | 36 +- templates/sync_failed.html | 678 +++++++++++++++++++++++++++++++++++ templates/sync_success.html | 678 +++++++++++++++++++++++++++++++++++ utils/woocommerce.py | 51 ++- 10 files changed, 1456 insertions(+), 26 deletions(-) create mode 100644 templates/sync_failed.html create mode 100644 templates/sync_success.html diff --git a/back_latienda/permissions.py b/back_latienda/permissions.py index 4d9cb9f..d4726cf 100644 --- a/back_latienda/permissions.py +++ b/back_latienda/permissions.py @@ -1,7 +1,25 @@ from rest_framework import permissions -class IsCreator(permissions.BasePermission): +class IsCompanyOwner(permissions.BasePermission): + """ + Grant permission if request.user.company same as obj.id + """ + + def has_object_permission(self, request, view, obj): + if obj is not None: + # allow if authenticated and method is safe + if request.method in permissions.SAFE_METHODS: + return True + + # admins always have permission + if request.user.is_staff is True: + return True + # permission if user is the object's creator + return obj == request.user.company + return False + +class IsProductOwner(permissions.BasePermission): """ Grant permission if request.user same as obj.creator """ @@ -16,10 +34,9 @@ class IsCreator(permissions.BasePermission): if request.user.is_staff is True: return True # permission if user is the object's creator - return obj.creator == request.user + return obj.company == request.user.company return False - class IsStaff(permissions.BasePermission): """ Grant permission if request.user.is_staff is True diff --git a/back_latienda/urls.py b/back_latienda/urls.py index 8349a64..513cdf4 100644 --- a/back_latienda/urls.py +++ b/back_latienda/urls.py @@ -47,6 +47,7 @@ urlpatterns = [ path('api/v1/companies/sample/', company_views.random_company_sample , name='company-sample'), path('api/v1/search/companies/', company_views.CompanySearchViewSet.as_view({'get': 'list'}) , name='company-search'), path('api/v1/purchase_email/', product_views.purchase_email, name='purchase-email'), + path('api/v1/sync_shop/', product_views.sync_shop, name='purchase-email'), path('api/v1/products/all_categories/', product_views.all_categories, name='all-categories'), path('api/v1/stats/me/', stat_views.track_user, name='user-tracker'), path('api/v1/autocomplete/category-tag/', product_views.CategoryTagAutocomplete.as_view(), name='category-autocomplete'), diff --git a/companies/views.py b/companies/views.py index 59452d0..b112e10 100644 --- a/companies/views.py +++ b/companies/views.py @@ -19,7 +19,7 @@ from companies.models import Company from companies.serializers import CompanySerializer from utils.tag_filters import CompanyTagFilter from rest_framework import filters -from back_latienda.permissions import IsCreator, IsSiteAdmin +from back_latienda.permissions import IsCompanyOwner, IsSiteAdmin from utils import woocommerce @@ -27,7 +27,7 @@ from utils import woocommerce class CompanySearchViewSet(viewsets.ModelViewSet): queryset = Company.objects.filter(is_validated=True).order_by('-created') serializer_class = CompanySerializer - permission_classes = [IsAuthenticatedOrReadOnly, IsCreator] + permission_classes = [IsAuthenticatedOrReadOnly, IsCompanyOwner] filter_backends = (filters.SearchFilter, ) # search_fields = ['company_name__unaccent__icontains', 'short_name__unaccent__icontains'] search_fields = ['@company_name', '@short_name'] @@ -36,7 +36,7 @@ class CompanySearchViewSet(viewsets.ModelViewSet): class CompanyViewSet(viewsets.ModelViewSet): queryset = Company.objects.filter(is_validated=True).order_by('-created') serializer_class = CompanySerializer - permission_classes = [IsAuthenticatedOrReadOnly, IsCreator] + permission_classes = [IsAuthenticatedOrReadOnly, IsCompanyOwner] filterset_class = CompanyTagFilter def perform_create(self, serializer): diff --git a/core/models.py b/core/models.py index 89d995f..1672174 100644 --- a/core/models.py +++ b/core/models.py @@ -57,7 +57,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): notify = models.BooleanField('Notificar', default=False, null=True) provider = models.CharField('Proveedor', max_length=1000, blank=True, null=True) # red social de registro email_verified = models.BooleanField('Email verificado', default=False, null=True) - company = models.ForeignKey(Company, null=True, on_delete=models.DO_NOTHING, related_name='custom_user') + company = models.ForeignKey(Company, null=True, blank=True, on_delete=models.DO_NOTHING, related_name='custom_user') is_active = models.BooleanField('Activo', default=True) is_staff = models.BooleanField('Empleado',default=False ) diff --git a/geo/views.py b/geo/views.py index ac6e94a..9a04e75 100644 --- a/geo/views.py +++ b/geo/views.py @@ -1,6 +1,6 @@ from rest_framework import viewsets -from back_latienda.permissions import IsCreator, ReadOnly +from back_latienda.permissions import ReadOnly from . import models from . import serializers diff --git a/history/views.py b/history/views.py index 1ca0c52..ffff720 100644 --- a/history/views.py +++ b/history/views.py @@ -10,9 +10,12 @@ from back_latienda.permissions import IsStaff class HistorySyncViewSet(viewsets.ModelViewSet): - queryset = HistorySync.objects.all() + model = HistorySync serializer_class = HistorySyncLogSerializer permission_classes = [IsAuthenticated,] def perform_create(self, serializer): serializer.save(creator=self.request.user) + + def get_queryset(self): + return self.model.objects.filter(company=self.request.user.company).order_by('-created') \ No newline at end of file diff --git a/products/views.py b/products/views.py index 2aef5a9..357503b 100644 --- a/products/views.py +++ b/products/views.py @@ -30,8 +30,9 @@ from products.models import Product, CategoryTag from products.serializers import ProductSerializer, TagFilterSerializer, SearchResultSerializer from companies.models import Company from stats.models import StatsLog -from back_latienda.permissions import IsCreator, IsSiteAdmin, ReadOnly +from back_latienda.permissions import IsProductOwner, IsSiteAdmin, ReadOnly from .utils import extract_search_filters, ranked_product_search, product_loader, get_related_products +from utils.woocommerce import migrate_shop_products from utils.tag_serializers import TaggitSerializer from utils.tag_filters import ProductTagFilter, ProductOrderFilter @@ -48,7 +49,7 @@ logging.basicConfig( class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.filter(active=True).order_by('-created') serializer_class = ProductSerializer - permission_classes = [IsAuthenticatedOrReadOnly, IsCreator] + permission_classes = [IsAuthenticatedOrReadOnly, IsProductOwner] filter_backends = [DjangoFilterBackend, OrderingFilter] filterset_class = ProductTagFilter @@ -71,7 +72,7 @@ class MyProductsViewSet(viewsets.ModelViewSet): """ model = Product serializer_class = ProductSerializer - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, IsProductOwner] def get_queryset(self): return self.model.objects.filter(company=self.request.user.company).order_by('-created') @@ -403,3 +404,32 @@ def all_categories(request): all_categories.append(instance.name) return Response(data=all_categories) + +@api_view(['POST']) +@permission_classes([IsAuthenticated,]) +def sync_shop(request): + print(request.data) + url = request.data.get('url') + key = request.data.get('key') + secret = request.data.get('secret') + print(f"Starting migration...") + response = migrate_shop_products(url, key, secret, request.user ) + print(f"Products created: {len(response['new_products'])}") + print(response["error"]) + if response["error"]: + message = render_to_string('sync_failed.html', {}) + # send email to company + subject = "Estado de la sincronización" + email = EmailMessage(subject, message, to=[request.user.email]) + email.content_subtype = "html" + email.send() + else: + message = render_to_string('sync_success.html', { + 'new_products': len(response['new_products']) + }) + # send email to company + subject = "Estado de la sincronización" + email = EmailMessage(subject, message, to=[request.user.email]) + email.content_subtype = "html" + email.send() + return Response() diff --git a/templates/sync_failed.html b/templates/sync_failed.html new file mode 100644 index 0000000..e4518c0 --- /dev/null +++ b/templates/sync_failed.html @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + +
+ + + + + + +
+ + + +
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + +
+
+ Se ha producido un error en la sincronización +
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + +
+ + + + + + +
+
+

+ Tu sincronización de productos desde tu tienda online ha finalizado con un error. +
+
+ Comprueba tus credenciales y URL e inténtalo de nuevo. Si el error persiste, contacta con nosotros. +
+
+

+
+
+ +
+
+ +
+ + + + + + +
+ +
+
+
+ +
+
+ + +
+ + + + +
+
+ 2021 La Tienda.coop +
+
+
+ + + + + +
+ + + + diff --git a/templates/sync_success.html b/templates/sync_success.html new file mode 100644 index 0000000..c5d6b7b --- /dev/null +++ b/templates/sync_success.html @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + +
+ + + + + + +
+ + + +
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + +
+
+ Sincronización realizada con éxito +
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + +
+ + + + + + +
+
+

+ Tu sincronización de productos desde tu tienda online ha finalizado con éxito. +
+
+ Se han creado {{count}} productos nuevos. +
+
+

+
+
+ +
+
+ +
+ + + + + + +
+ +
+
+
+ +
+
+ + +
+ + + + +
+
+ 2021 La Tienda.coop +
+
+
+ + + + + +
+ + + + diff --git a/utils/woocommerce.py b/utils/woocommerce.py index 11a947e..c241f30 100644 --- a/utils/woocommerce.py +++ b/utils/woocommerce.py @@ -65,15 +65,29 @@ def create_imported_product(info, company, history, user): # alternative method serializer = ProductSerializer(data=instance_data) if serializer.is_valid(): - try: - new = Product.objects.create(**serializer.validated_data) - new.tags = tags - new.attributes = attributes - new.save() - except Exception as e: - logging.error(f"Could not create product instance: {str(e)}") - return None + # try: + # new = Product.objects.create(**serializer.validated_data) + # new.tags = tags + # new.attributes = attributes + # new.save() + # except Exception as e: + # logging.error(f"Could not create product instance: {str(e)}") + # return None + new, created = Product.objects.get_or_create( + company=company, + creator=user if user is not None else None, + history=history, + url=instance_data['url'], + stock=instance_data['stock'], + name=instance_data['name'], + description=instance_data['description'], + sku=instance_data['sku'], + price=instance_data['price'] + ) + new.tags = tags + new.attributes = attributes + new.save() if len(info['images']) > 0: try: # get image @@ -109,11 +123,13 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): # get wcapi wcapi = get_wcapi_instance(url, key, secret, version) + + consumer_key = 'ck_565539bb25b472b1ff7a209eb157ca11c0a26397' consumer_secret = 'cs_9c1690ba5da0dd70f51d61c395628fa14d1a104c' # get company fom url - company = Company.objects.filter(web_link=url).first() + company = Company.objects.filter(shop_link=url).first() if not company: logging.error(f"Could not find Company with URL: {url}") @@ -122,8 +138,11 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): new_products = [] page = 1 + attemps = 0 + error = False # list products while True: + try: response = wcapi.get('products/', params={'per_page': 10, 'page': page}) page += 1 @@ -131,15 +150,19 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): products = response.json() elif response.status_code == 401: logging.error(f"{response.status_code} [{response.url}]: {response.json()}") - return None + error = True else: logging.error(f"Could not load products from {response.url}: [{response.status_code}]") print(f"Could not load products fom {response.url}: [{response.status_code}]") - return None + error = True except requests.exceptions.ReadTimeout as e: logging.error(f"Timeout reading backend: {str(e)}") # skip and try next - continue + attemps += 1 + if attemps>4: + break + else: + continue # exit loop if no more products if not products: break @@ -149,7 +172,6 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): company=company, sync_date=datetime.datetime.now(), ) - counter = 0 for product in products: # check if exists @@ -169,11 +191,12 @@ def migrate_shop_products(url, key, secret, user=None, version="wc/v3"): # update history.quantity history.quantity = counter history.save() + logging.info(f"Products created: {len(new_products)}") print(f"Products created: {len(new_products)}") - return new_products + return {"new_products": new_products, "error": error}