239 lines
11 KiB
Markdown
239 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). `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:
|
|
|
|
```env
|
|
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.
|
|
|
|
```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).
|
|
`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](#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; 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`).
|