Skip to content

Commit f56e0a7

Browse files
[ENG-9025] Add the ability to remove moderators/admins from products (Registrations, Preprints, Collections, Institutions) in admin (#11373)
* Refactor provider admin/moderator management * add preprint provider admin/moderator tests
1 parent 478d2af commit f56e0a7

File tree

6 files changed

+183
-56
lines changed

6 files changed

+183
-56
lines changed

admin/preprint_providers/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
re_path(r'^(?P<preprint_provider_id>[a-z0-9]+)/delete/$', views.DeletePreprintProvider.as_view(), name='delete'),
1919
re_path(r'^(?P<preprint_provider_id>[a-z0-9]+)/export/$', views.ExportPreprintProvider.as_view(), name='export'),
2020
re_path(r'^(?P<preprint_provider_id>[a-z0-9]+)/share_source/$', views.ShareSourcePreprintProvider.as_view(), name='share_source'),
21-
re_path(r'^(?P<preprint_provider_id>[a-z0-9]+)/register/$', views.PreprintProviderRegisterModeratorOrAdmin.as_view(), name='register_moderator_admin'),
2221
re_path(r'^(?P<preprint_provider_id>[a-z0-9]+)/edit/$', views.PreprintProviderChangeForm.as_view(), name='edit'),
22+
re_path(r'^(?P<provider_id>[a-z0-9]+)/add_admin_or_moderator/$', views.PreprintAddAdminOrModerator.as_view(), name='add_admin_or_moderator'),
23+
re_path(r'^(?P<provider_id>[a-z0-9]+)/remove_admins_and_moderators/$', views.PreprintRemoveAdminsAndModerators.as_view(), name='remove_admins_and_moderators'),
2324
]

admin/preprint_providers/views.py

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import json
22

33

4-
from django.http import Http404
54
from django.core import serializers
65
from django.core.exceptions import ValidationError
7-
from django.urls import reverse_lazy, reverse
6+
from django.urls import reverse_lazy
87
from django.http import HttpResponse, JsonResponse
98
from django.views.generic import ListView, DetailView, View, CreateView, DeleteView, TemplateView, UpdateView
10-
from django.views.generic.edit import FormView
119
from django.contrib import messages
1210
from django.contrib.auth.mixins import PermissionRequiredMixin
1311
from django.forms.models import model_to_dict
1412
from django.shortcuts import redirect, render
15-
from django.utils.functional import cached_property
1613

1714
from admin.base import settings
1815
from admin.base.forms import ImportFileForm
19-
from admin.preprint_providers.forms import PreprintProviderForm, PreprintProviderCustomTaxonomyForm, PreprintProviderRegisterModeratorOrAdminForm
20-
from osf.models import PreprintProvider, Subject, OSFUser, RegistrationProvider, CollectionProvider
16+
from admin.preprint_providers.forms import PreprintProviderForm, PreprintProviderCustomTaxonomyForm
17+
from osf.models import PreprintProvider, Subject, RegistrationProvider, CollectionProvider
2118
from osf.models.provider import rules_to_subjects, WhitelistedSHAREPreprintProvider
2219
from website import settings as website_settings
20+
from admin.providers.views import AddAdminOrModerator, RemoveAdminsAndModerators
2321

2422
FIELDS_TO_NOT_IMPORT_EXPORT = ['access_token', 'share_source', 'subjects_acceptable', 'primary_collection']
2523

@@ -454,43 +452,17 @@ def get(self, request):
454452
return render(request, self.template_name, {'share_api_url': share_api_url, 'api_v2_url': api_v2_url})
455453

456454

457-
class PreprintProviderRegisterModeratorOrAdmin(PermissionRequiredMixin, FormView):
455+
class PreprintAddAdminOrModerator(AddAdminOrModerator):
458456
permission_required = 'osf.change_preprintprovider'
457+
template_name = 'preprint_providers/edit_moderators.html'
458+
provider_class = PreprintProvider
459+
url_namespace = 'preprint_providers'
459460
raise_exception = True
460-
template_name = 'preprint_providers/register_moderator_admin.html'
461-
form_class = PreprintProviderRegisterModeratorOrAdminForm
462-
463-
@cached_property
464-
def target_provider(self):
465-
return PreprintProvider.objects.get(id=self.kwargs['preprint_provider_id'])
466-
467-
def get_form_kwargs(self):
468-
kwargs = super().get_form_kwargs()
469-
kwargs['provider_groups'] = self.target_provider.group_objects
470-
return kwargs
471-
472-
def get_context_data(self, **kwargs):
473-
context = super().get_context_data(**kwargs)
474-
context['provider_name'] = self.target_provider.name
475-
return context
476-
477-
def form_valid(self, form):
478-
user_id = form.cleaned_data.get('user_id')
479-
osf_user = OSFUser.load(user_id)
480-
481-
if not osf_user:
482-
raise Http404(f'OSF user with id "{user_id}" not found. Please double check.')
483-
484-
if osf_user.has_groups(self.target_provider.group_names):
485-
messages.error(self.request, f'User with guid: {user_id} is already a moderator or admin')
486-
return super().form_invalid(form)
487461

