diff --git a/admin/institutions/urls.py b/admin/institutions/urls.py index 6aa5cf7e0df..2e260cf4f1b 100644 --- a/admin/institutions/urls.py +++ b/admin/institutions/urls.py @@ -20,5 +20,7 @@ name='list_and_add_admin'), re_path(r'^(?P[0-9]+)/remove_admins/$', views.InstitutionRemoveAdmin.as_view(), name='remove_admins'), + re_path(r'^(?P[0-9]+)/affiliations/$', views.InstitutionListAndAddAffiliation.as_view(), name='affiliations'), + re_path(r'^(?P[0-9]+)/remove_affiliations/$', views.InstitutionRemoveAffiliation.as_view(), name='remove_affiliations'), ] diff --git a/admin/institutions/views.py b/admin/institutions/views.py index 46e6a0a7745..536d916d937 100644 --- a/admin/institutions/views.py +++ b/admin/institutions/views.py @@ -373,3 +373,63 @@ def form_valid(self, form): def get_success_url(self): return reverse('institutions:register_metrics_admin', kwargs={'institution_id': self.kwargs['institution_id']}) + + +class InstitutionAffiliationBaseView(PermissionRequiredMixin, ListView): + permission_required = 'osf.change_institution' + template_name = 'institutions/edit_affiliations.html' + raise_exception = True + + def get_queryset(self): + return Institution.objects.get(id=self.kwargs['institution_id']) + + def get_context_data(self, **kwargs): + institution = Institution.objects.get(id=self.kwargs['institution_id']) + context = super().get_context_data(**kwargs) + context['institution'] = institution + context['affiliations'] = institution.get_institution_users() + return context + + +class InstitutionListAndAddAffiliation(InstitutionAffiliationBaseView): + + def get_permission_required(self): + if self.request.method == 'GET': + return ('osf.view_institution',) + return (self.permission_required,) + + def post(self, request, *args, **kwargs): + institution = Institution.objects.get(id=self.kwargs['institution_id']) + data = dict(request.POST) + del data['csrfmiddlewaretoken'] # just to remove the key from the form dict + + target_user = OSFUser.load(data['add-affiliation-form'][0]) + if target_user is None: + messages.error(request, f'User for guid: {data["add-affiliation-form"][0]} could not be found') + return redirect('institutions:affiliations', institution_id=institution.id) + + target_user.add_or_update_affiliated_institution(institution) + + messages.success(request, f'The following user was successfully added: {target_user.fullname} ({target_user.username})') + + return redirect('institutions:affiliations', institution_id=institution.id) + + +class InstitutionRemoveAffiliation(InstitutionAffiliationBaseView): + + def post(self, request, *args, **kwargs): + institution = Institution.objects.get(id=self.kwargs['institution_id']) + data = dict(request.POST) + del data['csrfmiddlewaretoken'] # just to remove the key from the form dict + + to_be_removed = list(data.keys()) + removed_affiliations = [user.replace('User-', '') for user in to_be_removed if 'User-' in user] + affiliated_users = OSFUser.objects.filter(id__in=removed_affiliations) + for user in affiliated_users: + user.remove_affiliated_institution(institution._id) + + if affiliated_users: + users_names = ' ,'.join(affiliated_users.values_list('fullname', flat=True)) + messages.success(request, f'The following users were successfully removed: {users_names}') + + return redirect('institutions:affiliations', institution_id=institution.id) diff --git a/admin/templates/institutions/detail.html b/admin/templates/institutions/detail.html index 2ce1ad20a03..8c4a9e79e15 100644 --- a/admin/templates/institutions/detail.html +++ b/admin/templates/institutions/detail.html @@ -24,11 +24,12 @@ Delete institution {% endif %} {% if perms.osf.change_institution %} - {% if institution.deactivated is None %} - Deactivate institution - {% else %} - Reactivate institution - {% endif %} + {% if institution.deactivated is None %} + Deactivate institution + {% else %} + Reactivate institution + {% endif %} + Affiliations {% endif %} {% if perms.osf.change_institution %} Manage Admins diff --git a/admin/templates/institutions/edit_affiliations.html b/admin/templates/institutions/edit_affiliations.html new file mode 100644 index 00000000000..e4e25cf1ac6 --- /dev/null +++ b/admin/templates/institutions/edit_affiliations.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% load static %} +{% load render_bundle from webpack_loader %} +{% block title %} + Institution Affiliations +{% endblock title %} +{% block content %} +
+
+ {% if messages %} +
    + {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+
+
+

{{ institution.name }}

+
+
+
+
+
+ {% csrf_token %} + + + +
+
+
+
+
+
+
+ {% csrf_token %} + + + + + {% for user in affiliations %} + + + + + + {% endfor %} +
NameUsername
{{ user.fullname }}{{ user.username }}
+ +
+
+
+
+{% endblock content %} \ No newline at end of file diff --git a/admin/templates/users/affiliated_institutions.html b/admin/templates/users/affiliated_institutions.html new file mode 100644 index 00000000000..2f80ff79350 --- /dev/null +++ b/admin/templates/users/affiliated_institutions.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% load static %} +{% load render_bundle from webpack_loader %} +{% block title %} + Affiliated Institutions +{% endblock title %} +{% block content %} +
+
+ {% if messages %} +
    + {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+
+
+

{{ institution.name }}

+
+
+
+
+
+ {% csrf_token %} + + + +
+
+
+
+
+
+
+ {% csrf_token %} + + + + + {% for institution in institutions %} + + + + + + {% endfor %} +
NameGuid
{{ institution.name }}{{ institution.guid }}
+ +
+
+
+
+{% endblock content %} \ No newline at end of file diff --git a/admin/templates/users/user.html b/admin/templates/users/user.html index 921900d01c8..12247735151 100644 --- a/admin/templates/users/user.html +++ b/admin/templates/users/user.html @@ -37,6 +37,9 @@ {% include "users/disable_user.html" with user=user %} {% include "users/mark_spam.html" with user=user %} {% include "users/reindex_user_elastic.html" with user=user %} + {% if perms.osf.change_institution %} + Affiliations + {% endif %} diff --git a/admin/users/urls.py b/admin/users/urls.py index 309ba6bcd35..408a901c567 100644 --- a/admin/users/urls.py +++ b/admin/users/urls.py @@ -28,4 +28,6 @@ name='reindex-elastic-user'), re_path(r'^(?P[a-z0-9]+)/merge_accounts/$', views.UserMergeAccounts.as_view(), name='merge-accounts'), re_path(r'^(?P[a-z0-9]+)/draft_registrations/$', views.UserDraftRegistrationsList.as_view(), name='draft-registrations'), + re_path(r'^(?P[a-z0-9]+)/affiliations/$', views.UserListAndAddAffiliations.as_view(), name='affiliations'), + re_path(r'^(?P[a-z0-9]+)/remove_affiliations/$', views.UserRemoveAffiliations.as_view(), name='remove_affiliations'), ] diff --git a/admin/users/views.py b/admin/users/views.py index bc4e550f88f..e5b9f6be5df 100644 --- a/admin/users/views.py +++ b/admin/users/views.py @@ -22,6 +22,7 @@ from osf.models.spam import SpamStatus from framework.auth import get_user from framework.auth.core import generate_verification_key +from osf.models.institution import Institution from website import search from website.settings import EXTERNAL_IDENTITY_PROFILE @@ -575,3 +576,60 @@ def get_context_data(self, **kwargs): 'draft_registrations': query_set } ) + + +class UserAffiliationBaseView(UserMixin, ListView): + permission_required = 'osf.change_institution' + template_name = 'users/affiliated_institutions.html' + raise_exception = True + + def get_queryset(self): + # Django template does not like attributes with underscores for some reason, so we annotate. + return self.get_object().get_affiliated_institutions().annotate( + guid=F('_id') + ) + + def get_context_data(self, **kwargs): + institutions = self.get_queryset() + context = super().get_context_data(**kwargs) + context['institutions'] = institutions + context['user'] = self.get_object() + return context + + +class UserRemoveAffiliations(UserAffiliationBaseView): + + def post(self, request, *args, **kwargs): + user = self.get_object() + data = dict(request.POST) + + to_be_removed = list(data.keys()) + removed_affiliations = [institution.replace('institution-', '') for institution in to_be_removed if 'institution-' in institution] + institutions_qs = Institution.objects.filter(id__in=removed_affiliations) + for institution in institutions_qs: + user.remove_affiliated_institution(institution._id) + + if institutions_qs: + institutions_names = ' ,'.join(institutions_qs.values_list('name', flat=True)) + messages.success(request, f'The following users were successfully removed: {institutions_names}') + + return redirect('users:affiliations', guid=user.guid) + + +class UserListAndAddAffiliations(UserAffiliationBaseView): + + def post(self, request, *args, **kwargs): + user = self.get_object() + data = dict(request.POST) + del data['csrfmiddlewaretoken'] # just to remove the key from the form dict + + institution = Institution.load(data['add-affiliation-form'][0]) + if institution is None: + messages.error(request, f'Institution for guid: {data["add-affiliation-form"][0]} could not be found') + return redirect('users:affiliations', guid=user.guid) + + user.add_or_update_affiliated_institution(institution) + + messages.success(request, f'The following institution was successfully added: {institution.name}') + + return redirect('users:affiliations', guid=user.guid)