From 9a6b6fab2e5ddb67d52468db8bce08ca5f1c21f0 Mon Sep 17 00:00:00 2001 From: Catalin Date: Wed, 14 Aug 2024 11:13:04 +0300 Subject: [PATCH 1/5] HDX-9960 implement the new contact the contributor page --- .../ckanext/hdx_package/actions/authorize.py | 10 ++ .../dataset_contact_contributor.py | 47 ++++++ .../ckanext/hdx_package/plugin.py | 1 + .../ckanext/hdx_package/views/contact.py | 57 ------- .../ckanext/hdx_package/views/dataset.py | 138 +++++++++++++++++ .../fanstatic/datasets/contact-contributor.js | 24 +++ .../ckanext/hdx_theme/fanstatic/webassets.yml | 15 +- .../widget/membership/contact-contributor.css | 35 ----- .../widget/membership/contact-contributor.js | 42 ----- .../fanstatic/widget/onboarding/onboarding.js | 17 --- .../helpers/ui_constants/__init__.py | 2 + .../contact_contributor/__init__.py | 32 ++++ ...tributor_request_confirmation_to_user.html | 2 +- .../package/contact_contributor.html | 143 ++++++++++++++++++ .../package/snippets/base_actions_macros.html | 5 - .../package/snippets/base_actions_menu.html | 9 +- .../templates/qa_dashboard/qa_dashboard.html | 4 - .../qa_dashboard/qa_package_item.html | 5 +- .../membership/contact-contributor.html | 73 --------- 19 files changed, 403 insertions(+), 258 deletions(-) create mode 100644 ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js delete mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.css delete mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.js create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html delete mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_macros.html delete mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/widget/membership/contact-contributor.html diff --git a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py index 7c12351ad5..d36f0bf56b 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py +++ b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py @@ -1,4 +1,5 @@ import logging +import ckan.authz as new_authz import ckan.logic.auth.create as create import ckan.logic.auth.update as update import ckan.plugins.toolkit as tk @@ -163,3 +164,12 @@ def hdx_send_mail_request_tags(context, data_dict): def hdx_mark_resource_in_hapi(context: Context, data_dict: DataDict): return _check_hdx_user_permission(context, Permissions.PERMISSION_MANAGE_IN_HAPI_FLAG) + + +def hdx_contact_contributor(context: Context, data_dict: DataDict): + logged_in = not new_authz.auth_is_anon_user(context) + + if logged_in: + return {'success': True} + else: + return {'success': False, 'msg': _('You must be logged in to contact the contributor.')} diff --git a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py new file mode 100644 index 0000000000..0f6e0c39ff --- /dev/null +++ b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py @@ -0,0 +1,47 @@ +import logging + +import ckan.lib.navl.dictization_functions as dictization_functions +import ckan.logic as logic +import ckan.plugins.toolkit as tk +from ckan.types import Context, DataDict, Request, Validator +from ckan.logic.schema import validator_args +from ckan.lib.navl.dictization_functions import validate + +get_action = tk.get_action +check_access = tk.check_access +config = tk.config +h = tk.h +NotAuthorized = tk.NotAuthorized +unicode_safe = tk.get_validator('unicode_safe') +log = logging.getLogger(__name__) + + +@validator_args +def dataset_contact_contributor_schema(not_empty: Validator, email_validator: Validator): + schema = { + 'topic': [not_empty, unicode_safe], + 'fullname': [not_empty, unicode_safe], + 'email': [not_empty, email_validator, unicode_safe], + 'msg': [not_empty, unicode_safe], + } + return schema + + +class DatasetContactContributorLogic(object): + def __init__(self, context: Context, request: Request): + self.request = request + self.context = context + self.form = request.form + self.schema = dataset_contact_contributor_schema() + + def read(self) -> DataDict: + data_dict = logic.clean_dict(dictization_functions.unflatten(logic.tuplize_dict(logic.parse_params(self.form)))) + return data_dict + + def validate(self, data_dict: DataDict): + try: + validated_response = validate(data_dict, self.schema, self.context) + except Exception as ex: + log.error(ex) + + return validated_response diff --git a/ckanext-hdx_package/ckanext/hdx_package/plugin.py b/ckanext-hdx_package/ckanext/hdx_package/plugin.py index 53f1063248..f372f3d852 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/plugin.py +++ b/ckanext-hdx_package/ckanext/hdx_package/plugin.py @@ -551,6 +551,7 @@ def get_auth_functions(self): 'hdx_dataseries_update': authorize.hdx_dataseries_update, 'hdx_p_coded_resource_update': authorize.hdx_p_coded_resource_update, 'hdx_mark_resource_in_hapi': authorize.hdx_mark_resource_in_hapi, + 'hdx_contact_contributor': authorize.hdx_contact_contributor, } def make_middleware(self, app, config): diff --git a/ckanext-hdx_package/ckanext/hdx_package/views/contact.py b/ckanext-hdx_package/ckanext/hdx_package/views/contact.py index 63b4b28c53..e0ecbf1746 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/views/contact.py +++ b/ckanext-hdx_package/ckanext/hdx_package/views/contact.py @@ -2,14 +2,12 @@ import logging from flask import Blueprint, make_response -from six import text_type import ckan.lib.captcha as captcha import ckan.model as model import ckan.plugins.toolkit as tk from ckan.views.api import CONTENT_TYPES -from ckan.lib.mailer import MailerException import ckanext.hdx_package.helpers.membership_data as membership_data import ckanext.hdx_users.helpers.helpers as usr_h @@ -43,60 +41,6 @@ def _build_json_response(data_dict, status=200): return response -def contact_contributor(): - ''' - Send a contact request form - :return: - ''' - context = { - 'model': model, - 'session': model.Session, - 'user': g.user, - 'auth_user_obj': g.userobj - } - data_dict = {} - try: - usr_h.is_valid_captcha(request.form.get('g-recaptcha-response')) - - check_access('hdx_send_mail_contributor', context, data_dict) - # for k, v in membership_data.get('contributor_topics').iteritems(): - # if v == request.form.get('topic'): - # data_dict['topic'] = v - data_dict['topic'] = request.form.get('topic') - data_dict['fullname'] = request.form.get('fullname') - data_dict['email'] = request.form.get('email') - data_dict['msg'] = request.form.get('msg') - data_dict['pkg_owner_org'] = request.form.get('pkg_owner_org') - data_dict['pkg_title'] = request.form.get('pkg_title') - data_dict['pkg_id'] = request.form.get('pkg_id') - data_dict['pkg_url'] = h.url_for('dataset_read', id=request.form.get('pkg_id'), qualified=True) - data_dict['hdx_email'] = config.get('hdx.faqrequest.email', 'hdx@humdata.org') - - hdx_validate_email(data_dict['email']) - - except NotAuthorized: - return _build_json_response( - {'success': False, 'error': {'message': u'You have to log in before sending a contact request'}}) - except captcha.CaptchaError: - return _build_json_response( - {'success': False, 'error': {'message': _(u'Bad Captcha. Please try again.')}}) - except Exception as e: - log.error(e) - return _build_json_response({'success': False, 'error': {'message': u'There was an error. Please contact support'}}) - - try: - get_action('hdx_send_mail_contributor')(context, data_dict) - except MailerException as e: - error_summary = _('Could not send request for: %s') % text_type(e) - log.error(error_summary) - return _build_json_response({'success': False, 'error': {'message': error_summary}}) - except Exception as e: - # error_summary = e.error or str(e) - log.error(e) - return _build_json_response({'success': False, 'error': {'message': u'There was an error. Please contact support'}}) - return _build_json_response({'success': True}) - - def contact_members(): ''' Send a contact request form @@ -150,5 +94,4 @@ def contact_members(): return _build_json_response({'success': True}) -hdx_contact.add_url_rule(u'/contact_contributor', view_func=contact_contributor, methods=[u'POST']) hdx_contact.add_url_rule(u'/contact_members', view_func=contact_members, methods=[u'POST']) diff --git a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py index 0aaae5a989..c12d473db6 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py +++ b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py @@ -5,8 +5,16 @@ import re from flask import Blueprint, make_response +from flask.views import MethodView +from typing import Any, Optional, Union +from six import text_type + +from ckan.types import Response +from ckan.lib.mailer import MailerException import ckan.authz as authz +import ckan.lib.captcha as captcha +import ckan.lib.navl.dictization_functions as dictization_functions import ckan.model as model import ckan.plugins.toolkit as tk @@ -16,11 +24,13 @@ import ckanext.hdx_package.helpers.membership_data as membership_data import ckanext.hdx_search.helpers.search_history as search_history import ckanext.hdx_package.controller_logic.dataset_view_logic as dataset_view_logic +from ckanext.hdx_package.controller_logic.dataset_contact_contributor import DatasetContactContributorLogic from ckan.views.dataset import _setup_template_variables from ckanext.hdx_package.helpers import resource_grouping from ckanext.hdx_package.helpers.constants import PACKAGE_METADATA_FIELDS_MAP, RESOURCE_METADATA_FIELDS_MAP +from ckanext.hdx_package.helpers.membership_data import contributor_topics from ckanext.hdx_package.helpers.helpers import filesize_format from ckanext.hdx_package.helpers.util import find_approx_download from ckanext.hdx_package.views.light_dataset import generic_search @@ -28,6 +38,8 @@ from ckanext.hdx_theme.util.jql import fetch_downloads_per_week_for_dataset from ckanext.hdx_theme.util.light_redirect import check_redirect_needed +import ckanext.hdx_users.helpers.helpers as usr_h + log = logging.getLogger(__name__) config = tk.config @@ -43,6 +55,7 @@ NotAuthorized = tk.NotAuthorized NotFound = tk.ObjectNotFound +ValidationError = tk.ValidationError hdx_dataset = Blueprint(u'hdx_dataset', __name__, url_prefix=u'/dataset') hdx_search = Blueprint(u'hdx_search', __name__, url_prefix=u'/search') @@ -535,9 +548,134 @@ def _process_resource_metadata(metadata_dict: dict, fields: dict, file_format: s return metadata_dict +class DatasetContactContributorView(MethodView): + + def post(self, id: str) -> Union[Response, str]: + context = { + u'model': model, + u'session': model.Session, + u'user': g.user or g.author, + u'auth_user_obj': g.userobj, + } + + try: + pkg_dict = get_action('package_show')(context, {'id': id}) + + check_access(u'hdx_contact_contributor', context) + + dataset_contact_contributor_logic = DatasetContactContributorLogic(context, request) + + data_dict = None + try: + data_dict = dataset_contact_contributor_logic.read() + except dictization_functions.DataError: + abort(400, _(u'Integrity Error')) + + data, errors = dataset_contact_contributor_logic.validate(data_dict) + if errors: + return self.get(id, data, errors) + + usr_h.is_valid_captcha(request.form.get('g-recaptcha-response')) + + check_access('hdx_send_mail_contributor', context, data_dict) + + data_dict['topic'] = request.form.get('topic') + data_dict['fullname'] = request.form.get('fullname') + data_dict['email'] = request.form.get('email') + data_dict['msg'] = request.form.get('msg') + data_dict['pkg_owner_org'] = request.form.get('pkg_owner_org') + data_dict['pkg_title'] = request.form.get('pkg_title') + data_dict['pkg_id'] = request.form.get('pkg_id') + data_dict['pkg_url'] = h.url_for('dataset_read', id=request.form.get('pkg_id'), qualified=True) + data_dict['hdx_email'] = config.get('hdx.faqrequest.email', 'hdx@humdata.org') + + get_action('hdx_send_mail_contributor')(context, data_dict) + + analytics_is_cod = analytics.is_cod(pkg_dict) + analytics_is_indicator = analytics.is_indicator(pkg_dict) + analytics_is_archived = analytics.is_archived(pkg_dict) + analytics_group_names, analytics_group_ids = analytics.extract_locations_in_json(pkg_dict) + analytics_dataset_availability = analytics.dataset_availability(pkg_dict) + + extra_vars = { + u'pkg_dict': pkg_dict, + u'analytics_is_cod': analytics_is_cod, + u'analytics_is_indicator': 'false', + u'analytics_is_archived': analytics_is_archived, + u'analytics_group_names': analytics_group_names, + u'analytics_group_ids': analytics_group_ids, + u'analytics_dataset_availability': analytics_dataset_availability, + u'message_subject': request.form.get('topic'), + u'message_sent': True, + } + return render('package/contact_contributor.html', extra_vars=extra_vars) + + except NotFound: + return abort(404, _('Dataset not found')) + + except NotAuthorized: + came_from = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name')) + return redirect(h.url_for('hdx_signin.login', info_message_type='contact-contributor', came_from=came_from)) + + except captcha.CaptchaError: + error_summary = _(u'Bad Captcha. Please try again.') + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except MailerException as e: + error_summary = _('Could not send request for: %s') % text_type(e) + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except ValidationError as e: + error_summary = e.error_summary + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except Exception: + error_summary = _('Request can not be sent. Contact an administrator') + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + def get(self, id: str, + data: Optional[dict[str, Any]] = None, + errors: Optional[dict[str, Any]] = None, + error_summary: Optional[str] = None): + context = { + u'model': model, + u'session': model.Session, + u'user': g.user or g.author, + u'auth_user_obj': g.userobj, + } + + try: + pkg_dict = get_action('package_show')(context, {'id': id}) + + check_access(u'hdx_contact_contributor', context) + + extra_vars = { + u'pkg_dict': pkg_dict, + u'contact_topics': contributor_topics, + u'data': data or {}, + u'errors': errors or {}, + u'error_summary': error_summary or {}, + } + return render('package/contact_contributor.html', extra_vars=extra_vars) + + except NotFound: + return abort(404, _('Dataset not found')) + + except NotAuthorized: + came_from = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name')) + return redirect(h.url_for('hdx_signin.login', info_message_type='contact-contributor', came_from=came_from)) + + hdx_search.add_url_rule(u'/', view_func=search, strict_slashes=False) hdx_dataset.add_url_rule(u'/', view_func=search, strict_slashes=False) hdx_dataset.add_url_rule(u'', view_func=read) hdx_dataset.add_url_rule(u'/delete/', view_func=delete, methods=[u'GET', u'POST']) +hdx_dataset.add_url_rule(u'//contact/', + view_func=DatasetContactContributorView.as_view(str(u'contact_contributor')), + methods=[u'GET', u'POST'], strict_slashes=False) hdx_dataset.add_url_rule(u'/download_metadata', view_func=package_metadata) hdx_dataset.add_url_rule(u'/resource//download_metadata', view_func=resource_metadata) diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js new file mode 100644 index 0000000000..ea6e2ad375 --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js @@ -0,0 +1,24 @@ +$(document).ready(function () { + + var message_sent = $('#message_sent').val(); + var message_subject = $('#message_subject').val(); + + if (message_sent === 'true') { + var analyticsPromise = hdxUtil.analytics.sendMessagingEvent( + 'dataset', + 'contact contributor', + message_subject, + null, + true + ); + + $.when(analyticsPromise).then( + function () { + console.log('Analytics event sent successfully'); + }, + function () { + console.error('Failed to send the analytics event'); + } + ); + } +}); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml index a6846a994f..7a4e9ace65 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml @@ -635,7 +635,6 @@ dataset-read-styles: <<: *common-css extra: preload: - - hdx_theme/contact-contributor-styles - hdx_theme/group-message-styles - requestdata/main-styles - hdx_theme/resource-list-styles @@ -1317,19 +1316,8 @@ banner-styles: contact-contributor-scripts: <<: *common-js output: ckanext-hdx_theme/%(version)s_contact-contributor-scripts.js - extra: - preload: - - hdx_theme/ckan - - hdx_theme/contact-contributor-styles - contents: - - https://www.google.com/recaptcha/api.js - - widget/membership/contact-contributor.js - -contact-contributor-styles: - <<: *common-css - output: ckanext-hdx_theme/%(version)s_contact-contributor-styles.css contents: - - widget/membership/contact-contributor.css + - datasets/contact-contributor.js group-message-scripts: <<: *common-js @@ -1364,7 +1352,6 @@ qa-dashboard-styles: output: ckanext-hdx_theme/%(version)s_qa-dashboard-styles.css extra: preload: - - hdx_theme/contact-contributor-styles - hdx_theme/dataset-search-styles - hdx_theme/qa-package-styles - hdx_theme/requestdata-styles diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.css b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.css deleted file mode 100644 index 18b1765b61..0000000000 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.css +++ /dev/null @@ -1,35 +0,0 @@ -#contactContributorPopup { - position: relative; - background-color: #fff; -} -#contactContributorPopup .two-column-widget .two-column-left { - background-color: #1ebfb3; - height: 560px; -} -#contactContributorPopup .two-column-widget .two-column-right { - height: 560px; - padding: 60px; -} -#contactContributorPopup .two-column-widget .two-column-right .right-side-actions { - width: 257px; - right: 60px; -} -#contactContributorPopup .two-column-widget .two-column-right .right-side-actions .grecaptcha-badge { - position: absolute; - top: -75px; -} -#contactContributorPopup .two-column-widget .two-column-right .right-side-actions .btn-primary { - position: absolute; - top: 0; - right: 0; -} -#contactContributorPopup textarea { - height: 100px; - padding: 14px 20px; -} -#contactContributorPopup textarea ~ label { - bottom: 56px; -} -#contactContributorPopup textarea.input-content ~ label { - bottom: 6px; -} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.js deleted file mode 100644 index f916c2e739..0000000000 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/membership/contact-contributor.js +++ /dev/null @@ -1,42 +0,0 @@ -$(document).ready(function(){ - var $contactForm = $('#contact-contributor-form'); - $contactForm.find("select").select2(); - - contactContributorOnSubmit = function(){ - $this = $contactForm; - var toMessage = $("#membershipDonePopup").find(".to-message "); - toMessage.hide(); - - var analyticsPromise = - hdxUtil.analytics.sendMessagingEvent('dataset', 'contact contributor', - $this.find('select[name="topic"]').val(), null, true); - var postPromise = $.ajax({ - url: '/membership/contact_contributor', - type: 'POST', - data: $this.serialize(), - headers: hdxUtil.net.getCsrfTokenAsObject(), - }); - - $.when(postPromise, analyticsPromise).then( - function (postData, analyticsData) { - var result = postData[0]; - if (result.success) { - closeCurrentWidget($this); - showOnboardingWidget('#membershipDonePopup'); - } else { - if (result.error) { - alert("Can't send your request: " + result.error.message); - } - grecaptcha.reset(); - } - }, - function(){ - alert("Can't send your request!"); - } - ); - - return false; - } - - $contactForm.find('input, select, textarea').filter('[required]').on('input change', requiredFieldsFormValidator); -}); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/onboarding/onboarding.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/onboarding/onboarding.js index 2eb89cd165..2539c8a9df 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/onboarding/onboarding.js +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/widget/onboarding/onboarding.js @@ -17,23 +17,6 @@ function spawnRecaptcha(id){ } } - -function showContributorPopup(popupId, pkgTitle, pkgOwnerOrg, pkgId, overwritePopupTitle){ - spawnRecaptcha(popupId); - //populate popup with hidden fields content - pkgTitle = decodeURIComponent(pkgTitle); - let form = $('#contact-contributor-form'); - form.find('input[type="hidden"][name="pkg_title"]').val(pkgTitle); - form.find('input[type="hidden"][name="pkg_owner_org"]').val(pkgOwnerOrg); - form.find('input[type="hidden"][name="pkg_id"]').val(pkgId); - if (overwritePopupTitle) { - form.find('.contact-popup-title').html(pkgTitle); - - } - - showOnboardingWidget(popupId) -} - function showOnboardingWidget(id, elid, val){ $(id).show(); $(id).find("input[type!='button']:visible:first").focus(); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py index b4ac868a3a..0339b3a481 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py @@ -1,10 +1,12 @@ from ckanext.hdx_theme.helpers.ui_constants.onboarding import CONSTANTS as ONBOARDING_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.signin import CONSTANTS as SIGNIN_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.landing_pages import CONSTANTS as LANDING_PAGES_CONSTANTS +from ckanext.hdx_theme.helpers.ui_constants.contact_contributor import CONSTANTS as CONTACT_CONTRIBUTOR_CONSTANTS CONSTANTS = { 'ONBOARDING': ONBOARDING_CONSTANTS, 'SIGNIN': SIGNIN_CONSTANTS, 'LANDING_PAGES': LANDING_PAGES_CONSTANTS, + 'CONTACT_CONTRIBUTOR': CONTACT_CONTRIBUTOR_CONSTANTS, } diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py new file mode 100644 index 0000000000..ec57edcb68 --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py @@ -0,0 +1,32 @@ +CONSTANTS = { + 'PAGE_TITLE': '''Contact the Contributor''', + 'BODY_MAIN_TEXT': '''Use the form to ask a question or provide comments about this dataset to the contributor.''', + + 'PAGE_TITLE_MESSAGE_SENT': '''Your message was sent''', + 'BODY_MAIN_TEXT_MESSAGE_SENT': '''Go back to the dataset page.''', + + 'FEEDBACK_NOTICE_TEXT': '''This form sends feedback directly to the contributor and the HDX team will receive a + copy. It is the responsibility of the contributor to respond to you and the HDX team may not be included in + further correspondence. However, if you need further support, please contact us on hdx@un.org.''', + + 'MANDATORY_HELP': '''* indicates mandatory fields''', + + 'DATASET_NAME_TEXT': '''[Dataset] {0}''', + + 'SELECT_INQUIRY_TYPE_LABEL': '''Your inquiry is regarding''', + 'SELECT_INQUIRY_TYPE_PLACEHOLDER': '''Select an option...''', + 'SELECT_INQUIRY_TYPE_ERROR': '''Inquiry type cannot be empty''', + + 'INPUT_FULLNAME_LABEL': '''Your name''', + 'INPUT_FULLNAME_PLACEHOLDER': '''What is your name?''', + + 'INPUT_EMAIL_LABEL': '''Your email address''', + 'INPUT_EMAIL_PLACEHOLDER': '''What is your email address?''', + + 'TEXTAREA_MSG_LABEL': '''Comments''', + 'TEXTAREA_MSG_PLACEHOLDER': '''Ask a question or provide comments''', + + 'BUTTON_SUBMIT': '''Continue''', + 'BUTTON_CANCEL': '''Cancel''', +} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/email/content/contact_contributor_request_confirmation_to_user.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/email/content/contact_contributor_request_confirmation_to_user.html index 8c339d8300..3196fc1825 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/email/content/contact_contributor_request_confirmation_to_user.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/email/content/contact_contributor_request_confirmation_to_user.html @@ -3,7 +3,7 @@

