feat: cart detail leads to order creation

This commit is contained in:
2025-01-02 17:20:03 +01:00
parent ab2c9f4d22
commit 10b411ba33
9 changed files with 290 additions and 187 deletions
+50 -4
View File
@@ -1,10 +1,9 @@
# Generated by Django 5.1.3 on 2024-12-31 09:48
import uuid
from decimal import Decimal
# Generated by Django 5.1.3 on 2025-01-02 13:36
import django.db.models.deletion
import django.utils.timezone
import uuid
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
@@ -894,4 +893,51 @@ class Migration(migrations.Migration):
"ordering": ("-date",),
},
),
migrations.CreateModel(
name="WishlistedProduct",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"creation_date",
models.DateTimeField(
auto_now_add=True, verbose_name="fecha de creación"
),
),
(
"last_modification_date",
models.DateTimeField(
auto_now=True, verbose_name="fecha de última modificación"
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shop.product",
verbose_name="producto",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="usuario",
),
),
],
options={
"verbose_name": "productos deseados",
"verbose_name_plural": "productos deseados",
"unique_together": {("user", "product")},
},
),
]
-62
View File
@@ -1,62 +0,0 @@
# Generated by Django 5.1.3 on 2025-01-02 09:35
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="WishlistedProduct",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"creation_date",
models.DateTimeField(
auto_now_add=True, verbose_name="fecha de creación"
),
),
(
"last_modification_date",
models.DateTimeField(
auto_now=True, verbose_name="fecha de última modificación"
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="shop.product",
verbose_name="producto",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="usuario",
),
),
],
options={
"verbose_name": "productos deseados",
"verbose_name_plural": "productos deseados",
},
),
]
+10 -7
View File
@@ -122,11 +122,7 @@ class Product(TimestampedModel):
def __str__(self):
return self.name
def save(
self,
*args,
**kwargs,
):
def save(self, *args, **kwargs):
if not self.pk:
self.slug = slugify(self.name)[:128]
super().save(*args, **kwargs)
@@ -362,7 +358,7 @@ class Order(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=_("cliente"))
# Billing information
# Datos de facturación
billing_address = models.CharField(
max_length=255, blank=False, verbose_name=_("dirección de facturación")
)
@@ -379,7 +375,7 @@ class Order(TimestampedModel):
max_length=32, blank=False, verbose_name=_("código postal de facturación")
)
# Shipping information
# Datos de envío
shipping_address = models.CharField(
max_length=255, blank=False, verbose_name=_("dirección")
)
@@ -499,6 +495,9 @@ class CustomerAddress(models.Model):
default = models.BooleanField(default=True, verbose_name=_("por defecto"))
hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto"))
def __str__(self):
return f'{self.full_name} - {self.address}, {self.address_zip}, {self.address_state} - {self.address_country}'
class Meta:
verbose_name = _("dirección de cliente")
verbose_name_plural = _("direcciones de cliente")
@@ -529,3 +528,7 @@ class WishlistedProduct(TimestampedModel):
class Meta:
verbose_name = _("productos deseados")
verbose_name_plural = _("productos deseados")
unique_together = (
"user",
"product",
)
+3 -2
View File
@@ -88,6 +88,7 @@ def create_order_from_cart(
shipping_state=shipping_address.address_state,
shipping_country=shipping_address.address_country,
shipping_zip=shipping_address.address_zip,
user=cart.user,
)
for item in cart.items.all():
@@ -95,7 +96,7 @@ def create_order_from_cart(
price: ProductPrice = product.price
base_total = price.price * item.quantity
tax_value = price.tax.value
taxes = base_total * (tax_value / 100)
taxes = base_total * tax_value / Decimal(100)
total = base_total + taxes
OrderLine.objects.create(
@@ -119,7 +120,7 @@ def add_shipping_order_line(order, shipping_method):
shipping_price: ProductPrice = shipping_method.shipping_product.price
shipping_base_total = shipping_price.price
shipping_tax_value = shipping_price.tax.value
shipping_taxes = shipping_base_total * (shipping_tax_value / 100)
shipping_taxes = shipping_base_total * (shipping_tax_value / Decimal(100))
shipping_total = shipping_base_total + shipping_taxes
OrderLine.objects.create(
+6
View File
@@ -11,3 +11,9 @@ class CartItemForm(forms.Form):
product = forms.IntegerField(widget=widgets.HiddenInput)
quantity = forms.IntegerField()
class CreateOrderForm(forms.Form):
shipping_address = forms.IntegerField()
billing_address = forms.IntegerField()
shipping_method = forms.IntegerField()
+62 -8
View File
@@ -1,5 +1,7 @@
{% load i18n %}
<section class="bg-white py-2 antialiased dark:bg-gray-900 md:py-8">
<form action="{% url 'web:cart_detail' %}" method="post">
{% csrf_token %}
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">{% translate 'Carrito' %}</h2>
@@ -27,7 +29,8 @@
</span>
</div>
<div class="text-end md:order-4 md:w-32">
<p class="text-base font-bold text-gray-900 dark:text-white">{{ item.product.price.price_with_tax }}
<p
class="text-base font-bold text-gray-900 dark:text-white">{{ item.product.price.price_with_tax }}
</p>
</div>
</div>
@@ -59,13 +62,63 @@
{% endfor %}
{% if items.count == 0 %}
<span class="text-base font-normal text-gray-500 dark:text-gray-400">
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
{% translate 'No hay nada en el carrito...' %}
</span>
</p>
<p>
<a href="{% url 'web:index' %}" title=""
class="inline-flex items-center gap-2 text-sm font-medium text-primary-700 underline hover:no-underline dark:text-primary-500">
{% translate 'Volver a inicio' %}
</a>
</p>
{% endif %}
</div>
<section>
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
{% translate 'Dirección de envío' %}
</h2>
{% for address in shipping_addresses %}
<input type="radio" id="shipping_address_{{ address.pk }}" name="shipping_address"
value="{{ address.pk }}">
<label for="shipping_address_{{ address.pk }}">
{{ address }}
</label>
{% endfor %}
</section>
<section>
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
{% translate 'Dirección de facturación' %}
</h2>
{% for address in billing_addresses %}
<input type="radio" id="billing_address_{{ address.pk }}" name="billing_address"
value="{{ address.pk }}">
<label for="billing_address_{{ address.pk }}">
{{ address }}
</label>
{% endfor %}
</section>
<section>
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
{% translate 'Opciones de envío' %}
</h2>
{% for shipping_method in shipping_methods %}
<input type="radio" id="shipping_method_{{ shipping_method.pk }}" name="shipping_method"
value="{{ shipping_method.pk }}">
<label for="shipping_method_{{ shipping_method.pk }}">
{{ shipping_method.name }} - {{ shipping_method.shipping_product.price.price_with_tax }}€
</label>
{% endfor %}
</section>
</div>
</div>
<!-- Resumen del pedido -->
<div class="mx-auto mt-6 max-w-4xl flex-1 space-y-6 lg:mt-0 lg:w-full">
<div
class="space-y-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 sm:p-6">
@@ -74,7 +127,8 @@
<div class="space-y-4">
<div class="space-y-2">
<dl class="flex items-center justify-between gap-4">
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Base imponible' %}</dt>
<dt
class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Base imponible' %}</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ base_total }} €</dd>
</dl>
@@ -101,12 +155,10 @@
</dl>
</div>
<form action="">
<button
class="flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
{% translate 'Ir a pagar' %}
{% translate 'Terminar pedido' %}
</button>
</form>
<div class="flex items-center justify-center gap-2">
<span class="text-sm font-normal text-gray-500 dark:text-gray-400"> {% translate 'o' %} </span>
@@ -135,5 +187,7 @@
{# </div>#}
</div>
</div>
</div>
</form>
</section>
@@ -74,7 +74,7 @@
class="mb-2 me-2 inline-flex w-full items-center justify-center rounded-lg bg-primary-700 px-5 py-2.5 text-sm
font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300
dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
role="button"> {% translate 'Ir a pagar' %}
role="button"> {% translate 'Comprar' %}
</a>
{% endif %}
+19 -1
View File
@@ -4,7 +4,14 @@ from django.shortcuts import get_object_or_404, render
from django.views.generic import TemplateView
from shop.filters import ProductFilter
from shop.models import CartItem, Product, ProductPrice, WishlistedProduct
from shop.models import (
CartItem,
Product,
ProductPrice,
WishlistedProduct,
CustomerAddress,
ShippingMethod,
)
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
@@ -84,6 +91,14 @@ def cart(request, *args, **kwargs):
total = base_total + tax_total
billing_addresses = CustomerAddress.objects.filter(
user=request.user, address_type=CustomerAddress.Types.BILLING
)
shipping_addresses = CustomerAddress.objects.filter(
user=request.user, address_type=CustomerAddress.Types.SHIPPING
)
shipping_methods = ShippingMethod.objects.all()
response = render(
request,
"components/cart/cart.html",
@@ -93,6 +108,9 @@ def cart(request, *args, **kwargs):
"total": total,
"base_total": base_total,
"tax_total": tax_total,
"billing_addresses": billing_addresses,
"shipping_addresses": shipping_addresses,
"shipping_methods": shipping_methods,
},
)
+41 -4
View File
@@ -1,10 +1,19 @@
from django.http.response import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect, reverse
from django.utils.text import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from django.views.generic import TemplateView
from django.views.generic import TemplateView, CreateView
from shop.models import CartItem, Product, ProductCategory, WishlistedProduct
from shop.models import (
CartItem,
Product,
ProductCategory,
WishlistedProduct,
CustomerAddress,
ShippingMethod,
)
from shop.utils import create_order_from_cart
from web.forms import CreateOrderForm
from web.models import WebSettings
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
@@ -55,8 +64,9 @@ class ProductDetail(TemplateView):
}
class CartDetail(TemplateView):
class CartDetail(TemplateView, CreateView):
template_name = "web/cart_detail.html"
form_class = CreateOrderForm
def get_context_data(self, **kwargs):
web_settings = WebSettings.load()
@@ -70,6 +80,33 @@ class CartDetail(TemplateView):
"description": _("resumen del carrito"),
}
def post(self, request, *args, **kwargs):
form = CreateOrderForm(request.POST)
if not form.is_valid():
context = self.get_context_data(**kwargs)
context.update({"errors": form.errors})
return self.render_to_response(context)
billing_address_id = form.cleaned_data.get("billing_address")
shipping_address_id = form.cleaned_data.get("shipping_address")
shipping_method_id = form.cleaned_data.get("shipping_method")
cart, created = get_or_create_cart(request)
billing_address = get_object_or_404(CustomerAddress, pk=billing_address_id)
shipping_address = get_object_or_404(CustomerAddress, pk=shipping_address_id)
shipping_method = get_object_or_404(ShippingMethod, pk=shipping_method_id)
order = create_order_from_cart(
cart=cart,
billing_address=billing_address,
shipping_address=shipping_address,
shipping_method=shipping_method,
)
return redirect(reverse("web:order", kwargs={"pk": order.pk}))
class WishlistView(TemplateView):
template_name = "web/wishlist.html"