Files
crochet/README.md
T
pablo 5fe349d617
CI / test (push) Successful in 1m2s
CI / build (push) Successful in 50s
feat: added improvements from django-htmx-template
2026-09-21 14:27:17 +02:00

11 KiB

Crochet

Aplicación web para diseñar patrones de crochet con un editor visual, exportarlos a PDF y compartirlos con un enlace de solo lectura. Incluye cuentas de usuario para guardar y gestionar los propios patrones.

Funcionalidades

  • Editor visual de patrones: secciones de título, subtítulo, texto, nota, materiales, imágenes y "patrón" (puntos con contador), todo reordenable por arrastre. El contenido se guarda bilingüe (es/en).
  • Personalización de página: título, autor, imagen de portada, color de texto/acento/fondo, tipografía, tamaño de letra, alineación, tamaño y orientación de página.
  • Exportación a PDF con WeasyPrint, usando el propio diálogo de impresión del navegador.
  • Vista de solo lectura compartible (pattern/<uuid>/), sin necesidad de cuenta ni de cargar el editor.
  • Cuentas de usuario: registro con verificación de email, login/logout (con verificación en dos pasos opcional por TOTP + códigos de respaldo), ajustes de cuenta (cambiar email/contraseña, con HTMX) y recuperación de contraseña por email. Login, registro y recuperación de contraseña están protegidos contra fuerza bruta (django-ratelimit). Cada patrón pertenece a quien lo creó; solo su propietario puede editarlo o borrarlo.
  • "Mis patrones": listado de los patrones propios con portada, fecha de actualización y accesos directos a editar/ver/eliminar.
  • i18n completo (español/inglés): interfaz, URLs traducidas (/es/patron/... frente a /en/pattern/...) y contenido del propio patrón.

Stack técnico

  • Backend: Django 6, servido en ASGI con Uvicorn.
  • Base de datos: PostgreSQL en producción, SQLite por defecto en desarrollo (configurable con DATABASE_URL).
  • Frontend del editor: JavaScript vanilla (sin framework ni bundler), daisyUI + Tailwind CSS vía CDN con compilador JIT.
  • Vista de solo lectura / PDF: Tailwind precompilado sin JS (WeasyPrint no ejecuta JavaScript), generado con el CLI standalone de Tailwind (ver python manage.py build_pattern_detail_css).
  • Imágenes: Pillow (variantes de la portada del patrón en varios tamaños), almacenamiento en filesystem o S3 (django-storages, opcional).
  • Estáticos: WhiteNoise con manifest comprimido.
  • HTMX para los formularios de ajustes de cuenta.
  • Gestión de dependencias: uv.

Desarrollo

Requisitos

  • Python 3.13
  • uv
  • Librerías nativas de WeasyPrint (Pango, Cairo, GDK-Pixbuf) si vas a generar/probar el PDF fuera de Docker — en Debian/Ubuntu: apt-get install libpango-1.0-0 libpangocairo-1.0-0 fonts-dejavu-core.
  • gettext si vas a regenerar/compilar traducciones (msgfmt/msguniq/etc.) — en Debian/Ubuntu: apt-get install gettext.

Puesta en marcha

git clone <url-del-repositorio>
cd crochet
uv sync --group dev

Crea un archivo .env en la raíz del proyecto (ver Variables de entorno más abajo). SECRET_KEY y ALLOWED_HOSTS son las únicas que no tienen un valor por defecto (a propósito: una clave/lista de hosts insegura conocida de antemano no protege nada), así que hacen falta incluso en desarrollo local:

DEBUG=True
SECRET_KEY=cualquier-cadena-larga-y-aleatoria
ALLOWED_HOSTS=*

Con eso, la app arranca con SQLite, envío de email al backend de consola (se imprime en la terminal en vez de mandarse de verdad) y sin credenciales adicionales.

uv run python manage.py migrate
uv run python manage.py createsuperuser  # opcional, para /admin/
uv run python manage.py runserver

La app queda disponible en http://localhost:8000/.

Tests

uv run pytest

pytest-cov está incluido; para ver cobertura: uv run pytest --cov.

Traducciones

