diff --git a/.gitignore b/.gitignore index 374dc3f..5baf72a 100644 --- a/.gitignore +++ b/.gitignore @@ -145,4 +145,3 @@ dmypy.json .pytype/ # End of https://www.toptal.com/developers/gitignore/api/django -static \ No newline at end of file diff --git a/README.md b/README.md index 5113b40..ceb564b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,11 @@ python manage.py createsuperuser python manage.py runserver ``` +#### Access on browser +``` +http://localhost:8000/api-docs +``` + ### Setup with Docker (Alternative) ``` docker-compose up -d diff --git a/orders/models.py b/orders/models.py index a3c185d..c11a71b 100644 --- a/orders/models.py +++ b/orders/models.py @@ -1,7 +1,15 @@ +from typing import Dict + +from django.conf import settings +from django.core.mail import send_mail from django.db import models from django.db.models.signals import post_delete from django.dispatch import receiver +from django.template.loader import render_to_string from django.utils import timezone +from django.utils.html import strip_tags +from pydantic import EmailStr +from validate_email import validate_email from authentication.models import User from products.models import Product @@ -43,6 +51,23 @@ def get_cart_items(self): total = sum([item.quantity for item in items]) return total + @staticmethod + def send_email_notification( + customer_email: EmailStr, template: str, subject: str, context_data: Dict + ): + if validate_email(email_address=customer_email, check_smtp=False): + html_message = render_to_string(template, context_data) + plain_message = strip_tags(html_message) + + send_mail( + subject, + plain_message, + settings.EMAIL_HOST_USER, + [customer_email, settings.EMAIL_HOST_USER], + fail_silently=False, + html_message=html_message, + ) + class OrderItem(models.Model): product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True) diff --git a/orders/tests/test_orders_api.py b/orders/tests/test_orders_api.py index 529e1fd..8b82b9f 100644 --- a/orders/tests/test_orders_api.py +++ b/orders/tests/test_orders_api.py @@ -1,4 +1,5 @@ from datetime import datetime +from unittest.mock import patch import pytest @@ -149,6 +150,8 @@ def test_update_cart(logged_in_client): @pytest.mark.django_db +@patch("django.core.mail.send_mail", lambda **kwargs: kwargs) +@patch("validate_email.validate_email", lambda z, x: x) def test_process_order(logged_in_client, logged_in_user): """ Test process order diff --git a/orders/views.py b/orders/views.py index d86ee41..d397064 100644 --- a/orders/views.py +++ b/orders/views.py @@ -1,4 +1,5 @@ -import datetime +from datetime import datetime +from uuid import uuid4 from rest_condition import Or from rest_framework import mixins, status, viewsets @@ -105,7 +106,8 @@ def update_cart(self, request): ) def process_order(self, request): request_data = self.get_serializer(data=request.data) - transaction_id = datetime.datetime.now().timestamp() + transaction_id = str(uuid4()) + transaction_timestamp = datetime.now().strftime("%b %d, %Y %H:%M") customer = request.user self.check_object_permissions(self.request, customer) @@ -116,18 +118,37 @@ def process_order(self, request): order.complete = True order.save() - if order.shipping: - shipping = ShippingAddress.objects.create( - customer=customer, - order=order, - address=request_data.validated_data["address"], - city=request_data.validated_data["city"], - state=request_data.validated_data["state"], - zipcode=request_data.validated_data["zipcode"], - ) - shipping.save() - - return Response( - ShippingAddressSerializer(shipping).data, status=status.HTTP_201_CREATED - ) - return Response("Order not found", status=status.HTTP_400_BAD_REQUEST) + if not order.shipping: + return Response("Order not found", status=status.HTTP_400_BAD_REQUEST) + + shipping = ShippingAddress.objects.create( + customer=customer, + order=order, + address=request_data.validated_data["address"], + city=request_data.validated_data["city"], + state=request_data.validated_data["state"], + zipcode=request_data.validated_data["zipcode"], + ) + shipping.save() + + Order.send_email_notification( + customer.email, + "invoice_email_template.html", + f"Order Being Processed: {order.transaction_id}", + { + "user_first_name": customer.first_name, + "user_last_name": customer.last_name, + "shipping_address": shipping.address, + "shipping_city": shipping.city, + "shipping_state": shipping.state, + "shipping_zipcode": shipping.zipcode, + "invoice_code": transaction_id, + "order_items": order.order_items, + "order": order, + "date_ordered": transaction_timestamp, + }, + ) + + return Response( + ShippingAddressSerializer(shipping).data, status=status.HTTP_201_CREATED + ) diff --git a/requirements.txt b/requirements.txt index 1e4cf70..707d0fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ attrs==19.3.0 black==20.8b1 certifi==2020.6.20 chardet==3.0.4 +charset-normalizer==2.0.9 click==7.1.2 colorama==0.4.4 coreapi==2.3.3 @@ -14,12 +15,14 @@ Django==3.0.8 django-cors-headers==3.7.0 djangorestframework==3.11.0 djangorestframework-simplejwt==4.4.0 +dnspython==2.1.0 drf-yasg==1.17.1 factory-boy==3.2.0 Faker==8.8.0 +filelock==3.4.0 flake8==3.8.3 gunicorn==20.0.4 -idna==2.10 +idna==3.0 importlib-metadata==1.7.0 inflection==0.5.1 iniconfig==1.0.1 @@ -37,6 +40,7 @@ Pillow==7.2.0 pluggy==0.13.1 psycopg2-binary==2.8.6 py==1.9.0 +py3-validate-email==1.0.5 pycodestyle==2.6.0 pydantic==1.6.1 pyflakes==2.2.0 @@ -48,7 +52,7 @@ pytest-django==3.9.0 python-dateutil==2.8.1 pytz==2020.1 regex==2020.7.14 -requests==2.24.0 +requests==2.26.0 rest-condition==1.0.3 ruamel.yaml==0.16.12 ruamel.yaml.clib==0.2.2 diff --git a/static/images/zadala-logo.png b/static/images/zadala-logo.png new file mode 100644 index 0000000..e66a466 Binary files /dev/null and b/static/images/zadala-logo.png differ diff --git a/templates/invoice_email_template.html b/templates/invoice_email_template.html new file mode 100644 index 0000000..faee9f5 --- /dev/null +++ b/templates/invoice_email_template.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ + + + + +
+ Logo image + +
+ +
+ +
+
+
+ + +
+
+
+ + + + + + + +
+ +
+

