feat: order creation on cart detail view
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.1.3 on 2025-01-02 13:36
|
# Generated by Django 5.1.3 on 2025-01-03 11:17
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
@@ -275,6 +275,7 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="tipo de dirección",
|
verbose_name="tipo de dirección",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
("email", models.EmailField(max_length=254, verbose_name="e-mail")),
|
||||||
(
|
(
|
||||||
"default",
|
"default",
|
||||||
models.BooleanField(default=True, verbose_name="por defecto"),
|
models.BooleanField(default=True, verbose_name="por defecto"),
|
||||||
@@ -354,6 +355,12 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="total",
|
verbose_name="total",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, verbose_name="e-mail"
|
||||||
|
),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"billing_address",
|
"billing_address",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
@@ -411,6 +418,8 @@ class Migration(migrations.Migration):
|
|||||||
(
|
(
|
||||||
"user",
|
"user",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=settings.AUTH_USER_MODEL,
|
||||||
verbose_name="cliente",
|
verbose_name="cliente",
|
||||||
|
|||||||
+4
-2
@@ -356,7 +356,8 @@ class Order(TimestampedModel):
|
|||||||
default=Decimal("0"), max_digits=13, decimal_places=2, verbose_name=_("total")
|
default=Decimal("0"), max_digits=13, decimal_places=2, verbose_name=_("total")
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=_("cliente"))
|
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("cliente"))
|
||||||
|
email = models.EmailField(blank=True, verbose_name=_('e-mail'))
|
||||||
|
|
||||||
# Datos de facturación
|
# Datos de facturación
|
||||||
billing_address = models.CharField(
|
billing_address = models.CharField(
|
||||||
@@ -492,11 +493,12 @@ class CustomerAddress(models.Model):
|
|||||||
default=Types.SHIPPING,
|
default=Types.SHIPPING,
|
||||||
verbose_name=_("tipo de dirección"),
|
verbose_name=_("tipo de dirección"),
|
||||||
)
|
)
|
||||||
|
email = models.EmailField(blank=False, null=False, verbose_name=_("e-mail"))
|
||||||
default = models.BooleanField(default=True, verbose_name=_("por defecto"))
|
default = models.BooleanField(default=True, verbose_name=_("por defecto"))
|
||||||
hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto"))
|
hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.full_name} - {self.address}, {self.address_zip}, {self.address_state} - {self.address_country}'
|
return f"{self.full_name} - {self.address}, {self.address_zip}, {self.address_state} - {self.address_country}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("dirección de cliente")
|
verbose_name = _("dirección de cliente")
|
||||||
|
|||||||
+26
-12
@@ -73,22 +73,36 @@ def delete_product_batch(batch: ProductBatch):
|
|||||||
|
|
||||||
def create_order_from_cart(
|
def create_order_from_cart(
|
||||||
cart: Cart,
|
cart: Cart,
|
||||||
billing_address: CustomerAddress,
|
|
||||||
shipping_address: CustomerAddress,
|
|
||||||
shipping_method: ShippingMethod,
|
shipping_method: ShippingMethod,
|
||||||
|
billing_address_full_name="",
|
||||||
|
billing_address_address="",
|
||||||
|
billing_address_town="",
|
||||||
|
billing_address_state="",
|
||||||
|
billing_address_country="",
|
||||||
|
billing_address_zip="",
|
||||||
|
shipping_address_full_name="",
|
||||||
|
shipping_address_address="",
|
||||||
|
shipping_address_town="",
|
||||||
|
shipping_address_state="",
|
||||||
|
shipping_address_country="",
|
||||||
|
shipping_address_zip="",
|
||||||
|
shipping_address_phone="",
|
||||||
|
email="",
|
||||||
):
|
):
|
||||||
order = Order.objects.create(
|
order = Order.objects.create(
|
||||||
billing_address=billing_address.address,
|
billing_address=f'{billing_address_full_name} {billing_address_address}',
|
||||||
billing_city=billing_address.address_town,
|
billing_city=billing_address_town,
|
||||||
billing_state=billing_address.address_state,
|
billing_state=billing_address_state,
|
||||||
billing_country=billing_address.address_country,
|
billing_country=billing_address_country,
|
||||||
billing_zip=billing_address.address_zip,
|
billing_zip=billing_address_zip,
|
||||||
shipping_address=shipping_address.address,
|
contact_phone=shipping_address_phone,
|
||||||
shipping_city=shipping_address.address_town,
|
shipping_address=f'{shipping_address_address} {shipping_address_full_name}',
|
||||||
shipping_state=shipping_address.address_state,
|
shipping_city=shipping_address_town,
|
||||||
shipping_country=shipping_address.address_country,
|
shipping_state=shipping_address_state,
|
||||||
shipping_zip=shipping_address.address_zip,
|
shipping_country=shipping_address_country,
|
||||||
|
shipping_zip=shipping_address_zip,
|
||||||
user=cart.user,
|
user=cart.user,
|
||||||
|
email=email,
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in cart.items.all():
|
for item in cart.items.all():
|
||||||
|
|||||||
+22
-2
@@ -1,6 +1,8 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
|
|
||||||
|
from shop.models import CustomerAddress
|
||||||
|
|
||||||
|
|
||||||
class CartItemForm(forms.Form):
|
class CartItemForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
@@ -14,6 +16,24 @@ class CartItemForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class CreateOrderForm(forms.Form):
|
class CreateOrderForm(forms.Form):
|
||||||
shipping_address = forms.IntegerField()
|
email = forms.CharField(max_length=254)
|
||||||
billing_address = forms.IntegerField()
|
|
||||||
|
shipping_address_full_name = forms.CharField(max_length=128, required=True)
|
||||||
|
shipping_address = forms.CharField(max_length=128, required=True)
|
||||||
|
shipping_address_town = forms.CharField(max_length=64, required=True)
|
||||||
|
shipping_address_zip = forms.CharField(max_length=16, required=True)
|
||||||
|
shipping_address_state = forms.CharField(max_length=64, required=True)
|
||||||
|
shipping_address_country = forms.CharField(max_length=64, required=True)
|
||||||
|
shipping_address_phone = forms.CharField(max_length=16, required=False)
|
||||||
|
|
||||||
|
same_as_shipping = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
billing_address_full_name = forms.CharField(max_length=128, required=True)
|
||||||
|
billing_address = forms.CharField(max_length=128, required=True)
|
||||||
|
billing_address_town = forms.CharField(max_length=64, required=True)
|
||||||
|
billing_address_zip = forms.CharField(max_length=16, required=True)
|
||||||
|
billing_address_state = forms.CharField(max_length=64, required=True)
|
||||||
|
billing_address_country = forms.CharField(max_length=64, required=True)
|
||||||
|
billing_address_phone = forms.CharField(max_length=16, required=False)
|
||||||
|
|
||||||
shipping_method = forms.IntegerField()
|
shipping_method = forms.IntegerField()
|
||||||
|
|||||||
@@ -78,27 +78,179 @@
|
|||||||
{% translate 'Dirección de envío' %}
|
{% translate 'Dirección de envío' %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% for address in shipping_addresses %}
|
<div class="grid gap-4 sm:grid-cols-2 sm:gap-6 my-2">
|
||||||
<input type="radio" id="shipping_address_{{ address.pk }}" name="shipping_address"
|
{% if user.is_anonymous %}
|
||||||
value="{{ address.pk }}">
|
<div class="sm:col-span-2">
|
||||||
<label for="shipping_address_{{ address.pk }}">
|
<label for="email"
|
||||||
{{ address }}
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'E-Mail' %}</label>
|
||||||
</label>
|
<input type="text" name="email" id="email"
|
||||||
{% endfor %}
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'E-Mail' %}"
|
||||||
|
value=""
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" name="email" value="{{ user.email }}">
|
||||||
|
{% endif %}
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
<label for="shipping_address_full_name"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Nombre completo' %}</label>
|
||||||
|
<input type="text" name="shipping_address_full_name" id="shipping_address_full_name"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Nombre completo' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.full_name }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
<label for="shipping_address"
|
||||||
|
class="block my-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Dirección' %}</label>
|
||||||
|
<input type="text" name="shipping_address" id="shipping_address"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Dirección' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.address }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label for="shipping_address_town"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Localidad' %}</label>
|
||||||
|
<input type="text" name="shipping_address_town" id="shipping_address_town"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Localidad' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.address_town }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label for="shipping_address_zip"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Código postal' %}</label>
|
||||||
|
<input type="text" name="shipping_address_zip" id="shipping_address_zip"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Código postal' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.address_zip }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="shipping_address_state"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Provincia' %}</label>
|
||||||
|
<input type="text" name="shipping_address_state" id="shipping_address_state"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Provincia' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.address_state }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="shipping_address_country"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'País' %}</label>
|
||||||
|
<input type="text" name="shipping_address_country" id="shipping_address_country"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'País' %}"
|
||||||
|
value="{% if shipping_address %}{{ shipping_address.address_country }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">
|
||||||
{% translate 'Dirección de facturación' %}
|
{% translate 'Dirección de envío' %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% for address in billing_addresses %}
|
<div class="grid gap-4 sm:grid-cols-2 sm:gap-6 my-2">
|
||||||
<input type="radio" id="billing_address_{{ address.pk }}" name="billing_address"
|
<div class="sm:col-span-2">
|
||||||
value="{{ address.pk }}">
|
<label for="billing_address_full_name"
|
||||||
<label for="billing_address_{{ address.pk }}">
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Nombre completo' %}</label>
|
||||||
{{ address }}
|
<input type="text" name="billing_address_full_name" id="billing_address_full_name"
|
||||||
</label>
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
{% endfor %}
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Nombre completo' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.full_name }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
<label for="billing_address"
|
||||||
|
class="block my-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Dirección' %}</label>
|
||||||
|
<input type="text" name="billing_address" id="billing_address"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Dirección' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.address }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label for="billing_address_town"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Localidad' %}</label>
|
||||||
|
<input type="text" name="billing_address_town" id="billing_address_town"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Localidad' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.address_town }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<label for="billing_address_zip"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Código postal' %}</label>
|
||||||
|
<input type="text" name="billing_address_zip" id="billing_address_zip"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Código postal' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.address_zip }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="billing_address_state"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'Provincia' %}</label>
|
||||||
|
<input type="text" name="billing_address_state" id="billing_address_state"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'Provincia' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.address_state }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="billing_address_country"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{% translate 'País' %}</label>
|
||||||
|
<input type="text" name="billing_address_country" id="billing_address_country"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600
|
||||||
|
focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
|
||||||
|
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||||
|
placeholder="{% translate 'País' %}"
|
||||||
|
value="{% if billing_address %}{{ billing_address.address_country }}{% endif %}"
|
||||||
|
required=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
{% extends 'theme/base.html' %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'components/generic/navbar.html' %}
|
||||||
|
{% 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 'Resumen del pedido' %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="mt-4 sm:mt-6 md:gap-6 lg:flex lg:items-start xl:gap-8">
|
||||||
|
<div class="mx-auto w-full flex-none lg:max-w-2xl xl:max-w-4xl">
|
||||||
|
<div class="space-y-6">
|
||||||
|
|
||||||
|
{% for item in items %}
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 md:p-6">
|
||||||
|
<div class="space-y-4 md:flex md:items-center md:justify-between md:gap-6 md:space-y-0">
|
||||||
|
<a href="#" class="shrink-0 md:order-1">
|
||||||
|
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
|
||||||
|
alt="{{ item.product.name }}"/>
|
||||||
|
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
|
||||||
|
alt="{{ item.product.name }}"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<label for="counter-input" class="sr-only">{% translate 'Cantidad' %}</label>
|
||||||
|
<div class="flex items-center justify-between md:order-3 md:justify-end">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span
|
||||||
|
class="w-10 shrink-0 border-0 bg-transparent text-center text-sm font-medium text-gray-900 focus:outline-none focus:ring-0 dark:text-white">
|
||||||
|
{{ item.quantity }}x
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full min-w-0 flex-1 space-y-4 md:order-2 md:max-w-md">
|
||||||
|
<a href="{% url 'web:product_detail' pk=item.product.pk slug=item.product.slug %}"
|
||||||
|
class="text-base font-medium text-gray-900 hover:underline dark:text-white"
|
||||||
|
>
|
||||||
|
{{ item.product.name }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<form class="flex" hx-post="{% url 'web:delete_cart_item' pk=item.pk %}"
|
||||||
|
hx-swap="none">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex items-center text-sm font-medium text-red-600 hover:underline dark:text-red-500"
|
||||||
|
>
|
||||||
|
{% include 'components/icons/x.svg' %}
|
||||||
|
{% translate 'Quitar' %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if items.count == 0 %}
|
||||||
|
<p class="text-base font-normal text-gray-500 dark:text-gray-400">
|
||||||
|
{% translate 'No hay nada en el carrito...' %}
|
||||||
|
</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 %}
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<p class="text-xl font-semibold text-gray-900 dark:text-white">{% translate 'Resumen del pedido' %}</p>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ base_total }} €</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="flex items-center justify-between gap-4">
|
||||||
|
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Descuentos' %}</dt>
|
||||||
|
<dd class="text-base font-medium text-green-600">-{{ savings }} €</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="flex items-center justify-between gap-4">
|
||||||
|
<dt
|
||||||
|
class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Gastos de envío' %}</dt>
|
||||||
|
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ shipping_cost }} €</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<dl class="flex items-center justify-between gap-4">
|
||||||
|
<dt class="text-base font-normal text-gray-500 dark:text-gray-400">{% translate 'Impuestos' %}</dt>
|
||||||
|
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ tax_total }} €</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700">
|
||||||
|
<dt class="text-base font-bold text-gray-900 dark:text-white">{% translate 'Total' %}</dt>
|
||||||
|
<dd class="text-base font-bold text-gray-900 dark:text-white">{{ total }} €</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 'Pagar' %}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<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 'Seguir comprando' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
@@ -17,6 +17,7 @@ from web.views.web import (
|
|||||||
delete_from_wishlist,
|
delete_from_wishlist,
|
||||||
index,
|
index,
|
||||||
manifest,
|
manifest,
|
||||||
|
order_detail,
|
||||||
product_detail,
|
product_detail,
|
||||||
wishlist,
|
wishlist,
|
||||||
)
|
)
|
||||||
@@ -29,6 +30,7 @@ urlpatterns = [
|
|||||||
path("categories/<str:slug>/", category_view, name="category_view"),
|
path("categories/<str:slug>/", category_view, name="category_view"),
|
||||||
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
|
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
|
||||||
path("cart/", cart_detail, name="cart_detail"),
|
path("cart/", cart_detail, name="cart_detail"),
|
||||||
|
path("order/<str:uuid>/", order_detail, name="order"),
|
||||||
# users
|
# users
|
||||||
path("login/", login, name="login"),
|
path("login/", login, name="login"),
|
||||||
path("logout/", logout, name="logout"),
|
path("logout/", logout, name="logout"),
|
||||||
|
|||||||
+14
-8
@@ -91,12 +91,18 @@ def cart(request, *args, **kwargs):
|
|||||||
|
|
||||||
total = base_total + tax_total
|
total = base_total + tax_total
|
||||||
|
|
||||||
billing_addresses = CustomerAddress.objects.filter(
|
if request.user.is_authenticated:
|
||||||
user=request.user, address_type=CustomerAddress.Types.BILLING
|
billing_address = CustomerAddress.objects.filter(
|
||||||
)
|
user=request.user, address_type=CustomerAddress.Types.BILLING, default=True
|
||||||
shipping_addresses = CustomerAddress.objects.filter(
|
).first()
|
||||||
user=request.user, address_type=CustomerAddress.Types.SHIPPING
|
shipping_address = CustomerAddress.objects.filter(
|
||||||
)
|
user=request.user,
|
||||||
|
address_type=CustomerAddress.Types.SHIPPING,
|
||||||
|
).first()
|
||||||
|
else:
|
||||||
|
billing_address = None
|
||||||
|
shipping_address = None
|
||||||
|
|
||||||
shipping_methods = ShippingMethod.objects.all()
|
shipping_methods = ShippingMethod.objects.all()
|
||||||
|
|
||||||
response = render(
|
response = render(
|
||||||
@@ -108,8 +114,8 @@ def cart(request, *args, **kwargs):
|
|||||||
"total": total,
|
"total": total,
|
||||||
"base_total": base_total,
|
"base_total": base_total,
|
||||||
"tax_total": tax_total,
|
"tax_total": tax_total,
|
||||||
"billing_addresses": billing_addresses,
|
"billing_address": billing_address,
|
||||||
"shipping_addresses": shipping_addresses,
|
"shipping_address": shipping_address,
|
||||||
"shipping_methods": shipping_methods,
|
"shipping_methods": shipping_methods,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+44
-7
@@ -11,6 +11,8 @@ from shop.models import (
|
|||||||
WishlistedProduct,
|
WishlistedProduct,
|
||||||
CustomerAddress,
|
CustomerAddress,
|
||||||
ShippingMethod,
|
ShippingMethod,
|
||||||
|
Order,
|
||||||
|
OrderLine,
|
||||||
)
|
)
|
||||||
from shop.utils import create_order_from_cart
|
from shop.utils import create_order_from_cart
|
||||||
from web.forms import CreateOrderForm
|
from web.forms import CreateOrderForm
|
||||||
@@ -88,24 +90,58 @@ class CartDetail(TemplateView, CreateView):
|
|||||||
context.update({"errors": form.errors})
|
context.update({"errors": form.errors})
|
||||||
return self.render_to_response(context)
|
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")
|
shipping_method_id = form.cleaned_data.get("shipping_method")
|
||||||
|
|
||||||
cart, created = get_or_create_cart(request)
|
cart, created = get_or_create_cart(request)
|
||||||
|
|
||||||
billing_address = get_object_or_404(CustomerAddress, pk=billing_address_id)
|
billing_address_full_name = form.cleaned_data.get('billing_address_full_name')
|
||||||
shipping_address = get_object_or_404(CustomerAddress, pk=shipping_address_id)
|
billing_address_address = form.cleaned_data.get('billing_address')
|
||||||
|
billing_address_town = form.cleaned_data.get('billing_address_town')
|
||||||
|
billing_address_state = form.cleaned_data.get('billing_address_state')
|
||||||
|
billing_address_country = form.cleaned_data.get('billing_address_country')
|
||||||
|
billing_address_zip = form.cleaned_data.get('billing_address_zip')
|
||||||
|
|
||||||
|
shipping_address_full_name = form.cleaned_data.get('shipping_address_full_name')
|
||||||
|
shipping_address_address = form.cleaned_data.get('shipping_address')
|
||||||
|
shipping_address_town = form.cleaned_data.get('shipping_address_town')
|
||||||
|
shipping_address_state = form.cleaned_data.get('shipping_address_state')
|
||||||
|
shipping_address_country = form.cleaned_data.get('shipping_address_country')
|
||||||
|
shipping_address_zip = form.cleaned_data.get('shipping_address_zip')
|
||||||
|
shipping_address_phone = form.cleaned_data.get('shipping_address_phone')
|
||||||
|
|
||||||
shipping_method = get_object_or_404(ShippingMethod, pk=shipping_method_id)
|
shipping_method = get_object_or_404(ShippingMethod, pk=shipping_method_id)
|
||||||
|
|
||||||
order = create_order_from_cart(
|
order = create_order_from_cart(
|
||||||
cart=cart,
|
cart=cart,
|
||||||
billing_address=billing_address,
|
|
||||||
shipping_address=shipping_address,
|
|
||||||
shipping_method=shipping_method,
|
shipping_method=shipping_method,
|
||||||
|
billing_address_full_name=billing_address_full_name,
|
||||||
|
billing_address_address=billing_address_address,
|
||||||
|
billing_address_town=billing_address_town,
|
||||||
|
billing_address_state=billing_address_state,
|
||||||
|
billing_address_country=billing_address_country,
|
||||||
|
billing_address_zip=billing_address_zip,
|
||||||
|
shipping_address_full_name=shipping_address_full_name,
|
||||||
|
shipping_address_address=shipping_address_address,
|
||||||
|
shipping_address_town=shipping_address_town,
|
||||||
|
shipping_address_state=shipping_address_state,
|
||||||
|
shipping_address_country=shipping_address_country,
|
||||||
|
shipping_address_zip=shipping_address_zip,
|
||||||
|
shipping_address_phone=shipping_address_phone,
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect(reverse("web:order", kwargs={"pk": order.pk}))
|
return redirect(reverse("web:order", kwargs={"uuid": order.uuid}))
|
||||||
|
|
||||||
|
|
||||||
|
class OrderDetail(TemplateView):
|
||||||
|
template_name = "web/order.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
order = get_object_or_404(Order, uuid=kwargs.get("uuid"))
|
||||||
|
items = OrderLine.objects.filter(order=order)
|
||||||
|
return {
|
||||||
|
"order": order,
|
||||||
|
"items": items,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class WishlistView(TemplateView):
|
class WishlistView(TemplateView):
|
||||||
@@ -237,4 +273,5 @@ index = IndexView.as_view()
|
|||||||
category_view = CategoryView.as_view()
|
category_view = CategoryView.as_view()
|
||||||
product_detail = ProductDetail.as_view()
|
product_detail = ProductDetail.as_view()
|
||||||
cart_detail = CartDetail.as_view()
|
cart_detail = CartDetail.as_view()
|
||||||
|
order_detail = OrderDetail.as_view()
|
||||||
wishlist = WishlistView.as_view()
|
wishlist = WishlistView.as_view()
|
||||||
|
|||||||
Reference in New Issue
Block a user