feat: wishlist

This commit is contained in:
2025-01-02 11:17:25 +01:00
parent 2a51aabc5e
commit eed3905638
8 changed files with 155 additions and 14 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ WORKDIR /code
COPY requirements.txt /code
RUN apt-get update && \
apt-get install -y git nginx gettext vim && \
apt-get install -y gcc git nginx gettext vim libpq-dev && \
pip install -r requirements.txt && \
apt-get autoremove -y
+62
View File
@@ -0,0 +1,62 @@
# 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",
},
),
]
+9
View File
@@ -501,3 +501,12 @@ class ShippingMethod(models.Model):
class Meta:
verbose_name = _("método de envío")
verbose_name_plural = _("métodos de envío")
class WishlistedProduct(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('usuario'))
product = models.ForeignKey('shop.Product', on_delete=models.CASCADE, verbose_name=_('producto'))
class Meta:
verbose_name = _('productos deseados')
verbose_name_plural = _('productos deseados')
@@ -0,0 +1,20 @@
{% load i18n %}
{% if is_wishlisted %}
<form action="" hx-post="{% url 'web:delete_from_wishlist' pk=product.pk %}" hx-swap="none">
<input type="hidden" name="product" value="{{ product.pk }}">
{% csrf_token %}
<button class="flex items-center justify-center py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">
{% include 'components/icons/like.svg' %}
{% translate 'Quitar de la lista de deseados' %}
</button>
</form>
{% else %}
<form action="" hx-post="{% url 'web:add_to_wishlist' %}" hx-swap="none">
<input type="hidden" name="product" value="{{ product.pk }}">
{% csrf_token %}
<button class="flex items-center justify-center py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">
{% include 'components/icons/like.svg' %}
{% translate 'Añadir a lista de deseados' %}
</button>
</form>
{% endif %}
+5 -9
View File
@@ -51,15 +51,11 @@
</div>
<div class="my-4 sm:gap-4 sm:items-center sm:flex sm:m-8">
<a
href="#"
title=""
class="flex items-center justify-center py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
role="button"
>
{% include 'components/icons/like.svg' %}
{% translate 'Añadir a lista de deseados' %}
</a>
{% if user.is_authenticated %}
<div hx-get="{% url 'web:wishlist_button' pk=product.pk %}" hx-trigger="load, updated-wishlist">
</div>
{% endif %}
{% include 'components/cart/add_to_cart.html' %}
+10 -1
View File
@@ -1,11 +1,13 @@
from django.urls import path
from web.views.components import cart, cart_dropdown, list_products
from web.views.components import cart, cart_dropdown, list_products, wishlist_button
from web.views.users import login, logout, my_account, register, reset_password
from web.views.web import (
add_cart_item,
add_to_wishlist,
cart_detail,
delete_cart_item,
delete_from_wishlist,
index,
manifest,
product_detail,
@@ -19,19 +21,26 @@ urlpatterns = [
path("categories/<str:slug>/", category_view, name="category_view"),
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
path("cart/", cart_detail, name="cart_detail"),
# users
path("login/", login, name="login"),
path("logout/", logout, name="logout"),
path("register/", register, name="register"),
path("reset-password/", reset_password, name="reset-password"),
path("my-account/", my_account, name="my_account"),
# components
path("web/components/products/", list_products, name="list_products"),
path("web/components/cart-dropdown/", cart_dropdown, name="cart_dropdown"),
path("web/components/cart/", cart, name="cart"),
path("web/components/wishlist-button/<int:pk>/", wishlist_button, name="wishlist_button"),
# api
path("web/add-cart-item/", add_cart_item, name="add_cart_item"),
path("web/delete-cart-item/<int:pk>/", delete_cart_item, name="delete_cart_item"),
path("web/add-to-wishlist/", add_to_wishlist, name="add_to_wishlist"),
path("web/delete-from-wishlist/<int:pk>/", delete_from_wishlist, name="delete_from_wishlist"),
# manifest
path("manifest.json", manifest, name="manifest_json"),
]
+16 -2
View File
@@ -1,10 +1,10 @@
from decimal import Decimal
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404
from django.views.generic import TemplateView
from shop.filters import ProductFilter
from shop.models import CartItem, Product, ProductPrice
from shop.models import CartItem, Product, ProductPrice, WishlistedProduct
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
@@ -75,4 +75,18 @@ def cart(request, *args, **kwargs):
return response
class WishlistButton(TemplateView):
template_name = 'components/products/wishlist_button.html'
def get_context_data(self, **kwargs):
product = get_object_or_404(Product, pk=self.kwargs.get('pk'))
is_wishlisted = WishlistedProduct.objects.filter(user=self.request.user, product=product).exists()
return {
"is_wishlisted": is_wishlisted,
"product": product,
}
list_products = ListProducts.as_view()
wishlist_button = WishlistButton.as_view()
+32 -1
View File
@@ -4,7 +4,7 @@ from django.utils.text import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from django.views.generic import TemplateView
from shop.models import CartItem, Product, ProductCategory
from shop.models import CartItem, Product, ProductCategory, WishlistedProduct
from web.models import WebSettings
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
@@ -114,6 +114,37 @@ def delete_cart_item(request, pk, *args, **kwargs):
return HttpResponse(status=204, headers={"HX-Trigger": "updated-cart"})
@require_http_methods(
[
"POST",
]
)
def add_to_wishlist(request, *args, **kwargs):
product = get_object_or_404(Product, pk=request.POST.get("product"))
response = HttpResponse(status=201, headers={"HX-Trigger": "updated-wishlist"})
if request.user.is_anonymous:
return HttpResponse(status=400)
WishlistedProduct.objects.get_or_create(
user=request.user,
product=product,
)
return response
@require_http_methods(
[
"POST",
]
)
def delete_from_wishlist(request, pk, *args, **kwargs):
wishlisted_item = get_object_or_404(WishlistedProduct, product_id=pk, user=request.user)
wishlisted_item.delete()
return HttpResponse(status=204, headers={"HX-Trigger": "updated-wishlist"})
def manifest(request, *args, **kwargs):
settings = WebSettings.load()