Las cadenas ya traducidas están compiladas y listas para usar. Si añades o cambias texto traducible ({% trans %}/{% blocktrans %}, gettext_lazy):

uv run python manage.py makemessages -l es -l en
# revisa a mano cualquier entrada marcada como "#, fuzzy" antes de compilar
uv run python manage.py compilemessages

CSS precompilado de la vista de solo lectura / PDF

Solo hace falta volver a generarlo si cambian las clases de Tailwind usadas en pattern_detail.html o pattern_render.py:

uv run python manage.py build_pattern_detail_css

Docker

docker build -t crochet .
docker run --rm -p 8000:8000 --env-file .env crochet

El Dockerfile instala las dependencias del sistema necesarias para WeasyPrint y ejecuta scripts/build.sh (CSS precompilado, collectstatic, compilemessages) al construir la imagen, y scripts/run.sh (Uvicorn) al arrancar el contenedor.

Entorno local con Postgres + S3 (Garage)

docker-compose.yml levanta un Postgres y un Garage (S3 self-hosted) para poder probar S3_ENABLED sin depender de un bucket real:

docker compose up -d

Con eso, en tu .env:

DATABASE_URL=postgres://django:django@localhost:6543/django
S3_ENABLED=True
S3_ACCESS_KEY_ID=GKa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
S3_SECRET_ACCESS_KEY=f0e1d2c3b4a5968778695a4b3c2d1e0ff0e1d2c3b4a5968778695a4b3c2d1e0f
S3_STORAGE_BUCKET_NAME=default-bucket
S3_ENDPOINT_URL=http://localhost:3900
S3_REGION_NAME=garage
S3_USE_SSL=False
S3_ADDRESSING_STYLE=path

La web de administración de Garage queda en http://localhost:3909/ (usuario admin, contraseña garage-dev; cámbiala en docker-compose.yml si vas a exponerla más allá de tu propia máquina).

Herramientas de desarrollo

make help lista los atajos disponibles (make test, make lint, make format, make messages...). El estilo del código se comprueba con ruff (make lint); make precommit-install instala un hook de pre-commit que lo ejecuta automáticamente en cada commit.

Variables de entorno

Todas se leen en config/settings/env.py; sin .env, o si falta alguna, se usan los valores por defecto indicados (pensados para desarrollo local). SECRET_KEY y ALLOWED_HOSTS son la excepción: no tienen valor por defecto, así que el arranque falla con un error claro (ImproperlyConfigured) si faltan, en vez de servir tráfico con una clave insegura conocida de antemano o con el check de Host desactivado de hecho.

