From d9b6761e4cfa9b928c671912bb986b6ebdb346ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20De=CC=81le=CC=80ze?= Date: Thu, 28 May 2020 08:02:35 +0200 Subject: [PATCH] organisations: access records with specific URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds a property `isShared` in organisation to allow access to records by a specific URL. * Adds a property `isDedicated` in organisation to allow organisation to have a specific theme and logo. * Adds a specific route to document details instead of built-in invenio view. * Adds a method to get current view code in templates. * Renames `ir` query parameter to `view` for consistency. * Stores current organisation record in Flask globals. * Adds a header containing current organisation information in templates. * Checks if organisation is flagged as dedicated for including custom logo and styles. * Closes #216. Co-Authored-by: Sébastien Délèze --- sonar/config.py | 5 - .../documents/dojson/rerodoc/overdo.py | 4 +- .../documents/templates/documents/record.html | 2 +- sonar/modules/documents/views.py | 64 ++++--- sonar/modules/ext.py | 5 +- .../organisations/organisation-v1.0.0.json | 19 +- .../v6/organisations/organisation-v1.0.0.json | 7 +- .../modules/organisations/marshmallow/json.py | 2 + sonar/modules/utils.py | 13 +- sonar/theme/assets/scss/common/_theme.scss | 6 +- .../theme/assets/scss/common/_variables.scss | 2 +- sonar/theme/templates/sonar/frontpage.html | 11 +- sonar/theme/templates/sonar/page.html | 6 +- .../theme/templates/sonar/partial/navbar.html | 170 ++++++++++-------- sonar/translations/de/LC_MESSAGES/messages.po | 14 +- sonar/translations/en/LC_MESSAGES/messages.po | 14 +- sonar/translations/fr/LC_MESSAGES/messages.po | 14 +- sonar/translations/it/LC_MESSAGES/messages.po | 14 +- sonar/translations/messages.pot | 14 +- tests/conftest.py | 7 +- tests/ui/documents/test_documents_views.py | 70 +++++--- tests/ui/test_utils.py | 13 +- 22 files changed, 325 insertions(+), 151 deletions(-) diff --git a/sonar/config.py b/sonar/config.py index daef85af..2d056797 100644 --- a/sonar/config.py +++ b/sonar/config.py @@ -243,11 +243,6 @@ def _(x): SECURITY_REGISTER_USER_TEMPLATE = 'sonar/accounts/signup.html' RECORDS_UI_ENDPOINTS = { - 'document': { - 'pid_type': 'doc', - 'route': '/organisation//documents/', - 'view_imp': 'sonar.modules.documents.views:detail' - }, 'doc_previewer': { 'pid_type': 'doc', 'route': '/documents//preview/', diff --git a/sonar/modules/documents/dojson/rerodoc/overdo.py b/sonar/modules/documents/dojson/rerodoc/overdo.py index 6c3649cc..2e91fcde 100644 --- a/sonar/modules/documents/dojson/rerodoc/overdo.py +++ b/sonar/modules/documents/dojson/rerodoc/overdo.py @@ -47,7 +47,9 @@ def create_organisation(organisation_key): organisation = OrganisationRecord.create( { 'code': organisation_key, - 'name': organisation_key + 'name': organisation_key, + 'isShared': False, + 'isDedicated': False }, dbcommit=True) organisation.reindex() diff --git a/sonar/modules/documents/templates/documents/record.html b/sonar/modules/documents/templates/documents/record.html index 0895d90a..3e589b7d 100644 --- a/sonar/modules/documents/templates/documents/record.html +++ b/sonar/modules/documents/templates/documents/record.html @@ -226,7 +226,7 @@
{{ _(record.organisation.name) }}
{% endif %} - {% set link = url_for('invenio_records_ui.document', pid_value=record.pid, ir=g.ir|default('sonar'), _external=True) %} + {% set link = url_for('documents.detail', pid_value=record.pid, view=view_code, _external=True) %} {{ dl(_('Permalink'), '' + link + '') }} diff --git a/sonar/modules/documents/views.py b/sonar/modules/documents/views.py index 4411626e..6497c8ad 100644 --- a/sonar/modules/documents/views.py +++ b/sonar/modules/documents/views.py @@ -22,9 +22,11 @@ import re from datetime import datetime -from flask import Blueprint, current_app, g, render_template, request +from flask import Blueprint, abort, current_app, g, render_template, request from flask_babelex import gettext as _ +from sonar.modules.documents.api import DocumentRecord +from sonar.modules.organisations.api import OrganisationRecord from sonar.modules.utils import change_filename_extension from .utils import publication_statement_text, series_format_text @@ -33,7 +35,7 @@ __name__, template_folder='templates', static_folder='static', - url_prefix='/organisation/') + url_prefix='/organisation/') """Blueprint used for loading templates and static assets The sole purpose of this blueprint is to ensure that Invenio can find the @@ -43,33 +45,50 @@ @blueprint.url_defaults -def add_ir(endpoint, values): - """Add default ir parameter.""" - values.setdefault('ir', 'sonar') +def default_view_code(endpoint, values): + """Add default view code.""" + values.setdefault('view', + current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION')) @blueprint.url_value_preprocessor -def pull_ir(endpoint, values): - """Add ir parameter to global variables.""" - g.ir = values.pop('ir') +def store_organisation(endpoint, values): + """Add organisation record to global variables.""" + view = values.pop('view', + current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION')) + + if view != current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION'): + organisation = OrganisationRecord.get_record_by_pid(view) + + if not organisation or not organisation['isShared']: + raise Exception('Organisation\'s view is not accessible') + + g.organisation = organisation.dumps() @blueprint.route('/') def index(): - """IR (and SONAR) home view.""" + """Homepage.""" return render_template('sonar/frontpage.html') -@blueprint.route('/search/') -def search(resource_type=None): - """IR search results.""" +@blueprint.route('/search/documents') +def search(): + """Search results page.""" return render_template('sonar/search.html') -def detail(pid, record, template=None, **kwargs): - """Search details.""" - g.ir = kwargs.get('ir') - return render_template('documents/record.html', pid=pid, record=record) +@blueprint.route('/documents/') +def detail(pid_value): + """Document detail page.""" + record = DocumentRecord.get_record_by_pid(pid_value) + + if not record: + abort(404) + + return render_template('documents/record.html', + pid=pid_value, + record=record) @blueprint.app_template_filter() @@ -301,7 +320,7 @@ def is_restricted_by_scope(file): 'SONAR_APP_INTERNAL_IPS') # File is restricted by organisation - organisation = get_current_organisation() + organisation = get_current_organisation_code() # We are in global organisation, so restriction is active if organisation == current_app.config.get( @@ -389,13 +408,16 @@ def get_preferred_languages(force_language=None): return list(dict.fromkeys(preferred_languages)) -def get_current_organisation(): +def get_current_organisation_code(): """Return current organisation by globals or query parameter.""" + # Organisation is present in query parameters, useful for API calls. organisation = request.args.get('view') if organisation: return organisation - if g.get('ir'): - return g.ir + # Organisation stored in globals + if g.get('organisation', {}).get('code'): + return g.organisation['code'] - return None + # Default organisation + return current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION') diff --git a/sonar/modules/ext.py b/sonar/modules/ext.py index f7c8f60e..e3338cdf 100644 --- a/sonar/modules/ext.py +++ b/sonar/modules/ext.py @@ -26,7 +26,7 @@ from sonar.modules.permissions import has_admin_access, has_publisher_access, \ has_superuser_access -from sonar.modules.utils import get_switch_aai_providers +from sonar.modules.utils import get_switch_aai_providers, get_view_code from . import config @@ -38,7 +38,8 @@ def utility_processor(): has_admin_access=has_admin_access, has_superuser_access=has_superuser_access, ui_version=config.SONAR_APP_UI_VERSION, - aai_providers=get_switch_aai_providers) + aai_providers=get_switch_aai_providers, + view_code=get_view_code()) class Sonar(object): diff --git a/sonar/modules/organisations/jsonschemas/organisations/organisation-v1.0.0.json b/sonar/modules/organisations/jsonschemas/organisations/organisation-v1.0.0.json index 48715a39..d3361783 100644 --- a/sonar/modules/organisations/jsonschemas/organisations/organisation-v1.0.0.json +++ b/sonar/modules/organisations/jsonschemas/organisations/organisation-v1.0.0.json @@ -33,11 +33,28 @@ "title": "Name", "type": "string", "minLength": 1 + }, + "isShared": { + "title": "Is shared", + "description": "Organisation records can be accessed by a specific URL.", + "type": "boolean", + "default": false + }, + "isDedicated": { + "title": "Is dedicated", + "description": "Organisation has a specific theme for his view.", + "type": "boolean", + "default": false, + "form": { + "hideExpression": "!field.model.isShared" + } } }, "propertiesOrder": [ "code", - "name" + "name", + "isShared", + "isDedicated" ], "required": [ "pid", diff --git a/sonar/modules/organisations/mappings/v6/organisations/organisation-v1.0.0.json b/sonar/modules/organisations/mappings/v6/organisations/organisation-v1.0.0.json index 361735b8..4274ac7e 100644 --- a/sonar/modules/organisations/mappings/v6/organisations/organisation-v1.0.0.json +++ b/sonar/modules/organisations/mappings/v6/organisations/organisation-v1.0.0.json @@ -14,8 +14,11 @@ "code": { "type": "keyword" }, - "name": { - "type": "text" + "isShared": { + "type": "boolean" + }, + "isDedicated": { + "type": "boolean" }, "_created": { "type": "date" diff --git a/sonar/modules/organisations/marshmallow/json.py b/sonar/modules/organisations/marshmallow/json.py index 75c4240f..48a1fc9f 100644 --- a/sonar/modules/organisations/marshmallow/json.py +++ b/sonar/modules/organisations/marshmallow/json.py @@ -41,6 +41,8 @@ class OrganisationMetadataSchemaV1(StrictKeysMixin): pid = PersistentIdentifier() code = SanitizedUnicode(required=True) name = SanitizedUnicode(required=True) + isShared = fields.Boolean() + isDedicated = fields.Boolean() # When loading, if $schema is not provided, it's retrieved by # Record.schema property. schema = GenFunction(load_only=True, diff --git a/sonar/modules/utils.py b/sonar/modules/utils.py index f5dd51b1..decd043d 100644 --- a/sonar/modules/utils.py +++ b/sonar/modules/utils.py @@ -19,7 +19,7 @@ import re -from flask import current_app +from flask import current_app, g from invenio_i18n.ext import current_i18n from invenio_mail.api import TemplatedMessage from wand.color import Color @@ -118,3 +118,14 @@ def remove_trailing_punctuation(data, def get_current_language(): """Return the current selected locale.""" return current_i18n.locale.language + + +def get_view_code(): + """Return view code corresponding to organisation. + + :returns: View code as string. + """ + if g.get('organisation'): + return g.organisation['code'] + + return current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION') diff --git a/sonar/theme/assets/scss/common/_theme.scss b/sonar/theme/assets/scss/common/_theme.scss index 9ddcb48e..c1165fe1 100644 --- a/sonar/theme/assets/scss/common/_theme.scss +++ b/sonar/theme/assets/scss/common/_theme.scss @@ -24,8 +24,8 @@ background-color: #15334D; } -.bg-ir { - background-color: $bg-ir; +.bg-organisation { + background-color: $bg-organisation; } img.logo { @@ -33,7 +33,7 @@ img.logo { max-height: 90px; } -.text-ir { +.text-organisation { @extend .text-light } diff --git a/sonar/theme/assets/scss/common/_variables.scss b/sonar/theme/assets/scss/common/_variables.scss index 5aa717e9..6afcf3b5 100644 --- a/sonar/theme/assets/scss/common/_variables.scss +++ b/sonar/theme/assets/scss/common/_variables.scss @@ -20,6 +20,6 @@ $fa-font-path: "~font-awesome/fonts"; $primary: #205078 !default; $secondary: rgb(246, 130, 17) !default; -$bg-ir: $primary !default; +$bg-organisation: $primary !default; $font-family-base: 'Roboto', sans-serif; diff --git a/sonar/theme/templates/sonar/frontpage.html b/sonar/theme/templates/sonar/frontpage.html index 34c00ad3..279f7b11 100755 --- a/sonar/theme/templates/sonar/frontpage.html +++ b/sonar/theme/templates/sonar/frontpage.html @@ -20,13 +20,14 @@ {%- extends config.BASE_TEMPLATE %} {% block header %} -