MEJORA: Refactorizacion a HTMX para WebSockets en tiempo real y agregado widget-tweaks

This commit is contained in:
Nachou-u
2026-09-09 08:46:44 -03:00
parent 7684136e46
commit 87467b1092
8 changed files with 107 additions and 197 deletions
+3 -7
View File
@@ -19,11 +19,7 @@ class ExpenseConsumer(AsyncWebsocketConsumer):
# Recibir actualización desde el Channel Layer
async def expense_update(self, event):
expense_data = event['expense']
action = event['action']
html_payload = event['html']
# Enviar actualización por WebSocket al cliente
await self.send(text_data=json.dumps({
'action': action,
'expense': expense_data
}))
# Enviar actualización por WebSocket al cliente (HTMX espera HTML puro, no JSON)
await self.send(text_data=html_payload)
+16 -14
View File
@@ -2,40 +2,42 @@ from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.template.loader import render_to_string
from .models import Expense
@receiver(post_save, sender=Expense)
def expense_saved(sender, instance, created, **kwargs):
channel_layer = get_channel_layer()
action = 'created' if created else 'updated'
expense_data = {
'id': instance.id,
'amount': str(instance.amount_clp),
'category': instance.category.name if instance.category else None,
'project': instance.project.name if instance.project else None,
'user': instance.user.username if instance.user else None,
'date': instance.date.strftime('%Y-%m-%d'),
'description': instance.description,
}
# Renderizamos la fila parcial
html = render_to_string('expenses/partials/expense_row.html', {'expense': instance})
# Envolvemos el HTML con las directivas OOB (Out-of-Band) de HTMX
if created:
# Si es nuevo, lo insertamos al principio del tbody con id "expenses-tbody"
payload = f'<tbody id="expenses-tbody" hx-swap-oob="afterbegin">{html}</tbody>'
else:
# Si ya existe, HTMX reemplazará automáticamente el tr con el mismo ID si usamos hx-swap-oob="true"
payload = html.replace('<tr ', '<tr hx-swap-oob="true" ')
async_to_sync(channel_layer.group_send)(
'expenses_updates',
{
'type': 'expense_update',
'action': action,
'expense': expense_data
'html': payload
}
)
@receiver(post_delete, sender=Expense)
def expense_deleted(sender, instance, **kwargs):
channel_layer = get_channel_layer()
# Para borrar, mandamos un elemento vacío con swap out-of-band que reemplace la fila eliminada
payload = f'<tr id="expense-{instance.id}" hx-swap-oob="outerHTML"></tr>'
async_to_sync(channel_layer.group_send)(
'expenses_updates',
{
'type': 'expense_update',
'action': 'deleted',
'expense': {'id': instance.id}
'html': payload
}
)