diff --git a/config/settings/base.py b/config/settings/base.py
index 5b36c59..4169e42 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -1,8 +1,9 @@
import datetime
-from config.settings.environ import * # noqa
from django.utils.text import gettext_lazy as _
+from config.settings.environ import * # noqa
+
APP_NAME = "Shop"
DESCRIPTION = ""
VERSION = "0.1.0"
diff --git a/theme/templates/theme/base.html b/theme/templates/theme/base.html
index 6120cb0..47cb67d 100644
--- a/theme/templates/theme/base.html
+++ b/theme/templates/theme/base.html
@@ -6,6 +6,8 @@
{{ title }}
+
+
{% include "theme/seo.html" %}
diff --git a/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py b/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py
new file mode 100644
index 0000000..92d8afd
--- /dev/null
+++ b/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py
@@ -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"
+ ),
+ ),
+ ]
diff --git a/web/mixins.py b/web/mixins.py
index 7ba84fd..d75c4c8 100644
--- a/web/mixins.py
+++ b/web/mixins.py
@@ -1,6 +1,9 @@
from django.conf import settings
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:
@@ -55,3 +58,44 @@ class StylingMixin:
"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,
+ })
diff --git a/web/models.py b/web/models.py
index e6cdcbc..738ec24 100644
--- a/web/models.py
+++ b/web/models.py
@@ -1,5 +1,9 @@
+from io import BytesIO
+
+from django.core.files.base import ContentFile
from django.db import models
from django.utils.text import gettext_lazy as _
+from PIL import Image
from config.models import SingletonModel
@@ -75,13 +79,30 @@ class WebSettings(SingletonModel):
theme_color = models.CharField(
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(
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:
verbose_name = _("ajustes de la web")
verbose_name_plural = _("ajustes de la web")
diff --git a/web/templates/users/user_details.html b/web/templates/users/user_details.html
index c15a66c..cbdc187 100644
--- a/web/templates/users/user_details.html
+++ b/web/templates/users/user_details.html
@@ -31,7 +31,9 @@
{% translate 'No hay ninguna dirección' %}
{% endif %}
- {% include 'components/users/retrieve_update_address.html' %}
+
+
+
diff --git a/web/tests/test_login.py b/web/tests/test_login.py
index 12d84ba..e53dd88 100644
--- a/web/tests/test_login.py
+++ b/web/tests/test_login.py
@@ -122,7 +122,9 @@ class TestLogin(TestCase):
)
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)
assert response.status_code == 302
diff --git a/web/tests/test_manifest.py b/web/tests/test_manifest.py
index f6fe1dd..4ce126a 100644
--- a/web/tests/test_manifest.py
+++ b/web/tests/test_manifest.py
@@ -16,11 +16,8 @@ class TestManifest(TestCase):
os.path.join(settings.BASE_DIR, "web", "tests", "images", "trolley.png"),
"rb",
) as f:
- file_128 = SimpleUploadedFile("logo_128.png", f.read())
- self.settings.logo_128 = file_128
- file_240 = SimpleUploadedFile("logo_240.png", f.read())
- self.settings.logo_240 = file_240
- self.settings.logo = file_240
+ file = SimpleUploadedFile("logo.png", f.read())
+ self.settings.logo = file
self.settings.save()
def test_manifest_view(self):
diff --git a/web/urls.py b/web/urls.py
index f9f380f..a918c08 100644
--- a/web/urls.py
+++ b/web/urls.py
@@ -6,10 +6,12 @@ from django.utils.text import gettext_lazy as _
from web.views.components import (
cart,
cart_dropdown,
+ list_customer_addresses_component,
list_products,
list_wishlisted_products,
+ update_customer_address,
+ user_info_component,
wishlist_button,
- user_info_component, update_customer_address,
)
from web.views.users import (
login,
@@ -39,7 +41,9 @@ app_name = "web"
i18n_resolvers = i18n_patterns(
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//"), category_view, name="category_view"),
path(_("products///"), product_detail, name="product_detail"),
path(_("cart/"), cart_detail, name="cart_detail"),
@@ -96,7 +100,16 @@ urlpatterns = [
delete_from_wishlist,
name="delete_from_wishlist",
),
- path("web/customer-address//", update_customer_address, name="update_customer_address"),
+ path(
+ "web/customer-address//",
+ update_customer_address,
+ name="update_customer_address",
+ ),
+ path(
+ "web/customer-addresses/",
+ list_customer_addresses_component,
+ name="list_customer_addresses_component",
+ ),
# manifest
path("manifest.json", manifest, name="manifest_json"),
]
diff --git a/web/views/components.py b/web/views/components.py
index ddbd7be..647cbbc 100644
--- a/web/views/components.py
+++ b/web/views/components.py
@@ -77,7 +77,7 @@ def cart_dropdown(request, *args, **kwargs):
)
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
@@ -127,7 +127,7 @@ def cart(request, *args, **kwargs):
)
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
@@ -179,13 +179,22 @@ class UserInfoComponentView(TemplateView):
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):
customer_address = get_object_or_404(CustomerAddress, pk=pk, user=request.user)
form = CustomerAddressForm(request.POST, instance=customer_address)
if form.is_valid():
form.save()
- return HttpResponse(status=200)
+ return HttpResponse(status=200, headers={"HX-Trigger": "updated_addresses"})
return HttpResponse(form.errors, status=400)
@@ -195,3 +204,4 @@ list_wishlisted_products = ListWishlistedProducts.as_view()
wishlist_button = WishlistButton.as_view()
user_info_component = UserInfoComponentView.as_view()
+list_customer_addresses_component = ListCustomerAddressComponent.as_view()
diff --git a/web/views/users.py b/web/views/users.py
index d66b2fd..9a0241f 100644
--- a/web/views/users.py
+++ b/web/views/users.py
@@ -6,7 +6,11 @@ from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.db import IntegrityError
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.views.generic import TemplateView, View
@@ -30,10 +34,10 @@ class LoginView(View, RedirectionMixin):
login_form_class = LoginForm
def get_redirection(self):
- next_page = self.request.GET.get('next', '')
+ next_page = self.request.GET.get("next", "")
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)
diff --git a/web/views/web.py b/web/views/web.py
index 0d3126a..85b7fea 100644
--- a/web/views/web.py
+++ b/web/views/web.py
@@ -36,8 +36,8 @@ class IndexView(TemplateView):
}
def get(self, request, *args, **kwargs):
- if self.request.GET.get('next'):
- return redirect(self.request.GET.get('next'))
+ if self.request.GET.get("next"):
+ return redirect(self.request.GET.get("next"))
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"})
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
existing_cart_item = CartItem.objects.filter(cart=cart, product=product).first()
@@ -309,11 +309,21 @@ def manifest(request, *args, **kwargs):
"orientation": "portrait",
}
- if settings.logo_240:
+ if settings.logo:
data["icons"].append(
{
- "src": settings.logo_240.url,
- "sizes": "240x240",
+ "src": settings.logo.url,
+ "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",
"purpose": "maskable any",
}
@@ -323,7 +333,7 @@ def manifest(request, *args, **kwargs):
data["icons"].append(
{
"src": settings.logo_128.url,
- "sizes": "240x240",
+ "sizes": "128x128",
"type": "image/png",
"purpose": "maskable any",
}