Skip to content

Commit 0feb801

Browse files
Add affiliation management for institutions and users
1 parent ed441b1 commit 0feb801

File tree

8 files changed

+236
-0
lines changed

8 files changed

+236
-0
lines changed

admin/institutions/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@
2020
name='list_and_add_admin'),
2121
re_path(r'^(?P<institution_id>[0-9]+)/remove_admins/$', views.InstitutionRemoveAdmin.as_view(),
2222
name='remove_admins'),
23+
re_path(r'^(?P<institution_id>[0-9]+)/affiliations/$', views.InstitutionListAndAddAffiliation.as_view(), name='affiliations'),
24+
re_path(r'^(?P<institution_id>[0-9]+)/remove_affiliations/$', views.InstitutionRemoveAffiliation.as_view(), name='remove_affiliations'),
2325

2426
]

admin/institutions/views.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,63 @@ def form_valid(self, form):
373373

374374
def get_success_url(self):
375375
return reverse('institutions:register_metrics_admin', kwargs={'institution_id': self.kwargs['institution_id']})
376+
377+
378+
class InstitutionAffiliationBaseView(PermissionRequiredMixin, ListView):
379+
permission_required = 'osf.change_institution'
380+
template_name = 'institutions/edit_affiliations.html'
381+
raise_exception = True
382+
383+
def get_queryset(self):
384+
return Institution.objects.get(id=self.kwargs['institution_id'])
385+
386+
def get_context_data(self, **kwargs):
387+
institution = Institution.objects.get(id=self.kwargs['institution_id'])
388+
context = super().get_context_data(**kwargs)
389+
context['institution'] = institution
390+
context['affiliations'] = institution.get_institution_users()
391+
return context
392+
393+
394+
class InstitutionListAndAddAffiliation(InstitutionAffiliationBaseView):
395+
396+
def get_permission_required(self):
397+
if self.request.method == 'GET':
398+
return ('osf.view_institution',)
399+
return (self.permission_required,)
400+
401+
def post(self, request, *args, **kwargs):
402+
institution = Institution.objects.get(id=self.kwargs['institution_id'])
403+
data = dict(request.POST)
404+
del data['csrfmiddlewaretoken'] # just to remove the key from the form dict
405+
406+
target_user = OSFUser.load(data['add-affiliation-form'][0])
407+
if target_user is None:
408+
messages.error(request, f'User for guid: {data["add-affiliation-form"][0]} could not be found')
409+
return redirect('institutions:affiliations', institution_id=institution.id)
410+
411+
target_user.add_or_update_affiliated_institution(institution)
412+
413+
messages.success(request, f'The following user was successfully added: {target_user.fullname} ({target_user.username})')
414+
415+
return redirect('institutions:affiliations', institution_id=institution.id)
416+
417+
418+
class InstitutionRemoveAffiliation(InstitutionAffiliationBaseView):
419+
420+
def post(self, request, *args, **kwargs):
421+
institution = Institution.objects.get(id=self.kwargs['institution_id'])
422+
data = dict(request.POST)
423+
del data['csrfmiddlewaretoken'] # just to remove the key from the form dict
424+
425+
to_be_removed = list(data.keys())
426+
removed_affiliations = [user.replace('User-', '') for user in to_be_removed if 'User-' in user]
427+
affiliated_users = OSFUser.objects.filter(id__in=removed_affiliations)
428+
for user in affiliated_users:
429+
user.remove_affiliated_institution(institution._id)
430+
431+
if affiliated_users:
432+
users_names = ' ,'.join(affiliated_users.values_list('fullname', flat=True))
433+
messages.success(request, f'The following users were successfully removed: {users_names}')
434+
435+
return redirect('institutions:affiliations', institution_id=institution.id)

admin/templates/institutions/detail.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
{% else %}
3030
<a class="btn btn-danger" href={% url 'institutions:reactivate' institution.id %}>Reactivate institution</a>
3131
{% endif %}
32+
<a class="btn btn-primary" href={% url 'institutions:affiliations' institution.id %}>Affiliations</a>
3233
{% endif %}
3334
</div>
3435
</div>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{% extends "base.html" %}
2+
{% load static %}
3+
{% load render_bundle from webpack_loader %}
4+
{% block title %}
5+
<title>Institution Affiliations</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>{{ institution.name }}</h2>
21+
</div>
22+
</div>
23+
<div class="row">
24+
<div class="col-md-12">
25+
<form id="add-affiliation-form" action="{% url 'institutions:affiliations' institution.id %}" method="post">
26+
{% csrf_token %}
27+
<label>Add user by guid: </label>
28+
<input type="text" name="add-affiliation-form">
29+
<input type="submit" name="user" value="Add User" class="form-button btn btn-success">
30+
</form>
31+
</div>
32+
</div>
33+
<hr>
34+
<div class="row">
35+
<div class="col-md-12">
36+
<form id="remove-affiliation-form" action="{% url 'institutions:remove_affiliations' institution.id %}" method="post">
37+
{% csrf_token %}
38+
<table class="table table-striped">
39+
<th></th>
40+
<th>Name</th>
41+
<th>Username</th>
42+
{% for user in affiliations %}
43+
<tr>
44+
<td><input type='checkbox' name="User-{{user.id}}"></td>
45+
<td>{{ user.fullname }}</td>
46+
<td>{{ user.username }}</td>
47+
</tr>
48+
{% endfor %}
49+
</table>
50+
<input class="form-button btn btn-danger" type="submit" value="Remove affiliation" />
51+
</form>
52+
</div>
53+
</div>
54+
</div>
55+
{% endblock content %}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{% extends "base.html" %}
2+
{% load static %}
3+
{% load render_bundle from webpack_loader %}
4+
{% block title %}
5+
<title>Affiliated Institutions</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>{{ institution.name }}</h2>
21+
</div>
22+
</div>
23+
<div class="row">
24+
<div class="col-md-12">
25+
<form id="add-affiliation-form" action="{% url 'users:affiliations' guid=user.guid %}" method="post">
26+
{% csrf_token %}
27+
<label>Add Institution by guid: </label>
28+
<input type="text" name="add-affiliation-form">
29+
<input type="submit" name="user" value="Add Institution" class="form-button btn btn-success">
30+
</form>
31+
</div>
32+
</div>
33+
<hr>
34+
<div class="row">
35+
<div class="col-md-12">
36+
<form id="remove-affiliation-form" action="{% url 'users:remove_affiliations' guid=user.guid %}" method="post">
37+
{% csrf_token %}
38+
<table class="table table-striped">
39+
<th></th>
40+
<th>Name</th>
41+
<th>Guid</th>
42+
{% for institution in institutions %}
43+
<tr>
44+
<td><input type='checkbox' name="institution-{{institution.id}}"></td>
45+
<td>{{ institution.name }}</td>
46+
<td>{{ institution.guid }}</td>
47+
</tr>
48+
{% endfor %}
49+
</table>
50+
<input class="form-button btn btn-danger" type="submit" value="Remove affiliation" />
51+
</form>
52+
</div>
53+
</div>
54+
</div>
55+
{% endblock content %}

