Compare commits
6 Commits
805565aa8e
...
feat/seria
| Author | SHA1 | Date | |
|---|---|---|---|
| b088794716 | |||
| 177b4c01ca | |||
| ea3727363b | |||
| bee35ac5cb | |||
| 85168e24f0 | |||
| 56bbc8f9b4 |
39
AGENTS.md
39
AGENTS.md
@@ -159,7 +159,44 @@ Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto
|
||||
## Tests
|
||||
- Framework: Django unittest
|
||||
- Directorio: don_confiao/tests/
|
||||
- Ejecutar: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py test`
|
||||
- Ejecutar: `docker compose -f docker-compose.dev.yml run --rm django python manage.py test`
|
||||
|
||||
## Workflow TDD (Semáforo Rojo/Verde)
|
||||
|
||||
Todo cambio o nueva funcionalidad debe seguir TDD. **Está prohibido asumir fallos de tests: todo fallo debe ser confirmado ejecutando los tests.**
|
||||
|
||||
### Regla de oro: confirmar antes de modificar
|
||||
Antes de modificar tests (por cambios en comportamiento o por nuevas funcionalidades), **SIEMPRE ejecutar los tests primero** y confirmar que el fallo esperado realmente ocurre. Nunca asumir que un test falla: verificar el fallo con la ejecución real.
|
||||
|
||||
### Ciclo Rojo → Verde → Refactor
|
||||
1. **ROJO (escribir el test y confirmar que falla)**:
|
||||
- Escribir o modificar el test que describe el comportamiento esperado.
|
||||
- Ejecutar los tests **antes de tocar el código de producción**.
|
||||
- Confirmar que el test falla por el motivo esperado (assertion fallida por el comportamiento nuevo, no por error de setup o de importación).
|
||||
- Copiar el output del fallo como evidencia del semáforo rojo.
|
||||
|
||||
2. **VERDE (implementar lo mínimo para que pase)**:
|
||||
- Implementar el código de producción mínimo necesario para que el test pase.
|
||||
- Volver a ejecutar los tests y confirmar que el semáforo queda en verde.
|
||||
|
||||
3. **REFACTOR (limpiar con los tests en verde)**:
|
||||
- Refactorizar y limpiar el código sin cambiar comportamiento.
|
||||
- Re-ejecutar los tests para confirmar que siguen en verde.
|
||||
|
||||
### Ejecutar tests
|
||||
Siempre ejecutar dentro del contenedor de desarrollo:
|
||||
```bash
|
||||
# Test específico (durante el ciclo, preferible sobre el suite completo para ser rápido)
|
||||
docker compose -f docker-compose.dev.yml run --rm django python manage.py test don_confiao.tests.<archivo>
|
||||
|
||||
# Suite completa (al finalizar, para confirmar que nada se rompió)
|
||||
docker compose -f docker-compose.dev.yml run --rm django python manage.py test
|
||||
```
|
||||
|
||||
### Confirmar el fallo, no asumirlo
|
||||
- Si un test falla o se espera que falle, **ejecutarlo y mostrar el resultado** antes de cambiar cualquier código o test.
|
||||
- Verificar que el fallo corresponde a la razón esperada (leer el traceback), no a un problema de entorno, migraciones, importaciones o fixtures.
|
||||
- No modificar tests para "hacerlos pasar" si el fallo no fue previamente confirmado.
|
||||
|
||||
## Comandos Útiles (dentro del contenedor)
|
||||
- Migraciones: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations && docker-compose -f docker-compose.dev.yml run --rm django python manage.py migrate`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,12 @@ from .models.sales import (
|
||||
)
|
||||
from .models.products import Product, ProductCategory
|
||||
from .models.payments import ReconciliationJar
|
||||
from .models.store_settings import StoreSettings
|
||||
|
||||
|
||||
@admin.register(StoreSettings)
|
||||
class StoreSettingsAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "address", "latitude", "longitude", "updated_at")
|
||||
|
||||
|
||||
@admin.register(Customer)
|
||||
|
||||
@@ -19,6 +19,7 @@ from .payments import (
|
||||
Pagination,
|
||||
)
|
||||
from .admin import AdminCodeValidateView
|
||||
from .store_settings import StoreSettingsView
|
||||
|
||||
__all__ = [
|
||||
# Catalogue Images
|
||||
@@ -46,4 +47,6 @@ __all__ = [
|
||||
"Pagination",
|
||||
# Admin
|
||||
"AdminCodeValidateView",
|
||||
# Store Settings
|
||||
"StoreSettingsView",
|
||||
]
|
||||
|
||||
32
tienda_ilusion/don_confiao/api/store_settings.py
Normal file
32
tienda_ilusion/don_confiao/api/store_settings.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from ..models.store_settings import StoreSettings
|
||||
from ..permissions import IsAdministrator
|
||||
from ..serializers.store_settings import StoreSettingsSerializer
|
||||
|
||||
|
||||
class StoreSettingsView(APIView):
|
||||
def get_permissions(self):
|
||||
if self.request.method == "GET":
|
||||
return [AllowAny()]
|
||||
return [IsAuthenticated(), IsAdministrator()]
|
||||
|
||||
def get(self, request):
|
||||
settings = StoreSettings.get_singleton()
|
||||
return Response(
|
||||
StoreSettingsSerializer(
|
||||
settings, context={"request": request}
|
||||
).data
|
||||
)
|
||||
|
||||
def patch(self, request):
|
||||
settings = StoreSettings.get_singleton()
|
||||
serializer = StoreSettingsSerializer(
|
||||
settings, data=request.data, partial=True, context={"request": request}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
27
tienda_ilusion/don_confiao/migrations/0050_storesettings.py
Normal file
27
tienda_ilusion/don_confiao/migrations/0050_storesettings.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.0.6 on 2026-08-08 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0049_catalogueimage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StoreSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('address', models.TextField()),
|
||||
('latitude', models.FloatField(blank=True, null=True)),
|
||||
('longitude', models.FloatField(blank=True, null=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Configuración de la tienda',
|
||||
'verbose_name_plural': 'Configuración de la tienda',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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/'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
# Generated by Django 5.0.6 on 2026-08-10 03:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('don_confiao', '0050_catalogsale_code_sale_code'),
|
||||
('don_confiao', '0051_storesettings_logo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
from .store_settings import StoreSettings
|
||||
|
||||
__all__ = ["StoreSettings"]
|
||||
|
||||
21
tienda_ilusion/don_confiao/models/store_settings.py
Normal file
21
tienda_ilusion/don_confiao/models/store_settings.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
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:
|
||||
verbose_name = "Configuración de la tienda"
|
||||
verbose_name_plural = "Configuración de la tienda"
|
||||
|
||||
def __str__(self):
|
||||
return f"StoreSettings (id={self.id})"
|
||||
|
||||
@classmethod
|
||||
def get_singleton(cls):
|
||||
instance, _ = cls.objects.get_or_create(pk=1)
|
||||
return instance
|
||||
@@ -16,6 +16,7 @@ from .payments import (
|
||||
ReconciliationJarSerializer,
|
||||
PaymentMethodSerializer,
|
||||
)
|
||||
from .store_settings import StoreSettingsSerializer
|
||||
|
||||
__all__ = [
|
||||
# Catalogue Images
|
||||
@@ -39,4 +40,6 @@ __all__ = [
|
||||
# Payments
|
||||
"ReconciliationJarSerializer",
|
||||
"PaymentMethodSerializer",
|
||||
# Store Settings
|
||||
"StoreSettingsSerializer",
|
||||
]
|
||||
|
||||
@@ -36,6 +36,7 @@ class SaleSerializer(serializers.ModelSerializer):
|
||||
model = Sale
|
||||
fields = [
|
||||
"id",
|
||||
"code",
|
||||
"customer",
|
||||
"date",
|
||||
"saleline_set",
|
||||
@@ -68,6 +69,7 @@ class CatalogSaleSerializer(serializers.ModelSerializer):
|
||||
model = CatalogSale
|
||||
fields = [
|
||||
"id",
|
||||
"code",
|
||||
"customer",
|
||||
"date",
|
||||
"catalogsaleline_set",
|
||||
|
||||
22
tienda_ilusion/don_confiao/serializers/store_settings.py
Normal file
22
tienda_ilusion/don_confiao/serializers/store_settings.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models.store_settings import StoreSettings
|
||||
|
||||
|
||||
class StoreSettingsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = StoreSettings
|
||||
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
|
||||
@@ -8,6 +8,7 @@ from ..admin import CatalogSaleAdmin, SaleAdmin
|
||||
from ..models.customers import Customer
|
||||
from ..models.products import Product
|
||||
from ..models.sales import CatalogSale, Sale
|
||||
from ..serializers import CatalogSaleSerializer, SaleSerializer
|
||||
from .Mixins import LoginMixin
|
||||
|
||||
|
||||
@@ -191,3 +192,31 @@ class TestOrderSummaryPublic(APITestCase, LoginMixin):
|
||||
def test_admin_list_display_includes_code(self):
|
||||
self.assertIn("code", SaleAdmin.list_display)
|
||||
self.assertIn("code", CatalogSaleAdmin.list_display)
|
||||
|
||||
def test_sale_serializer_includes_code(self):
|
||||
sale = self._create_sale()
|
||||
data = SaleSerializer(sale).data
|
||||
self.assertIn("code", data)
|
||||
self.assertEqual(data["code"], sale.code)
|
||||
|
||||
def test_catalog_sale_serializer_includes_code(self):
|
||||
catalog_sale = self._create_catalog_sale()
|
||||
data = CatalogSaleSerializer(catalog_sale).data
|
||||
self.assertIn("code", data)
|
||||
self.assertEqual(data["code"], catalog_sale.code)
|
||||
|
||||
def test_sales_list_endpoint_includes_code(self):
|
||||
self.login()
|
||||
sale = self._create_sale()
|
||||
response = self.client.get("/don_confiao/api/sales/")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
item = next(i for i in response.json() if i["id"] == sale.id)
|
||||
self.assertEqual(item["code"], sale.code)
|
||||
|
||||
def test_catalog_sales_list_endpoint_includes_code(self):
|
||||
self.login()
|
||||
catalog_sale = self._create_catalog_sale()
|
||||
response = self.client.get("/don_confiao/api/catalog_sales/")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
item = next(i for i in response.json() if i["id"] == catalog_sale.id)
|
||||
self.assertEqual(item["code"], catalog_sale.code)
|
||||
|
||||
246
tienda_ilusion/don_confiao/tests/test_store_settings.py
Normal file
246
tienda_ilusion/don_confiao/tests/test_store_settings.py
Normal file
@@ -0,0 +1,246 @@
|
||||
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
|
||||
|
||||
from ..models.store_settings import StoreSettings
|
||||
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):
|
||||
first = StoreSettings.get_singleton()
|
||||
second = StoreSettings.get_singleton()
|
||||
self.assertEqual(first.pk, second.pk)
|
||||
self.assertEqual(StoreSettings.objects.count(), 1)
|
||||
|
||||
def test_singleton_created_without_address(self):
|
||||
instance = StoreSettings.get_singleton()
|
||||
self.assertEqual(instance.address, "")
|
||||
|
||||
def test_str(self):
|
||||
instance = StoreSettings.get_singleton()
|
||||
self.assertEqual(str(instance), f"StoreSettings (id={instance.id})")
|
||||
|
||||
|
||||
class TestStoreSettingsAPIPermissions(APITestCase, LoginMixin):
|
||||
def setUp(self):
|
||||
self.url = URL
|
||||
|
||||
def test_get_unauthenticated(self):
|
||||
client = APIClient()
|
||||
response = client.get(self.url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_patch_unauthenticated(self):
|
||||
client = APIClient()
|
||||
response = client.patch(
|
||||
self.url,
|
||||
{"address": "Calle falsa 123"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_patch_non_admin(self):
|
||||
user = User.objects.create_user(
|
||||
username="regularuser",
|
||||
email="regular@example.com",
|
||||
password="regularpass",
|
||||
)
|
||||
refresh = RefreshToken.for_user(user)
|
||||
client = APIClient()
|
||||
client.credentials(
|
||||
HTTP_AUTHORIZATION=f"Bearer {str(refresh.access_token)}"
|
||||
)
|
||||
response = client.patch(
|
||||
self.url,
|
||||
{"address": "Calle falsa 123"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_patch_publico(self):
|
||||
user = User.objects.create_user(
|
||||
username="publico",
|
||||
email="publico@example.com",
|
||||
password="publicopass",
|
||||
)
|
||||
user.profile.user_type = "publico"
|
||||
user.profile.save()
|
||||
refresh = RefreshToken.for_user(user)
|
||||
client = APIClient()
|
||||
client.credentials(
|
||||
HTTP_AUTHORIZATION=f"Bearer {str(refresh.access_token)}"
|
||||
)
|
||||
response = client.patch(
|
||||
self.url,
|
||||
{"address": "Calle falsa 123"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
|
||||
class TestStoreSettingsAPI(APITestCase, LoginMixin):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
self.url = URL
|
||||
|
||||
def test_get_creates_singleton(self):
|
||||
self.assertEqual(StoreSettings.objects.count(), 0)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(StoreSettings.objects.count(), 1)
|
||||
data = response.json()
|
||||
self.assertIn("id", data)
|
||||
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):
|
||||
self.client.get(self.url)
|
||||
response = self.client.patch(
|
||||
self.url,
|
||||
{
|
||||
"address": "Carrera 5 # 10-20, Bogotá",
|
||||
"latitude": 4.6097,
|
||||
"longitude": -74.0817,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
data = response.json()
|
||||
self.assertEqual(data["address"], "Carrera 5 # 10-20, Bogotá")
|
||||
self.assertEqual(data["latitude"], 4.6097)
|
||||
self.assertEqual(data["longitude"], -74.0817)
|
||||
|
||||
settings = StoreSettings.objects.get(pk=1)
|
||||
self.assertEqual(settings.address, "Carrera 5 # 10-20, Bogotá")
|
||||
self.assertEqual(settings.latitude, 4.6097)
|
||||
self.assertEqual(settings.longitude, -74.0817)
|
||||
|
||||
def test_patch_partial(self):
|
||||
self.client.get(self.url)
|
||||
response = self.client.patch(
|
||||
self.url,
|
||||
{"address": "Solo dirección"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
settings = StoreSettings.objects.get(pk=1)
|
||||
self.assertEqual(settings.address, "Solo dirección")
|
||||
self.assertIsNone(settings.latitude)
|
||||
|
||||
def test_patch_reuses_singleton(self):
|
||||
self.client.get(self.url)
|
||||
self.client.patch(
|
||||
self.url,
|
||||
{"address": "Primera"},
|
||||
format="json",
|
||||
)
|
||||
self.client.patch(
|
||||
self.url,
|
||||
{"address": "Segunda"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(StoreSettings.objects.count(), 1)
|
||||
|
||||
def test_patch_invalid_coordinates(self):
|
||||
self.client.get(self.url)
|
||||
response = self.client.patch(
|
||||
self.url,
|
||||
{"latitude": "not-a-number"},
|
||||
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)
|
||||
@@ -27,6 +27,8 @@ from .api import (
|
||||
SalesForReconciliationView,
|
||||
# Admin
|
||||
AdminCodeValidateView,
|
||||
# Store Settings
|
||||
StoreSettingsView,
|
||||
)
|
||||
|
||||
app_name = "don_confiao"
|
||||
@@ -106,4 +108,5 @@ urlpatterns = [
|
||||
AdminCodeValidateView.as_view(),
|
||||
),
|
||||
path("api/sales/for_tryton", SalesForTrytonView.as_view()),
|
||||
path("api/store_settings", StoreSettingsView.as_view()),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user