Hello {{ user_first_name }} {{ user_last_name }}!

+
+ +
+ +
+
+
+ + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+

Shipping Address

+
+ +
+ + + + + + + +
+ +
+

{{ shipping_address }}, {{ shipping_city }}

+

{{ shipping_state }}, Philippines {{ shipping_zipcode }}

+
+ +
+ +
+
+
+ + +
+
+
+ + + + + + + +
+ +
+

Invoice Code

+
+ +
+ + + + + + + +
+ +
+

#{{ invoice_code }}

+
+ +
+ +
+
+
+ + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+

Purchased Items

+
+ +
+ + + + + + + +
+ + + + + + + +
+   +
+ +
+ +
+
+
+ + +
+
+
+ + +{% for order_item in order_items %} +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+

{{ order_item.product.name }}

+

Quantity : {{ order_item.quantity }} x ₱{{ order_item.product.price }}

+
+ +
+ +
+
+
+ + +
+
+
+ + + + + + + +
+ +
+

₱{{ order_item.total }}

+
+ +
+ +
+
+
+ + +
+
+
+ + +{% endfor %} + +
+
+
+ + + +
+
+
+ + + + + + + +
+ + + + + + + +
+   +
+ +
+ +
+
+
+ + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+

Grand Total

+
+ +
+ +
+
+
+ + +
+
+
+ + + + + + + +
+ +
+

₱{{ order.get_cart_total }}

+
+ +
+ +
+
+
+ + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+

We received your order on {{ date_ordered }} and you’ll be paying for this via Cash on delivery. We wish you enjoy shopping with us and hope to see you again real soon!

+

 

+

zadala.herokuapp.com

+
+ +
+ +
+
+
+ + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + + + + + +
+ +
+
You received this email because you signed up for Zadala Inc.
+
 
+
+ +
+ +
+
+
+ + +
+
+
+ + + +
+ + + + + diff --git a/zadalaAPI/settings.py b/zadalaAPI/settings.py index b551436..d7d9ae2 100644 --- a/zadalaAPI/settings.py +++ b/zadalaAPI/settings.py @@ -12,7 +12,14 @@ import datetime import os -from zadala_config import ZADALA_SECRET_KEY, database +from zadala_config import ( + EMAIL_HOST_PASSWORD, + EMAIL_HOST_PORT, + EMAIL_HOST_PROVIDER, + EMAIL_HOST_USER, + ZADALA_SECRET_KEY, + database, +) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -68,11 +75,12 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" ROOT_URLCONF = "zadalaAPI.urls" +TEMPLATE_ROOT = os.path.join(BASE_DIR, "templates") TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": [TEMPLATE_ROOT], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -181,3 +189,10 @@ STATIC_URL = "/static/" MEDIA_ROOT = os.path.join(BASE_DIR, "static/images") STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") + +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = os.environ.get("EMAIL_HOST_PROVIDER", EMAIL_HOST_PROVIDER) +EMAIL_PORT = os.environ.get("EMAIL_HOST_PORT", EMAIL_HOST_PORT) +EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", EMAIL_HOST_USER) +EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", EMAIL_HOST_PASSWORD) +EMAIL_USE_TLS = True diff --git a/zadala_config.py b/zadala_config.py index 6aca6b7..7b30fc6 100644 --- a/zadala_config.py +++ b/zadala_config.py @@ -8,3 +8,9 @@ } ZADALA_SECRET_KEY = "my-secret" + + +EMAIL_HOST_PROVIDER = "smtp.gmail.com" +EMAIL_HOST_PORT = 587 +EMAIL_HOST_USER = "gmail email" +EMAIL_HOST_PASSWORD = "gmail password"