Metadata-Version: 2.4
Name: django-weasyprint-reports
Version: 0.1.0
Summary: Reportes PDF genéricos para Django con templates HTML y WeasyPrint (sin guardar archivos).
Author: Hector Leal
License-Expression: LGPL-3.0-or-later
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: COPYING
Requires-Dist: Django>=4.2
Requires-Dist: weasyprint>=70.0
Dynamic: license-file

# django-weasyprint-reports

Reportes PDF genéricos para Django: **datos → template HTML → WeasyPrint → PDF → `HttpResponse`**.

- Usa templates normales de Django (sin XML) y WeasyPrint directamente.
- El PDF se genera bajo demanda **en memoria**: nunca se guarda en disco, en `MEDIA_ROOT` ni en la base de datos.
- No conoce ningún modelo: sirve para cualquiera.
- Incluye una acción **Imprimir** para los listados del admin.

Requisitos: Python 3.10+, Django 4.2+, WeasyPrint 70+.

## Instalación

```bash
pip install django-weasyprint-reports
```

Desde el repositorio: `pip install .` (o `pip install git+<url-del-repo>`).

Añade la app a `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    ...
    "django.contrib.staticfiles",
    "weasyprint_reports",
]
```

No tiene modelos ni migraciones, no hace falta `migrate`.

### Dependencias del sistema (Pango)

WeasyPrint necesita la librería **Pango** del sistema operativo. Sin ella aparece
`OSError: cannot load library '...libgobject-2.0-0.dll'` al generar un PDF.
Compruébalo con `python -m weasyprint --info`.

**Ubuntu / Debian**

```bash
sudo apt install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
```

**macOS**

```bash
brew install pango
```

**Windows**

1. Instala [MSYS2](https://www.msys2.org/) con las opciones por defecto (`C:\msys64`).
2. En el shell **MSYS2 UCRT64** ejecuta `pacman -S mingw-w64-ucrt-x86_64-pango`.
3. Si usaste la ruta por defecto no hay que hacer nada más: la app la detecta sola. Si instalaste
   MSYS2 en otro sitio, indícalo en `settings.py`:

   ```python
   WEASYPRINT_DLL_DIRECTORIES = r"D:\otra\ruta\ucrt64\bin"
   ```

La app antepone esa carpeta al `PATH` antes de importar WeasyPrint, para que sus DLL ganen a las de
otros programas (p. ej. Tesseract-OCR, que provoca el error `0x7e`).

Sin WeasyPrint o sin Pango el proyecto sigue arrancando; el error aparece solo al generar un PDF.

Más detalles: <https://doc.courtbouillon.org/weasyprint/stable/first_steps.html>

## Uso desde una vista

```python
from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import get_object_or_404
from weasyprint_reports.pdf import generar_pdf

@login_required
@permission_required("ventas.view_cliente", raise_exception=True)
def reporte_cliente(request, pk):
    cliente = get_object_or_404(Cliente.objects.prefetch_related("pedidos"), pk=pk)
    return generar_pdf(
        template="ventas/cliente_pdf.html",          # el nombre lo decide el programador
        context={"cliente": cliente},
        filename="cliente.pdf",
        request=request,                          # opcional: habilita request/user en el template
    )
```

`generar_pdf(template, context=None, *, filename="reporte.pdf", request=None, descargar=False)`
devuelve un `HttpResponse`. Con `descargar=True` fuerza la descarga; por defecto se muestra en el navegador.
`renderizar_pdf(template, context=None, *, request=None)` devuelve los `bytes` (p. ej. para adjuntar a un correo).

### Template

```django
{% extends "weasyprint_reports/base.html" %}
{% block header %}Reporte de cliente{% endblock %}
{% block content %}
  <h1>{{ cliente.nombre }}</h1>
  <table>
    <thead><tr><th>Pedido</th><th>Total</th></tr></thead>
    <tbody>
      {% for p in cliente.pedidos.all %}
        <tr><td>{{ p.numero }}</td><td class="num">{{ p.total|floatformat:2 }}</td></tr>
      {% endfor %}
    </tbody>
  </table>
{% endblock %}
```

`weasyprint_reports/base.html` define página A4, header y footer repetidos en cada página y "Página X de Y".
Bloques disponibles: `title`, `header`, `footer`, `extra_css`, `content`, `lang`.
Puedes sobrescribir `weasyprint_reports/base.html` en tu proyecto para poner tu logotipo, colores, etc.

Imágenes y CSS: usa `{% static '...' %}` (y `MEDIA_URL` para archivos subidos). Son los únicos
archivos que WeasyPrint puede cargar, además de URIs `data:`.

## Acción "Imprimir" en el admin

```python
from django.contrib import admin
from weasyprint_reports.mixins import ReportAdminMixin

@admin.register(Cliente)
class ClienteAdmin(ReportAdminMixin, admin.ModelAdmin):
    report = True
    list_display = ("nombre", "email", "activo")     # columnas del PDF
```

En el listado aparece **Imprimir** en el desplegable de acciones: marcas los registros, pulsas el botón
y el PDF se abre en una pestaña nueva (el listado queda como estaba).

Opciones del `ModelAdmin`:

| Atributo | Por defecto | Descripción |
|---|---|---|
| `report` | `False` | Activa la acción. |
| `report_template` | `weasyprint_reports/admin_list.html` | Template propio; recibe `objects` y `opts`. |
| `report_max` | `500` | Máximo de registros por PDF. |

Para añadir datos a un template propio sobrescribe `get_report_context(self, request, queryset)`.
La acción solo la ven quienes tienen permiso de **ver** el modelo y solo imprime registros que el admin
les permite ver.

## Seguridad

- El nombre del template lo decide siempre el programador, nunca el usuario; no se acepta HTML en bruto.
- Autoescape de Django activo: no uses `|safe` ni `mark_safe` con datos de usuarios.
- WeasyPrint solo carga archivos de `STATIC_URL`/`MEDIA_URL` y URIs `data:`. Quedan bloqueados `http(s)`
  externo (SSRF), `file://` y cualquier otro esquema, y se rechaza el path traversal.
- Nombre de archivo saneado; respuestas con `Cache-Control: no-store`.
- Filtra siempre el queryset por lo que el usuario puede ver antes de generar el reporte.
- Generar PDFs consume CPU: limita la frecuencia si el endpoint queda expuesto.

## Tests

```bash
python manage.py test weasyprint_reports
```

Son autocontenidos (usan su propio admin y templates en memoria). Los que usan WeasyPrint real se
omiten si no está disponible.

## Licencia

Copyright (C) 2026 Hector Leal.

Este software es libre y se distribuye bajo los términos de la **GNU Lesser General Public License v3.0
o posterior** (`LGPL-3.0-or-later`). Los textos completos están en los archivos
[LICENSE](../LICENSE) (LGPL 3) y [COPYING](../COPYING) (GPL 3, a la que remite la LGPL).

En la práctica: puedes usar esta librería en proyectos propios o comerciales sin liberar tu código,
pero si modificas la propia librería debes publicar esas modificaciones bajo la misma licencia.
Esto no es asesoría legal.
