From 9499ec2a3d0f93c23d44e8376d508165528b99c4 Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:51:59 -0300 Subject: [PATCH 01/38] def index, delete , edit --- ckanext/dashboard/blueprints/dashboard.py | 72 +++++++++++++++++++++++ ckanext/dashboard/config.py | 29 +++++++++ 2 files changed, 101 insertions(+) create mode 100644 ckanext/dashboard/blueprints/dashboard.py create mode 100644 ckanext/dashboard/config.py diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py new file mode 100644 index 0000000..d6374aa --- /dev/null +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -0,0 +1,72 @@ +import logging +import ckan.plugins as p +from flask import Blueprint, request, redirect, url_for, flash +from ckan.logic import NotFound +from ckan.plugins.toolkit import render + +# Importa o define el decorador para restringir el acceso a sysadmins. +# Puedes definirlo en tu extensión o importarlo si ya lo tienes. +from ckanext.dashboard.decorators import require_sysadmin_user + +log = logging.getLogger(__name__) + +dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard') + + +@dashboard_bp.route('/', methods=['GET']) +@require_sysadmin_user +def index(): + """Listar las configuraciones de los dashboards""" + log.debug("Listando configuraciones de dashboard") + context = {'model': p.model, 'user': p.toolkit.c.user} + try: + dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) + except Exception as e: + log.error("Error al listar dashboards: %s", e) + dashboards = [] + return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) + + +@dashboard_bp.route('/edit/', methods=['GET', 'POST']) +@require_sysadmin_user +def edit(package_id): + """Editar la configuración de un dashboard para el dataset indicado""" + log.debug("Editando dashboard para package_id: %s", package_id) + context = {'model': p.model, 'user': p.toolkit.c.user} + + if request.method == 'POST': + data = { + 'package_id': package_id, + 'embeded_url': request.form.get('embeded_url'), + 'report_url': request.form.get('report_url') + } + try: + p.toolkit.get_action('dataset_dashboard_update')(context, data) + flash('Dashboard updated successfully', 'success') + log.info("Dashboard actualizado para package_id: %s", package_id) + except Exception as e: + flash('Error: {}'.format(e), 'error') + log.error("Error actualizando dashboard para package_id %s: %s", package_id, e) + return redirect(url_for('dashboard_bp.index')) + else: + try: + dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'package_id': package_id}) + except NotFound: + dashboard = None + return render('dashboard/edit.html', extra_vars={'dashboard': dashboard, 'package_id': package_id}) + + +@dashboard_bp.route('/delete/', methods=['POST']) +@require_sysadmin_user +def delete(package_id): + """Eliminar la configuración de un dashboard para el dataset indicado""" + log.debug("Eliminando dashboard para package_id: %s", package_id) + context = {'model': p.model, 'user': p.toolkit.c.user} + try: + p.toolkit.get_action('dataset_dashboard_delete')(context, {'package_id': package_id}) + flash('Dashboard configuration deleted', 'success') + log.info("Dashboard eliminado para package_id: %s", package_id) + except Exception as e: + flash('Error: {}'.format(e), 'error') + log.error("Error eliminando dashboard para package_id %s: %s", package_id, e) + return redirect(url_for('dashboard_bp.index')) diff --git a/ckanext/dashboard/config.py b/ckanext/dashboard/config.py new file mode 100644 index 0000000..4cbf2ad --- /dev/null +++ b/ckanext/dashboard/config.py @@ -0,0 +1,29 @@ +""" +Connect to the dashboard API (through a proxy server if required) +""" +import logging + +from ckan.plugins.toolkit import config + + +log = logging.getLogger(__name__) + + +def get_config(): + """ Get configuration values for dashboard and proxy""" + log.debug("Getting dashboard configuration values") + cfg = { + 'dashboard_url': config.get("ckanext.dashboard.instance.url").rstrip('/'), + 'dashboard_user': config.get("ckanext.dashboard.instance.user"), + 'dashboard_pass': config.get("ckanext.dashboard.instance.pass"), + 'dashboard_provider': config.get("ckanext.dashboard.instance.provider", "db"), + 'dashboard_refresh': config.get("ckanext.dashboard.instance.refresh", "true"), + 'proxy_url': config.get("ckanext.dashboard.proxy.url"), + 'proxy_port': config.get("ckanext.dashboard.proxy.port", '3128'), + 'proxy_user': config.get("ckanext.dashboard.proxy.user"), + 'proxy_pass': config.get("ckanext.dashboard.proxy.pass"), + } + + log.info(f'Configuration values: dashboard: {cfg["dashboard_url"]}, Proxy: {cfg.get("proxy_url")}') + + return cfg From f1da136e62f17396f4f117b2d0d46ef0f68a3c6c Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:52:12 -0300 Subject: [PATCH 02/38] decorators sysadmin --- ckanext/dashboard/decorators.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ckanext/dashboard/decorators.py diff --git a/ckanext/dashboard/decorators.py b/ckanext/dashboard/decorators.py new file mode 100644 index 0000000..2290a0b --- /dev/null +++ b/ckanext/dashboard/decorators.py @@ -0,0 +1,18 @@ +from functools import wraps +from ckan.plugins import toolkit + + +def require_sysadmin_user(func): + ''' + Decorator for flask view functions. Returns 403 response if no user is logged in or if the login user is not sysadmin. + ''' + + @wraps(func) + def view_wrapper(*args, **kwargs): + if not hasattr(toolkit.c, "user") or not toolkit.c.user: + return toolkit.abort(403, "Forbidden") + if not toolkit.c.userobj.sysadmin: + return toolkit.abort(403, "Sysadmin user required") + return func(*args, **kwargs) + + return view_wrapper From db7f75576c7e748b03b3f507cb23120dfccd84a9 Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:52:23 -0300 Subject: [PATCH 03/38] model DatasetDashboard --- ckanext/dashboard/models.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 ckanext/dashboard/models.py diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py new file mode 100644 index 0000000..0787324 --- /dev/null +++ b/ckanext/dashboard/models.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, Integer, String +from ckan.plugins import toolkit +from ckan.model.base import ActiveRecordMixin + +from ckan.model.types import UuidType + + +class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): + """Modelo de datos para almacenar la configuración de un dashboard por dataset""" + __tablename__ = "ndx_dataset_dashboard" + + id = Column(Integer, primary_key=True) + package_id = Column(UuidType, nullable=False, unique=True) + embeded_url = Column(String(200)) + report_url = Column(String(200)) From c78e3f5551baa463333901e76931efb9b8f0b471 Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:52:41 -0300 Subject: [PATCH 04/38] plugin DashboardPlugin --- ckanext/dashboard/plugin.py | 39 ++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index b8925d1..e77297e 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,16 +1,37 @@ -import ckan.plugins as plugins -import ckan.plugins.toolkit as toolkit +import ckan.plugins as p +from .blueprints.dashboard import dashboard_bp +from .models import DatasetDashboard +from .logic import dashboard -class DashboardPlugin(plugins.SingletonPlugin): - plugins.implements(plugins.IConfigurer) - +class DashboardPlugin(p.SingletonPlugin): + """Plugin para el manejo de dashboards en CKAN""" + p.implements(p.IConfigurer) + p.implements(p.IBlueprint) + p.implements(p.IActions) + p.implements(p.ITemplateHelpers) # IConfigurer def update_config(self, config_): - toolkit.add_template_directory(config_, "templates") - toolkit.add_public_directory(config_, "public") - toolkit.add_resource("assets", "dashboard") + p.toolkit.add_template_directory(config_, "templates") + p.toolkit.add_public_directory(config_, "public") + p.toolkit.add_resource("assets", "dashboard") - + def get_blueprint(self): + return dashboard_bp + + def get_actions(self): + return { + 'dataset_dashboard_list': dashboard.dataset_dashboard_list, + 'dataset_dashboard_show': dashboard.dataset_dashboard_show, + 'dataset_dashboard_update': dashboard.dataset_dashboard_update, + 'dataset_dashboard_delete': dashboard.dataset_dashboard_delete + } + + def get_helpers(self): + return {'get_dataset_dashboard': self.get_dataset_dashboard} + + def get_dataset_dashboard(self, package_id): + session = p.toolkit.model.Session + return session.query(DatasetDashboard).filter_by(package_id=package_id).first() From 5335363d3c17cf8660c5ebaff6f589d62622c55f Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:53:04 -0300 Subject: [PATCH 05/38] actins dataset_dashboard_list --- ckanext/dashboard/actions/__init__.py | 0 .../dashboard/actions/dashboard_dataset.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 ckanext/dashboard/actions/__init__.py create mode 100644 ckanext/dashboard/actions/dashboard_dataset.py diff --git a/ckanext/dashboard/actions/__init__.py b/ckanext/dashboard/actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py new file mode 100644 index 0000000..b65661c --- /dev/null +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -0,0 +1,29 @@ +import logging +from ckan.plugins import toolkit +from ckan import model +from ckanext.dashboard.models import DatasetDashboard + +log = logging.getLogger(__name__) + + +@toolkit.side_effect_free +def dataset_dashboard_list(context, data_dict): + """ + Devuelve la lista de configuraciones de dashboard almacenadas en la base de datos. + + Esta acción es side_effect_free (no tiene efectos colaterales) y verifica + que el usuario tenga permisos para listar las configuraciones. + """ + toolkit.check_access('dataset_dashboard_list', context, data_dict) + + session = model.Session + dashboards = session.query(DatasetDashboard).all() + result = [] + for dash in dashboards: + result.append({ + 'package_id': dash.package_id, + 'embeded_url': dash.embeded_url, + 'report_url': dash.report_url, + }) + log.debug("Se han recuperado %d configuraciones de dashboard", len(result)) + return result From 1b0d0c9d9caf41a6a163989f556774dd2d9ba15b Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:53:27 -0300 Subject: [PATCH 06/38] auth --- ckanext/dashboard/auth/__init__.py | 0 ckanext/dashboard/auth/dashboard_dataset.py | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 ckanext/dashboard/auth/__init__.py create mode 100644 ckanext/dashboard/auth/dashboard_dataset.py diff --git a/ckanext/dashboard/auth/__init__.py b/ckanext/dashboard/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ckanext/dashboard/auth/dashboard_dataset.py b/ckanext/dashboard/auth/dashboard_dataset.py new file mode 100644 index 0000000..05e315a --- /dev/null +++ b/ckanext/dashboard/auth/dashboard_dataset.py @@ -0,0 +1,5 @@ + + +def dashboard_dataset_list(context, data_dict): + """ Sysadmin only """ + return {'success': False} From c2d65fe2f172ee5ff68cb41c5bfedbefcdeb34f4 Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 15:53:38 -0300 Subject: [PATCH 07/38] html --- ckanext/dashboard/blueprints/__init__.py | 0 ckanext/dashboard/templates/edit.html | 13 +++++++++++++ ckanext/dashboard/templates/index.html | 16 ++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 ckanext/dashboard/blueprints/__init__.py create mode 100644 ckanext/dashboard/templates/edit.html create mode 100644 ckanext/dashboard/templates/index.html diff --git a/ckanext/dashboard/blueprints/__init__.py b/ckanext/dashboard/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ckanext/dashboard/templates/edit.html b/ckanext/dashboard/templates/edit.html new file mode 100644 index 0000000..bed526f --- /dev/null +++ b/ckanext/dashboard/templates/edit.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} +{% block content %} +

{% if dashboard %}Edit{% else %}Add{% endif %} Dashboard for Dataset {{ package_id }}

+
+ + +
+ + +
+ +
+{% endblock %} diff --git a/ckanext/dashboard/templates/index.html b/ckanext/dashboard/templates/index.html new file mode 100644 index 0000000..2f941bb --- /dev/null +++ b/ckanext/dashboard/templates/index.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block content %} +

Dashboard Configurations

+Add new dashboard +
    + {% for d in dashboards %} +
  • + Dataset: {{ d.package_id }} - + Edit +
    + +
    +
  • + {% endfor %} +
