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. gettextsi 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). Para desarrollo
local basta con:
DEBUG=True
Con eso, la app arranca con SQLite, SECRET_KEY de desarrollo, 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).
| Variable | Por defecto | Descripción |
|---|---|---|
SECRET_KEY |
clave insegura de desarrollo | Cámbiala en producción. |
DEBUG |
False |
Activa páginas de error detalladas, Django Debug Toolbar y Silk. No debe estar activo en producción. |
ALLOWED_HOSTS |
* |
Lista separada por comas. Debe restringirse 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:
SECRET_KEY: un valor único y secreto (no el de desarrollo).DEBUG=False.ALLOWED_HOSTS: los dominios reales, no*.CSRF_TRUSTED_ORIGINS: los orígenes reales (con esquema, p. ej.https://patrones.ejemplo.com).DATABASE_URL: apuntando a PostgreSQL.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).- 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=Truey las credenciales deS3_*— el filesystem local (por defecto) no es compartido ni persistente entre despliegues. - HTTPS: una vez la app esté detrás de HTTPS de verdad, activa
SECURE_SSL_REDIRECT=True,SESSION_COOKIE_SECURE=TrueyCSRF_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.
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).