diff --git a/scipost/constants.py b/scipost/constants.py index aa19e9b476d151d4400e2a14037b60c24b4a17b9..3f8ef98b446e7553635d2424c16d040dc986c2ea 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/forms.py b/scipost/forms.py index 21c68fe51c5aa3a94289857a942539a620b7757d..61677101d231d2acdf4d79f1666a498d3c53fc68 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User, Group from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse_lazy +from django.utils import timezone from django.utils.http import is_safe_url from django_countries import countries @@ -133,6 +134,41 @@ class DraftInvitationForm(forms.ModelForm): 'cited_in_submission', 'cited_in_publication' ] + def __init__(self, *args, **kwargs): + ''' + This form has a required keyword argument `current_user` which is used for validation of + the form fields. + ''' + self.current_user = kwargs.pop('current_user') + super().__init__(*args, **kwargs) + + def clean_email(self): + email = self.cleaned_data['email'] + if self.instance.id: + return email + + if RegistrationInvitation.objects.filter(email=email).exists(): + self.add_error('email', 'This email address has already been used for an invitation') + elif DraftInvitation.objects.filter(email=email).exists(): + self.add_error('email', ('This email address has already been' + ' used for a draft invitation')) + elif User.objects.filter(email=email).exists(): + self.add_error('email', 'This email address is already associated to a Contributor') + + return email + + def clean_invitation_type(self): + invitation_type = self.cleaned_data['invitation_type'] + if invitation_type == 'F' and not self.current_user.has_perm('scipost.can_invite_Fellows'): + self.add_error('invitation_type', ('You do not have the authorization' + ' to send a Fellow-type invitation.' + ' Consider Contributor, or cited (sub/pub).')) + if invitation_type == 'R': + self.add_error('invitation_type', ('Referee-type invitations must be made' + 'by the Editor-in-charge at the relevant' + ' Submission\'s Editorial Page.')) + return invitation_type + class RegistrationInvitationForm(forms.ModelForm): cited_in_submission = AutoCompleteSelectField('submissions_lookup', required=False) @@ -147,12 +183,17 @@ class RegistrationInvitationForm(forms.ModelForm): ] def __init__(self, *args, **kwargs): + ''' + This form has a required keyword argument `current_user` which is used for validation of + the form fields. + ''' + self.current_user = kwargs.pop('current_user') if kwargs.get('initial', {}).get('cited_in_submission', False): kwargs['initial']['cited_in_submission'] = kwargs['initial']['cited_in_submission'].id if kwargs.get('initial', {}).get('cited_in_publication', False): kwargs['initial']['cited_in_publication'] = kwargs['initial']['cited_in_publication'].id - super(RegistrationInvitationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['personal_message'].widget.attrs.update( {'placeholder': ('NOTE: a personal phrase or two.' ' The bulk of the text will be auto-generated.')}) @@ -161,6 +202,27 @@ class RegistrationInvitationForm(forms.ModelForm): queryset=Publication.objects.all().order_by('-publication_date'), required=False) + def clean_email(self): + email = self.cleaned_data['email'] + if RegistrationInvitation.objects.filter(email=email).exists(): + self.add_error('email', 'This email address has already been used for an invitation') + elif User.objects.filter(email=email).exists(): + self.add_error('email', 'This email address is already associated to a Contributor') + + return email + + def clean_invitation_type(self): + invitation_type = self.cleaned_data['invitation_type'] + if invitation_type == 'F' and not self.current_user.has_perm('scipost.can_invite_Fellows'): + self.add_error('invitation_type', ('You do not have the authorization' + ' to send a Fellow-type invitation.' + ' Consider Contributor, or cited (sub/pub).')) + if invitation_type == 'R': + self.add_error('invitation_type', ('Referee-type invitations must be made by the' + ' Editor-in-charge at the relevant Submission' + '\'s Editorial Page. ')) + return invitation_type + class ModifyPersonalMessageForm(forms.Form): personal_message = forms.CharField(widget=forms.Textarea()) @@ -314,10 +376,21 @@ class UnavailabilityPeriodForm(forms.ModelForm): fields = ['start', 'end'] def __init__(self, *args, **kwargs): - super(UnavailabilityPeriodForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['start'].widget.attrs.update({'placeholder': 'YYYY-MM-DD'}) self.fields['end'].widget.attrs.update({'placeholder': 'YYYY-MM-DD'}) + def clean_end(self): + now = timezone.now() + start = self.cleaned_data['start'] + end = self.cleaned_data['end'] + if start > end: + self.add_error('end', 'The start date you have entered is later than the end date.') + + if end < now.date(): + self.add_error('end', 'You have entered an end date in the past.') + return end + class RemarkForm(forms.Form): remark = forms.CharField(widget=forms.Textarea(), label='') diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 5dc95ea7613d475ba31725e485d11e650878639b..ef95e885debea981eff40c8f073b9cd336ad6148 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( @@ -189,6 +193,7 @@ class Command(BaseCommand): can_manage_registration_invitations, can_email_group_members, can_email_particulars, + can_resend_registration_requests, can_vet_registration_requests, can_vet_commentary_requests, can_vet_thesislink_requests, diff --git a/scipost/managers.py b/scipost/managers.py index 518ca5aaf34d411d1ab3cde65c4a5023b9a7b1df..cd9344ef5b99d97a7825d57016b24e9b6638e260 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/migrations/0056_auto_20170619_2049.py b/scipost/migrations/0056_auto_20170619_2049.py new file mode 100644 index 0000000000000000000000000000000000000000..fff00ef763559a8050171f9ee584cd5d851d1ed7 --- /dev/null +++ b/scipost/migrations/0056_auto_20170619_2049.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-06-19 18:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0055_auto_20170519_0937'), + ] + + operations = [ + migrations.AlterField( + model_name='draftinvitation', + name='date_drafted', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='draftinvitation', + name='first_name', + field=models.CharField(max_length=30), + ), + migrations.AlterField( + model_name='draftinvitation', + name='last_name', + field=models.CharField(max_length=30), + ), + ] diff --git a/scipost/models.py b/scipost/models.py index dd2bc50535f6bef22f63975dbebd01b167c19593..8c477ff6b07f1aea1fa6b5628f7bb7f9066e3636 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 @@ -161,7 +160,7 @@ class Contributor(models.Model): class UnavailabilityPeriod(models.Model): - contributor = models.ForeignKey(Contributor, on_delete=models.CASCADE) + contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) start = models.DateField() end = models.DateField() @@ -205,8 +204,8 @@ class DraftInvitation(models.Model): Draft of an invitation, filled in by an officer. """ title = models.CharField(max_length=4, choices=TITLE_CHOICES) - first_name = models.CharField(max_length=30, default='') - last_name = models.CharField(max_length=30, default='') + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) email = models.EmailField() invitation_type = models.CharField(max_length=2, choices=INVITATION_TYPE, default=INVITATION_CONTRIBUTOR) @@ -216,10 +215,9 @@ class DraftInvitation(models.Model): cited_in_publication = models.ForeignKey('journals.Publication', on_delete=models.CASCADE, blank=True, null=True) - drafted_by = models.ForeignKey(Contributor, - on_delete=models.CASCADE, + drafted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, blank=True, null=True) - date_drafted = models.DateTimeField(default=timezone.now) + date_drafted = models.DateTimeField(auto_now_add=True) processed = models.BooleanField(default=False) def __str__(self): diff --git a/scipost/templates/scipost/edit_draft_reg_inv.html b/scipost/templates/scipost/edit_draft_reg_inv.html index 14a601fcce56ce0a207a5f8fa1d1e500ac24a2b1..f6fd6afe34f7e90492f7e502306e8cc1e280bc1f 100644 --- a/scipost/templates/scipost/edit_draft_reg_inv.html +++ b/scipost/templates/scipost/edit_draft_reg_inv.html @@ -36,10 +36,7 @@ $(document).ready(function(){ <div class="row"> <div class="col-12"> <h1 class="highlight">Edit a draft registration invitation</h1> - {% if errormessage %} - <h3 class="text-danger">{{ errormessage }}</h3> - {% endif %} - <form action="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}" method="post"> + <form action="{% url 'scipost:edit_draft_reg_inv' draft_id=draft_inv_form.instance.id %}" method="post"> {% csrf_token %} {{draft_inv_form.media}} {{draft_inv_form|bootstrap}} diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 883f8dcdfa54f0006aa954ec0574c7fa7cf2763b..32fcb4a45dd902f6925bd7211eb46b5be3e1cece 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -158,16 +158,23 @@ </div> <div class="col-md-4 offset-md-1"> {% if unavailabilities %} - <h3>Your unavailability periods in our records (YYYY-DD-MM):</h3> - <table class="availabilities"> + <h3>Your unavailability periods in our records</h3> + <p class="text-muted">(YYYY-DD-MM)</p> + <table class="table"> <tr> <th>Start</th> - <th>End</th> + <th colspan="2">End</th> </tr> {% for unav in unavailabilities %} <tr> <td>{{ unav.start }}</td> <td>{{ unav.end }}</td> + <td> + <form action="{% url 'scipost:delete_unavailable_period' unav.id %}" method="post"> + {% csrf_token %} + <input class="btn btn-danger" type="submit" value="Delete" /> + </form> + </td> </tr> {% endfor %} </table> @@ -198,7 +205,9 @@ <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> + {% endif %} + {% if perms.scipost.can_resend_registration_requests %} + <li><a href="{% url 'scipost:registration_requests' %}">Awaiting validation</a> ({{ nr_reg_awaiting_validation }})</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 0000000000000000000000000000000000000000..3402c5b1ec0c224feacfe77ba7b6b2162e1ed9d3 --- /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/templates/scipost/unavailability_period_form.html b/scipost/templates/scipost/unavailability_period_form.html new file mode 100644 index 0000000000000000000000000000000000000000..fc4b11d94fad505d3e50293f9104bd09a166ad68 --- /dev/null +++ b/scipost/templates/scipost/unavailability_period_form.html @@ -0,0 +1,26 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Mark a period as unavailable</span> +{% endblock %} + +{% load bootstrap %} + +{% block pagetitle %}: Mark a period as unavailable{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-lg-8 offset-lg-2"> + <h1 class="highlight">Mark a period as unavailable</h1> + + <form method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input type="submit" class="btn btn-secondary" value="Submit" /> + </form> + </div> +</div> + +{% endblock content %} diff --git a/scipost/urls.py b/scipost/urls.py index ac5f3ab49fd370739874923df4f9d863bb3268a0..8eaf7b40ed5126af0d0397149b1081e65ac4c964 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$', @@ -133,11 +136,13 @@ urlpatterns = [ url(r'^update_personal_data$', views.update_personal_data, name='update_personal_data'), # Unavailabilities - url(r'^mark_unavailable_period$', views.mark_unavailable_period, - name='mark_unavailable_period'), + url(r'^unavailable_period$', views.mark_unavailable_period, name='mark_unavailable_period'), + url(r'^unavailable_period/(?P<period_id>[0-9]+)/delete$', views.delete_unavailable_period, + name='delete_unavailable_period'), # Contributor info - url(r'^contributor/(?P<contributor_id>[0-9]+)$', views.contributor_info, name="contributor_info"), + url(r'^contributor/(?P<contributor_id>[0-9]+)$', views.contributor_info, + name="contributor_info"), # Authorship claims url(r'^claim_authorships$', views.claim_authorships, name="claim_authorships"), diff --git a/scipost/views.py b/scipost/views.py index 793e442f16126d790ef3f314231df94731121af0..229123403a324cc62918110819e7349e0045dc0b 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -7,7 +7,6 @@ from django.contrib.auth import login, logout, update_session_auth_hash from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group from django.contrib.auth.views import password_reset, password_reset_confirm -from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core import mail from django.core.mail import EmailMessage, EmailMultiAlternatives from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @@ -15,6 +14,7 @@ from django.core.urlresolvers import reverse from django.db.models import Q from django.shortcuts import redirect from django.template import Context, Template +from django.views.decorators.http import require_POST from django.views.generic.list import ListView from django.db.models import Prefetch @@ -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,38 @@ 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() + messages.success(request, ('New key successfully generated and sent to <i>%s</i>' + % contributor.user.email)) + return redirect(reverse('scipost:registration_requests')) + + @permission_required('scipost.can_draft_registration_invitations', return_403=True) def draft_registration_invitation(request): """ @@ -392,36 +425,13 @@ def draft_registration_invitation(request): This is similar to the registration_invitations method, which is used to complete the invitation process. """ - errormessage = '' - if request.method == 'POST': - draft_inv_form = DraftInvitationForm(request.POST) - Utils.load({'contributor': request.user.contributor, 'form': draft_inv_form}) - if draft_inv_form.is_valid(): - if Utils.email_already_invited(): - errormessage = ('DUPLICATE ERROR: ' - 'This email address has already been used for an invitation') - elif Utils.email_already_drafted(): - errormessage = ('DUPLICATE ERROR: ' - 'This email address has already been used for a draft invitation') - elif Utils.email_already_taken(): - errormessage = ('DUPLICATE ERROR: ' - 'This email address is already associated to a Contributor') - elif (draft_inv_form.cleaned_data['invitation_type'] == 'F' - and not request.user.has_perm('scipost.can_invite_Fellows')): - errormessage = ('You do not have the authorization to send a Fellow-type ' - 'invitation. Consider Contributor, or cited (sub/pub). ') - elif (draft_inv_form.cleaned_data['invitation_type'] == 'R'): - errormessage = ('Referee-type invitations must be made by the Editor-in-charge ' - 'at the relevant Submission\'s Editorial Page. ') - else: - Utils.create_draft_invitation() - messages.success(request, 'Draft invitation saved.') - return redirect(reverse('scipost:draft_registration_invitation')) - else: - errormessage = 'The form was not filled validly.' - - else: - draft_inv_form = DraftInvitationForm() + draft_inv_form = DraftInvitationForm(request.POST or None, current_user=request.user) + if draft_inv_form.is_valid(): + invitation = draft_inv_form.save(commit=False) + invitation.drafted_by = request.user.contributor + invitation.save() + messages.success(request, 'Draft invitation saved.') + return redirect(reverse('scipost:draft_registration_invitation')) sent_reg_inv = RegistrationInvitation.objects.filter(responded=False, declined=False) sent_reg_inv_fellows = sent_reg_inv.filter(invitation_type='F').order_by('last_name') @@ -445,7 +455,7 @@ def draft_registration_invitation(request): existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name') context = { - 'draft_inv_form': draft_inv_form, 'errormessage': errormessage, + 'draft_inv_form': draft_inv_form, 'sent_reg_inv_fellows': sent_reg_inv_fellows, 'sent_reg_inv_contrib': sent_reg_inv_contrib, 'sent_reg_inv_ref': sent_reg_inv_ref, @@ -466,23 +476,15 @@ def draft_registration_invitation(request): @permission_required('scipost.can_manage_registration_invitations', return_403=True) def edit_draft_reg_inv(request, draft_id): draft = get_object_or_404(DraftInvitation, id=draft_id) - errormessage = '' - if request.method == 'POST': - draft_inv_form = DraftInvitationForm(request.POST) - if draft_inv_form.is_valid(): - draft.title = draft_inv_form.cleaned_data['title'] - draft.first_name = draft_inv_form.cleaned_data['first_name'] - draft.last_name = draft_inv_form.cleaned_data['last_name'] - draft.email = draft_inv_form.cleaned_data['email'] - draft.save() - return redirect(reverse('scipost:registration_invitations')) - else: - errormessage = 'The form is invalidly filled' - else: - draft_inv_form = DraftInvitationForm(instance=draft) - context = {'draft_inv_form': draft_inv_form, - 'draft': draft, - 'errormessage': errormessage, } + + draft_inv_form = DraftInvitationForm(request.POST or None, current_user=request.user, + instance=draft) + if draft_inv_form.is_valid(): + draft = draft_inv_form.save() + messages.success(request, 'Draft invitation saved.') + return redirect(reverse('scipost:registration_invitations')) + + context = {'draft_inv_form': draft_inv_form} return render(request, 'scipost/edit_draft_reg_inv.html', context) @@ -511,62 +513,38 @@ def map_draft_reg_inv_to_contributor(request, draft_id, contributor_id): def registration_invitations(request, draft_id=None): """ Overview and tools for administrators """ # List invitations sent; send new ones - errormessage = '' associated_contributors = None - if request.method == 'POST': - # Send invitation from form information - reg_inv_form = RegistrationInvitationForm(request.POST) - Utils.load({'contributor': request.user.contributor, 'form': reg_inv_form}) - if reg_inv_form.is_valid(): - if Utils.email_already_invited(): - errormessage = ('DUPLICATE ERROR: ' - 'This email address has already been used for an invitation') - elif Utils.email_already_taken(): - errormessage = ('DUPLICATE ERROR: ' - 'This email address is already associated to a Contributor') - elif (reg_inv_form.cleaned_data['invitation_type'] == 'F' - and not request.user.has_perm('scipost.can_invite_Fellows')): - errormessage = ('You do not have the authorization to send a Fellow-type ' - 'invitation. Consider Contributor, or cited (sub/pub). ') - elif (reg_inv_form.cleaned_data['invitation_type'] == 'R'): - errormessage = ('Referee-type invitations must be made by the Editor-in-charge ' - 'at the relevant Submission\'s Editorial Page. ') - else: - Utils.create_invitation() - Utils.send_registration_invitation_email() - try: - draft = DraftInvitation.objects.get( - email=reg_inv_form.cleaned_data['email']) - draft.processed = True - draft.save() - except ObjectDoesNotExist: - pass - except MultipleObjectsReturned: - # Delete the first invitation - draft_to_delete = RegistrationInvitation.objects.filter( - email=reg_inv_form.cleaned_data['email']).first() - draft_to_delete.delete() - messages.success(request, 'Registration Invitation sent') - return redirect(reverse('scipost:registration_invitations')) - else: - errormessage = 'The form was not filled validly.' + initial = {} + if draft_id: + # Fill draft data if draft_id given + draft = get_object_or_404(DraftInvitation, id=draft_id) + associated_contributors = Contributor.objects.filter( + user__last_name__icontains=draft.last_name) + initial = { + 'title': draft.title, + 'first_name': draft.first_name, + 'last_name': draft.last_name, + 'email': draft.email, + 'invitation_type': draft.invitation_type, + 'cited_in_submission': draft.cited_in_submission, + 'cited_in_publication': draft.cited_in_publication, + } - else: - initial = {} - if draft_id: - draft = get_object_or_404(DraftInvitation, id=draft_id) - associated_contributors = Contributor.objects.filter( - user__last_name__icontains=draft.last_name) - initial = { - 'title': draft.title, - 'first_name': draft.first_name, - 'last_name': draft.last_name, - 'email': draft.email, - 'invitation_type': draft.invitation_type, - 'cited_in_submission': draft.cited_in_submission, - 'cited_in_publication': draft.cited_in_publication, - } - reg_inv_form = RegistrationInvitationForm(initial=initial) + # Send invitation from form information + reg_inv_form = RegistrationInvitationForm(request.POST or None, initial=initial, + current_user=request.user) + if reg_inv_form.is_valid(): + invitation = reg_inv_form.save(commit=False) + invitation.invited_by = request.user.contributor + invitation.save() + + Utils.load({'invitation': invitation}) + Utils.send_registration_invitation_email() + (DraftInvitation.objects.filter(email=reg_inv_form.cleaned_data['email']) + .update(processed=True)) + + messages.success(request, 'Registration Invitation sent') + return redirect(reverse('scipost:registration_invitations')) sent_reg_inv = RegistrationInvitation.objects.filter(responded=False, declined=False) sent_reg_inv_fellows = sent_reg_inv.filter(invitation_type='F').order_by('last_name') @@ -590,7 +568,7 @@ def registration_invitations(request, draft_id=None): existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name') context = { - 'reg_inv_form': reg_inv_form, 'errormessage': errormessage, + 'reg_inv_form': reg_inv_form, 'sent_reg_inv_fellows': sent_reg_inv_fellows, 'sent_reg_inv_contrib': sent_reg_inv_contrib, 'sent_reg_inv_ref': sent_reg_inv_ref, @@ -758,28 +736,34 @@ def logout_view(request): return redirect(reverse('scipost:index')) +@login_required def mark_unavailable_period(request): - if request.method == 'POST': - unav_form = UnavailabilityPeriodForm(request.POST) - errormessage = None - if unav_form.is_valid(): - now = timezone.now() - if unav_form.cleaned_data['start'] > unav_form.cleaned_data['end']: - errormessage = 'The start date you have entered is later than the end date.' - elif unav_form.cleaned_data['end'] < now.date(): - errormessage = 'You have entered an end date in the past.' - if errormessage is not None: - return render(request, 'scipost/error.html', - context={'errormessage': errormessage}) - else: - unav = UnavailabilityPeriod( - contributor=request.user.contributor, - start=unav_form.cleaned_data['start'], - end=unav_form.cleaned_data['end']) - unav.save() - else: - errormessage = 'Please enter valid dates (format: YYYY-MM-DD).' - return render(request, 'scipost/error.html', context={'errormessage': errormessage}) + ''' + Mark period unavailable for Contributor using this view. + ''' + unav_form = UnavailabilityPeriodForm(request.POST or None) + if unav_form.is_valid(): + unav = unav_form.save(commit=False) + unav.contributor = request.user.contributor + unav.save() + messages.success(request, 'Unavailability period registered') + return redirect('scipost:personal_page') + + # Template acts as a backup in case the form is invalid. + context = {'form': unav_form} + return render(request, 'scipost/unavailability_period_form.html', context) + + +@require_POST +@login_required +def delete_unavailable_period(request, period_id): + ''' + Delete period unavailable registered. + ''' + unav = get_object_or_404(UnavailabilityPeriod, + contributor=request.user.contributor, id=int(period_id)) + unav.delete() + messages.success(request, 'Unavailability period deleted') return redirect('scipost:personal_page') @@ -807,9 +791,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()