Compare commits

11 Commits

Author SHA1 Message Date
8a4cb3964e Actualizar README.md 2026-08-11 01:53:55 -04:00
juse
8b0204a344 ajuste de tabla 2026-08-08 21:54:13 -04:00
juse
68a19230ed ajuste de medios adjuntos 2026-08-08 21:25:24 -04:00
juse
b37aebd4ba Ajuste del enlace con minIO 2026-08-07 23:40:23 -04:00
e072c77c69 Actualizar README.md 2026-08-07 21:04:36 -04:00
c6e7e88c06 Actualizar README.md 2026-08-07 20:53:56 -04:00
f25040b4d7 Actualizar README.md 2026-08-07 20:52:44 -04:00
4998bb0c85 Actualizar README.md 2026-08-07 20:52:02 -04:00
28597979a7 Actualizar README.md 2026-08-07 20:48:01 -04:00
Juan Burgos
ce15e86252 Guía actualizada 2026-08-08 00:42:42 +00:00
Juan Burgos
7795ba0703 Protección del token de mapbox 2026-08-07 23:32:37 +00:00
6 changed files with 139 additions and 45 deletions

View File

@@ -1,6 +1,92 @@
# exportPDF dev/no_docker # exportPDF
> **Precaución:** > **Precaución:**
> Para instalar en modo producción, utlize la rama main > Para instalar en modo desarrollo utilize la rama dev/no_docker
Funcionalidad para implementar la capacidad de exportar los datos de un recurso a formato PDF. Funcionalidad para implementar la capacidad de exportar los datos de un recurso a formato PDF.
esta rama es para el caso de un entorno de desarrollo clásico, es decir sin utilizar contenedores Los modelos de recursos habilitados para la función son los siguientes:
* Patrimonio material - Bienes inmuebles
* Patrimonio material - Bienes muebles
* Patrimonio inmaterial
* Organización o persona
# Para agregar la funcionalidad ExportPDF a un proyecto de Arches puede seguir el siguiente procedimiento.
Procedimiento válido para un proyecto en modo desarrollo implementado con un enfoque tradicional sin contenedores.
Para el caso de un enfoque utilizando contenedores o en modo Producción, el proceso varía levemente.
### 0. Instalar dependencias y librerias de python (servidor Ubuntu)
Solo en caso de que su proyecto de arches haya sido desplegado según la guía oficial de archesproject si desplegó según las guías del repositorio gitea.patrimonio.org.bo, no necesita instalar las dependencias de ubuntu
```shell
sudo apt install build-essential python3.12-dev python3.12-pip python3.12-setuptools python3.12-wheel python3.12-cffi
sudo apt install build-essential python3-dev python3-pip python3-setuptools python3-wheel python3-cffi libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev shared-mime-info
```
y en el entorno de (arches) python:
```python
pip install django-weasyprint
pip install pypdf
```
### 1. Clonar este repositorio en alguna carpeta de su servidor.
```shell
# puede ser en el home del servidor
git clone https://gitea.patrimonio.org.bo/sysadmin/sprpcb-exportpdf-func.git
```
### 2. Copiar los archivos necesarios para crear una nueva plantilla de reporte que contendrá los botones necesarios:
```shell
cp -r ../sprpcb-exportpdf-func/reportes ./mi_proyecto/reportes
cp ../sprpcb-exportpdf-func/templates_views_report-templates/export-pdf-report.htm ./mi_proyecto/templates/views/report-templates/
cp ../sprpcb-exportpdf-func/media_js_reports/export-pdf-report.js ./mi_proyecto/media/js/reports/
```
### 3. Registrar la plantilla personalizada de proyecto que agrega los botones necesarios:
```python
python manage.py report register -s mi_proyecto/reportes/export-pdf-report.json
```
### 4. Agregar la vista personalizada creando (copiando) las carpetas indicadas y luego los contenidos respectivos:
```shell
# en caso de que el repo clonado esté en el home y desde la 1er carpeta mi_proyecto
cp -r ../sprpcb-exportpdf-func/pdf ./mi_proyecto/pdf
```
### 5. Copiar las plantillas pdf correspondientes a los modelos de recursos:
```shell
cp -r ../sprpcb-exportpdf-func/templates_reports ./mi_proyecto/templates/reports
```
### 6. Agregar una fuente y una entrada de ruta en el archivo urls.py como valor de urlpatterns:
```shell
from .pdf.views import export_resource_pdf
#urlpatterns = [
# ...
path('export/pdf/<str:resourceid>/<str:printubic>/<str:satmap>/<str:printimgs>/<str:printpdfs>/', export_resource_pdf, name='export_resource_pdf'),
# project-level urls
#]
```
### 7. A continuación, asegúrese de reconstruir la interfaz de su proyecto para habilitar los cambios:
```
npm run build_development
```
### 8. Configuración final
Por último, se deben modificar los modelos de recursos para que adopten la nueva plantilla de reporte registrada:
Cuando haya terminado, debería ver los botones de exportar en la ventana de vista de reporte de su modelo de recursos:
![Alt Text](captura_exportPDF.jpg)
### 9. Forma de uso:
El botón "Exportar a PDF", de manera predeterminada, crea el reporte PDF incluyendo casi todos los datos del recurso. Y de manera opcional se pueden incluir 3 características adicionales a través de los botones adyacentes que controlan los siguientes parámetros:
* **Incluir ubicación detallada** Se puede incluir datos de ubicación (geo-localización) que podrían estar contraindicados en algunos tipos de recursos.
* **Mapa satelital** Si la anterior opción está habilitada, puede cambiar el tipo de mapa de simple a satelital.
* **Adjuntar imágenes** Una galería de imágenes distribuida en mosaicos de 2x6 imágenes por hoja a manera de Anexo al informe (existen recursos que tienen más de 20 imágenes relacionadas)
* **Adjuntar documentos (pdf)** se ajuntan los documentos relacionados en el mismo archivo pdf del informe

