feat: improved product attribute views
This commit is contained in:
@@ -33,7 +33,20 @@
|
||||
<a href="{% url 'backoffice:dashboard' %}" class="text-xl font-semibold block p-4">Backoffice</a>
|
||||
<ul class="menu w-full">
|
||||
<li>
|
||||
<a href="{% url 'backoffice:product_list' %}" class="{% if section == 'products' %}menu-active{% endif %}">Productos</a>
|
||||
<details {% if section == 'products' %}open{% endif %}>
|
||||
<summary>
|
||||
<a
|
||||
href="{% url 'backoffice:product_list' %}"
|
||||
onclick="event.stopPropagation()"
|
||||
class="grow {% if section == 'products' and '/attributes/' not in request.path %}menu-active{% endif %}"
|
||||
>Productos</a>
|
||||
</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{% url 'backoffice:product_attribute_list' %}" class="{% if '/attributes/' in request.path %}menu-active{% endif %}">Atributos</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'backoffice:order_list' %}" class="{% if section == 'orders' %}menu-active{% endif %}">Pedidos</a>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<h3 class="text-lg font-bold mb-4">{{ title }}</h3>
|
||||
|
||||
<form method="post" hx-post="{{ request.path }}" hx-target="#backoffice-modal-box" hx-swap="innerHTML">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="text-sm font-medium opacity-70 mb-1 block" for="{{ form.sku.id_for_label }}">SKU</label>
|
||||
{{ form.sku }}
|
||||
{{ form.sku.errors }}
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="text-sm font-medium opacity-70 mb-1 block" for="{{ form.stock.id_for_label }}">Stock</label>
|
||||
{{ form.stock }}
|
||||
{{ form.stock.errors }}
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h4 class="text-sm font-medium opacity-70 mb-1">Atributos</h4>
|
||||
<div class="overflow-x-auto bg-base-100 rounded-box border border-base-300">
|
||||
<table class="table">
|
||||
<thead><tr><th>Atributo</th><th>Valores</th></tr></thead>
|
||||
<tbody>
|
||||
{% for attribute in attributes %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap align-top">{{ attribute.name }}</td>
|
||||
<td>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for value in attribute.values.all %}
|
||||
<label class="label cursor-pointer gap-2 border border-base-300 rounded-box px-2 py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="attribute_values"
|
||||
value="{{ value.pk }}"
|
||||
class="checkbox checkbox-sm"
|
||||
{% if value.pk in selected_value_ids %}checked{% endif %}
|
||||
>
|
||||
<span class="label-text">{{ value.value }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2" class="text-center py-4">No hay atributos definidos.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ form.attribute_values.errors }}
|
||||
</div>
|
||||
|
||||
{{ form.non_field_errors }}
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn btn-primary">Guardar</button>
|
||||
<button type="button" class="btn" onclick="document.getElementById('backoffice-modal').close()">Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'backoffice/base.html' %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'backoffice/products/_variant_form_fragment.html' %}
|
||||
{% endblock %}
|
||||
@@ -306,14 +306,38 @@ class ProductImageDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, De
|
||||
|
||||
# --- ProductVariant (nested under a product) ---
|
||||
|
||||
VARIANT_FORM_FRAGMENT = 'backoffice/products/_variant_form_fragment.html'
|
||||
|
||||
class ProductVariantCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
|
||||
class ProductVariantAttributesContextMixin:
|
||||
"""Agrupa los valores de atributo por atributo para pintarlos como tabla
|
||||
(ver _variant_form_fragment.html) en vez del <ul> de checkboxes que genera
|
||||
por defecto CheckboxSelectMultiple. Si el form viene con datos (reenvío
|
||||
tras un error de validación, p.ej. combinación duplicada) se respeta lo
|
||||
que el usuario había marcado en vez de lo que tuviera guardado la instancia."""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
form = context['form']
|
||||
|
||||
if form.is_bound:
|
||||
selected_value_ids = {int(v) for v in form.data.getlist('attribute_values') if v.isdigit()}
|
||||
else:
|
||||
instance = getattr(self, 'object', None)
|
||||
selected_value_ids = set(instance.attribute_values.values_list('pk', flat=True)) if instance and instance.pk else set()
|
||||
|
||||
context['attributes'] = ProductAttribute.objects.prefetch_related('values')
|
||||
context['selected_value_ids'] = selected_value_ids
|
||||
return context
|
||||
|
||||
|
||||
class ProductVariantCreateView(ProductVariantAttributesContextMixin, BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductVariant
|
||||
form_class = ProductVariantForm
|
||||
permission_required = 'shop.add_productvariant'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
template_name = 'backoffice/products/variant_form.html'
|
||||
fragment_template_name = VARIANT_FORM_FRAGMENT
|
||||
|
||||
def get_product(self):
|
||||
return get_object_or_404(Product, pk=self.kwargs['product_pk'])
|
||||
@@ -335,13 +359,13 @@ class ProductVariantCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, Cr
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductVariantUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
class ProductVariantUpdateView(ProductVariantAttributesContextMixin, BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
model = ProductVariant
|
||||
form_class = ProductVariantForm
|
||||
permission_required = 'shop.change_productvariant'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
template_name = 'backoffice/products/variant_form.html'
|
||||
fragment_template_name = VARIANT_FORM_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user