Files
pablo 2a9407e676
CI / ci (ubuntu-24.04, 3.13) (push) Has been cancelled
feat: backoffice setup
2026-07-23 13:31:53 +02:00

108 lines
3.6 KiB
Python

import json
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin
from django.http import HttpResponse, HttpResponseRedirect
DAISYUI_WIDGET_CLASSES = (
(forms.CheckboxInput, 'checkbox'),
(forms.CheckboxSelectMultiple, 'checkbox'),
(forms.ClearableFileInput, 'file-input file-input-bordered w-full'),
(forms.Textarea, 'textarea textarea-bordered w-full'),
(forms.Select, 'select select-bordered w-full'),
)
DAISYUI_DEFAULT_WIDGET_CLASS = 'input input-bordered w-full'
class BackofficeSectionMixin:
section = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['section'] = self.section
return context
class BackofficeAccessMixin(BackofficeSectionMixin, LoginRequiredMixin, UserPassesTestMixin):
login_url = 'users:login'
def test_func(self):
return self.request.user.is_staff
class BackofficeCRUDMixin(BackofficeAccessMixin, PermissionRequiredMixin):
raise_exception = True
class BackofficeStyledFormMixin:
"""Aplica clases de DaisyUI a los widgets del formulario según su tipo,
sin necesitar declarar un ModelForm explícito por modelo (a diferencia de
web.mixins.StylingMixin, que requiere listar `styled_fields` a mano)."""
def get_form(self, form_class=None):
form = super().get_form(form_class)
for field in form.fields.values():
css_class = DAISYUI_DEFAULT_WIDGET_CLASS
for widget_type, widget_css_class in DAISYUI_WIDGET_CLASSES:
if isinstance(field.widget, widget_type):
css_class = widget_css_class
break
existing = field.widget.attrs.get('class', '')
field.widget.attrs['class'] = f'{existing} {css_class}'.strip()
return form
class BackofficeHtmxMixin:
"""Toda vista de backoffice puede responder con la página completa (navegación
directa) o con un fragmento (carga en el modal, o autorefresco de una lista/detalle)."""
fragment_template_name = None
def is_htmx(self):
return self.request.headers.get('HX-Request') == 'true'
def get_template_names(self):
if self.is_htmx() and self.fragment_template_name:
return [self.fragment_template_name]
return super().get_template_names()
class BackofficeModalFormMixin(BackofficeStyledFormMixin, BackofficeHtmxMixin):
"""Create/Update pensadas para abrirse en modal. Al guardar con éxito vía htmx
no redirige (un <dialog> no navega): responde 204 + HX-Trigger para cerrar el
modal y avisar a quien lo abrió (lista o detalle) de que se refresque."""
trigger_event = 'backoffice:list-changed'
def form_valid(self, form):
self.object = form.save()
if self.is_htmx():
response = HttpResponse(status=204)
response['HX-Trigger'] = json.dumps({'backoffice:modal-close': True, self.trigger_event: True})
return response
return HttpResponseRedirect(self.get_success_url())
class BackofficeModalDeleteMixin(BackofficeHtmxMixin):
trigger_event = 'backoffice:list-changed'
def perform_delete(self):
self.object.delete()
def form_valid(self, form):
success_url = self.get_success_url()
self.perform_delete()
if self.is_htmx():
response = HttpResponse(status=204)
response['HX-Trigger'] = json.dumps({'backoffice:modal-close': True, self.trigger_event: True})
return response
return HttpResponseRedirect(success_url)