BIN
captura_exportPDF.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,11 +1,10 @@
# mi_proyecto/pdf/views.py # mi_proyecto/pdf/views.py
# dpsk - 2026
import io import io
from pathlib import Path from pathlib import Path
from django.http import HttpResponse from django.http import HttpResponse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from weasyprint import HTML from weasyprint import HTML
from arches.app.models.models import GraphModel
from arches.app.models.resource import Resource from arches.app.models.resource import Resource
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from datetime import datetime from datetime import datetime
@@ -13,6 +12,8 @@ from zoneinfo import ZoneInfo
from functools import reduce from functools import reduce
from operator import getitem from operator import getitem
from pypdf import PdfReader, PdfWriter from pypdf import PdfReader, PdfWriter
from django.conf import settings
import boto3
import urllib.parse import urllib.parse
import requests import requests
import base64 import base64
@@ -20,14 +21,9 @@ import json
import ast import ast
def static_map_view(geojson_colec, sat_map): def static_map_view(geojson_colec, sat_map):
g_sys_cfg=GraphModel.objects.get(name='{"en": "Arches System Settings"}')
r_sys_cfg=Resource.objects.get(graph=g_sys_cfg)
r_sys_cfg_data=r_sys_cfg.to_json()
access_token = r_sys_cfg_data['Map Settings']['Mapbox']['MAPBOX_API_KEY']
geojson_dict = ast.literal_eval(geojson_colec) geojson_dict = ast.literal_eval(geojson_colec)
if sat_map: if sat_map:
style_id = "satellite-streets-v12" # streets-v12 | outdoors-v11 | satellite-streets-v12
for geo in geojson_dict['features'] : for geo in geojson_dict['features'] :
if geo['geometry'].get('type') == 'Polygon': if geo['geometry'].get('type') == 'Polygon':
geo['properties'] = { geo['properties'] = {
@@ -48,16 +44,22 @@ def static_map_view(geojson_colec, sat_map):
"stroke-width": 2, "stroke-width": 2,
"stroke-opacity": 0.8 "stroke-opacity": 0.8
} }
else:
style_id = "light-v11"
geojson_string = json.dumps(geojson_dict, separators=(',', ':')) geojson_string = json.dumps(geojson_dict, separators=(',', ':'))
encoded_geojson = urllib.parse.quote(geojson_string) encoded_geojson = urllib.parse.quote(geojson_string)
username = "mapbox" username = "mapbox"
if sat_map:
style_id = "satellite-streets-v12" # streets-v12 | outdoors-v11 | satellite-streets-v12
else:
style_id = "light-v11"
width, height = 400, 400 width, height = 400, 400
padding = 100 padding = 100
# Procedimiento pendiente para extraer el token desde la configuración del sistema
access_token = 'pk.eyJ1IjoianNidXJnb3N2IiwiYSI6ImNsaWhoN3ZtMTBxeHUzZm1xaGM0cGpxOXIifQ.ki3vldVB5MwZMqDSpLSjLg'
mapbox_url = ( mapbox_url = (
f"https://api.mapbox.com/styles/v1/{username}/{style_id}/static/" f"https://api.mapbox.com/styles/v1/{username}/{style_id}/static/"
f"geojson({encoded_geojson})/auto/{width}x{height}" f"geojson({encoded_geojson})/auto/{width}x{height}"
@@ -208,26 +210,38 @@ def export_resource_pdf(request, resourceid, printubic, satmap, printimgs, print
nombre_pdf = f'Inmaterial_{resourceid}' nombre_pdf = f'Inmaterial_{resourceid}'
gen_pdf_file = HTML(string=html_string, base_url=request.build_absolute_uri("/")).write_pdf() gen_pdf_file = HTML(string=html_string, base_url=request.build_absolute_uri("/")).write_pdf()
merger = PdfWriter() merger = PdfWriter()
merger.append(PdfReader(io.BytesIO(gen_pdf_file))) merger.append(PdfReader(io.BytesIO(gen_pdf_file)))
root_path = Path(__file__).resolve().parent.parent
if ruta_medios is not None and bool(int(printpdfs)) : if ruta_medios is not None and bool(int(printpdfs)) :
for medio in reduce(getitem, ruta_medios[:-1], resource_data)[ruta_medios[-1]] : for medio in reduce(getitem, ruta_medios[:-1], resource_data)[ruta_medios[-1]] :
if medio['formato'] == 'application/pdf' : if medio['formato'] == 'application/pdf' :
medio_file = f'{root_path}/uploadedfiles/{medio["file"]}' try :
with open(medio_file,'rb') as pdf_file : minio_bucket=settings.AWS_STORAGE_BUCKET_NAME
merger.append(pdf_file) s3_client = boto3.client('s3',
output_buffer = io.BytesIO() endpoint_url=settings.AWS_S3_ENDPOINT_URL,
merger.write(output_buffer) aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
merger.close() aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
else: )
output_buffer = io.BytesIO() pdf_s3_url = s3_client.generate_presigned_url(
merger.write(output_buffer) 'get_object',
merger.close() Params={'Bucket': minio_bucket, 'Key': f'uploadedfiles/{medio["file"]}'},
ExpiresIn=3600
)
response_s3 = requests.get(pdf_s3_url, timeout=10)
response_s3.raise_for_status()
remote_pdf_stream = io.BytesIO(response_s3.content)
merger.append(remote_pdf_stream)
remote_pdf_stream.close()
except requests.RequestException as e:
return HttpResponse(f"Error al incluir el archivo PDF: {e}", status=502)
output_buffer = io.BytesIO()
merger.write(output_buffer)
output_buffer.seek(0) output_buffer.seek(0)
merger.close()
response = HttpResponse(output_buffer.read(), content_type='application/pdf') response = HttpResponse(output_buffer.read(), content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{nombre_pdf}.pdf"' response['Content-Disposition'] = f'attachment; filename="{nombre_pdf}.pdf"'
return response return response

View File

@@ -30,7 +30,7 @@
.rtable{margin:0 0 0 30px;table-layout: fixed;width:95%;display:table; border:1px solid var(--ceniza)} .rtable{margin:0 0 0 30px;table-layout: fixed;width:95%;display:table; border:1px solid var(--ceniza)}
.rrow{display:table-row; flex-direction:row } .rrow{display:table-row; flex-direction:row }
.rcol-larga{width: auto;}.rcol-media{width: 25%;}.rcol-corta{width: 10%;} .rcol-larga{width: auto;}.rcol-media{width: 22%;}.rcol-corta{width: 10%;}
.rcell{padding:3px 6px;display:table-cell;border:1px solid var(--ceniza)} .rcell{padding:3px 6px;display:table-cell;border:1px solid var(--ceniza)}
.rrow.rheader{ background-color: solid var(--ceniza); border:1px solid var(--ceniza)} .rrow.rheader{ background-color: solid var(--ceniza); border:1px solid var(--ceniza)}
.rrow.rheader .rcell{font-weight:600; border-bottom: 1px solid var(--ceniza);} .rrow.rheader .rcell{font-weight:600; border-bottom: 1px solid var(--ceniza);}
@@ -418,12 +418,10 @@
<div class="tile-block"> <div class="tile-block">
<div class="tile-title">Medios adjuntos</div> <div class="tile-title">Medios adjuntos</div>
{% if resource_data|has_key:"Recursos" %} {% if resource_data|has_key:"Recursos" %}
<div class="flex-container"> <div class="tiles-grid-grp">
{% for medio in resource_data|val_from_key:'Recursos'|val_from_key:'Medio de información' %} {% for medio in resource_data|val_from_key:'Recursos'|val_from_key:'Medio de información' %}
<div class="flex-item"> <div class="data-row" style="margin-left: 15px; padding: 0;">
<div class="data-row" style="margin-left: 15px;"> <a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -474,4 +472,4 @@
</body> </body>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</html> </html>

View File

@@ -312,12 +312,10 @@
<div class="tile-block"> <div class="tile-block">
<div class="tile-title">Medios adjuntos</div> <div class="tile-title">Medios adjuntos</div>
{% if resource_data|has_key:"Recursos adjuntos" %} {% if resource_data|has_key:"Recursos adjuntos" %}
<div class="flex-container"> <div class="tiles-grid-grp">
{% for medio in resource_data|val_from_key:'Recursos adjuntos'|val_from_key:'Medio de información' %} {% for medio in resource_data|val_from_key:'Recursos adjuntos'|val_from_key:'Medio de información' %}
<div class="flex-item"> <div class="data-row" style="margin-left: 15px; padding: 0;">
<div class="data-row" style="margin-left: 15px;"> <a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -365,4 +363,4 @@
</body> </body>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</html> </html>

View File

@@ -253,12 +253,10 @@
<div class="tile-block"> <div class="tile-block">
<div class="tile-title">Medios adjuntos</div> <div class="tile-title">Medios adjuntos</div>
{% if resource_data|has_key:"Información gráfica" %} {% if resource_data|has_key:"Información gráfica" %}
<div class="flex-container"> <div class="tiles-grid-grp">
{% for medio in resource_data|val_from_key:'Información gráfica'|val_from_key:'Medio de información' %} {% for medio in resource_data|val_from_key:'Información gráfica'|val_from_key:'Medio de información' %}
<div class="flex-item"> <div class="data-row" style="margin-left: 15px; padding: 0;">
<div class="data-row" style="margin-left: 15px;"> <a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -310,4 +308,4 @@
</body> </body>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</html> </html>