diff --git a/back_latienda/routers.py b/back_latienda/routers.py index bad6b0c..83385ca 100644 --- a/back_latienda/routers.py +++ b/back_latienda/routers.py @@ -1,7 +1,7 @@ from rest_framework import routers from core.views import CustomUserViewSet -from companies.views import CompanyViewSet, MyCompanyViewSet, AdminCompanyViewSet +from companies.views import CompanyViewSet, AdminCompanyViewSet from products.views import ProductViewSet, MyProductsViewSet, AdminProductsViewSet from history.views import HistorySyncViewSet from stats.views import StatsLogViewSet @@ -13,7 +13,6 @@ router = routers.DefaultRouter() router.register('users', CustomUserViewSet, basename='users') router.register('companies', CompanyViewSet, basename='company') -router.register('my_company', MyCompanyViewSet, basename='my-company') router.register('admin_companies', AdminCompanyViewSet, basename='admin-companies') router.register('products', ProductViewSet, basename='product') router.register('my_products', MyProductsViewSet, basename='my-products') diff --git a/back_latienda/settings/base.py b/back_latienda/settings/base.py index 0ee9a92..5298aca 100644 --- a/back_latienda/settings/base.py +++ b/back_latienda/settings/base.py @@ -171,3 +171,6 @@ MAP_WIDGETS = { ), "GOOGLE_MAP_API_KEY": os.getenv('GOOGLE_MAP_API_KEY') } + +# ACTIVATION_REDIRECT URL +ACTIVATION_REDIRECT = os.getenv('ACTIVATION_REDIRECT') diff --git a/back_latienda/urls.py b/back_latienda/urls.py index 91431ad..c6743e3 100644 --- a/back_latienda/urls.py +++ b/back_latienda/urls.py @@ -39,6 +39,7 @@ urlpatterns = [ path('api/v1/search_products/', product_views.product_search, name='product-search'), path('api/v1/create_company_user/', core_views.create_company_user, name='create-company-user'), path('api/v1/my_user/', core_views.my_user, name='my-user'), + path('api/v1/my_company/', company_views.my_company, name='my-company'), path('api/v1/companies/sample/', company_views.random_company_sample , name='company-sample'), path('api/v1/purchase_email/', product_views.purchase_email, name='purchase-email'), path('api/v1/stats/me/', stat_views.track_user, name='user-tracker'), diff --git a/companies/admin.py b/companies/admin.py index 0575242..2e21305 100644 --- a/companies/admin.py +++ b/companies/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.contrib.gis.db.models import PointField -from django_admin_listfilter_dropdown.filters import DropdownFilter, RelatedDropdownFilter +from django_admin_listfilter_dropdown.filters import DropdownFilter from mapwidgets.widgets import GooglePointFieldWidget from . import models @@ -10,8 +10,8 @@ from . import models class CompanyAdmin(admin.ModelAdmin): list_display = ('short_name', 'city', 'email', 'shop', 'platform', 'sync', 'is_validated', 'is_active', 'link') - list_filter = ('platform', 'sync', 'is_validated', 'is_active', 'city') - search_fields = ('short_name', 'company_name', 'email', 'web_link', ('city', RelatedDropdownFilter)) + list_filter = ('platform', 'sync', 'is_validated', 'is_active', ('city', DropdownFilter)) + search_fields = ('short_name', 'company_name', 'email', 'web_link', 'city') formfield_overrides = { PointField: {"widget": GooglePointFieldWidget} diff --git a/companies/tests.py b/companies/tests.py index 343ea7c..1ad084f 100644 --- a/companies/tests.py +++ b/companies/tests.py @@ -146,7 +146,7 @@ class CompanyViewSetTest(APITestCase): 'logo': None, 'city': None, 'address': 'qwer qewr 5', - 'geo': None, + 'geo': {'longitude': 1.0, 'latitude': 1.0}, 'phone': '1234', 'mobile': '4321', 'other_phone': '41423', @@ -312,12 +312,11 @@ class MyCompanyViewTest(APITestCase): self.user.set_password(self.password) self.user.save() - def tearDown(self): - self.model.objects.all().delete() - def test_auth_user_gets_data(self): # create instance - user_instances = [self.factory(creator=self.user) for i in range(5)] + company = CompanyFactory() + self.user.company = company + self.user.save() # Authenticate token = get_tokens_for_user(self.user) @@ -325,32 +324,10 @@ class MyCompanyViewTest(APITestCase): # Query endpoint response = self.client.get(self.endpoint) - payload = response.json() - # Assert forbidden code self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEquals(len(user_instances), len(payload)) - - def test_auth_user_can_paginate_instances(self): - """authenticated user can paginate instances - """ - - # Authenticate - token = get_tokens_for_user(self.user) - self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") - - # create instances - instances = [self.factory(creator=self.user) for n in range(12)] - - # Request list - url = f"{self.endpoint}?limit=5&offset=10" - response = self.client.get(url) - - # Assert access is allowed - self.assertEqual(response.status_code, status.HTTP_200_OK) - # assert only 2 instances in response payload = response.json() - self.assertEquals(2, len(payload['results'])) + self.assertEquals(payload['company']['id'], company.id) def test_anon_user_cannot_access(self): # send in request diff --git a/companies/views.py b/companies/views.py index 5133d43..62c4767 100644 --- a/companies/views.py +++ b/companies/views.py @@ -155,17 +155,27 @@ class CompanyViewSet(viewsets.ModelViewSet): return Response(message) +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def my_company(request): + if request.user.company: + serializer = CompanySerializer(request.user.company) + return Response({'company': serializer.data}) + else: + return Response(status=status.HTTP_406_NOT_ACCEPTABLE) + +''' class MyCompanyViewSet(viewsets.ModelViewSet): model = Company serializer_class = CompanySerializer permission_classes = [IsAuthenticated] def get_queryset(self): - return self.model.objects.filter(creator=self.request.user) + return self.model.objects.filter(company=self.request.user.company) def perform_create(self, serializer): serializer.save(creator=self.request.user) - +''' class AdminCompanyViewSet(viewsets.ModelViewSet): """ Allows user with role 'SITE_ADMIN' to access all company instances diff --git a/core/models.py b/core/models.py index 3c49a74..89d995f 100644 --- a/core/models.py +++ b/core/models.py @@ -24,6 +24,8 @@ class UserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): extra_fields.setdefault('is_superuser', False) + extra_fields.setdefault('is_staff', False) + extra_fields.setdefault('is_active', False) return self._create_user(email, password, **extra_fields) def create_superuser(self, email, password, **extra_fields): diff --git a/core/serializers.py b/core/serializers.py index e71c71f..6a2039e 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -23,7 +23,7 @@ class CustomUserWriteSerializer(serializers.ModelSerializer): class Meta: model = models.CustomUser - fields = ('email', 'full_name', 'role', 'password', 'provider') + fields = ('email', 'full_name', 'role', 'password', 'provider', 'notify') class CreatorSerializer(serializers.ModelSerializer): diff --git a/core/tests.py b/core/tests.py index 70b9941..2ed7ca1 100644 --- a/core/tests.py +++ b/core/tests.py @@ -9,6 +9,7 @@ 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 django.conf import settings from rest_framework.test import APITestCase from rest_framework import status @@ -41,7 +42,7 @@ class CustomUserViewSetTest(APITestCase): self.user = self.factory(email=self.reg_email, password=self.password, is_active=True) # anon user - def test_anon_user_can_create_active_instance(self): + def test_anon_user_can_create_inactive_instance(self): """Not logged-in user can create new instance of User but it's inactive """ data = { @@ -57,7 +58,7 @@ class CustomUserViewSetTest(APITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) # assert instance is inactive info = json.loads(response.content) - self.assertTrue(info['is_active']) + self.assertFalse(info['is_active']) # Assert instance exists on db self.assertTrue(self.model.objects.get(email=info['email'])) # assert verification email @@ -161,6 +162,27 @@ class CustomUserViewSetTest(APITestCase): # Assert access is forbidden self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + def test_auth_user_can_modify_own_instance(self): + """Regular user can modify own instance + """ + # Create instance + data = { + "email": "new_email@mail.com", + "full_name": "New Full Name", + 'provider': 'PROVIDER', + 'notify': True, + } + + # Authenticate + token = get_tokens_for_user(self.user) + self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") + + # Query endpoint + url = f'{self.endpoint}{self.user.pk}/' + response = self.client.put(url, data=data, format='json') + # Assert forbidden code + self.assertEqual(response.status_code, status.HTTP_200_OK) + # admin user def test_admin_user_can_create_instance(self): """Admin user can create new instance @@ -332,99 +354,6 @@ class ChangeUserPasswordViewTest(APITestCase): self.assertEqual(stored_password_hash, base64.b64encode(new_password_hash).decode()) -class UpdateUserViewTest(APITestCase): - - def setUp(self): - """Tests setup - """ - self.endpoint = '/api/v1/users/' - self.factory = factories.CustomUserFactory - self.model = models.CustomUser - # create regular user - self.reg_email = f"user@mail.com" - self.password = ''.join(random.choices(string.ascii_uppercase, k = 10)) - self.user = self.factory(email=self.reg_email, is_active=True) - self.user.set_password(self.password) - self.user.save() - # create admin user - self.admin_email = f"admin_user@mail.com" - self.admin_user = self.factory(email=self.admin_email, is_staff=True, is_active=True) - self.admin_user.set_password(self.password) - self.admin_user.save() - - def test_auth_user_can_modify_own_instance(self): - """Regular user can modify own instance - """ - # Create instance - data = { - "email": "new_email@mail.com", - "full_name": "New Full Name", - 'provider': 'PROVIDER', - 'notify': True, - } - - # Authenticate - token = get_tokens_for_user(self.user) - self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") - - # Query endpoint - url = f'{self.endpoint}{self.user.pk}/' - response = self.client.put(url, data=data, format='json') - # Assert forbidden code - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_auth_user_cannot_modify_random_instance(self): - """Regular user cannot modify randnom instance - """ - # Create instance - instance = self.factory() - - # Authenticate - token = get_tokens_for_user(self.user) - self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") - - # Query endpoint - url = f'{self.endpoint}{instance.pk}/' - response = self.client.put(url, data={}, format='json') - # Assert forbidden code - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_anon_user_cannot_modify_random_instance(self): - """anon user cannot modify instance - """ - # Create instance - instance = self.factory() - - # Query endpoint - url = f'{self.endpoint}{instance.pk}/' - response = self.client.put(url, data={}, format='json') - # Assert forbidden code - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_admin_user_can_modify_random_instance(self): - """Regular user cannot modify randnom instance - """ - # Create instance - instance = self.factory() - - # Authenticate - token = get_tokens_for_user(self.admin_user) - self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") - - data = { - "email": "new_email@mail.com", - "full_name": "New Full Name", - 'provider': 'PROVIDER', - 'notify': True, - } - - # Query endpoint - url = f'{self.endpoint}{instance.pk}/' - response = self.client.put(url, data=data, format='json') - # Assert forbidden code - self.assertEqual(response.status_code, status.HTTP_200_OK) - - class LoadCoopManagerTestCase(APITestCase): def setUp(self): @@ -564,6 +493,21 @@ class ActivateUserTest(APITestCase): response = self.client.get(url) + # assertions + self.assertEquals(response.status_code, 302) + self.assertEquals(response.url, settings.ACTIVATION_REDIRECT) + + def test_correct_activation_no_redirect(self): + # set ACTIVATION_REDIRECT to '' + settings.ACTIVATION_REDIRECT = '' + # 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)) @@ -580,3 +524,67 @@ class ActivateUserTest(APITestCase): # assertions self.assertEquals(response.status_code, 406) self.assertTrue('error' in response.json()) + + +class CreateCompanyUserTest(APITestCase): + + def setUp(self): + self.endpoint = '/api/v1/create_company_user/' + self.factory = factories.CustomUserFactory + self.model = models.CustomUser + # create user + self.email = "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_succesful_creation(self): + data = { + 'user': { + 'email': 'test@email.com', + 'full_name': 'TEST NAME', + 'password': 'VENTILADORES1234499.89', + }, + 'company': { + 'cif': 'qwerewq', + 'company_name': 'qwerewq', + 'short_name': 'qwerewq', + 'web_link': 'http://qwerewq.com', + 'shop': True, + 'shop_link': 'http://qwerewq.com', + 'platform': 'PRESTASHOP', + 'email': 'test@email.com', + 'logo': None, + 'city': None, + 'address': 'qwer qewr 5', + 'geo': {'longitude': 1.0, 'latitude': 1.0}, + 'phone': '1234', + 'mobile': '4321', + 'other_phone': '41423', + 'description': 'dfgfdgdfg', + 'shop_rss_feed': 'http://qwerewq.com', + 'sale_terms': 'tewrnmfew f ewfrfew ewewew f', + 'shipping_cost': '12.25', + 'sync': False + } + } + + response = self.client.post(self.endpoint, data=data, format='json') + + self.assertEquals(response.status_code, 201) + self.assertEquals(len(mail.outbox), 1) + # user exists and it's inactice + self.assertTrue(self.model.objects.get(email='test@email.com')) + self.assertFalse(self.model.objects.get(email='test@email.com').is_active) + self.assertTrue(Company.objects.get(cif='qwerewq')) + # assert verification email + self.assertTrue(len(mail.outbox) == 1) + + def test_creation_error(self): + + response = self.client.post(self.endpoint, data={}, format='json') + + self.assertEquals(response.status_code, 406) + self.assertEquals(len(mail.outbox), 0) + diff --git a/core/views.py b/core/views.py index 1bb70e4..bc9afd0 100644 --- a/core/views.py +++ b/core/views.py @@ -11,6 +11,8 @@ from django.utils.http import urlsafe_base64_decode from django.utils.encoding import force_text from django.db import IntegrityError from django.contrib.gis.geos import Point +from django.shortcuts import redirect +from django.conf import settings from rest_framework import status from rest_framework import viewsets @@ -20,6 +22,7 @@ from rest_framework.generics import UpdateAPIView from rest_framework.decorators import api_view, permission_classes from companies.models import Company +from companies.serializers import CompanySerializer from geo.models import City from . import models @@ -43,9 +46,9 @@ logging.basicConfig( class CustomUserViewSet(viewsets.ModelViewSet): - model = models.CustomUser + model = User model_name = 'custom_user' - queryset = models.CustomUser.objects.all() + queryset = User.objects.all() permission_classes = [CustomUserPermissions,] read_serializer_class = core_serializers.CustomUserReadSerializer write_serializer_class = core_serializers.CustomUserWriteSerializer @@ -76,7 +79,7 @@ class CustomUserViewSet(viewsets.ModelViewSet): if serializer.is_valid(): # save model instance data password = serializer.validated_data.pop('password') - instance = self.model(**serializer.validated_data) + instance = self.model.objects.create_user(**serializer.validated_data) instance.set_password(password) instance.save() # send verification email @@ -100,63 +103,47 @@ class ChangeUserPasswordView(UpdateAPIView): serializer_class = core_serializers.ChangePasswordSerializer -class UpdateUserView(UpdateAPIView): - - model = models.CustomUser - queryset = model.objects.all() - permission_classes = (YourOwnUserPermissions,) - serializer_class = core_serializers.UpdateUserSerializer - - @api_view(['POST',]) @permission_classes([CustomUserPermissions,]) def create_company_user(request): """ - Create non-validated company and manager user associated + Create non-validated company and associated managing user """ - user_data = { - 'full_name': request.data['user']['full_name'], - 'email': request.data['user']['email'], - 'password': request.data['user']['password'] - } - company_data = { - 'cif': request.data['company']['cif'], - 'company_name': request.data['company']['company_name'], - 'short_name': request.data['company']['short_name'], - 'web_link': request.data['company']['web_link'], - 'shop': request.data['company']['shop'], - 'city': request.data['company']['city'], - 'geo': request.data['company']['geo'], - 'address': request.data['company']['address'] - } - try: - user = models.CustomUser.objects.create(email=user_data['email'], full_name=user_data['full_name']) - except IntegrityError as e: - return Response({"errors": {"details": str(e)}}, status=status.HTTP_409_CONFLICT) + if 'user' not in request.data: + return Response({"error": "Missing parameter: user"}, status=406) + if 'company' not in request.data: + return Response({"error": "Missing parameter: company"}, status=406) - try: - city = company_data.pop('city') - #city = City.objects.get(name=city) + # create company + company_data = request.data['company'] + company_serializer = CompanySerializer( + data=company_data, + ) + if company_serializer.is_valid(): + # save model instance data + new_company = Company.objects.create(**company_serializer.validated_data) + else: + return Response({"error": company_serializer.errors}, status=406) - geo = company_data.pop('geo') - geo = Point(geo['latitude'],geo['longitude']) + # create user + user_data = request.data['user'] + user_data['role'] = 'COOP_MANAGER' + user_data['company'] = new_company.id + user_serializer = core_serializers.CustomUserWriteSerializer( + data=user_data, + ) + if user_serializer.is_valid(): + # save model instance data + password = user_serializer.validated_data.pop('password') + new_user = User.objects.create_user(**user_serializer.validated_data) + new_user.set_password(password) + new_user.save() + # send verification email + utils.send_verification_email(request, new_user) + else: + return Response({"error": user_serializer.errors}, status=406) - company = Company.objects.create(**company_data, city=city, geo=geo) - except Exception as e: - user.delete() - return Response({"errors": {"details": str(e)}}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - user.set_password(user_data['password']) - user.company = company - user.role = 'COOP_MANAGER' - user.save() - - company.creator = user - company.save() - - serializer = core_serializers.CustomUserSerializer(user) - - return Response(data=serializer.data,status=status.HTTP_201_CREATED) + return Response(status=status.HTTP_201_CREATED) @api_view(['GET',]) @@ -205,6 +192,8 @@ def activate_user(request, uidb64, token): # activate user user.is_active = True user.save() + if settings.ACTIVATION_REDIRECT: + return redirect(settings.ACTIVATION_REDIRECT) return Response(f"Tu cuenta de usuario {user.email} ha sido activada") else: return Response({"error": f"Tu token de verificacion no coincide con ningĂșn usuario registrado"}, status=status.HTTP_406_NOT_ACCEPTABLE) diff --git a/example.env b/example.env index 66a53de..87e699b 100644 --- a/example.env +++ b/example.env @@ -14,4 +14,6 @@ AWS_SECRET_ACCESS_KEY_SES = '' WC_KEY = '' WC_SECRET = '' # GOOGLE MAPS -GOOGLE_MAP_API_KEY = '' \ No newline at end of file +GOOGLE_MAP_API_KEY = '' +# USER ACTIVATION REDIRECTION +ACTIVATION_REDIRECT = '' \ No newline at end of file diff --git a/products/tests.py b/products/tests.py index f503677..d9db3ea 100644 --- a/products/tests.py +++ b/products/tests.py @@ -952,9 +952,13 @@ class MyProductsViewTest(APITestCase): def test_auth_user_gets_data(self): # create instance + company = CompanyFactory() + self.user.company = company + self.user.save() + user_instances = [ - self.factory(creator=self.user), - self.factory(creator=self.user), + self.factory(company=company), + self.factory(company=company), ] # Authenticate @@ -976,7 +980,11 @@ class MyProductsViewTest(APITestCase): self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") # create instances - instances = [self.factory(creator=self.user) for n in range(12)] + company = CompanyFactory() + self.user.company = company + self.user.save() + + instances = [self.factory(company=company) for n in range(12)] # Request list url = f"{self.endpoint}?limit=5&offset=10" @@ -996,6 +1004,18 @@ class MyProductsViewTest(APITestCase): # check response self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + def test_auth_user_without_company(self): + # Authenticate + token = get_tokens_for_user(self.user) + self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {token['access']}") + + # Query endpoint + response = self.client.get(self.endpoint) + payload = response.json() + # Assert forbidden code + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEquals([], payload) + class AdminProductViewSetTest(APITestCase): diff --git a/products/utils.py b/products/utils.py index 7f3805f..c0f4c7e 100644 --- a/products/utils.py +++ b/products/utils.py @@ -6,6 +6,7 @@ from django.db.models import Q from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector, TrigramSimilarity from django.db.models import Max, Min from django.conf import settings +from django.utils import timezone import requests @@ -203,7 +204,7 @@ def product_loader(csv_reader, user, company=None): return None # create historysync instance - history = HistorySync.objects.create(company=company, sync_date=datetime.datetime.now()) + history = HistorySync.objects.create(company=company, sync_date=timezone.now()) for row in csv_reader: # trim strings for key in row: diff --git a/products/views.py b/products/views.py index 8103f67..4e132ae 100644 --- a/products/views.py +++ b/products/views.py @@ -72,10 +72,7 @@ class MyProductsViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] def get_queryset(self): - return self.model.objects.filter(creator=self.request.user) - - def perform_create(self, serializer): - serializer.save(creator=self.request.user) + return self.model.objects.filter(company=self.request.user.company).order_by('-created') class AdminProductsViewSet(viewsets.ModelViewSet): diff --git a/stats/tests.py b/stats/tests.py index 3bfe3ce..ea3510c 100644 --- a/stats/tests.py +++ b/stats/tests.py @@ -116,7 +116,7 @@ class TrackUserViewTest(APITestCase): 'model': 'company', 'id': company.id, }, - 'geo': (12.2, -0.545) + 'geo': {'latitude': 12.2, 'longitude': -0.545} } # Query endpoint diff --git a/stats/views.py b/stats/views.py index a41ba3d..a11193e 100644 --- a/stats/views.py +++ b/stats/views.py @@ -52,13 +52,18 @@ def track_user(request): try: data = json.loads(request.body) + if data.get('geo'): + coordinates = (data['geo'].get('latitude'), data['geo'].get('longitude')) + else: + coordinates = None + # gather instance data instance_data = { 'action_object': data.get('action_object'), 'user': None if request.user.is_anonymous else request.user, 'anonymous': request.user.is_anonymous, 'ip_address': data.get('ip'), - 'geo': Point(data.get('geo')['latitude'], data.get('geo')['longitude']), + 'geo': Point(coordinates), } if data['action_object'].get('model') == 'product': @@ -76,4 +81,4 @@ def track_user(request): return Response(status=status.HTTP_201_CREATED) except Exception as e: logging.error(f"Stats could not be created: {str(e)}") - return Response(f"Process could not be registered: {str(type(e))}", status=status.HTTP_406_NOT_ACCEPTABLE) + return Response(f"Process could not be registered [{str(type(e))}]: {str(e)}", status=status.HTTP_406_NOT_ACCEPTABLE)