diff --git a/shop/migrations/0001_initial.py b/shop/migrations/0001_initial.py index aa3e8c2..77c10dc 100644 --- a/shop/migrations/0001_initial.py +++ b/shop/migrations/0001_initial.py @@ -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")}, + }, + ), ] diff --git a/shop/migrations/0002_wishlistedproduct.py b/shop/migrations/0002_wishlistedproduct.py deleted file mode 100644 index 3134301..0000000 --- a/shop/migrations/0002_wishlistedproduct.py +++ /dev/null @@ -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", - }, - ), - ] diff --git a/shop/models.py b/shop/models.py index 1eda9c0..9bd07b0 100644 --- a/shop/models.py +++ b/shop/models.py @@ -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", + ) diff --git a/shop/utils.py b/shop/utils.py index ceb6f0d..701e0b7 100644 --- a/shop/utils.py +++ b/shop/utils.py @@ -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( diff --git a/web/forms.py b/web/forms.py index 3bfdf87..9f412c1 100644 --- a/web/forms.py +++ b/web/forms.py @@ -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() diff --git a/web/templates/components/cart/cart.html b/web/templates/components/cart/cart.html index 55ff8dc..4268d2d 100644 --- a/web/templates/components/cart/cart.html +++ b/web/templates/components/cart/cart.html @@ -1,139 +1,193 @@ {% load i18n %}
-
-

{% translate 'Carrito' %}

+
+ {% csrf_token %} +
+

{% translate 'Carrito' %}

-
-
-
+
+
+
- {% for item in items %} -
-
- - {{ item.product.name }} - - + {% for item in items %} +
+
+ + {{ item.product.name }} + + - -
-
+ +
+
{{ item.quantity }}x +
+
+

{{ item.product.price.price_with_tax }} + €

+
-
-

{{ item.product.price.price_with_tax }} - €

-
-
-
- - {{ item.product.name }} - +
+ + {{ item.product.name }} + -
- - {% csrf_token %} - - +
+
+ {% csrf_token %} + +
+
-
- {% endfor %} + {% endfor %} - {% if items.count == 0 %} - - {% translate 'No hay nada en el carrito...' %} - - {% endif %} -
-
+ {% if items.count == 0 %} +

+ {% translate 'No hay nada en el carrito...' %} +

+

+ + {% translate 'Volver a inicio' %} + +

+ {% endif %} -
-
-

{% translate 'Resumen del pedido' %}

+
+

+ {% translate 'Dirección de envío' %} +

-
-
-
-
{% translate 'Base imponible' %}
-
{{ base_total }} €
-
+ {% for address in shipping_addresses %} + + + {% endfor %} +
-
-
{% translate 'Descuentos' %}
-
-{{ savings }} €
-
+
+

+ {% translate 'Dirección de facturación' %} +

-
-
{% translate 'Gastos de envío' %}
-
{{ shipping_cost }} €
-
+ {% for address in billing_addresses %} + + + {% endfor %} +
-
-
{% translate 'Impuestos' %}
-
{{ tax_total }} €
-
-
+
+

+ {% translate 'Opciones de envío' %} +

-
-
{% translate 'Total' %}
-
{{ total }} €
-
+ {% for shipping_method in shipping_methods %} + + + {% endfor %} +
-
+
+ + +
+
+

{% translate 'Resumen del pedido' %}

+ +
+
+
+
{% translate 'Base imponible' %}
+
{{ base_total }} €
+
+ +
+
{% translate 'Descuentos' %}
+
-{{ savings }} €
+
+ +
+
{% translate 'Gastos de envío' %}
+
{{ shipping_cost }} €
+
+ +
+
{% translate 'Impuestos' %}
+
{{ tax_total }} €
+
+
+ +
+
{% translate 'Total' %}
+
{{ total }} €
+
+
+ - - -
- {# #} - {#
#} - {#
#} - {# #} - {# #} - {#
#} - {# #} - {#
#} - {#
#} + {# #} + {#
#} + {#
#} + {# #} + {# #} + {#
#} + {# #} + {#
#} + {#
#} +
+
-
+
\ No newline at end of file diff --git a/web/templates/components/cart/cart_navbar.html b/web/templates/components/cart/cart_navbar.html index bdafb66..bb44bd8 100644 --- a/web/templates/components/cart/cart_navbar.html +++ b/web/templates/components/cart/cart_navbar.html @@ -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' %} {% endif %} diff --git a/web/views/components.py b/web/views/components.py index 3a471f0..0da0f6c 100644 --- a/web/views/components.py +++ b/web/views/components.py @@ -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, }, ) diff --git a/web/views/web.py b/web/views/web.py index cf20d35..e97d93b 100644 --- a/web/views/web.py +++ b/web/views/web.py @@ -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"