+{% endblock %} From 56619f8c9e21094d07948828542f051555168e9e Mon Sep 17 00:00:00 2001 From: german Date: Fri, 14 Feb 2025 16:14:24 -0300 Subject: [PATCH 08/38] models y html --- ckanext/dashboard/plugin.py | 3 ++- .../dashboard/templates/dashboard/snippet.html | 15 +++++++++++++++ ckanext/dashboard/templates/package/read.html | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 ckanext/dashboard/templates/dashboard/snippet.html create mode 100644 ckanext/dashboard/templates/package/read.html diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index e77297e..93676b0 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,4 +1,5 @@ import ckan.plugins as p +from ckan.plugins.toolkit import model from .blueprints.dashboard import dashboard_bp from .models import DatasetDashboard from .logic import dashboard @@ -33,5 +34,5 @@ def get_helpers(self): return {'get_dataset_dashboard': self.get_dataset_dashboard} def get_dataset_dashboard(self, package_id): - session = p.toolkit.model.Session + session = model.Session return session.query(DatasetDashboard).filter_by(package_id=package_id).first() diff --git a/ckanext/dashboard/templates/dashboard/snippet.html b/ckanext/dashboard/templates/dashboard/snippet.html new file mode 100644 index 0000000..1dc3f18 --- /dev/null +++ b/ckanext/dashboard/templates/dashboard/snippet.html @@ -0,0 +1,15 @@ + + + + + + + +{% if dashboard.report_url %} + +{% endif %} diff --git a/ckanext/dashboard/templates/package/read.html b/ckanext/dashboard/templates/package/read.html new file mode 100644 index 0000000..c3e179b --- /dev/null +++ b/ckanext/dashboard/templates/package/read.html @@ -0,0 +1,14 @@ +{% extends "package/read.html" %} + +{% block package_description %} + {{ super() }} + + {# Llamamos al helper para obtener la configuración del dashboard #} + {% set dashboard = h.get_dataset_dashboard(package['id']) %} + + {% if dashboard and dashboard.embeded_url %} +

Dashboard

+ {# Incluimos el snippet de embebido #} + {% include "dashboard/snippet.html" %} + {% endif %} +{% endblock %} From 710ab4044d46e249f95b46c95dc3eb0d6e2152a1 Mon Sep 17 00:00:00 2001 From: german Date: Mon, 17 Feb 2025 15:38:10 -0300 Subject: [PATCH 09/38] fix --- ckanext/dashboard/blueprints/dashboard.py | 9 +- ckanext/dashboard/logic.py | 219 ++++++++++++++++++ ckanext/dashboard/plugin.py | 3 +- .../dashboard/templates/dashboard/index.html | 30 +++ 4 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 ckanext/dashboard/logic.py create mode 100644 ckanext/dashboard/templates/dashboard/index.html diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index d6374aa..45b27f3 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -1,4 +1,5 @@ import logging +from ckan import model import ckan.plugins as p from flask import Blueprint, request, redirect, url_for, flash from ckan.logic import NotFound @@ -10,7 +11,7 @@ log = logging.getLogger(__name__) -dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard') +dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard_2') @dashboard_bp.route('/', methods=['GET']) @@ -18,7 +19,7 @@ def index(): """Listar las configuraciones de los dashboards""" log.debug("Listando configuraciones de dashboard") - context = {'model': p.model, 'user': p.toolkit.c.user} + context = {'model': model, 'user': p.toolkit.c.user} try: dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) except Exception as e: @@ -32,7 +33,7 @@ def index(): def edit(package_id): """Editar la configuración de un dashboard para el dataset indicado""" log.debug("Editando dashboard para package_id: %s", package_id) - context = {'model': p.model, 'user': p.toolkit.c.user} + context = {'model': model, 'user': p.toolkit.c.user} if request.method == 'POST': data = { @@ -61,7 +62,7 @@ def edit(package_id): def delete(package_id): """Eliminar la configuración de un dashboard para el dataset indicado""" log.debug("Eliminando dashboard para package_id: %s", package_id) - context = {'model': p.model, 'user': p.toolkit.c.user} + context = {'model': model, 'user': p.toolkit.c.user} try: p.toolkit.get_action('dataset_dashboard_delete')(context, {'package_id': package_id}) flash('Dashboard configuration deleted', 'success') diff --git a/ckanext/dashboard/logic.py b/ckanext/dashboard/logic.py new file mode 100644 index 0000000..29328e3 --- /dev/null +++ b/ckanext/dashboard/logic.py @@ -0,0 +1,219 @@ +import logging +from ckan import model +from .models import DatasetDashboard +from ckan.plugins import SingletonPlugin, implements, IConfigurer + +log = logging.getLogger(__name__) + + +class Dashboard(SingletonPlugin): + """ + Clase Dashboard para la extensión ckanext-dashboard. + Esta clase implementa la interfaz IConfigurer para actualizar la configuración de CKAN. + Puedes extenderla implementando otras interfaces según tus necesidades. + """ + + # Implementa IConfigurer para modificar la configuración de CKAN + implements(IConfigurer, inherit=True) + + def update_config(self, config): + """ + Actualiza la configuración de CKAN, por ejemplo, añadiendo rutas de plantillas. + """ + log.info("Dashboard: Actualizando configuración de CKAN") + # Ejemplo: agregar un directorio de plantillas personalizado para el dashboard + dashboard_template_path = '/app/ckanext/dashboard/templates' + if 'extra_template_paths' in config: + config['extra_template_paths'] += ';' + dashboard_template_path + else: + config['extra_template_paths'] = dashboard_template_path + + @staticmethod + def dataset_dashboard_list(context, data_dict): + """ + Acción personalizada que retorna una lista de dashboards asociados a un dataset. + + Se espera que en data_dict se incluya la clave 'id' que corresponde al identificador + del dataset (package_id). La función consulta la base de datos y retorna una lista de + diccionarios, donde cada diccionario representa un dashboard. + + :param context: Diccionario con información del contexto de la acción. + :param data_dict: Diccionario con los datos de entrada de la acción, debe incluir 'id'. + :return: Lista de diccionarios con la información de cada dashboard. + """ + log = logging.getLogger(__name__) + log.info("Ejecutando acción dataset_dashboard_list") + + # Validar que se haya proporcionado el identificador del dataset + dataset_id = data_dict.get('id') + if not dataset_id: + raise ValueError("El parámetro 'id' es obligatorio en data_dict.") + + # Realiza la consulta a la base de datos para obtener todos los dashboards asociados + session = model.Session + dashboards = session.query(DatasetDashboard).filter_by(package_id=dataset_id).all() + + # Convierte cada objeto dashboard a un diccionario. + # Se asume que el modelo DatasetDashboard tiene los atributos: id, package_id, title, description. + dashboard_list = [] + for dashboard in dashboards: + dashboard_dict = { + 'id': dashboard.id, + 'package_id': dashboard.package_id, + 'title': dashboard.title, + 'description': dashboard.description, + } + dashboard_list.append(dashboard_dict) + + log.info("Se encontraron %d dashboards para el dataset %s", len(dashboard_list), dataset_id) + return dashboard_list + + @staticmethod + def dataset_dashboard_show(context, data_dict): + """ + Acción que retorna los detalles de un dashboard en particular. + + :param context: Diccionario con información del contexto de la acción. + :param data_dict: Diccionario con los datos de entrada, que debe incluir + el identificador del dashboard o del dataset. + :return: Diccionario con los detalles del dashboard (por defecto, vacío). + """ + log = logging.getLogger(__name__) + log.info("Ejecutando acción dataset_dashboard_show") + + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("El parámetro 'id' es obligatorio para mostrar el dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard no encontrado.") + + # Convierte el objeto dashboard a un diccionario con los campos necesarios. + dashboard_dict = { + 'id': dashboard.id, + 'package_id': dashboard.package_id, + 'title': dashboard.title, + 'description': dashboard.description, + # Agrega aquí otros campos que necesites exponer. + } + return dashboard_dict + + @staticmethod + def dataset_dashboard_update(context, data_dict): + """ + Acción que actualiza un dashboard en particular. + + :param context: Diccionario con información del contexto de la acción. + :param data_dict: Diccionario con los datos de entrada, que debe incluir + el identificador del dashboard o del dataset y los datos a actualizar. + :return: Diccionario con el resultado de la operación (por defecto, vacío). + """ + log.info("Ejecutando acción dataset_dashboard_update") + + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("El parámetro 'id' es obligatorio para actualizar el dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard no encontrado.") + + # Actualiza los campos disponibles. Por ejemplo, actualizamos 'title' y 'description' + if 'title' in data_dict: + dashboard.title = data_dict['title'] + if 'description' in data_dict: + dashboard.description = data_dict['description'] + # Agrega aquí la actualización de otros campos si es necesario. + + session.add(dashboard) + session.commit() + + updated_dashboard = { + 'id': dashboard.id, + 'package_id': dashboard.package_id, + 'title': dashboard.title, + 'description': dashboard.description, + # Incluye otros campos actualizados según corresponda. + } + return updated_dashboard + + @staticmethod + def dataset_dashboard_delete(context, data_dict): + """ + Acción que elimina un dashboard en particular. + + :param context: Diccionario con información del contexto de la acción. + :param data_dict: Diccionario con los datos de entrada, que debe incluir + el identificador del dashboard o del dataset. + :return: Diccionario con el resultado de la operación (por defecto, vacío). + """ + log.info("Ejecutando acción dataset_dashboard_delete") + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("El parámetro 'id' es obligatorio para eliminar el dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard no encontrado.") + + session.delete(dashboard) + session.commit() + + return {'success': True, 'message': 'Dashboard eliminado correctamente.'} + + @staticmethod + def dataset_dashboard_create(context, data_dict): + """ + Acción que crea un dashboard para un dataset. + + Se espera que en data_dict se incluyan las claves necesarias para crear el dashboard, + por ejemplo, 'package_id' y 'title'. La 'description' es opcional, pero puedes agregar + otros campos según la definición de tu modelo. + + :param context: Diccionario con información del contexto de la acción. + :param data_dict: Diccionario con los datos de entrada para crear el dashboard. + :return: Diccionario con los detalles del dashboard recién creado. + """ + + log.info("Ejecutando acción dataset_dashboard_create") + + # Validar campos obligatorios + required_fields = ['package_id', 'title'] + missing_fields = [field for field in required_fields if field not in data_dict] + if missing_fields: + raise ValueError("Faltan campos obligatorios: " + ", ".join(missing_fields)) + + session = model.Session + + # Crear una nueva instancia del dashboard + new_dashboard = DatasetDashboard( + package_id=data_dict.get('package_id'), + title=data_dict.get('title'), + description=data_dict.get('description', '') # 'description' es opcional + # Agrega aquí otros campos según tu modelo + ) + + # Agrega la nueva instancia a la sesión y confirma la transacción + session.add(new_dashboard) + session.commit() + + # Retorna el dashboard creado convertido a diccionario + created_dashboard = { + 'id': new_dashboard.id, + 'package_id': new_dashboard.package_id, + 'title': new_dashboard.title, + 'description': new_dashboard.description, + # Incluye otros campos que necesites exponer + } + return created_dashboard + + +# Exporta la clase asignándola a la variable 'dashboard' +dashboard = Dashboard diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 93676b0..f0a90a4 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,5 +1,5 @@ import ckan.plugins as p -from ckan.plugins.toolkit import model +from ckan import model from .blueprints.dashboard import dashboard_bp from .models import DatasetDashboard from .logic import dashboard @@ -25,6 +25,7 @@ def get_blueprint(self): def get_actions(self): return { 'dataset_dashboard_list': dashboard.dataset_dashboard_list, + 'dataset_dashboard_create': dashboard.dataset_dashboard_create, 'dataset_dashboard_show': dashboard.dataset_dashboard_show, 'dataset_dashboard_update': dashboard.dataset_dashboard_update, 'dataset_dashboard_delete': dashboard.dataset_dashboard_delete diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html new file mode 100644 index 0000000..62d3531 --- /dev/null +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -0,0 +1,30 @@ + + + + + Dashboard - List + + + +
+

Dashboard - List

+
+
+ {% if dashboards %} +
    + {% for dashboard in dashboards %} +
  • {{ dashboard }}
  • + {% endfor %} +
+ {% else %} +

No dashboards found.

+ {% endif %} +
+ + From ae1ec2ba350b4d604672c18dfb421895e517a195 Mon Sep 17 00:00:00 2001 From: german Date: Tue, 18 Feb 2025 14:43:10 -0300 Subject: [PATCH 10/38] cambios --- ckanext/dashboard/auth/dashboard_dataset.py | 25 ++++++++++ ckanext/dashboard/blueprints/dashboard.py | 20 +++++--- ckanext/dashboard/plugin.py | 21 +++++++-- ckanext/dashboard/templates/admin/base.html | 10 ++++ .../dashboard/templates/dashboard/index.html | 46 +++++++------------ .../dashboard/templates/dashboard/new.html | 16 +++++++ ckanext/dashboard/templates/edit.html | 2 +- 7 files changed, 100 insertions(+), 40 deletions(-) create mode 100644 ckanext/dashboard/templates/admin/base.html create mode 100644 ckanext/dashboard/templates/dashboard/new.html diff --git a/ckanext/dashboard/auth/dashboard_dataset.py b/ckanext/dashboard/auth/dashboard_dataset.py index 05e315a..fbe7fa2 100644 --- a/ckanext/dashboard/auth/dashboard_dataset.py +++ b/ckanext/dashboard/auth/dashboard_dataset.py @@ -1,5 +1,30 @@ +""" +Dashboard auth functions +""" def dashboard_dataset_list(context, data_dict): """ Sysadmin only """ return {'success': False} + + +def dashboard_dataset_create(context, data_dict): + """Only sysadmins are allowed""" + user_obj = context.get("auth_user_obj") + return {"success": user_obj.sysadmin} + + +def dashboard_dataset_update(context, data_dict): + """Only sysadmins are allowed""" + user_obj = context.get("auth_user_obj") + return {"success": user_obj.sysadmin} + + +def dashboard_dataset_delete(context, data_dict): + """Only sysadmins are allowed""" + user_obj = context.get("auth_user_obj") + return {"success": user_obj.sysadmin} + + +def dashboard_dataset_show(context, data_dict): + return {"success": True} diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 45b27f3..56812f5 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -11,10 +11,10 @@ log = logging.getLogger(__name__) -dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard_2') +dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard-external') -@dashboard_bp.route('/', methods=['GET']) +@dashboard_bp.route('/', methods=['GET'], endpoint='dashboard_list') @require_sysadmin_user def index(): """Listar las configuraciones de los dashboards""" @@ -28,9 +28,17 @@ def index(): return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) -@dashboard_bp.route('/edit/', methods=['GET', 'POST']) +@dashboard_bp.route('/new', methods=['GET', 'POST'], endpoint='dashboard_new') @require_sysadmin_user -def edit(package_id): +def dashboard_new(): + """Crear un nuevo dashboard (vista y lógica para la creación)""" + # Aquí implementa la lógica para crear un dashboard + return render('dashboard/new.html') + + +@dashboard_bp.route('/edit/', methods=['GET', 'POST'], endpoint='dashboard_edit') +@require_sysadmin_user +def dashboard_edit(package_id): """Editar la configuración de un dashboard para el dataset indicado""" log.debug("Editando dashboard para package_id: %s", package_id) context = {'model': model, 'user': p.toolkit.c.user} @@ -57,9 +65,9 @@ def edit(package_id): return render('dashboard/edit.html', extra_vars={'dashboard': dashboard, 'package_id': package_id}) -@dashboard_bp.route('/delete/', methods=['POST']) +@dashboard_bp.route('/delete/', methods=['POST'], endpoint='dashboard_delete') @require_sysadmin_user -def delete(package_id): +def dashboard_delete(package_id): """Eliminar la configuración de un dashboard para el dataset indicado""" log.debug("Eliminando dashboard para package_id: %s", package_id) context = {'model': model, 'user': p.toolkit.c.user} diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index f0a90a4..2c3e75c 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,8 +1,10 @@ import ckan.plugins as p +import ckan.plugins.toolkit as toolkit from ckan import model from .blueprints.dashboard import dashboard_bp from .models import DatasetDashboard from .logic import dashboard +from ckanext.dashboard.auth import dashboard_dataset as auth class DashboardPlugin(p.SingletonPlugin): @@ -11,17 +13,30 @@ class DashboardPlugin(p.SingletonPlugin): p.implements(p.IBlueprint) p.implements(p.IActions) p.implements(p.ITemplateHelpers) + p.implements(p.IAuthFunctions) # IConfigurer def update_config(self, config_): - p.toolkit.add_template_directory(config_, "templates") - p.toolkit.add_public_directory(config_, "public") - p.toolkit.add_resource("assets", "dashboard") + toolkit.add_template_directory(config_, "templates") + toolkit.add_public_directory(config_, "public") + toolkit.add_resource("assets", "dashboard") def get_blueprint(self): return dashboard_bp + # IAuthFunctions + + def get_auth_functions(self): + functions = { + "dashboard_dataset_list": auth.dashboard_dataset_list, + "dashboard_dataset_create": auth.dashboard_dataset_create, + "dashboard_dataset_show": auth.dashboard_dataset_show, + "dashboard_dataset_update": auth.dashboard_dataset_update, + "dashboard_dataset_delete": auth.dashboard_dataset_delete, + } + return functions + def get_actions(self): return { 'dataset_dashboard_list': dashboard.dataset_dashboard_list, diff --git a/ckanext/dashboard/templates/admin/base.html b/ckanext/dashboard/templates/admin/base.html new file mode 100644 index 0000000..555ab15 --- /dev/null +++ b/ckanext/dashboard/templates/admin/base.html @@ -0,0 +1,10 @@ +{% ckan_extends %} + +{% block content_primary_nav %} + {{ super() }} + {{ h.build_nav_icon('dashboard_bp.dashboard_list', _('Dashboards'), icon='icon-list') }} + {{ h.build_nav_icon('dashboard_bp.dashboard_new', _('New Dashboard'), icon='icon-plus') }} + {# Los endpoints que requieren package_id se muestran en contextos específicos y no en la navegación global #} +{% endblock %} + + diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index 62d3531..e1533f9 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -1,30 +1,16 @@ - - - - - Dashboard - List - - - -
-

