Compare commits
6 Commits
main
...
dev/no_doc
| Author | SHA1 | Date | |
|---|---|---|---|
| f1c3f711c4 | |||
| ce89e67181 | |||
| ab5c0e6d54 | |||
|
|
4ff8888472 | ||
|
|
0ff577e8b1 | ||
| 96eb619a95 |
92
README.md
92
README.md
@@ -1,92 +1,6 @@
|
|||||||
# exportPDF
|
# exportPDF dev/no_docker
|
||||||
> **Precaución:**
|
> **Precaución:**
|
||||||
> Para instalar en modo desarrollo utilize la rama dev/no_docker
|
> Para instalar en modo producción, utlize la rama main
|
||||||
|
|
||||||
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.
|
||||||
Los modelos de recursos habilitados para la función son los siguientes:
|
esta rama es para el caso de un entorno de desarrollo clásico, es decir sin utilizar contenedores
|
||||||
|
|
||||||
* 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:
|
|
||||||

|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
56
pdf/views.py
56
pdf/views.py
@@ -1,10 +1,11 @@
|
|||||||
# 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
|
||||||
@@ -12,8 +13,6 @@ 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
|
||||||
@@ -21,9 +20,14 @@ 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'] = {
|
||||||
@@ -44,22 +48,16 @@ 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}"
|
||||||
@@ -214,34 +212,22 @@ def export_resource_pdf(request, resourceid, printubic, satmap, printimgs, print
|
|||||||
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' :
|
||||||
try :
|
medio_file = f'{root_path}/uploadedfiles/{medio["file"]}'
|
||||||
minio_bucket=settings.AWS_STORAGE_BUCKET_NAME
|
with open(medio_file,'rb') as pdf_file :
|
||||||
s3_client = boto3.client('s3',
|
merger.append(pdf_file)
|
||||||
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
|
output_buffer = io.BytesIO()
|
||||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
merger.write(output_buffer)
|
||||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
|
merger.close()
|
||||||
)
|
else:
|
||||||
pdf_s3_url = s3_client.generate_presigned_url(
|
output_buffer = io.BytesIO()
|
||||||
'get_object',
|
merger.write(output_buffer)
|
||||||
Params={'Bucket': minio_bucket, 'Key': f'uploadedfiles/{medio["file"]}'},
|
merger.close()
|
||||||
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
|
||||||
@@ -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: 22%;}.rcol-corta{width: 10%;}
|
.rcol-larga{width: auto;}.rcol-media{width: 25%;}.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,10 +418,12 @@
|
|||||||
<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="tiles-grid-grp">
|
<div class="flex-container">
|
||||||
{% 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="data-row" style="margin-left: 15px; padding: 0;">
|
<div class="flex-item">
|
||||||
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -312,10 +312,12 @@
|
|||||||
<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="tiles-grid-grp">
|
<div class="flex-container">
|
||||||
{% 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="data-row" style="margin-left: 15px; padding: 0;">
|
<div class="flex-item">
|
||||||
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -253,10 +253,12 @@
|
|||||||
<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="tiles-grid-grp">
|
<div class="flex-container">
|
||||||
{% 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="data-row" style="margin-left: 15px; padding: 0;">
|
<div class="flex-item">
|
||||||
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user