diff --git a/compendium_v2/routes/api.py b/compendium_v2/routes/api.py
index 3707e116df9f7754e16feb9f218b3113986c8fe8..97eac240383e1478f546bece6b057aab899d8c91 100644
--- a/compendium_v2/routes/api.py
+++ b/compendium_v2/routes/api.py
@@ -18,9 +18,10 @@ import time
from flask import Blueprint, jsonify
from compendium_v2.routes import common
+from compendium_v2.routes.data_entry import routes as data_entry_routes
routes = Blueprint('compendium-v2-api', __name__)
-
+routes.register_blueprint(data_entry_routes, url_prefix='/data-entries/')
THING_LIST_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
diff --git a/compendium_v2/routes/data_entry.py b/compendium_v2/routes/data_entry.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2a03b59dd2505ce3f94fa16636d6db7ca42e036
--- /dev/null
+++ b/compendium_v2/routes/data_entry.py
@@ -0,0 +1,228 @@
+from typing import Any
+
+from flask import Blueprint, abort, jsonify, url_for
+
+from compendium_v2.db import db
+from compendium_v2.db.models import (DataEntryItem, DataEntrySection,
+ DataSourceType)
+from compendium_v2.db.survey import get_budget_by_year
+from compendium_v2.routes import common
+
+routes = Blueprint('data-entry', __name__)
+
+col_pal = ['#fd7f6f', '#7eb0d5', '#b2e061',
+ '#bd7ebe', '#ffb55a', '#ffee65',
+ '#beb9db', '#fdcce5', '#8bd3c7']
+
+DATA_ENTRY_SECTIONS_LIST_SCHEMA = {
+ '$schema': 'http://json-schema.org/draft-07/schema#',
+
+ 'definitions': {
+ 'section': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'number'},
+ 'name': {'type': 'string'},
+ 'description': {'type': 'string'},
+ 'url': {'type': 'string'}
+ },
+ 'required': ['id', 'name', 'description', 'url'],
+ 'additionalProperties': False
+ }
+ },
+
+ 'type': 'array',
+ 'items': {'$ref': '#/definitions/section'}
+}
+
+DATA_ENTRY_SECTIONS_DETAIL_SCHEMA = {
+ '$schema': 'http://json-schema.org/draft-07/schema#',
+
+ 'definitions': {
+ 'item': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'number'},
+ 'title': {'type': 'string'},
+ 'url': {'type': 'string'}
+ },
+ 'required': ['id', 'title', 'url'],
+ 'additionalProperties': False
+ }
+ },
+
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'description': {'type': 'string'},
+ 'items': {
+ 'type': 'array',
+ 'items': {'$ref': '#/definitions/item'}
+ }
+ },
+ 'required': ['name', 'description', 'items'],
+ 'additionalProperties': False
+}
+
+DATA_ENTRY_ITEM_DETAIL_SCHEMA = {
+ '$schema': 'http://json-schema.org/draft-07/schema#',
+
+ 'definitions': {
+ 'settings': {
+ 'type': 'object',
+ 'properties': {
+
+ },
+ 'additionalProperties': False
+ },
+ 'dataset': {
+ 'type': 'object',
+ 'properties': {
+ 'data': {
+ 'type': 'array',
+ 'items': {'type': ['number', 'null']}
+ },
+ 'backgroundColor': {
+ 'type': 'string'
+ },
+ 'label': {
+ 'type': 'string'
+ }
+ }
+ },
+ 'data': {
+ 'type': 'object',
+ 'properties': {
+ 'labels': {
+ 'type': 'array',
+ 'items': {'type': 'string'}},
+ 'datasets': {
+ 'type': 'array',
+ 'items': {
+ '$ref': '#/definitions/dataset'
+ }
+ }
+ },
+ 'required': ['labels', 'datasets'],
+ 'additionalProperties': False
+ }
+ },
+
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'number'},
+ 'title': {'type': 'string'},
+ 'description': {'type': 'string'},
+ 'settings': {
+ 'type': 'object',
+ '$ref': '#/definitions/settings'
+ },
+ 'data': {
+ 'type': 'object',
+ '$ref': '#/definitions/data'
+ }
+ },
+ 'required': ['id', 'title', 'description', 'settings', 'data'],
+ 'additionalProperties': False
+}
+
+
+def load_data(data_source_id: DataSourceType) -> Any:
+ response_data = {}
+ if data_source_id == DataSourceType.BUDGETS_BY_YEAR:
+ response_data = get_budget_by_year()
+
+ # Enrich response data
+ # Add the colour formatting
+ for index, dataset in enumerate(response_data['datasets']):
+ dataset['backgroundColor'] = col_pal[index % len(col_pal)]
+
+ return response_data
+
+
+@routes.route('/item/<int:item_id>', methods=['GET'])
+@common.require_accepts_json
+def item_view(item_id):
+ """
+ handler for /api/data-entries/item/<item_id> requests
+
+ response will be formatted as:
+
+ .. asjson::
+ compendium_v2.routes.data_entry.DATA_ENTRY_ITEM_DETAIL_SCHEMA
+
+ :return:
+ """
+ de_item = db.get_or_404(DataEntryItem, item_id)
+ # Confirm that only active sections can be loaded
+ if not de_item.is_active:
+ return abort(404)
+
+ return jsonify({
+ 'id': de_item.id,
+ 'title': de_item.title,
+ 'description': de_item.description,
+ 'settings': {},
+ 'data': load_data(de_item.data_source)
+ })
+
+
+@routes.route('/sections/<int:section_id>', methods=['GET'])
+@common.require_accepts_json
+def section_view(section_id):
+ """
+ handler for /api/data-entries/sections/<section_id> requests
+
+ response will be formatted as:
+
+ .. asjson::
+ compendium_v2.routes.data_entry.DATA_ENTRY_SECTIONS_DETAIL_SCHEMA
+
+ :return:
+ """
+ de_section = db.get_or_404(DataEntrySection, section_id)
+ # Confirm that only active sections can be loaded
+ if not de_section.is_active:
+ return abort(404)
+
+ items = [
+ {
+ 'id': item.id,
+ 'url': url_for('.item_view', item_id=item.id),
+ 'title': item.title
+ } for item in de_section.items if item.is_active
+ ]
+ response_section = {
+ 'name': de_section.name,
+ 'description': de_section.description,
+ 'items': items
+ }
+ return jsonify(response_section)
+
+
+@routes.route('/sections', methods=['GET'])
+@common.require_accepts_json
+def sections_view():
+ """
+ handler for /api/data-entries/sections requests
+
+ response will be formatted as:
+
+ .. asjson::
+ compendium_v2.routes.data_entry.DATA_ENTRY_SECTIONS_LIST_SCHEMA
+
+ :return:
+ """
+ model_sections = db.session.execute(
+ db.select(DataEntrySection)
+ .filter(DataEntrySection.is_active)
+ .order_by(DataEntrySection.sort_order)
+ ).scalars()
+
+ de_sections = [{'id': de_section.id,
+ 'name': de_section.name,
+ 'description': de_section.description,
+ 'url': url_for('.section_view', section_id=de_section.id)
+ } for de_section in model_sections]
+
+ return jsonify(list(de_sections))
diff --git a/config-example.json b/config-example.json
index 03c0451aa13163afe7ee0cbea79c32db9106b1c7..5a9ddebf6d02d12a1f3d9e7036d22ea85fc98ba3 100644
--- a/config-example.json
+++ b/config-example.json
@@ -1,3 +1,3 @@
{
- "SQLALCHEMY_DATABASE_URI": "psql://username:password@hostname/db_name"
+ "SQLALCHEMY_DATABASE_URI": "postgresql://compendium_v2:password@localhost/compendium_v2"
}
diff --git a/test/test_routes.py b/test/test_routes.py
index 2e42f3e438e6eddb5215e25af7e343d605bb9b4e..5d2d12d62d5ce4f87f07cdad3cbb1e158d4cb96a 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -4,6 +4,9 @@ import jsonschema
import pytest
from compendium_v2.routes.api import THING_LIST_SCHEMA
+from compendium_v2.routes.data_entry import (DATA_ENTRY_ITEM_DETAIL_SCHEMA,
+ DATA_ENTRY_SECTIONS_DETAIL_SCHEMA,
+ DATA_ENTRY_SECTIONS_LIST_SCHEMA)
from compendium_v2.routes.default import VERSION_SCHEMA
@@ -18,7 +21,6 @@ def test_bad_accept(endpoint, client):
def test_version_request(client):
-
rv = client.post(
'version',
headers={'Accept': ['application/json']})
@@ -28,10 +30,36 @@ def test_version_request(client):
def test_things(client):
-
rv = client.post(
'api/things',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, THING_LIST_SCHEMA)
+
+
+def test_api_data_entry_sections(client):
+ rv = client.get(
+ 'api/data-entries/sections',
+ headers={'Accept': ['application/json']})
+ assert rv.status_code == 200
+ result = json.loads(rv.data.decode('utf-8'))
+ jsonschema.validate(result, DATA_ENTRY_SECTIONS_LIST_SCHEMA)
+
+
+def test_api_data_entry_sections_detail(client):
+ rv = client.get(
+ 'api/data-entries/sections/1',
+ headers={'Accept': ['application/json']})
+ assert rv.status_code == 200
+ result = json.loads(rv.data.decode('utf-8'))
+ jsonschema.validate(result, DATA_ENTRY_SECTIONS_DETAIL_SCHEMA)
+
+
+def test_api_data_entry_item_detail(client):
+ rv = client.get(
+ 'api/data-entries/item/1',
+ headers={'Accept': ['application/json']})
+ assert rv.status_code == 200
+ result = json.loads(rv.data.decode('utf-8'))
+ jsonschema.validate(result, DATA_ENTRY_ITEM_DETAIL_SCHEMA)