From bee35ac5cb10a565dc8c81c9b1356393d3baa3ff Mon Sep 17 00:00:00 2001 From: aserrador Date: Sat, 8 Aug 2026 14:20:07 -0500 Subject: [PATCH] #51 feat: add editable store logo to StoreSettings --- tienda_ilusion/config/settings/base.py | 1 + .../don_confiao/api/store_settings.py | 8 +- .../migrations/0051_storesettings_logo.py | 18 ++++ .../don_confiao/models/store_settings.py | 1 + .../don_confiao/serializers/store_settings.py | 14 ++- .../don_confiao/tests/test_store_settings.py | 89 +++++++++++++++++++ 6 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 tienda_ilusion/don_confiao/migrations/0051_storesettings_logo.py diff --git a/tienda_ilusion/config/settings/base.py b/tienda_ilusion/config/settings/base.py index c11cf08..78a0422 100644 --- a/tienda_ilusion/config/settings/base.py +++ b/tienda_ilusion/config/settings/base.py @@ -153,6 +153,7 @@ CATALOGUE_BACKGROUND_IMAGES_RGBA = os.environ.get( _rgba_parts = CATALOGUE_BACKGROUND_IMAGES_RGBA.split(",") CATALOGUE_BACKGROUND_IMAGES_RGBA = tuple(int(p.strip()) for p in _rgba_parts) CATALOGUE_MAX_UPLOAD_SIZE = int(os.environ.get("CATALOGUE_MAX_UPLOAD_SIZE", str(5 * 1024 * 1024))) +LOGO_MAX_UPLOAD_SIZE = int(os.environ.get("LOGO_MAX_UPLOAD_SIZE", str(5 * 1024 * 1024))) # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/tienda_ilusion/don_confiao/api/store_settings.py b/tienda_ilusion/don_confiao/api/store_settings.py index dc1c585..59ec714 100644 --- a/tienda_ilusion/don_confiao/api/store_settings.py +++ b/tienda_ilusion/don_confiao/api/store_settings.py @@ -16,12 +16,16 @@ class StoreSettingsView(APIView): def get(self, request): settings = StoreSettings.get_singleton() - return Response(StoreSettingsSerializer(settings).data) + return Response( + StoreSettingsSerializer( + settings, context={"request": request} + ).data + ) def patch(self, request): settings = StoreSettings.get_singleton() serializer = StoreSettingsSerializer( - settings, data=request.data, partial=True + settings, data=request.data, partial=True, context={"request": request} ) serializer.is_valid(raise_exception=True) serializer.save() diff --git a/tienda_ilusion/don_confiao/migrations/0051_storesettings_logo.py b/tienda_ilusion/don_confiao/migrations/0051_storesettings_logo.py new file mode 100644 index 0000000..252246d --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0051_storesettings_logo.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2026-08-08 19:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0050_storesettings'), + ] + + operations = [ + migrations.AddField( + model_name='storesettings', + name='logo', + field=models.ImageField(blank=True, null=True, upload_to='logos/'), + ), + ] diff --git a/tienda_ilusion/don_confiao/models/store_settings.py b/tienda_ilusion/don_confiao/models/store_settings.py index d7fe4dd..1c968be 100644 --- a/tienda_ilusion/don_confiao/models/store_settings.py +++ b/tienda_ilusion/don_confiao/models/store_settings.py @@ -5,6 +5,7 @@ class StoreSettings(models.Model): address = models.TextField() latitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(null=True, blank=True) + logo = models.ImageField(upload_to="logos/", null=True, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: diff --git a/tienda_ilusion/don_confiao/serializers/store_settings.py b/tienda_ilusion/don_confiao/serializers/store_settings.py index 20ac4b8..28a585d 100644 --- a/tienda_ilusion/don_confiao/serializers/store_settings.py +++ b/tienda_ilusion/don_confiao/serializers/store_settings.py @@ -1,3 +1,4 @@ +from django.conf import settings from rest_framework import serializers from ..models.store_settings import StoreSettings @@ -6,5 +7,16 @@ from ..models.store_settings import StoreSettings class StoreSettingsSerializer(serializers.ModelSerializer): class Meta: model = StoreSettings - fields = ["id", "address", "latitude", "longitude", "updated_at"] + fields = ["id", "address", "latitude", "longitude", "logo", "updated_at"] read_only_fields = ["id", "updated_at"] + + def validate_logo(self, value): + if value is None: + return value + max_size = settings.LOGO_MAX_UPLOAD_SIZE + if value.size > max_size: + raise serializers.ValidationError( + f"Logo size exceeds the maximum allowed size of " + f"{max_size // (1024 * 1024)}MB." + ) + return value diff --git a/tienda_ilusion/don_confiao/tests/test_store_settings.py b/tienda_ilusion/don_confiao/tests/test_store_settings.py index 1795987..f677ee3 100644 --- a/tienda_ilusion/don_confiao/tests/test_store_settings.py +++ b/tienda_ilusion/don_confiao/tests/test_store_settings.py @@ -1,4 +1,8 @@ +import base64 + from django.contrib.auth.models import User +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import override_settings from rest_framework import status from rest_framework.test import APIClient, APITestCase from rest_framework_simplejwt.tokens import RefreshToken @@ -8,6 +12,11 @@ from .Mixins import LoginMixin URL = "/don_confiao/api/store_settings" +PNG_1X1 = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk" + "+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" +) + class TestStoreSettingsModel(APITestCase, LoginMixin): def test_singleton_returns_single_instance(self): @@ -97,6 +106,7 @@ class TestStoreSettingsAPI(APITestCase, LoginMixin): self.assertIn("address", data) self.assertIn("latitude", data) self.assertIn("longitude", data) + self.assertIn("logo", data) self.assertIn("updated_at", data) def test_patch_updates_settings(self): @@ -155,3 +165,82 @@ class TestStoreSettingsAPI(APITestCase, LoginMixin): format="json", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_patch_upload_logo(self): + self.client.get(self.url) + logo = SimpleUploadedFile("logo.png", PNG_1X1, content_type="image/png") + response = self.client.patch( + self.url, + {"logo": logo}, + format="multipart", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() + self.assertIn("/media/logos/", data["logo"]) + self.assertTrue(data["logo"].startswith("http")) + + settings = StoreSettings.objects.get(pk=1) + self.assertIsNotNone(settings.logo) + self.assertTrue(settings.logo.name.startswith("logos/logo")) + + def test_get_returns_absolute_logo_url(self): + self.client.get(self.url) + logo = SimpleUploadedFile("logo.png", PNG_1X1, content_type="image/png") + self.client.patch(self.url, {"logo": logo}, format="multipart") + response = self.client.get(self.url) + data = response.json() + self.assertTrue(data["logo"].startswith("http")) + + def test_patch_logo_non_image(self): + self.client.get(self.url) + fake = SimpleUploadedFile("logo.txt", b"not an image", content_type="text/plain") + response = self.client.patch( + self.url, + {"logo": fake}, + format="multipart", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + @override_settings(LOGO_MAX_UPLOAD_SIZE=10) + def test_patch_logo_too_large(self): + self.client.get(self.url) + logo = SimpleUploadedFile("logo.png", PNG_1X1, content_type="image/png") + response = self.client.patch( + self.url, + {"logo": logo}, + format="multipart", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + settings = StoreSettings.objects.get(pk=1) + self.assertFalse(settings.logo) + + def test_patch_clear_logo(self): + self.client.get(self.url) + logo = SimpleUploadedFile("logo.png", PNG_1X1, content_type="image/png") + self.client.patch(self.url, {"logo": logo}, format="multipart") + response = self.client.patch( + self.url, + {"logo": None}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() + self.assertIsNone(data["logo"]) + settings = StoreSettings.objects.get(pk=1) + self.assertFalse(settings.logo) + + def test_patch_upload_logo_keeps_other_fields(self): + self.client.get(self.url) + logo = SimpleUploadedFile("logo.png", PNG_1X1, content_type="image/png") + response = self.client.patch( + self.url, + { + "address": "Avenida Siempre Viva 742", + "logo": logo, + }, + format="multipart", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + settings = StoreSettings.objects.get(pk=1) + self.assertEqual(settings.address, "Avenida Siempre Viva 742") + self.assertIsNotNone(settings.logo)