Added document generation, configured email sending
rodzic
55c20c7193
commit
d2b3cf486c
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -24,6 +24,3 @@
|
|||
<hr>
|
||||
<a href={% url 'cart' %} alt="Koszyk" > Koszyk </a>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -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
|
|
@ -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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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"
|
|
@ -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):
|
||||
...
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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"))
|
||||
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}")
|
||||
|
|
Ładowanie…
Reference in New Issue