Compare commits

...

21 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
ccaaca4b4e Actualizar copy2proyfolder.sh 2026-08-07 11:22:32 -04:00
ff4dcd2834 Actualizar copy2proyfolder.sh 2026-08-07 11:20:49 -04:00
423a904d7e Actualizar copy2proyfolder.sh 2026-08-07 11:19:29 -04:00
Juan Burgos
69dc786cac ajustes a la vista 2026-08-07 15:14:52 +00:00
Juan Burgos
8f64d2abdb vista actualizada 2026-08-06 01:39:21 +00:00
juse
3817618208 script de copia 2026-08-05 15:52:38 -04:00
Juan Burgos
f867cac9c1 captura actualizada 2026-08-05 19:38:51 +00:00
Juan Burgos
79debe5f46 Guía actualizada 2026-08-05 19:35:18 +00:00
Juan Burgos
70460b3310 Adición del modo mapa satelital 2026-08-05 13:13:04 +00:00
Juan Burgos
e31ab73c05 Adición del modo mapa satelital 2026-08-05 13:12:42 +00:00
10 changed files with 177 additions and 60 deletions

View File

@@ -1,4 +1,6 @@
# exportPDF
> **Precaución:**
> 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.
Los modelos de recursos habilitados para la función son los siguientes:
@@ -13,7 +15,7 @@ Procedimiento válido para un proyecto en modo desarrollo implementado con un en
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 resida en una implementación según la guía oficial de archesproject
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
@@ -63,7 +65,7 @@ cp -r ../sprpcb-exportpdf-func/templates_reports ./mi_proyecto/templates/reports
from .pdf.views import export_resource_pdf
#urlpatterns = [
# ...
path('export/pdf/<str:resourceid>/<str:printubic>/<str:printimgs>/<str:printpdfs>/', export_resource_pdf, name='export_resource_pdf'),
path('export/pdf/<str:resourceid>/<str:printubic>/<str:satmap>/<str:printimgs>/<str:printpdfs>/', export_resource_pdf, name='export_resource_pdf'),
# project-level urls
#]
@@ -84,6 +86,7 @@ Cuando haya terminado, debería ver los botones de exportar en la ventana de vis
### 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.
* Incluir imágenes adjuntas: 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)
* Incluir documentos (pdf) adjuntos: se ajuntan los documentos relacionados en el mismo archivo pdf del informe
* **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: 23 KiB

After

Width:  |  Height:  |  Size: 18 KiB

26
copy2proyfolder.sh Executable file
View File

