Skip to content

Commit

Permalink
Fix mozilla#2896, mozilla#3131 - Profile Status (mozilla#3152)
Browse files Browse the repository at this point in the history
Fix mozilla#2896 - Add a banner to the profile's avatar with the message "Account disabled by an administrator" if the user.is_active = False.

Fix mozilla#3131 - Ads the ability for admins to enable/disable users from their profiles.
  • Loading branch information
cedricium authored Apr 2, 2024
1 parent a24a365 commit 0c7943f
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 0 deletions.
39 changes: 39 additions & 0 deletions pontoon/contributors/static/css/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ h2 {
border-color: var(--dark-grey-1);
}

.avatar .status {
box-sizing: border-box;
display: flex;
width: 100%;
height: 24px;
align-items: center;
justify-content: center;
gap: 4px;
position: absolute;
bottom: 3px;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
background: var(--dark-grey-1);
color: var(--light-grey-6);
}

.avatar .status span {
font-size: 12px;
font-weight: bold;
}

.display-name {
font-size: 24px;
padding-bottom: 0;
Expand Down Expand Up @@ -424,3 +445,21 @@ svg.js-calendar-graph-svg {
color: var(--light-grey-2);
float: right;
}

#account-status-form {
margin-top: 8px;
}

#account-status-form button {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
border: none;
border-radius: 3px;
color: var(--light-grey-7);
}

#account-status-form button:hover {
color: var(--white-1);
}
16 changes: 16 additions & 0 deletions pontoon/contributors/static/js/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,19 @@ $('body').on('click', '#insights .chart-group-navigation li', function () {
// Show the selected graph view
$('.chart-group').css('marginLeft', -index * itemWidth);
});

$('#account-status-form').on('submit', function (event) {
event.preventDefault();

const form = $(this);
$.ajax({
url: form.attr('action'),
type: 'POST',
data: {
csrfmiddlewaretoken: $('body').data('csrf'),
},
success: function () {
location.reload();
},
});
});
18 changes: 18 additions & 0 deletions pontoon/contributors/templates/contributors/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@
{% set translator_for_locales = contributor.translator_for_locales %}
{% set manager_for_locales = contributor.manager_for_locales %}
{% set user_is_translator = user.translated_locales %}
{% set profile_is_disabled = contributor.is_active == False %}

<a class="avatar" href="{% if is_my_profile %}https://gravatar.com/{% endif %}">
{% if is_my_profile %}
<div class="desc">Update profile picture</div>
{% endif %}
{% if profile_is_disabled %}
<div class="status">
<i class="fa fa-exclamation-circle fa-sm"></i>
<span>Account disabled by an administrator</span>
</div>
{% endif %}
<img class="rounded" src="{{ contributor.gravatar_url(512) }}" width="256" height="256">
</a>

Expand Down Expand Up @@ -97,6 +104,17 @@ <h2 class="display-name">{{ contributor.first_name }}</h2>
<a class="button" href="{{ url('pontoon.contributors.settings') }}">Change Settings</a>
</div>
{% endif %}
{% set is_admin = (user.is_authenticated and user.is_superuser) %}
{% if is_admin and not is_my_profile %}
<form id="account-status-form" action="{{ url('pontoon.contributors.toggle_active_user_status', username=contributor.username) }}" method="POST">
{% csrf_token %}
{% if contributor.is_active %}
<button class="button" type="submit"><i class="icon fa fa-lock fa-sm"></i>Disable</button>
{% else %}
<button class="button" type="submit"><i class="icon fa fa-unlock fa-sm"></i>Enable</button>
{% endif %}
</form>
{% endif %}
</div>

{% if is_name_visible or profile.bio or is_email_visible or are_external_accounts_visible or is_my_profile %}
Expand Down
38 changes: 38 additions & 0 deletions pontoon/contributors/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,41 @@ def test_given_period(member, mock_contributors_render):
):
member.client.get("/contributors/?period=6")
assert mock_contributors_render.call_args[0][0]["period"] == 6


@pytest.mark.django_db
def test_toggle_active_user_status(client_superuser, user_a):
url = reverse(
"pontoon.contributors.toggle_active_user_status", args=[user_a.username]
)

# request on active user --> user disabled
assert user_a.is_active is True
response = client_superuser.post(url, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
assert response.status_code == 200, User.objects.get(pk=user_a).is_active is False

# request on disabled user --> user enabled
response = client_superuser.post(url, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
assert response.status_code == 200, User.objects.get(pk=user_a).is_active is True


@pytest.mark.django_db
def test_toggle_active_user_status_user_not_found(client_superuser):
url = reverse("pontoon.contributors.toggle_active_user_status", args=["unknown"])
response = client_superuser.post(url, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
assert response.status_code == 404


@pytest.mark.django_db
def test_toggle_active_user_status_requires_admin(member, admin, client_superuser):
url = reverse(
"pontoon.contributors.toggle_active_user_status", args=[member.user.username]
)

member.client.force_login(member.user)
response = member.client.post(url, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
assert response.status_code == 403

member.client.force_login(admin)
response = client_superuser.post(url, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
assert response.status_code == 200
6 changes: 6 additions & 0 deletions pontoon/contributors/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,10 @@ class UsernameConverter(StringConverter):
views.update_contribution_timeline,
name="pontoon.contributors.update_contribution_timeline",
),
# AJAX: Toggle user account status (i.e. `is_active`)
path(
"toggle-active-user-status/<username:username>/",
views.toggle_active_user_status,
name="pontoon.contributors.toggle_active_user_status",
),
]
20 changes: 20 additions & 0 deletions pontoon/contributors/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,26 @@ def mark_all_notifications_as_read(request):
return JsonResponse({"status": True})


@login_required(redirect_field_name="", login_url="/403")
@require_POST
@transaction.atomic
def toggle_active_user_status(request, username):
# only admins are authorized to (dis|en)able users
if not request.user.is_superuser:
return JsonResponse(
{
"status": False,
"message": "Forbidden: You don't have permission to change user active status.",
},
status=403,
)

user = get_object_or_404(User, username=username)
user.is_active = not user.is_active
user.save(update_fields=["is_active"])
return JsonResponse({"status": True})


class ContributorsMixin:
def contributors_filter(self, **kwargs):
"""
Expand Down

0 comments on commit 0c7943f

Please sign in to comment.