Variable Por defecto Descripción
SECRET_KEY (obligatoria) Clave única y secreta. Distinta en cada entorno, nunca la de otro despliegue.
DEBUG False Activa páginas de error detalladas, Django Debug Toolbar y Silk. No debe estar activo en producción.
ALLOWED_HOSTS (obligatoria) Lista separada por comas. * vale para desarrollo local; restringida a los dominios reales en producción.
CSRF_TRUSTED_ORIGINS http://localhost:8000 Lista separada por comas. Necesario si la app se sirve detrás de un dominio/HTTPS distinto.
DATABASE_URL sqlite:///db.sqlite3 Formato django-environ (p. ej. postgres://usuario:password@host:5432/nombre_bd).
S3_ENABLED False Si es True, los archivos subidos (imágenes de portada/patrón) se guardan en S3 en vez de en el filesystem local.
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY / S3_STORAGE_BUCKET_NAME / S3_ENDPOINT_URL vacío Credenciales del bucket, solo necesarias si S3_ENABLED=True.
S3_REGION_NAME / S3_USE_SSL / S3_ADDRESSING_STYLE vacío / True / auto Para S3 compatibles que lo exigen (p. ej. Garage, ver entorno local con Garage): región obligatoria y sin soporte de direccionamiento "virtual" por defecto.
EMAIL_BACKEND backend de consola Usa django.core.mail.backends.smtp.EmailBackend para enviar emails de verdad (verificación de cuenta, recuperación de contraseña...).
EMAIL_HOST / EMAIL_HOST_USER / EMAIL_HOST_PASSWORD / EMAIL_PORT vacío / vacío / vacío / 587 Credenciales SMTP, solo con el backend SMTP.
DEFAULT_FROM_EMAIL webmaster@localhost Remitente de los emails salientes. La mayoría de proveedores SMTP rechazan enviar si no coincide con la cuenta autenticada (o un alias verificado).
SUPPORT_EMAIL soporte@localhost Dirección de contacto que se muestra en el aviso que recibe el email anterior de una cuenta cuando alguien lo cambia (ver EmailUpdateForm).
SITE_NAME Crochet Nombre mostrado en los emails de aviso de la cuenta (registro, verificación en dos pasos...).
RATELIMIT_ENABLE True Desactiva la limitación de peticiones en login/registro/recuperación de contraseña/verificación en dos pasos (pensado para tests o depuración, no para producción).
SECURE_SSL_REDIRECT / SESSION_COOKIE_SECURE / CSRF_COOKIE_SECURE False Actívalas en producción una vez la app esté detrás de HTTPS de verdad; en desarrollo local (HTTP plano) dejarlas activas rompe la sesión/CSRF.
SECURE_HSTS_SECONDS / SECURE_HSTS_INCLUDE_SUBDOMAINS / SECURE_HSTS_PRELOAD 0 / False / False HSTS; empieza con un valor bajo (p. ej. 3600) antes de subirlo a uno largo (p. ej. 31536000 = 1 año) — un valor largo mal configurado deja el dominio inaccesible por HTTPS durante ese tiempo.
LOG_LEVEL INFO Nivel de log de Django y de la app (crochet).

Existen además CORS_ORIGIN_WHITELIST, REDIS_HOST, REDIS_PORT, CELERY_BROKER_URL, PAGE_SIZE e ITEMS_PER_PAGE en env.py: quedaron de una plantilla de proyecto y actualmente no los usa ninguna parte de la aplicación (no hay Celery, cachés en Redis, CORS ni paginación configurados), así que no hace falta definirlos para desplegar.

Despliegue a producción

Como mínimo hay que ajustar, respecto al .env de desarrollo:

  1. SECRET_KEY: un valor único y secreto (no el de desarrollo).
  2. DEBUG=False.
  3. ALLOWED_HOSTS: los dominios reales, no *.
  4. CSRF_TRUSTED_ORIGINS: los orígenes reales (con esquema, p. ej. https://patrones.ejemplo.com).
  5. DATABASE_URL: apuntando a PostgreSQL.
  6. EMAIL_BACKEND/EMAIL_HOST/EMAIL_HOST_USER/EMAIL_HOST_PASSWORD/DEFAULT_FROM_EMAIL: sin esto, el registro de usuarios funciona pero la recuperación de contraseña no llega a enviarse (se queda en el log en vez de salir por SMTP).
  7. Almacenamiento de archivos: si vas a correr más de una instancia o quieres que las imágenes sobrevivan a un redeploy del contenedor, configura S3_ENABLED=True y las credenciales de S3_* — el filesystem local (por defecto) no es compartido ni persistente entre despliegues.
  8. HTTPS: una vez la app esté detrás de HTTPS de verdad, activa SECURE_SSL_REDIRECT=True, SESSION_COOKIE_SECURE=True y CSRF_COOKIE_SECURE=True (y, si quieres, SECURE_HSTS_SECONDS — ver la tabla de arriba antes de subirlo a un valor largo).

La imagen Docker ya ejecuta collectstatic, compilemessages y el CSS precompilado al construirse, y sirve la app con Uvicorn (variables WSGI_HOST/WSGI_WORKERS para ajustar host/nº de workers). WhiteNoise se encarga de servir los estáticos directamente desde la propia app, sin necesidad de un servidor/proxy de estáticos aparte; también sirve /media/ (portadas/imágenes de patrón) cuando S3_ENABLED=False (ver crochet/middleware.py), así que tampoco hace falta nada aparte para eso salvo que uses S3.

Al hacer push a master se dispara la integración continua (tests) y, si pasan, se construye y publica una imagen Docker (ver .gitea/workflows/test-build.yaml).