Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions ckanext/activityinfo/actions/activity_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,29 @@ def act_info_get_forms(context, data_dict):
'database': data['database']
}
return ret


def act_info_get_form(context, data_dict):
'''
Action function to get a specific ActivityInfo form.
'''
toolkit.check_access('act_info_get_form', context, data_dict)
user = context.get('user')
database_id = data_dict.get('database_id')
form_id = data_dict.get('form_id')
if not database_id:
raise toolkit.ValidationError({'database_id': 'Missing value'})
if not form_id:
raise toolkit.ValidationError({'form_id': 'Missing value'})

log.debug(f"Getting ActivityInfo form {form_id} for database {database_id} and user {user}")
token = get_user_token(user)
aic = ActivityInfoClient(api_key=token)
try:
form = aic.get_form(database_id, form_id)
except HTTPError as e:
error = f"Error retrieving form {form_id} for database {database_id} and user {user}: {e}"
log.error(error)
raise ActivityInfoConnectionError(error)

return form
3 changes: 3 additions & 0 deletions ckanext/activityinfo/assets/css/activityinfo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.btn {
margin: 7px;
}
Empty file.
5 changes: 0 additions & 5 deletions ckanext/activityinfo/assets/style.css
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
/*
body {
border-radius: 0;
}
*/
26 changes: 13 additions & 13 deletions ckanext/activityinfo/assets/webassets.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# activityinfo-js:
# filter: rjsmin
# output: ckanext-activityinfo/%(version)s-activityinfo.js
# contents:
# - js/activityinfo.js
# extra:
# preload:
# - base/main
activityinfo-js:
filter: rjsmin
output: ckanext-activityinfo/%(version)s-activityinfo.js
contents:
- js/activityinfo.js
extra:
preload:
- base/main

# activityinfo-css:
# filter: cssrewrite
# output: ckanext-activityinfo/%(version)s-activityinfo.css
# contents:
# - css/activityinfo.css
activityinfo-css:
filter: cssrewrite
output: ckanext-activityinfo/%(version)s-activityinfo.css
contents:
- css/activityinfo.css
6 changes: 6 additions & 0 deletions ckanext/activityinfo/auth/activity_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ def act_info_get_databases(context, data_dict):
@require_activity_info_token_decorator
def act_info_get_forms(context, data_dict):
return {'success': True}


@toolkit.auth_disallow_anonymous_access
@require_activity_info_token_decorator
def act_info_get_form(context, data_dict):
return {'success': True}
44 changes: 43 additions & 1 deletion ckanext/activityinfo/blueprints/activity_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask import Blueprint
from ckan.common import current_user
from ckan.plugins import toolkit
from ckanext.activityinfo.data.base import ActivityInfoClient
from ckanext.activityinfo.exceptions import ActivityInfoConnectionError
from ckanext.activityinfo.utils import get_activity_info_user_plugin_extras, get_user_token

Expand Down Expand Up @@ -32,13 +33,18 @@ def databases():
return toolkit.redirect_to('activity_info.index')

log.info(f"Retrieved {ai_databases}")
# add the ActivityInfo URL to each database
aic = ActivityInfoClient()
for db in ai_databases:
db['url'] = aic.get_url_to_database(db['databaseId'])

extra_vars = {
'databases': ai_databases,
}
return toolkit.render('activity_info/databases.html', extra_vars)


