Finish Form Rewrite

This commit is contained in:
2026-05-08 09:43:19 +02:00
parent 551057b067
commit a50cadc873
18 changed files with 163 additions and 167 deletions
-11
View File
@@ -1,11 +0,0 @@
from jinja2 import Environment
from django.urls import reverse
from django.templatetags.static import static
def environment(**options):
env = Environment(**options)
env.globals.update({
'static': static,
'url': reverse,
})
return env
+9 -8
View File
@@ -14,6 +14,7 @@ import logging
import os, sys import os, sys
from pathlib import Path from pathlib import Path
DEV_ENV = (sys.argv[1] == 'runserver')
RUNNING_TESTS = any(arg in {'test', 'pytest'} for arg in sys.argv) or 'PYTEST_CURRENT_TEST' in os.environ RUNNING_TESTS = any(arg in {'test', 'pytest'} for arg in sys.argv) or 'PYTEST_CURRENT_TEST' in os.environ
@@ -101,6 +102,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.forms',
'compressor', 'compressor',
] ]
@@ -136,14 +138,6 @@ TEMPLATES = [
], ],
}, },
}, },
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [BASE_DIR / 'templates/jinja2'],
'APP_DIRS': True,
'OPTIONS': {
'environment': 'proyecto.jinja2.environment',
},
}
] ]
WSGI_APPLICATION = 'proyecto.wsgi.application' WSGI_APPLICATION = 'proyecto.wsgi.application'
@@ -430,3 +424,10 @@ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True USE_X_FORWARDED_HOST = True
SECURE_REFERER_POLICY = "strict-origin-when-cross-origin" SECURE_REFERER_POLICY = "strict-origin-when-cross-origin"
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "tienda/form_snippet.html"
FORM_RENDERER = "proyecto.settings.CustomFormRenderer"
+27 -22
View File
@@ -1,46 +1,51 @@
amqp==5.3.1 amqp==5.3.1
asgiref==3.11.0 asgiref==3.11.1
billiard==4.2.4 billiard==4.2.4
celery==5.6.2 boto3==1.43.5
certifi==2026.1.4 botocore==1.43.5
celery==5.6.3
certifi==2026.4.22
cffi==2.0.0 cffi==2.0.0
charset-normalizer==3.4.4 charset-normalizer==3.4.7
click==8.3.1 click==8.3.3
click-didyoumean==0.3.1 click-didyoumean==0.3.1
click-plugins==1.1.1.2 click-plugins==1.1.1.2
click-repl==0.3.0 click-repl==0.3.0
cryptography==46.0.7 cryptography==48.0.0
Django==6.0.4 defusedxml==0.7.1
Django==6.0.5
django-appconf==1.2.0 django-appconf==1.2.0
django-redis==5.4.0 django-redis==6.0.0
django-storages==1.14.6
django_compressor==4.6.0 django_compressor==4.6.0
django-storages[boto3]==1.14.6 fonttools==4.62.1
gunicorn==25.1.0 fpdf2==2.8.7
idna==3.11 gunicorn==26.0.0
Jinja2==3.1.6 idna==3.13
jmespath==1.1.0
kombu==5.6.2 kombu==5.6.2
MarkupSafe==3.0.3 MarkupSafe==3.0.3
packaging==26.0 packaging==26.2
paypalrestsdk==1.13.3 paypalrestsdk==1.13.3
pillow==12.2.0 pillow==12.2.0
boto3==1.42.97
prompt_toolkit==3.0.52 prompt_toolkit==3.0.52
psycopg2-binary==2.9.12
pycparser==3.0 pycparser==3.0
pyOpenSSL==26.0.0 pyOpenSSL==26.2.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
rcssmin==1.2.2 rcssmin==1.2.2
redis==5.2.1 redis==7.4.0
requests==2.33.0 requests==2.33.1
rjsmin==1.2.5 rjsmin==1.2.5
s3transfer==0.17.0
six==1.17.0 six==1.17.0
sqlparse==0.5.5 sqlparse==0.5.5
stripe==14.3.0 stripe==15.1.0
typing_extensions==4.15.0 typing_extensions==4.15.0
tzdata==2025.3 tzdata==2026.2
tzlocal==5.3.1 tzlocal==5.3.1
urllib3==2.6.3 urllib3==2.6.3
vine==5.1.0 vine==5.1.0
wcwidth==0.6.0 wcwidth==0.7.0
whitenoise==6.12.0 whitenoise==6.12.0
fpdf2==2.8.7
psycopg2-binary==2.9.11
@@ -0,0 +1,23 @@
# Generated by Django 6.0.4 on 2026-05-07 08:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tienda', '0007_add_product_sku'),
]
operations = [
migrations.AlterField(
model_name='product',
name='briefdesc',
field=models.TextField(default='', max_length=250),
),
migrations.AlterField(
model_name='product',
name='description',
field=models.TextField(default='', max_length=5000),
),
]
+1 -1
View File
@@ -86,7 +86,7 @@ class Product(models.Model):
name = models.CharField(max_length=200, default="") name = models.CharField(max_length=200, default="")
sku = models.CharField(max_length=50, unique=True, blank=True, null=True) sku = models.CharField(max_length=50, unique=True, blank=True, null=True)
description = models.TextField(default = "", max_length=5000) description = models.TextField(default = "", max_length=5000)
briefdesc = models.TextField(default = "", max_length=1000) briefdesc = models.TextField(default = "", max_length=250)
price = models.FloatField(default = 0) price = models.FloatField(default = 0)
stock = models.PositiveIntegerField(default=0) stock = models.PositiveIntegerField(default=0)
category = models.ForeignKey(Category, on_delete=models.CASCADE) category = models.ForeignKey(Category, on_delete=models.CASCADE)
+4 -8
View File
@@ -11,21 +11,19 @@ from .models import User, VerificationCode
@shared_task @shared_task
def enviar_correo_bienvenida(email_usuario: str, nombre_usuario: str): def enviar_correo_bienvenida(email_usuario: str, nombre_usuario: str):
html_content = render_to_string( html_content = render_to_string(
'emails/welcome.html', 'tienda/emails/welcome.html',
{ {
"name": nombre_usuario "name": nombre_usuario
}, },
using='jinja2'
) )
send_hemail(email_usuario, "Inicio de Sesión correcto", html_content, "Has iniciado sesión...") send_hemail(email_usuario, "Inicio de Sesión correcto", html_content, "Has iniciado sesión...")
@shared_task @shared_task
def banear_usuario(email_usuario: str): def banear_usuario(email_usuario: str):
html_content = render_to_string( html_content = render_to_string(
'emails/ban.html', 'tienda/emails/ban.html',
{ {
}, },
using='jinja2'
) )
send_hemail(email_usuario, "Cuenta Bloqueada", html_content, "Tu cuenta ha sido bloqueada...") send_hemail(email_usuario, "Cuenta Bloqueada", html_content, "Tu cuenta ha sido bloqueada...")
@@ -33,9 +31,8 @@ def banear_usuario(email_usuario: str):
@shared_task @shared_task
def desbanear_usuario(email_usuario: str): def desbanear_usuario(email_usuario: str):
html_content = render_to_string( html_content = render_to_string(
'emails/unban.html', 'tienda/emails/unban.html',
{}, {},
using='jinja2'
) )
send_hemail(email_usuario, "Cuenta Desbloqueada", html_content, "Tu cuenta ha sido desbloqueada...") send_hemail(email_usuario, "Cuenta Desbloqueada", html_content, "Tu cuenta ha sido desbloqueada...")
@@ -67,14 +64,13 @@ def enviar_correo_recuperacion(email: str):
) )
ver_code.save() ver_code.save()
html_content = render_to_string( html_content = render_to_string(
'emails/reset_pass.html', 'tienda/emails/reset_pass.html',
{ {
"name": usuario.get_full_name(), "name": usuario.get_full_name(),
"domain": settings.DOMAIN, "domain": settings.DOMAIN,
"protocol": settings.PROTOCOL, "protocol": settings.PROTOCOL,
"code": ver_code.code "code": ver_code.code
}, },
using='jinja2'
) )
send_hemail(email, "Reset de Contraseña", html_content, "Estas reseteando la contraseña...") send_hemail(email, "Reset de Contraseña", html_content, "Estas reseteando la contraseña...")
+1 -68
View File
@@ -13,74 +13,7 @@
<div class="card-body"> <div class="card-body">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form }}
<!-- Nombre del producto -->
<div class="mb-3">
<label for="name" class="form-label">Nombre del Producto <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="name" name="name" required maxlength="200"
placeholder="Ej: iPhone 15 Pro Max">
</div>
<!-- Descripción breve -->
<div class="mb-3">
<label for="briefdesc" class="form-label">Descripción Breve</label>
<input type="text" class="form-control" id="briefdesc" name="briefdesc" maxlength="250"
placeholder="Una descripción corta para mostrar en las tarjetas de producto">
<div class="form-text">Opcional. Se mostrará en las vistas de listado de productos.</div>
</div>
<!-- Descripción completa -->
<div class="mb-3">
<label for="description" class="form-label">Descripción Completa <span class="text-danger">*</span></label>
<textarea class="form-control" id="description" name="description" rows="5" required
placeholder="Describe tu producto en detalle..."></textarea>
</div>
<!-- Precio -->
<div class="mb-3">
<label for="price" class="form-label">Precio <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text"></span>
<input type="number" class="form-control" id="price" name="price" required
min="0" step="0.01" placeholder="0.00">
</div>
</div>
<!-- Stock -->
<div class="mb-3">
<label for="stock" class="form-label">Stock disponible <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="stock" name="stock" required
min="0" step="1" placeholder="0">
<div class="form-text">Cantidad máxima que podrán comprar los clientes.</div>
</div>
<!-- Categoría -->
<div class="mb-3">
<label for="category" class="form-label">Categoría <span class="text-danger">*</span></label>
<select class="form-select" id="category" name="category" required>
<option value="" selected disabled>Selecciona una categoría</option>
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<!-- Imagen principal -->
<div class="mb-3">
<label for="primary_image" class="form-label">Imagen Principal</label>
<input type="file" class="form-control" id="primary_image" name="primary_image"
accept="image/*">
<div class="form-text">Opcional. Esta será la imagen destacada del producto.</div>
</div>
<!-- Imágenes secundarias -->
<div class="mb-4">
<label for="secondary_images" class="form-label">Imágenes Secundarias</label>
<input type="file" class="form-control" id="secondary_images" name="secondary_images"
accept="image/*" multiple>
<div class="form-text">Opcional. Puedes seleccionar múltiples imágenes adicionales.</div>
</div>
<!-- Botones --> <!-- Botones -->
<div class="d-flex justify-content-end gap-2"> <div class="d-flex justify-content-end gap-2">
<a href="{% url 'mis_productos' %}" class="btn btn-secondary">Cancelar</a> <a href="{% url 'mis_productos' %}" class="btn btn-secondary">Cancelar</a>
@@ -0,0 +1,6 @@
{% for field in form %}
<div class="mb-3">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
@@ -0,0 +1,81 @@
{% extends "tienda/base.html" %}
{% load static %}
{% block content %}
<div class="row mt-4 mb-5">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2>Gestionar Imágenes</h2>
<p class="text-muted mb-0">Producto: <strong>{{ producto.name }}</strong></p>
</div>
<a href="{% url 'mis_productos' %}" class="btn btn-outline-secondary">← Volver a Mis Productos</a>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Imagen Principal</h5>
</div>
<div class="card-body">
{% if producto.primary_image %}
<img src="{{ producto.primary_image.image.url }}" alt="{{ producto.primary_image.alt|default:producto.name }}" class="rounded" style="width: 200px; height: 200px; object-fit: cover;">
<p class="mt-2 text-muted mb-0">Esta imagen no se puede cambiar desde aquí.</p>
{% else %}
<p class="text-muted">No hay imagen principal asignada.</p>
{% endif %}
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Imágenes Secundarias</h5>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#agregarImagenModal">
Agregar Imagen
</button>
</div>
<div class="card-body">
{% if secondary_images %}
<div class="row">
{% for img in secondary_images %}
<div class="col-md-3 col-sm-4 col-6 mb-3">
<div class="card">
<img src="{{ img.image.url }}" alt="{{ img.alt|default:producto.name }}" class="card-img-top" style="height: 180px; object-fit: cover;">
<div class="card-body p-2">
<form method="POST" action="{% url 'eliminar_imagen_secundaria' producto.id img.id %}" onsubmit="return confirm('¿Seguro que quieres eliminar esta imagen?');">
{% csrf_token %}
<button type="submit" class="btn btn-outline-danger btn-sm w-100">🗑 Eliminar</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted text-center py-4">No hay imágenes secundarias. ¡Agrega una!</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="modal fade" id="agregarImagenModal" tabindex="-1" aria-labelledby="agregarImagenModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="agregarImagenModalLabel">Agregar Imagen Secundaria</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
{{ form }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Subir Imagen</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
+1 -16
View File
@@ -12,22 +12,7 @@
<form method="post" action="{% url 'login' %}"> <form method="post" action="{% url 'login' %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> {{ form }}
<label for="loginEmail" class="form-label">Correo Electrónico</label>
<input type="email" class="form-control" id="loginEmail" name="email" required>
</div>
<div class="mb-3">
<label for="loginPassword" class="form-label">Contraseña</label>
<input type="password" class="form-control" id="loginPassword" name="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="rememberMe" name="remember">
<label class="form-check-label" for="rememberMe">
Recordarme
</label>
</div>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Iniciar Sesión</button> <button type="submit" class="btn btn-primary">Iniciar Sesión</button>
@@ -57,6 +57,7 @@
<td class="text-end">{{ producto.stock }}</td> <td class="text-end">{{ producto.stock }}</td>
<td class="text-end"> <td class="text-end">
<div class="d-flex justify-content-end gap-2"> <div class="d-flex justify-content-end gap-2">
<a href="{% url 'gestionar_imagenes' producto.id %}" class="btn btn-outline-secondary btn-sm">Gestionar Imágenes</a>
<a href="{% url 'editar_producto' producto.id %}" class="btn btn-outline-primary btn-sm">Editar</a> <a href="{% url 'editar_producto' producto.id %}" class="btn btn-outline-primary btn-sm">Editar</a>
<form method="POST" action="{% url 'borrar_producto' producto.id %}" onsubmit="return confirm('¿Seguro que quieres borrar este producto?');"> <form method="POST" action="{% url 'borrar_producto' producto.id %}" onsubmit="return confirm('¿Seguro que quieres borrar este producto?');">
{% csrf_token %} {% csrf_token %}
+1 -27
View File
@@ -12,33 +12,7 @@
<form method="post" action="{% url 'register' %}"> <form method="post" action="{% url 'register' %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> {{ form }}
<label for="registerName" class="form-label">Nombre Completo</label>
<input type="text" class="form-control" id="registerName" name="name" required>
</div>
<div class="mb-3">
<label for="registerEmail" class="form-label">Correo Electrónico</label>
<input type="email" class="form-control" id="registerEmail" name="email" required>
</div>
<div class="mb-3">
<label for="registerPassword" class="form-label">Contraseña</label>
<input type="password" class="form-control" id="registerPassword" name="password" required>
<div class="form-text">La contraseña debe tener al menos 8 caracteres.</div>
</div>
<div class="mb-3">
<label for="registerPasswordConfirm" class="form-label">Confirmar Contraseña</label>
<input type="password" class="form-control" id="registerPasswordConfirm" name="password_confirm" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="acceptTerms" name="terms" required>
<label class="form-check-label" for="acceptTerms">
Acepto los <a href="{% url 'terminos' %}" target="_blank">términos y condiciones</a>
</label>
</div>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Crear Cuenta</button> <button type="submit" class="btn btn-primary">Crear Cuenta</button>
+2
View File
@@ -18,6 +18,8 @@ urlpatterns = [
path("venta/crear-producto/", views.crear_producto, name="crear_producto"), path("venta/crear-producto/", views.crear_producto, name="crear_producto"),
path("venta/editar-producto/<int:id>/", views.editar_producto, name="editar_producto"), path("venta/editar-producto/<int:id>/", views.editar_producto, name="editar_producto"),
path("venta/borrar-producto/<int:id>/", views.borrar_producto, name="borrar_producto"), path("venta/borrar-producto/<int:id>/", views.borrar_producto, name="borrar_producto"),
path("venta/gestionar-imagenes/<int:id>/", views.gestionar_imagenes, name="gestionar_imagenes"),
path("venta/gestionar-imagenes/<int:product_id>/eliminar/<int:image_id>/", views.eliminar_imagen_secundaria, name="eliminar_imagen_secundaria"),
# Carrito # Carrito
path("cart/", views.view_cart, name="view_cart"), path("cart/", views.view_cart, name="view_cart"),
path("cart/add/<int:product_id>/", views.add_to_cart, name="add_to_cart"), path("cart/add/<int:product_id>/", views.add_to_cart, name="add_to_cart"),