Files
crochet/README.md
T
pablo 4b0d404116
CI / test (push) Successful in 2m13s
CI / build (push) Successful in 55s
feat: included 2FA and lots of feature from django-htmx-template
2026-09-16 13:20:44 +02:00

227 lines
11 KiB
Markdown

# 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](https://docs.astral.sh/uv/).
## Desarrollo
### Requisitos
- Python 3.13
- [uv](https://docs.astral.sh/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
```bash
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](#variables-de-entorno) más abajo). Para desarrollo
local basta con:
```env
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.
```bash
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
```bash
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`):
```bash
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`:
```bash
uv run python manage.py build_pattern_detail_css
```
### Docker
```bash
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](https://garagehq.deuxfleurs.fr/) (S3 self-hosted) para poder
probar `S3_ENABLED` sin depender de un bucket real:
```bash
docker compose up -d
```
Con eso, en tu `.env`:
```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](https://docs.astral.sh/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](#entorno-local-con-postgres--s3-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.
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`).