@activityinfo_bp.route('/databases/<database_id>/forms')
@activityinfo_bp.route('/database/<database_id>/forms')
def forms(database_id):
try:
data = toolkit.get_action('act_info_get_forms')(
Expand All @@ -52,6 +58,12 @@ def forms(database_id):
return toolkit.redirect_to('activity_info.databases')

log.info(f"Retrieved {data}")

# Add urls to each form
aic = ActivityInfoClient()
for form in data['forms']:
form['url'] = aic.get_url_to_form(form['id'])

extra_vars = {
'forms': data['forms'],
'database_id': database_id,
Expand All @@ -60,6 +72,36 @@ def forms(database_id):
return toolkit.render('activity_info/forms.html', extra_vars)


@activityinfo_bp.route('/database/<database_id>/form/<form_id>')
def form(database_id, form_id):
try:
data = toolkit.get_action('act_info_get_form')(
context={'user': toolkit.c.user},
data_dict={
'database_id': database_id,
'form_id': form_id
}
)
except (ActivityInfoConnectionError, toolkit.ValidationError) as e:
message = f"Could not retrieve ActivityInfo form details: {e}"
log.error(message)
toolkit.h.flash_error(message)
return toolkit.redirect_to('activity_info.forms', database_id=database_id)

log.info(f"Retrieved {data}")
form = data['forms'][form_id]
schema = form.get('schema', {})
fields = schema.get('elements', {})
extra_vars = {
'data': data,
'form': form,
'database_id': schema['databaseId'],
'database': {'label': 'Test DB'},
'fields': fields,
}
return toolkit.render('activity_info/form_details.html', extra_vars)


@activityinfo_bp.route('/update-api-key', methods=['POST'])
def update_api_key():
"""Create or update the current ActivityInfo API key for the logged-in user."""
Expand Down
25 changes: 24 additions & 1 deletion ckanext/activityinfo/data/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,28 @@ def get_form(self, database_id, form_id):
form_id (str): The ID of the form to fetch.
Returns:
A dictionary containing the details of the form.
See a data sample here ckanext/activityinfo/data/samples/form-tree-translated.json

We here get the data schema, the actual data must be acceced in chunks from
POST /resources/query/chunks
"""
return self.get(f"resources/form/{form_id}/tree/translated")

def get_url_to_database(self, database_id):
""" Utility function to get the URL to access a database in ActivityInfo web app.
Args:
database_id (str): The ID of the database.
Returns:
A string containing the URL to access the database.
"""
return f"{self.base_url}/app#database/{database_id}/"

def get_url_to_form(self, form_id):
""" Utility function to get the URL to access a form in ActivityInfo web app.
Args:
database_id (str): The ID of the database.
form_id (str): The ID of the form.
Returns:
A string containing the URL to access the form.
"""
return self.get(f"resources/databases/{database_id}/forms/{form_id}")
return f"{self.base_url}/app#form/{form_id}/table"
80 changes: 80 additions & 0 deletions ckanext/activityinfo/data/samples/form-tree-translated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"root": "FORM-ID",
"forms": {
"FORM-ID": {
"id": "FORM-ID",
"schema": {
"id": "FORM-ID",
"schemaVersion": 1,
"databaseId": "DATABASE-ID",
"label": "RIDL Form",
"elements": [
{
"id": "cbgn1gumhum9w5s4",
"code": null,
"label": "Name of tools",
"description": null,
"relevanceCondition": "",
"validationCondition": "",
"required": true,
"type": "FREE_TEXT",
"typeParameters": {
"barcode": false
}
},
{
"id": "cuik9dimhuma8wx5",
"code": null,
"label": "Year",
"description": null,
"relevanceCondition": "",
"validationCondition": "",
"required": false,
"type": "quantity",
"typeParameters": {
"units": "",
"aggregation": "SUM"
}
},
{
"id": "cwwus97mhumaoe96",
"code": null,
"label": "Comment",
"description": null,
"relevanceCondition": "",
"validationCondition": "",
"required": false,
"type": "FREE_TEXT",
"typeParameters": {
"barcode": false
}
}
]
},
"schemaVersion": 1,
"permissions": {
"view": true,
"viewFilter": null,
"createRecord": true,
"createFilter": null,
"editRecord": true,
"editFilter": null,
"securityCategories": [
"reviewer"
],
"deleteRecord": true,
"deleteFilter": null,
"bulkDelete": true,
"exportRecords": true,
"resolveDuplicates": true,
"manageImportConfigs": true,
"updateSchema": true,
"collectionLinks": true,
"publicForm": false,
"locks": [],
"api": true,
"fieldLevelConditions": []
}
}
}
}
2 changes: 2 additions & 0 deletions ckanext/activityinfo/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def get_actions(self):
return {
'act_info_get_databases': activity_info_actions.act_info_get_databases,
'act_info_get_forms': activity_info_actions.act_info_get_forms,
'act_info_get_form': activity_info_actions.act_info_get_form,
}

# IAuthFunctions
Expand All @@ -34,6 +35,7 @@ def get_auth_functions(self):
return {
'act_info_get_databases': activity_info_auth.act_info_get_databases,
'act_info_get_forms': activity_info_auth.act_info_get_forms,
'act_info_get_form': activity_info_auth.act_info_get_form,
}

# ITemplateHelpers
Expand Down
3 changes: 2 additions & 1 deletion ckanext/activityinfo/templates/activity_info/databases.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h1>Activity Info databases</h1>
<th class="col-md-1">Description</th>
<th class="col-md-1">Suspended</th>
<th class="col-md-1">Languages</th>
<th class="col-md-1">Actions</th>
<th class="col-md-3">Actions</th>
</tr>
</thead>
<tbody>
Expand All @@ -39,6 +39,7 @@ <h1>Activity Info databases</h1>
href="{{ h.url_for('activity_info.forms', database_id=database.databaseId) }}">
Get Forms
</a>
<a href="{{ database.url }}" target="_blank" class="btn btn-secondary">Open in ActivityInfo</a>
</td>
</tr>
{% endfor %}
Expand Down
30 changes: 30 additions & 0 deletions ckanext/activityinfo/templates/activity_info/form_details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% extends "activity_info/base.html" %}

{%- block subtitle %}Activity Info forms{% endblock -%}

{% block primary_content %}
<div class="internal-div-section">
<h1>Form <i>{{ form.label }}</i> for database <i>{{ database.label }}</i></h1>

<table id="activity-info-form-schema" class="table table-header table-hover table-bordered table-responsive">
<thead>
<tr>
<th class="col-md-3">Field id</th>
<th class="col-md-3">Label</th>
<th class="col-md-3">Description</th>
<th class="col-md-3">code</th>
</tr>
</thead>
<tbody>
{% for field in fields %}
<tr>
<td>{{ field.id | default('', true) }}</td>
<td>{{ field.label | default('', true) }}</td>
<td>{{ field.description | default('', true) }}</td>
<td>{{ field.code | default('', true) }}</td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}
13 changes: 13 additions & 0 deletions ckanext/activityinfo/templates/activity_info/forms.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h1>Activity Info forms for database <i>{{ database.label }}</i></h1>
<th class="col-md-3">Form</th>
<th class="col-md-1">Visibility</th>
<th class="col-md-1">Description</th>
<th class="col-md-3">Actions</th>
</tr>
</thead>
<tbody>
Expand All @@ -20,6 +21,18 @@ <h1>Activity Info forms for database <i>{{ database.label }}</i></h1>
<td>{{ form.label }}</td>
<td>{{ form.visibility }}</td>
<td>{{ form.description }}</td>
<td>
<!-- TODO detect created datasets with this form
<a href="#" class="btn btn-primary">See created Dataset</a>
-->
<!-- TODO detect no related dataset and allow the creation
<a href="#" class="btn btn-primary">Create a Dataset</a>
-->
<a href="{% url_for 'activity_info.form', database_id=database.databaseId, form_id=form.id %}" class="btn btn-primary">
Get form Schema
</a>
<a href="{{ form.url }}" target="_blank" class="btn btn-secondary">Open in ActivityInfo</a>
</td>
</tr>
{% endfor %}
</tbody>
Expand Down
11 changes: 11 additions & 0 deletions ckanext/activityinfo/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% ckan_extends %}

{% block styles %}
{{ super() }}
{% asset 'activityinfo/activityinfo-css' %}
{% endblock %}

{% block body_extras %}
{{ super() }}
{% asset 'activityinfo/activityinfo-js' %}
{% endblock %}
2 changes: 1 addition & 1 deletion ckanext/activityinfo/tests/test_activityinfo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_get_forms(requests_mock_fixture, client):
def test_get_form(requests_mock_fixture, client):
db_id = "db1"
form_id = "f1"
url = f"https://www.activityinfo.org/resources/databases/{db_id}/forms/{form_id}"
url = f"https://www.activityinfo.org/resources/form/{form_id}/tree/translated"
fake_response = {"id": form_id, "fields": []}
requests_mock_fixture.get(url, json=fake_response)
result = client.get_form(db_id, form_id)
Expand Down
Loading