documents are being sent properly, store models has changed a bit, added a lot of tests
rodzic
000b911f89
commit
8bf933a469
|
@ -184,4 +184,5 @@ 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)
|
||||
EMAIL_USE_TLS = True
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl')
|
||||
|
|
|
@ -9,7 +9,13 @@ SECRET_KEY = "django-insecure-s7hlfa-#n7-v0#&-0ko3(efe+@^d@ie1_1-633e&jb1rh$)j1p
|
|||
# SECURITY WARNING: define the correct hosts in production!
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = "smtp-server"
|
||||
EMAIL_HOST_USER = None
|
||||
EMAIL_HOST_PASSWORD = None
|
||||
EMAIL_PORT = 1025
|
||||
EMAIL_USE_TLS = False
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl')
|
||||
|
||||
|
||||
try:
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
version: "3.8"
|
||||
services:
|
||||
smtp-server:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- "1025:1025"
|
||||
- "8025:8025"
|
||||
comfy:
|
||||
build:
|
||||
dockerfile: Dockerfile.local
|
||||
|
|
|
@ -7,4 +7,5 @@ djangorestframework==3.14.0
|
|||
phonenumbers==8.13.13
|
||||
django-phonenumber-field==7.1.0
|
||||
factory-boy==3.2.1
|
||||
pdfkit==1.0.0
|
||||
pdfkit==1.0.0
|
||||
num2words==0.5.12
|
|
@ -35,10 +35,14 @@ class ProductAdmin(ModelAdmin):
|
|||
list_display = ("title", "price")
|
||||
|
||||
|
||||
class PaymentMethodAdmin(ModelAdmin):
|
||||
model = models.PaymentMethod
|
||||
list_display = ("name", "active")
|
||||
|
||||
|
||||
class DocumentTemplateAdmin(ModelAdmin):
|
||||
model = models.DocumentTemplate
|
||||
list_display = ("name", "doc_type")
|
||||
list_display = ("name", )
|
||||
|
||||
|
||||
class StoreAdminGroup(ModelAdminGroup):
|
||||
|
@ -51,7 +55,8 @@ class StoreAdminGroup(ModelAdminGroup):
|
|||
ProductCategoryParamAdmin,
|
||||
ProductTemplateAdmin,
|
||||
ProductAdmin,
|
||||
DocumentTemplateAdmin
|
||||
DocumentTemplateAdmin,
|
||||
PaymentMethodAdmin
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -2,23 +2,23 @@ from abc import (
|
|||
ABC,
|
||||
abstractmethod
|
||||
)
|
||||
from typing import List
|
||||
from typing import (
|
||||
List,
|
||||
Any
|
||||
)
|
||||
from dataclasses import dataclass
|
||||
from django.http.request import HttpRequest
|
||||
from django.conf import settings
|
||||
|
||||
from store.models import Product
|
||||
|
||||
|
||||
@dataclass
|
||||
class CartItem:
|
||||
product: Product
|
||||
quantity: int
|
||||
from store.models import (
|
||||
Product,
|
||||
ProductAuthor
|
||||
)
|
||||
|
||||
|
||||
class BaseCart(ABC):
|
||||
|
||||
def validate_item_id(self, item_id):
|
||||
def validate_and_get_product(self, item_id):
|
||||
return Product.objects.get(id=item_id)
|
||||
|
||||
@abstractmethod
|
||||
|
@ -43,57 +43,76 @@ class SessionCart(BaseCart):
|
|||
def __init__(self, request: HttpRequest) -> None:
|
||||
super().__init__()
|
||||
self.session = request.session
|
||||
if not self.session.get(settings.CART_SESSION_ID):
|
||||
self.session[settings.CART_SESSION_ID] = {}
|
||||
self._cart = self.session.get(settings.CART_SESSION_ID, None)
|
||||
if not self._cart:
|
||||
self._cart = {}
|
||||
self.session[settings.CART_SESSION_ID] = self._cart
|
||||
|
||||
def save_cart(self):
|
||||
self.session[settings.CART_SESSION_ID] = self._cart
|
||||
self.session.modified = True
|
||||
|
||||
def add_item(self, item_id: int, quantity: int) -> None:
|
||||
# TODO - add logging
|
||||
self.validate_item_id(item_id)
|
||||
product = self.validate_and_get_product(item_id)
|
||||
author = product.author
|
||||
quantity = int(quantity)
|
||||
item_id = int(item_id)
|
||||
if not self.session[settings.CART_SESSION_ID].get(str(item_id)):
|
||||
self.session[settings.CART_SESSION_ID][item_id] = quantity
|
||||
self.session.modified = True
|
||||
if not self._cart.get(str(author.id)):
|
||||
self._cart[str(author.id)] = {str(item_id): quantity}
|
||||
self.save_cart()
|
||||
elif not self._cart[str(author.id)].get(str(item_id)):
|
||||
self._cart[str(author.id)].update({str(item_id): quantity})
|
||||
self.save_cart()
|
||||
else:
|
||||
self.update_item_quantity(item_id, quantity)
|
||||
new_quantity = self._cart[str(author.id)][str(item_id)] + quantity
|
||||
self.update_item_quantity(item_id, new_quantity)
|
||||
|
||||
def remove_item(self, item_id: int) -> None:
|
||||
self.validate_item_id(item_id)
|
||||
product = self.validate_and_get_product(item_id)
|
||||
author = product.author
|
||||
try:
|
||||
self.session[settings.CART_SESSION_ID].pop(item_id)
|
||||
self.session.modified = True
|
||||
self._cart[str(author.id)].pop(str(item_id))
|
||||
self.save_cart()
|
||||
except KeyError:
|
||||
# TODO - add logging
|
||||
...
|
||||
|
||||
def update_item_quantity(self, item_id: int, new_quantity: int) -> None:
|
||||
self.validate_item_id(item_id)
|
||||
product = self.validate_and_get_product(item_id)
|
||||
author = product.author
|
||||
if new_quantity < 1:
|
||||
self.remove_item(item_id)
|
||||
return
|
||||
try:
|
||||
self.session[settings.CART_SESSION_ID][str(item_id)] = new_quantity
|
||||
self.session.modified = True
|
||||
except KeyError:
|
||||
# TODO - add logging
|
||||
if not self._cart.get(str(author.id)):
|
||||
self.add_item(item_id, new_quantity)
|
||||
return
|
||||
self._cart[str(author.id)][str(product.id)] = new_quantity
|
||||
self.save_cart()
|
||||
|
||||
def get_items(self) -> List[CartItem]:
|
||||
_items = []
|
||||
for item_id, quantity in self.session[settings.CART_SESSION_ID].items():
|
||||
_items.append(CartItem(quantity=quantity, product=Product.objects.get(id=item_id)))
|
||||
return _items
|
||||
def get_items(self) -> List[dict[str, dict|str]]:
|
||||
items: List[dict[str, dict|str]] = []
|
||||
for author_id, cart_items in self._cart.items():
|
||||
author = ProductAuthor.objects.get(id=int(author_id))
|
||||
products = []
|
||||
for item_id, quantity in cart_items.items():
|
||||
product=Product.objects.get(id=int(item_id))
|
||||
products.append({"product": product, "quantity": quantity})
|
||||
items.append({"author": author, "products": products})
|
||||
return items
|
||||
|
||||
@property
|
||||
def total_price(self):
|
||||
total = 0
|
||||
for item in self.get_items():
|
||||
total += item.product.price * int(item.quantity)
|
||||
for _, cart_items in self._cart.items():
|
||||
for item_id, quantity in cart_items.items():
|
||||
product = Product.objects.get(id=int(item_id))
|
||||
total += product.price * quantity
|
||||
return total
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return not bool(self.session[settings.CART_SESSION_ID].items())
|
||||
return not bool(self._cart.items())
|
||||
|
||||
def clear(self) -> None:
|
||||
self.session[settings.CART_SESSION_ID] = {}
|
||||
self.session.modified = True
|
||||
self._cart = {}
|
||||
self.save_cart()
|
||||
|
|
|
@ -2,18 +2,9 @@ from django import forms
|
|||
from phonenumber_field.formfields import PhoneNumberField
|
||||
# from phonenumber_field.widgets import PhoneNumberPrefixWidget
|
||||
|
||||
from store.models import (
|
||||
CustomerData,
|
||||
)
|
||||
|
||||
|
||||
class CustomerDataForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CustomerData
|
||||
fields = [
|
||||
"name", "surname", "email", "phone",
|
||||
"street", "city", "zip_code"
|
||||
]
|
||||
class CustomerDataForm(forms.Form):
|
||||
|
||||
name = forms.CharField(
|
||||
max_length=255, label="Imię", widget=forms.TextInput(attrs={"class": "form-control"})
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
# Generated by Django 4.1.9 on 2023-06-01 19:00
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import phonenumber_field.modelfields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("store", "0003_product_info_product_name_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CustomerData",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("surname", models.CharField(max_length=255)),
|
||||
("email", models.EmailField(max_length=254)),
|
||||
("phone", phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None)),
|
||||
("street", models.CharField(max_length=255)),
|
||||
("city", models.CharField(max_length=255)),
|
||||
("zip_code", models.CharField(max_length=120)),
|
||||
("country", models.CharField(max_length=120)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Order",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("sent", models.BooleanField(default=False)),
|
||||
("customer", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="store.customerdata")),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OrderProduct",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("quantity", models.IntegerField(validators=[django.core.validators.MinValueValidator(1)])),
|
||||
(
|
||||
"order",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, related_name="products", to="store.order"
|
||||
),
|
||||
),
|
||||
("product", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="store.product")),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,129 @@
|
|||
# Generated by Django 4.1.9 on 2023-06-16 15:33
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import phonenumber_field.modelfields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("store", "0003_product_info_product_name_and_more"),
|
||||
]
|
||||
|
||||
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, unique=True
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Order",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("sent", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PaymentMethod",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("description", models.TextField(blank=True)),
|
||||
("active", models.BooleanField(default=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="city",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="country",
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="display_name",
|
||||
field=models.CharField(blank=True, max_length=255, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="email",
|
||||
field=models.EmailField(blank=True, max_length=254),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="phone",
|
||||
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="street",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="surname",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="productauthor",
|
||||
name="zip_code",
|
||||
field=models.CharField(blank=True, max_length=120),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="productauthor",
|
||||
name="name",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OrderProduct",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("quantity", models.IntegerField(validators=[django.core.validators.MinValueValidator(1)])),
|
||||
(
|
||||
"order",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, related_name="products", to="store.order"
|
||||
),
|
||||
),
|
||||
("product", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="store.product")),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="OrderDocument",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("sent", models.BooleanField(default=False)),
|
||||
(
|
||||
"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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="order",
|
||||
name="payment_method",
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="store.paymentmethod"),
|
||||
),
|
||||
]
|
|
@ -1,41 +0,0 @@
|
|||
# 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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.1.9 on 2023-06-18 11:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("store", "0004_documenttemplate_order_paymentmethod_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="order",
|
||||
name="order_number",
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 4.1.9 on 2023-06-07 23:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("store", "0005_documenttemplate_orderdocument"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="documenttemplate",
|
||||
name="created_at",
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="orderdocument",
|
||||
name="sent",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,8 @@
|
|||
import pdfkit
|
||||
import datetime
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
from django.db import models
|
||||
from django.core.paginator import (
|
||||
Paginator,
|
||||
|
@ -21,18 +25,42 @@ from wagtail.models import Page
|
|||
from wagtail import fields as wagtail_fields
|
||||
from taggit.managers import TaggableManager
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from num2words import num2words
|
||||
|
||||
from store.utils import (
|
||||
send_mail
|
||||
notify_user_about_order,
|
||||
notify_manufacturer_about_order
|
||||
)
|
||||
|
||||
|
||||
class ProductAuthor(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
# TODO - add author contact data
|
||||
class PersonalData(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
name = models.CharField(max_length=255, blank=True)
|
||||
surname = models.CharField(max_length=255, blank=True)
|
||||
email = models.EmailField(blank=True)
|
||||
phone = PhoneNumberField(blank=True)
|
||||
street = models.CharField(max_length=255, blank=True)
|
||||
city = models.CharField(max_length=255, blank=True)
|
||||
zip_code = models.CharField(max_length=120, blank=True)
|
||||
country = models.CharField(max_length=120, blank=True)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.name} {self.surname}"
|
||||
|
||||
@property
|
||||
def full_address(self):
|
||||
return f"{self.street}, {self.zip_code} {self.city}, {self.country}"
|
||||
|
||||
|
||||
class ProductAuthor(PersonalData):
|
||||
display_name = models.CharField(max_length=255, unique=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return self.display_name
|
||||
|
||||
|
||||
class ProductCategory(ClusterableModel):
|
||||
|
@ -111,7 +139,6 @@ class Product(ClusterableModel):
|
|||
@property
|
||||
def main_image(self):
|
||||
images = self.template.images.all()
|
||||
print(images)
|
||||
if images:
|
||||
return images.first().image
|
||||
|
||||
|
@ -119,6 +146,10 @@ class Product(ClusterableModel):
|
|||
def tags(self):
|
||||
return self.template.tags.all()
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
return self.template.author
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self.info or self.template.description
|
||||
|
@ -166,33 +197,22 @@ class ProductListPage(Page):
|
|||
FieldPanel("tags")
|
||||
]
|
||||
|
||||
class CustomerData(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
surname = models.CharField(max_length=255)
|
||||
email = models.EmailField()
|
||||
phone = PhoneNumberField()
|
||||
street = models.CharField(max_length=255)
|
||||
city = models.CharField(max_length=255)
|
||||
zip_code = models.CharField(max_length=120)
|
||||
country = models.CharField(max_length=120)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.name} {self.surname}"
|
||||
|
||||
@property
|
||||
def full_address(self):
|
||||
return f"{self.street}, {self.zip_code} {self.city}, {self.country}"
|
||||
|
||||
|
||||
class OrderProductManager(models.Manager):
|
||||
def create_from_cart(self, cart, order):
|
||||
for item in cart.get_items():
|
||||
self.create(
|
||||
product=item.product,
|
||||
def create_from_cart(self, items: dict[str, Product|int], order: models.Model):
|
||||
pks = []
|
||||
for item in items:
|
||||
if item["quantity"] < 1:
|
||||
# TODO - logging
|
||||
continue
|
||||
|
||||
pk = self.create(
|
||||
product=item["product"],
|
||||
order=order,
|
||||
quantity=item.quantity
|
||||
)
|
||||
quantity=item["quantity"]
|
||||
).pk
|
||||
pks.append(pk)
|
||||
return self.filter(pk__in=pks)
|
||||
|
||||
|
||||
class OrderProduct(models.Model):
|
||||
|
@ -204,41 +224,107 @@ class OrderProduct(models.Model):
|
|||
|
||||
|
||||
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
|
||||
# NOTE - this is temporary
|
||||
# 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
|
||||
|
||||
def _get_order_number(self, author: ProductAuthor):
|
||||
number_of_prev_orders = OrderProduct.objects.filter(
|
||||
product__template__author=author
|
||||
).values("order").distinct().count()
|
||||
number_of_prev_orders += 1
|
||||
year = datetime.datetime.now().year
|
||||
return f"{author.id}/{number_of_prev_orders:06}/{year}"
|
||||
|
||||
def create_from_cart(
|
||||
self, cart_items: list[dict[str, str|dict]],
|
||||
payment_method: models.Model| None,
|
||||
customer_data: dict[str, Any]
|
||||
) -> models.QuerySet:
|
||||
# split cart
|
||||
orders_pks = []
|
||||
|
||||
payment_method = payment_method or PaymentMethod.objects.first()
|
||||
agreement_template = DocumentTemplate.objects.get(doc_type=DocumentTypeChoices.AGREEMENT)
|
||||
receipt_template = DocumentTemplate.objects.get(doc_type=DocumentTypeChoices.RECEIPT)
|
||||
|
||||
for item in cart_items:
|
||||
author = item["author"]
|
||||
author_products = item["products"]
|
||||
|
||||
order = self.create(
|
||||
payment_method=payment_method,
|
||||
order_number=self._get_order_number(author)
|
||||
)
|
||||
OrderProduct.objects.create_from_cart(author_products, order)
|
||||
orders_pks.append(order.pk)
|
||||
agreement = OrderDocument.objects.create(
|
||||
order=order,
|
||||
template=agreement_template
|
||||
)
|
||||
receipt = OrderDocument.objects.create(
|
||||
order=order,
|
||||
template=receipt_template
|
||||
)
|
||||
extra_document_kwargs = {
|
||||
"customer_data": customer_data
|
||||
}
|
||||
default_kwargs ={
|
||||
"docs": [
|
||||
agreement.generate_document(extra_document_kwargs),
|
||||
receipt.generate_document(extra_document_kwargs)
|
||||
],
|
||||
"order_number": order.order_number
|
||||
}
|
||||
user_kwargs = {
|
||||
"customer_email": customer_data["email"],
|
||||
}
|
||||
user_kwargs.update(default_kwargs)
|
||||
user_notified = notify_user_about_order(**user_kwargs)
|
||||
manufacturer_kwargs = {
|
||||
"manufacturer_email": author.email,
|
||||
}
|
||||
manufacturer_kwargs.update(default_kwargs)
|
||||
manufacturer_notified = notify_manufacturer_about_order(**manufacturer_kwargs)
|
||||
sent = user_notified and manufacturer_notified
|
||||
agreement.sent = sent
|
||||
receipt.sent = sent
|
||||
agreement.save()
|
||||
receipt.save()
|
||||
return Order.objects.filter(pk__in=orders_pks)
|
||||
|
||||
|
||||
class PaymentMethod(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
description = models.TextField(blank=True)
|
||||
active = models.BooleanField(default=True)
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
customer = models.ForeignKey(CustomerData, on_delete=models.CASCADE)
|
||||
payment_method = models.ForeignKey(PaymentMethod, on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
sent = models.BooleanField(default=False)
|
||||
|
||||
order_number = models.CharField(max_length=255, null=True)
|
||||
|
||||
objects = OrderManager()
|
||||
|
||||
@property
|
||||
def order_number(self) -> str:
|
||||
return f"{self.id:06}/{self.created_at.year}"
|
||||
def manufacturer(self) -> str:
|
||||
return self.products.first().product.author
|
||||
|
||||
@property
|
||||
def total_price(self) -> Decimal:
|
||||
return sum(
|
||||
[order_product.product.price * order_product.quantity
|
||||
for order_product in self.products.all()]
|
||||
)
|
||||
|
||||
@property
|
||||
def total_price_words(self) -> str:
|
||||
return num2words(self.total_price, lang="pl", to="currency", currency="PLN")
|
||||
|
||||
@property
|
||||
def payment_date(self) -> datetime.date:
|
||||
return self.created_at.date() + datetime.timedelta(days=7)
|
||||
|
||||
|
||||
class DocumentTypeChoices(models.TextChoices):
|
||||
|
@ -249,7 +335,8 @@ class DocumentTypeChoices(models.TextChoices):
|
|||
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)
|
||||
# there may be only one document of each type
|
||||
doc_type = models.CharField(max_length=255, choices=DocumentTypeChoices.choices, unique=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -264,16 +351,19 @@ class OrderDocument(models.Model):
|
|||
def get_document_context(self):
|
||||
_context = {
|
||||
"order": self.order,
|
||||
"customer": self.order.customer,
|
||||
"products": self.order.products.all(),
|
||||
"author": self.order.manufacturer,
|
||||
"order_products": self.order.products.all(),
|
||||
"payment_data": self.order.payment_method,
|
||||
}
|
||||
return Context(_context)
|
||||
|
||||
@property
|
||||
def document(self):
|
||||
with open(self.template.file.path, "rb") as f:
|
||||
def generate_document(self, extra_context: dict = None):
|
||||
extra_context = extra_context or {}
|
||||
context = self.get_document_context()
|
||||
context.update(extra_context)
|
||||
|
||||
with open(self.template.file.path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
template = Template(content)
|
||||
context = self.get_document_context()
|
||||
content = template.render(context)
|
||||
return pdfkit.from_string(content, False)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from store.models import Product
|
||||
from store.models import (
|
||||
Product,
|
||||
ProductAuthor
|
||||
)
|
||||
|
||||
|
||||
class TagSerializer(serializers.Serializer):
|
||||
|
@ -21,6 +24,17 @@ class CartProductSerializer(serializers.Serializer):
|
|||
quantity = serializers.IntegerField()
|
||||
|
||||
|
||||
class ProductAuthorSerializer(serializers.Serializer):
|
||||
class Meta:
|
||||
model = ProductAuthor
|
||||
fields = ["display_name"]
|
||||
|
||||
|
||||
class CartSerializer(serializers.Serializer):
|
||||
author = ProductAuthorSerializer()
|
||||
products = CartProductSerializer(many=True)
|
||||
|
||||
|
||||
class CartProductAddSerializer(serializers.Serializer):
|
||||
|
||||
product_id = serializers.IntegerField()
|
||||
|
|
|
@ -133,7 +133,9 @@ $(document).on('click', '.add-to-cart-button', function(event) {
|
|||
data: formData, // Use the serialized form data
|
||||
headers: { 'X-CSRFToken': csrfToken },
|
||||
dataType: 'json',
|
||||
success: location.reload(),
|
||||
success: function(data) {
|
||||
setTimeout(location.reload(), 500)
|
||||
},
|
||||
processData: false, // Prevent jQuery from processing the data
|
||||
contentType: false, // Let the browser set the content type
|
||||
});
|
||||
|
|
|
@ -9,15 +9,20 @@
|
|||
<h3 class="fw-normal mb-0 text-black">Koszyk</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% for item in cart.get_items %}
|
||||
{% include 'store/partials/cart_item.html' %}
|
||||
{% for group in cart.get_items %}
|
||||
{% if group.products %}
|
||||
<h4>Wykonawca: {{group.author.display_name}}</h4>
|
||||
{% for item in group.products %}
|
||||
{% include 'store/partials/cart_item.html' %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="card ">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h5 class="fw-normal mb-0 text-black">Do zapłaty: {{cart.total_price}}</h5>
|
||||
<h5 class="fw-normal mb-0 text-black">W sumie do zapłaty: {{cart.total_price}}</h5>
|
||||
</div>
|
||||
<div class="col-sm-6 text-end">
|
||||
<a href="{% url 'order' %}" class="btn btn-success btn-block btn-lg">Dalej</a>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<p class="mb-0">Imię i Nazwisko</p>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<p class="text-muted mb-0">{{customer_data.full_name}}</p>
|
||||
<p class="text-muted mb-0">{{customer_data.name}} {{customer_data.surname}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
@ -41,7 +41,10 @@
|
|||
<p class="mb-0">Adres</p>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<p class="text-muted mb-0">{{customer_data.full_address}}</p>
|
||||
<p class="text-muted mb-0">
|
||||
{{customer_data.city}}, {{customer_data.zip_code}}<br/>
|
||||
{{customer_data.street}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -54,8 +57,13 @@
|
|||
<h3 class="fw-normal mb-0 text-black">Zamówione przedmioty</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% for item in cart.get_items %}
|
||||
{% include 'store/partials/summary_cart_item.html' %}
|
||||
{% for group in cart.get_items %}
|
||||
{% if group.products %}
|
||||
<h4>Wykonawca: {{group.author.display_name}}</h4>
|
||||
{% for item in group.products %}
|
||||
{% include 'store/partials/summary_cart_item.html' %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="card ">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load static %}
|
||||
|
||||
<div class="card rounded-3 mb-4">
|
||||
<div class="card rounded-3 mb-1">
|
||||
<div class="card-body p-4">
|
||||
<div class="row d-flex justify-content-between align-items-center">
|
||||
<div class="col-md-2 col-lg-2 col-xl-2">
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
from factory import (
|
||||
Faker,
|
||||
SubFactory
|
||||
SubFactory,
|
||||
Factory
|
||||
)
|
||||
from factory.django import (
|
||||
FileField,
|
||||
DjangoModelFactory
|
||||
DjangoModelFactory,
|
||||
)
|
||||
|
||||
|
||||
class CustomerDataFactory(DjangoModelFactory):
|
||||
class ProductAuthorFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.CustomerData'
|
||||
model = 'store.ProductAuthor'
|
||||
|
||||
name = Faker('name')
|
||||
surname = Faker('name')
|
||||
|
@ -20,13 +21,61 @@ class CustomerDataFactory(DjangoModelFactory):
|
|||
city = Faker('city')
|
||||
zip_code = Faker('postcode')
|
||||
country = Faker('country')
|
||||
display_name = Faker('name')
|
||||
|
||||
|
||||
class ProductCategoryFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.ProductCategory'
|
||||
|
||||
name = Faker('name')
|
||||
|
||||
|
||||
class ProductCategoryParamFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.ProductCategoryParam'
|
||||
|
||||
key = Faker('name')
|
||||
category = SubFactory(ProductCategoryFactory)
|
||||
param_type = 'str'
|
||||
|
||||
|
||||
class ProductTemplateFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.ProductTemplate'
|
||||
|
||||
title = Faker('name')
|
||||
description = Faker('text')
|
||||
code = Faker('name')
|
||||
author = SubFactory(ProductAuthorFactory)
|
||||
category = SubFactory(ProductCategoryFactory)
|
||||
|
||||
|
||||
class ProductFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.Product'
|
||||
|
||||
name = Faker('name')
|
||||
info = Faker('text')
|
||||
price = Faker('pydecimal', left_digits=5, right_digits=2, positive=True)
|
||||
available = Faker('boolean')
|
||||
template = SubFactory(ProductTemplateFactory)
|
||||
|
||||
|
||||
class PaymentMethodFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.PaymentMethod'
|
||||
|
||||
name = Faker('name')
|
||||
description = Faker('text')
|
||||
active = Faker('boolean')
|
||||
|
||||
|
||||
class OrderFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = 'store.Order'
|
||||
|
||||
customer = SubFactory(CustomerDataFactory)
|
||||
payment_method = SubFactory(PaymentMethodFactory)
|
||||
created_at = Faker('date_time')
|
||||
updated_at = Faker('date_time')
|
||||
sent = Faker('boolean')
|
||||
|
@ -38,4 +87,4 @@ class DocumentTemplateFactory(DjangoModelFactory):
|
|||
|
||||
name = Faker('name')
|
||||
file = FileField(filename="doc.odt")
|
||||
doc_type = "AGREEMENT"
|
||||
doc_type = "agreement"
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from store.tests import factories
|
||||
|
||||
|
||||
|
||||
class SessionCartTestCase(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.product = factories.ProductFactory(price=100)
|
||||
self.second_product = factories.ProductFactory(price=200)
|
||||
|
||||
def test_add_item_simple_success(self):
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
1
|
||||
)
|
||||
|
||||
def test_add_item_complex_success(self):
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
1
|
||||
)
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
2
|
||||
)
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.second_product.id, "quantity": 5},
|
||||
)
|
||||
final_dict = {
|
||||
str(self.product.author.id): {
|
||||
str(self.product.id): 2,
|
||||
}
|
||||
}
|
||||
final_dict.update({
|
||||
str(self.second_product.author.id): {
|
||||
str(self.second_product.id): 5,
|
||||
}
|
||||
})
|
||||
self.assertDictEqual(
|
||||
self.client.session[settings.CART_SESSION_ID], final_dict
|
||||
)
|
||||
|
||||
def test_add_item_invalid_product_id(self):
|
||||
response = self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": 999, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_add_item_invalid_quantity(self):
|
||||
response = self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": "invalid"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_remove_item_success(self):
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
1
|
||||
)
|
||||
self.client.post(
|
||||
reverse("cart-action-remove-product"),
|
||||
{"product_id": self.product.id},
|
||||
)
|
||||
self.assertEqual(self.client.session[settings.CART_SESSION_ID], {str(self.product.author.id): {}})
|
||||
|
||||
def test_remove_item_invalid_product_id(self):
|
||||
response = self.client.post(
|
||||
reverse("cart-action-remove-product"),
|
||||
{"product_id": 999},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_update_item_quantity_success(self):
|
||||
self.client.post(
|
||||
reverse("cart-action-add-product"),
|
||||
{"product_id": self.product.id, "quantity": 1},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
1
|
||||
)
|
||||
self.client.put(
|
||||
reverse("cart-action-update-product", kwargs={"pk": self.product.id}),
|
||||
{"quantity": 5},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.session[settings.CART_SESSION_ID][str(self.product.author.id)][str(self.product.id)],
|
||||
5
|
||||
)
|
||||
|
||||
def test_update_item_quantity_invalid_product_id(self):
|
||||
response = self.client.put(
|
||||
reverse("cart-action-update-product", kwargs={"pk": 2137}),
|
||||
{"quantity": 5},
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
|
@ -1,5 +1,6 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.core import mail
|
||||
|
||||
from store.tests import factories
|
||||
from store import models as store_models
|
||||
|
@ -9,33 +10,102 @@ from store import models as store_models
|
|||
# https://factoryboy.readthedocs.io/en/stable/
|
||||
# TODO - test have to rewritten - I'll do it tommorow
|
||||
|
||||
class OrderDocumentTestCase(TestCase):
|
||||
|
||||
class OrderProductTestCase(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.author = factories.ProductAuthorFactory()
|
||||
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)
|
||||
self.product = factories.ProductFactory(template__author=self.author, price=100)
|
||||
self.second_product = factories.ProductFactory(template__author=self.author, price=200)
|
||||
|
||||
def test_send_order_document_mail_success(self):
|
||||
...
|
||||
|
||||
def test_send_order_document_mail_failure_wrong_email(self):
|
||||
...
|
||||
def test_create_from_cart_single_product_success(self):
|
||||
products = store_models.OrderProduct.objects.create_from_cart(
|
||||
items=[{"product": self.product, "quantity": 1}],
|
||||
order=self.order
|
||||
)
|
||||
self.assertEqual(products.count(), 1)
|
||||
|
||||
def test_create_from_cart_multiple_products_success(self):
|
||||
products = store_models.OrderProduct.objects.create_from_cart(
|
||||
items=[
|
||||
{"product": self.product, "quantity": 1},
|
||||
{"product": self.second_product, "quantity": 1}
|
||||
],
|
||||
order=self.order
|
||||
)
|
||||
self.assertEqual(products.count(), 2)
|
||||
|
||||
def test_create_from_cart_wrong_quanitity_failure(self):
|
||||
products = store_models.OrderProduct.objects.create_from_cart(
|
||||
items=[{"product": self.product, "quantity": -123}],
|
||||
order=self.order
|
||||
)
|
||||
self.assertEqual(products.count(), 0)
|
||||
|
||||
|
||||
def test_create_from_cart_empty_data_failure(self):
|
||||
products = store_models.OrderProduct.objects.create_from_cart(
|
||||
items=[],
|
||||
order=self.order
|
||||
)
|
||||
self.assertEqual(products.count(), 0)
|
||||
|
||||
|
||||
class OrderTestCase(TestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.author = factories.ProductAuthorFactory()
|
||||
self.second_author = factories.ProductAuthorFactory()
|
||||
self.customer_data = {
|
||||
"first_name": "Jan",
|
||||
"last_name": "Kowalski",
|
||||
"email": "jan.kowalski@tepewu.pl",
|
||||
"phone": "",
|
||||
"address": "",
|
||||
"postal_code": "",
|
||||
"city": "",
|
||||
"country": "",
|
||||
|
||||
}
|
||||
self.payment_method = factories.PaymentMethodFactory()
|
||||
factories.DocumentTemplateFactory()
|
||||
factories.DocumentTemplateFactory(doc_type="receipt")
|
||||
|
||||
def test_create_from_cart_success_single_author(self):
|
||||
product = factories.ProductFactory(template__author=self.author, price=100)
|
||||
cart_items = [{
|
||||
"author": self.author,
|
||||
"products": [{"product": product, "quantity": 1}]
|
||||
}]
|
||||
orders = store_models.Order.objects.create_from_cart(
|
||||
cart_items=cart_items,
|
||||
customer_data=self.customer_data,
|
||||
payment_method=self.payment_method
|
||||
)
|
||||
self.assertEqual(orders.count(), 1)
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
self.assertEqual(mail.outbox[0].subject, f"Zamówienie {orders[0].order_number}")
|
||||
|
||||
|
||||
def test_create_from_cart_success_multpile_authors(self):
|
||||
product = factories.ProductFactory(template__author=self.second_author, price=100)
|
||||
cart_items = [
|
||||
{
|
||||
"author": self.author,
|
||||
"products": [{"product": product, "quantity": 1}]
|
||||
}, {
|
||||
"author": self.second_author,
|
||||
"products": [{"product": product, "quantity": 1}]
|
||||
}
|
||||
]
|
||||
orders = store_models.Order.objects.create_from_cart(
|
||||
cart_items=cart_items,
|
||||
customer_data=self.customer_data,
|
||||
payment_method=self.payment_method
|
||||
)
|
||||
self.assertEqual(orders.count(), 2)
|
||||
self.assertEqual(len(mail.outbox), 4)
|
||||
self.assertEqual(mail.outbox[0].subject, f"Zamówienie {orders[0].order_number}")
|
||||
self.assertEqual(mail.outbox[2].subject, f"Zamówienie {orders[1].order_number}")
|
||||
|
|
|
@ -1,18 +1,39 @@
|
|||
from typing import Any
|
||||
from django.core.mail import EmailMessage
|
||||
from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
|
||||
|
||||
# TODO - add celery task for sending not sent earlier
|
||||
def send_mail(order_doc):
|
||||
order = order_doc.order
|
||||
def send_mail(
|
||||
to: list[str], docs: Any, order_number: str,
|
||||
subject: str, body: str
|
||||
):
|
||||
message = EmailMessage(
|
||||
subject=f"Zamówienie {order.order_number}",
|
||||
body="Dokumenty dla Twojego zamówienia",
|
||||
subject=subject,
|
||||
body=body,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
to=[order.customer.email]
|
||||
to=to
|
||||
)
|
||||
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
|
||||
for doc in docs:
|
||||
message.attach(f"{order_number}.pdf", doc, "application/pdf")
|
||||
return bool(message.send())
|
||||
|
||||
|
||||
def notify_user_about_order(customer_email, docs, order_number):
|
||||
return send_mail(
|
||||
to=[customer_email],
|
||||
docs=docs,
|
||||
order_number=order_number,
|
||||
subject=f"Zamówienie {order_number}",
|
||||
body="Dokumenty dla Twojego zamówienia"
|
||||
)
|
||||
|
||||
|
||||
def notify_manufacturer_about_order(manufacturer_email, docs, order_number):
|
||||
return send_mail(
|
||||
to=[manufacturer_email],
|
||||
docs=docs,
|
||||
order_number=order_number,
|
||||
subject=f"Złożono zamówienie {order_number}",
|
||||
body="Dokumenty dla złożonego zamówienia"
|
||||
)
|
|
@ -13,16 +13,13 @@ from rest_framework.response import Response
|
|||
|
||||
from store.cart import SessionCart
|
||||
from store.serializers import (
|
||||
CartProductSerializer,
|
||||
CartSerializer,
|
||||
CartProductAddSerializer
|
||||
)
|
||||
from store.forms import CustomerDataForm
|
||||
from store.models import (
|
||||
CustomerData,
|
||||
Order,
|
||||
OrderProduct,
|
||||
OrderDocument,
|
||||
DocumentTemplate
|
||||
Product
|
||||
)
|
||||
|
||||
|
||||
|
@ -43,12 +40,13 @@ class CartView(TemplateView):
|
|||
|
||||
class CartActionView(ViewSet):
|
||||
|
||||
# TODO - test this, currently not in use
|
||||
@action(detail=False, methods=["get"], url_path="list-products")
|
||||
def list_products(self, request):
|
||||
# get cart items
|
||||
cart = SessionCart(self.request)
|
||||
items = cart.get_items()
|
||||
serializer = CartProductSerializer(instance=items, many=True)
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=["post"])
|
||||
|
@ -58,27 +56,32 @@ class CartActionView(ViewSet):
|
|||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=400)
|
||||
serializer.save(cart)
|
||||
|
||||
items = cart.get_items()
|
||||
serializer = CartProductSerializer(instance=items, many=True)
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
@action(detail=False, methods=["post"])
|
||||
def remove_product(self, request):
|
||||
cart = SessionCart(self.request)
|
||||
product_id = request.POST.get("product_id")
|
||||
cart.remove_item(product_id)
|
||||
try:
|
||||
cart.remove_item(product_id)
|
||||
except Product.DoesNotExist:
|
||||
return Response({"error": "Product does not exist"}, status=400)
|
||||
|
||||
items = cart.get_items()
|
||||
serializer = CartProductSerializer(instance=items, many=True)
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
@action(detail=True, methods=["put"])
|
||||
def update_product(self, request, pk):
|
||||
cart = SessionCart(self.request)
|
||||
cart.update_item_quantity(pk, int(request.data["quantity"]))
|
||||
try:
|
||||
cart.update_item_quantity(pk, int(request.data["quantity"]))
|
||||
except Product.DoesNotExist:
|
||||
return Response({"error": "Product does not exist"}, status=404)
|
||||
items = cart.get_items()
|
||||
serializer = CartProductSerializer(instance=items, many=True)
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
|
||||
|
@ -104,13 +107,12 @@ class OrderView(View):
|
|||
return HttpResponseRedirect(reverse("cart"))
|
||||
form = CustomerDataForm(request.POST)
|
||||
if not form.is_valid():
|
||||
print(form.errors)
|
||||
context = self.get_context_data()
|
||||
context["form"] = form
|
||||
return render(request, self.template_name, context)
|
||||
customer_data = form.save()
|
||||
request.session["customer_data_id"] = customer_data.id
|
||||
# TODO - add this page
|
||||
customer_data = form.data
|
||||
# TODO - add encryption
|
||||
request.session["customer_data"] = customer_data
|
||||
return HttpResponseRedirect(reverse("order-confirm"))
|
||||
|
||||
|
||||
|
@ -118,7 +120,7 @@ class OrderConfirmView(View):
|
|||
template_name = "store/order_confirm.html"
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||
customer_data = CustomerData.objects.get(id=self.request.session["customer_data_id"])
|
||||
customer_data = self.request.session["customer_data"]
|
||||
return {
|
||||
"cart": SessionCart(self.request),
|
||||
"customer_data": customer_data
|
||||
|
@ -132,12 +134,13 @@ class OrderConfirmView(View):
|
|||
return render(request, self.template_name, self.get_context_data())
|
||||
|
||||
def post(self, request):
|
||||
customer_data = CustomerData.objects.get(id=self.request.session["customer_data_id"])
|
||||
customer_data = request.session["customer_data"]
|
||||
cart = SessionCart(self.request)
|
||||
order = Order.objects.create_from_cart(
|
||||
cart, customer_data
|
||||
Order.objects.create_from_cart(
|
||||
cart.get_items(),
|
||||
None, customer_data
|
||||
)
|
||||
self.request.session.pop("customer_data_id")
|
||||
request.session.pop("customer_data")
|
||||
cart.clear()
|
||||
# TODO - messages
|
||||
return HttpResponseRedirect(reverse("cart"))
|
||||
|
|
Ładowanie…
Reference in New Issue