Dashboard - List

-
-
- {% if dashboards %} -
    - {% for dashboard in dashboards %} -
  • {{ dashboard }}
  • - {% endfor %} -
- {% else %} -

No dashboards found.

- {% endif %} -
- - +{% extends "admin/base.html" %} + +{% block title %}Dashboard - List{% endblock %} + +{% block content %} +

Dashboard - List

+ {% if dashboards %} +
    + {% for dashboard in dashboards %} +
  • {{ dashboard }}
  • + {% endfor %} +
+ {% else %} +

No dashboards found.

+ {% endif %} +{% endblock %} diff --git a/ckanext/dashboard/templates/dashboard/new.html b/ckanext/dashboard/templates/dashboard/new.html new file mode 100644 index 0000000..37163a0 --- /dev/null +++ b/ckanext/dashboard/templates/dashboard/new.html @@ -0,0 +1,16 @@ +{% extends "admin/base.html" %} + +{% block title %}New Dashboard{% endblock %} + +{% block content %} +

Create a New Dashboard

+
+ + +
+ + +
+ +
+{% endblock %} diff --git a/ckanext/dashboard/templates/edit.html b/ckanext/dashboard/templates/edit.html index bed526f..2889d78 100644 --- a/ckanext/dashboard/templates/edit.html +++ b/ckanext/dashboard/templates/edit.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "admin/base.html" %} {% block content %}

{% if dashboard %}Edit{% else %}Add{% endif %} Dashboard for Dataset {{ package_id }}

From 782cafb5857f0a1c43e448e4b4f15c012079bd0d Mon Sep 17 00:00:00 2001 From: german Date: Tue, 18 Feb 2025 15:03:05 -0300 Subject: [PATCH 11/38] fixes --- ckanext/dashboard/auth/dashboard_dataset.py | 9 ++-- ckanext/dashboard/blueprints/dashboard.py | 52 ++++++++++----------- ckanext/dashboard/models.py | 2 +- ckanext/dashboard/plugin.py | 2 +- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/ckanext/dashboard/auth/dashboard_dataset.py b/ckanext/dashboard/auth/dashboard_dataset.py index fbe7fa2..8b0bf5c 100644 --- a/ckanext/dashboard/auth/dashboard_dataset.py +++ b/ckanext/dashboard/auth/dashboard_dataset.py @@ -10,20 +10,17 @@ def dashboard_dataset_list(context, data_dict): def dashboard_dataset_create(context, data_dict): """Only sysadmins are allowed""" - user_obj = context.get("auth_user_obj") - return {"success": user_obj.sysadmin} + return {"success": False} def dashboard_dataset_update(context, data_dict): """Only sysadmins are allowed""" - user_obj = context.get("auth_user_obj") - return {"success": user_obj.sysadmin} + return {"success": False} def dashboard_dataset_delete(context, data_dict): """Only sysadmins are allowed""" - user_obj = context.get("auth_user_obj") - return {"success": user_obj.sysadmin} + return {"success": False} def dashboard_dataset_show(context, data_dict): diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 56812f5..b9e4d5d 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -5,8 +5,8 @@ from ckan.logic import NotFound from ckan.plugins.toolkit import render -# Importa o define el decorador para restringir el acceso a sysadmins. -# Puedes definirlo en tu extensión o importarlo si ya lo tienes. +# Import or define the decorator to restrict access to sysadmins. +# You can define it in your extension or import it if you already have it. from ckanext.dashboard.decorators import require_sysadmin_user log = logging.getLogger(__name__) @@ -17,13 +17,13 @@ @dashboard_bp.route('/', methods=['GET'], endpoint='dashboard_list') @require_sysadmin_user def index(): - """Listar las configuraciones de los dashboards""" - log.debug("Listando configuraciones de dashboard") + """List dashboard configurations""" + log.debug("Listing dashboard configurations") context = {'model': model, 'user': p.toolkit.c.user} try: dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) - except Exception as e: - log.error("Error al listar dashboards: %s", e) + except Exception: + flash("An error occurred while loading the dashboards.", "error") dashboards = [] return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) @@ -31,51 +31,51 @@ def index(): @dashboard_bp.route('/new', methods=['GET', 'POST'], endpoint='dashboard_new') @require_sysadmin_user def dashboard_new(): - """Crear un nuevo dashboard (vista y lógica para la creación)""" - # Aquí implementa la lógica para crear un dashboard + """Create a new dashboard (view and logic for creation)""" + # Implement the logic to create a dashboard here return render('dashboard/new.html') -@dashboard_bp.route('/edit/', methods=['GET', 'POST'], endpoint='dashboard_edit') +@dashboard_bp.route('/edit/', methods=['GET', 'POST'], endpoint='dashboard_edit') @require_sysadmin_user -def dashboard_edit(package_id): - """Editar la configuración de un dashboard para el dataset indicado""" - log.debug("Editando dashboard para package_id: %s", package_id) +def dashboard_edit(dashboard_id): + """Edit the configuration of a dashboard using its unique ID""" + log.debug("Editing dashboard for dashboard_id: %s", dashboard_id) context = {'model': model, 'user': p.toolkit.c.user} if request.method == 'POST': data = { - 'package_id': package_id, + 'dashboard_id': dashboard_id, 'embeded_url': request.form.get('embeded_url'), 'report_url': request.form.get('report_url') } try: p.toolkit.get_action('dataset_dashboard_update')(context, data) flash('Dashboard updated successfully', 'success') - log.info("Dashboard actualizado para package_id: %s", package_id) + log.info("Dashboard updated for dashboard_id: %s", dashboard_id) except Exception as e: - flash('Error: {}'.format(e), 'error') - log.error("Error actualizando dashboard para package_id %s: %s", package_id, e) + flash(f'Error: {e}', 'error') + log.error("Error updating dashboard for dashboard_id %s: %s", dashboard_id, e) return redirect(url_for('dashboard_bp.index')) else: try: - dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'package_id': package_id}) + dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'dashboard_id': dashboard_id}) except NotFound: dashboard = None - return render('dashboard/edit.html', extra_vars={'dashboard': dashboard, 'package_id': package_id}) + return render('dashboard/edit.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id}) -@dashboard_bp.route('/delete/', methods=['POST'], endpoint='dashboard_delete') +@dashboard_bp.route('/delete/', methods=['POST'], endpoint='dashboard_delete') @require_sysadmin_user -def dashboard_delete(package_id): - """Eliminar la configuración de un dashboard para el dataset indicado""" - log.debug("Eliminando dashboard para package_id: %s", package_id) +def dashboard_delete(dashboard_id): + """Delete the configuration of a dashboard using its unique ID""" + log.debug("Deleting dashboard for dashboard_id: %s", dashboard_id) context = {'model': model, 'user': p.toolkit.c.user} try: - p.toolkit.get_action('dataset_dashboard_delete')(context, {'package_id': package_id}) + p.toolkit.get_action('dataset_dashboard_delete')(context, {'dashboard_id': dashboard_id}) flash('Dashboard configuration deleted', 'success') - log.info("Dashboard eliminado para package_id: %s", package_id) + log.info("Dashboard deleted for dashboard_id: %s", dashboard_id) except Exception as e: - flash('Error: {}'.format(e), 'error') - log.error("Error eliminando dashboard para package_id %s: %s", package_id, e) + flash(f'Error: {e}', 'error') + log.error("Error deleting dashboard for dashboard_id %s: %s", dashboard_id, e) return redirect(url_for('dashboard_bp.index')) diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index 0787324..4aa8ce5 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -6,7 +6,7 @@ class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): - """Modelo de datos para almacenar la configuración de un dashboard por dataset""" + """Data model for storing the configuration of a dashboard per dataset""" __tablename__ = "ndx_dataset_dashboard" id = Column(Integer, primary_key=True) diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 2c3e75c..73a7a00 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -8,7 +8,7 @@ class DashboardPlugin(p.SingletonPlugin): - """Plugin para el manejo de dashboards en CKAN""" + """Plugin for managing dashboards in CKAN""" p.implements(p.IConfigurer) p.implements(p.IBlueprint) p.implements(p.IActions) From 8a463cff61b804a010523607d359e75f4e09a23b Mon Sep 17 00:00:00 2001 From: german Date: Tue, 18 Feb 2025 15:05:34 -0300 Subject: [PATCH 12/38] dashboard_id --- ckanext/dashboard/actions/dashboard_dataset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index b65661c..8f273ed 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -21,6 +21,7 @@ def dataset_dashboard_list(context, data_dict): result = [] for dash in dashboards: result.append({ + 'dashboard_id': dash.id, 'package_id': dash.package_id, 'embeded_url': dash.embeded_url, 'report_url': dash.report_url, From efea410608ac3357cbda143e6943bdcf0edfc8a4 Mon Sep 17 00:00:00 2001 From: german Date: Tue, 18 Feb 2025 15:08:31 -0300 Subject: [PATCH 13/38] log error --- ckanext/dashboard/blueprints/dashboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index b9e4d5d..4784d2d 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -22,8 +22,9 @@ def index(): context = {'model': model, 'user': p.toolkit.c.user} try: dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) - except Exception: - flash("An error occurred while loading the dashboards.", "error") + except Exception as e: + log.error("Failed to load dashboards: %s", e) + flash("An error occurred while retrieving the dashboards.", "error") dashboards = [] return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) From 09512bb8d5e87316cac17825679eb492e12f1513 Mon Sep 17 00:00:00 2001 From: german Date: Tue, 18 Feb 2025 16:35:50 -0300 Subject: [PATCH 14/38] fixes --- .../dashboard/actions/dashboard_dataset.py | 137 ++++++++++- ckanext/dashboard/config.py | 6 +- ckanext/dashboard/logic.py | 219 ------------------ ckanext/dashboard/models.py | 2 +- ckanext/dashboard/plugin.py | 21 +- ckanext/dashboard/templates/admin/base.html | 3 +- ckanext/dashboard/templates/index.html | 2 +- ckanext/dashboard/templates/package/read.html | 2 +- 8 files changed, 153 insertions(+), 239 deletions(-) delete mode 100644 ckanext/dashboard/logic.py diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index 8f273ed..43c1b08 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -9,10 +9,10 @@ @toolkit.side_effect_free def dataset_dashboard_list(context, data_dict): """ - Devuelve la lista de configuraciones de dashboard almacenadas en la base de datos. + Returns a list of dashboard configurations stored in the database. - Esta acción es side_effect_free (no tiene efectos colaterales) y verifica - que el usuario tenga permisos para listar las configuraciones. + This action is side_effect_free (has no side effects) and ensures + that the user has the necessary permissions to list dashboard configurations. """ toolkit.check_access('dataset_dashboard_list', context, data_dict) @@ -26,5 +26,134 @@ def dataset_dashboard_list(context, data_dict): 'embeded_url': dash.embeded_url, 'report_url': dash.report_url, }) - log.debug("Se han recuperado %d configuraciones de dashboard", len(result)) + log.debug("Retrieved %d dashboard configurations", len(result)) return result + + +@toolkit.side_effect_free +def dataset_dashboard_show(context, data_dict): + """ + Returns details of a specific dashboard. + + :param context: Dictionary with action context information. + :param data_dict: Dictionary with input data, must include the dashboard ID. + :return: Dictionary with dashboard details. + """ + log.info("Executing dataset_dashboard_show") + + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("The 'id' parameter is required to show the dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard not found.") + + return { + 'id': dashboard.id, + 'package_id': dashboard.package_id, + 'title': dashboard.title, + 'description': dashboard.description, + } + + +def dataset_dashboard_create(context, data_dict): + """ + Creates a new dashboard for a dataset. + + Expected keys in `data_dict` include 'package_id' and 'title'. + 'description' is optional, but you can add other fields as needed. + + :param context: Dictionary with action context information. + :param data_dict: Dictionary with input data for creating the dashboard. + :return: Dictionary with the details of the newly created dashboard. + """ + log.info("Executing dataset_dashboard_create") + + # Validate required fields + required_fields = ['package_id', 'title'] + missing_fields = [field for field in required_fields if field not in data_dict] + if missing_fields: + raise ValueError("Missing required fields: " + ", ".join(missing_fields)) + + session = model.Session + + new_dashboard = DatasetDashboard( + package_id=data_dict.get('package_id'), + title=data_dict.get('title'), + description=data_dict.get('description', '') # 'description' is optional + ) + + session.add(new_dashboard) + session.commit() + + return { + 'id': new_dashboard.id, + 'package_id': new_dashboard.package_id, + 'title': new_dashboard.title, + 'description': new_dashboard.description, + } + + +def dataset_dashboard_update(context, data_dict): + """ + Updates a specific dashboard. + + :param context: Dictionary with action context information. + :param data_dict: Dictionary with input data, must include the dashboard ID. + :return: Dictionary with the updated dashboard details. + """ + log.info("Executing dataset_dashboard_update") + + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("The 'id' parameter is required to update the dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard not found.") + + if 'title' in data_dict: + dashboard.title = data_dict['title'] + if 'description' in data_dict: + dashboard.description = data_dict['description'] + + session.add(dashboard) + session.commit() + + return { + 'id': dashboard.id, + 'package_id': dashboard.package_id, + 'title': dashboard.title, + 'description': dashboard.description, + } + + +def dataset_dashboard_delete(context, data_dict): + """ + Deletes a specific dashboard. + + :param context: Dictionary with action context information. + :param data_dict: Dictionary with input data, must include the dashboard ID. + :return: Dictionary confirming the deletion. + """ + log.info("Executing dataset_dashboard_delete") + + dashboard_id = data_dict.get('id') + if not dashboard_id: + raise ValueError("The 'id' parameter is required to delete the dashboard.") + + session = model.Session + dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() + + if not dashboard: + raise ValueError("Dashboard not found.") + + session.delete(dashboard) + session.commit() + + return {'success': True, 'message': 'Dashboard successfully deleted.'} diff --git a/ckanext/dashboard/config.py b/ckanext/dashboard/config.py index 4cbf2ad..5b70409 100644 --- a/ckanext/dashboard/config.py +++ b/ckanext/dashboard/config.py @@ -17,11 +17,7 @@ def get_config(): 'dashboard_user': config.get("ckanext.dashboard.instance.user"), 'dashboard_pass': config.get("ckanext.dashboard.instance.pass"), 'dashboard_provider': config.get("ckanext.dashboard.instance.provider", "db"), - 'dashboard_refresh': config.get("ckanext.dashboard.instance.refresh", "true"), - 'proxy_url': config.get("ckanext.dashboard.proxy.url"), - 'proxy_port': config.get("ckanext.dashboard.proxy.port", '3128'), - 'proxy_user': config.get("ckanext.dashboard.proxy.user"), - 'proxy_pass': config.get("ckanext.dashboard.proxy.pass"), + 'dashboard_refresh': config.get("ckanext.dashboard.instance.refresh", "true") } log.info(f'Configuration values: dashboard: {cfg["dashboard_url"]}, Proxy: {cfg.get("proxy_url")}') diff --git a/ckanext/dashboard/logic.py b/ckanext/dashboard/logic.py deleted file mode 100644 index 29328e3..0000000 --- a/ckanext/dashboard/logic.py +++ /dev/null @@ -1,219 +0,0 @@ -import logging -from ckan import model -from .models import DatasetDashboard -from ckan.plugins import SingletonPlugin, implements, IConfigurer - -log = logging.getLogger(__name__) - - -class Dashboard(SingletonPlugin): - """ - Clase Dashboard para la extensión ckanext-dashboard. - Esta clase implementa la interfaz IConfigurer para actualizar la configuración de CKAN. - Puedes extenderla implementando otras interfaces según tus necesidades. - """ - - # Implementa IConfigurer para modificar la configuración de CKAN - implements(IConfigurer, inherit=True) - - def update_config(self, config): - """ - Actualiza la configuración de CKAN, por ejemplo, añadiendo rutas de plantillas. - """ - log.info("Dashboard: Actualizando configuración de CKAN") - # Ejemplo: agregar un directorio de plantillas personalizado para el dashboard - dashboard_template_path = '/app/ckanext/dashboard/templates' - if 'extra_template_paths' in config: - config['extra_template_paths'] += ';' + dashboard_template_path - else: - config['extra_template_paths'] = dashboard_template_path - - @staticmethod - def dataset_dashboard_list(context, data_dict): - """ - Acción personalizada que retorna una lista de dashboards asociados a un dataset. - - Se espera que en data_dict se incluya la clave 'id' que corresponde al identificador - del dataset (package_id). La función consulta la base de datos y retorna una lista de - diccionarios, donde cada diccionario representa un dashboard. - - :param context: Diccionario con información del contexto de la acción. - :param data_dict: Diccionario con los datos de entrada de la acción, debe incluir 'id'. - :return: Lista de diccionarios con la información de cada dashboard. - """ - log = logging.getLogger(__name__) - log.info("Ejecutando acción dataset_dashboard_list") - - # Validar que se haya proporcionado el identificador del dataset - dataset_id = data_dict.get('id') - if not dataset_id: - raise ValueError("El parámetro 'id' es obligatorio en data_dict.") - - # Realiza la consulta a la base de datos para obtener todos los dashboards asociados - session = model.Session - dashboards = session.query(DatasetDashboard).filter_by(package_id=dataset_id).all() - - # Convierte cada objeto dashboard a un diccionario. - # Se asume que el modelo DatasetDashboard tiene los atributos: id, package_id, title, description. - dashboard_list = [] - for dashboard in dashboards: - dashboard_dict = { - 'id': dashboard.id, - 'package_id': dashboard.package_id, - 'title': dashboard.title, - 'description': dashboard.description, - } - dashboard_list.append(dashboard_dict) - - log.info("Se encontraron %d dashboards para el dataset %s", len(dashboard_list), dataset_id) - return dashboard_list - - @staticmethod - def dataset_dashboard_show(context, data_dict): - """ - Acción que retorna los detalles de un dashboard en particular. - - :param context: Diccionario con información del contexto de la acción. - :param data_dict: Diccionario con los datos de entrada, que debe incluir - el identificador del dashboard o del dataset. - :return: Diccionario con los detalles del dashboard (por defecto, vacío). - """ - log = logging.getLogger(__name__) - log.info("Ejecutando acción dataset_dashboard_show") - - dashboard_id = data_dict.get('id') - if not dashboard_id: - raise ValueError("El parámetro 'id' es obligatorio para mostrar el dashboard.") - - session = model.Session - dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() - - if not dashboard: - raise ValueError("Dashboard no encontrado.") - - # Convierte el objeto dashboard a un diccionario con los campos necesarios. - dashboard_dict = { - 'id': dashboard.id, - 'package_id': dashboard.package_id, - 'title': dashboard.title, - 'description': dashboard.description, - # Agrega aquí otros campos que necesites exponer. - } - return dashboard_dict - - @staticmethod - def dataset_dashboard_update(context, data_dict): - """ - Acción que actualiza un dashboard en particular. - - :param context: Diccionario con información del contexto de la acción. - :param data_dict: Diccionario con los datos de entrada, que debe incluir - el identificador del dashboard o del dataset y los datos a actualizar. - :return: Diccionario con el resultado de la operación (por defecto, vacío). - """ - log.info("Ejecutando acción dataset_dashboard_update") - - dashboard_id = data_dict.get('id') - if not dashboard_id: - raise ValueError("El parámetro 'id' es obligatorio para actualizar el dashboard.") - - session = model.Session - dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() - - if not dashboard: - raise ValueError("Dashboard no encontrado.") - - # Actualiza los campos disponibles. Por ejemplo, actualizamos 'title' y 'description' - if 'title' in data_dict: - dashboard.title = data_dict['title'] - if 'description' in data_dict: - dashboard.description = data_dict['description'] - # Agrega aquí la actualización de otros campos si es necesario. - - session.add(dashboard) - session.commit() - - updated_dashboard = { - 'id': dashboard.id, - 'package_id': dashboard.package_id, - 'title': dashboard.title, - 'description': dashboard.description, - # Incluye otros campos actualizados según corresponda. - } - return updated_dashboard - - @staticmethod - def dataset_dashboard_delete(context, data_dict): - """ - Acción que elimina un dashboard en particular. - - :param context: Diccionario con información del contexto de la acción. - :param data_dict: Diccionario con los datos de entrada, que debe incluir - el identificador del dashboard o del dataset. - :return: Diccionario con el resultado de la operación (por defecto, vacío). - """ - log.info("Ejecutando acción dataset_dashboard_delete") - dashboard_id = data_dict.get('id') - if not dashboard_id: - raise ValueError("El parámetro 'id' es obligatorio para eliminar el dashboard.") - - session = model.Session - dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() - - if not dashboard: - raise ValueError("Dashboard no encontrado.") - - session.delete(dashboard) - session.commit() - - return {'success': True, 'message': 'Dashboard eliminado correctamente.'} - - @staticmethod - def dataset_dashboard_create(context, data_dict): - """ - Acción que crea un dashboard para un dataset. - - Se espera que en data_dict se incluyan las claves necesarias para crear el dashboard, - por ejemplo, 'package_id' y 'title'. La 'description' es opcional, pero puedes agregar - otros campos según la definición de tu modelo. - - :param context: Diccionario con información del contexto de la acción. - :param data_dict: Diccionario con los datos de entrada para crear el dashboard. - :return: Diccionario con los detalles del dashboard recién creado. - """ - - log.info("Ejecutando acción dataset_dashboard_create") - - # Validar campos obligatorios - required_fields = ['package_id', 'title'] - missing_fields = [field for field in required_fields if field not in data_dict] - if missing_fields: - raise ValueError("Faltan campos obligatorios: " + ", ".join(missing_fields)) - - session = model.Session - - # Crear una nueva instancia del dashboard - new_dashboard = DatasetDashboard( - package_id=data_dict.get('package_id'), - title=data_dict.get('title'), - description=data_dict.get('description', '') # 'description' es opcional - # Agrega aquí otros campos según tu modelo - ) - - # Agrega la nueva instancia a la sesión y confirma la transacción - session.add(new_dashboard) - session.commit() - - # Retorna el dashboard creado convertido a diccionario - created_dashboard = { - 'id': new_dashboard.id, - 'package_id': new_dashboard.package_id, - 'title': new_dashboard.title, - 'description': new_dashboard.description, - # Incluye otros campos que necesites exponer - } - return created_dashboard - - -# Exporta la clase asignándola a la variable 'dashboard' -dashboard = Dashboard diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index 4aa8ce5..ee4d533 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -7,7 +7,7 @@ class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): """Data model for storing the configuration of a dashboard per dataset""" - __tablename__ = "ndx_dataset_dashboard" + __tablename__ = "dashboard_package" id = Column(Integer, primary_key=True) package_id = Column(UuidType, nullable=False, unique=True) diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 73a7a00..764032c 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -3,7 +3,11 @@ from ckan import model from .blueprints.dashboard import dashboard_bp from .models import DatasetDashboard -from .logic import dashboard +from ckanext.dashboard.actions.dashboard_dataset import (dataset_dashboard_list, + dataset_dashboard_create, + dataset_dashboard_show, + dataset_dashboard_update, + dataset_dashboard_delete) from ckanext.dashboard.auth import dashboard_dataset as auth @@ -21,6 +25,11 @@ def update_config(self, config_): toolkit.add_template_directory(config_, "templates") toolkit.add_public_directory(config_, "public") toolkit.add_resource("assets", "dashboard") + dashboard_template_path = '/app/ckanext/dashboard/templates' + if 'extra_template_paths' in config_: + config_['extra_template_paths'] += ';' + dashboard_template_path + else: + config_['extra_template_paths'] = dashboard_template_path def get_blueprint(self): return dashboard_bp @@ -39,11 +48,11 @@ def get_auth_functions(self): def get_actions(self): return { - 'dataset_dashboard_list': dashboard.dataset_dashboard_list, - 'dataset_dashboard_create': dashboard.dataset_dashboard_create, - 'dataset_dashboard_show': dashboard.dataset_dashboard_show, - 'dataset_dashboard_update': dashboard.dataset_dashboard_update, - 'dataset_dashboard_delete': dashboard.dataset_dashboard_delete + 'dataset_dashboard_list': dataset_dashboard_list, + 'dataset_dashboard_create': dataset_dashboard_create, + 'dataset_dashboard_show': dataset_dashboard_show, + 'dataset_dashboard_update': dataset_dashboard_update, + 'dataset_dashboard_delete': dataset_dashboard_delete } def get_helpers(self): diff --git a/ckanext/dashboard/templates/admin/base.html b/ckanext/dashboard/templates/admin/base.html index 555ab15..301251f 100644 --- a/ckanext/dashboard/templates/admin/base.html +++ b/ckanext/dashboard/templates/admin/base.html @@ -2,8 +2,7 @@ {% block content_primary_nav %} {{ super() }} - {{ h.build_nav_icon('dashboard_bp.dashboard_list', _('Dashboards'), icon='icon-list') }} - {{ h.build_nav_icon('dashboard_bp.dashboard_new', _('New Dashboard'), icon='icon-plus') }} + {{ h.build_nav_icon('dashboard_bp.dashboard_list', _('External dashboards'), icon='icon-list') }} {# Los endpoints que requieren package_id se muestran en contextos específicos y no en la navegación global #} {% endblock %} diff --git a/ckanext/dashboard/templates/index.html b/ckanext/dashboard/templates/index.html index 2f941bb..e01eab8 100644 --- a/ckanext/dashboard/templates/index.html +++ b/ckanext/dashboard/templates/index.html @@ -6,7 +6,7 @@