488-
group = form.cleaned_data.get('group_perms')
489-
self.target_provider.add_to_group(osf_user, group)
490-
osf_user.save()
491462

492-
messages.success(self.request, f'Permissions update successful for OSF User {osf_user.username}!')
493-
return super().form_valid(form)
494-
495-
def get_success_url(self):
496-
return reverse('preprint_providers:register_moderator_admin', kwargs={'preprint_provider_id': self.kwargs['preprint_provider_id']})
463+
class PreprintRemoveAdminsAndModerators(RemoveAdminsAndModerators):
464+
permission_required = 'osf.change_preprintprovider'
465+
template_name = 'preprint_providers/edit_moderators.html'
466+
provider_class = PreprintProvider
467+
url_namespace = 'preprint_providers'
468+
raise_exception = True

admin/templates/institutions/detail.html

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
<a class="btn btn-danger" href={% url 'institutions:delete' institution.id %}>Delete institution</a>
2525
{% endif %}
2626
{% if perms.osf.change_institution %}
27-
{% if institution.deactivated is None %}
28-
<a class="btn btn-danger" href={% url 'institutions:deactivate' institution.id %}>Deactivate institution</a>
29-
{% else %}
30-
<a class="btn btn-danger" href={% url 'institutions:reactivate' institution.id %}>Reactivate institution</a>
31-
{% endif %}
27+
{% if institution.deactivated is None %}
28+
<a class="btn btn-danger" href={% url 'institutions:deactivate' institution.id %}>Deactivate institution</a>
29+
{% else %}
30+
<a class="btn btn-danger" href={% url 'institutions:reactivate' institution.id %}>Reactivate institution</a>
31+
{% endif %}
3232
{% endif %}
33+
{% if perms.osf.change_institution %}
34+
<a class="btn btn-primary" href={% url 'institutions:list_and_add_admin' institution.id %}>Manage Admins</a>
35+
{% endif %}
3336
</div>
3437
</div>
3538

@@ -64,12 +67,6 @@ <h4>Banner:</h4>
6467
<button id="show-modify-form" class="btn btn-link" type="button">
6568
Modify Institution
6669
</button>
67-
<div class="col-md-12">
68-
<a class="btn btn-link" href={% url 'institutions:register_metrics_admin' institution.id %}>Create Moderator/Admin</a>
69-
</div>
70-
<div class="col-md-12">
71-
<a class="btn btn-link" href={% url 'institutions:list_and_add_admin' institution.id %}>Contributors list</a>
72-
</div>
7370
{% endif %}
7471
</div>
7572
</div>

admin/templates/preprint_providers/detail.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<a class="btn btn-primary" href={% url 'preprint_providers:share_source' preprint_provider.id %}>Setup Share Source</a>
3737
{% endif %}
3838
{% if perms.osf.change_preprintprovider %}
39-
<a class="btn btn-primary" href={% url 'preprint_providers:register_moderator_admin' preprint_provider.id %}>Register Moderator/Admin</a>
39+
<a class="btn btn-primary" href={% url 'preprint_providers:add_admin_or_moderator' preprint_provider.id %}>Manage Admins and Moderators</a>
4040
<a class="btn btn-primary" href={% url 'preprint_providers:edit' preprint_provider.id %}>Modify Preprint Provider</a>
4141
{% if show_taxonomies %}
4242
<a class="btn btn-primary" href={% url 'preprint_providers:process_custom_taxonomy' preprint_provider.id %}>Modify Custom Taxonomy</a>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{% extends "base.html" %}
2+
{% load static %}
3+
{% load render_bundle from webpack_loader %}
4+
{% block title %}
5+
<title>Preprint Provider</title>
6+
{% endblock title %}
7+
{% block content %}
8+
<div class="container-fluid">
9+
<div class="row">
10+
{% if messages %}
11+
<ul>
12+
{% for message in messages %}
13+
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
14+
{% endfor %}
15+
</ul>
16+
{% endif %}
17+
</div>
18+
<div class="row">
19+
<div class="col-md-12 text-center">
20+
<h2>{{ provider.name }}</h2>
21+
</div>
22+
</div>
23+
<div class="row">
24+
<div class="col-md-12">
25+
<form id="add-moderators-form" action="{% url 'preprint_providers:add_admin_or_moderator' provider.id %}" method="post">
26+
{% csrf_token %}
27+
<label>Add moderator by guid: </label>
28+
<input type="text" name="add-moderators-form">
29+
<input type="submit" name="mod" value="Add Moderator" class="form-button btn btn-success">
30+
<input type="submit" name="admin" value="Add Admin" class="form-button btn btn-success">
31+
</form>
32+
</div>
33+
</div>
34+
<hr>
35+
<div class="row">
36+
<div class="col-md-12">
37+
<form id="remove-moderators-form" action="{% url 'preprint_providers:remove_admins_and_moderators' provider.id %}" method="post">
38+
{% csrf_token %}
39+
<table class="table table-striped">
40+
<th></th>
41+
<th>Name</th>
42+
<th>Type</th>
43+
{% for moderator in moderators %}
44+
<tr>
45+
<td><input type='checkbox' name="Moderator-{{moderator.id}}"></td>
46+
<td>{{ moderator.fullname }} ({{moderator.username}})</td>
47+
<td>Moderator</td>
48+
</tr>
49+
{% endfor %}
50+
{% for admin in admins %}
51+
<tr>
52+
<td><input type='checkbox' name="Admin-{{admin.id}}"></td>
53+
<td>{{ admin.fullname }} ({{admin.username}})</td>
54+
<td>Admin</td>
55+
</tr>
56+
{% endfor %}
57+
</table>
58+
<input class="form-button btn btn-danger" type="submit" value="Remove Moderators/Admins" />
59+
</form>
60+
</div>
61+
</div>
62+
</div>
63+
{% endblock content %}