@@ -0,0 +1,26 @@
#!bin/bash
#Script demostrativo para copiar del repo clonado a las carpetas de proyecto
#Utilize con precaución
echo -e "\n\t Script para copiar a carpeta de proyecto"
read -p "Escribe la carpeta de proyecto ( ejem: ~/mi_proy/miproy ): " destino
echo $destino
if [ ! -d "$destino" ]; then
echo "La carpeta de destino no existe. Intente nuevamente!"
exit 1
fi
mkdir -p $destino/pdf
cp ./pdf/views.py $destino/pdf/
cp ./media_js_reports/export-pdf-report.js $destino/media/js/reports/
cp -r ./reportes $destino/
mkdir -p $destino/templates/views/report-templates
cp ./templates_views_report-templates/export-pdf-report.htm $destino/templates/views/report-templates/
mkdir -p $destino/templates/reports
cp ./templates_reports/* $destino/templates/reports/

View File

@@ -12,8 +12,10 @@ define([
//console.log('Params:', params);
this.anexarUbic = ko.observable(false);
this.satMap = ko.observable(false);
this.anexarImagenes = ko.observable(false);
this.anexarPdfs = ko.observable(false);
const boolToStr = (bool) => bool ? "1" : "0";
self.resourceId = ko.observable(
@@ -23,7 +25,7 @@ define([
self.exportPDF = function() {
var id = self.resourceId();
if (id) {
var url = '/export/pdf/' + id + '/' + boolToStr(self.anexarUbic()) + '/' + boolToStr(self.anexarImagenes()) + '/' + boolToStr(self.anexarPdfs()) + '/';
var url = '/export/pdf/' + id + '/' + boolToStr(self.anexarUbic()) + '/' + boolToStr(self.satMap()) + '/' + boolToStr(self.anexarImagenes()) + '/' + boolToStr(self.anexarPdfs()) + '/';
window.open(url, '_blank');
} else {
alert('No se encontró el ID del recurso.');

View File

@@ -12,9 +12,64 @@ from zoneinfo import ZoneInfo
from functools import reduce
from operator import getitem
from pypdf import PdfReader, PdfWriter
from django.conf import settings
import boto3
import urllib.parse
import requests
import base64
import json
import ast
def static_map_view(geojson_colec, sat_map):
geojson_dict = ast.literal_eval(geojson_colec)
if sat_map:
for geo in geojson_dict['features'] :
if geo['geometry'].get('type') == 'Polygon':
geo['properties'] = {
"stroke": "#00ffff",
"stroke-width": 2,
"stroke-opacity": 0.8,
"fill": "#00ffff",
"fill-opacity": 0.3
}
elif geo['geometry'].get('type') == 'Point':
geo['properties'] = {
"marker-color": "#ff0000",
"marker-size": "small"
}
elif geo['geometry'].get('type') == 'LineString':
geo['properties'] = {
"stroke": "#0000ff",
"stroke-width": 2,
"stroke-opacity": 0.8
}
geojson_string = json.dumps(geojson_dict, separators=(',', ':'))
encoded_geojson = urllib.parse.quote(geojson_string)
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
padding = 100
# Procedimiento pendiente para extraer el token desde la configuración del sistema
access_token = 'pk.eyJ1IjoianNidXJnb3N2IiwiYSI6ImNsaWhoN3ZtMTBxeHUzZm1xaGM0cGpxOXIifQ.ki3vldVB5MwZMqDSpLSjLg'
mapbox_url = (
f"https://api.mapbox.com/styles/v1/{username}/{style_id}/static/"
f"geojson({encoded_geojson})/auto/{width}x{height}"
f"?padding={padding}&access_token={access_token}"
)
response = requests.get(mapbox_url)
return base64.b64encode(response.content).decode('utf-8')
def ajustar_medios(resource_media_rel, medios_list):
medios = []
img=["image/bmp", "image/gif", "image/jpg", "image/jpeg", "image/png", "image/tif", "image/tiff"]
@@ -83,12 +138,13 @@ def strdict_to_dict(strdict):
pass
return value
def export_resource_pdf(request, resourceid, printubic, printimgs, printpdfs):
def export_resource_pdf(request, resourceid, printubic, satmap, printimgs, printpdfs):
resource = Resource.objects.get(pk=resourceid)
resource_data = resource.to_json()
# Corrección de medios relacionados, se normalizan claves y se adicionan propiedades
ruta_medios = find_key_path( resource_data, "Medio de información")
if ruta_medios is not None:
valor_medios=reduce(lambda dict_layer, key: dict_layer[key], ruta_medios, resource_data)
resource_rel=resource.get_related_resources(user=request.user,resourceinstance_graphid='6afc4e9e-727e-4152-8084-309267752650')
@@ -96,6 +152,7 @@ def export_resource_pdf(request, resourceid, printubic, printimgs, printpdfs):
reduce(getitem, ruta_medios[:-1], resource_data)[ruta_medios[-1]] = ajustar_medios(resource_rel,valor_medios)
# Cambio de datos gis en texto a formato json
ruta_gis = find_key_path( resource_data, "Coordenadas / Geometría")
if ruta_gis is not None:
valor_gis=reduce(lambda dict_layer, key: dict_layer[key], ruta_gis, resource_data)["@value"]
@@ -111,7 +168,13 @@ def export_resource_pdf(request, resourceid, printubic, printimgs, printpdfs):
# Fecha del informe
sprpcb_url=request.build_absolute_uri('/')[:-1]
fecha=datetime.now(ZoneInfo("America/La_Paz"))
map_img = None
if ruta_gis is not None and bool(int(printubic)) :
if reduce(getitem, ruta_gis[:-1], resource_data)[ruta_gis[-1]]["@value"] :
map_img = static_map_view(reduce(getitem, ruta_gis[:-1], resource_data)[ruta_gis[-1]]["@value"], bool(int(satmap)))
resources = [{
'resourceinstanceid': str(resource.resourceinstanceid),
'graph_id': str(resource.graph_id),
@@ -120,8 +183,10 @@ def export_resource_pdf(request, resourceid, printubic, printimgs, printpdfs):
'sprpcb_url': sprpcb_url,
'fecha': fecha,
'eta_group': eta_group,
'print_ubic': bool(int(printubic)),
'print_imgs': bool(int(printimgs)),
'print_pdfs': bool(int(printpdfs))
'print_pdfs': bool(int(printpdfs)),
'map_img': map_img
}]
context = {'resources': resources}
@@ -148,19 +213,35 @@ def export_resource_pdf(request, resourceid, printubic, printimgs, printpdfs):
merger = PdfWriter()
merger.append(PdfReader(io.BytesIO(gen_pdf_file)))
root_path = Path(__file__).resolve().parent.parent
for medio in reduce(getitem, ruta_medios[:-1], resource_data)[ruta_medios[-1]] :
if medio['formato'] == 'application/pdf' and bool(int(printpdfs)) :
merger.append(PdfReader(f'{root_path}/uploadedfiles/{medio["file"]}'))
output_buffer = io.BytesIO()
merger.write(output_buffer)
merger.close()
else:
output_buffer = io.BytesIO()
merger.write(output_buffer)
merger.close()
response = HttpResponse(output_buffer.getvalue(), content_type='application/pdf')
if ruta_medios is not None and bool(int(printpdfs)) :
for medio in reduce(getitem, ruta_medios[:-1], resource_data)[ruta_medios[-1]] :
if medio['formato'] == 'application/pdf' :
try :
minio_bucket=settings.AWS_STORAGE_BUCKET_NAME
s3_client = boto3.client('s3',
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
)
pdf_s3_url = s3_client.generate_presigned_url(
'get_object',
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)
merger.close()
response = HttpResponse(output_buffer.read(), content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{nombre_pdf}.pdf"'
return response
return response

View File

@@ -16,14 +16,15 @@
h3 { font-size: 12pt;}
h4 { font-size: 11pt;}
.tiles-grid-grp { display: grid; grid-template-columns: 3fr 2fr; grid-template-rows: auto auto; gap: 2px;}
.tiles-grid-grp { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; gap: 2px;}
.tiles-grid-grp-col { grid-column: 2; grid-row: 1 / span 2; }
.tile-block { border: 1px solid var(--ceniza); border-radius: 4px; padding: 2px; margin: 2px 0; background-color: var(--claro);}
.tile-title { font-size: 11pt; font-weight: bold; color: var(--oscuro); border-bottom: 1px solid var(--ceniza); padding-bottom: 2px; margin-bottom: 5px;}
.data-row { display: flex; padding: 2px 0; border-bottom: 1px solid var(--claro);}
.data-label { font-weight: 600; letter-spacing: -0.05em; width: 40%; color: var(--oscuro);}
.data-value { width: 40%; letter-spacing: -0.05em; word-wrap: break-word;}
.data-value { width: 60%; font-size:.9em; word-wrap: break-word;}
.no-data { color: var(--ceniza); font-style: italic;}
.tiles-grid-grp-col .tile-block img { object-fit: cover; width: 330px; max-height: 230px;}
@page {
@top-left { background: var(--resaltado); content: counter(page); height: 1cm; text-align: center; width: 1cm;}
@@ -117,7 +118,7 @@
{% with gis_vector=resource_data|val_from_key:"Ubicación simple"|val_from_key:"Coordenadas / Geometría"|val_from_key:"@value"|json_to_obj %}
{% for gis in gis_vector.features %}
<div class="data-row">
<span class="data-value" style="width: 100%;">{{ gis.geometry }}</span>
<span class="data-value" style="width: 100%; font-size: .72em;">{{ gis.geometry|truncatewords:24 }}</span>
</div>
{% endfor %}
{% endwith %}
@@ -131,6 +132,7 @@
<div class="tiles-grid-grp-col">
<div class="tile-block">
<div class="data-label">Mapa de referencia</div>
<img src="data:image/png;base64,{{ resource.map_img }}">
</div>
</div>
</div>

View File

@@ -30,7 +30,7 @@
.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 }
.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)}
.rrow.rheader{ background-color: solid var(--ceniza); border:1px solid var(--ceniza)}
.rrow.rheader .rcell{font-weight:600; border-bottom: 1px solid var(--ceniza);}
@@ -162,7 +162,7 @@
{% with gis_vector=resource_data|val_from_key:"Ubicación BIM"|val_from_key:"Coordenadas / Geometría"|val_from_key:"@value"|json_to_obj %}
{% for gis in gis_vector.features %}
<div class="data-row">
<span class="data-value" style="width: 100%;">{{ gis.geometry }}</span>
<span class="data-value" style="width: 100%; font-size: .72em;">{{ gis.geometry|truncatewords:24 }}</span>
</div>
{% endfor %}
{% endwith %}
@@ -175,6 +175,7 @@
<div class="tiles-grid-grp-col">
<div class="tile-block">
<div class="tile-title">Mapa de referencia</div>
<img src="data:image/png;base64,{{ resource.map_img }}">
</div>
</div>
</div>
@@ -417,12 +418,10 @@
<div class="tile-block">
<div class="tile-title">Medios adjuntos</div>
{% 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' %}
<div class="flex-item">
<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 class="data-row" style="margin-left: 15px; padding: 0;">
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
{% endfor %}
</div>
@@ -473,4 +472,4 @@
</body>
{% endwith %}
{% endfor %}
</html>
</html>

View File

@@ -152,15 +152,17 @@
<span class="data-label">Espacio: </span>
<span class="data-value">{{ resource_data|val_from_key:"Localización"|val_from_key:"Espacio" }}</span>
</div>
<div class="data-row">
<span class="data-label">Inmueble: </span>
<span class="data-value">{{ resource_data|val_from_key:"Localización"|val_from_key:"Inmueble" }}</span>
</div>
<div class="data-row">
<span class="data-label">Colección: </span>
<span class="data-value">{{ resource_data|val_from_key:"Localización"|val_from_key:"Colección" }}</span>
</div>
{% if resource_data|val_from_key:"Localización"|has_key:"Ubicación" %}
{% if resource.print_ubic %}
<div class="data-row">
<span class="data-label">Inmueble: </span>
<span class="data-value">{{ resource_data|val_from_key:"Localización"|val_from_key:"Inmueble" }}</span>
</div>
<div class="data-row">
<span class="data-label">Colección: </span>
<span class="data-value">{{ resource_data|val_from_key:"Localización"|val_from_key:"Colección" }}</span>
</div>
{% endif %}
{% if resource_data|val_from_key:"Localización"|has_key:"Ubicación" and resource.print_ubic %}
<div class="data-row" style="margin-left: 15px;">
<span class="data-value" style="width: 100%;"><b>Ambiente : </b> {{resource_data|val_from_key:"Localización"|val_from_key:"Ubicación"|val_from_key:"Ambiente" }}</span>
</div>
@@ -180,11 +182,11 @@
<div class="data-row">
<span class="data-label" style="width: 100%;">Coordenadas / Geometría: </span>
</div>
{% if resource_data|val_from_key:"Localización"|has_key:"Coordenadas / Geometría" %}
{% if resource_data|val_from_key:"Localización"|has_key:"Coordenadas / Geometría" and resource.print_ubic %}
{% with gis_vector=resource_data|val_from_key:"Localización"|val_from_key:"Coordenadas / Geometría"|val_from_key:"@value"|json_to_obj %}
{% for gis in gis_vector.features %}
<div class="data-row">
<span class="data-value" style="width: 100%;">{{ gis.geometry }}</span>
<span class="data-value" style="width: 100%; font-size: .72em;">{{ gis.geometry|truncatewords:24 }}</span>
</div>
{% endfor %}
{% endwith %}
@@ -197,6 +199,7 @@
<div class="tiles-grid-grp-col">
<div class="tile-block">
<div class="tile-title">Mapa de referencia</div>
<img src="data:image/png;base64,{{ resource.map_img }}">
</div>
</div>
</div>
@@ -309,12 +312,10 @@
<div class="tile-block">
<div class="tile-title">Medios adjuntos</div>
{% 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' %}
<div class="flex-item">
<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 class="data-row" style="margin-left: 15px; padding: 0;">
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
{% endfor %}
</div>
@@ -362,4 +363,4 @@
</body>
{% endwith %}
{% endfor %}
</html>
</html>

View File

@@ -126,7 +126,7 @@
{% with gis_vector=resource_data|val_from_key:"Ubicación 2"|val_from_key:"Coordenadas / Geometría"|val_from_key:"@value"|json_to_obj %}
{% for gis in gis_vector.features %}
<div class="data-row">
<span class="data-value" style="width: 100%;">{{ gis.geometry }}</span>
<span class="data-value" style="width: 100%; font-size: .72em;">{{ gis.geometry|truncatewords:24 }}</span>
</div>
{% endfor %}
{% endwith %}
@@ -140,6 +140,7 @@
<div class="tiles-grid-grp-col">
<div class="tile-block">
<div class="tile-title">Mapa de referencia</div>
<img src="data:image/png;base64,{{ resource.map_img }}">
</div>
</div>
</div>
@@ -252,12 +253,10 @@
<div class="tile-block">
<div class="tile-title">Medios adjuntos</div>
{% 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' %}
<div class="flex-item">
<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 class="data-row" style="margin-left: 15px; padding: 0;">
<a href={{resource.sprpcb_url}}{{ medio.url }} >{{ medio.tipo }} : {{ medio|val_from_key:'@value' }} ({{ medio.formato|slice:"-4:" }})</a>
</div>
{% endfor %}
</div>
@@ -309,4 +308,4 @@
</body>
{% endwith %}
{% endfor %}
</html>
</html>

View File

@@ -15,11 +15,15 @@
</div>
<div class="report-print-date" data-bind="component: {
name: 'views/components/simple-switch',
params: { value: anexarImagenes, config:{ label: 'Incluir imágenes adjuntas'}}}">
params: { value: satMap, config:{ label: 'Mapa satelital'}}}">
</div>
<div class="report-print-date" data-bind="component: {
name: 'views/components/simple-switch',
params: { value: anexarPdfs, config:{ label: 'Incluir documentos (pdf) adjuntos'}}}">
params: { value: anexarImagenes, config:{ label: 'Adjuntar imágenes'}}}">
</div>
<div class="report-print-date" data-bind="component: {
name: 'views/components/simple-switch',
params: { value: anexarPdfs, config:{ label: 'Adjuntar documentos (pdf)'}}}">
</div>
</div>
</div>