This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.views.generic import DetailView, ListView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeHtmxMixin
|
||||
from shop.models import CustomerAddress, Order
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
SECTION = 'customers'
|
||||
|
||||
|
||||
class CustomerListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = User
|
||||
permission_required = 'auth.view_user'
|
||||
section = SECTION
|
||||
paginate_by = 20
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = 'backoffice/generic/_list_fragment.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(is_staff=False).order_by('email')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Clientes',
|
||||
'columns': [('E-mail', 'email'), ('Nombre', 'first_name'), ('Apellidos', 'last_name')],
|
||||
'detail_url_name': 'backoffice:customer_detail',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class CustomerDetailView(BackofficeCRUDMixin, DetailView):
|
||||
model = User
|
||||
permission_required = 'auth.view_user'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/customers/customer_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'addresses': CustomerAddress.objects.filter(user=self.object),
|
||||
'orders': Order.objects.filter(user=self.object),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
# CRUD pendiente: la edición/borrado de clientes ya se gestiona a través del
|
||||
# admin de Django (auth.User) — aquí solo se expone consulta.
|
||||
@@ -0,0 +1,23 @@
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from backoffice.mixins import BackofficeAccessMixin
|
||||
|
||||
SECTIONS = [
|
||||
{'label': 'Productos', 'url_name': 'backoffice:product_list'},
|
||||
{'label': 'Pedidos', 'url_name': 'backoffice:order_list'},
|
||||
{'label': 'Pagos', 'url_name': 'backoffice:payment_list'},
|
||||
{'label': 'Clientes', 'url_name': 'backoffice:customer_list'},
|
||||
{'label': 'Proveedores', 'url_name': 'backoffice:provider_list'},
|
||||
{'label': 'Impuestos', 'url_name': 'backoffice:tax_list'},
|
||||
{'label': 'Marca y ajustes', 'url_name': 'backoffice:brand_settings'},
|
||||
]
|
||||
|
||||
|
||||
class DashboardView(BackofficeAccessMixin, TemplateView):
|
||||
template_name = 'backoffice/dashboard.html'
|
||||
section = 'dashboard'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['sections'] = SECTIONS
|
||||
return context
|
||||
@@ -0,0 +1,84 @@
|
||||
from django import forms
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeHtmxMixin
|
||||
from shop.models import Order
|
||||
|
||||
SECTION = 'orders'
|
||||
|
||||
|
||||
class ShippingStatusForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ('shipping_status',)
|
||||
widgets = {'shipping_status': forms.Select(attrs={'class': 'select select-bordered w-full'})}
|
||||
|
||||
|
||||
class OrderListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Order
|
||||
permission_required = 'shop.view_order'
|
||||
section = SECTION
|
||||
paginate_by = 20
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = 'backoffice/generic/_list_fragment.html'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
status = self.request.GET.get('status')
|
||||
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Pedidos',
|
||||
'columns': [('Código', 'code'), ('Cliente', 'email'), ('Estado', 'get_status_display'), ('Total', 'total')],
|
||||
'detail_url_name': 'backoffice:order_detail',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class OrderDetailView(BackofficeCRUDMixin, DetailView):
|
||||
model = Order
|
||||
permission_required = 'shop.view_order'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/orders/order_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'lines': self.object.lines.all(),
|
||||
'payments': self.object.payments.all(),
|
||||
'shipping_status_form': ShippingStatusForm(instance=self.object),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class OrderUpdateShippingStatusView(BackofficeCRUDMixin, UpdateView):
|
||||
model = Order
|
||||
form_class = ShippingStatusForm
|
||||
permission_required = 'shop.change_order'
|
||||
section = SECTION
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:order_detail', args=[self.object.pk])
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_to_response(self.get_context_data(shipping_status_form=form))
|
||||
|
||||
def get_template_names(self):
|
||||
return ['backoffice/orders/order_detail.html']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'lines': self.object.lines.all(), 'payments': self.object.payments.all()})
|
||||
context.setdefault('shipping_status_form', ShippingStatusForm(instance=self.object))
|
||||
return context
|
||||
@@ -0,0 +1,31 @@
|
||||
from django.views.generic import ListView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeHtmxMixin
|
||||
from shop.models import Payment
|
||||
|
||||
SECTION = 'payments'
|
||||
|
||||
|
||||
class PaymentListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Payment
|
||||
permission_required = 'shop.view_payment'
|
||||
section = SECTION
|
||||
paginate_by = 20
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = 'backoffice/generic/_list_fragment.html'
|
||||
ordering = '-creation_date'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Pagos',
|
||||
'columns': [
|
||||
('Fecha', 'creation_date'),
|
||||
('Pedido', 'order.code'),
|
||||
('Importe', 'amount'),
|
||||
('Método', 'get_method_display'),
|
||||
],
|
||||
}
|
||||
)
|
||||
return context
|
||||
@@ -0,0 +1,708 @@
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
|
||||
|
||||
from backoffice.forms import ProductVariantForm
|
||||
from backoffice.mixins import (
|
||||
BackofficeCRUDMixin,
|
||||
BackofficeHtmxMixin,
|
||||
BackofficeModalDeleteMixin,
|
||||
BackofficeModalFormMixin,
|
||||
)
|
||||
from shop.models import (
|
||||
Brand,
|
||||
Product,
|
||||
ProductAttribute,
|
||||
ProductAttributeValue,
|
||||
ProductBatch,
|
||||
ProductCategory,
|
||||
ProductImage,
|
||||
ProductPrice,
|
||||
ProductVariant,
|
||||
Tag,
|
||||
)
|
||||
from shop.utils import delete_product_batch
|
||||
|
||||
SECTION = 'products'
|
||||
|
||||
LIST_FRAGMENT = 'backoffice/generic/_list_fragment.html'
|
||||
FORM_FRAGMENT = 'backoffice/generic/_form_fragment.html'
|
||||
DELETE_FRAGMENT = 'backoffice/generic/_confirm_delete_fragment.html'
|
||||
|
||||
|
||||
# --- Product ---
|
||||
|
||||
|
||||
class ProductListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Product
|
||||
permission_required = 'shop.view_product'
|
||||
section = SECTION
|
||||
paginate_by = 20
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = LIST_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Productos',
|
||||
'columns': [('SKU', 'sku'), ('Nombre', 'name'), ('Stock', 'stock'), ('Oculto', 'hidden')],
|
||||
'create_url': reverse_lazy('backoffice:product_create'),
|
||||
'detail_url_name': 'backoffice:product_detail',
|
||||
'delete_url_name': 'backoffice:product_delete',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class ProductCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = Product
|
||||
permission_required = 'shop.add_product'
|
||||
section = SECTION
|
||||
fields = ('sku', 'name', 'description', 'stock', 'brand', 'categories', 'tags', 'hidden', 'is_shipping_method')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Añadir producto', 'cancel_url': reverse_lazy('backoffice:product_list')})
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.pk])
|
||||
|
||||
|
||||
class ProductUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
model = Product
|
||||
permission_required = 'shop.change_product'
|
||||
section = SECTION
|
||||
fields = ('sku', 'name', 'description', 'stock', 'brand', 'categories', 'tags', 'hidden', 'is_shipping_method')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': f'Editar {self.object.name}', 'cancel_url': reverse_lazy('backoffice:product_list')})
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.pk])
|
||||
|
||||
|
||||
class ProductDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = Product
|
||||
permission_required = 'shop.delete_product'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_list')
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
# Borrar el producto invalida la página en la que estuvieras (lista o su
|
||||
# propio detalle), así que aquí sí navegamos de verdad en vez de solo
|
||||
# cerrar el modal y refrescar en sitio.
|
||||
success_url = self.get_success_url()
|
||||
self.perform_delete()
|
||||
|
||||
if self.is_htmx():
|
||||
response = HttpResponse(status=200)
|
||||
response['HX-Redirect'] = success_url
|
||||
return response
|
||||
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
||||
|
||||
class ProductDetailView(BackofficeCRUDMixin, DetailView):
|
||||
model = Product
|
||||
permission_required = 'shop.view_product'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/products/product_detail.html'
|
||||
fragment_template_name = 'backoffice/products/_product_detail_fragment.html'
|
||||
|
||||
def get_template_names(self):
|
||||
if self.request.headers.get('HX-Request') == 'true':
|
||||
return [self.fragment_template_name]
|
||||
return super().get_template_names()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['variants'] = self.object.variants.prefetch_related('attribute_values__attribute')
|
||||
context['batches'] = ProductBatch.objects.filter(product=self.object)
|
||||
context['images'] = self.object.images.all()
|
||||
return context
|
||||
|
||||
|
||||
# --- ProductImage (nested under a product) ---
|
||||
|
||||
|
||||
class ProductImageCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductImage
|
||||
permission_required = 'shop.add_productimage'
|
||||
section = SECTION
|
||||
fields = ('original',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_product(self):
|
||||
return get_object_or_404(Product, pk=self.kwargs['product_pk'])
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.instance.product = self.get_product()
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
product = self.get_product()
|
||||
context.update(
|
||||
{'title': f'Añadir imagen a {product.name}', 'cancel_url': reverse_lazy('backoffice:product_detail', args=[product.pk])}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductImageDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductImage
|
||||
permission_required = 'shop.delete_productimage'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
# --- ProductVariant (nested under a product) ---
|
||||
|
||||
|
||||
class ProductVariantCreateView(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
|
||||
|
||||
def get_product(self):
|
||||
return get_object_or_404(Product, pk=self.kwargs['product_pk'])
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['product'] = self.get_product()
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
product = self.get_product()
|
||||
context.update(
|
||||
{'title': f'Añadir variante a {product.name}', 'cancel_url': reverse_lazy('backoffice:product_detail', args=[product.pk])}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductVariantUpdateView(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
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': f'Editar variante {self.object.sku}',
|
||||
'cancel_url': reverse_lazy('backoffice:product_detail', args=[self.object.product_id]),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductVariantDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductVariant
|
||||
permission_required = 'shop.delete_productvariant'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
# --- ProductPrice (create only — la historia es de solo alta, ver shop/models.py:ProductPrice) ---
|
||||
|
||||
|
||||
class ProductPriceCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductPrice
|
||||
permission_required = 'shop.add_productprice'
|
||||
section = SECTION
|
||||
fields = ('price', 'tax', 'current')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_product(self):
|
||||
return get_object_or_404(Product, pk=self.kwargs['product_pk'])
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
# El FK debe fijarse antes de is_valid(), porque ProductPrice.clean()
|
||||
# exige exactamente uno de product/variant en el momento de validar.
|
||||
form = super().get_form(form_class)
|
||||
form.instance.product = self.get_product()
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
product = self.get_product()
|
||||
context.update(
|
||||
{'title': f'Añadir precio a {product.name}', 'cancel_url': reverse_lazy('backoffice:product_detail', args=[product.pk])}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductVariantPriceCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductPrice
|
||||
permission_required = 'shop.add_productprice'
|
||||
section = SECTION
|
||||
fields = ('price', 'tax', 'current')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_variant(self):
|
||||
return get_object_or_404(ProductVariant, pk=self.kwargs['variant_pk'])
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.instance.variant = self.get_variant()
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
variant = self.get_variant()
|
||||
context.update(
|
||||
{
|
||||
'title': f'Añadir precio a la variante {variant.sku}',
|
||||
'cancel_url': reverse_lazy('backoffice:product_detail', args=[variant.product_id]),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.variant.product_id])
|
||||
|
||||
|
||||
# --- ProductBatch (nested under a product) ---
|
||||
|
||||
|
||||
class ProductBatchCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductBatch
|
||||
permission_required = 'shop.add_productbatch'
|
||||
section = SECTION
|
||||
fields = ('code', 'quantity', 'expiration_date', 'provider')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_product(self):
|
||||
return get_object_or_404(Product, pk=self.kwargs['product_pk'])
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.instance.product = self.get_product()
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
product = self.get_product()
|
||||
context.update(
|
||||
{'title': f'Añadir remesa a {product.name}', 'cancel_url': reverse_lazy('backoffice:product_detail', args=[product.pk])}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
|
||||
class ProductBatchDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductBatch
|
||||
permission_required = 'shop.delete_productbatch'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_detail', args=[self.object.product_id])
|
||||
|
||||
def perform_delete(self):
|
||||
# Reutiliza la lógica ya existente de descuento de stock al borrar una remesa.
|
||||
delete_product_batch(self.object)
|
||||
|
||||
|
||||
# --- ProductCategory ---
|
||||
|
||||
|
||||
class ProductCategoryListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = ProductCategory
|
||||
permission_required = 'shop.view_productcategory'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = LIST_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Categorías de producto',
|
||||
'columns': [('Nombre', 'name'), ('Padre', 'parent'), ('Oculta', 'hidden')],
|
||||
'create_url': reverse_lazy('backoffice:product_category_create'),
|
||||
'update_url_name': 'backoffice:product_category_update',
|
||||
'delete_url_name': 'backoffice:product_category_delete',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class ProductCategoryCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductCategory
|
||||
permission_required = 'shop.add_productcategory'
|
||||
section = SECTION
|
||||
fields = ('name', 'parent', 'promoted', 'hidden', 'show_in_navbar')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_category_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Añadir categoría', 'cancel_url': reverse_lazy('backoffice:product_category_list')})
|
||||
return context
|
||||
|
||||
|
||||
class ProductCategoryUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
model = ProductCategory
|
||||
permission_required = 'shop.change_productcategory'
|
||||
section = SECTION
|
||||
fields = ('name', 'parent', 'promoted', 'hidden', 'show_in_navbar')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_category_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': f'Editar {self.object.name}', 'cancel_url': reverse_lazy('backoffice:product_category_list')})
|
||||
return context
|
||||
|
||||
|
||||
class ProductCategoryDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductCategory
|
||||
permission_required = 'shop.delete_productcategory'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_category_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_category_list')
|
||||
return context
|
||||
|
||||
|
||||
# --- ProductAttribute / ProductAttributeValue ---
|
||||
|
||||
|
||||
class ProductAttributeListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = ProductAttribute
|
||||
permission_required = 'shop.view_productattribute'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = LIST_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Atributos de producto',
|
||||
'columns': [('Nombre', 'name')],
|
||||
'create_url': reverse_lazy('backoffice:product_attribute_create'),
|
||||
'detail_url_name': 'backoffice:product_attribute_detail',
|
||||
'update_url_name': 'backoffice:product_attribute_update',
|
||||
'delete_url_name': 'backoffice:product_attribute_delete',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class ProductAttributeCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductAttribute
|
||||
permission_required = 'shop.add_productattribute'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_attribute_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Añadir atributo', 'cancel_url': reverse_lazy('backoffice:product_attribute_list')})
|
||||
return context
|
||||
|
||||
|
||||
class ProductAttributeUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
"""Solo edita el nombre del atributo. Los valores se gestionan en ProductAttributeDetailView."""
|
||||
|
||||
model = ProductAttribute
|
||||
permission_required = 'shop.change_productattribute'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_attribute_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': f'Editar {self.object.name}', 'cancel_url': reverse_lazy('backoffice:product_attribute_list')})
|
||||
return context
|
||||
|
||||
|
||||
class ProductAttributeDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductAttribute
|
||||
permission_required = 'shop.delete_productattribute'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:product_attribute_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_attribute_list')
|
||||
return context
|
||||
|
||||
|
||||
class ProductAttributeDetailView(BackofficeCRUDMixin, DetailView):
|
||||
"""Gestión de los valores (ProductAttributeValue) de un atributo."""
|
||||
|
||||
model = ProductAttribute
|
||||
permission_required = 'shop.view_productattribute'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/products/attribute_detail.html'
|
||||
fragment_template_name = 'backoffice/products/_attribute_detail_fragment.html'
|
||||
|
||||
def get_template_names(self):
|
||||
if self.request.headers.get('HX-Request') == 'true':
|
||||
return [self.fragment_template_name]
|
||||
return super().get_template_names()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['values'] = self.object.values.all()
|
||||
return context
|
||||
|
||||
|
||||
class ProductAttributeValueCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = ProductAttributeValue
|
||||
permission_required = 'shop.add_productattributevalue'
|
||||
section = SECTION
|
||||
fields = ('value',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
|
||||
def get_attribute(self):
|
||||
return get_object_or_404(ProductAttribute, pk=self.kwargs['attribute_pk'])
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.instance.attribute = self.get_attribute()
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
attribute = self.get_attribute()
|
||||
context.update(
|
||||
{
|
||||
'title': f'Añadir valor a {attribute.name}',
|
||||
'cancel_url': reverse_lazy('backoffice:product_attribute_detail', args=[attribute.pk]),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_attribute_detail', args=[self.object.attribute_id])
|
||||
|
||||
|
||||
class ProductAttributeValueDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = ProductAttributeValue
|
||||
permission_required = 'shop.delete_productattributevalue'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:product_attribute_detail', args=[self.object.attribute_id])
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('backoffice:product_attribute_detail', args=[self.object.attribute_id])
|
||||
|
||||
|
||||
# --- Brand / Tag ---
|
||||
|
||||
|
||||
class BrandListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Brand
|
||||
permission_required = 'shop.view_brand'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = LIST_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Marcas',
|
||||
'columns': [('Nombre', 'name')],
|
||||
'create_url': reverse_lazy('backoffice:brand_create'),
|
||||
'update_url_name': 'backoffice:brand_update',
|
||||
'delete_url_name': 'backoffice:brand_delete',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class BrandCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = Brand
|
||||
permission_required = 'shop.add_brand'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:brand_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Añadir marca', 'cancel_url': reverse_lazy('backoffice:brand_list')})
|
||||
return context
|
||||
|
||||
|
||||
class BrandUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
model = Brand
|
||||
permission_required = 'shop.change_brand'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:brand_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': f'Editar {self.object.name}', 'cancel_url': reverse_lazy('backoffice:brand_list')})
|
||||
return context
|
||||
|
||||
|
||||
class BrandDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = Brand
|
||||
permission_required = 'shop.delete_brand'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:brand_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:brand_list')
|
||||
return context
|
||||
|
||||
|
||||
class TagListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Tag
|
||||
permission_required = 'shop.view_tag'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = LIST_FRAGMENT
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Etiquetas',
|
||||
'columns': [('Nombre', 'name')],
|
||||
'create_url': reverse_lazy('backoffice:tag_create'),
|
||||
'update_url_name': 'backoffice:tag_update',
|
||||
'delete_url_name': 'backoffice:tag_delete',
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class TagCreateView(BackofficeCRUDMixin, BackofficeModalFormMixin, CreateView):
|
||||
model = Tag
|
||||
permission_required = 'shop.add_tag'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:tag_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Añadir etiqueta', 'cancel_url': reverse_lazy('backoffice:tag_list')})
|
||||
return context
|
||||
|
||||
|
||||
class TagUpdateView(BackofficeCRUDMixin, BackofficeModalFormMixin, UpdateView):
|
||||
model = Tag
|
||||
permission_required = 'shop.change_tag'
|
||||
section = SECTION
|
||||
fields = ('name',)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
fragment_template_name = FORM_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:tag_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': f'Editar {self.object.name}', 'cancel_url': reverse_lazy('backoffice:tag_list')})
|
||||
return context
|
||||
|
||||
|
||||
class TagDeleteView(BackofficeCRUDMixin, BackofficeModalDeleteMixin, DeleteView):
|
||||
model = Tag
|
||||
permission_required = 'shop.delete_tag'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/confirm_delete.html'
|
||||
fragment_template_name = DELETE_FRAGMENT
|
||||
success_url = reverse_lazy('backoffice:tag_list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['cancel_url'] = reverse_lazy('backoffice:tag_list')
|
||||
return context
|
||||
@@ -0,0 +1,29 @@
|
||||
from django.views.generic import ListView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeHtmxMixin
|
||||
from shop.models import Provider
|
||||
|
||||
SECTION = 'providers'
|
||||
|
||||
|
||||
class ProviderListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Provider
|
||||
permission_required = 'shop.view_provider'
|
||||
section = SECTION
|
||||
paginate_by = 20
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = 'backoffice/generic/_list_fragment.html'
|
||||
ordering = 'name'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'title': 'Proveedores',
|
||||
'columns': [('Nombre', 'name'), ('NIF/CIF', 'vat_id'), ('E-mail', 'email'), ('Teléfono', 'phone')],
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
# CRUD pendiente: replicar ProductCategoryCreateView/UpdateView/DeleteView de
|
||||
# backoffice/views/products.py sobre el modelo Provider.
|
||||
@@ -0,0 +1,59 @@
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import UpdateView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeStyledFormMixin
|
||||
from shop.models import ShopSettings
|
||||
from web.models import WebSettings
|
||||
|
||||
SECTION = 'settings'
|
||||
|
||||
|
||||
class BrandSettingsView(BackofficeCRUDMixin, BackofficeStyledFormMixin, UpdateView):
|
||||
model = WebSettings
|
||||
permission_required = 'web.change_websettings'
|
||||
section = SECTION
|
||||
fields = (
|
||||
'web_title',
|
||||
'web_description',
|
||||
'logo',
|
||||
'business_name',
|
||||
'business_vat_id',
|
||||
'business_brand',
|
||||
'business_address',
|
||||
'business_state',
|
||||
'business_zip',
|
||||
'business_phone',
|
||||
'business_email',
|
||||
'business_email_2',
|
||||
'bg_color',
|
||||
'theme_color',
|
||||
)
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
success_url = reverse_lazy('backoffice:brand_settings')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj, created = WebSettings.objects.get_or_create(pk=1)
|
||||
return obj
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Marca y ajustes de la web', 'cancel_url': reverse_lazy('backoffice:dashboard')})
|
||||
return context
|
||||
|
||||
|
||||
class ShopSettingsView(BackofficeCRUDMixin, BackofficeStyledFormMixin, UpdateView):
|
||||
model = ShopSettings
|
||||
permission_required = 'shop.change_shopsettings'
|
||||
section = SECTION
|
||||
fields = ('merchant_code', 'currency_code', 'terminal', 'shared_secret', 'tpv_domain')
|
||||
template_name = 'backoffice/generic/form.html'
|
||||
success_url = reverse_lazy('backoffice:shop_settings')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj, created = ShopSettings.objects.get_or_create(pk=1)
|
||||
return obj
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Ajustes de la tienda (TPV)', 'cancel_url': reverse_lazy('backoffice:dashboard')})
|
||||
return context
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.views.generic import ListView
|
||||
|
||||
from backoffice.mixins import BackofficeCRUDMixin, BackofficeHtmxMixin
|
||||
from shop.models import Tax
|
||||
|
||||
SECTION = 'taxes'
|
||||
|
||||
|
||||
class TaxListView(BackofficeCRUDMixin, BackofficeHtmxMixin, ListView):
|
||||
model = Tax
|
||||
permission_required = 'shop.view_tax'
|
||||
section = SECTION
|
||||
template_name = 'backoffice/generic/list.html'
|
||||
fragment_template_name = 'backoffice/generic/_list_fragment.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({'title': 'Impuestos', 'columns': [('Código', 'code'), ('Valor (%)', 'value')]})
|
||||
return context
|
||||
|
||||
# CRUD pendiente: replicar ProductCategoryCreateView/UpdateView/DeleteView de
|
||||
# backoffice/views/products.py sobre el modelo Tax.
|
||||
Reference in New Issue
Block a user