From 696c1e14cdd47627c12f8485c423ab452b2d77e5 Mon Sep 17 00:00:00 2001 From: Jorran de Wit <jorrandewit@outlook.com> Date: Mon, 19 Jun 2017 21:56:22 +0200 Subject: [PATCH] Improve registration vetting Editorial Administrators are able to reset and resend the verification key to a person who requested a key. All registrations are shown, so also old ones. Should this view be extended to have some kind of cleanup function to periodically remove (specific) old registrations? --- scipost/constants.py | 3 +- .../commands/add_groups_and_permissions.py | 5 ++ scipost/managers.py | 5 +- scipost/models.py | 1 - scipost/templates/scipost/personal_page.html | 8 ++- .../scipost/registration_requests.html | 67 +++++++++++++++++++ scipost/urls.py | 3 + scipost/views.py | 37 +++++++++- 8 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 scipost/templates/scipost/registration_requests.html diff --git a/scipost/constants.py b/scipost/constants.py index aa19e9b47..3f8ef98b4 100644 --- a/scipost/constants.py +++ b/scipost/constants.py @@ -124,6 +124,7 @@ subject_areas_dict = {} for k in subject_areas_raw_dict.keys(): subject_areas_dict.update(dict(subject_areas_raw_dict[k])) +CONTRIBUTOR_NEWLY_REGISTERED = 0 CONTRIBUTOR_NORMAL = 1 CONTRIBUTOR_STATUS = ( # status determine the type of Contributor: @@ -135,7 +136,7 @@ CONTRIBUTOR_STATUS = ( # -2: other account already exists for this person # -3: barred from SciPost (abusive behaviour) # -4: disabled account (deceased) - (0, 'newly registered'), + (CONTRIBUTOR_NEWLY_REGISTERED, 'newly registered'), (CONTRIBUTOR_NORMAL, 'normal user'), (-1, 'not a professional scientist'), (-2, 'other account already exists'), diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 5dc95ea76..9889c3a6f 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -61,6 +61,10 @@ class Command(BaseCommand): codename='can_invite_Fellows', name='Can invite Fellows', content_type=content_type) + can_resend_registration_requests, created = Permission.objects.get_or_create( + codename='can_resend_registration_requests', + name='Can resend registration activation emails', + content_type=content_type) # Communications can_email_group_members, created = Permission.objects.get_or_create( @@ -215,6 +219,7 @@ class Command(BaseCommand): can_view_production, can_publish_accepted_submission, can_attend_VGMs, + can_resend_registration_requests, ]) EditorialCollege.permissions.set([ can_view_pool, diff --git a/scipost/managers.py b/scipost/managers.py index 518ca5aaf..cd9344ef5 100644 --- a/scipost/managers.py +++ b/scipost/managers.py @@ -3,7 +3,7 @@ import datetime from django.db import models from django.db.models import Q -from .constants import CONTRIBUTOR_NORMAL +from .constants import CONTRIBUTOR_NORMAL, CONTRIBUTOR_NEWLY_REGISTERED class FellowManager(models.Manager): @@ -20,3 +20,6 @@ class FellowManager(models.Manager): class ContributorManager(models.Manager): def active(self): return self.filter(user__is_active=True, status=CONTRIBUTOR_NORMAL) + + def awaiting_validation(self): + return self.filter(user__is_active=False, status=CONTRIBUTOR_NEWLY_REGISTERED) diff --git a/scipost/models.py b/scipost/models.py index 9bd5f46d8..8c477ff6b 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -103,7 +103,6 @@ class Contributor(models.Model): salt = self.user.username.encode('utf8') self.activation_key = hashlib.sha1(salt+salt).hexdigest() self.key_expires = datetime.datetime.now() + datetime.timedelta(days=2) - self.save() def discipline_as_string(self): # Redundant, to be removed in future diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 7956a555a..5468435a3 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -205,7 +205,13 @@ <ul> {% if perms.scipost.can_vet_registration_requests %} <li><a href="{% url 'scipost:vet_registration_requests' %}">Vet Registration requests</a> ({{ nr_reg_to_vet }})</li> - <li>Awaiting validation ({{ nr_reg_awaiting_validation }}) (no action necessary)</li> + <li> + {% if perms.scipost.can_resend_registration_requests %} + <a href="{% url 'scipost:registration_requests' %}">Awaiting validation</a> ({{ nr_reg_awaiting_validation }}) + {% else %} + Awaiting validation ({{ nr_reg_awaiting_validation }}) + {% endif %} + </li> {% endif %} {% if perms.scipost.can_draft_registration_invitations %} <li><a href="{% url 'scipost:draft_registration_invitation' %}">Draft a Registration Invitation</a></li> diff --git a/scipost/templates/scipost/registration_requests.html b/scipost/templates/scipost/registration_requests.html new file mode 100644 index 000000000..3402c5b1e --- /dev/null +++ b/scipost/templates/scipost/registration_requests.html @@ -0,0 +1,67 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: registration awaiting validation{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Registration awaiting validation</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Registration awaiting validation</h1> + <p> + These Contributors did not yet activate their account. Sometimes, this link is never clicked on (email is either lost to spam, or not received).<br> + As per this page, you are able to send a reminder email to the as-yet-unconfirmed contributor. + </p> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <table class="table"> + <thead> + <tr> + <th>Name</th> + <th>Email</th> + <th>Date requested</th> + <th>Key expires</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for contributor in unactive_contributors %} + <tr> + <td>{{contributor.user.first_name}} {{contributor.user.last_name}}</td> + <td>{{contributor.user.email}}</td> + <td>{{contributor.user.date_joined|timesince}} ago</td> + <td> + {% if contributor.key_expires < now %} + <span class="text-danger">Expired {{contributor.key_expires|timesince}} ago</span> + {% else %} + Expires in {{contributor.key_expires|timeuntil}} + {% endif %} + </td> + <td> + <form action="{% url 'scipost:registration_requests_reset' contributor.id %}" method="post"> + {% csrf_token %} + <input type="submit" class="btn btn-warning" value="Reset and resend" /> + </form> + </td> + </tr> + {% empty %} + <tr> + <td colspan="5">All registrations have been activated.</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + +{% endblock content %} diff --git a/scipost/urls.py b/scipost/urls.py index 307e602b5..8eaf7b40e 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -84,6 +84,9 @@ urlpatterns = [ views.vet_registration_requests, name='vet_registration_requests'), url(r'^vet_registration_request_ack/(?P<contributor_id>[0-9]+)$', views.vet_registration_request_ack, name='vet_registration_request_ack'), + url(r'^registration_requests$', views.registration_requests, name="registration_requests"), + url(r'^registration_requests/(?P<contributor_id>[0-9]+)/reset$', + views.registration_requests_reset, name="registration_requests_reset"), url(r'^registration_invitations/(?P<draft_id>[0-9]+)$', views.registration_invitations, name="registration_invitations_from_draft"), url(r'^registration_invitations$', diff --git a/scipost/views.py b/scipost/views.py index d7b30e9a4..0b7425b9e 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -278,6 +278,7 @@ def request_new_activation_link(request, contributor_id, key): if request.GET.get('confirm', False): # Generate a new email activation key and link contributor.generate_key() + contributor.save() Utils.load({'contributor': contributor}, request) Utils.send_new_activation_link_email() @@ -385,6 +386,36 @@ def vet_registration_request_ack(request, contributor_id): return redirect(reverse('scipost:vet_registration_requests')) +@permission_required('scipost.can_resend_registration_requests', return_403=True) +def registration_requests(request): + ''' + List all inactive users. These are users that have filled the registration form, + but did not yet activate their account using the validation email. + ''' + unactive_contributors = (Contributor.objects.awaiting_validation() + .prefetch_related('user') + .order_by('-key_expires')) + context = { + 'unactive_contributors': unactive_contributors, + 'now': timezone.now() + } + return render(request, 'scipost/registration_requests.html', context) + + +@require_POST +@permission_required('scipost.can_resend_registration_requests', return_403=True) +def registration_requests_reset(request, contributor_id): + ''' + Reset specific activation_key for Contributor and resend activation mail. + ''' + contributor = get_object_or_404(Contributor.objects.awaiting_validation(), id=contributor_id) + contributor.generate_key() + contributor.save() + Utils.load({'contributor': contributor}, request) + Utils.send_new_activation_link_email() + return redirect(reverse('scipost:registration_requests')) + + @permission_required('scipost.can_draft_registration_invitations', return_403=True) def draft_registration_invitation(request): """ @@ -758,9 +789,9 @@ def personal_page(request): # count the number of pending registration requests nr_reg_to_vet = Contributor.objects.filter(user__is_active=True, status=0).count() - nr_reg_awaiting_validation = Contributor.objects.filter( - user__is_active=False, key_expires__gte=now, - key_expires__lte=intwodays, status=0).count() + nr_reg_awaiting_validation = (Contributor.objects.awaiting_validation() + # .filter(key_expires__gte=now, key_expires__lte=intwodays) + .count()) nr_submissions_to_assign = Submission.objects.filter(status__in=['unassigned']).count() nr_recommendations_to_prepare_for_voting = EICRecommendation.objects.filter( submission__status__in=['voting_in_preparation']).count() -- GitLab