admin/templates/users/user.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
{% include "users/disable_user.html" with user=user %}
3838
{% include "users/mark_spam.html" with user=user %}
3939
{% include "users/reindex_user_elastic.html" with user=user %}
40+
{% if perms.osf.change_institution %}
41+
<a class="btn btn-primary" href="{% url 'users:affiliations' guid=user.guid %}">Affiliations</a>
42+
{% endif %}
4043
</div>
4144
</div>
4245
</div>

admin/users/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@
2727
name='reindex-elastic-user'),
2828
re_path(r'^(?P<guid>[a-z0-9]+)/merge_accounts/$', views.UserMergeAccounts.as_view(), name='merge-accounts'),
2929
re_path(r'^(?P<guid>[a-z0-9]+)/draft_registrations/$', views.UserDraftRegistrationsList.as_view(), name='draft-registrations'),
30+
re_path(r'^(?P<guid>[a-z0-9]+)/affiliations/$', views.UserListAndAddAffiliations.as_view(), name='affiliations'),
31+
re_path(r'^(?P<guid>[a-z0-9]+)/remove_affiliations/$', views.UserRemoveAffiliations.as_view(), name='remove_affiliations'),
3032
]

admin/users/views.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from osf.models.spam import SpamStatus
2323
from framework.auth import get_user
2424
from framework.auth.core import generate_verification_key
25+
from osf.models.institution import Institution
2526

2627
from website import search
2728
from website.settings import EXTERNAL_IDENTITY_PROFILE
@@ -579,3 +580,60 @@ def get_context_data(self, **kwargs):
579580
'draft_registrations': query_set
580581
}
581582
)
583+
584+
585+
class UserAffiliationBaseView(UserMixin, ListView):
586+
permission_required = 'osf.change_institution'
587+
template_name = 'users/affiliated_institutions.html'
588+
raise_exception = True
589+
590+
def get_queryset(self):
591+
# Django template does not like attributes with underscores for some reason, so we annotate.
592+
return self.get_object().get_affiliated_institutions().annotate(
593+
guid=F('_id')
594+
)
595+
596+
def get_context_data(self, **kwargs):
597+
institutions = self.get_queryset()
598+
context = super().get_context_data(**kwargs)
599+
context['institutions'] = institutions
600+
context['user'] = self.get_object()
601+
return context
602+
603+
604+
class UserRemoveAffiliations(UserAffiliationBaseView):
605+
606+
def post(self, request, *args, **kwargs):
607+
user = self.get_object()
608+
data = dict(request.POST)
609+
610+
to_be_removed = list(data.keys())
611+
removed_affiliations = [institution.replace('institution-', '') for institution in to_be_removed if 'institution-' in institution]
612+
institutions_qs = Institution.objects.filter(id__in=removed_affiliations)
613+
for institution in institutions_qs:
614+
user.remove_affiliated_institution(institution._id)
615+
616+
if institutions_qs:
617+
institutions_names = ' ,'.join(institutions_qs.values_list('name', flat=True))
618+
messages.success(request, f'The following users were successfully removed: {institutions_names}')
619+
620+
return redirect('users:affiliations', guid=user.guid)
621+
622+
623+
class UserListAndAddAffiliations(UserAffiliationBaseView):
624+
625+
def post(self, request, *args, **kwargs):
626+
user = self.get_object()
627+
data = dict(request.POST)
628+
del data['csrfmiddlewaretoken'] # just to remove the key from the form dict
629+
630+
institution = Institution.load(data['add-affiliation-form'][0])
631+
if institution is None:
632+
messages.error(request, f'Institution for guid: {data["add-affiliation-form"][0]} could not be found')
633+
return redirect('users:affiliations', guid=user.guid)
634+
635+
user.add_or_update_affiliated_institution(institution)
636+
637+
messages.success(request, f'The following institution was successfully added: {institution.name}')
638+
639+
return redirect('users:affiliations', guid=user.guid)

0 commit comments

Comments
 (0)