Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Algunas configuraciones #1

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9499ec2
def index, delete , edit
germankay Feb 14, 2025
f1da136
decorators sysadmin
germankay Feb 14, 2025
db7f755
model DatasetDashboard
germankay Feb 14, 2025
c78e3f5
plugin DashboardPlugin
germankay Feb 14, 2025
5335363
actins dataset_dashboard_list
germankay Feb 14, 2025
1b0d0c9
auth
germankay Feb 14, 2025
c2d65fe
html
germankay Feb 14, 2025
56619f8
models y html
germankay Feb 14, 2025
710ab40
fix
germankay Feb 17, 2025
ae1ec2b
cambios
germankay Feb 18, 2025
782cafb
fixes
germankay Feb 18, 2025
8a463cf
dashboard_id
germankay Feb 18, 2025
efea410
log error
germankay Feb 18, 2025
09512bb
fixes
germankay Feb 18, 2025
8aea1ff
options: --user root
germankay Feb 20, 2025
0d4b573
fixes
germankay Feb 20, 2025
a047fca
snippet
germankay Feb 20, 2025
821a5b5
css
germankay Feb 20, 2025
be92a93
css ok
germankay Feb 20, 2025
da5b5bc
css ok
germankay Feb 20, 2025
65b070e
dashboard.js
germankay Feb 20, 2025
47ec590
avances
germankay Feb 21, 2025
8d37d48
flash_error & flash_success
germankay Feb 24, 2025
76d5b11
migrations
germankay Feb 24, 2025
04de62f
html detail
germankay Feb 24, 2025
819d5b0
html
germankay Feb 24, 2025
8b10929
Casi andando migraciones
germankay Feb 25, 2025
92dad90
Andando una primera version
germankay Feb 25, 2025
de2ca2e
fixes
germankay Feb 25, 2025
f108939
change dashbord_bp a embeded_dashboard
germankay Feb 26, 2025
7fcb581
Create helpers module
pdelboca Feb 28, 2025
fe1750f
Clean not necessary flash message
pdelboca Feb 28, 2025
bfc39aa
Move dashboard form to dataset UI
pdelboca Feb 28, 2025
1d8f73d
Remove print statement
pdelboca Feb 28, 2025
4382ac3
Create Dashboard with hardcoded type
pdelboca Feb 28, 2025
5956375
Reuse blueprint for create/update
pdelboca Feb 28, 2025
235b085
Rename new to create (CKAN uses create verb)
pdelboca Feb 28, 2025
fa2f8fe
Add TODO comment
pdelboca Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Tests
name: Tests 2.11
on: [push, pull_request]
jobs:
test:
Expand All @@ -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
Expand Down
Empty file.
170 changes: 170 additions & 0 deletions ckanext/dashboard/actions/dashboard_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import logging
import uuid
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):
"""
Returns a list of dashboard configurations stored in the database.

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)

session = model.Session
dashboards = session.query(DatasetDashboard).all()
result = []
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,
})
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")

pkg_id = toolkit.get_or_bust(data_dict, 'pkg_id')

session = model.Session
dashboard = session.query(DatasetDashboard).filter_by(package_id=pkg_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,
'embeded_url': dashboard.embeded_url,
'report_url': dashboard.report_url
}


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
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()

return {
'id': new_dashboard.id,
'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,
}


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 package ID.
:return: Dictionary with the updated dashboard details.
"""
log.info("Executing dataset_dashboard_update")

package_id = toolkit.get_or_bust(data_dict, 'package_id')

session = model.Session
dashboard = session.query(DatasetDashboard).filter_by(package_id=package_id).first()

if not dashboard:
raise toolkit.ObjectNotFound("Dashboard not found.")

if 'title' in 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:
dashboard.report_url = data_dict['report_url']

session.add(dashboard)
session.commit()

return {
'id': dashboard.id,
'package_id': dashboard.package_id,
'title': dashboard.title,
'description': dashboard.description,
'dashboard_type': dashboard.dashboard_type,
'embeded_url': dashboard.embeded_url,
'report_url': dashboard.report_url
}


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 = toolkit.get_or_bust(data_dict, 'id')

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.'}
32 changes: 32 additions & 0 deletions ckanext/dashboard/assets/css/dashboard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.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;
}
3 changes: 3 additions & 0 deletions ckanext/dashboard/assets/js/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$(document).ready(function() {

});
10 changes: 5 additions & 5 deletions ckanext/dashboard/assets/webassets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Empty file.
27 changes: 27 additions & 0 deletions ckanext/dashboard/auth/dashboard_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
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"""
return {"success": False}


def dashboard_dataset_update(context, data_dict):
"""Only sysadmins are allowed"""
return {"success": False}


def dashboard_dataset_delete(context, data_dict):
"""Only sysadmins are allowed"""
return {"success": False}


def dashboard_dataset_show(context, data_dict):
return {"success": True}
Empty file.
84 changes: 84 additions & 0 deletions ckanext/dashboard/blueprints/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import logging
from ckan import model
import ckan.plugins as p
from flask import Blueprint, request, redirect, url_for
from ckan.logic import NotFound
from ckan.plugins import toolkit
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.
from ckanext.dashboard.decorators import require_sysadmin_user

log = logging.getLogger(__name__)

dashboard_bp = Blueprint('embeded_dashboard', __name__)


@dashboard_bp.route('/dataset/dashboard/<package_id>', methods=['GET', 'POST'], endpoint='create')
@require_sysadmin_user
def dashboard_create(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})

try:
dashboard_dict = toolkit.get_action('dataset_dashboard_show')({}, {'pkg_id': package_id})
except toolkit.ObjectNotFound:
dashboard_dict = {}

if request.method == 'POST':
data = {
'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')
}
context = {'model': model, 'user': p.toolkit.c.user}
try:
if not dashboard_dict:
p.toolkit.get_action('dataset_dashboard_create')(context, data)
h.flash_success('Dashboard created successfully', 'success')
log.info("Dashboard created")
else:
toolkit.get_action('dataset_dashboard_update')(context, data)
h.flash_success('Dashboard updated successfully', 'success')
log.info("Dashboard updated")
except Exception as e:
h.flash_error(f'Error: {e}', 'error')
log.error("Error creating dashboard: %s", e)
return redirect(url_for('dataset.read', id=pkg_dict['id']))
return toolkit.render('dashboard/new.html', {"pkg_dict": pkg_dict, "dashboard": dashboard_dict})


@dashboard_bp.route('/delete/<dashboard_id>', methods=['POST'], endpoint='dashboard_delete')
@require_sysadmin_user
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, {'id': dashboard_id})
h.flash_success('Dashboard configuration deleted', 'success')
log.info("Dashboard deleted for dashboard_id: %s", 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('embeded_dashboard.dashboard_list'))


@dashboard_bp.route('/show/<dashboard_id>', 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, {'id': dashboard_id})
except NotFound:
dashboard = None
return render('dashboard/show.html', extra_vars={'dashboard': dashboard, 'dashboard_id': dashboard_id})
25 changes: 25 additions & 0 deletions ckanext/dashboard/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
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")
}

log.info(f'Configuration values: dashboard: {cfg["dashboard_url"]}, Proxy: {cfg.get("proxy_url")}')

return cfg
18 changes: 18 additions & 0 deletions ckanext/dashboard/decorators.py
Original file line number Diff line number Diff line change
@@ -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
Loading