From 03cc5ccd3c669125f0dcc29d3687085f4cf0685b Mon Sep 17 00:00:00 2001 From: Catalin Date: Mon, 26 Aug 2024 14:11:31 +0300 Subject: [PATCH 1/2] 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']) %}