diff --git a/artel/Dockerfile b/artel/Dockerfile
index a8ed9a6..39fb199 100644
--- a/artel/Dockerfile
+++ b/artel/Dockerfile
@@ -22,6 +22,7 @@ RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-r
libjpeg62-turbo-dev \
zlib1g-dev \
libwebp-dev \
+ wkhtmltopdf \
&& rm -rf /var/lib/apt/lists/*
# Install the application server.
diff --git a/artel/Dockerfile.local b/artel/Dockerfile.local
index deccf97..3809fc2 100644
--- a/artel/Dockerfile.local
+++ b/artel/Dockerfile.local
@@ -19,6 +19,7 @@ RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-r
libjpeg62-turbo-dev \
zlib1g-dev \
libwebp-dev \
+ wkhtmltopdf \
&& rm -rf /var/lib/apt/lists/*
# Install the project requirements.
diff --git a/artel/artel/settings/base.py b/artel/artel/settings/base.py
index 977a9bb..3576ecb 100644
--- a/artel/artel/settings/base.py
+++ b/artel/artel/settings/base.py
@@ -170,10 +170,18 @@ WAGTAILSEARCH_BACKENDS = {
# Base URL to use when referring to full URLs within the Wagtail admin backend -
# e.g. in notification emails. Don't include '/admin' or a trailing slash
-WAGTAILADMIN_BASE_URL = "http://example.com"
+WAGTAILADMIN_BASE_URL = "https://artel.tepewu.pl"
# STORE SETTINGS
PRODUCTS_PER_PAGE = 6
# CART settings
CART_SESSION_ID = 'cart'
+
+# EMAIL settings
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.gmail.com')
+EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
+EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", '')
+EMAIL_PORT = os.environ.get('EMAIL_PORT', 587)
+DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl')
diff --git a/artel/artel/templates/menu/custom_main_menu.html b/artel/artel/templates/menu/custom_main_menu.html
index 56e8d8f..5098dcb 100644
--- a/artel/artel/templates/menu/custom_main_menu.html
+++ b/artel/artel/templates/menu/custom_main_menu.html
@@ -24,6 +24,3 @@
Koszyk
-
-
-
\ No newline at end of file
diff --git a/artel/requirements.txt b/artel/requirements.txt
index 147e103..89d53de 100644
--- a/artel/requirements.txt
+++ b/artel/requirements.txt
@@ -6,3 +6,5 @@ dj-database-url<=2.0.0
djangorestframework==3.14.0
phonenumbers==8.13.13
django-phonenumber-field==7.1.0
+factory-boy==3.2.1
+pdfkit==1.0.0
\ No newline at end of file
diff --git a/artel/store/migrations/0005_documenttemplate_orderdocument.py b/artel/store/migrations/0005_documenttemplate_orderdocument.py
new file mode 100644
index 0000000..deb0a75
--- /dev/null
+++ b/artel/store/migrations/0005_documenttemplate_orderdocument.py
@@ -0,0 +1,41 @@
+# Generated by Django 4.1.9 on 2023-06-04 09:38
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("store", "0004_customerdata_order_orderproduct"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="DocumentTemplate",
+ fields=[
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("name", models.CharField(max_length=255)),
+ ("file", models.FileField(upload_to="documents")),
+ (
+ "doc_type",
+ models.CharField(choices=[("agreement", "Agreement"), ("receipt", "Receipt")], max_length=255),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="OrderDocument",
+ fields=[
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ (
+ "order",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, related_name="documents", to="store.order"
+ ),
+ ),
+ (
+ "template",
+ models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="store.documenttemplate"),
+ ),
+ ],
+ ),
+ ]
diff --git a/artel/store/models.py b/artel/store/models.py
index 487f792..785da07 100644
--- a/artel/store/models.py
+++ b/artel/store/models.py
@@ -1,3 +1,4 @@
+import pdfkit
from django.db import models
from django.core.paginator import (
Paginator,
@@ -5,6 +6,10 @@ from django.core.paginator import (
)
from django.conf import settings
from django.core.validators import MinValueValidator
+from django.template import (
+ Template,
+ Context
+)
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
@@ -17,6 +22,10 @@ from wagtail import fields as wagtail_fields
from taggit.managers import TaggableManager
from phonenumber_field.modelfields import PhoneNumberField
+from store.utils import (
+ send_mail
+)
+
class ProductAuthor(models.Model):
name = models.CharField(max_length=255)
@@ -194,8 +203,76 @@ class OrderProduct(models.Model):
objects = OrderProductManager()
+class OrderManager(models.Manager):
+ def create_from_cart(self, cart, customer_data):
+ order = self.create(customer=customer_data)
+ OrderProduct.objects.create_from_cart(cart, order)
+ # create proper documents
+ agreement_template = DocumentTemplate.objects.filter(
+ doc_type=DocumentTypeChoices.AGREEMENT
+ ).order_by("-created_at").first()
+ receipt_template = DocumentTemplate.objects.filter(
+ doc_type=DocumentTypeChoices.RECEIPT
+ ).order_by("-created_at").first()
+ agreement = OrderDocument.objects.create(
+ order=order,
+ template=agreement_template
+ )
+ receipt = OrderDocument.objects.create(
+ order=order,
+ template=receipt_template
+ )
+ send_mail(agreement)
+ send_mail(receipt)
+ return order
+
+
class Order(models.Model):
customer = models.ForeignKey(CustomerData, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
sent = models.BooleanField(default=False)
+
+ objects = OrderManager()
+
+ @property
+ def order_number(self) -> str:
+ return f"{self.id:06}/{self.created_at.year}"
+
+
+class DocumentTypeChoices(models.TextChoices):
+ AGREEMENT = "agreement"
+ RECEIPT = "receipt"
+
+
+class DocumentTemplate(models.Model):
+ name = models.CharField(max_length=255)
+ file = models.FileField(upload_to="documents")
+ doc_type = models.CharField(max_length=255, choices=DocumentTypeChoices.choices)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+
+class OrderDocument(models.Model):
+ order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="documents")
+ template = models.ForeignKey(DocumentTemplate, on_delete=models.CASCADE)
+ sent = models.BooleanField(default=False)
+
+ def get_document_context(self):
+ _context = {
+ "order": self.order,
+ "customer": self.order.customer,
+ "products": self.order.products.all(),
+ }
+ return Context(_context)
+
+ @property
+ def document(self):
+ with open(self.template.file.path, "rb") as f:
+ content = f.read()
+ template = Template(content)
+ context = self.get_document_context()
+ content = template.render(context)
+ return pdfkit.from_string(content, False)
diff --git a/artel/store/tests.py b/artel/store/tests.py
deleted file mode 100644
index ee19e98..0000000
--- a/artel/store/tests.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from django.test import TestCase
-from django.urls import reverse
-from store.models import ProductAuthor, ProductCategory, ProductTemplate, Product
-
-
-# TODO - this is fine for now, but we'll want to use factoryboy for this:
-# https://factoryboy.readthedocs.io/en/stable/
-# TODO - test have to rewritten - I'll do it tommorow
-class CartTestCase(TestCase):
- def setUp(self):
- self.productid = 1
- self.author = ProductAuthor.objects.create(name='Test Author')
- self.category = ProductCategory.objects.create(name='Test Category')
- self.template = ProductTemplate.objects.create(category=self.category,
- author=self.author,
- title='Test title',
- code='Test code',
- description='Test description'
- )
- self.product = Product.objects.create(template=self.template,
- price=10.99)
- self.cart_url = reverse('view_cart')
-
- def test_add_to_cart(self):
- response = self.client.post(reverse('add_to_cart',
- args=[self.productid]))
- self.assertEqual(response.status_code, 302)
-
- def test_remove_from_cart(self):
- response = self.client.post(reverse('remove_from_cart',
- args=[self.productid]))
- self.assertEqual(response.status_code, 302)
-
- def test_view_cart(self):
- response = self.client.get(self.cart_url)
- self.assertEqual(response.status_code, 200)
diff --git a/artel/store/tests/__init__.py b/artel/store/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/artel/store/tests/factories.py b/artel/store/tests/factories.py
new file mode 100644
index 0000000..0f6cb2c
--- /dev/null
+++ b/artel/store/tests/factories.py
@@ -0,0 +1,41 @@
+from factory import (
+ Faker,
+ SubFactory
+)
+from factory.django import (
+ FileField,
+ DjangoModelFactory
+)
+
+
+class CustomerDataFactory(DjangoModelFactory):
+ class Meta:
+ model = 'store.CustomerData'
+
+ name = Faker('name')
+ surname = Faker('name')
+ email = Faker('email')
+ phone = Faker('phone_number')
+ street = Faker('street_address')
+ city = Faker('city')
+ zip_code = Faker('postcode')
+ country = Faker('country')
+
+
+class OrderFactory(DjangoModelFactory):
+ class Meta:
+ model = 'store.Order'
+
+ customer = SubFactory(CustomerDataFactory)
+ created_at = Faker('date_time')
+ updated_at = Faker('date_time')
+ sent = Faker('boolean')
+
+
+class DocumentTemplateFactory(DjangoModelFactory):
+ class Meta:
+ model = 'store.DocumentTemplate'
+
+ name = Faker('name')
+ file = FileField(filename="doc.odt")
+ doc_type = "AGREEMENT"
diff --git a/artel/store/tests/test_models.py b/artel/store/tests/test_models.py
new file mode 100644
index 0000000..162facf
--- /dev/null
+++ b/artel/store/tests/test_models.py
@@ -0,0 +1,41 @@
+from django.test import TestCase
+from django.urls import reverse
+
+from store.tests import factories
+from store import models as store_models
+
+
+# TODO - this is fine for now, but we'll want to use factoryboy for this:
+# https://factoryboy.readthedocs.io/en/stable/
+# TODO - test have to rewritten - I'll do it tommorow
+
+class OrderDocumentTestCase(TestCase):
+ def setUp(self):
+ super().setUp()
+ self.order = factories.OrderFactory()
+ self.document_template = factories.DocumentTemplateFactory(file__data="test")
+
+ def test_generate_document_success(self):
+ order_doc = store_models.OrderDocument.objects.create(
+ order=self.order,
+ template=self.document_template
+ )
+ document = order_doc.document
+ self.assertIsInstance(document, bytes)
+
+ def test_get_document_context_success(self):
+ order_doc = store_models.OrderDocument.objects.create(
+ order=self.order,
+ template=self.document_template
+ )
+ context = order_doc.get_document_context()
+ self.assertIsInstance(context, store_models.Context)
+ self.assertEqual(context["order"].id, self.order.id)
+ self.assertEqual(context["customer"].id, self.order.customer.id)
+ self.assertEqual(context["products"].count(), 0)
+
+ def test_send_order_document_mail_success(self):
+ ...
+
+ def test_send_order_document_mail_failure_wrong_email(self):
+ ...
diff --git a/artel/store/urls.py b/artel/store/urls.py
index 3dbf6d6..cfb2bb7 100644
--- a/artel/store/urls.py
+++ b/artel/store/urls.py
@@ -12,4 +12,5 @@ urlpatterns = [
path('cart/', store_views.CartView.as_view(), name='cart'),
path("order/", store_views.OrderView.as_view(), name="order"),
path("order/confirm/", store_views.OrderConfirmView.as_view(), name="order-confirm"),
+ path("send-mail/", store_views.SendMailView.as_view(), name="send-mail"),
] + router.urls
diff --git a/artel/store/utils.py b/artel/store/utils.py
new file mode 100644
index 0000000..9a9c7bd
--- /dev/null
+++ b/artel/store/utils.py
@@ -0,0 +1,18 @@
+from django.core.mail import EmailMessage
+from django.conf import settings
+
+
+# TODO - add celery task for sending not sent earlier
+def send_mail(order_doc):
+ order = order_doc.order
+ message = EmailMessage(
+ subject=f"Zamówienie {order.order_number}",
+ body="Dokumenty dla Twojego zamówienia",
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ to=[order.customer.email]
+ )
+ message.attach(f"{order.order_number}.pdf", order_doc.document, "application/pdf")
+ sent = bool(message.send())
+ order_doc.sent = sent
+ order_doc.save()
+ return sent
diff --git a/artel/store/views.py b/artel/store/views.py
index fb61607..483daa7 100644
--- a/artel/store/views.py
+++ b/artel/store/views.py
@@ -20,7 +20,9 @@ from store.forms import CustomerDataForm
from store.models import (
CustomerData,
Order,
- OrderProduct
+ OrderProduct,
+ OrderDocument,
+ DocumentTemplate
)
@@ -133,12 +135,23 @@ class OrderConfirmView(View):
def post(self, request):
customer_data = CustomerData.objects.get(id=self.request.session["customer_data_id"])
cart = SessionCart(self.request)
- order = Order.objects.create(
- customer=customer_data,
+ order = Order.objects.create_from_cart(
+ cart, customer_data
)
- OrderProduct.objects.create_from_cart(order, cart)
- cart.clear()
self.request.session.pop("customer_data_id")
- # TODO - to be tested
# TODO - messages
- return HttpResponseRedirect(reverse("cart"))
\ No newline at end of file
+ return HttpResponseRedirect(reverse("cart"))
+
+
+class SendMailView(View):
+ def get(self, request):
+ from django.core import mail
+ from django.http import HttpResponse
+ from django.conf import settings
+ r = mail.send_mail(
+ subject=f"Test",
+ message="Dokumenty dla Twojego zamówienia",
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ recipient_list=["mateusz.tyton99@gmail.com"]
+ )
+ return HttpResponse(f"Mail sent: {r}")