Your inquiry (copied below) has been shared with the HDX dataset contributor. If you don’t hear back within five business days, please reply to this email to let us know.

-

Dataset: {{ data.pkg_title }}.

+

Dataset: {{ data.pkg_title }}

Inquiry type: {{ data.topic }}

Message: {{ data.msg }}

diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html new file mode 100644 index 0000000000..c1018e1e13 --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html @@ -0,0 +1,143 @@ +{% extends "onboarding/base.html" %} + +{% block scripts %} + {{ super() }} + + {% if message_sent %} + {% asset 'hdx_theme/contact-contributor-scripts' %} + {% endif %} +{% endblock %} + +{% set CONST = h.HDX_CONST('UI_CONSTANTS')['CONTACT_CONTRIBUTOR'] %} + +{% block subtitle %}{{ CONST.PAGE_TITLE }}{% endblock %} + +{% block breadcrumb_content %} + + +{% endblock %} + +{% block content %} + +
+ +
+
+
+ {% set main_title = CONST.PAGE_TITLE_MESSAGE_SENT if message_sent else CONST.PAGE_TITLE %} + {% set main_text = CONST.BODY_MAIN_TEXT_MESSAGE_SENT.format(h.url_for('hdx_dataset.read', id=pkg_dict.name or pkg_dict.id), pkg_dict.title) if message_sent else CONST.BODY_MAIN_TEXT %} + {{ h.snippet('bem.blocks/heading.html', title=main_title, spacing_class="mb-5") }} + {{ h.snippet('bem.blocks/paragraph.html', text=main_text, paragraph_classes=["paragraph__text_font-size-big"], spacing_class="mb-5") }} +
+
+
+ + {% if message_sent %} + + + {% else %} +
+
+
+ {{ h.snippet('bem.blocks/paragraph.html', text='--', spacing_class="mb-5") }} + {{ h.snippet('bem.blocks/paragraph.html', text=CONST.FEEDBACK_NOTICE_TEXT.format('mailto:hdx@un.org'), spacing_class="mb-5") }} +
+
+
+ +
+
+ {{ h.snippet('bem.blocks/form_info.html', message=CONST.MANDATORY_HELP, spacing_class="mb-4") }} + {{ h.snippet('bem.blocks/paragraph.html', text=CONST.DATASET_NAME_TEXT.format(pkg_dict.title), paragraph_classes=["paragraph__text_font-weight-bold"], spacing_class="mb-4") }} + + {% if error_summary %} +
+

{{ error_summary }}

