Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ckanext/unaids/assets/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,12 @@ float: right;
width: 100%;
}

/* Exempt toggles: width:100% stretches them to the full field width, pushing their label onto the next line. */
.dataset-form input[type="checkbox"],
.dataset-form input[type="radio"] {
width: auto;
}

.avenir-logo {
margin: -10px auto;
}
Expand Down
75 changes: 75 additions & 0 deletions ckanext/unaids/tests/test_user_edit_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Tests for the user edit form template."""
import re
import pytest
import ckan.plugins.toolkit as toolkit

from ckan.tests import factories

NOTIFICATIONS_INPUT = re.compile(
r'<input[^>]*name="activity_streams_email_notifications"[^>]*>'
)
SYSADMIN_PASSWORD_INPUT = re.compile(r'<input[^>]*name="old_password"[^>]*>')


def find_input(pattern, response, description):
match = pattern.search(response.body)
assert match, "{} is missing from the form".format(description)
return match.group(0)


def is_checked(input_tag):
# Matching the bare attribute avoids counting a future data-checked.
return re.search(r'(?<![-\w])checked(?![-\w])', input_tag) is not None


def user_edit_page(app, viewer, edited_user):
return app.get(
toolkit.url_for('user.edit', id=edited_user['name']),
extra_environ={'REMOTE_USER': viewer['name']}
)


@pytest.mark.ckan_config(
'ckan.plugins', 'activity ytp_request unaids pages scheming_datasets')
@pytest.mark.ckan_config('ckan.activity_streams_email_notifications', True)
@pytest.mark.usefixtures('with_plugins')
class TestUserEditForm(object):
def test_notifications_checkbox_reflects_the_edited_user(self, app):
# Reading this from the logged-in user instead of the edited one lets a
# sysadmin's save overwrite the other user's stored preference. Both
# directions are asserted so a hardcoded state cannot satisfy the test.
subscribed = factories.User(
activity_streams_email_notifications=True)
unsubscribed = factories.User(
activity_streams_email_notifications=False)
subscribed_sysadmin = factories.Sysadmin(
activity_streams_email_notifications=True)
unsubscribed_sysadmin = factories.Sysadmin(
activity_streams_email_notifications=False)

subscribed_page = user_edit_page(app, unsubscribed_sysadmin, subscribed)
unsubscribed_page = user_edit_page(
app, subscribed_sysadmin, unsubscribed)

assert is_checked(find_input(
NOTIFICATIONS_INPUT, subscribed_page, "notification checkbox"))
assert not is_checked(find_input(
NOTIFICATIONS_INPUT, unsubscribed_page, "notification checkbox"))

def test_sysadmin_password_shown_only_when_editing_someone_else(self, app):
sysadmin = factories.Sysadmin()
other_user = factories.User()

own_profile = user_edit_page(app, sysadmin, sysadmin)
other_profile = user_edit_page(app, sysadmin, other_user)

# A hidden or non-required field would leave cross-user saves unable to
# supply the password ckan/views/user.py demands.
password_input = find_input(
SYSADMIN_PASSWORD_INPUT, other_profile, "sysadmin password field")
assert 'type="password"' in password_input
assert 'required' in password_input

assert 'type="hidden"' in find_input(
SYSADMIN_PASSWORD_INPUT, own_profile, "old_password input")
assert 'field-password-old' not in own_profile.body
18 changes: 14 additions & 4 deletions ckanext/unaids/theme/templates/user/edit_user_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@

{{ form.markdown('about', label=_('About'), id='field-about', value=data.about, error=errors.about, placeholder=_('A little information about yourself')) }}

{% if show_email_notifications %}
{% call form.checkbox('activity_streams_email_notifications', label=_('Subscribe to notification emails'), id='field-activity-streams-email-notifications', value=True, checked=g.userobj.activity_streams_email_notifications) %}
{% if 'activity_show_email_notifications' in h and h.activity_show_email_notifications() %}
{% call form.checkbox('activity_streams_email_notifications', label=_('Subscribe to notification emails'), id='field-activity-streams-email-notifications', value=True, checked=data.activity_streams_email_notifications) %}
{% set helper_text = _("You will receive notification emails from {site_title}, e.g. when you have new activities on your dashboard."|string) %}
{{ form.info(helper_text.format(site_title=g.site_title), classes=['info-help-tight']) }}
{{ form.info(helper_text.format(site_title=g.site_title)) }}
{% endcall %}
{% endif %}

Expand All @@ -48,13 +48,23 @@

<div class="d-none">
<!-- Hidden inputs required for the CKAN flask view to work -->
<input type="hidden" name="old_password"/>
<input type="hidden" name="password1"/>
<input type="hidden" name="password2"/>
<input type="hidden" name="email" value="{{data.email}}" />
<input type="hidden" name="name" value="{{data.name}}" />
</div>

{% if is_sysadmin and current_user.name != data.name %}
<fieldset>
<legend>{{ _('Sysadmin password') }}</legend>
{# ckan/views/user.py treats email_changed as always True on a cross-user edit,
so it requires the acting sysadmin's own password to authorize the save. #}
{{ form.input('old_password', type='password', label=_('Sysadmin Password'), id='field-password-old', error=errors.oldpassword, classes=['control-medium'], attrs={'autocomplete': 'off', 'class': 'form-control', 'required': ''}, is_required=true) }}
</fieldset>
{% else %}
<input type="hidden" name="old_password"/>
{% endif %}

<div class="form-actions">
{% block delete_button %}
{% if h.check_access('user_delete', {'id': data.id}) %}
Expand Down