feat: create/edit product views
This commit is contained in:
@@ -17,53 +17,24 @@ class TestBackofficeHtmxContract(TestCase, CreateProductsMixin):
|
||||
self.client.force_login(self.superuser)
|
||||
self.product = self.create_product()
|
||||
|
||||
def test_htmx_get_create_form_returns_fragment_not_full_page(self):
|
||||
def test_product_create_is_a_full_page_not_a_modal_fragment(self):
|
||||
# ProductCreateView ya no es modal: navegar a la URL de creación siempre
|
||||
# devuelve la página completa, incluso si la petición lleva HX-Request.
|
||||
response = self.client.get(reverse('backoffice:product_create'), HTTP_HX_REQUEST='true')
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert '<html' not in body
|
||||
assert 'Añadir producto' in body
|
||||
assert '<html' in body
|
||||
assert 'Nuevo producto' in body
|
||||
|
||||
def test_htmx_create_success_returns_204_and_triggers_modal_close_and_refresh(self):
|
||||
def test_product_create_redirects_to_detail_even_with_hx_request(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'HTMX-1',
|
||||
'name': 'Producto htmx',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
},
|
||||
{'sku': 'HTMX-1', 'name': 'Producto htmx', 'description': '', 'stock': '0'},
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 204
|
||||
trigger = json.loads(response['HX-Trigger'])
|
||||
assert trigger == {'backoffice:modal-close': True, 'backoffice:list-changed': True}
|
||||
assert Product.objects.filter(sku='HTMX-1').exists()
|
||||
|
||||
def test_htmx_create_invalid_returns_200_with_errors_not_204(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{'sku': '', 'name': '', 'description': '', 'stock': '0', 'categories': [], 'tags': []},
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert 'HX-Trigger' not in response
|
||||
|
||||
def test_non_htmx_create_still_redirects(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'PLAIN-1',
|
||||
'name': 'Producto normal',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
},
|
||||
)
|
||||
product = Product.objects.get(sku='HTMX-1')
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse('backoffice:product_detail', args=[product.pk])
|
||||
assert 'HX-Trigger' not in response
|
||||
|
||||
def test_htmx_variant_create_triggers_refresh(self):
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from PIL import Image
|
||||
|
||||
from shop.models import Product, ProductImage, ProductPrice, ProductVariant, Tax
|
||||
from shop.models import Brand, Product, ProductCategory, ProductImage, ProductPrice, ProductVariant, Tag, Tax
|
||||
from shop.tests.mixins import CreateProductsMixin
|
||||
|
||||
|
||||
@@ -23,19 +23,40 @@ class TestBackofficeProducts(TestCase, CreateProductsMixin):
|
||||
assert self.product.name in response.content.decode()
|
||||
|
||||
def test_create_product(self):
|
||||
response = self.client.get(reverse('backoffice:product_create'))
|
||||
assert response.status_code == 200
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{'sku': 'NEW-1', 'name': 'Producto nuevo', 'description': '', 'stock': '0'},
|
||||
)
|
||||
product = Product.objects.get(sku='NEW-1')
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse('backoffice:product_detail', args=[product.pk])
|
||||
|
||||
def test_create_product_with_brand_categories_and_tags_in_one_submit(self):
|
||||
brand = Brand.objects.create(name='Marca X')
|
||||
cat1 = ProductCategory.objects.create(name='Cat 1')
|
||||
cat2 = ProductCategory.objects.create(name='Cat 2')
|
||||
tag1 = Tag.objects.create(name='Tag 1')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'NEW-1',
|
||||
'name': 'Producto nuevo',
|
||||
'sku': 'NEW-2',
|
||||
'name': 'Producto completo',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
'brand': brand.pk,
|
||||
'categories': [cat1.pk, cat2.pk],
|
||||
'tags': [tag1.pk],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert Product.objects.filter(sku='NEW-1').exists()
|
||||
product = Product.objects.get(sku='NEW-2')
|
||||
assert product.brand == brand
|
||||
assert set(product.categories.all()) == {cat1, cat2}
|
||||
assert set(product.tags.all()) == {tag1}
|
||||
|
||||
def test_create_variant_for_product(self):
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
@@ -104,6 +125,97 @@ class TestBackofficeProducts(TestCase, CreateProductsMixin):
|
||||
assert response.status_code == 302
|
||||
assert not ProductImage.objects.filter(pk=image.pk).exists()
|
||||
|
||||
def test_inline_view_name(self):
|
||||
response = self.client.get(reverse('backoffice:product_inline_view', args=[self.product.pk, 'name']))
|
||||
assert response.status_code == 200
|
||||
assert self.product.name in response.content.decode()
|
||||
|
||||
def test_inline_edit_name_form(self):
|
||||
response = self.client.get(reverse('backoffice:product_inline_edit', args=[self.product.pk, 'name']))
|
||||
assert response.status_code == 200
|
||||
assert 'name="name"' in response.content.decode()
|
||||
assert 'description' not in response.content.decode()
|
||||
|
||||
def test_inline_update_name(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_inline_edit', args=[self.product.pk, 'name']), {'name': 'Nuevo nombre'}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.product.refresh_from_db()
|
||||
assert self.product.name == 'Nuevo nombre'
|
||||
assert 'Nuevo nombre' in response.content.decode()
|
||||
|
||||
def test_inline_update_description(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_inline_edit', args=[self.product.pk, 'description']),
|
||||
{'description': 'Descripción actualizada'},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.product.refresh_from_db()
|
||||
assert self.product.description == 'Descripción actualizada'
|
||||
|
||||
def test_inline_update_brand(self):
|
||||
brand = Brand.objects.create(name='Marca X')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_inline_edit', args=[self.product.pk, 'brand']), {'brand': brand.pk}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.product.refresh_from_db()
|
||||
assert self.product.brand == brand
|
||||
assert 'Marca X' in response.content.decode()
|
||||
|
||||
def test_inline_update_categories_accepts_multiple(self):
|
||||
cat1 = ProductCategory.objects.create(name='Cat 1')
|
||||
cat2 = ProductCategory.objects.create(name='Cat 2')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_inline_edit', args=[self.product.pk, 'categories']),
|
||||
{'categories': [cat1.pk, cat2.pk]},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.product.refresh_from_db()
|
||||
assert set(self.product.categories.all()) == {cat1, cat2}
|
||||
|
||||
def test_inline_update_tags_accepts_multiple(self):
|
||||
tag1 = Tag.objects.create(name='Tag 1')
|
||||
tag2 = Tag.objects.create(name='Tag 2')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_inline_edit', args=[self.product.pk, 'tags']), {'tags': [tag1.pk, tag2.pk]}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
self.product.refresh_from_db()
|
||||
assert set(self.product.tags.all()) == {tag1, tag2}
|
||||
|
||||
def test_inline_edit_rejects_arbitrary_field(self):
|
||||
response = self.client.get(reverse('backoffice:product_inline_edit', args=[self.product.pk, 'sku']))
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_field_search_filters_by_query(self):
|
||||
Brand.objects.create(name='Acme')
|
||||
Brand.objects.create(name='Globex')
|
||||
|
||||
response = self.client.get(reverse('backoffice:product_field_search', args=['brand']), {'q': 'Acm'})
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert 'Acme' in body
|
||||
assert 'Globex' not in body
|
||||
|
||||
def test_field_search_without_query_returns_all(self):
|
||||
Brand.objects.create(name='Acme')
|
||||
Brand.objects.create(name='Globex')
|
||||
|
||||
response = self.client.get(reverse('backoffice:product_field_search', args=['brand']))
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert 'Acme' in body
|
||||
assert 'Globex' in body
|
||||
|
||||
def test_field_search_rejects_arbitrary_field(self):
|
||||
response = self.client.get(reverse('backoffice:product_field_search', args=['sku']))
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_batch_reduces_stock(self):
|
||||
self.product.stock = Decimal('10')
|
||||
self.product.save()
|
||||
|
||||
Reference in New Issue
Block a user