+
+ {% endif %} + +
+ + {{ h.csrf_input() }} + + + + + + {{ h.snippet('bem.blocks/select2_field.html', + name="topic", + data=contact_topics, + label=CONST.SELECT_INQUIRY_TYPE_LABEL, + required=True, + selected=data.get('topic'), + errors=errors.get('topic'), + select_classes=['select2-field__select_size_large'], + data_attributes={"placeholder": CONST.SELECT_INQUIRY_TYPE_PLACEHOLDER}, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/input_field.html', + type="text", + required=True, + label=CONST.INPUT_FULLNAME_LABEL, + name="fullname", + value=data.get('fullname') if 'fullname' in data else c.userobj.fullname, + errors=errors.get('fullname'), + placeholder=CONST.INPUT_FULLNAME_PLACEHOLDER, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/input_field.html', + type="email", + required=True, + autocomplete='new-email', + label=CONST.INPUT_EMAIL_LABEL, + name="email", + value=data.get('email') if 'email' in data else c.userobj.email, + errors=errors.get('email'), + placeholder=CONST.INPUT_EMAIL_PLACEHOLDER, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/textarea_field.html', + name="msg", + required=True, + label=CONST.TEXTAREA_MSG_LABEL, + value=data.get('msg'), + errors=errors.get('msg'), + placeholder=CONST.TEXTAREA_MSG_PLACEHOLDER, + rows=6, + spacing_class="mb-4") }} + + {% if g.recaptcha_publickey %} +
+
+
+ {% endif %} + + {{ h.snippet('bem.blocks/form_button.html', type="submit", title=CONST.BUTTON_SUBMIT, button_classes=["form-button__btn_font-size_big", "btn-primary", "btn-lg", "d-block", "w-100"]) }} + +
+ {{ h.snippet('bem.blocks/form_button.html', type="href", url=h.url_for('hdx_dataset.read', id=pkg_dict.name), title=CONST.BUTTON_CANCEL, button_classes=["form-button__btn_font-size_medium", "btn-link"], container_classes=["mt-1"]) }} +
+ +
+ +
+
+ {% endif %} + +
+{% endblock %} + +{% block analytics_org_name %}{{ pkg_dict.organization.name }}{% endblock %} +{% block analytics_org_id %}{{ pkg_dict.organization.id }}{% endblock %} +{% block analytics_is_cod %}{{ analytics_is_cod }}{% endblock %} +{% block analytics_is_indicator %}{{ analytics_is_indicator }}{% endblock %} +{% block analytics_is_archived %}{{ analytics_is_archived }}{% endblock %} +{% block analytics_group_names %}{{ analytics_group_names | safe }}{% endblock %} +{% block analytics_group_ids %}{{ analytics_group_ids | safe }}{% endblock %} +{% block analytics_dataset_name %}{{ pkg_dict.name }}{% endblock %} +{% block analytics_dataset_id %}{{ pkg_dict.id }}{% endblock %} +{% block analytics_dataset_availability %}{{ analytics_dataset_availability }}{% endblock %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_macros.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_macros.html deleted file mode 100644 index ee2f71e4eb..0000000000 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_macros.html +++ /dev/null @@ -1,5 +0,0 @@ -{% macro contributor_action(pkg, h, overrideText) -%} - {% set is_current_user_a_maintainer = h.hdx_is_current_user_a_maintainer(pkg.maintainer, pkg) if pkg else false %} - {% set contact_confirm_text = "WARNING: you are the contributor of this dataset. Do you wish to continue contacting the contributor?" if is_current_user_a_maintainer else None %} - {% if is_current_user_a_maintainer %}if (confirm('{{contact_confirm_text}}')){ {% endif %}closeCurrentWidget(this);showContributorPopup('#contactContributorPopup','{{pkg.title | urlencode }}','{{pkg.owner_org}}', '{{pkg.name or pkg.id}}', {{overrideText}});{% if is_current_user_a_maintainer %} } {% endif %} -{%- endmacro %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_menu.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_menu.html index 6617876e44..ff86a83047 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_menu.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/base_actions_menu.html @@ -1,9 +1,7 @@ {% set url = request.url %} -{% import 'package/snippets/base_actions_macros.html' as macro %} {% set panel_title = panel_title or "panel title" %} {% set title = title or "title" %} -{% set contact_topics = contact_topics or {} %} {% set group_topics = group_topics or {} %} {% set hide_contact_contributor = (hide_contact_contributor or false) or (pkg.is_requestdata_type and pkg.is_requestdata_type == True) %} {% set hide_membership = hide_membership or false %} @@ -14,9 +12,6 @@ {% if not hide_membership %} {% set membership_data = membership.data %} - {% if not hide_contact_contributor%} - {{ h.snippet('widget/membership/contact-contributor.html', id="contactContributorPopup", title=title, topics=contact_topics, pkg = pkg, membership_data=membership_data) }} - {% endif %} {{ h.snippet('widget/membership/group-message.html', id="groupMessagePopup", title=title, groups=group_topics, org_id = org_id, pkg_id = pkg_id, membership_data=membership_data, base_title=base_title, base_type=base_type) }} {{ h.snippet('widget/membership/done.html', id="membershipDonePopup") }} {% endif %} @@ -32,7 +27,7 @@ {% endif %} {% if not hide_contact_contributor %}
  • - + Contact the contributor @@ -46,7 +41,7 @@ data-start-page-type="contact-contributor" data-start-page-additional-params="{{ onboarding_additional_params }}"> - diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_dashboard.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_dashboard.html index 03a39c480a..fbc1045589 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_dashboard.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_dashboard.html @@ -19,10 +19,6 @@ {{ h.snippet('widget/loading/loading.html', id="loadingScreen", message="Sending, please wait ...") }} {% block search_results_wrapper %} - {% set contact_topics = data.membership.data.contributor_topics %} - {% set membership_data = data.membership.data %} - {{ h.snippet('widget/membership/contact-contributor.html', id="contactContributorPopup", title=title, topics=contact_topics, membership_data=membership_data) }} -
    {% snippet 'qa_dashboard/qa_search_results_wrapper.html', tracking_enabled=g.tracking_enabled, my_c=data.search_template_data, display_min=true %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_package_item.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_package_item.html index dd4ad95f15..f3e325db9d 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_package_item.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/qa_dashboard/qa_package_item.html @@ -1,5 +1,4 @@ {% extends "search/snippets/package_item.html" %} -{% import 'package/snippets/base_actions_macros.html' as macro %} {% import "qa_dashboard/qa_macros.html" as qa_macros with context %} {% block package_item_content %} @@ -33,7 +32,7 @@
    Remove from quarantine all resources
  • - Contact the contributor @@ -48,7 +47,7 @@ {{ super() }} {% endblock %} - + {% endblock %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/widget/membership/contact-contributor.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/widget/membership/contact-contributor.html deleted file mode 100644 index ae84700b32..0000000000 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/widget/membership/contact-contributor.html +++ /dev/null @@ -1,73 +0,0 @@ -{# -Membership: Contact Contributor widget - -Depends on: - Fanstatic resource bundle "popup" - -Example: - {{ h.snippet('widget/membership/contact-contributor.html', - id="contactContributorPopup", - title="Dataset title", - topics={'1':'Dataset problem'} - ) - }} -#} - -{% set class = "popup-onboarding" %} -{% set two_column_class = "two-column-select-organisation" %} -{#{% resource 'hdx_theme/widget/membership/contact-contributor.css' %}#} -{#{% resource 'hdx_theme/widget/membership/contact-contributor.js' %}#} - -{% asset 'hdx_theme/contact-contributor-scripts' %} - -{% extends "widget/onboarding/two-column.html" %} - -{% block left_content %} -
    -
    {{ _("Contact the Contributor") }}
    -
    {{ _("Use the form to ask a question or provide comments about this dataset to the contributor.") }}
    -
    - {{ _("HDX feedback will be blind copied on this message so that we are aware of the initial correspondence related to datasets on the HDX site. Please ")}}  contact us  {{ _("directly should you need further support.") }} -
    -
    -{% endblock %} - -{% block right_content %} -
    -
    -
    -
    -
    - [Dataset] {{ title | truncate(65) }} - - - -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -{% endblock %} - From 03cc5ccd3c669125f0dcc29d3687085f4cf0685b Mon Sep 17 00:00:00 2001 From: Catalin Date: Mon, 26 Aug 2024 14:11:31 +0300 Subject: [PATCH 2/5] HDX-9987 new HDX Connect page implementation --- .../helpers/organization_helper.py | 14 +- .../ckanext/hdx_org_group/plugin.py | 2 +- .../hdx_org_group/views/organization_join.py | 4 +- .../ckanext/hdx_package/actions/authorize.py | 13 +- .../dataset_contact_contributor.py | 15 + .../dataset_request_access.py | 165 +++++++++ .../ckanext/hdx_package/plugin.py | 2 +- .../ckanext/hdx_package/views/dataset.py | 170 +++++++-- .../fanstatic/bem.blocks/select2_field.js | 48 +++ .../fanstatic/datasets/contact-contributor.js | 32 +- .../fanstatic/datasets/request-access.js | 39 ++ .../ckanext/hdx_theme/fanstatic/webassets.yml | 6 + .../ckanext/hdx_theme/helpers/helpers.py | 51 +++ .../helpers/ui_constants/__init__.py | 2 + .../contact_contributor/__init__.py | 3 +- .../ui_constants/request_access/__init__.py | 39 ++ ckanext-hdx_theme/ckanext/hdx_theme/plugin.py | 4 +- .../ajax_snippets/request_contact.html | 124 ------- .../templates/bem.blocks/input_field.html | 2 +- .../templates/bem.blocks/select2_field.html | 34 +- .../light/dataset/resource_req_item.html | 15 +- .../package/contact_contributor.html | 12 +- .../templates/package/request_access.html | 200 ++++++++++ .../package/snippets/resources_list.html | 15 +- .../search/snippets/package_item.html | 16 +- ckanext-hdx_users/ckanext/hdx_users/plugin.py | 2 - .../test_emails/test_requestdata_emails.py | 10 +- .../hdx_users/views/requestdata_view.py | 343 ------------------ 28 files changed, 793 insertions(+), 589 deletions(-) create mode 100644 ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/request-access.js create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/request_access/__init__.py delete mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/ajax_snippets/request_contact.html create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/package/request_access.html delete mode 100644 ckanext-hdx_users/ckanext/hdx_users/views/requestdata_view.py diff --git a/ckanext-hdx_org_group/ckanext/hdx_org_group/helpers/organization_helper.py b/ckanext-hdx_org_group/ckanext/hdx_org_group/helpers/organization_helper.py index 5a62d077a2..bf4c78a64c 100644 --- a/ckanext-hdx_org_group/ckanext/hdx_org_group/helpers/organization_helper.py +++ b/ckanext-hdx_org_group/ckanext/hdx_org_group/helpers/organization_helper.py @@ -26,6 +26,7 @@ import ckan.logic.action as core import ckan.model as model import ckan.plugins as plugins +from collections import OrderedDict from ckan.common import _, c, config import ckan.plugins.toolkit as toolkit import ckan.lib.base as base @@ -756,13 +757,14 @@ def hdx_user_in_org_or_group(group_id, include_pending=False): return length != 0 -def hdx_organization_type_list(include_default_value=None): - result = [] +def hdx_organization_type_dict(include_default_value=None): + result = OrderedDict() + if include_default_value: - result.append({'value': '-1', 'text': _('-- Please select --')}) - result.extend([{'value': t[1], 'text': _(t[0])} for t in static_lists.ORGANIZATION_TYPE_LIST]) - # return [{'value': '-1', 'text': _('-- Please select --')}] + \ - # [{'value': t[1], 'text': _(t[0])} for t in static_lists.ORGANIZATION_TYPE_LIST] + result['-1'] = _('-- Please select --') + + result.update(OrderedDict({t[1]: _(t[0]) for t in static_lists.ORGANIZATION_TYPE_LIST})) + return result diff --git a/ckanext-hdx_org_group/ckanext/hdx_org_group/plugin.py b/ckanext-hdx_org_group/ckanext/hdx_org_group/plugin.py index 96dff80b48..d377c0037b 100644 --- a/ckanext-hdx_org_group/ckanext/hdx_org_group/plugin.py +++ b/ckanext-hdx_org_group/ckanext/hdx_org_group/plugin.py @@ -44,7 +44,7 @@ def get_helpers(self): from ckanext.hdx_org_group.helpers import organization_helper as hdx_org_h from ckanext.hdx_org_group.helpers import country_helper as hdx_country_h return { - 'hdx_organization_type_list': hdx_org_h.hdx_organization_type_list, + 'hdx_organization_type_dict': hdx_org_h.hdx_organization_type_dict, 'hdx_organization_type_get_value': hdx_org_h.hdx_organization_type_get_value, 'hdx_datagrid_org_get_display_text': hdx_country_h.hdx_datagrid_org_get_display_text } diff --git a/ckanext-hdx_org_group/ckanext/hdx_org_group/views/organization_join.py b/ckanext-hdx_org_group/ckanext/hdx_org_group/views/organization_join.py index 843a9d13ed..edd1ca5914 100644 --- a/ckanext-hdx_org_group/ckanext/hdx_org_group/views/organization_join.py +++ b/ckanext-hdx_org_group/ckanext/hdx_org_group/views/organization_join.py @@ -68,7 +68,7 @@ def find_organisation() -> str: -def _set_custom_rect_logo_url(org_dict): +def set_custom_rect_logo_url(org_dict): if 'customization' in org_dict and org_dict.get('customization'): customization = json.loads(org_dict.get('customization')) if customization and customization.get('image_rect', None): @@ -82,7 +82,7 @@ def confirm_organisation() -> str: org_id = request.form.get('org_id') if org_id is not None: org_dict = get_action(u'organization_show')(context, {'id':org_id}) - _set_custom_rect_logo_url(org_dict) + set_custom_rect_logo_url(org_dict) else: return redirect(url_for('hdx_org_join.find_organisation')) except Exception as ex: diff --git a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py index d36f0bf56b..1089897c33 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py +++ b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py @@ -166,10 +166,13 @@ def hdx_mark_resource_in_hapi(context: Context, data_dict: DataDict): return _check_hdx_user_permission(context, Permissions.PERMISSION_MANAGE_IN_HAPI_FLAG) -def hdx_contact_contributor(context: Context, data_dict: DataDict): - logged_in = not new_authz.auth_is_anon_user(context) +def hdx_request_access(context: Context): + """ + Only a logged-in user can request data access. + """ - if logged_in: + user_obj = context.get('auth_user_obj') or context.get('user_obj') + if user_obj: return {'success': True} - else: - return {'success': False, 'msg': _('You must be logged in to contact the contributor.')} + + return {'success': False, 'msg': _('Not authorized to perform this request.')} diff --git a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py index 0f6e0c39ff..9907cf6c56 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py +++ b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py @@ -45,3 +45,18 @@ def validate(self, data_dict: DataDict): log.error(ex) return validated_response + + def send_mail(self): + data_dict = { + 'topic': self.request.form.get('topic'), + 'fullname': self.request.form.get('fullname'), + 'email': self.request.form.get('email'), + 'msg': self.request.form.get('msg'), + 'pkg_owner_org': self.request.form.get('pkg_owner_org'), + 'pkg_title': self.request.form.get('pkg_title'), + 'pkg_id': self.request.form.get('pkg_id'), + 'pkg_url': h.url_for('dataset_read', id=self.request.form.get('pkg_id'), qualified=True), + 'hdx_email': config.get('hdx.faqrequest.email', 'hdx@humdata.org'), + } + + get_action('hdx_send_mail_contributor')(self.context, data_dict) diff --git a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py new file mode 100644 index 0000000000..b2f2b9b631 --- /dev/null +++ b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py @@ -0,0 +1,165 @@ +import json +import logging + +import ckan.lib.navl.dictization_functions as dictization_functions +import ckan.logic as logic +import ckan.model as model +import ckan.plugins.toolkit as tk +import ckanext.hdx_users.helpers.mailer as hdx_mailer +from ckan.types import Context, DataDict, Request +from ckan.lib.navl.dictization_functions import validate +from ckanext.requestdata.logic.schema import request_create_schema +from ckanext.requestdata.view_helper import process_extras_fields + +get_action = tk.get_action +check_access = tk.check_access +config = tk.config +h = tk.h +g = tk.g +NotAuthorized = tk.NotAuthorized +NotFound = tk.ObjectNotFound +unicode_safe = tk.get_validator('unicode_safe') +log = logging.getLogger(__name__) + + +class DatasetRequestAccessLogic(object): + def __init__(self, context: Context, request: Request): + self.request = request + self.context = context + self.form = request.form + self.schema = request_create_schema() + + def read(self) -> DataDict: + data_dict = logic.clean_dict(dictization_functions.unflatten(logic.tuplize_dict(logic.parse_params(self.form)))) + return data_dict + + def validate(self, data_dict: DataDict): + try: + validated_response = validate(data_dict, self.schema, self.context) + except Exception as ex: + log.error(ex) + + return validated_response + + def send_request(self) -> tuple[bool, str]: + data = self.request.form.to_dict() + get_action('requestdata_request_create')(self.context, data) + + pkg_dict = get_action('package_show')(self.context, {'id': data['package_id']}) + + maintainer_id = pkg_dict['maintainer'] + if maintainer_id is None: + return False, 'Dataset maintainer email not found.' + + user_obj = self.context['auth_user_obj'] + + # Get users objects from maintainers list + context_user_show = { + 'model': model, + 'session': model.Session, + 'user': g.user, + 'auth_user_obj': g.userobj, + 'keep_email': True, + } + + data_dict = { + 'users': [] + } + recipients = [] + maintainer_dict = {} + try: + maintainer_dict = get_action('user_show')(context_user_show, {'id': maintainer_id}) + data_dict['users'].append(maintainer_dict) + recipients.append({'display_name': maintainer_dict.get('fullname'), 'email': maintainer_dict.get('email')}) + except NotFound: + pass + + if len(recipients) == 0: + admins = _org_admins_for_dataset(self.context, pkg_dict['name']) + + for admin in admins: + recipients.append({'display_name': admin.get('fullname'), 'email': admin.get('email')}) + + sender_name = data.get('sender_name', '') + sender_email = data.get('email_address', '') + user_email = user_obj.email + message = data['message_content'] + + try: + sender_org = get_action('organization_show')(self.context, {'id': data.get('sender_organization_id')}) + except NotFound: + sender_org = None + + organizations = get_action('organization_list_for_user')(self.context, { + 'id': user_obj.id, + 'permission': 'read' + }) + extras = json.loads(process_extras_fields(data, organizations, sender_org)) + + _send_email_to_maintainer(sender_name, message, user_email, extras, recipients, maintainer_dict, pkg_dict) + _send_email_to_requester(sender_name, sender_email, message, user_email, pkg_dict) + + # notify package creator that new data request was made + get_action('requestdata_notification_create')(self.context, data_dict) + + data_dict = { + 'package_id': data['package_id'], + 'flag': 'request' + } + get_action('requestdata_increment_request_data_counters')(self.context, data_dict) + + return True, 'Email message was successfully sent.' + + +def _org_admins_for_dataset(context: Context, dataset_name: str): + pkg_dict = get_action('package_show')(context, {'id': dataset_name}) + owner_org = pkg_dict['owner_org'] + + org = get_action('organization_show')(context, {'id': owner_org}) + + admins = [] + for user in org['users']: + if user['capacity'] == 'admin': + db_user = model.User.get(user['id']) + data = { + 'email': db_user.email, + 'fullname': db_user.fullname or db_user.name + } + admins.append(data) + + return admins + + +def _send_email_to_requester(sender_name: str, sender_email: str, message: str, user_email: str, + pkg_dict: DataDict) -> None: + subject = u'Request for access to metadata-only dataset' + email_data = { + 'user_fullname': sender_name, + 'msg': message, + 'org_name': pkg_dict.get('organization').get('title'), + 'dataset_link': h.url_for('dataset_read', id=pkg_dict['name'], qualified=True), + 'dataset_title': pkg_dict['title'], + } + senders_email = [{'display_name': sender_name, 'email': sender_email}] + hdx_mailer.mail_recipient(senders_email, subject, email_data, footer=user_email, + snippet='email/content/request_data_to_user.html') + + +def _send_email_to_maintainer(sender_name: str, message: str, user_email: str, extras, recipients, + maintainer_dict: DataDict, pkg_dict: DataDict): + subject = sender_name + u' has requested access to one of your datasets: ' + pkg_dict['title'] + email_data = { + 'user_fullname': sender_name, + 'user_email': user_email, + 'msg': message, + 'extras': extras, + 'org_name': pkg_dict.get('organization').get('title'), + 'dataset_link': h.url_for('dataset_read', id=pkg_dict['name'], qualified=True), + 'dataset_title': pkg_dict['title'], + 'maintainer_fullname': maintainer_dict.get('display_name') or maintainer_dict.get( + 'fullname') if maintainer_dict else 'HDX user', + 'requestdata_org_url': h.url_for('requestdata_organization_requests.requested_data', + id=pkg_dict.get('owner_org'), qualified=True) + } + hdx_mailer.mail_recipient(recipients, subject, email_data, footer='hdx@un.org', + snippet='email/content/request_data_to_admins.html') diff --git a/ckanext-hdx_package/ckanext/hdx_package/plugin.py b/ckanext-hdx_package/ckanext/hdx_package/plugin.py index f372f3d852..157f3fa2ba 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/plugin.py +++ b/ckanext-hdx_package/ckanext/hdx_package/plugin.py @@ -551,7 +551,7 @@ def get_auth_functions(self): 'hdx_dataseries_update': authorize.hdx_dataseries_update, 'hdx_p_coded_resource_update': authorize.hdx_p_coded_resource_update, 'hdx_mark_resource_in_hapi': authorize.hdx_mark_resource_in_hapi, - 'hdx_contact_contributor': authorize.hdx_contact_contributor, + 'hdx_request_access': authorize.hdx_request_access, } def make_middleware(self, app, config): diff --git a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py index c12d473db6..8155fff08e 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py +++ b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py @@ -25,6 +25,7 @@ import ckanext.hdx_search.helpers.search_history as search_history import ckanext.hdx_package.controller_logic.dataset_view_logic as dataset_view_logic from ckanext.hdx_package.controller_logic.dataset_contact_contributor import DatasetContactContributorLogic +from ckanext.hdx_package.controller_logic.dataset_request_access import DatasetRequestAccessLogic from ckan.views.dataset import _setup_template_variables @@ -38,6 +39,8 @@ from ckanext.hdx_theme.util.jql import fetch_downloads_per_week_for_dataset from ckanext.hdx_theme.util.light_redirect import check_redirect_needed +from ckanext.hdx_org_group.views.organization_join import set_custom_rect_logo_url + import ckanext.hdx_users.helpers.helpers as usr_h log = logging.getLogger(__name__) @@ -561,7 +564,10 @@ def post(self, id: str) -> Union[Response, str]: try: pkg_dict = get_action('package_show')(context, {'id': id}) - check_access(u'hdx_contact_contributor', context) + if pkg_dict.get('is_requestdata_type'): + return abort(404, _('Dataset not found')) + + check_access(u'hdx_send_mail_contributor', context) dataset_contact_contributor_logic = DatasetContactContributorLogic(context, request) @@ -577,34 +583,13 @@ def post(self, id: str) -> Union[Response, str]: usr_h.is_valid_captcha(request.form.get('g-recaptcha-response')) - check_access('hdx_send_mail_contributor', context, data_dict) - - data_dict['topic'] = request.form.get('topic') - data_dict['fullname'] = request.form.get('fullname') - data_dict['email'] = request.form.get('email') - data_dict['msg'] = request.form.get('msg') - data_dict['pkg_owner_org'] = request.form.get('pkg_owner_org') - data_dict['pkg_title'] = request.form.get('pkg_title') - data_dict['pkg_id'] = request.form.get('pkg_id') - data_dict['pkg_url'] = h.url_for('dataset_read', id=request.form.get('pkg_id'), qualified=True) - data_dict['hdx_email'] = config.get('hdx.faqrequest.email', 'hdx@humdata.org') + dataset_contact_contributor_logic.send_mail() - get_action('hdx_send_mail_contributor')(context, data_dict) - - analytics_is_cod = analytics.is_cod(pkg_dict) - analytics_is_indicator = analytics.is_indicator(pkg_dict) - analytics_is_archived = analytics.is_archived(pkg_dict) - analytics_group_names, analytics_group_ids = analytics.extract_locations_in_json(pkg_dict) - analytics_dataset_availability = analytics.dataset_availability(pkg_dict) + analytics_dict = h.hdx_compute_analytics(pkg_dict) extra_vars = { u'pkg_dict': pkg_dict, - u'analytics_is_cod': analytics_is_cod, - u'analytics_is_indicator': 'false', - u'analytics_is_archived': analytics_is_archived, - u'analytics_group_names': analytics_group_names, - u'analytics_group_ids': analytics_group_ids, - u'analytics_dataset_availability': analytics_dataset_availability, + u'analytics': analytics_dict, u'message_subject': request.form.get('topic'), u'message_sent': True, } @@ -632,7 +617,7 @@ def post(self, id: str) -> Union[Response, str]: log.error(error_summary) return self.get(id, data, errors, error_summary) - except Exception: + except Exception as e: error_summary = _('Request can not be sent. Contact an administrator') log.error(error_summary) return self.get(id, data, errors, error_summary) @@ -651,14 +636,20 @@ def get(self, id: str, try: pkg_dict = get_action('package_show')(context, {'id': id}) - check_access(u'hdx_contact_contributor', context) + if pkg_dict.get('is_requestdata_type'): + return abort(404, _('Dataset not found')) + + check_access(u'hdx_send_mail_contributor', context) + + analytics_dict = h.hdx_compute_analytics(pkg_dict) extra_vars = { u'pkg_dict': pkg_dict, + u'analytics': analytics_dict, u'contact_topics': contributor_topics, u'data': data or {}, u'errors': errors or {}, - u'error_summary': error_summary or {}, + u'error_summary': error_summary or '', } return render('package/contact_contributor.html', extra_vars=extra_vars) @@ -670,6 +661,126 @@ def get(self, id: str, return redirect(h.url_for('hdx_signin.login', info_message_type='contact-contributor', came_from=came_from)) +class DatasetRequestAccessView(MethodView): + + def post(self, id: str) -> Union[Response, str]: + context = { + u'model': model, + u'session': model.Session, + u'user': g.user or g.author, + u'auth_user_obj': g.userobj, + } + + try: + pkg_dict = get_action('package_show')(context, {'id': id}) + + check_access(u'hdx_request_access', context) + + if not pkg_dict.get('is_requestdata_type'): + return abort(404, _('Dataset not request data type')) + + pending_request = h.hdx_pending_request_data(g.userobj.id, pkg_dict.get('id')) + if len(pending_request) > 0: + return redirect('hdx_dataset.request_access', id=pkg_dict.get('name')) + + dataset_request_access_logic = DatasetRequestAccessLogic(context, request) + + data_dict = None + try: + data_dict = dataset_request_access_logic.read() + except dictization_functions.DataError: + abort(400, _(u'Integrity Error')) + + data, errors = dataset_request_access_logic.validate(data_dict) + if errors: + return self.get(id, data, errors) + + request_sent, send_request_message = dataset_request_access_logic.send_request() + + if request_sent: + analytics_dict = h.hdx_compute_analytics(pkg_dict) + + extra_vars = { + u'pkg_dict': pkg_dict, + u'analytics': analytics_dict, + u'request_sent': request_sent, + } + return render('package/request_access.html', extra_vars=extra_vars) + else: + error_summary = send_request_message + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except NotFound: + return abort(404, _('Dataset not found')) + + except NotAuthorized: + came_from = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name')) + return redirect(h.url_for('hdx_signin.login', info_message_type='hdx-connect', came_from=came_from)) + + except MailerException as e: + error_summary = _('Could not send request for: %s') % text_type(e) + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except ValidationError as e: + error_summary = e.error_summary + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + except Exception as e: + error_summary = _('Request can not be sent. Contact an administrator') + log.error(error_summary) + return self.get(id, data, errors, error_summary) + + def get(self, id: str, + data: Optional[dict[str, Any]] = None, + errors: Optional[dict[str, Any]] = None, + error_summary: Optional[str] = None): + context = { + u'model': model, + u'session': model.Session, + u'user': g.user or g.author, + u'auth_user_obj': g.userobj, + } + + try: + pkg_dict = get_action('package_show')(context, {'id': id}) + + check_access(u'hdx_request_access', context) + + if not pkg_dict.get('is_requestdata_type'): + return abort(404, _('Dataset not request data type')) + + pending_request = h.hdx_pending_request_data(g.userobj.id, pkg_dict.get('id')) + if pending_request: + if not error_summary: + error_summary = _('You already have a pending request. Please wait for the reply.') + + org_dict = get_action(u'organization_show')(context, {'id': pkg_dict.get('organization', {}).get('id')}) + set_custom_rect_logo_url(org_dict) + + analytics_dict = h.hdx_compute_analytics(pkg_dict) + + extra_vars = { + u'pkg_dict': pkg_dict, + u'analytics': analytics_dict, + u'org_dict': org_dict, + u'pending_request': pending_request, + u'data': data or {}, + u'errors': errors or {}, + u'error_summary': error_summary or '', + } + return render('package/request_access.html', extra_vars=extra_vars) + + except NotFound: + return abort(404, _('Dataset not found')) + + except NotAuthorized: + came_from = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name')) + return redirect(h.url_for('hdx_signin.login', info_message_type='hdx-connect', came_from=came_from)) + + hdx_search.add_url_rule(u'/', view_func=search, strict_slashes=False) hdx_dataset.add_url_rule(u'/', view_func=search, strict_slashes=False) hdx_dataset.add_url_rule(u'', view_func=read) @@ -677,5 +788,8 @@ def get(self, id: str, hdx_dataset.add_url_rule(u'//contact/', view_func=DatasetContactContributorView.as_view(str(u'contact_contributor')), methods=[u'GET', u'POST'], strict_slashes=False) +hdx_dataset.add_url_rule(u'//request-access/', + view_func=DatasetRequestAccessView.as_view(str(u'request_access')), + methods=[u'GET', u'POST'], strict_slashes=False) hdx_dataset.add_url_rule(u'/download_metadata', view_func=package_metadata) hdx_dataset.add_url_rule(u'/resource//download_metadata', view_func=resource_metadata) diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/bem.blocks/select2_field.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/bem.blocks/select2_field.js index d249dae918..b54a3040d4 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/bem.blocks/select2_field.js +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/bem.blocks/select2_field.js @@ -13,4 +13,52 @@ $(document).ready(function () { }); }); + $('.select2-field__select[data-has-other-option="true"]') + .on('select2:select', function (e) { + var value = e.params.data.id; + + var name = $(this).attr('name'); + var new_name = name + "_other"; + + var $form = $(this).closest('form'); + var $other_input = $form.find('input[name="' + new_name + '"]'); + var $other_input_container = $other_input.parent().parent(); // .input-field + + if (value === 'other') { + $other_input_container.removeClass('d-none'); + } else { + $other_input.val(''); + $other_input_container.addClass('d-none'); + } + }) + .on('select2:clear', function (e) { + var value = e.params.data[0].id; + + var name = $(this).attr('name'); + var new_name = name + "_other"; + + var $form = $(this).closest('form'); + var $other_input = $form.find('input[name="' + new_name + '"]'); + var $other_input_container = $other_input.parent().parent(); // .input-field + + if (value === 'other') { + $other_input.val(''); + $other_input_container.addClass('d-none'); + } + }) + .on('change', function (e) { + var name = $(this).attr('name'); + var new_name = name + "_other"; + + var $form = $(this).closest('form'); + var $other_input = $form.find('input[name="' + new_name + '"]'); + var $other_input_container = $other_input.parent().parent(); // .input-field + + var value = $(this).val(); + if (value !== 'other') { + $other_input.val(''); + $other_input_container.addClass('d-none'); + } + }); + }); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js index ea6e2ad375..d251e9b777 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/contact-contributor.js @@ -3,22 +3,22 @@ $(document).ready(function () { var message_sent = $('#message_sent').val(); var message_subject = $('#message_subject').val(); - if (message_sent === 'true') { - var analyticsPromise = hdxUtil.analytics.sendMessagingEvent( - 'dataset', - 'contact contributor', - message_subject, - null, - true - ); + if (message_sent && message_sent.toLowerCase() === 'true') { + var analyticsPromise = hdxUtil.analytics.sendMessagingEvent( + 'dataset', + 'contact contributor', + message_subject, + null, + true + ); - $.when(analyticsPromise).then( - function () { - console.log('Analytics event sent successfully'); - }, - function () { - console.error('Failed to send the analytics event'); - } - ); + $.when(analyticsPromise).then( + function () { + console.log('Analytics event sent successfully'); + }, + function () { + console.error('Failed to send the analytics event'); + } + ); } }); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/request-access.js b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/request-access.js new file mode 100644 index 0000000000..9133e4f742 --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/datasets/request-access.js @@ -0,0 +1,39 @@ +$(document).ready(function () { + + var request_sent = $('#request_sent').val(); + + if (request_sent && request_sent.toLowerCase() === 'true') { + var analyticsPromise = hdxUtil.analytics.sendMessagingEvent( + 'dataset', + 'data request', + null, + null, + true + ); + + $.when(analyticsPromise).then( + function () { + console.log('Analytics event sent successfully'); + }, + function () { + console.error('Failed to send the analytics event'); + } + ); + } + + var $form = $('#request-access-form'); + var $org_select = $form.find('#select-sender_organization_id'); + var $org_type_select = $form.find('#select-sender_organization_type'); + + $org_select + .on('select2:select', function (e) { + var selected_option = $(this).find(':selected'); + var org_type = selected_option.data('org_type'); + + $org_type_select.val((org_type) ? org_type : null).trigger('change'); + }) + .on('select2:clear', function (e) { + $org_type_select.val(null).trigger('change'); + }); + +}); diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml index 7a4e9ace65..66a3cfc366 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml +++ b/ckanext-hdx_theme/ckanext/hdx_theme/fanstatic/webassets.yml @@ -1319,6 +1319,12 @@ contact-contributor-scripts: contents: - datasets/contact-contributor.js +request-access-scripts: + <<: *common-js + output: ckanext-hdx_theme/%(version)s_request-access-scripts.js + contents: + - datasets/request-access.js + group-message-scripts: <<: *common-js output: ckanext-hdx_theme/%(version)s_group-message-scripts.js diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/helpers.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/helpers.py index 141f7a343e..203e8a54d7 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/helpers.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/helpers.py @@ -17,6 +17,7 @@ from six import text_type +from collections import OrderedDict from ckan.lib import munge from ckan.plugins import toolkit from ckanext.hdx_package.helpers.freshness_calculator import UPDATE_FREQ_INFO @@ -688,6 +689,56 @@ def hdx_location_list(include_world=True): return top_locations + bottom_locations +def hdx_location_dict(include_world=True): + world_location_name = 'world' + top_values = [world_location_name] if include_world else [] + + locations = logic.get_action('cached_group_list')({}, {}) + + top_locations = OrderedDict() + bottom_locations = OrderedDict() + + for loc in locations: + key = loc.get('title') + value = loc.get('title') + if loc.get('name') in top_values: + top_locations[key] = value + else: + if loc.get('name') == world_location_name and include_world is False: + continue + bottom_locations[key] = value + + return OrderedDict(list(top_locations.items()) + list(bottom_locations.items())) + + +def hdx_user_orgs_dict(user_id, include_org_type=False): + try: + orgs = _get_action('organization_list_for_user', {'id': user_id}) + + if include_org_type: + query = model.Session.query(model.GroupExtra).filter_by(key='hdx_org_type', state='active') + org_extras = query.all() + + extras = {org_extra.group_id: org_extra.value for org_extra in org_extras} + + for org in orgs: + org_id = org.get('id') + if org_id in extras: + org['org_type'] = extras[org_id] + + result = OrderedDict() + for org in orgs: + org_data = {'name': org.get('display_name')} + if include_org_type and 'org_type' in org: + org_data['org_type'] = org['org_type'] + result[org.get('display_name')] = org_data + + return result + + except Exception: + return OrderedDict() + + def hdx_organisation_list(): orgs = h.organizations_available('create_dataset') orgs_dict_list = [{'value': org.get('name'), 'text': org.get('title')} for org in orgs] diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py index 0339b3a481..bf7b97eba9 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/__init__.py @@ -2,6 +2,7 @@ from ckanext.hdx_theme.helpers.ui_constants.signin import CONSTANTS as SIGNIN_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.landing_pages import CONSTANTS as LANDING_PAGES_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.contact_contributor import CONSTANTS as CONTACT_CONTRIBUTOR_CONSTANTS +from ckanext.hdx_theme.helpers.ui_constants.request_access import CONSTANTS as REQUEST_ACCESS_CONSTANTS CONSTANTS = { @@ -9,4 +10,5 @@ 'SIGNIN': SIGNIN_CONSTANTS, 'LANDING_PAGES': LANDING_PAGES_CONSTANTS, 'CONTACT_CONTRIBUTOR': CONTACT_CONTRIBUTOR_CONSTANTS, + 'REQUEST_ACCESS': REQUEST_ACCESS_CONSTANTS, } diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py index ec57edcb68..aa6dbda6b9 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/contact_contributor/__init__.py @@ -16,7 +16,6 @@ 'SELECT_INQUIRY_TYPE_LABEL': '''Your inquiry is regarding''', 'SELECT_INQUIRY_TYPE_PLACEHOLDER': '''Select an option...''', - 'SELECT_INQUIRY_TYPE_ERROR': '''Inquiry type cannot be empty''', 'INPUT_FULLNAME_LABEL': '''Your name''', 'INPUT_FULLNAME_PLACEHOLDER': '''What is your name?''', @@ -27,6 +26,6 @@ 'TEXTAREA_MSG_LABEL': '''Comments''', 'TEXTAREA_MSG_PLACEHOLDER': '''Ask a question or provide comments''', - 'BUTTON_SUBMIT': '''Continue''', + 'BUTTON_SUBMIT': '''Submit''', 'BUTTON_CANCEL': '''Cancel''', } diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/request_access/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/request_access/__init__.py new file mode 100644 index 0000000000..3b5f01975b --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/request_access/__init__.py @@ -0,0 +1,39 @@ +CONSTANTS = { + 'PAGE_TITLE': '''Request access''', + 'BODY_MAIN_TEXT': '''Get in touch with the contributing organization to request access to the data.''', + + 'PAGE_TITLE_REQUEST_SENT': '''Your request was sent''', + 'BODY_MAIN_TEXT_REQUEST_SENT': '''Go back to the previous page.''', + + 'DATA_USAGE_TEXT': '''Tell them about your work and why you would like to use their data.''', + + 'MANDATORY_HELP': '''* indicates mandatory fields''', + + 'DATASET_NAME_TEXT': '''[Dataset] {0}''', + + 'INPUT_FULLNAME_LABEL': '''Your name''', + 'INPUT_FULLNAME_PLACEHOLDER': '''What is your name?''', + + 'INPUT_EMAIL_LABEL': '''Your email address''', + 'INPUT_EMAIL_PLACEHOLDER': '''What is your email address?''', + + 'SELECT_ORGANIZATION_LABEL': '''Your organization''', + 'SELECT_ORGANIZATION_PLACEHOLDER': '''Select an option...''', + + 'SELECT_ORGANIZATION_TYPE_LABEL': '''Your organization type''', + 'SELECT_ORGANIZATION_TYPE_PLACEHOLDER': '''Select an option...''', + + 'SELECT_LOCATION_LABEL': '''Where are you located?''', + 'SELECT_LOCATION_PLACEHOLDER': '''Select an option...''', + + 'SELECT_INTENDED_USE_LABEL': '''Intended use of this data''', + 'SELECT_INTENDED_USE_PLACEHOLDER': '''Select an option...''', + + 'TEXTAREA_MSG_LABEL': '''Comments''', + 'TEXTAREA_MSG_PLACEHOLDER': '''Please explain why you need to access this data. Clear explanations will help the data contributor decide whether to share data.''', + + 'DATA_REQUEST_ACKNOWLEDGMENT': '''I understand that my request for this dataset may be denied by the contributor at their discretion. If the contributor decides to share this dataset I will manage the data in accordance with the applicable license and any other instructions the contributor may provide.''', + + 'BUTTON_SUBMIT': '''Send request''', + 'BUTTON_CANCEL': '''Cancel''', +} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/plugin.py b/ckanext-hdx_theme/ckanext/hdx_theme/plugin.py index 490920d119..69af3c4783 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/plugin.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/plugin.py @@ -260,7 +260,9 @@ def get_helpers(self): 'are_new_p_code_filters_enabled': hdx_helpers.are_new_p_code_filters_enabled, 'bs5_build_nav_icon': hdx_helpers.bs5_build_nav_icon, 'hdx_decode_markup': hdx_helpers.hdx_decode_markup, - 'HDX_CONST': const + 'HDX_CONST': const, + 'hdx_location_dict': hdx_helpers.hdx_location_dict, + 'hdx_user_orgs_dict': hdx_helpers.hdx_user_orgs_dict, } def get_actions(self): diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/ajax_snippets/request_contact.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/ajax_snippets/request_contact.html deleted file mode 100644 index a22dcdbccf..0000000000 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/ajax_snippets/request_contact.html +++ /dev/null @@ -1,124 +0,0 @@ -{% asset 'hdx_theme/requestdata-styles' %} - -{% import 'macros/form.html' as form %} - -{% set user_orgs = h.requestdata_get_orgs_for_user(c.userobj.id, include_org_type=True) %} -{% set pending_request = h.hdx_pending_request_data(c.userobj.id, package_id) %} - - diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/input_field.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/input_field.html index 125df71883..44648d227d 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/input_field.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/input_field.html @@ -1,4 +1,4 @@ -
    +
    {% if label %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/select2_field.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/select2_field.html index 0c044ee9ad..89c1e668ed 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/select2_field.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/bem.blocks/select2_field.html @@ -10,17 +10,47 @@ id="select-{{ name }}" {% if required %}required{% endif %} {% if data_attributes %}{% for key, value in data_attributes.items() %}data-{{ key }}="{{ value }}" + {% if other_option %}data-has-other-option="true"{% endif %} {% endfor %}{% endif %} > {% if data_attributes and data_attributes['placeholder'] %} {% endif %} {% if data %} - {% for value, name in data.items() %} - + {% for value, item in data.items() %} + {% if item is mapping %} + + {% else %} + + {% endif %} {% endfor %} {% endif %} + {% if other_option and 'other' not in data %} + + {% endif %} + + {% if other_option %} + {% set other_name = name ~ "_other" %} + {% set container_classes = [] if selected == 'other' else ["d-none"] %} + {{ h.snippet('bem.blocks/input_field.html', + type="text", + label=label, + name=other_name, + value=other_option_value, + errors=other_option_error, + placeholder=label, + container_classes=container_classes, + spacing_class="mt-4") }} + {% endif %} + {% if errors or (data_attributes and data_attributes['validation-error']) %}
  • + +{% endblock %} + +{% block content %} + +
    + +
    +
    +
    + {% set main_title = CONST.PAGE_TITLE_REQUEST_SENT if request_sent else CONST.PAGE_TITLE %} + {% set main_text = CONST.BODY_MAIN_TEXT_REQUEST_SENT.format(h.url_for('hdx_dataset.read', id=pkg_dict.name or pkg_dict.id), pkg_dict.title) if request_sent else CONST.BODY_MAIN_TEXT %} + {{ h.snippet('bem.blocks/heading.html', title=main_title, spacing_class="mb-5") }} + {{ h.snippet('bem.blocks/paragraph.html', text=main_text, paragraph_classes=["paragraph__text_font-size-big"], spacing_class="mb-5") }} +
    +
    +
    + + {% if request_sent %} + + {% else %} +
    +
    +
    + {{ h.snippet('bem.blocks/paragraph.html', text='--', spacing_class="mb-5") }} + {{ h.snippet('bem.blocks/paragraph.html', text=CONST.DATA_USAGE_TEXT.format('mailto:hdx@un.org'), spacing_class="mb-5") }} +
    +
    +
    + +
    +
    + {% if org_dict.custom_rect_logo_url %} +
    + {{ org_dict.display_name }} +
    + {% endif %} + + {% if error_summary %} +
    +

    {{ error_summary }}

    +
    + {% endif %} + + {% if not pending_request %} + {{ h.snippet('bem.blocks/form_info.html', message=CONST.MANDATORY_HELP, spacing_class="mb-4") }} + {{ h.snippet('bem.blocks/paragraph.html', text=CONST.DATASET_NAME_TEXT.format(pkg_dict.title), paragraph_classes=["paragraph__text_font-weight-bold"], spacing_class="mb-4") }} + +
    + + {{ h.csrf_input() }} + + {{ h.snippet('bem.blocks/input_field.html', + type="hidden", + name="package_id", + value=pkg_dict.id, + errors=errors.get('package_id')) }} + + {{ h.snippet('bem.blocks/input_field.html', + type="text", + required=True, + label=CONST.INPUT_FULLNAME_LABEL, + name="sender_name", + value=data.get('sender_name') if 'sender_name' in data else c.userobj.fullname, + errors=errors.get('sender_name'), + placeholder=CONST.INPUT_FULLNAME_PLACEHOLDER, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/input_field.html', + type="email", + required=True, + autocomplete='new-email', + label=CONST.INPUT_EMAIL_LABEL, + name="email_address", + value=data.get('email_address') if 'email_address' in data else c.userobj.email, + errors=errors.get('email_address'), + placeholder=CONST.INPUT_EMAIL_PLACEHOLDER, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/select2_field.html', + name="sender_organization_id", + data=h.hdx_user_orgs_dict(g.userobj.id, include_org_type=True), + label=CONST.SELECT_ORGANIZATION_LABEL, + required=True, + other_option=True, + other_option_value=data.get('sender_organization_id_other'), + other_option_error=errors.get('sender_organization_id_other'), + selected=data.get('sender_organization_id'), + errors=errors.get('sender_organization_id'), + select_classes=['select2-field__select_size_large'], + data_attributes={"placeholder": CONST.SELECT_ORGANIZATION_PLACEHOLDER}, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/select2_field.html', + name="sender_organization_type", + data=h.hdx_organization_type_dict(), + label=CONST.SELECT_ORGANIZATION_TYPE_LABEL, + required=True, + other_option=True, + other_option_value=data.get('sender_organization_type_other'), + other_option_error=errors.get('sender_organization_type_other'), + selected=data.get('sender_organization_type'), + errors=errors.get('sender_organization_type'), + select_classes=['select2-field__select_size_large'], + data_attributes={"placeholder": CONST.SELECT_ORGANIZATION_TYPE_PLACEHOLDER}, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/select2_field.html', + name="sender_country", + data=h.hdx_location_dict(include_world=False), + label=CONST.SELECT_LOCATION_LABEL, + required=True, + selected=data.get('sender_country'), + errors=errors.get('sender_country'), + select_classes=['select2-field__select_size_large'], + data_attributes={"placeholder": CONST.SELECT_LOCATION_PLACEHOLDER}, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/select2_field.html', + name="sender_intend", + data={'Humanitarian Assistance': 'Humanitarian Assistance', 'Academic': 'Academic', 'Advocacy': 'Advocacy', 'other': 'Other'}, + label=CONST.SELECT_INTENDED_USE_LABEL, + required=True, + other_option=True, + other_option_value=data.get('sender_intend_other'), + other_option_error=errors.get('sender_intend_other'), + selected=data.get('sender_intend'), + errors=errors.get('sender_intend'), + select_classes=['select2-field__select_size_large'], + data_attributes={"placeholder": CONST.SELECT_INTENDED_USE_PLACEHOLDER}, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/textarea_field.html', + name="message_content", + required=True, + label=CONST.TEXTAREA_MSG_LABEL, + value=data.get('message_content'), + errors=errors.get('message_content'), + placeholder=CONST.TEXTAREA_MSG_PLACEHOLDER, + rows=6, + spacing_class="mb-4") }} + + {{ h.snippet('bem.blocks/checkbox_field.html', + required=True, + label=CONST.DATA_REQUEST_ACKNOWLEDGMENT, + name="user_info_accept_terms", + checked=False, + spacing_class="mb-3") }} + + {{ h.snippet('bem.blocks/form_button.html', + type="submit", + title=CONST.BUTTON_SUBMIT, + button_classes=["form-button__btn_font-size_big", "btn-primary", "btn-lg", "d-block", "w-100"]) }} + +
    + {{ h.snippet('bem.blocks/form_button.html', + type="href", + url=h.url_for('hdx_dataset.read', id=pkg_dict.name), + title=CONST.BUTTON_CANCEL, + button_classes=["form-button__btn_font-size_medium", "btn-link"], + container_classes=["mt-1"]) }} +
    + +
    + {% endif %} + +
    +
    + {% endif %} + +
    +{% endblock %} + +{% block analytics_org_name %}{{ pkg_dict.organization.name }}{% endblock %} +{% block analytics_org_id %}{{ pkg_dict.organization.id }}{% endblock %} +{% block analytics_is_cod %}{{ analytics.analytics_is_cod }}{% endblock %} +{% block analytics_is_indicator %}{{ analytics.analytics_is_indicator }}{% endblock %} +{% block analytics_is_archived %}{{ analytics.analytics_is_archived }}{% endblock %} +{% block analytics_group_names %}{{ analytics.analytics_group_names | safe }}{% endblock %} +{% block analytics_group_ids %}{{ analytics.analytics_group_ids | safe }}{% endblock %} +{% block analytics_dataset_name %}{{ pkg_dict.name }}{% endblock %} +{% block analytics_dataset_id %}{{ pkg_dict.id }}{% endblock %} +{% block analytics_dataset_availability %}{{ analytics.analytics_dataset_availability }}{% endblock %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/resources_list.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/resources_list.html index 935bf74dab..da124da650 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/resources_list.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/snippets/resources_list.html @@ -35,11 +35,6 @@ {% else %} {% if pkg.is_requestdata_type %} {% set is_logged_in = True if g.userobj and not g.userobj.is_anonymous else False %} - {% set is_hdx = h.requestdata_is_hdx_portal() %} - {% set redirect_url = h.url_for('user.login') %} - {% set requestdata_action = h.url_for('requestdata_send_request.send_request') %} - {% set request_post_data = h.dump_json({'package_id':pkg.id, 'package_name': pkg.name, 'package_title':pkg.title}) %} - {% set is_current_user_a_maintainer = h.requestdata_is_current_user_a_maintainer(pkg.maintainer) %}

    The contributor has only shared the metadata for this dataset. To access the data, please use the request button.

    ' + new_terms[i] + '' - elif available_terms[i] == '{organization}' and is_user_sysadmin: - new_terms[i] = config.get('ckan.site_title') - elif available_terms[i] == '{data_maintainers}': - if len(new_terms[i]) == 1: - new_terms[i] = new_terms[i][0] - else: - maintainers = '' - for j, term in enumerate(new_terms[i][:]): - maintainers += term - - if j == len(new_terms[i]) - 2: - maintainers += ' and ' - elif j < len(new_terms[i]) - 1: - maintainers += ', ' - - new_terms[i] = maintainers - elif available_terms[i] == '{email}': - # display a mask of the email - email_list = new_terms[i].split('@') - new_terms[i] = "@".join( - [email_list[0].replace(email_list[0][1:len(email_list[0]) - 1], '********'), email_list[1]]) - email_header = email_header.replace(available_terms[i], new_terms[i]) - email_body = email_body.replace(available_terms[i], new_terms[i]) - email_footer = email_footer.replace(available_terms[i], new_terms[i]) - - if only_org_admins: - owner_org = _get_action('package_show', - {'id': dataset_name}).get('owner_org') - url = url_for('requestdata_organization_requests.requested_data', - id=owner_org, qualified=True) - email_body += '

    This dataset\'s maintainer does not exist.\ - Go to your organisation\'s Requested Data\ - page to see the new request. Please also edit the dataset and assign\ - a new maintainer.' - else: - if len(data_maintainers_ids) > 1: - owner_org = _get_action('package_show', {'id': dataset_name}).get('owner_org') - url = url_for('requestdata_organization_requests.requested_data', id=owner_org, qualified=True) - else: - url = url_for('requestdata.my_requested_data', id=data_maintainers_ids[0], qualified=True) - email_body += '

    Please accept or decline the request\ - as soon as you can by visiting the \ - My Requests page.' - - organizations = \ - _get_action('hdx_organization_list_for_user', {'id': data_owner}) - - package = _get_action('package_show', {'id': dataset_name}) - - if not only_org_admins: - for org in organizations: - if org['name'] in organization and package['owner_org'] == org['id']: - url = \ - url_for('requestdata_organization_requests.requested_data', - id=org['name'], qualified=True) - email_body += '

    Go to Requested data page in organization admin.' - - site_url = config.get('ckan.site_url') - site_title = config.get('ckan.site_title') - newsletter_url = config.get('ckanext.requestdata.newsletter_url', site_url) - twitter_url = \ - config.get('ckanext.requestdata.twitter_url', 'https://twitter.com') - contact_email = config.get('ckanext.requestdata.contact_email', '') - - email_footer += """ -

    - -

    - """ + site_title + """ -

    -

    - \ - Sign up for our newsletter | \ - Follow us on Twitter\ - | Contact us -

    -
    - - """ - - result = email_header + '

    ' + email_body + '

    ' + email_footer - - return result - - -def send_request(): - '''Send mail to resource owner. - - :param data: Contact form data. - :type data: object - - :rtype: json - ''' - context = {'model': model, 'session': model.Session, - 'user': g.user, 'auth_user_obj': g.userobj} - try: - if request.method == 'POST': - data = request.form.to_dict() - _get_action('requestdata_request_create', data) - else: - abort(403, _('Unauthorized to access this page')) - except NotAuthorized: - abort(403, _('Unauthorized to update this dataset.')) - except ValidationError as e: - error = { - 'success': False, - 'error': { - 'fields': e.error_dict - } - } - - return json.dumps(error) - - data_dict = {'id': data['package_id']} - package = _get_action('package_show', data_dict) - - sender_name = data.get('sender_name', '') - sender_email = data.get('email_address', '') - - user_obj = context['auth_user_obj'] - data_dict = { - 'id': user_obj.id, - 'permission': 'read' - } - - organizations = _get_action('organization_list_for_user', data_dict) - try: - sender_org = _get_action('organization_show', {'id': data.get('sender_organization_id')}) - except NotFound: - sender_org = None - - extras = json.loads(process_extras_fields(data, organizations, sender_org)) - - orgs = [] - for i in organizations: - orgs.append(i['display_name']) - org = ','.join(orgs) - dataset_name = package['name'] - dataset_title = package['title'] - email = user_obj.email - message = data['message_content'] - creator_user_id = package['creator_user_id'] - data_owner = \ - _get_action('user_show', {'id': creator_user_id}).get('name') - _sysadmins = _get_sysadmins() - if len(_sysadmins) > 0: - sysadmin = _sysadmins[0].name - context_sysadmin = { - 'model': model, - 'session': model.Session, - 'user': sysadmin, - 'auth_user_obj': g.userobj - } - to = package['maintainer'] - if to is None: - message = { - 'success': False, - 'error': { - 'fields': { - 'email': 'Dataset maintainer email not found.' - } - } - } - - return json.dumps(message) - maintainers = to.split(',') - data_dict = { - 'users': [] - } - users_email = [] - only_org_admins = False - data_maintainers = [] - data_maintainers_ids = [] - # Get users objects from maintainers list - user = {} - for id in maintainers: - try: - user = get_action('user_show')(context_sysadmin, {'id': id}) - data_dict['users'].append(user) - users_email.append({'display_name': user.get('fullname'), 'email': user.get('email')}) - data_maintainers.append(user['fullname'] or user['name']) - data_maintainers_ids.append(user['name'] or user['id']) - except NotFound: - pass - mail_subject = \ - config.get('ckan.site_title') + ': New data request "' \ - + dataset_title + '"' - - if len(users_email) == 0: - admins = _org_admins_for_dataset(dataset_name) - # admins=og_create.get_organization_admins(package.get('owner_org')) - - for admin in admins: - users_email.append({'display_name': admin.get('fullname'), 'email': admin.get('email')}) - data_maintainers.append(admin.get('fullname')) - data_maintainers_ids.append(admin.get('name') or admin.get('id')) - only_org_admins = True - - subject = sender_name + u' has requested access to one of your datasets: ' + dataset_title - email_data = { - 'user_fullname': sender_name, - 'user_email': email, - 'msg': message, - 'extras': extras, - 'org_name': package.get('organization').get('title'), - 'dataset_link': h.url_for('dataset_read', id=dataset_name, qualified=True), - 'dataset_title': dataset_title, - 'maintainer_fullname': user.get('display_name') or user.get('fullname') if user else 'HDX user', - 'requestdata_org_url': h.url_for('requestdata_organization_requests.requested_data', - id=package.get('owner_org'), - qualified=True) - } - hdx_mailer.mail_recipient(users_email, subject, email_data, footer='hdx@un.org', - snippet='email/content/request_data_to_admins.html') - - subject = u'Request for access to metadata-only dataset' - email_data = { - 'user_fullname': sender_name, - 'msg': message, - 'org_name': package.get('organization').get('title'), - 'dataset_link': h.url_for('dataset_read', id=dataset_name, qualified=True), - 'dataset_title': dataset_title, - } - senders_email = [{'display_name': sender_name, 'email': sender_email}] - hdx_mailer.mail_recipient(senders_email, subject, email_data, footer=email, - snippet='email/content/request_data_to_user.html') - - # notify package creator that new data request was made - _get_action('requestdata_notification_create', data_dict) - data_dict = { - 'package_id': data['package_id'], - 'flag': 'request' - } - - action_name = 'requestdata_increment_request_data_counters' - _get_action(action_name, data_dict) - response_dict = { - 'success': True, - 'message': 'Email message was successfully sent.' - } - return json.dumps(response_dict) - else: - message = { - 'success': True, - 'message': 'Request sent, but email message was not sent.' - } - - return json.dumps(message) - - -def _org_admins_for_dataset(dataset_name): - package = _get_action('package_show', {'id': dataset_name}) - owner_org = package['owner_org'] - admins = [] - - org = _get_action('organization_show', {'id': owner_org}) - - for user in org['users']: - if user['capacity'] == 'admin': - db_user = model.User.get(user['id']) - data = { - 'email': db_user.email, - 'fullname': db_user.fullname or db_user.name - } - admins.append(data) - - return admins - - -requestdata_send_request.add_url_rule(u'/request_data', view_func=send_request, methods=(u'GET', u'POST',)) From 3417d94d45cfd074795619ed65af331ad58288e5 Mon Sep 17 00:00:00 2001 From: Catalin Date: Mon, 26 Aug 2024 14:14:53 +0300 Subject: [PATCH 3/5] HDX-9987 update requestdata version --- requirements.in | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.in b/requirements.in index 40d13cd9e6..fa5dfc8b9b 100644 --- a/requirements.in +++ b/requirements.in @@ -64,7 +64,7 @@ rcssmin==1.1.2 # email_validator==1.0.2 timeago==1.0.16 --e git+https://github.com/OCHA-DAP/ckanext-requestdata.git@3.0.35#egg=ckanext-requestdata +-e git+https://github.com/OCHA-DAP/ckanext-requestdata.git@3.0.36#egg=ckanext-requestdata # ckanext-showcase -e git+https://github.com/OCHA-DAP/ckanext-showcase.git@1.5.4#egg=ckanext-showcase diff --git a/requirements.txt b/requirements.txt index b032172680..0f8188f243 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ # -e git+https://github.com/OCHA-DAP/ckanext-dcat.git@1.1.206#egg=ckanext-dcat # via -r requirements.in --e git+https://github.com/OCHA-DAP/ckanext-requestdata.git@3.0.35#egg=ckanext-requestdata +-e git+https://github.com/OCHA-DAP/ckanext-requestdata.git@3.0.36#egg=ckanext-requestdata # via -r requirements.in -e git+https://github.com/OCHA-DAP/ckanext-s3filestore.git@0.6.1#egg=ckanext-s3filestore # via -r requirements.in From 0c6f529c36ad77e383ad60ef27f7f74678025306 Mon Sep 17 00:00:00 2001 From: Catalin Date: Tue, 27 Aug 2024 14:21:31 +0300 Subject: [PATCH 4/5] HDX-9987 & HDX-9990 add tests --- .../ckanext/hdx_package/actions/authorize.py | 2 +- .../test_contact_contributor_view.py | 180 ++++++++++++++ .../test_pages/test_request_access_view.py | 233 ++++++++++++++++++ .../ckanext/hdx_package/views/dataset.py | 12 +- .../package/contact_contributor.html | 3 +- 5 files changed, 422 insertions(+), 8 deletions(-) create mode 100644 ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_contact_contributor_view.py create mode 100644 ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_request_access_view.py diff --git a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py index f9f0b92b91..fbfe3658f2 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py +++ b/ckanext-hdx_package/ckanext/hdx_package/actions/authorize.py @@ -171,7 +171,7 @@ def hdx_mark_resource_in_hapi(context: Context, data_dict: DataDict): return _check_hdx_user_permission(context, Permissions.PERMISSION_MANAGE_IN_HAPI_FLAG) -def hdx_request_access(context: Context): +def hdx_request_access(context: Context, data_dict: DataDict): """ Only a logged-in user can request data access. """ diff --git a/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_contact_contributor_view.py b/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_contact_contributor_view.py new file mode 100644 index 0000000000..4be45fbef6 --- /dev/null +++ b/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_contact_contributor_view.py @@ -0,0 +1,180 @@ +import mock +import pytest +import ckan.model as model +import ckan.plugins.toolkit as tk +import ckan.tests.factories as factories +from ckanext.hdx_org_group.helpers.static_lists import ORGANIZATION_TYPE_LIST + +get_action = tk.get_action +config = tk.config +h = tk.h + +USER_NAME = 'test_user' +USER_EMAIL = 'test_user@test.org' +SYSADMIN_NAME = 'sysadmin_user' +SYSADMIN_EMAIL = 'sysadmin_user@test.org' +ORGANIZATION_NAME = 'test_organization' +LOCATION_NAME = 'test_location' +PUBLIC_DATASET_NAME = 'public_test_dataset' +REQUEST_DATA_DATASET_NAME = 'contact_contributor_test_dataset' +CONST_CONTACT_CONTRIBUTOR = h.HDX_CONST('UI_CONSTANTS')['CONTACT_CONTRIBUTOR'] +CONST_SIGNIN = h.HDX_CONST('UI_CONSTANTS')['SIGNIN'] + + +@pytest.fixture() +def setup_data(): + factories.Sysadmin(name=SYSADMIN_NAME, email=SYSADMIN_EMAIL, fullname='Sysadmin User') + factories.User(name=USER_NAME, email=USER_EMAIL, fullname='Test User') + factories.Organization( + name=ORGANIZATION_NAME, + title='Test Organization', + hdx_org_type=ORGANIZATION_TYPE_LIST[0][1], + org_url='https://hdx.hdxtest.org/' + ) + factories.Group(name=LOCATION_NAME) + + +def get_sysadmin_context(): + return {'model': model, 'user': SYSADMIN_NAME} + + +def get_user_context(): + return {'model': model, 'user': USER_NAME} + + +@pytest.mark.usefixtures('clean_db', 'clean_index', 'setup_data') +class TestContactContributorView(object): + @mock.patch('ckanext.hdx_package.actions.get.hdx_mailer.mail_recipient') + def test_contact_contributor(self, mock_mail_recipient, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + package = { + 'package_creator': 'test function', + 'private': False, + 'dataset_date': '01/01/1960-12/31/2012', + 'caveats': 'These are the caveats', + 'license_other': 'TEST OTHER LICENSE', + 'methodology': 'This is a test methodology', + 'dataset_source': 'Test data', + 'license_id': 'hdx-other', + 'name': PUBLIC_DATASET_NAME, + 'notes': 'This is a public test dataset', + 'title': 'Public Test Dataset', + 'groups': [{'name': LOCATION_NAME}], + 'owner_org': ORGANIZATION_NAME, + 'maintainer': SYSADMIN_NAME, + 'data_update_frequency': '-1', + 'resources': [ + { + 'url': config.get('ckan.site_url', '') + '/storage/f/test_folder/test.csv', + 'resource_type': 'file.upload', + 'format': 'CSV', + 'name': 'test.csv' + } + ] + } + pkg_dict = get_action('package_create')(get_sysadmin_context(), package) + + contact_contributor_url = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name') or pkg_dict.get('id')) + + # GET request without user (anonymous access) + response = app.get(contact_contributor_url, headers={}) + assert '/sign-in/?info_message_type=contact-contributor' in response.request.url, \ + 'Anonymous users should be redirected to the sign-in page with the contact-contributor parameter' + assert CONST_SIGNIN['contact-contributor'] in response.body, 'Anonymous users should see the contact contributor info message' + + # GET request with regular user + response = app.get(contact_contributor_url, headers=auth_headers) + assert response.status_code == 200 + assert CONST_CONTACT_CONTRIBUTOR['PAGE_TITLE'] in response.body + assert CONST_CONTACT_CONTRIBUTOR['BODY_MAIN_TEXT'] in response.body + assert 'id="contact-contributor-form"' in response.body, 'The contact contributor form should be visible' + + assert f"'datasetName': '{PUBLIC_DATASET_NAME}'," in response.body, \ + 'The analytics info dict should contain dataset name' + + # POST request without user (anonymous access) + response = app.post(contact_contributor_url, data={}) + assert '/sign-in/?info_message_type=contact-contributor' in response.request.url, \ + 'Anonymous users should be redirected to the sign-in page with the contact-contributor parameter' + assert CONST_SIGNIN['contact-contributor'] in response.body, 'Anonymous users should see the contact contributor info message' + + # POST request with invalid email address & missing fields + response = app.post(contact_contributor_url, data={'email': 'invalid'}, headers=auth_headers) + assert response.status_code == 200 + assert 'Email invalid is not a valid format' in response.body, \ + 'There should be an error about invalid email format' + assert 'Missing value' in response.body, 'There should be errors about missing required values' + assert 'id="contact-contributor-form"' in response.body, 'The contact contributor form should be visible' + + # POST request with valid data + data_dict = { + 'topic': 'suggested edits', + 'fullname': 'John Doe', + 'email': 'hdx.feedback@gmail.com', + 'msg': 'my message', + 'pkg_id': pkg_dict.get('id'), + 'pkg_owner_org': pkg_dict.get('owner_org'), + 'pkg_title': pkg_dict.get('title'), + } + response = app.post(contact_contributor_url, data=data_dict, headers=auth_headers) + assert response.status_code == 200 + assert '

    {0}

    '.format(CONST_CONTACT_CONTRIBUTOR['PAGE_TITLE']) not in response.body + assert '

    {0}

    '.format( + CONST_CONTACT_CONTRIBUTOR['PAGE_TITLE_MESSAGE_SENT']) in response.body + assert '' in response.body, \ + 'The hidden input indicates analytics tracking for request submission' + assert (''.format(data_dict.get('topic')) in + response.body), 'The hidden input indicates analytics tracking message subject for request submission' + + def test_contact_contributor_request_data_dataset(self, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + package = { + 'package_creator': 'test function', + 'private': False, + 'dataset_date': '[1960-01-01 TO 2012-12-31]', + 'indicator': '0', + 'caveats': 'These are the caveats', + 'license_other': 'TEST OTHER LICENSE', + 'methodology': 'This is a test methodology', + 'dataset_source': 'Test data', + 'license_id': 'hdx-other', + 'name': REQUEST_DATA_DATASET_NAME, + 'notes': 'This is a contact contributor test', + 'title': 'Request Access Test Dataset', + 'groups': [{'name': LOCATION_NAME}], + 'owner_org': ORGANIZATION_NAME, + 'maintainer': SYSADMIN_NAME, + 'is_requestdata_type': True, + 'file_types': ['csv'], + 'field_names': ['field1', 'field2'] + } + pkg_dict = get_action('package_create')(get_sysadmin_context(), package) + + contact_contributor_url = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name') or pkg_dict.get('id')) + + # GET request on HDX Connect dataset + response = app.get(contact_contributor_url, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when accessing a HDX Connect dataset') + + # POST request on HDX Connect dataset + response = app.post(contact_contributor_url, data={}, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when doing a POST to a HDX Connect dataset') + + + def test_contact_contributor_non_existent_dataset(self, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + invalid_url = h.url_for('hdx_dataset.contact_contributor', id='invalid-dataset') + + # GET request on non-existent dataset + response = app.get(invalid_url, headers=auth_headers) + assert response.status_code == 404, 'A 404 error should be returned when accessing a non-existent dataset' + + # POST request on non-existent dataset + response = app.post(invalid_url, data={}, headers=auth_headers) + assert response.status_code == 404, 'A 404 error should be returned when doing a POST to a non-existent dataset' diff --git a/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_request_access_view.py b/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_request_access_view.py new file mode 100644 index 0000000000..79d8d1ebae --- /dev/null +++ b/ckanext-hdx_package/ckanext/hdx_package/tests/test_pages/test_request_access_view.py @@ -0,0 +1,233 @@ +import mock +import pytest +import ckan.model as model +import ckan.plugins.toolkit as tk +import ckan.tests.factories as factories +from ckanext.hdx_org_group.helpers.static_lists import ORGANIZATION_TYPE_LIST + +get_action = tk.get_action +config = tk.config +h = tk.h + +USER_NAME = 'test_user' +USER_EMAIL = 'test_user@test.org' +SYSADMIN_NAME = 'sysadmin_user' +SYSADMIN_EMAIL = 'sysadmin_user@test.org' +ORGANIZATION_NAME = 'test_organization' +LOCATION_NAME = 'test_location' +REQUEST_ACCESS_DATASET_NAME = 'request_access_test_dataset' +PUBLIC_DATASET_NAME = 'public_test_dataset' +PRIVATE_DATASET_NAME = 'private_test_dataset' +CONST_REQUEST_ACCESS = h.HDX_CONST('UI_CONSTANTS')['REQUEST_ACCESS'] +CONST_SIGNIN = h.HDX_CONST('UI_CONSTANTS')['SIGNIN'] + + +@pytest.fixture() +def setup_data(): + factories.Sysadmin(name=SYSADMIN_NAME, email=SYSADMIN_EMAIL, fullname='Sysadmin User') + factories.User(name=USER_NAME, email=USER_EMAIL, fullname='Test User') + factories.Organization( + name=ORGANIZATION_NAME, + title='Test Organization', + hdx_org_type=ORGANIZATION_TYPE_LIST[0][1], + org_url='https://hdx.hdxtest.org/' + ) + factories.Group(name=LOCATION_NAME) + + +def get_sysadmin_context(): + return {'model': model, 'user': SYSADMIN_NAME} + + +def get_user_context(): + return {'model': model, 'user': USER_NAME} + + +@pytest.mark.usefixtures('clean_db', 'clean_index', 'setup_data') +class TestRequestAccessView(object): + @mock.patch('ckanext.hdx_package.actions.get.hdx_mailer.mail_recipient') + def test_request_access(self, mock_mail_recipient, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + package = { + 'package_creator': 'test function', + 'private': False, + 'dataset_date': '[1960-01-01 TO 2012-12-31]', + 'indicator': '0', + 'caveats': 'These are the caveats', + 'license_other': 'TEST OTHER LICENSE', + 'methodology': 'This is a test methodology', + 'dataset_source': 'Test data', + 'license_id': 'hdx-other', + 'name': REQUEST_ACCESS_DATASET_NAME, + 'notes': 'This is a request access test', + 'title': 'Request Access Test Dataset', + 'groups': [{'name': LOCATION_NAME}], + 'owner_org': ORGANIZATION_NAME, + 'maintainer': SYSADMIN_NAME, + 'is_requestdata_type': True, + 'file_types': ['csv'], + 'field_names': ['field1', 'field2'] + } + pkg_dict = get_action('package_create')(get_sysadmin_context(), package) + + request_access_url = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name') or pkg_dict.get('id')) + + # GET request without user (anonymous access) + response = app.get(request_access_url, headers={}) + assert '/sign-in/?info_message_type=hdx-connect' in response.request.url, \ + 'Anonymous users should be redirected to the sign-in page with the hdx-connect parameter' + assert CONST_SIGNIN['hdx-connect'] in response.body, 'Anonymous users should see the HDX Connect info message' + + # GET request with regular user + response = app.get(request_access_url, headers=auth_headers) + assert response.status_code == 200 + assert CONST_REQUEST_ACCESS['PAGE_TITLE'] in response.body + assert CONST_REQUEST_ACCESS['BODY_MAIN_TEXT'] in response.body + assert 'id="request-access-form"' in response.body, 'The request access form should be visible' + + assert f"'datasetName': '{REQUEST_ACCESS_DATASET_NAME}'," in response.body, ('The analytics info dict should ' + 'contain dataset name') + assert "'datasetAvailability': 'metadata only'," in response.body, ('The analytics info dict should contain ' + 'dataset availability') + + # POST request without user (anonymous access) + response = app.post(request_access_url, data={}) + assert '/sign-in/?info_message_type=hdx-connect' in response.request.url, \ + 'Anonymous users should be redirected to the sign-in page with the hdx-connect parameter' + assert CONST_SIGNIN['hdx-connect'] in response.body, 'Anonymous users should see the HDX Connect info message' + + # POST request with invalid email address & missing fields + response = app.post(request_access_url, data={'email_address': 'invalid'}, headers=auth_headers) + assert response.status_code == 200 + assert 'Please provide a valid email address' in response.body, ('There should be an error about invalid email ' + 'format') + assert 'Missing value' in response.body, 'There should be errors about missing required values' + assert 'id="request-access-form"' in response.body, 'The request access form should be visible' + + # POST request with valid data + data_dict = { + 'package_id': pkg_dict.get('id'), + 'sender_name': 'John Doe', + 'message_content': 'I want to access additional data.', + 'organization': 'test_organization', + 'email_address': 'test@test.com', + 'sender_country': 'Sweden', + 'sender_organization_id': 'test_organization', + 'sender_organization_type': 'NGO', + 'sender_intend': 'other', + 'sender_intend_other': 'Testing Purposes', + } + response = app.post(request_access_url, data=data_dict, headers=auth_headers) + assert response.status_code == 200 + assert '

    {0}

    '.format(CONST_REQUEST_ACCESS['PAGE_TITLE']) not in response.body + assert '

    {0}

    '.format( + CONST_REQUEST_ACCESS['PAGE_TITLE_REQUEST_SENT']) in response.body + assert '' in response.body, \ + 'The hidden input indicates analytics tracking for request submission' + + # POST request with a pending request + response = app.post(request_access_url, data=data_dict, headers=auth_headers) + assert response.status_code == 200 + assert 'You already have a pending request. Please wait for the reply.' in response.body, \ + 'The same user cannot send another request when there is a pending one' + assert 'id="request-access-form"' not in response.body, ('The request access form should not be visible for ' + 'pending requests') + + def test_request_access_public_dataset(self, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + package = { + 'package_creator': 'test function', + 'private': False, + 'dataset_date': '01/01/1960-12/31/2012', + 'caveats': 'These are the caveats', + 'license_other': 'TEST OTHER LICENSE', + 'methodology': 'This is a test methodology', + 'dataset_source': 'Test data', + 'license_id': 'hdx-other', + 'name': PUBLIC_DATASET_NAME, + 'notes': 'This is a public test dataset', + 'title': 'Public Test Dataset', + 'groups': [{'name': LOCATION_NAME}], + 'owner_org': ORGANIZATION_NAME, + 'maintainer': SYSADMIN_NAME, + 'data_update_frequency': '-1', + 'resources': [ + { + 'url': config.get('ckan.site_url', '') + '/storage/f/test_folder/test.csv', + 'resource_type': 'file.upload', + 'format': 'CSV', + 'name': 'test.csv' + } + ] + } + pkg_dict = get_action('package_create')(get_sysadmin_context(), package) + + request_access_url = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name') or pkg_dict.get('id')) + + # GET request on public dataset + response = app.get(request_access_url, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when accessing a public dataset') + + # POST request on public dataset + response = app.post(request_access_url, data={}, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when doing a POST to a public dataset') + + def test_request_access_private_dataset(self, app): + sysadmin_token = factories.APIToken(user=SYSADMIN_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': sysadmin_token} + + package = { + 'package_creator': 'test function', + 'private': True, + 'dataset_date': '01/01/1960-12/31/2012', + 'caveats': 'These are the caveats', + 'license_other': 'TEST OTHER LICENSE', + 'methodology': 'This is a test methodology', + 'dataset_source': 'Test data', + 'license_id': 'hdx-other', + 'name': PRIVATE_DATASET_NAME, + 'notes': 'This is a private test dataset', + 'title': 'Private Test Dataset', + 'groups': [{'name': LOCATION_NAME}], + 'owner_org': ORGANIZATION_NAME, + 'maintainer': SYSADMIN_NAME, + 'data_update_frequency': '-1', + 'resources': [ + { + 'url': config.get('ckan.site_url', '') + '/storage/f/test_folder/test.csv', + 'resource_type': 'file.upload', + 'format': 'CSV', + 'name': 'test.csv' + } + ] + } + pkg_dict = get_action('package_create')(get_sysadmin_context(), package) + + request_access_url = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name') or pkg_dict.get('id')) + + # GET request on private dataset + response = app.get(request_access_url, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when accessing a private dataset') + + # POST request on private dataset + response = app.post(request_access_url, data={}, headers=auth_headers) + assert response.status_code == 404, ('A 404 error should be returned when doing a POST to a private dataset') + + + def test_request_access_non_existent_dataset(self, app): + user_token = factories.APIToken(user=USER_NAME, expires_in=2, unit=60 * 60)['token'] + auth_headers = {'Authorization': user_token} + + invalid_url = h.url_for('hdx_dataset.request_access', id='invalid-dataset') + + # GET request on non-existent dataset + response = app.get(invalid_url, headers=auth_headers) + assert response.status_code == 404, 'A 404 error should be returned when accessing a non-existent dataset' + + # POST request on non-existent dataset + response = app.post(invalid_url, data={}, headers=auth_headers) + assert response.status_code == 404, 'A 404 error should be returned when doing a POST to a non-existent dataset' diff --git a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py index 8155fff08e..b33a592174 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py +++ b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py @@ -561,6 +561,7 @@ def post(self, id: str) -> Union[Response, str]: u'auth_user_obj': g.userobj, } + data = errors = {} try: pkg_dict = get_action('package_show')(context, {'id': id}) @@ -599,7 +600,7 @@ def post(self, id: str) -> Union[Response, str]: return abort(404, _('Dataset not found')) except NotAuthorized: - came_from = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name')) + came_from = h.url_for('hdx_dataset.contact_contributor', id=id) return redirect(h.url_for('hdx_signin.login', info_message_type='contact-contributor', came_from=came_from)) except captcha.CaptchaError: @@ -657,7 +658,7 @@ def get(self, id: str, return abort(404, _('Dataset not found')) except NotAuthorized: - came_from = h.url_for('hdx_dataset.contact_contributor', id=pkg_dict.get('name')) + came_from = h.url_for('hdx_dataset.contact_contributor', id=id) return redirect(h.url_for('hdx_signin.login', info_message_type='contact-contributor', came_from=came_from)) @@ -671,6 +672,7 @@ def post(self, id: str) -> Union[Response, str]: u'auth_user_obj': g.userobj, } + data = errors = {} try: pkg_dict = get_action('package_show')(context, {'id': id}) @@ -681,7 +683,7 @@ def post(self, id: str) -> Union[Response, str]: pending_request = h.hdx_pending_request_data(g.userobj.id, pkg_dict.get('id')) if len(pending_request) > 0: - return redirect('hdx_dataset.request_access', id=pkg_dict.get('name')) + return redirect('hdx_dataset.request_access', id=id) dataset_request_access_logic = DatasetRequestAccessLogic(context, request) @@ -715,7 +717,7 @@ def post(self, id: str) -> Union[Response, str]: return abort(404, _('Dataset not found')) except NotAuthorized: - came_from = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name')) + came_from = h.url_for('hdx_dataset.request_access', id=id) return redirect(h.url_for('hdx_signin.login', info_message_type='hdx-connect', came_from=came_from)) except MailerException as e: @@ -777,7 +779,7 @@ def get(self, id: str, return abort(404, _('Dataset not found')) except NotAuthorized: - came_from = h.url_for('hdx_dataset.request_access', id=pkg_dict.get('name')) + came_from = h.url_for('hdx_dataset.request_access', id=id) return redirect(h.url_for('hdx_signin.login', info_message_type='hdx-connect', came_from=came_from)) diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html index 27b37443bd..82bb733899 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/package/contact_contributor.html @@ -59,8 +59,7 @@ {% endif %} -
    + {{ h.csrf_input() }} From 88ffbf9a2e4267c428277b51992106bc649b05b2 Mon Sep 17 00:00:00 2001 From: Catalin Date: Tue, 27 Aug 2024 19:05:44 +0300 Subject: [PATCH 5/5] HDX-9987 & HDX-9990 rename logic files --- ...ct_contributor.py => dataset_contact_contributor_logic.py} | 0 ...aset_request_access.py => dataset_request_access_logic.py} | 0 ckanext-hdx_package/ckanext/hdx_package/views/dataset.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename ckanext-hdx_package/ckanext/hdx_package/controller_logic/{dataset_contact_contributor.py => dataset_contact_contributor_logic.py} (100%) rename ckanext-hdx_package/ckanext/hdx_package/controller_logic/{dataset_request_access.py => dataset_request_access_logic.py} (100%) diff --git a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor_logic.py similarity index 100% rename from ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor.py rename to ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_contact_contributor_logic.py diff --git a/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py b/ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access_logic.py similarity index 100% rename from ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access.py rename to ckanext-hdx_package/ckanext/hdx_package/controller_logic/dataset_request_access_logic.py diff --git a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py index b33a592174..00d66b89ca 100644 --- a/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py +++ b/ckanext-hdx_package/ckanext/hdx_package/views/dataset.py @@ -24,8 +24,8 @@ import ckanext.hdx_package.helpers.membership_data as membership_data import ckanext.hdx_search.helpers.search_history as search_history import ckanext.hdx_package.controller_logic.dataset_view_logic as dataset_view_logic -from ckanext.hdx_package.controller_logic.dataset_contact_contributor import DatasetContactContributorLogic -from ckanext.hdx_package.controller_logic.dataset_request_access import DatasetRequestAccessLogic +from ckanext.hdx_package.controller_logic.dataset_contact_contributor_logic import DatasetContactContributorLogic +from ckanext.hdx_package.controller_logic.dataset_request_access_logic import DatasetRequestAccessLogic from ckan.views.dataset import _setup_template_variables