sforkowany z mtyton/comfy
added delivery feature and payment method
rodzic
3580a3b1e1
commit
783e04a134
|
@ -1,6 +1,9 @@
|
|||
import logging
|
||||
|
||||
from abc import (
|
||||
ABC,
|
||||
abstractmethod
|
||||
abstractmethod,
|
||||
abstractproperty
|
||||
)
|
||||
from typing import (
|
||||
List,
|
||||
|
@ -13,9 +16,12 @@ from django.core import signing
|
|||
|
||||
from store.models import (
|
||||
Product,
|
||||
ProductAuthor
|
||||
ProductAuthor,
|
||||
DeliveryMethod
|
||||
)
|
||||
|
||||
logger = logging.getLogger("cart_logger")
|
||||
|
||||
|
||||
class BaseCart(ABC):
|
||||
|
||||
|
@ -34,22 +40,54 @@ class BaseCart(ABC):
|
|||
def update_item_quantity(self, item_id, change):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_items(self):
|
||||
@abstractproperty
|
||||
def display_items(self):
|
||||
...
|
||||
|
||||
|
||||
class SessionCart(BaseCart):
|
||||
|
||||
def __init__(self, request: HttpRequest) -> None:
|
||||
|
||||
def _get_author_total_price(self, author_id: int):
|
||||
author_cart = self._cart[str(author_id)]
|
||||
author_price = 0
|
||||
product_ids = list(int(pk) for pk in author_cart.keys())
|
||||
queryset = Product.objects.filter(id__in=product_ids)
|
||||
for product in queryset:
|
||||
author_price += product.price * author_cart[str(product.id)]
|
||||
|
||||
if self._delivery_info:
|
||||
author_price += self._delivery_info.price
|
||||
|
||||
return author_price
|
||||
|
||||
def _prepare_display_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,
|
||||
"group_price": self._get_author_total_price(author_id)
|
||||
})
|
||||
return items
|
||||
|
||||
def __init__(self, request: HttpRequest, delivery: DeliveryMethod=None) -> None:
|
||||
super().__init__()
|
||||
self.session = request.session
|
||||
self._cart = self.session.get(settings.CART_SESSION_ID, None)
|
||||
if not self._cart:
|
||||
self._cart = {}
|
||||
self.session[settings.CART_SESSION_ID] = self._cart
|
||||
|
||||
self._delivery_info = delivery
|
||||
self._display_items = self._prepare_display_items()
|
||||
|
||||
def save_cart(self):
|
||||
self._display_items = self._prepare_display_items()
|
||||
self.session[settings.CART_SESSION_ID] = self._cart
|
||||
self.session.modified = True
|
||||
|
||||
|
@ -76,8 +114,7 @@ class SessionCart(BaseCart):
|
|||
self._cart[str(author.id)].pop(str(item_id))
|
||||
self.save_cart()
|
||||
except KeyError:
|
||||
# TODO - add logging
|
||||
...
|
||||
logger.exception(f"Item {item_id} not found in cart")
|
||||
|
||||
def update_item_quantity(self, item_id: int, new_quantity: int) -> None:
|
||||
product = self.validate_and_get_product(item_id)
|
||||
|
@ -91,17 +128,14 @@ class SessionCart(BaseCart):
|
|||
self._cart[str(author.id)][str(product.id)] = new_quantity
|
||||
self.save_cart()
|
||||
|
||||
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 delivery_info(self):
|
||||
return self._delivery_info
|
||||
|
||||
@property
|
||||
def display_items(self) -> List[dict[str, dict|str]]:
|
||||
return self._display_items
|
||||
|
||||
@property
|
||||
def total_price(self):
|
||||
total = 0
|
||||
|
@ -109,6 +143,8 @@ class SessionCart(BaseCart):
|
|||
for item_id, quantity in cart_items.items():
|
||||
product = Product.objects.get(id=int(item_id))
|
||||
total += product.price * quantity
|
||||
if self._delivery_info:
|
||||
total += self._delivery_info.price * len(self._cart.keys())
|
||||
return total
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
|
|
|
@ -51,11 +51,10 @@ class CustomerDataForm(forms.Form):
|
|||
widget=forms.Select(attrs={"class": "form-control"})
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
def serialize(self):
|
||||
"""Clean method should return JSON serializable"""
|
||||
cleaned_data = super().clean()
|
||||
new_cleaned_data = {}
|
||||
for key, value in cleaned_data.items():
|
||||
for key, value in self.cleaned_data.items():
|
||||
if isinstance(value, PhoneNumber):
|
||||
new_cleaned_data[key] = str(value)
|
||||
elif isinstance(value, Model):
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-truck" viewBox="0 0 16 16">
|
||||
<path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h9A1.5 1.5 0 0 1 12 3.5V5h1.02a1.5 1.5 0 0 1 1.17.563l1.481 1.85a1.5 1.5 0 0 1 .329.938V10.5a1.5 1.5 0 0 1-1.5 1.5H14a2 2 0 1 1-4 0H5a2 2 0 1 1-3.998-.085A1.5 1.5 0 0 1 0 10.5v-7zm1.294 7.456A1.999 1.999 0 0 1 4.732 11h5.536a2.01 2.01 0 0 1 .732-.732V3.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .294.456zM12 10a2 2 0 0 1 1.732 1h.768a.5.5 0 0 0 .5-.5V8.35a.5.5 0 0 0-.11-.312l-1.48-1.85A.5.5 0 0 0 13.02 6H12v4zm-9 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm9 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 658 B |
|
@ -9,7 +9,7 @@
|
|||
<h3 class="fw-normal mb-0 text-black">Koszyk</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% for group in cart.get_items %}
|
||||
{% for group in cart.display_items %}
|
||||
{% if group.products %}
|
||||
<h4>Wykonawca: {{group.author.display_name}}</h4>
|
||||
{% for item in group.products %}
|
||||
|
|
|
@ -57,20 +57,28 @@
|
|||
<h3 class="fw-normal mb-0 text-black">Zamówione przedmioty</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% for group in cart.get_items %}
|
||||
{% for group in cart.display_items %}
|
||||
{% if group.products %}
|
||||
<h4>Wykonawca: {{group.author.display_name}}</h4>
|
||||
{% for item in group.products %}
|
||||
{% include 'store/partials/summary_cart_item.html' %}
|
||||
{% endfor %}
|
||||
{% if cart.delivery_info %}
|
||||
{% with delivery=cart.delivery_info %}
|
||||
{% include 'store/partials/delivery_cart_item.html' %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<div class="col-sm-11 text-end">
|
||||
<h5 class="fw-normal mb-0 pr-3text-black">W sumie: {{group.group_price}} zł</h5>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="card ">
|
||||
<div class="card mt-5">
|
||||
<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">Do zapłaty: {{cart.total_price}} zł</h5>
|
||||
</div>
|
||||
<div class="col-sm-6 text-end">
|
||||
<form action="" method="POST">
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{% load static %}
|
||||
|
||||
<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">
|
||||
<img
|
||||
src="{% static 'images/icons/truck.svg'%}"
|
||||
class="rounded mx-auto d-block">
|
||||
</div>
|
||||
<div class="col-md-3 col-lg-3 col-xl-3">
|
||||
<p class="lead fw-normal mb-2">{{delivery.name}}</p>
|
||||
</div>
|
||||
<div class="col-md-3 col-lg-3 col-xl-2 d-flex">
|
||||
1
|
||||
</div>
|
||||
<div class="col-md-3 col-lg-2 col-xl-2 offset-lg-1">
|
||||
<h5 class="mb-0">{{delivery.price}} zł</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
{{item.quantity}}
|
||||
</div>
|
||||
<div class="col-md-3 col-lg-2 col-xl-2 offset-lg-1">
|
||||
<h5 class="mb-0">{{item.product.price}} ZŁ</h5>
|
||||
<h5 class="mb-0">{{item.product.price}} zł</h5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -126,7 +126,7 @@ class ProductTestCase(TestCase):
|
|||
self.assertIsNotNone(prod)
|
||||
self.assertNotEqual(prod.pk, product.pk)
|
||||
self.assertFalse(prod.available)
|
||||
self.assertEqual(prod.price, 13.0)
|
||||
self.assertEqual(prod.price, 0)
|
||||
|
||||
def test_get_or_create_by_params_success_not_existing_product_no_other_products(self):
|
||||
template = factories.ProductTemplateFactory()
|
||||
|
|
|
@ -58,7 +58,7 @@ class CartActionView(ViewSet):
|
|||
def list_products(self, request):
|
||||
# get cart items
|
||||
cart = SessionCart(self.request)
|
||||
items = cart.get_items()
|
||||
items = cart.display_items
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
@ -69,7 +69,7 @@ class CartActionView(ViewSet):
|
|||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=400)
|
||||
serializer.save(cart)
|
||||
items = cart.get_items()
|
||||
items = cart.display_items
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
|
@ -82,7 +82,7 @@ class CartActionView(ViewSet):
|
|||
except Product.DoesNotExist:
|
||||
return Response({"error": "Product does not exist"}, status=400)
|
||||
|
||||
items = cart.get_items()
|
||||
items = cart.display_items
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
|
@ -93,7 +93,7 @@ class CartActionView(ViewSet):
|
|||
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()
|
||||
items = cart.display_items
|
||||
serializer = CartSerializer(instance=items, many=True)
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
|
@ -176,7 +176,7 @@ class OrderView(View):
|
|||
context = self.get_context_data()
|
||||
context["form"] = form
|
||||
return render(request, self.template_name, context)
|
||||
customer_data = CustomerData(data=form.cleaned_data)
|
||||
customer_data = CustomerData(data=form.serialize())
|
||||
request.session["customer_data"] = customer_data.data
|
||||
return HttpResponseRedirect(reverse("order-confirm"))
|
||||
|
||||
|
@ -185,12 +185,19 @@ class OrderConfirmView(View):
|
|||
template_name = "store/order_confirm.html"
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||
customer_data = CustomerData(
|
||||
encrypted_data=self.request.session["customer_data"]
|
||||
).decrypted_data
|
||||
|
||||
form = CustomerDataForm(
|
||||
data=CustomerData(
|
||||
encrypted_data=self.request.session["customer_data"]
|
||||
).decrypted_data
|
||||
)
|
||||
if not form.is_valid():
|
||||
raise Exception("Customer data is not valid")
|
||||
|
||||
customer_data = form.cleaned_data
|
||||
return {
|
||||
"cart": SessionCart(self.request),
|
||||
"customer_data": customer_data
|
||||
"cart": SessionCart(self.request, delivery=customer_data["delivery_method"]),
|
||||
"customer_data": customer_data
|
||||
}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -201,10 +208,12 @@ class OrderConfirmView(View):
|
|||
return render(request, self.template_name, self.get_context_data())
|
||||
|
||||
def post(self, request):
|
||||
customer_data = request.session["customer_data"]
|
||||
customer_data = CustomerData(
|
||||
encrypted_data=self.request.session["customer_data"]
|
||||
).decrypted_data
|
||||
cart = SessionCart(self.request)
|
||||
order = Order.objects.create_from_cart(
|
||||
cart.get_items(),
|
||||
cart.display_items,
|
||||
None, customer_data
|
||||
)
|
||||
request.session.pop("customer_data")
|
||||
|
|
Ładowanie…
Reference in New Issue