From c6f051ac65cb6298ed3ab5d5b87c5f37bd423cd8 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 10:10:16 +0000 Subject: [PATCH 01/10] added email validation to purchase_email view --- products/tests.py | 21 +++++++++++++++++++++ products/views.py | 8 ++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/products/tests.py b/products/tests.py index bbf7cec..19d7f2a 100644 --- a/products/tests.py +++ b/products/tests.py @@ -1253,3 +1253,24 @@ class PurchaseEmailTest(APITestCase): self.assertEquals(response.status_code, 200) self.assertEquals(2, len(mail.outbox)) + def test_anon_user_bad_email(self): + company = CompanyFactory() + self.user.role = 'COOP_MANAGER' + self.user.company = company + self.user.save() + product = ProductFactory(company=company) + + data = { + 'email': '324r@qwer', + 'telephone': '123123123', + 'company': company.id, + 'product': product.id, + 'comment': '', + } + + response = self.client.post(self.endpoint, data=data, format='json') + # assertions + self.assertEquals(response.status_code, 406) + payload = response.json() + self.assertTrue( 'email' in payload['error']) + diff --git a/products/views.py b/products/views.py index 091990d..280f36a 100644 --- a/products/views.py +++ b/products/views.py @@ -5,6 +5,7 @@ import json from django.db.models import Q from django.core import serializers +from django.core.validators import EmailValidator, validate_email from django.contrib.auth import get_user_model from django.template.loader import render_to_string from django.core.mail import EmailMessage @@ -259,7 +260,6 @@ def purchase_email(request): # check data if request.user.is_anonymous and 'email' not in data: return Response({"error": "Anonymous users must include an email parameter value"}, status=status.HTTP_406_NOT_ACCEPTABLE) - try: for param in ('telephone', 'company', 'product', 'comment'): assert(param in data.keys()) @@ -271,7 +271,11 @@ def purchase_email(request): else: email = request.user.email telephone = data.get('telephone') - + # validate email + try: + validate_email(email) + except: + return Response({"error": "Value for email is not valid"}, status=status.HTTP_406_NOT_ACCEPTABLE) # get company company = Company.objects.filter(id=data['company']).first() if not company: From 3cbc807bf7c055ed12c9915ea5d37763572f892a Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 10:23:43 +0000 Subject: [PATCH 02/10] finished work on purchase_email endpoint, for now --- products/tests.py | 6 ------ products/views.py | 22 ++++++++++---------- templates/purchase_contact_confirmation.html | 8 +++++-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/products/tests.py b/products/tests.py index 19d7f2a..e68f9cc 100644 --- a/products/tests.py +++ b/products/tests.py @@ -1233,9 +1233,6 @@ class PurchaseEmailTest(APITestCase): def test_auth_user_can_use(self): company = CompanyFactory() - self.user.role = 'COOP_MANAGER' - self.user.company = company - self.user.save() product = ProductFactory(company=company) data = { @@ -1255,9 +1252,6 @@ class PurchaseEmailTest(APITestCase): def test_anon_user_bad_email(self): company = CompanyFactory() - self.user.role = 'COOP_MANAGER' - self.user.company = company - self.user.save() product = ProductFactory(company=company) data = { diff --git a/products/views.py b/products/views.py index 280f36a..f5b2a1e 100644 --- a/products/views.py +++ b/products/views.py @@ -5,7 +5,7 @@ import json from django.db.models import Q from django.core import serializers -from django.core.validators import EmailValidator, validate_email +from django.core.validators import validate_email from django.contrib.auth import get_user_model from django.template.loader import render_to_string from django.core.mail import EmailMessage @@ -280,32 +280,32 @@ def purchase_email(request): company = Company.objects.filter(id=data['company']).first() if not company: return Response({"error": "Invalid value for company"}, status=status.HTTP_406_NOT_ACCEPTABLE) - # get company manager - manager = User.objects.filter(company=company).first() - if not manager or manager.role != 'COOP_MANAGER': - return Response({"error": "Company has no managing user"}, status=status.HTTP_406_NOT_ACCEPTABLE) # get product product = Product.objects.filter(id=data['product'], company=company).first() if not product: return Response({"error": "Invalid value for product"}, status=status.HTTP_406_NOT_ACCEPTABLE) + # check company.email + if company.email is None: + return Response({"error": "Related compay has no contact email address"}, status=status.HTTP_406_NOT_ACCEPTABLE) - # send email to company manager - manager_message = render_to_string('purchase_notification.html', { + # send email to company + company_message = render_to_string('purchase_notification.html', { 'company': company, 'user': request.user, 'product': product, 'telephone': data['telephone'], }) - subject = "Contacto de usuario sobre venta" - email = EmailMessage(subject, manager_message, to=[manager.email]) + subject = "[latienda.coop] Solicitud de compra" + email = EmailMessage(subject, company_message, to=[company.email]) email.send() - logging.info(f"Email sent to {manager.email} as manager of {company}") + logging.info(f"Email sent to {company}") # send confirmation email to user user_message = render_to_string('purchase_contact_confirmation.html', { 'company': company, 'product': product, + 'company_message': company_message, }) - subject = 'Confirmación de contacto con vendedor' + subject = 'Confirmación de petición de compra' email = EmailMessage(subject, user_message, to=[email]) email.send() logging.info(f"Purchase Contact confirmation email sent to {email}") diff --git a/templates/purchase_contact_confirmation.html b/templates/purchase_contact_confirmation.html index eef225c..7d5deb6 100644 --- a/templates/purchase_contact_confirmation.html +++ b/templates/purchase_contact_confirmation.html @@ -1,6 +1,10 @@ Hola usuario. -Hemos envíado correctamente el email al usuario que gestiona {{company}} sobre el producto {{product}}. -Deberías revibir una respuesta directa en los próximos días. +Hemos envíado correctamente el siguiente email a la empresa {{company}} sobre el producto {{product}}: + +{{company_message}} + + +Deberías recibir una respuesta directa en los próximos días. LaTiendaCOOP From c8181a2893d0c8a129e3ccb052cbf163a7b028b4 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 10:42:33 +0000 Subject: [PATCH 03/10] tweaks to email template --- templates/purchase_notification.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/purchase_notification.html b/templates/purchase_notification.html index 8b485c1..896baef 100644 --- a/templates/purchase_notification.html +++ b/templates/purchase_notification.html @@ -1,9 +1,8 @@ -Hola usuario. -Te contactamos por tu puesto como gestor de {{company}}. +Hola {{company}}. -El usuario {{user.email}} ha mostrado interés en la compra de {{product}}. +El usuario {{user.email}} ha mostrado interés en la compra del producto {{product}}. -Ponte en contacto con el usuario tan pronto como te sea posible, y finalizar la venta. +Ponte en contacto con el usuario tan pronto como te sea posible para finalizar la venta. Teléfono de contacto: {{telephone}} From aa9a6bdf815c09424d0d8a6ed7fc9fc2fc1d693f Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 10:51:45 +0000 Subject: [PATCH 04/10] fixed email error --- back_latienda/settings/production.py | 2 ++ products/views.py | 52 +++++++++++++++------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/back_latienda/settings/production.py b/back_latienda/settings/production.py index 46fb796..653acc2 100644 --- a/back_latienda/settings/production.py +++ b/back_latienda/settings/production.py @@ -41,6 +41,8 @@ DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend" DEFAULT_FROM_EMAIL = "no-reply@latienda.com" +# DEFAULT_FROM_EMAIL = "samuel.molina@enreda.coop" + SERVER_EMAIL = "mail-server@latienda.com" ANYMAIL = { diff --git a/products/views.py b/products/views.py index f5b2a1e..483bc6b 100644 --- a/products/views.py +++ b/products/views.py @@ -267,13 +267,13 @@ def purchase_email(request): return Response({"error": "Required parameters for anonymous user: telephone, company, product, comment"}, status=status.HTTP_406_NOT_ACCEPTABLE) if request.user.is_anonymous: - email = data.get('email') + user_email = data.get('email') else: - email = request.user.email + user_email = request.user.email telephone = data.get('telephone') # validate email try: - validate_email(email) + validate_email(user_email) except: return Response({"error": "Value for email is not valid"}, status=status.HTTP_406_NOT_ACCEPTABLE) # get company @@ -287,28 +287,30 @@ def purchase_email(request): # check company.email if company.email is None: return Response({"error": "Related compay has no contact email address"}, status=status.HTTP_406_NOT_ACCEPTABLE) - - # send email to company - company_message = render_to_string('purchase_notification.html', { - 'company': company, - 'user': request.user, - 'product': product, - 'telephone': data['telephone'], - }) - subject = "[latienda.coop] Solicitud de compra" - email = EmailMessage(subject, company_message, to=[company.email]) - email.send() - logging.info(f"Email sent to {company}") - # send confirmation email to user - user_message = render_to_string('purchase_contact_confirmation.html', { - 'company': company, - 'product': product, - 'company_message': company_message, - }) - subject = 'Confirmación de petición de compra' - email = EmailMessage(subject, user_message, to=[email]) - email.send() - logging.info(f"Purchase Contact confirmation email sent to {email}") + try: + # send email to company + company_message = render_to_string('purchase_notification.html', { + 'company': company, + 'user': request.user, + 'product': product, + 'telephone': data['telephone'], + }) + subject = "[latienda.coop] Solicitud de compra" + email = EmailMessage(subject, company_message, to=[company.email]) + email.send() + logging.info(f"Email sent to {company}") + # send confirmation email to user + user_message = render_to_string('purchase_contact_confirmation.html', { + 'company': company, + 'product': product, + 'company_message': company_message, + }) + subject = 'Confirmación de petición de compra' + email = EmailMessage(subject, user_message, to=[user_email]) + email.send() + logging.info(f"Purchase Contact confirmation email sent to {user_email}") + except Exception as e: + return Response({'error': f"Could not send emails [{str(type(e))}]: {str(e)}"}, status=500) # create statslog instance to register interaction stats_data = { From d01d5bf40732d0356e1e8035974c414c4ee4a47f Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 11:41:02 +0000 Subject: [PATCH 05/10] fix for history admin crashing when company is null --- history/admin.py | 5 ++++- products/tests.py | 5 +++-- products/utils.py | 8 ++++---- products/views.py | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/history/admin.py b/history/admin.py index 87e7f48..da110c1 100644 --- a/history/admin.py +++ b/history/admin.py @@ -9,7 +9,10 @@ class HistoryAdmin(admin.ModelAdmin): list_filter = ('company__company_name',) def company_name(self, instance): - return instance.company.company_name + if instance.company and instance.company.company_name: + return instance.company.company_name + else: + return 'NULL' admin.site.register(models.HistorySync, HistoryAdmin) diff --git a/products/tests.py b/products/tests.py index e68f9cc..ea1dfd8 100644 --- a/products/tests.py +++ b/products/tests.py @@ -14,7 +14,7 @@ from rest_framework import status from companies.factories import CompanyFactory from products.factories import ProductFactory, ActiveProductFactory from products.models import Product -from products.utils import find_related_products_v3 +# from products.utils import find_related_products_v3 from core.factories import CustomUserFactory from core.utils import get_tokens_for_user @@ -1159,6 +1159,7 @@ class AdminProductViewSetTest(APITestCase): self.assertEquals(response.status_code, 204) +''' class FindRelatedProductsTest(APITestCase): def setUp(self): @@ -1195,7 +1196,7 @@ class FindRelatedProductsTest(APITestCase): # assert result self.assertTrue(len(results) == len(expected_instances)) - +''' class PurchaseEmailTest(APITestCase): diff --git a/products/utils.py b/products/utils.py index 05ebadf..ddec191 100644 --- a/products/utils.py +++ b/products/utils.py @@ -85,7 +85,7 @@ def extract_search_filters(result_set): return filter_dict -def find_related_products_v7(description, tags, attributes, category): +def get_related_products(description, tags, attributes, category): products_qs = Product.objects.filter( description=description, tags__in=tags, @@ -94,7 +94,7 @@ def find_related_products_v7(description, tags, attributes, category): )[:6] return products_qs - +''' def find_related_products_v3(keyword): """ Ranked product search @@ -111,9 +111,9 @@ def find_related_products_v3(keyword): ).filter(rank__gt=0.05) # removed order_by because its lost in casting return set(products_qs) +''' - -def find_related_products_v6(keyword, shipping_cost=None, discount=None, category=None, tags=None, price_min=None,price_max=None): +def ranked_product_search(keyword, shipping_cost=None, discount=None, category=None, tags=None, price_min=None,price_max=None): """ Ranked product search diff --git a/products/views.py b/products/views.py index 483bc6b..360b935 100644 --- a/products/views.py +++ b/products/views.py @@ -29,7 +29,7 @@ from products.serializers import ProductSerializer, TagFilterSerializer, SearchR from companies.models import Company from stats.models import StatsLog from back_latienda.permissions import IsCreator, IsSiteAdmin, ReadOnly -from .utils import extract_search_filters, find_related_products_v6, product_loader, find_related_products_v7 +from .utils import extract_search_filters, ranked_product_search, product_loader, get_related_products from utils.tag_serializers import TaggitSerializer from utils.tag_filters import ProductTagFilter, ProductOrderFilter @@ -59,7 +59,7 @@ class ProductViewSet(viewsets.ModelViewSet): """ # TODO: find the most similar products product = self.get_object() - qs = find_related_products_v7(product.description, product.tags.all(), product.attributes.all(), product.category) + qs = get_related_products(product.description, product.tags.all(), product.attributes.all(), product.category) serializer = self.serializer_class(qs, many=True) return Response(data=serializer.data) @@ -195,7 +195,7 @@ def product_search(request): # 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) + product_set, min_price, max_price = ranked_product_search(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']: From 39c8bd5e44c8f36f4f68f860dfc274b6d084dc33 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 11:52:43 +0000 Subject: [PATCH 06/10] history admin dropdown for admin --- geo/models.py | 5 ++++- history/admin.py | 6 +++++- products/tests.py | 40 ---------------------------------------- products/utils.py | 28 +++++----------------------- 4 files changed, 14 insertions(+), 65 deletions(-) diff --git a/geo/models.py b/geo/models.py index 9d1dec9..f2cbd9a 100644 --- a/geo/models.py +++ b/geo/models.py @@ -73,7 +73,10 @@ class City(models.Model): updated = models.DateTimeField('date last update', auto_now=True) def __str__(self): - return f'{self.name} [{self.province}]' + if self.province: + return f'{self.name} [{self.province}]' + else: + return f'{self.name}' class Meta: verbose_name = "Municipio" diff --git a/history/admin.py b/history/admin.py index da110c1..adeeefb 100644 --- a/history/admin.py +++ b/history/admin.py @@ -1,12 +1,16 @@ from django.contrib import admin +from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter, ChoiceDropdownFilter + from . import models # Register your models here. class HistoryAdmin(admin.ModelAdmin): list_display = ('company_name', 'rss_url', 'sync_date', 'result', 'quantity',) - list_filter = ('company__company_name',) + list_filter = ( + ('company', RelatedDropdownFilter), + ) def company_name(self, instance): if instance.company and instance.company.company_name: diff --git a/products/tests.py b/products/tests.py index ea1dfd8..8b497ff 100644 --- a/products/tests.py +++ b/products/tests.py @@ -14,7 +14,6 @@ from rest_framework import status from companies.factories import CompanyFactory from products.factories import ProductFactory, ActiveProductFactory from products.models import Product -# from products.utils import find_related_products_v3 from core.factories import CustomUserFactory from core.utils import get_tokens_for_user @@ -1159,45 +1158,6 @@ class AdminProductViewSetTest(APITestCase): self.assertEquals(response.status_code, 204) -''' -class FindRelatedProductsTest(APITestCase): - - def setUp(self): - """Tests setup - """ - self.factory = ActiveProductFactory - self.model = Product - # clear table - self.model.objects.all().delete() - - def test_v3_find_by_tags(self): - # create tagged product - tag = 'cool' - expected_instances = [ - self.factory(tags=tag), - self.factory(tags=f'{tag} hat'), - self.factory(tags=f'temperatures/{tag}'), - self.factory(tags=f'temperatures/{tag}, body/hot'), - self.factory(tags=f'temperatures/{tag}, hats/{tag}'), - # multiple hits - self.factory(tags=tag, attributes=tag), - self.factory(tags=tag, attributes=tag, category=tag), - self.factory(tags=tag, attributes=tag, category=tag, name=tag), - self.factory(tags=tag, attributes=tag, category=tag, name=tag, description=tag), - ] - - unexpected_instances = [ - self.factory(tags="notcool"), # shouldn't catch it - self.factory(tags="azules"), - ] - - # searh for it - results = find_related_products_v3(tag) - - # assert result - self.assertTrue(len(results) == len(expected_instances)) -''' - class PurchaseEmailTest(APITestCase): def setUp(self): diff --git a/products/utils.py b/products/utils.py index ddec191..80c1010 100644 --- a/products/utils.py +++ b/products/utils.py @@ -87,31 +87,13 @@ def extract_search_filters(result_set): def get_related_products(description, tags, attributes, category): products_qs = Product.objects.filter( - description=description, - tags__in=tags, - attributes__in=attributes, - category=category - )[:6] + Q(description=description) | + Q(tags__in=tags) | + Q(attributes__in=attributes) | + Q(category=category) + )[:10] return products_qs -''' -def find_related_products_v3(keyword): - """ - Ranked product search - - SearchVectors for the fields - SearchQuery for the value - SearchRank for relevancy scoring and ranking - """ - vector = SearchVector('name') + SearchVector('description') + SearchVector('tags__label') + SearchVector('attributes__label') + SearchVector('category__name') - query = SearchQuery(keyword) - - products_qs = Product.objects.annotate( - rank=SearchRank(vector, query) - ).filter(rank__gt=0.05) # removed order_by because its lost in casting - - return set(products_qs) -''' def ranked_product_search(keyword, shipping_cost=None, discount=None, category=None, tags=None, price_min=None,price_max=None): """ From bb0b8729cbea27156ed0f1a85a5d8a69f8bd99d7 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 12:13:21 +0000 Subject: [PATCH 07/10] changes to related product action, test ok --- products/tests.py | 8 ++++++-- products/utils.py | 44 ++++++++++++++++++++++++++++++++++++-------- products/views.py | 2 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/products/tests.py b/products/tests.py index 8b497ff..f503677 100644 --- a/products/tests.py +++ b/products/tests.py @@ -195,19 +195,23 @@ class ProductViewSetTest(APITestCase): self.assertEquals(len(expected_instance), len(payload)) def test_anon_can_get_related_products(self): + tag = 'cosa' + company = CompanyFactory() # Create instances instance = self.factory() # make our user the creator instance.creator = self.user instance.save() - url = f"{self.endpoint}{instance.id}/related/" + instances = [self.factory(tags=tag, company=company) for i in range(10)] + + url = f"{self.endpoint}{instances[0].id}/related/" response = self.client.get(url) self.assertEquals(response.status_code, 200) payload= response.json() - self.assertTrue(len(payload) <= 6) + self.assertTrue(len(payload) <= 10) # authenticated user def test_auth_user_can_paginate_instances(self): diff --git a/products/utils.py b/products/utils.py index 80c1010..8c938dc 100644 --- a/products/utils.py +++ b/products/utils.py @@ -85,14 +85,42 @@ def extract_search_filters(result_set): return filter_dict -def get_related_products(description, tags, attributes, category): - products_qs = Product.objects.filter( - Q(description=description) | - Q(tags__in=tags) | - Q(attributes__in=attributes) | - Q(category=category) - )[:10] - return products_qs +def get_related_products(product): + """Make different db searches until you get 10 instances to return + """ + total_results = [] + + # search by category + category_qs = Product.objects.filter(category=product.category)[:10] + # add to results + for item in category_qs: + total_results.append(item) + + # check size + if len(total_results) < 10: + # search by tags + tags_qs = Product.objects.filter(tags__in=product.tags.all())[:10] + # add to results + for item in tags_qs: + total_results.append(item) + + # check size + if len(total_results) < 10: + # search by coop + coop_qs = Product.objects.filter(company=product.company)[:10] + # add to results + for item in coop_qs: + total_results.append(item) + + # check size + if len(total_results) < 10: + # search by latest + latest_qs = Product.objects.order_by('-created')[:10] + # add to results + for item in coop_qs: + total_results.append(item) + + return total_results[:10] def ranked_product_search(keyword, shipping_cost=None, discount=None, category=None, tags=None, price_min=None,price_max=None): diff --git a/products/views.py b/products/views.py index 360b935..8103f67 100644 --- a/products/views.py +++ b/products/views.py @@ -59,7 +59,7 @@ class ProductViewSet(viewsets.ModelViewSet): """ # TODO: find the most similar products product = self.get_object() - qs = get_related_products(product.description, product.tags.all(), product.attributes.all(), product.category) + qs = get_related_products(product) serializer = self.serializer_class(qs, many=True) return Response(data=serializer.data) From e31d725ed13059938b43ae0ce626a3efad75f2c5 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 12:24:59 +0000 Subject: [PATCH 08/10] email verification being sent upon user creation --- core/tests.py | 6 ++++++ core/views.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/core/tests.py b/core/tests.py index bc48c2c..00e4536 100644 --- a/core/tests.py +++ b/core/tests.py @@ -56,6 +56,10 @@ class CustomUserViewSetTest(APITestCase): # assert instance is inactive info = json.loads(response.content) self.assertTrue(info['is_active']) + # Assert instance exists on db + self.assertTrue(self.model.objects.get(email=info['email'])) + # assert verification email + self.assertTrue(len(mail.outbox) == 1) def test_anon_user_cannot_modify_existing_instance(self): """Not logged-in user cannot modify existing instance @@ -182,6 +186,8 @@ class CustomUserViewSetTest(APITestCase): # Assert instance exists on db self.assertTrue(self.model.objects.get(email=response.data['email'])) + # assert verification email + self.assertTrue(len(mail.outbox) == 1) def test_admin_user_can_modify_existing_instance(self): """Admin user can modify existing instance diff --git a/core/views.py b/core/views.py index f3c90ef..f392703 100644 --- a/core/views.py +++ b/core/views.py @@ -79,6 +79,8 @@ class CustomUserViewSet(viewsets.ModelViewSet): instance = self.model(**serializer.validated_data) instance.set_password(password) instance.save() + # send verification email + utils.send_verification_email(request, instance) return Response(self.read_serializer_class( instance, many=False, context={'request': request}).data, From faf3cfc3fa9f13aa2fc9b746f678e6bb7973243c Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 12:37:24 +0000 Subject: [PATCH 09/10] new user activation working --- core/tests.py | 43 ++++++++++++++++++++++++++++++++++++++++++- core/views.py | 6 +++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/core/tests.py b/core/tests.py index 00e4536..70b9941 100644 --- a/core/tests.py +++ b/core/tests.py @@ -7,11 +7,13 @@ import csv from django.test import TestCase from django.core import mail +from django.utils.http import urlsafe_base64_encode +from django.utils.encoding import force_bytes from rest_framework.test import APITestCase from rest_framework import status -from core.utils import get_tokens_for_user +from core.utils import get_tokens_for_user, account_activation_token from companies.models import Company @@ -539,3 +541,42 @@ class MyUserViewTest(APITestCase): # check response self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + +class ActivateUserTest(APITestCase): + + def setUp(self): + self.endpoint = 'activate///' + self.factory = factories.CustomUserFactory + self.model = models.CustomUser + # create user + self.email = f"user@mail.com" + self.password = ''.join(random.choices(string.ascii_uppercase, k = 10)) + self.user = self.factory(email=self.email, is_active=False) + self.user.set_password(self.password) + self.user.save() + + def test_correct_activation(self): + # create values + uid = urlsafe_base64_encode(force_bytes(self.user.pk)) + token = account_activation_token.make_token(self.user) + + url = f'/activate/{uid}/{token}/' + + response = self.client.get(url) + + # assertions + self.assertEquals(response.status_code, 200) + self.assertTrue(self.user.email in str(response.content)) + + def test_bad_activation(self): + # create values + uid = urlsafe_base64_encode(force_bytes(self.user.pk))[:-1] + token = account_activation_token.make_token(self.user)[:-1] + + url = f'/activate/{uid}/{token}/' + + response = self.client.get(url) + + # assertions + self.assertEquals(response.status_code, 406) + self.assertTrue('error' in response.json()) diff --git a/core/views.py b/core/views.py index f392703..7120211 100644 --- a/core/views.py +++ b/core/views.py @@ -202,10 +202,10 @@ def activate_user(request, uidb64, token): except (TypeError, ValueError, OverflowError, User.DoesNotExist): user = None - if user is not None and account_activation_token.check_token(user, token): + if user is not None and utils.account_activation_token.check_token(user, token): # activate user user.is_active = True user.save() - return HttpResponse(f"Tu cuenta de usuario {request.user.email} ha sido activada") + return Response(f"Tu cuenta de usuario {user.email} ha sido activada") else: - return HttpResponse(f"Tu token de verificacion no coincide con ningún usuario registrado") + return Response({"error": f"Tu token de verificacion no coincide con ningún usuario registrado"}, status=status.HTTP_406_NOT_ACCEPTABLE) From a033fa3606b5891b5ab2cc9c5f34af0f6f568ab1 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 10 Mar 2021 12:44:23 +0000 Subject: [PATCH 10/10] readme update --- README.md | 15 +++++++++++++++ core/views.py | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26b7c70..b45a011 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,14 @@ Endpoint url: `/api/v1/user/update/` Permissions: only accessible for your own user instance +### create_company_user [POST] + +Edndpoint: `/api/v1/create_company_user/` + +Simultaneously create a company and its related user + +NOT WORKING!!! + ### my_user [GET] Endpoint url: `/api/v1/my_user/` @@ -208,6 +216,12 @@ Ednpoint url: `/api/v1/load_coops/` For each row it creates a Company instance, and a user instance linked to the company, with role `COOP_MANAGER` +### activate_user + +Endpoint: `/activate///` + +This endpoint is reached from the URL sent to the user after their registration + ### User Management Creation: @@ -309,6 +323,7 @@ Location ednpoints: - `/api/v1/provinces/` - `/api/v1/cities/` +Tables filled with data from `datasets/gadm36_ESP.gpkg` with `loadgisdata` command. ## Shop Integrations diff --git a/core/views.py b/core/views.py index 7120211..1bb70e4 100644 --- a/core/views.py +++ b/core/views.py @@ -170,7 +170,6 @@ def my_user(request): return Response({'error': {str(type(e))}}, status=500) - @api_view(['POST',]) @permission_classes([IsAdminUser,]) def load_coop_managers(request):