Dashboard Configurations

{% for d in dashboards %}
  • Dataset: {{ d.package_id }} - - Edit + Edit
  • diff --git a/ckanext/dashboard/templates/package/read.html b/ckanext/dashboard/templates/package/read.html index c3e179b..25affb3 100644 --- a/ckanext/dashboard/templates/package/read.html +++ b/ckanext/dashboard/templates/package/read.html @@ -4,7 +4,7 @@ {{ super() }} {# Llamamos al helper para obtener la configuración del dashboard #} - {% set dashboard = h.get_dataset_dashboard(package['id']) %} + {% set dashboard = h.get_dataset_dashboards(package['id']) %} {% if dashboard and dashboard.embeded_url %}

    Dashboard

    From 8aea1ff0427fa57b06fdacabbe3057fe80df9b60 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 12:52:19 -0300 Subject: [PATCH 15/38] options: --user root --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc212b2..76a26af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Tests +name: Tests 2.11 on: [push, pull_request] jobs: test: @@ -8,6 +8,7 @@ jobs: # the one of the container the tests run on. # You can switch this base image with a custom image tailored to your project image: ckan/ckan-dev:2.11 + options: --user root services: solr: image: ckan/ckan-solr:2.11-solr9 From 0d4b573880e8360bffe2dce18b099557bd493ee5 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 12:57:26 -0300 Subject: [PATCH 16/38] fixes --- ckanext/dashboard/templates/edit.html | 9 +++++++++ ckanext/dashboard/tests/test_plugin.py | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ckanext/dashboard/templates/edit.html b/ckanext/dashboard/templates/edit.html index 2889d78..5674255 100644 --- a/ckanext/dashboard/templates/edit.html +++ b/ckanext/dashboard/templates/edit.html @@ -1,7 +1,16 @@ {% extends "admin/base.html" %} {% block content %}

    {% if dashboard %}Edit{% else %}Add{% endif %} Dashboard for Dataset {{ package_id }}

    +
    + + {{ h.csrf_input() }} + + + {% if dashboard %} + + {% endif %} +
    diff --git a/ckanext/dashboard/tests/test_plugin.py b/ckanext/dashboard/tests/test_plugin.py index b18292c..7dbbe4f 100644 --- a/ckanext/dashboard/tests/test_plugin.py +++ b/ckanext/dashboard/tests/test_plugin.py @@ -47,10 +47,11 @@ def test_some_endpoint(app): def test_some_action(): pass """ -import ckanext.dashboard.plugin as plugin +import pytest +from ckan import plugins @pytest.mark.ckan_config("ckan.plugins", "dashboard") @pytest.mark.usefixtures("with_plugins") def test_plugin(): - assert plugin_loaded("dashboard") + assert plugins.plugin_loaded("dashboard") From a047fca0b1eb9b8a986f887ae4856e765f539d76 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 14:25:21 -0300 Subject: [PATCH 17/38] snippet --- .../templates/dashboard/snippet.html | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/ckanext/dashboard/templates/dashboard/snippet.html b/ckanext/dashboard/templates/dashboard/snippet.html index 1dc3f18..11be2a0 100644 --- a/ckanext/dashboard/templates/dashboard/snippet.html +++ b/ckanext/dashboard/templates/dashboard/snippet.html @@ -1,15 +1,32 @@ - - + +{% if dashboard.type == "tableau" %} + +{% endif %} - - - + +
    + {% if dashboard.type == "tableau" %} + + + + {% elif dashboard.type == "powerbi" %} + + + {% else %} +

    Unsupported BI tool.

    + {% endif %} +
    + {% if dashboard.report_url %} {% endif %} From 821a5b548ec4e5d306b7ef3d7dfe2c3c8f8c7e95 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 15:07:20 -0300 Subject: [PATCH 18/38] css --- ckanext/dashboard/assets/css/dashboard.css | 33 +++++++++++++++++++ ckanext/dashboard/assets/style.css | 5 --- ckanext/dashboard/assets/webassets.yml | 10 +++--- .../dashboard/templates/dashboard/index.html | 26 +++++++++------ 4 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 ckanext/dashboard/assets/css/dashboard.css delete mode 100644 ckanext/dashboard/assets/style.css diff --git a/ckanext/dashboard/assets/css/dashboard.css b/ckanext/dashboard/assets/css/dashboard.css new file mode 100644 index 0000000..5c8317b --- /dev/null +++ b/ckanext/dashboard/assets/css/dashboard.css @@ -0,0 +1,33 @@ + +.dashboard-list-container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +.dashboard-list { + list-style: none; + padding: 0; +} + +.dashboard-item { + background-color: #f8f8f8; + padding: 15px; + margin-bottom: 10px; + border-radius: 5px; +} + +.dashboard-item a { + text-decoration: none; + color: #007bff; +} + +.dashboard-item a:hover { + text-decoration: underline; +} + +.no-dashboards { + text-align: center; + color: #888; + font-style: italic; +} diff --git a/ckanext/dashboard/assets/style.css b/ckanext/dashboard/assets/style.css deleted file mode 100644 index 9631595..0000000 --- a/ckanext/dashboard/assets/style.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -body { - border-radius: 0; -} -*/ diff --git a/ckanext/dashboard/assets/webassets.yml b/ckanext/dashboard/assets/webassets.yml index 1eea963..befb118 100644 --- a/ckanext/dashboard/assets/webassets.yml +++ b/ckanext/dashboard/assets/webassets.yml @@ -7,8 +7,8 @@ # preload: # - base/main -# dashboard-css: -# filter: cssrewrite -# output: ckanext-dashboard/%(version)s-dashboard.css -# contents: -# - css/dashboard.css +dashboard-css: + filter: cssrewrite + output: ckanext-dashboard/%(version)s-dashboard.css + contents: + - css/dashboard.css diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index e1533f9..f0a85a3 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -3,14 +3,20 @@ {% block title %}Dashboard - List{% endblock %} {% block content %} -

    Dashboard - List

    - {% if dashboards %} -
      - {% for dashboard in dashboards %} -
    • {{ dashboard }}
    • - {% endfor %} -
    - {% else %} -

    No dashboards found.

    - {% endif %} +
    +

    📊 Dashboard - List

    + + {% if dashboards %} +
      + {% for dashboard in dashboards %} +
    • + {{ dashboard.title }}
      + View Dashboard +
    • + {% endfor %} +
    + {% else %} +

    🚫 No dashboards found.

    + {% endif %} +
    {% endblock %} From be92a9345ff06239610262792a1d68a8e0cd2853 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 16:18:25 -0300 Subject: [PATCH 19/38] css ok --- .../assets/css/{dashboard.css => superset.css} | 1 - ckanext/dashboard/assets/js/superset.js | 3 +++ ckanext/dashboard/assets/script.js | 2 +- ckanext/dashboard/assets/style.css | 5 +++++ ckanext/dashboard/assets/webassets.yml | 12 ++++++------ ckanext/dashboard/plugin.py | 2 +- ckanext/dashboard/templates/admin/base.html | 5 +++++ 7 files changed, 21 insertions(+), 9 deletions(-) rename ckanext/dashboard/assets/css/{dashboard.css => superset.css} (99%) create mode 100644 ckanext/dashboard/assets/js/superset.js create mode 100644 ckanext/dashboard/assets/style.css diff --git a/ckanext/dashboard/assets/css/dashboard.css b/ckanext/dashboard/assets/css/superset.css similarity index 99% rename from ckanext/dashboard/assets/css/dashboard.css rename to ckanext/dashboard/assets/css/superset.css index 5c8317b..95e0b36 100644 --- a/ckanext/dashboard/assets/css/dashboard.css +++ b/ckanext/dashboard/assets/css/superset.css @@ -1,4 +1,3 @@ - .dashboard-list-container { max-width: 800px; margin: 0 auto; diff --git a/ckanext/dashboard/assets/js/superset.js b/ckanext/dashboard/assets/js/superset.js new file mode 100644 index 0000000..62ddb03 --- /dev/null +++ b/ckanext/dashboard/assets/js/superset.js @@ -0,0 +1,3 @@ +$(document).ready(function() { + +}); \ No newline at end of file diff --git a/ckanext/dashboard/assets/script.js b/ckanext/dashboard/assets/script.js index 4679181..ab4e6ab 100644 --- a/ckanext/dashboard/assets/script.js +++ b/ckanext/dashboard/assets/script.js @@ -1,4 +1,4 @@ -ckan.module("dashboard-module", function ($, _) { +ckan.module("superset-module", function ($, _) { "use strict"; return { options: { diff --git a/ckanext/dashboard/assets/style.css b/ckanext/dashboard/assets/style.css new file mode 100644 index 0000000..9631595 --- /dev/null +++ b/ckanext/dashboard/assets/style.css @@ -0,0 +1,5 @@ +/* +body { + border-radius: 0; +} +*/ diff --git a/ckanext/dashboard/assets/webassets.yml b/ckanext/dashboard/assets/webassets.yml index befb118..f52ca37 100644 --- a/ckanext/dashboard/assets/webassets.yml +++ b/ckanext/dashboard/assets/webassets.yml @@ -1,14 +1,14 @@ -# dashboard-js: +# superset-js: # filter: rjsmin -# output: ckanext-dashboard/%(version)s-dashboard.js +# output: ckanext-superset/%(version)s-superset.js # contents: -# - js/dashboard.js +# - js/superset.js # extra: # preload: # - base/main -dashboard-css: +superset-css: filter: cssrewrite - output: ckanext-dashboard/%(version)s-dashboard.css + output: ckanext-superset/%(version)s-superset.css contents: - - css/dashboard.css + - css/superset.css diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 764032c..170b3ee 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -24,7 +24,7 @@ class DashboardPlugin(p.SingletonPlugin): def update_config(self, config_): toolkit.add_template_directory(config_, "templates") toolkit.add_public_directory(config_, "public") - toolkit.add_resource("assets", "dashboard") + toolkit.add_resource("assets", "superset") dashboard_template_path = '/app/ckanext/dashboard/templates' if 'extra_template_paths' in config_: config_['extra_template_paths'] += ';' + dashboard_template_path diff --git a/ckanext/dashboard/templates/admin/base.html b/ckanext/dashboard/templates/admin/base.html index 301251f..3246272 100644 --- a/ckanext/dashboard/templates/admin/base.html +++ b/ckanext/dashboard/templates/admin/base.html @@ -1,5 +1,10 @@ {% ckan_extends %} +{% block styles %} + {{ super() }} + {% asset 'superset/superset-css' %} +{% endblock %} + {% block content_primary_nav %} {{ super() }} {{ h.build_nav_icon('dashboard_bp.dashboard_list', _('External dashboards'), icon='icon-list') }} From da5b5bcd27d79a41fbfb256dab8373640dec3617 Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 16:23:41 -0300 Subject: [PATCH 20/38] css ok --- .../assets/css/{superset.css => dashboard.css} | 0 ckanext/dashboard/assets/script.js | 2 +- ckanext/dashboard/assets/webassets.yml | 12 ++++++------ ckanext/dashboard/plugin.py | 2 +- ckanext/dashboard/templates/admin/base.html | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename ckanext/dashboard/assets/css/{superset.css => dashboard.css} (100%) diff --git a/ckanext/dashboard/assets/css/superset.css b/ckanext/dashboard/assets/css/dashboard.css similarity index 100% rename from ckanext/dashboard/assets/css/superset.css rename to ckanext/dashboard/assets/css/dashboard.css diff --git a/ckanext/dashboard/assets/script.js b/ckanext/dashboard/assets/script.js index ab4e6ab..4679181 100644 --- a/ckanext/dashboard/assets/script.js +++ b/ckanext/dashboard/assets/script.js @@ -1,4 +1,4 @@ -ckan.module("superset-module", function ($, _) { +ckan.module("dashboard-module", function ($, _) { "use strict"; return { options: { diff --git a/ckanext/dashboard/assets/webassets.yml b/ckanext/dashboard/assets/webassets.yml index f52ca37..befb118 100644 --- a/ckanext/dashboard/assets/webassets.yml +++ b/ckanext/dashboard/assets/webassets.yml @@ -1,14 +1,14 @@ -# superset-js: +# dashboard-js: # filter: rjsmin -# output: ckanext-superset/%(version)s-superset.js +# output: ckanext-dashboard/%(version)s-dashboard.js # contents: -# - js/superset.js +# - js/dashboard.js # extra: # preload: # - base/main -superset-css: +dashboard-css: filter: cssrewrite - output: ckanext-superset/%(version)s-superset.css + output: ckanext-dashboard/%(version)s-dashboard.css contents: - - css/superset.css + - css/dashboard.css diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 170b3ee..764032c 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -24,7 +24,7 @@ class DashboardPlugin(p.SingletonPlugin): def update_config(self, config_): toolkit.add_template_directory(config_, "templates") toolkit.add_public_directory(config_, "public") - toolkit.add_resource("assets", "superset") + toolkit.add_resource("assets", "dashboard") dashboard_template_path = '/app/ckanext/dashboard/templates' if 'extra_template_paths' in config_: config_['extra_template_paths'] += ';' + dashboard_template_path diff --git a/ckanext/dashboard/templates/admin/base.html b/ckanext/dashboard/templates/admin/base.html index 3246272..29f3988 100644 --- a/ckanext/dashboard/templates/admin/base.html +++ b/ckanext/dashboard/templates/admin/base.html @@ -2,7 +2,7 @@ {% block styles %} {{ super() }} - {% asset 'superset/superset-css' %} + {% asset 'dashboard/dashboard-css' %} {% endblock %} {% block content_primary_nav %} From 65b070eb4d10ad91bb95db14a2bb07d48c62ed6d Mon Sep 17 00:00:00 2001 From: german Date: Thu, 20 Feb 2025 16:51:57 -0300 Subject: [PATCH 21/38] dashboard.js --- ckanext/dashboard/assets/js/{superset.js => dashboard.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ckanext/dashboard/assets/js/{superset.js => dashboard.js} (100%) diff --git a/ckanext/dashboard/assets/js/superset.js b/ckanext/dashboard/assets/js/dashboard.js similarity index 100% rename from ckanext/dashboard/assets/js/superset.js rename to ckanext/dashboard/assets/js/dashboard.js From 47ec59037cfe81416b405b799d32cd213f8b21d0 Mon Sep 17 00:00:00 2001 From: german Date: Fri, 21 Feb 2025 16:58:46 -0300 Subject: [PATCH 22/38] avances --- ckanext/dashboard/blueprints/dashboard.py | 22 ++++++++-- ckanext/dashboard/models.py | 2 + ckanext/dashboard/plugin.py | 14 +++--- .../dashboard/templates/dashboard/index.html | 2 +- .../dashboard/templates/dashboard/new.html | 25 +++++++---- ckanext/dashboard/templates/edit.html | 43 +++++++++++-------- 6 files changed, 72 insertions(+), 36 deletions(-) diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 4784d2d..75eae89 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -33,7 +33,23 @@ def index(): @require_sysadmin_user def dashboard_new(): """Create a new dashboard (view and logic for creation)""" - # Implement the logic to create a dashboard here + log.debug("Creating a new dashboard") + if request.method == 'POST': + data = { + 'package_id': request.form.get('package_id'), + 'title': request.form.get('title'), + 'embeded_url': request.form.get('embeded_url'), + 'report_url': request.form.get('report_url') + } + context = {'model': model, 'user': p.toolkit.c.user} + try: + p.toolkit.get_action('dataset_dashboard_create')(context, data) + flash('Dashboard created successfully', 'success') + log.info("Dashboard created") + except Exception as e: + flash(f'Error: {e}', 'error') + log.error("Error creating dashboard: %s", e) + return redirect(url_for('dashboard_bp.dashboard_list')) return render('dashboard/new.html') @@ -57,7 +73,7 @@ def dashboard_edit(dashboard_id): except Exception as e: flash(f'Error: {e}', 'error') log.error("Error updating dashboard for dashboard_id %s: %s", dashboard_id, e) - return redirect(url_for('dashboard_bp.index')) + return redirect(url_for('dashboard_bp.dashboard_list')) else: try: dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'dashboard_id': dashboard_id}) @@ -79,4 +95,4 @@ def dashboard_delete(dashboard_id): except Exception as e: flash(f'Error: {e}', 'error') log.error("Error deleting dashboard for dashboard_id %s: %s", dashboard_id, e) - return redirect(url_for('dashboard_bp.index')) + return redirect(url_for('dashboard_bp.dashboard_list')) diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index ee4d533..c85bf6e 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -11,5 +11,7 @@ class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): id = Column(Integer, primary_key=True) package_id = Column(UuidType, nullable=False, unique=True) + title = Column(String(200), nullable=False) + description = Column(String(500)) embeded_url = Column(String(200)) report_url = Column(String(200)) diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index 764032c..b87d993 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,8 +1,8 @@ import ckan.plugins as p import ckan.plugins.toolkit as toolkit from ckan import model -from .blueprints.dashboard import dashboard_bp -from .models import DatasetDashboard +from ckanext.dashboard.blueprints.dashboard import dashboard_bp +from ckanext.dashboard.models import DatasetDashboard from ckanext.dashboard.actions.dashboard_dataset import (dataset_dashboard_list, dataset_dashboard_create, dataset_dashboard_show, @@ -38,11 +38,11 @@ def get_blueprint(self): def get_auth_functions(self): functions = { - "dashboard_dataset_list": auth.dashboard_dataset_list, - "dashboard_dataset_create": auth.dashboard_dataset_create, - "dashboard_dataset_show": auth.dashboard_dataset_show, - "dashboard_dataset_update": auth.dashboard_dataset_update, - "dashboard_dataset_delete": auth.dashboard_dataset_delete, + "dataset_dashboard_list": auth.dashboard_dataset_list, + "dataset_dashboard_create": auth.dashboard_dataset_create, + "dataset_dashboard_show": auth.dashboard_dataset_show, + "dataset_dashboard_update": auth.dashboard_dataset_update, + "dataset_dashboard_delete": auth.dashboard_dataset_delete, } return functions diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index f0a85a3..40ff3e0 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -4,7 +4,7 @@ {% block content %}
    -

    📊 Dashboard - List

    +

    📊 Dashboard - List

    New {% if dashboards %}
      diff --git a/ckanext/dashboard/templates/dashboard/new.html b/ckanext/dashboard/templates/dashboard/new.html index 37163a0..1a98301 100644 --- a/ckanext/dashboard/templates/dashboard/new.html +++ b/ckanext/dashboard/templates/dashboard/new.html @@ -3,14 +3,23 @@ {% block title %}New Dashboard{% endblock %} {% block content %} -

      Create a New Dashboard

      +
      +

      Create a New Dashboard

      - - -
      - - -
      - + +
      + + +
      +
      + + +
      +
      + + +
      + +
      {% endblock %} diff --git a/ckanext/dashboard/templates/edit.html b/ckanext/dashboard/templates/edit.html index 5674255..585750a 100644 --- a/ckanext/dashboard/templates/edit.html +++ b/ckanext/dashboard/templates/edit.html @@ -1,22 +1,31 @@ {% extends "admin/base.html" %} -{% block content %} -

      {% if dashboard %}Edit{% else %}Add{% endif %} Dashboard for Dataset {{ package_id }}

      +{% block title %}{% if dashboard %}Edit{% else %}Add{% endif %} Dashboard{% endblock %} -
      - - {{ h.csrf_input() }} +{% block content %} +
      +

      + {% if dashboard %}Edit{% else %}Add{% endif %} Dashboard for Dataset {{ package_id }} +

      + + + {{ h.csrf_input() }} - - {% if dashboard %} - - {% endif %} + + {% if dashboard %} + + {% endif %} - - -
      - - -
      - - +
      + + +
      + +
      + + +
      + + + +
      {% endblock %} From 8d37d48f8028491b5275c24deb777e9d01828c68 Mon Sep 17 00:00:00 2001 From: german Date: Mon, 24 Feb 2025 14:17:35 -0300 Subject: [PATCH 23/38] flash_error & flash_success --- ckanext/dashboard/blueprints/dashboard.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 75eae89..ae9ff74 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -1,9 +1,10 @@ import logging from ckan import model import ckan.plugins as p -from flask import Blueprint, request, redirect, url_for, flash +from flask import Blueprint, request, redirect, url_for from ckan.logic import NotFound from ckan.plugins.toolkit import render +from ckan.lib.helpers import helper_functions as h # Import or define the decorator to restrict access to sysadmins. # You can define it in your extension or import it if you already have it. @@ -23,9 +24,11 @@ def index(): try: dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) except Exception as e: + model.Session.rollback() log.error("Failed to load dashboards: %s", e) - flash("An error occurred while retrieving the dashboards.", "error") + h.flash_error("An error occurred while retrieving the dashboards.", "error") dashboards = [] + h.flash_error(f'Error: {e}', 'error') return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) @@ -44,10 +47,10 @@ def dashboard_new(): context = {'model': model, 'user': p.toolkit.c.user} try: p.toolkit.get_action('dataset_dashboard_create')(context, data) - flash('Dashboard created successfully', 'success') + h.flash_success('Dashboard created successfully', 'success') log.info("Dashboard created") except Exception as e: - flash(f'Error: {e}', 'error') + h.flash_error(f'Error: {e}', 'error') log.error("Error creating dashboard: %s", e) return redirect(url_for('dashboard_bp.dashboard_list')) return render('dashboard/new.html') @@ -68,10 +71,10 @@ def dashboard_edit(dashboard_id): } try: p.toolkit.get_action('dataset_dashboard_update')(context, data) - flash('Dashboard updated successfully', 'success') + h.flash_success('Dashboard updated successfully', 'success') log.info("Dashboard updated for dashboard_id: %s", dashboard_id) except Exception as e: - flash(f'Error: {e}', 'error') + h.flash_error(f'Error: {e}', 'error') log.error("Error updating dashboard for dashboard_id %s: %s", dashboard_id, e) return redirect(url_for('dashboard_bp.dashboard_list')) else: @@ -90,9 +93,9 @@ def dashboard_delete(dashboard_id): context = {'model': model, 'user': p.toolkit.c.user} try: p.toolkit.get_action('dataset_dashboard_delete')(context, {'dashboard_id': dashboard_id}) - flash('Dashboard configuration deleted', 'success') + h.flash_success('Dashboard configuration deleted', 'success') log.info("Dashboard deleted for dashboard_id: %s", dashboard_id) except Exception as e: - flash(f'Error: {e}', 'error') + h.flash_error(f'Error: {e}', 'error') log.error("Error deleting dashboard for dashboard_id %s: %s", dashboard_id, e) return redirect(url_for('dashboard_bp.dashboard_list')) From 76d5b1163763889a3323545a71dda7fccdb72075 Mon Sep 17 00:00:00 2001 From: german Date: Mon, 24 Feb 2025 15:07:04 -0300 Subject: [PATCH 24/38] migrations --- ckanext/dashboard/migrations/__init__.py | 0 ckanext/dashboard/migrations/alembic.ini | 42 ++++++++++ ckanext/dashboard/migrations/env.py | 81 +++++++++++++++++++ ...2b9d1c09_create_dashboard_package_table.py | 31 +++++++ ckanext/dashboard/models.py | 7 ++ 5 files changed, 161 insertions(+) create mode 100644 ckanext/dashboard/migrations/__init__.py create mode 100644 ckanext/dashboard/migrations/alembic.ini create mode 100644 ckanext/dashboard/migrations/env.py create mode 100644 ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py diff --git a/ckanext/dashboard/migrations/__init__.py b/ckanext/dashboard/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ckanext/dashboard/migrations/alembic.ini b/ckanext/dashboard/migrations/alembic.ini new file mode 100644 index 0000000..f86c15a --- /dev/null +++ b/ckanext/dashboard/migrations/alembic.ini @@ -0,0 +1,42 @@ +[alembic] +# Path to migration scripts +script_location = %(here)s + +version_locations = %(here)s/versions + +# Database connection URL +sqlalchemy.url = postgresql://ckan_default:pass@postgresql_bcie/ckan_test + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/ckanext/dashboard/migrations/env.py b/ckanext/dashboard/migrations/env.py new file mode 100644 index 0000000..064a92c --- /dev/null +++ b/ckanext/dashboard/migrations/env.py @@ -0,0 +1,81 @@ +from alembic import context +from sqlalchemy import engine_from_config, pool +from logging.config import fileConfig + +import os + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +name = os.path.basename(os.path.dirname(__file__)) + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + version_table="{}_alembic_version".format(name), + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + version_table="{}_alembic_version".format(name), + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py b/ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py new file mode 100644 index 0000000..4119a1a --- /dev/null +++ b/ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py @@ -0,0 +1,31 @@ +"""Create dashboard_package table + +Revision ID: 43a02b9d1c09 +Revises: +Create Date: 2025-02-24 17:29:28.632072 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '43a02b9d1c09' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'dashboard_package', + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('package_id', sa.String(36), nullable=False, unique=True), + sa.Column('title', sa.String(200), nullable=False), + sa.Column('description', sa.String(500)), + sa.Column('embeded_url', sa.String(200)), + sa.Column('report_url', sa.String(200)) + ) + + +def downgrade(): + op.drop_table('dashboard_package') diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index c85bf6e..12695ee 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -3,6 +3,7 @@ from ckan.model.base import ActiveRecordMixin from ckan.model.types import UuidType +from ckan import model class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): @@ -15,3 +16,9 @@ class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): description = Column(String(500)) embeded_url = Column(String(200)) report_url = Column(String(200)) + + def save(self): + model.Session.add(self) + model.Session.commit() + model.Session.refresh(self) + return self From 04de62f904c510bf8b3c4830b460d9f35e4e234e Mon Sep 17 00:00:00 2001 From: german Date: Mon, 24 Feb 2025 15:07:39 -0300 Subject: [PATCH 25/38] html detail --- ckanext/dashboard/blueprints/dashboard.py | 13 ++++++ .../dashboard/templates/dashboard/index.html | 2 +- .../dashboard/templates/dashboard/show.html | 41 +++++++++++++++++++ ckanext/dashboard/templates/index.html | 6 +-- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 ckanext/dashboard/templates/dashboard/show.html diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index ae9ff74..fcab4e0 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -99,3 +99,16 @@ def dashboard_delete(dashboard_id): h.flash_error(f'Error: {e}', 'error') log.error("Error deleting dashboard for dashboard_id %s: %s", dashboard_id, e) return redirect(url_for('dashboard_bp.dashboard_list')) + + +@dashboard_bp.route('/show/', methods=['GET'], endpoint='dashboard_show') +@require_sysadmin_user +def dashboard_show(dashboard_id): + """Show the configuration of a dashboard using its unique ID""" + log.debug("Showing dashboard for dashboard_id: %s", dashboard_id) + context = {'model': model, 'user': p.toolkit.c.user} + try: + dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'dashboard_id': dashboard_id}) + except NotFound: + dashboard = None + return render('dashboard/show.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id}) diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index 40ff3e0..38b594a 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -11,7 +11,7 @@

      📊 Dashboard - List

      {{ dashboard.title }}
      -
      View Dashboard + View Dashboard {% endfor %}
    diff --git a/ckanext/dashboard/templates/dashboard/show.html b/ckanext/dashboard/templates/dashboard/show.html new file mode 100644 index 0000000..2ba39c3 --- /dev/null +++ b/ckanext/dashboard/templates/dashboard/show.html @@ -0,0 +1,41 @@ +{% extends "admin/base.html" %} + +{% block page_title %} + View Dashboard - {{ dashboard.title if dashboard else "Not Found" }} +{% endblock %} + +{% block content %} +
    + {% if dashboard %} +

    {{ dashboard.title }}

    + +

    Package ID: {{ dashboard.package_id }}

    +

    Description: {{ dashboard.description or "No description provided." }}

    +

    Embedded URL: + {% if dashboard.embeded_url %} + {{ dashboard.embeded_url }} + {% else %} + N/A + {% endif %} +

    +

    Report URL: + {% if dashboard.report_url %} + {{ dashboard.report_url }} + {% else %} + N/A + {% endif %} +

    + +
    + Edit +
    + +
    +
    + + {% else %} +

    Dashboard Not Found

    +

    The dashboard you're trying to view does not exist.

    + {% endif %} +
    +{% endblock %}z \ No newline at end of file diff --git a/ckanext/dashboard/templates/index.html b/ckanext/dashboard/templates/index.html index e01eab8..ed29dd3 100644 --- a/ckanext/dashboard/templates/index.html +++ b/ckanext/dashboard/templates/index.html @@ -1,13 +1,13 @@ {% extends "base.html" %} {% block content %}

    Dashboard Configurations

    -Add new dashboard +Add new dashboard
      {% for d in dashboards %}
    • Dataset: {{ d.package_id }} - - Edit -
      + Edit +
    • From 819d5b041c566ded19998bc3b0ee04a88cb70b45 Mon Sep 17 00:00:00 2001 From: german Date: Mon, 24 Feb 2025 16:35:56 -0300 Subject: [PATCH 26/38] html --- ckanext/dashboard/actions/dashboard_dataset.py | 2 ++ ckanext/dashboard/blueprints/dashboard.py | 8 +++++--- ckanext/dashboard/templates/{ => dashboard}/edit.html | 0 ckanext/dashboard/templates/dashboard/index.html | 5 +++-- ckanext/dashboard/templates/dashboard/new.html | 4 ++++ ckanext/dashboard/templates/dashboard/show.html | 1 - 6 files changed, 14 insertions(+), 6 deletions(-) rename ckanext/dashboard/templates/{ => dashboard}/edit.html (100%) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index 43c1b08..c753c5f 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -22,6 +22,8 @@ def dataset_dashboard_list(context, data_dict): for dash in dashboards: result.append({ 'dashboard_id': dash.id, + 'title': dash.title, + 'description': dash.description, 'package_id': dash.package_id, 'embeded_url': dash.embeded_url, 'report_url': dash.report_url, diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index fcab4e0..25b7026 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -41,6 +41,7 @@ def dashboard_new(): data = { 'package_id': request.form.get('package_id'), 'title': request.form.get('title'), + 'description': request.form.get('description'), 'embeded_url': request.form.get('embeded_url'), 'report_url': request.form.get('report_url') } @@ -79,7 +80,7 @@ def dashboard_edit(dashboard_id): return redirect(url_for('dashboard_bp.dashboard_list')) else: try: - dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'dashboard_id': dashboard_id}) + dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'id': dashboard_id}) except NotFound: dashboard = None return render('dashboard/edit.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id}) @@ -92,7 +93,7 @@ def dashboard_delete(dashboard_id): log.debug("Deleting dashboard for dashboard_id: %s", dashboard_id) context = {'model': model, 'user': p.toolkit.c.user} try: - p.toolkit.get_action('dataset_dashboard_delete')(context, {'dashboard_id': dashboard_id}) + p.toolkit.get_action('dataset_dashboard_delete')(context, {'id': dashboard_id}) h.flash_success('Dashboard configuration deleted', 'success') log.info("Dashboard deleted for dashboard_id: %s", dashboard_id) except Exception as e: @@ -108,7 +109,8 @@ def dashboard_show(dashboard_id): log.debug("Showing dashboard for dashboard_id: %s", dashboard_id) context = {'model': model, 'user': p.toolkit.c.user} try: - dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'dashboard_id': dashboard_id}) + dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'id': dashboard_id}) + h.flash_success('Dashboard configuration deleted', 'success') except NotFound: dashboard = None return render('dashboard/show.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id}) diff --git a/ckanext/dashboard/templates/edit.html b/ckanext/dashboard/templates/dashboard/edit.html similarity index 100% rename from ckanext/dashboard/templates/edit.html rename to ckanext/dashboard/templates/dashboard/edit.html diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index 38b594a..95c3393 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -4,14 +4,15 @@ {% block content %}
      -

      📊 Dashboard - List

      New +

      📊 Dashboard - List

      + New {% if dashboards %}
        {% for dashboard in dashboards %}
      • {{ dashboard.title }}
        - View Dashboard + View
      • {% endfor %}
      diff --git a/ckanext/dashboard/templates/dashboard/new.html b/ckanext/dashboard/templates/dashboard/new.html index 1a98301..9516d57 100644 --- a/ckanext/dashboard/templates/dashboard/new.html +++ b/ckanext/dashboard/templates/dashboard/new.html @@ -11,6 +11,10 @@

      Create a New Dashboard

      +
      + + +
      diff --git a/ckanext/dashboard/templates/dashboard/show.html b/ckanext/dashboard/templates/dashboard/show.html index 2ba39c3..55a134e 100644 --- a/ckanext/dashboard/templates/dashboard/show.html +++ b/ckanext/dashboard/templates/dashboard/show.html @@ -9,7 +9,6 @@ {% if dashboard %}

      {{ dashboard.title }}

      -

      Package ID: {{ dashboard.package_id }}

      Description: {{ dashboard.description or "No description provided." }}

      Embedded URL: {% if dashboard.embeded_url %} From 8b109297d84169c4ab7540192e928788da908144 Mon Sep 17 00:00:00 2001 From: german Date: Tue, 25 Feb 2025 16:09:59 -0300 Subject: [PATCH 27/38] Casi andando migraciones --- .../dashboard}/alembic.ini | 0 .../{migrations => migration/dashboard}/env.py | 4 +++- ...3a02b9d1c09_create_dashboard_package_table.py | 11 ++++++----- ckanext/dashboard/migrations/__init__.py | 0 ckanext/dashboard/models.py | 16 +++++++++++++++- 5 files changed, 24 insertions(+), 7 deletions(-) rename ckanext/dashboard/{migrations => migration/dashboard}/alembic.ini (100%) rename ckanext/dashboard/{migrations => migration/dashboard}/env.py (93%) rename ckanext/dashboard/{migrations => migration/dashboard}/versions/43a02b9d1c09_create_dashboard_package_table.py (56%) delete mode 100644 ckanext/dashboard/migrations/__init__.py diff --git a/ckanext/dashboard/migrations/alembic.ini b/ckanext/dashboard/migration/dashboard/alembic.ini similarity index 100% rename from ckanext/dashboard/migrations/alembic.ini rename to ckanext/dashboard/migration/dashboard/alembic.ini diff --git a/ckanext/dashboard/migrations/env.py b/ckanext/dashboard/migration/dashboard/env.py similarity index 93% rename from ckanext/dashboard/migrations/env.py rename to ckanext/dashboard/migration/dashboard/env.py index 064a92c..e187092 100644 --- a/ckanext/dashboard/migrations/env.py +++ b/ckanext/dashboard/migration/dashboard/env.py @@ -58,8 +58,10 @@ def run_migrations_online(): and associate a connection with the context. """ + config_ini_section = config.get_section(config.config_ini_section) + print("Config Section:", config_ini_section) connectable = engine_from_config( - config.get_section(config.config_ini_section), + config_ini_section, prefix="sqlalchemy.", poolclass=pool.NullPool, ) diff --git a/ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py similarity index 56% rename from ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py rename to ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py index 4119a1a..ceacbe4 100644 --- a/ckanext/dashboard/migrations/versions/43a02b9d1c09_create_dashboard_package_table.py +++ b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py @@ -7,6 +7,7 @@ """ from alembic import op import sqlalchemy as sa +from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision = '43a02b9d1c09' @@ -19,11 +20,11 @@ def upgrade(): op.create_table( 'dashboard_package', sa.Column('id', sa.Integer, primary_key=True), - sa.Column('package_id', sa.String(36), nullable=False, unique=True), - sa.Column('title', sa.String(200), nullable=False), - sa.Column('description', sa.String(500)), - sa.Column('embeded_url', sa.String(200)), - sa.Column('report_url', sa.String(200)) + sa.Column('package_id', postgresql.UUID(as_uuid=True), nullable=False, unique=True), + sa.Column('title', sa.String(length=200), nullable=False), + sa.Column('description', sa.String(length=500)), + sa.Column('embeded_url', sa.String(length=200)), + sa.Column('report_url', sa.String(length=200)) ) diff --git a/ckanext/dashboard/migrations/__init__.py b/ckanext/dashboard/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index 12695ee..d8d2869 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -1,3 +1,4 @@ +from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String from ckan.plugins import toolkit from ckan.model.base import ActiveRecordMixin @@ -6,7 +7,10 @@ from ckan import model -class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): +Base = declarative_base() + + +class DatasetDashboard(Base): """Data model for storing the configuration of a dashboard per dataset""" __tablename__ = "dashboard_package" @@ -17,6 +21,16 @@ class DatasetDashboard(toolkit.BaseModel, ActiveRecordMixin): embeded_url = Column(String(200)) report_url = Column(String(200)) + def dictize(self): + return { + 'id': self.id, + 'package_id': str(self.package_id), + 'title': self.title, + 'description': self.description, + 'embeded_url': self.embeded_url, + 'report_url': self.report_url + } + def save(self): model.Session.add(self) model.Session.commit() From 92dad90428bcba0d684e68ffa2e449c9aa7fd59a Mon Sep 17 00:00:00 2001 From: german Date: Tue, 25 Feb 2025 16:24:26 -0300 Subject: [PATCH 28/38] Andando una primera version --- .../dashboard/actions/dashboard_dataset.py | 19 +++++++++++++++++-- ...2b9d1c09_create_dashboard_package_table.py | 2 +- ckanext/dashboard/models.py | 2 -- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index c753c5f..91d2b35 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -1,4 +1,5 @@ import logging +import uuid from ckan.plugins import toolkit from ckan import model from ckanext.dashboard.models import DatasetDashboard @@ -58,6 +59,8 @@ def dataset_dashboard_show(context, data_dict): 'package_id': dashboard.package_id, 'title': dashboard.title, 'description': dashboard.description, + 'embeded_url': dashboard.embeded_url, + 'report_url': dashboard.report_url } @@ -81,11 +84,19 @@ def dataset_dashboard_create(context, data_dict): raise ValueError("Missing required fields: " + ", ".join(missing_fields)) session = model.Session + # Extract and validate package_id + package_id = data_dict.get('package_id', '').strip() + if not package_id: + # Generate a new UUID if package_id is empty + package_id = str(uuid.uuid4()) + log.info(f"Generated new package_id: {package_id}") new_dashboard = DatasetDashboard( - package_id=data_dict.get('package_id'), + package_id=package_id, title=data_dict.get('title'), - description=data_dict.get('description', '') # 'description' is optional + description=data_dict.get('description', ''), + embeded_url=data_dict.get('embeded_url', ''), + report_url=data_dict.get('report_url', ''), ) session.add(new_dashboard) @@ -96,6 +107,8 @@ def dataset_dashboard_create(context, data_dict): 'package_id': new_dashboard.package_id, 'title': new_dashboard.title, 'description': new_dashboard.description, + 'embeded_url': new_dashboard.embeded_url, + 'report_url': new_dashboard.report_url, } @@ -132,6 +145,8 @@ def dataset_dashboard_update(context, data_dict): 'package_id': dashboard.package_id, 'title': dashboard.title, 'description': dashboard.description, + 'embeded_url': dashboard.embeded_url, + 'report_url': dashboard.report } diff --git a/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py index ceacbe4..8639564 100644 --- a/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py +++ b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py @@ -1,7 +1,7 @@ """Create dashboard_package table Revision ID: 43a02b9d1c09 -Revises: +Revises: Create Date: 2025-02-24 17:29:28.632072 """ diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index d8d2869..8165d42 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -1,7 +1,5 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String -from ckan.plugins import toolkit -from ckan.model.base import ActiveRecordMixin from ckan.model.types import UuidType from ckan import model From de2ca2eff5b23ddb031190dc90a05b770046b0b3 Mon Sep 17 00:00:00 2001 From: german Date: Tue, 25 Feb 2025 17:03:34 -0300 Subject: [PATCH 29/38] fixes --- ckanext/dashboard/actions/dashboard_dataset.py | 13 ++++--------- ckanext/dashboard/blueprints/dashboard.py | 1 - ckanext/dashboard/templates/dashboard/edit.html | 2 +- ckanext/dashboard/templates/dashboard/show.html | 2 +- ckanext/dashboard/templates/package/read.html | 2 -- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index 91d2b35..ae375c4 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -44,9 +44,7 @@ def dataset_dashboard_show(context, data_dict): """ log.info("Executing dataset_dashboard_show") - dashboard_id = data_dict.get('id') - if not dashboard_id: - raise ValueError("The 'id' parameter is required to show the dashboard.") + dashboard_id = toolkit.get_or_bust(data_dict, 'id') session = model.Session dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() @@ -78,14 +76,11 @@ def dataset_dashboard_create(context, data_dict): log.info("Executing dataset_dashboard_create") # Validate required fields - required_fields = ['package_id', 'title'] - missing_fields = [field for field in required_fields if field not in data_dict] - if missing_fields: - raise ValueError("Missing required fields: " + ", ".join(missing_fields)) + package_id, title = toolkit.get_or_bust(data_dict, ['package_id', 'title']) session = model.Session # Extract and validate package_id - package_id = data_dict.get('package_id', '').strip() + if not package_id: # Generate a new UUID if package_id is empty package_id = str(uuid.uuid4()) @@ -122,7 +117,7 @@ def dataset_dashboard_update(context, data_dict): """ log.info("Executing dataset_dashboard_update") - dashboard_id = data_dict.get('id') + dashboard_id = toolkit.get_or_bust('id') if not dashboard_id: raise ValueError("The 'id' parameter is required to update the dashboard.") diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 25b7026..b612b5e 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -24,7 +24,6 @@ def index(): try: dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) except Exception as e: - model.Session.rollback() log.error("Failed to load dashboards: %s", e) h.flash_error("An error occurred while retrieving the dashboards.", "error") dashboards = [] diff --git a/ckanext/dashboard/templates/dashboard/edit.html b/ckanext/dashboard/templates/dashboard/edit.html index 585750a..37fe9df 100644 --- a/ckanext/dashboard/templates/dashboard/edit.html +++ b/ckanext/dashboard/templates/dashboard/edit.html @@ -10,7 +10,7 @@

      {{ h.csrf_input() }} - + {% if dashboard %} {% endif %} diff --git a/ckanext/dashboard/templates/dashboard/show.html b/ckanext/dashboard/templates/dashboard/show.html index 55a134e..cdebeb3 100644 --- a/ckanext/dashboard/templates/dashboard/show.html +++ b/ckanext/dashboard/templates/dashboard/show.html @@ -37,4 +37,4 @@

      Dashboard Not Found

      The dashboard you're trying to view does not exist.

      {% endif %}
      -{% endblock %}z \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/ckanext/dashboard/templates/package/read.html b/ckanext/dashboard/templates/package/read.html index 25affb3..2f95846 100644 --- a/ckanext/dashboard/templates/package/read.html +++ b/ckanext/dashboard/templates/package/read.html @@ -3,12 +3,10 @@ {% block package_description %} {{ super() }} - {# Llamamos al helper para obtener la configuración del dashboard #} {% set dashboard = h.get_dataset_dashboards(package['id']) %} {% if dashboard and dashboard.embeded_url %}

      Dashboard

      - {# Incluimos el snippet de embebido #} {% include "dashboard/snippet.html" %} {% endif %} {% endblock %} From f108939a1ff30a3e39a374ed421ae584d55181c0 Mon Sep 17 00:00:00 2001 From: german Date: Wed, 26 Feb 2025 09:39:12 -0300 Subject: [PATCH 30/38] change dashbord_bp a embeded_dashboard --- ckanext/dashboard/actions/dashboard_dataset.py | 14 +++++++------- ckanext/dashboard/blueprints/dashboard.py | 8 ++++---- ckanext/dashboard/templates/admin/base.html | 2 +- ckanext/dashboard/templates/dashboard/index.html | 4 ++-- ckanext/dashboard/templates/dashboard/show.html | 4 ++-- ckanext/dashboard/templates/index.html | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index ae375c4..ffc7dc5 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -117,9 +117,7 @@ def dataset_dashboard_update(context, data_dict): """ log.info("Executing dataset_dashboard_update") - dashboard_id = toolkit.get_or_bust('id') - if not dashboard_id: - raise ValueError("The 'id' parameter is required to update the dashboard.") + dashboard_id = toolkit.get_or_bust(data_dict, 'dashboard_id') session = model.Session dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() @@ -131,6 +129,10 @@ def dataset_dashboard_update(context, data_dict): dashboard.title = data_dict['title'] if 'description' in data_dict: dashboard.description = data_dict['description'] + if 'embeded_url' in data_dict: + dashboard.embeded_url = data_dict['embeded_url'] + if 'report_url' in data_dict: + dashboard.report_url = data_dict['report_url'] session.add(dashboard) session.commit() @@ -141,7 +143,7 @@ def dataset_dashboard_update(context, data_dict): 'title': dashboard.title, 'description': dashboard.description, 'embeded_url': dashboard.embeded_url, - 'report_url': dashboard.report + 'report_url': dashboard.report_url } @@ -155,9 +157,7 @@ def dataset_dashboard_delete(context, data_dict): """ log.info("Executing dataset_dashboard_delete") - dashboard_id = data_dict.get('id') - if not dashboard_id: - raise ValueError("The 'id' parameter is required to delete the dashboard.") + dashboard_id = toolkit.get_or_bust(data_dict, 'id') session = model.Session dashboard = session.query(DatasetDashboard).filter_by(id=dashboard_id).first() diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index b612b5e..0294144 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -12,7 +12,7 @@ log = logging.getLogger(__name__) -dashboard_bp = Blueprint('dashboard_bp', __name__, url_prefix='/dashboard-external') +dashboard_bp = Blueprint('embeded_dashboard', __name__, url_prefix='/embeded-dashboard') @dashboard_bp.route('/', methods=['GET'], endpoint='dashboard_list') @@ -52,7 +52,7 @@ def dashboard_new(): except Exception as e: h.flash_error(f'Error: {e}', 'error') log.error("Error creating dashboard: %s", e) - return redirect(url_for('dashboard_bp.dashboard_list')) + return redirect(url_for('embeded_dashboard.dashboard_list')) return render('dashboard/new.html') @@ -76,7 +76,7 @@ def dashboard_edit(dashboard_id): except Exception as e: h.flash_error(f'Error: {e}', 'error') log.error("Error updating dashboard for dashboard_id %s: %s", dashboard_id, e) - return redirect(url_for('dashboard_bp.dashboard_list')) + return redirect(url_for('embeded_dashboard.dashboard_list')) else: try: dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'id': dashboard_id}) @@ -98,7 +98,7 @@ def dashboard_delete(dashboard_id): except Exception as e: h.flash_error(f'Error: {e}', 'error') log.error("Error deleting dashboard for dashboard_id %s: %s", dashboard_id, e) - return redirect(url_for('dashboard_bp.dashboard_list')) + return redirect(url_for('embeded_dashboard.dashboard_list')) @dashboard_bp.route('/show/', methods=['GET'], endpoint='dashboard_show') diff --git a/ckanext/dashboard/templates/admin/base.html b/ckanext/dashboard/templates/admin/base.html index 29f3988..7cf3311 100644 --- a/ckanext/dashboard/templates/admin/base.html +++ b/ckanext/dashboard/templates/admin/base.html @@ -7,7 +7,7 @@ {% block content_primary_nav %} {{ super() }} - {{ h.build_nav_icon('dashboard_bp.dashboard_list', _('External dashboards'), icon='icon-list') }} + {{ h.build_nav_icon('embeded_dashboard.dashboard_list', _('Embebed dashboards'), icon='icon-list') }} {# Los endpoints que requieren package_id se muestran en contextos específicos y no en la navegación global #} {% endblock %} diff --git a/ckanext/dashboard/templates/dashboard/index.html b/ckanext/dashboard/templates/dashboard/index.html index 95c3393..a49e813 100644 --- a/ckanext/dashboard/templates/dashboard/index.html +++ b/ckanext/dashboard/templates/dashboard/index.html @@ -5,14 +5,14 @@ {% block content %}

      📊 Dashboard - List

      - New + New {% if dashboards %}
        {% for dashboard in dashboards %}
      • {{ dashboard.title }}
        - View + View
      • {% endfor %}
      diff --git a/ckanext/dashboard/templates/dashboard/show.html b/ckanext/dashboard/templates/dashboard/show.html index cdebeb3..c07c167 100644 --- a/ckanext/dashboard/templates/dashboard/show.html +++ b/ckanext/dashboard/templates/dashboard/show.html @@ -26,8 +26,8 @@

      {{ dashboard.title }}

      - Edit -
      + Edit +
      diff --git a/ckanext/dashboard/templates/index.html b/ckanext/dashboard/templates/index.html index ed29dd3..51469b3 100644 --- a/ckanext/dashboard/templates/index.html +++ b/ckanext/dashboard/templates/index.html @@ -1,13 +1,13 @@ {% extends "base.html" %} {% block content %}

      Dashboard Configurations

      -Add new dashboard +Add new dashboard
        {% for d in dashboards %}
      • Dataset: {{ d.package_id }} - - Edit -
        + Edit +
      • From 7fcb5812e91d50e9156b784892c974c23411745e Mon Sep 17 00:00:00 2001 From: pdelboca Date: Fri, 28 Feb 2025 16:42:11 +0100 Subject: [PATCH 31/38] Create helpers module --- ckanext/dashboard/helpers.py | 8 ++++++++ ckanext/dashboard/plugin.py | 9 ++------- ckanext/dashboard/templates/package/read.html | 6 ++---- 3 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 ckanext/dashboard/helpers.py diff --git a/ckanext/dashboard/helpers.py b/ckanext/dashboard/helpers.py new file mode 100644 index 0000000..3a75d1a --- /dev/null +++ b/ckanext/dashboard/helpers.py @@ -0,0 +1,8 @@ +from ckan import model + +from ckanext.dashboard.models import DatasetDashboard + + +def get_dataset_dashboard(package_id): + session = model.Session + return session.query(DatasetDashboard).filter_by(package_id=package_id).first() diff --git a/ckanext/dashboard/plugin.py b/ckanext/dashboard/plugin.py index b87d993..7e602e1 100644 --- a/ckanext/dashboard/plugin.py +++ b/ckanext/dashboard/plugin.py @@ -1,14 +1,13 @@ import ckan.plugins as p import ckan.plugins.toolkit as toolkit -from ckan import model from ckanext.dashboard.blueprints.dashboard import dashboard_bp -from ckanext.dashboard.models import DatasetDashboard from ckanext.dashboard.actions.dashboard_dataset import (dataset_dashboard_list, dataset_dashboard_create, dataset_dashboard_show, dataset_dashboard_update, dataset_dashboard_delete) from ckanext.dashboard.auth import dashboard_dataset as auth +from ckanext.dashboard.helpers import get_dataset_dashboard class DashboardPlugin(p.SingletonPlugin): @@ -56,8 +55,4 @@ def get_actions(self): } def get_helpers(self): - return {'get_dataset_dashboard': self.get_dataset_dashboard} - - def get_dataset_dashboard(self, package_id): - session = model.Session - return session.query(DatasetDashboard).filter_by(package_id=package_id).first() + return {'get_dataset_dashboard': get_dataset_dashboard} diff --git a/ckanext/dashboard/templates/package/read.html b/ckanext/dashboard/templates/package/read.html index 2f95846..ba50670 100644 --- a/ckanext/dashboard/templates/package/read.html +++ b/ckanext/dashboard/templates/package/read.html @@ -1,10 +1,8 @@ -{% extends "package/read.html" %} +{% ckan_extends %} {% block package_description %} {{ super() }} - - {% set dashboard = h.get_dataset_dashboards(package['id']) %} - + {% set dashboard = h.get_dataset_dashboard(pkg_dict.id) %} {% if dashboard and dashboard.embeded_url %}

        Dashboard

        {% include "dashboard/snippet.html" %} From fe1750f5465a48d4438e70657733705934603494 Mon Sep 17 00:00:00 2001 From: pdelboca Date: Fri, 28 Feb 2025 16:46:14 +0100 Subject: [PATCH 32/38] Clean not necessary flash message --- ckanext/dashboard/blueprints/dashboard.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 0294144..f6593a7 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -109,7 +109,6 @@ def dashboard_show(dashboard_id): context = {'model': model, 'user': p.toolkit.c.user} try: dashboard = p.toolkit.get_action('dataset_dashboard_show')(context, {'id': dashboard_id}) - h.flash_success('Dashboard configuration deleted', 'success') except NotFound: dashboard = None return render('dashboard/show.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id}) From bfc39aa80a80a2b0f3cc0bc15da1460ce043fb7f Mon Sep 17 00:00:00 2001 From: pdelboca Date: Fri, 28 Feb 2025 17:19:34 +0100 Subject: [PATCH 33/38] Move dashboard form to dataset UI --- ckanext/dashboard/blueprints/dashboard.py | 30 +++++-------------- .../dashboard/templates/dashboard/new.html | 5 ++-- .../templates/package/read_base.html | 8 +++++ 3 files changed, 18 insertions(+), 25 deletions(-) create mode 100644 ckanext/dashboard/templates/package/read_base.html diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index f6593a7..9de883b 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -3,7 +3,7 @@ import ckan.plugins as p from flask import Blueprint, request, redirect, url_for from ckan.logic import NotFound -from ckan.plugins.toolkit import render +from ckan.plugins import toolkit from ckan.lib.helpers import helper_functions as h # Import or define the decorator to restrict access to sysadmins. @@ -15,30 +15,16 @@ dashboard_bp = Blueprint('embeded_dashboard', __name__, url_prefix='/embeded-dashboard') -@dashboard_bp.route('/', methods=['GET'], endpoint='dashboard_list') +@dashboard_bp.route('/dataset/dashboard//new', methods=['GET', 'POST'], endpoint='new') @require_sysadmin_user -def index(): - """List dashboard configurations""" - log.debug("Listing dashboard configurations") - context = {'model': model, 'user': p.toolkit.c.user} - try: - dashboards = p.toolkit.get_action('dataset_dashboard_list')(context, {}) - except Exception as e: - log.error("Failed to load dashboards: %s", e) - h.flash_error("An error occurred while retrieving the dashboards.", "error") - dashboards = [] - h.flash_error(f'Error: {e}', 'error') - return render('dashboard/index.html', extra_vars={'dashboards': dashboards}) - - -@dashboard_bp.route('/new', methods=['GET', 'POST'], endpoint='dashboard_new') -@require_sysadmin_user -def dashboard_new(): +def dashboard_new(package_id): """Create a new dashboard (view and logic for creation)""" log.debug("Creating a new dashboard") + # TODO: try-catch + pkg_dict = toolkit.get_action('package_show')({}, {'id': package_id}) if request.method == 'POST': data = { - 'package_id': request.form.get('package_id'), + 'package_id': pkg_dict['id'], 'title': request.form.get('title'), 'description': request.form.get('description'), 'embeded_url': request.form.get('embeded_url'), @@ -52,8 +38,8 @@ def dashboard_new(): except Exception as e: h.flash_error(f'Error: {e}', 'error') log.error("Error creating dashboard: %s", e) - return redirect(url_for('embeded_dashboard.dashboard_list')) - return render('dashboard/new.html') + return redirect(url_for('dataset.read', id=pkg_dict['id'])) + return toolkit.render('dashboard/new.html', {"pkg_dict": pkg_dict}) @dashboard_bp.route('/edit/', methods=['GET', 'POST'], endpoint='dashboard_edit') diff --git a/ckanext/dashboard/templates/dashboard/new.html b/ckanext/dashboard/templates/dashboard/new.html index 9516d57..dc68e87 100644 --- a/ckanext/dashboard/templates/dashboard/new.html +++ b/ckanext/dashboard/templates/dashboard/new.html @@ -1,12 +1,11 @@ -{% extends "admin/base.html" %} +{% extends "package/read_base.html" %} {% block title %}New Dashboard{% endblock %} -{% block content %} +{% block primary_content_inner %}

        Create a New Dashboard

        -
        diff --git a/ckanext/dashboard/templates/package/read_base.html b/ckanext/dashboard/templates/package/read_base.html new file mode 100644 index 0000000..2759cb8 --- /dev/null +++ b/ckanext/dashboard/templates/package/read_base.html @@ -0,0 +1,8 @@ +{% ckan_extends %} + +{% block content_primary_nav %} + {{ super() }} + {% if h.check_access("dataset_dashboard_create", {}) %} + {{ h.build_nav_icon("embeded_dashboard.new", _("Dashboard"), package_id=pkg.id, icon="chart-column") }} + {% endif %} +{% endblock %} From 1d8f73d8e82f5b9f23e5f4c2016b067338a3cefb Mon Sep 17 00:00:00 2001 From: pdelboca Date: Fri, 28 Feb 2025 17:27:44 +0100 Subject: [PATCH 34/38] Remove print statement --- ckanext/dashboard/migration/dashboard/env.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ckanext/dashboard/migration/dashboard/env.py b/ckanext/dashboard/migration/dashboard/env.py index e187092..4565b19 100644 --- a/ckanext/dashboard/migration/dashboard/env.py +++ b/ckanext/dashboard/migration/dashboard/env.py @@ -59,7 +59,6 @@ def run_migrations_online(): """ config_ini_section = config.get_section(config.config_ini_section) - print("Config Section:", config_ini_section) connectable = engine_from_config( config_ini_section, prefix="sqlalchemy.", From 4382ac3ebb86f9323ce58e225a62c00babfbd62b Mon Sep 17 00:00:00 2001 From: pdelboca Date: Fri, 28 Feb 2025 17:54:53 +0100 Subject: [PATCH 35/38] Create Dashboard with hardcoded type --- ckanext/dashboard/actions/dashboard_dataset.py | 16 +++++++--------- ckanext/dashboard/blueprints/dashboard.py | 4 +++- ...3a02b9d1c09_create_dashboard_package_table.py | 1 + ckanext/dashboard/models.py | 2 ++ .../dashboard/templates/dashboard/snippet.html | 6 +++--- ckanext/dashboard/templates/package/read.html | 2 +- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/ckanext/dashboard/actions/dashboard_dataset.py b/ckanext/dashboard/actions/dashboard_dataset.py index ffc7dc5..57b27a0 100644 --- a/ckanext/dashboard/actions/dashboard_dataset.py +++ b/ckanext/dashboard/actions/dashboard_dataset.py @@ -76,24 +76,20 @@ def dataset_dashboard_create(context, data_dict): log.info("Executing dataset_dashboard_create") # Validate required fields - package_id, title = toolkit.get_or_bust(data_dict, ['package_id', 'title']) - - session = model.Session - # Extract and validate package_id - - if not package_id: - # Generate a new UUID if package_id is empty - package_id = str(uuid.uuid4()) - log.info(f"Generated new package_id: {package_id}") + package_id, title, dashboard_type = toolkit.get_or_bust( + data_dict, ['package_id', 'title', 'dashboard_type'] + ) new_dashboard = DatasetDashboard( package_id=package_id, title=data_dict.get('title'), description=data_dict.get('description', ''), + dashboard_type=dashboard_type, embeded_url=data_dict.get('embeded_url', ''), report_url=data_dict.get('report_url', ''), ) + session = model.Session session.add(new_dashboard) session.commit() @@ -102,6 +98,7 @@ def dataset_dashboard_create(context, data_dict): 'package_id': new_dashboard.package_id, 'title': new_dashboard.title, 'description': new_dashboard.description, + 'dashboard_type': dashboard_type, 'embeded_url': new_dashboard.embeded_url, 'report_url': new_dashboard.report_url, } @@ -129,6 +126,7 @@ def dataset_dashboard_update(context, data_dict): dashboard.title = data_dict['title'] if 'description' in data_dict: dashboard.description = data_dict['description'] + # TODO: Handle dashboard_type if 'embeded_url' in data_dict: dashboard.embeded_url = data_dict['embeded_url'] if 'report_url' in data_dict: diff --git a/ckanext/dashboard/blueprints/dashboard.py b/ckanext/dashboard/blueprints/dashboard.py index 9de883b..f3a2ea5 100644 --- a/ckanext/dashboard/blueprints/dashboard.py +++ b/ckanext/dashboard/blueprints/dashboard.py @@ -12,7 +12,7 @@ log = logging.getLogger(__name__) -dashboard_bp = Blueprint('embeded_dashboard', __name__, url_prefix='/embeded-dashboard') +dashboard_bp = Blueprint('embeded_dashboard', __name__) @dashboard_bp.route('/dataset/dashboard//new', methods=['GET', 'POST'], endpoint='new') @@ -27,6 +27,8 @@ def dashboard_new(package_id): 'package_id': pkg_dict['id'], 'title': request.form.get('title'), 'description': request.form.get('description'), + # TODO: Add field in form + 'dashboard_type': 'tableau', 'embeded_url': request.form.get('embeded_url'), 'report_url': request.form.get('report_url') } diff --git a/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py index 8639564..8e9509d 100644 --- a/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py +++ b/ckanext/dashboard/migration/dashboard/versions/43a02b9d1c09_create_dashboard_package_table.py @@ -23,6 +23,7 @@ def upgrade(): sa.Column('package_id', postgresql.UUID(as_uuid=True), nullable=False, unique=True), sa.Column('title', sa.String(length=200), nullable=False), sa.Column('description', sa.String(length=500)), + sa.Column('dashboard_type', sa.String(length=20)), sa.Column('embeded_url', sa.String(length=200)), sa.Column('report_url', sa.String(length=200)) ) diff --git a/ckanext/dashboard/models.py b/ckanext/dashboard/models.py index 8165d42..1f2757d 100644 --- a/ckanext/dashboard/models.py +++ b/ckanext/dashboard/models.py @@ -16,6 +16,7 @@ class DatasetDashboard(Base): package_id = Column(UuidType, nullable=False, unique=True) title = Column(String(200), nullable=False) description = Column(String(500)) + dashboard_type = Column(String(20)) embeded_url = Column(String(200)) report_url = Column(String(200)) @@ -25,6 +26,7 @@ def dictize(self): 'package_id': str(self.package_id), 'title': self.title, 'description': self.description, + 'dashboard_type': self.dashboard_type, 'embeded_url': self.embeded_url, 'report_url': self.report_url } diff --git a/ckanext/dashboard/templates/dashboard/snippet.html b/ckanext/dashboard/templates/dashboard/snippet.html index 11be2a0..417b863 100644 --- a/ckanext/dashboard/templates/dashboard/snippet.html +++ b/ckanext/dashboard/templates/dashboard/snippet.html @@ -1,18 +1,18 @@ -{% if dashboard.type == "tableau" %} +{% if dashboard.dashboard_type == "tableau" %} {% endif %}
        - {% if dashboard.type == "tableau" %} + {% if dashboard.dashboard_type == "tableau" %} - {% elif dashboard.type == "powerbi" %} + {% elif dashboard.dashboard_type == "powerbi" %}