feat: improve customer address views
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from config.settings.environ import * # noqa
|
|
||||||
from django.utils.text import gettext_lazy as _
|
from django.utils.text import gettext_lazy as _
|
||||||
|
|
||||||
|
from config.settings.environ import * # noqa
|
||||||
|
|
||||||
APP_NAME = "Shop"
|
APP_NAME = "Shop"
|
||||||
DESCRIPTION = ""
|
DESCRIPTION = ""
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.1.0"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
|
|
||||||
|
<link rel="manifest" href="{% url 'web:manifest_json' %}">
|
||||||
|
|
||||||
{% include "theme/seo.html" %}
|
{% include "theme/seo.html" %}
|
||||||
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
|
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-19 10:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("web", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="websettings",
|
||||||
|
name="logo_240",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="websettings",
|
||||||
|
name="logo_256",
|
||||||
|
field=models.ImageField(
|
||||||
|
blank=True, null=True, upload_to="assets", verbose_name="logo 256x256"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
+45
-1
@@ -1,6 +1,9 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.utils.text import gettext_lazy as _
|
from django import forms
|
||||||
|
from django.db import models
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
||||||
|
|
||||||
class FilteredQuerysetMixin:
|
class FilteredQuerysetMixin:
|
||||||
@@ -55,3 +58,44 @@ class StylingMixin:
|
|||||||
"placeholder": self.placeholder_for_field.get(field_name),
|
"placeholder": self.placeholder_for_field.get(field_name),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReadUpdateView(TemplateView):
|
||||||
|
model: models.Model = None
|
||||||
|
form_class: forms.ModelForm = None
|
||||||
|
template_name: str = ""
|
||||||
|
ok_template_name: str = ""
|
||||||
|
error_template_name: str = ""
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
return self.model
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
cls = self.get_model()
|
||||||
|
return get_object_or_404(cls, pk=pk)
|
||||||
|
|
||||||
|
def get_initial_values(self, instance):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
form = self.form_class(initial=self.get_initial_values(instance))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"form": form,
|
||||||
|
}
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = self.form_class(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
instance = form.save()
|
||||||
|
return render(request, self.ok_template_name, {
|
||||||
|
"form": form,
|
||||||
|
"instance": instance,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(request, self.error_template_name, {
|
||||||
|
"form": form,
|
||||||
|
})
|
||||||
|
|||||||
+23
-2
@@ -1,5 +1,9 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.text import gettext_lazy as _
|
from django.utils.text import gettext_lazy as _
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from config.models import SingletonModel
|
from config.models import SingletonModel
|
||||||
|
|
||||||
@@ -75,13 +79,30 @@ class WebSettings(SingletonModel):
|
|||||||
theme_color = models.CharField(
|
theme_color = models.CharField(
|
||||||
max_length=9, default="", blank=True, null=True, verbose_name=_("color de tema")
|
max_length=9, default="", blank=True, null=True, verbose_name=_("color de tema")
|
||||||
)
|
)
|
||||||
logo_240 = models.ImageField(
|
|
||||||
upload_to="assets", blank=True, null=True, verbose_name=_("logo 240x240")
|
logo_256 = models.ImageField(
|
||||||
|
upload_to="assets", blank=True, null=True, verbose_name=_("logo 256x256")
|
||||||
)
|
)
|
||||||
logo_128 = models.ImageField(
|
logo_128 = models.ImageField(
|
||||||
upload_to="assets", blank=True, null=True, verbose_name=_("logo 128x128")
|
upload_to="assets", blank=True, null=True, verbose_name=_("logo 128x128")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_resized_logo(self, size):
|
||||||
|
width, height = size
|
||||||
|
image = Image.open(self.logo)
|
||||||
|
image = image.resize(size, Image.Resampling.LANCZOS)
|
||||||
|
buffer = BytesIO()
|
||||||
|
image.save(fp=buffer, format="PNG")
|
||||||
|
|
||||||
|
return ContentFile(buffer.getvalue(), name=f"logo_{width}.png")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.logo:
|
||||||
|
self.logo_128 = self.get_resized_logo((128, 128))
|
||||||
|
self.logo_256 = self.get_resized_logo((256, 256))
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("ajustes de la web")
|
verbose_name = _("ajustes de la web")
|
||||||
verbose_name_plural = _("ajustes de la web")
|
verbose_name_plural = _("ajustes de la web")
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
<span class="text-gray-500 dark:text-gray-400">{% translate 'No hay ninguna dirección' %}</span>
|
<span class="text-gray-500 dark:text-gray-400">{% translate 'No hay ninguna dirección' %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include 'components/users/retrieve_update_address.html' %}
|
<div hx-get="{% url 'web:list_customer_addresses_component' %}" hx-trigger="load, updated_addresses">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ class TestLogin(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == f'{reverse("web:index")}?next={reverse("web:my_account")}'
|
assert (
|
||||||
|
response.url == f'{reverse("web:index")}?next={reverse("web:my_account")}'
|
||||||
|
)
|
||||||
|
|
||||||
response = self.client.get(response.url)
|
response = self.client.get(response.url)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
|||||||
@@ -16,11 +16,8 @@ class TestManifest(TestCase):
|
|||||||
os.path.join(settings.BASE_DIR, "web", "tests", "images", "trolley.png"),
|
os.path.join(settings.BASE_DIR, "web", "tests", "images", "trolley.png"),
|
||||||
"rb",
|
"rb",
|
||||||
) as f:
|
) as f:
|
||||||
file_128 = SimpleUploadedFile("logo_128.png", f.read())
|
file = SimpleUploadedFile("logo.png", f.read())
|
||||||
self.settings.logo_128 = file_128
|
self.settings.logo = file
|
||||||
file_240 = SimpleUploadedFile("logo_240.png", f.read())
|
|
||||||
self.settings.logo_240 = file_240
|
|
||||||
self.settings.logo = file_240
|
|
||||||
self.settings.save()
|
self.settings.save()
|
||||||
|
|
||||||
def test_manifest_view(self):
|
def test_manifest_view(self):
|
||||||
|
|||||||
+16
-3
@@ -6,10 +6,12 @@ from django.utils.text import gettext_lazy as _
|
|||||||
from web.views.components import (
|
from web.views.components import (
|
||||||
cart,
|
cart,
|
||||||
cart_dropdown,
|
cart_dropdown,
|
||||||
|
list_customer_addresses_component,
|
||||||
list_products,
|
list_products,
|
||||||
list_wishlisted_products,
|
list_wishlisted_products,
|
||||||
|
update_customer_address,
|
||||||
|
user_info_component,
|
||||||
wishlist_button,
|
wishlist_button,
|
||||||
user_info_component, update_customer_address,
|
|
||||||
)
|
)
|
||||||
from web.views.users import (
|
from web.views.users import (
|
||||||
login,
|
login,
|
||||||
@@ -39,7 +41,9 @@ app_name = "web"
|
|||||||
|
|
||||||
i18n_resolvers = i18n_patterns(
|
i18n_resolvers = i18n_patterns(
|
||||||
path("", index, name="index"),
|
path("", index, name="index"),
|
||||||
path(_("wishlist"), login_required(wishlist, login_url=_("/login/")), name="wishlist"),
|
path(
|
||||||
|
_("wishlist"), login_required(wishlist, login_url=_("/login/")), name="wishlist"
|
||||||
|
),
|
||||||
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"),
|
||||||
@@ -96,7 +100,16 @@ urlpatterns = [
|
|||||||
delete_from_wishlist,
|
delete_from_wishlist,
|
||||||
name="delete_from_wishlist",
|
name="delete_from_wishlist",
|
||||||
),
|
),
|
||||||
path("web/customer-address/<int:pk>/", update_customer_address, name="update_customer_address"),
|
path(
|
||||||
|
"web/customer-address/<int:pk>/",
|
||||||
|
update_customer_address,
|
||||||
|
name="update_customer_address",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"web/customer-addresses/",
|
||||||
|
list_customer_addresses_component,
|
||||||
|
name="list_customer_addresses_component",
|
||||||
|
),
|
||||||
# manifest
|
# manifest
|
||||||
path("manifest.json", manifest, name="manifest_json"),
|
path("manifest.json", manifest, name="manifest_json"),
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-3
@@ -77,7 +77,7 @@ def cart_dropdown(request, *args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid)
|
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ def cart(request, *args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid)
|
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -179,13 +179,22 @@ class UserInfoComponentView(TemplateView):
|
|||||||
return render(request, self.template_name, {"form": form})
|
return render(request, self.template_name, {"form": form})
|
||||||
|
|
||||||
|
|
||||||
|
class ListCustomerAddressComponent(TemplateView):
|
||||||
|
template_name = "components/users/retrieve_update_address.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return {
|
||||||
|
"customer_addresses": CustomerAddress.objects.filter(user=self.request.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def update_customer_address(request, pk):
|
def update_customer_address(request, pk):
|
||||||
customer_address = get_object_or_404(CustomerAddress, pk=pk, user=request.user)
|
customer_address = get_object_or_404(CustomerAddress, pk=pk, user=request.user)
|
||||||
form = CustomerAddressForm(request.POST, instance=customer_address)
|
form = CustomerAddressForm(request.POST, instance=customer_address)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return HttpResponse(status=200)
|
return HttpResponse(status=200, headers={"HX-Trigger": "updated_addresses"})
|
||||||
|
|
||||||
return HttpResponse(form.errors, status=400)
|
return HttpResponse(form.errors, status=400)
|
||||||
|
|
||||||
@@ -195,3 +204,4 @@ list_wishlisted_products = ListWishlistedProducts.as_view()
|
|||||||
wishlist_button = WishlistButton.as_view()
|
wishlist_button = WishlistButton.as_view()
|
||||||
|
|
||||||
user_info_component = UserInfoComponentView.as_view()
|
user_info_component = UserInfoComponentView.as_view()
|
||||||
|
list_customer_addresses_component = ListCustomerAddressComponent.as_view()
|
||||||
|
|||||||
+7
-3
@@ -6,7 +6,11 @@ from django.contrib.auth.tokens import default_token_generator
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.shortcuts import redirect, render, reverse
|
from django.shortcuts import redirect, render, reverse
|
||||||
from django.utils.http import url_has_allowed_host_and_scheme, urlsafe_base64_decode, urlencode
|
from django.utils.http import (
|
||||||
|
url_has_allowed_host_and_scheme,
|
||||||
|
urlencode,
|
||||||
|
urlsafe_base64_decode,
|
||||||
|
)
|
||||||
from django.utils.text import gettext_lazy as _
|
from django.utils.text import gettext_lazy as _
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
|
|
||||||
@@ -30,10 +34,10 @@ class LoginView(View, RedirectionMixin):
|
|||||||
login_form_class = LoginForm
|
login_form_class = LoginForm
|
||||||
|
|
||||||
def get_redirection(self):
|
def get_redirection(self):
|
||||||
next_page = self.request.GET.get('next', '')
|
next_page = self.request.GET.get("next", "")
|
||||||
|
|
||||||
if next_page:
|
if next_page:
|
||||||
return f'{reverse(self.redirect_to)}?next={next_page}'
|
return f"{reverse(self.redirect_to)}?next={next_page}"
|
||||||
|
|
||||||
return reverse(self.redirect_to)
|
return reverse(self.redirect_to)
|
||||||
|
|
||||||
|
|||||||
+17
-7
@@ -36,8 +36,8 @@ class IndexView(TemplateView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if self.request.GET.get('next'):
|
if self.request.GET.get("next"):
|
||||||
return redirect(self.request.GET.get('next'))
|
return redirect(self.request.GET.get("next"))
|
||||||
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ def add_cart_item(request, *args, **kwargs):
|
|||||||
response = HttpResponse(status=201, headers={"HX-Trigger": "updated-cart"})
|
response = HttpResponse(status=201, headers={"HX-Trigger": "updated-cart"})
|
||||||
|
|
||||||
if created and request.user.is_anonymous:
|
if created and request.user.is_anonymous:
|
||||||
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid)
|
response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict")
|
||||||
|
|
||||||
# Comprobamos si existe una línea de carrito para ese carrito de ese producto
|
# Comprobamos si existe una línea de carrito para ese carrito de ese producto
|
||||||
existing_cart_item = CartItem.objects.filter(cart=cart, product=product).first()
|
existing_cart_item = CartItem.objects.filter(cart=cart, product=product).first()
|
||||||
@@ -309,11 +309,21 @@ def manifest(request, *args, **kwargs):
|
|||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.logo_240:
|
if settings.logo:
|
||||||
data["icons"].append(
|
data["icons"].append(
|
||||||
{
|
{
|
||||||
"src": settings.logo_240.url,
|
"src": settings.logo.url,
|
||||||
"sizes": "240x240",
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable any",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.logo_256:
|
||||||
|
data["icons"].append(
|
||||||
|
{
|
||||||
|
"src": settings.logo_256.url,
|
||||||
|
"sizes": "256x256",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable any",
|
"purpose": "maskable any",
|
||||||
}
|
}
|
||||||
@@ -323,7 +333,7 @@ def manifest(request, *args, **kwargs):
|
|||||||
data["icons"].append(
|
data["icons"].append(
|
||||||
{
|
{
|
||||||
"src": settings.logo_128.url,
|
"src": settings.logo_128.url,
|
||||||
"sizes": "240x240",
|
"sizes": "128x128",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable any",
|
"purpose": "maskable any",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user