admin_tests/preprint_providers/test_views.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from admin.preprint_providers import views
3030
from admin.preprint_providers.forms import PreprintProviderForm
3131
from admin.base.forms import ImportFileForm
32-
32+
from django.contrib.messages.storage.fallback import FallbackStorage
3333
import website
3434

3535
pytestmark = pytest.mark.django_db
@@ -424,3 +424,97 @@ def provider_factory(self):
424424
def view(self, req):
425425
plain_view = views.ProcessCustomTaxonomy()
426426
return setup_view(plain_view, req)
427+
428+
429+
from osf.migrations import update_provider_auth_groups
430+
431+
@pytest.mark.urls('admin.base.urls')
432+
class TestEditModerators:
433+
434+
@pytest.fixture()
435+
def req(self, user):
436+
req = RequestFactory().get('/fake_path')
437+
req.user = user
438+
return req
439+
440+
@pytest.fixture()
441+
def provider(self):
442+
provider = PreprintProviderFactory()
443+
update_provider_auth_groups()
444+
return provider
445+
446+
@pytest.fixture()
447+
def moderator(self, provider):
448+
user = AuthUserFactory()
449+
provider.add_to_group(user, 'moderator')
450+
return user
451+
452+
@pytest.fixture()
453+
def user(self):
454+
return AuthUserFactory()
455+
456+
@pytest.fixture()
457+
def remove_moderator_view(self, req, provider):
458+
view = views.PreprintRemoveAdminsAndModerators()
459+
view = setup_view(view, req)
460+
view.kwargs = {'provider_id': provider.id}
461+
return view
462+
463+
@pytest.fixture()
464+
def add_moderator_view(self, req, provider):
465+
view = views.PreprintAddAdminOrModerator()
466+
view = setup_view(view, req)
467+
view.kwargs = {'provider_id': provider.id}
468+
return view
469+
470+
def test_get(self, add_moderator_view, remove_moderator_view, req):
471+
res = add_moderator_view.get(req)
472+
assert res.status_code == 200
473+
474+
res = remove_moderator_view.get(req)
475+
assert res.status_code == 200
476+
477+
def test_post_remove(self, remove_moderator_view, req, moderator, provider):
478+
moderator_id = f'Moderator-{moderator.id}'
479+
req.POST = {
480+
'csrfmiddlewaretoken': 'fake csfr',
481+
moderator_id: ['on']
482+
}
483+
484+
# django.contrib.messages has a bug which effects unittests
485+
# more info here -> https://code.djangoproject.com/ticket/17971
486+
setattr(req, 'session', 'session')
487+
messages = FallbackStorage(req)
488+
setattr(req, '_messages', messages)
489+
490+
res = remove_moderator_view.post(req)
491+
assert res.status_code == 302
492+
assert not provider.get_group('moderator').user_set.all()
493+
494+
def test_post_add(self, add_moderator_view, req, user, provider):
495+
req.POST = {
496+
'csrfmiddlewaretoken': 'fake csfr',
497+
'add-moderators-form': [user._id],
498+
'moderator': ['Add Moderator']
499+
}
500+
501+
# django.contrib.messages has a bug which effects unittests
502+
# more info here -> https://code.djangoproject.com/ticket/17971
503+
setattr(req, 'session', 'session')
504+
messages = FallbackStorage(req)
505+
setattr(req, '_messages', messages)
506+
507+
res = add_moderator_view.post(req)
508+
assert res.status_code == 302
509+
assert user in provider.get_group('moderator').user_set.all()
510+
511+
# try to add the same user, but another group
512+
req.POST = {
513+
'csrfmiddlewaretoken': 'fake csfr',
514+
'add-moderators-form': [user._id],
515+
'admin': ['Add Admin']
516+
}
517+
res = add_moderator_view.post(req)
518+
assert res.status_code == 302
519+
assert user in provider.get_group('moderator').user_set.all()
520+
assert user not in provider.get_group('admin').user_set.all()

0 commit comments

Comments
 (0)