diff --git a/README.md b/README.md index a19af33..73a15e1 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This README aims to document functionality of backend as well as required steps - [Location Data](#location-data) - [Endpoints](#endpoints) - [Data Load](#data-load) +- [GeoIP Setup](#geoip-setup) - [Development Utils](#development-utils) ## First Steps @@ -177,6 +178,22 @@ CSV headers: `id,nombre-producto,descripcion,imagen,url,precio,gastos-envio,cond Only admin users have access to endoint + +## GeoIP Setup + +Module: `geoip2` + +- Download the `GeoLite2 City` and `GeoLite2 Country` binary datasets from maxmind.com +- Unzip files into `datasets/` folder +- Set `settings.GEOIP_PATH` to datasets folder + +Optional: + +- install `libmaxminddb` C library for improved performance: + + `sudo apt install libmaxminddb0 libmaxminddb-dev mmdb-bin` + + ## Development Utils ### Fake product load diff --git a/back_latienda/settings/development.py b/back_latienda/settings/development.py index 32d91b6..fda1182 100644 --- a/back_latienda/settings/development.py +++ b/back_latienda/settings/development.py @@ -21,8 +21,9 @@ DATABASES = { }, } -MEDIA_ROOT = BASE_DIR + '/media/' +MEDIA_ROOT = BASE_DIR + '/../media/' MEDIA_URL = '/media/' +GEOIP_PATH = BASE_DIR + '/../datasets/' # JWT SETTINGS SIMPLE_JWT = { diff --git a/companies/views.py b/companies/views.py index a7cd54d..845e8a8 100644 --- a/companies/views.py +++ b/companies/views.py @@ -1,8 +1,9 @@ import logging -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.core.mail import EmailMessage from django.template.loader import render_to_string +from django.contrib.gis.geoip2 import GeoIP2 # Create your views here. from rest_framework import viewsets @@ -10,6 +11,9 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated from rest_framework.decorators import api_view, permission_classes, action +from ipware import get_client_ip + +from stats.models import StatsLog from companies.models import Company from companies.serializers import CompanySerializer @@ -29,31 +33,92 @@ class CompanyViewSet(viewsets.ModelViewSet): """ Send email to company.creator """ + try: + queryset = self.get_custom_queryset(request) + instance = queryset.filter(pk=kwargs['pk']).first() + if instance: + # IP stuff + client_ip, is_routable = get_client_ip(request) + g = GeoIP2() - queryset = self.get_custom_queryset(request) - instance = queryset.filter(pk=kwargs['pk']).first() - if instance: - data = json.loads(request.body) - # send email to manager - message = render_to_string('company_contact.html', { - 'user': request.user, - 'data': data, - }) - email = EmailMessage(subject, message, to=[instance.creator.email]) - email.send() - logging.info(f"Email sent to {instance.creator.email} as manager of {instance.name}") + # deserialize payload + data = json.loads(request.body) + if request.user.is_authenticated: + # send email to manager + company_message = render_to_string('company_contact.html', { + 'company': instance, + 'email': request.user.email, + 'full_name': request.user.full_name, + 'quantity': data['quantity'], + 'phone_number': data.get('phone_number'), + 'comments': data['comments'], + 'product_info': data['product_info'], + }) + user_message = render_to_string('confirm_company_contact.html', { + 'company': instance, + 'username': request.user.full_name, + 'data': data, + }) + # send email to company + subject = "Contacto de usuario" + email = EmailMessage(subject, company_message, to=[instance.creator.email]) + email.send() + logging.info(f"Email sent to {instance.creator.email} as manager of {instance.name}") + # send confirmation email to user + subject = 'Confirmación de contacto' + email = EmailMessage(subject, message, to=[request.user.email]) + email.send() + logging.info(f"Contact confirmation email sent to {request.user.email}") + stats_data = { + 'action_object': instance, + 'user': None, + 'anonymous': True, + 'ip_address': client_ip, + 'geo': g.geos(client_ip), + 'contact': True, + 'shop': instance.shop, + } + else: + # for unauthenticated users + company_message = render_to_string('company_contact.html', { + 'company': instance, + 'email': data['email'], + 'full_name': data['full_name'], + 'quantity': data['quantity'], + 'phone_number': data.get('phone_number'), + 'comments': data['comments'], + 'product_info': data['product_info'], + }) + user_message = render_to_string('confirm_company_contact.html', { + 'company': instance, + 'username': data['full_name'], + }) + # send email to company + email = EmailMessage(subject, company_message, to=[instance.creator.email]) + email.send() + logging.info(f"Email sent to {instance.creator.email} as manager of {instance.name}") + # send confirmation email to user + email = EmailMessage(subject, user_message, to=[data['email']]) + email.send() + logging.info(f"Contact confirmation email sent to anonymous user {data['email']}") + # statslog data to register interaction + stats_data = { + 'action_object': instance, + 'user': request.user, + 'anonymous': False, + 'ip_address': client_ip, + 'geo': g.geos(client_ip), + 'contact': True, + 'shop': instance.shop, + } + # create statslog instance to register interaction + StatsLog.objects.create(**stats_data) - # send confirmation email to user - message = render_to_string('confirm_company_contact.html', { - 'user': request.user, - }) - email = EmailMessage(subject, message, to=[request.user.email]) - email.send() - logging.info(f"Confirmation email sent to {request.user.email}") - - return Response(data=data) - else: - return Response({"errors":{"details": f"No instance of company with id {kwargs['pk']}",}}) + return Response(data=data) + else: + return Response({"errors":{"details": f"No instance of company with id {kwargs['pk']}",}}) + except Exception as e: + return Response({"errors":{"details": str(e),}}, status=500) @api_view(['GET',]) diff --git a/core/utils.py b/core/utils.py index 29cdab4..a2da2ad 100644 --- a/core/utils.py +++ b/core/utils.py @@ -17,8 +17,10 @@ class AccountActivationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return f"{user.pk}{timestamp}{user.full_name}" + account_activation_token = AccountActivationTokenGenerator() + def get_tokens_for_user(user): refresh = RefreshToken.for_user(user) @@ -39,12 +41,14 @@ def get_auth_token(client, email, password): # logging.error(f"User {email} was refused a token: {response.content}") return None + def create_active_user(email, password): user = User.objects.create_user(email=email, password=password) user.is_active = True user.save() return user + def create_admin_user(email, password): user = User.objects.create_user(email=email, password=password) user.is_staff = True @@ -52,6 +56,7 @@ def create_admin_user(email, password): user.save() return user + def send_verification_email(request, user): try: current_site = get_current_site(request) diff --git a/datasets/GeoLite2-City.mmdb b/datasets/GeoLite2-City.mmdb new file mode 100644 index 0000000..c080c5f Binary files /dev/null and b/datasets/GeoLite2-City.mmdb differ diff --git a/datasets/GeoLite2-Country.mmdb b/datasets/GeoLite2-Country.mmdb new file mode 100644 index 0000000..b52677f Binary files /dev/null and b/datasets/GeoLite2-Country.mmdb differ diff --git a/products/views.py b/products/views.py index 3246e55..947d1c8 100644 --- a/products/views.py +++ b/products/views.py @@ -13,7 +13,7 @@ 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 +from rest_framework.decorators import api_view, permission_classes, action import requests @@ -42,6 +42,11 @@ class ProductViewSet(viewsets.ModelViewSet): def perform_create(self, serializer): serializer.save(creator=self.request.user) + @action(detail=True, methods=['GET',]) + def related(request): + # find the most similar products + return Response(data=[]) + @api_view(['GET',]) @permission_classes([IsAuthenticated,]) diff --git a/requirements.txt b/requirements.txt index f62b08f..05d3342 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,6 @@ django-cors-headers==3.5.0 django-taggit-serializer==0.1.7 django-tagulous==1.1.0 Pillow==8.1.0 -drf-extra-fields==3.0.4 \ No newline at end of file +drf-extra-fields==3.0.4 +django-ipware==3.0.2 +geoip2==4.1.0 \ No newline at end of file diff --git a/stats/models.py b/stats/models.py index bc7d827..1de6942 100644 --- a/stats/models.py +++ b/stats/models.py @@ -1,5 +1,6 @@ from django.contrib.gis.db import models from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.auth import get_user_model User = get_user_model() @@ -7,7 +8,14 @@ User = get_user_model() class StatsLog(models.Model): - model = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING, null=True) + action_object_content_type = models.ForeignKey( + ContentType, blank=True, + null=True, + related_name='statslogs', + on_delete=models.CASCADE + ) + 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) ip_address = models.GenericIPAddressField('IP usuario', null=True, blank=True) diff --git a/templates/company_contact.html b/templates/company_contact.html index e69de29..9d4d4a5 100644 --- a/templates/company_contact.html +++ b/templates/company_contact.html @@ -0,0 +1,10 @@ +Hola {{company.creator.full_name}}. +Estamos contactando contigo como usuario gerente de la compañina {{company.company_name}}. + +Datos de usuario: + +- {{user.full_name}} +- {{user.email}} + +Contenido del mensaje: +{{data}} diff --git a/templates/confirm_contact_company.html b/templates/confirm_contact_company.html index e69de29..70dcb0e 100644 --- a/templates/confirm_contact_company.html +++ b/templates/confirm_contact_company.html @@ -0,0 +1,2 @@ +Hola {{user.full_name}}. +Hemos enviado un email a {{company.company_name}} sobre tu petición.