From 2fcde919179bf786c3bf3678923679264e0bf6b1 Mon Sep 17 00:00:00 2001 From: "J.-S. Caux" <J.S.Caux@uva.nl> Date: Sat, 26 Mar 2016 16:44:00 +0100 Subject: [PATCH] Introduce RegistrationInvitation facility, email message not yet cleaned --- scipost/admin.py | 4 +- scipost/forms.py | 5 ++ scipost/models.py | 22 +++++++ .../scipost/accept_invitation_error.html | 14 +++++ scipost/templates/scipost/personal_page.html | 1 + .../scipost/registration_invitation_sent.html | 14 +++++ .../scipost/registration_invitations.html | 57 +++++++++++++++++++ scipost/urls.py | 16 ++++-- scipost/utils.py | 36 ++++++++++++ scipost/views.py | 39 +++++++++++++ 10 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 scipost/templates/scipost/accept_invitation_error.html create mode 100644 scipost/templates/scipost/registration_invitation_sent.html create mode 100644 scipost/templates/scipost/registration_invitations.html diff --git a/scipost/admin.py b/scipost/admin.py index f15a05577..29e7c8cce 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -3,7 +3,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User -from scipost.models import Contributor, AuthorshipClaim#, Opinion +from scipost.models import * class ContributorInline(admin.StackedInline): #class ContributorInline(admin.TabularInline): @@ -18,6 +18,8 @@ class UserAdmin(UserAdmin): admin.site.unregister(User) admin.site.register(User, UserAdmin) +admin.site.register(RegistrationInvitation) + #admin.site.register(Contributor) admin.site.register(AuthorshipClaim) diff --git a/scipost/forms.py b/scipost/forms.py index 4fb1de02f..d97a7ae13 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -34,6 +34,11 @@ class RegistrationForm(forms.Form): captcha = CaptchaField(label='* I am not a robot') +class RegistrationInvitationForm(forms.ModelForm): + class Meta: + model = RegistrationInvitation + fields = ['title', 'first_name', 'last_name', 'email_address', 'invitation_type'] + class UpdateUserDataForm(forms.ModelForm): class Meta: model = User diff --git a/scipost/models.py b/scipost/models.py index fefa59e3a..2343c113e 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -94,6 +94,28 @@ class Contributor(models.Model): output += '</table>' return output + +INVITATION_TYPE = ( + ('F', 'Editorial Fellow'), + ('C', 'Contributor'), + ) + +class RegistrationInvitation(models.Model): + """ + Invitation to particular persons for registration + """ + 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='') + email_address = models.EmailField() + invitation_type = models.CharField(max_length=2, choices=INVITATION_TYPE, default='C') + invitation_key = models.CharField(max_length=40, default='') + key_expires = models.DateTimeField(default=timezone.now) + date_sent = models.DateTimeField(default=timezone.now) + responded = models.BooleanField(default=False) + + + AUTHORSHIP_CLAIM_STATUS = ( (1, 'accepted'), (0, 'not yet vetted (pending)'), diff --git a/scipost/templates/scipost/accept_invitation_error.html b/scipost/templates/scipost/accept_invitation_error.html new file mode 100644 index 000000000..22929ee0a --- /dev/null +++ b/scipost/templates/scipost/accept_invitation_error.html @@ -0,0 +1,14 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: accept invitation: error{% endblock pagetitle %} + +{% block bodysup %} + +<section> + <h1>Registration Invitation: error</h1> + + <p>Error message: {{ errormessage }}</p> + +</section> + +{% endblock bodysup %} diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index adfd1c9a8..a2ff2f57c 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -66,6 +66,7 @@ <ul> <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><a href="{% url 'scipost:registration_invitations' %}">Manage Registration invitations</a></li> </ul> </div> {% endif %} diff --git a/scipost/templates/scipost/registration_invitation_sent.html b/scipost/templates/scipost/registration_invitation_sent.html new file mode 100644 index 000000000..b58b41fbd --- /dev/null +++ b/scipost/templates/scipost/registration_invitation_sent.html @@ -0,0 +1,14 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: registration invitation sent{% endblock pagetitle %} + +{% block bodysup %} + +<section> + <h1>Registration Invitation sent</h1> + + <p>Return to the <a href="{% url 'scipost:registration_invitations' %}">registration invitations page</a>.</p> + +</section> + +{% endblock bodysup %} diff --git a/scipost/templates/scipost/registration_invitations.html b/scipost/templates/scipost/registration_invitations.html new file mode 100644 index 000000000..196e75e65 --- /dev/null +++ b/scipost/templates/scipost/registration_invitations.html @@ -0,0 +1,57 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: registration invitations{% endblock pagetitle %} + +{% block bodysup %} + +<section> + <div class="flex-greybox"> + <h1>Registration Invitations</h1> + </div> + + <div class="flex-greybox"> + <h2>Send a new invitation:</h2> + <form action="{% url 'scipost:registration_invitations' %}" method="post"> + {% csrf_token %} + <table> + {{ reg_inv_form.as_table }} + </table> + <input type="submit" value="Submit" /> + </form> + </div> + + <div class="flex-greybox"> + <h2>Invitations sent:</h2> + + <h3>Editorial Fellows</h3> + <table> + <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type (Fellow, Contrib)</td><td>Responded</td></tr> + {% for fellow in sent_reg_inv_fellows %} + <tr> + <td>{{ fellow.last_name }}</td> + <td>{{ fellow.first_name }}</td> + <td>{{ fellow.email_address }}</td> + <td>{{ fellow.date_sent }} </td> + <td>{{ fellow.invitation_type }}</td> + <td>{{ fellow.responded }}</td></tr> + {% endfor %} + </table> + + <h3>Normal Contributors</h3> + <table> + <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type (Fellow, Contrib)</td><td>Responded</td></tr> + {% for fellow in sent_reg_inv_contrib %} + <tr> + <td>{{ fellow.last_name }}</td> + <td>{{ fellow.first_name }}</td> + <td>{{ fellow.email_address }}</td> + <td>{{ fellow.date_sent }} </td> + <td>{{ fellow.invitation_type }}</td> + <td>{{ fellow.responded }}</td></tr> + {% endfor %} + </table> + + </div> +</section> + +{% endblock bodysup %} diff --git a/scipost/urls.py b/scipost/urls.py index 2528779b0..dc8afb052 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -29,6 +29,10 @@ urlpatterns = [ url(r'^already_activated$', TemplateView.as_view(template_name='scipost/already_activated.html'), name='already_activated'), url(r'^vet_registration_requests$', 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_invitations$', views.registration_invitations, name="registration_invitations"), + url(r'^registration_invitation_sent$', TemplateView.as_view(template_name='scipost/registration_invitation_sent.html'), name='registration_invitation_sent'), + url(r'^invitation/(?P<key>.+)$', views.accept_invitation, name='accept_invitation'), + url(r'^accept_invitation_error$', TemplateView.as_view(template_name='scipost/accept_invitation_error.html'), name='accept_invitation_error'), ## Authentication url(r'^login$', views.login_view, name='login'), @@ -45,11 +49,11 @@ urlpatterns = [ # Authorship claims url(r'^claim_authorships$', views.claim_authorships, name="claim_authorships"), - url(r'^claim_sub_authorship/(?P<submission_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_sub_authorship, name="claim_sub_authorship"), - url(r'^claim_com_authorship/(?P<commentary_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_com_authorship, name="claim_com_authorship"), - url(r'^claim_thesis_authorship/(?P<thesis_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_thesis_authorship, name="claim_thesis_authorship"), + url(r'^claim_sub_authorship/(?P<submission_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_sub_authorship, name='claim_sub_authorship'), + url(r'^claim_com_authorship/(?P<commentary_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_com_authorship, name='claim_com_authorship'), + url(r'^claim_thesis_authorship/(?P<thesis_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_thesis_authorship, name='claim_thesis_authorship'), url(r'^vet_authorship_claims$', views.vet_authorship_claims, name="vet_authorship_claims"), - url(r'^vet_sub_authorship_claim/(?P<submission_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_sub_authorship_claim, name="vet_sub_authorship_claim"), - url(r'^vet_com_authorship_claim/(?P<commentary_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_com_authorship_claim, name="vet_com_authorship_claim"), - url(r'^vet_thesis_authorship_claim/(?P<thesis_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_thesis_authorship_claim, name="vet_thesis_authorship_claim"), + url(r'^vet_sub_authorship_claim/(?P<submission_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_sub_authorship_claim, name='vet_sub_authorship_claim'), + url(r'^vet_com_authorship_claim/(?P<commentary_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_com_authorship_claim, name='vet_com_authorship_claim'), + url(r'^vet_thesis_authorship_claim/(?P<thesis_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_thesis_authorship_claim, name='vet_thesis_authorship_claim'), ] diff --git a/scipost/utils.py b/scipost/utils.py index 20cf57c1e..40eeaaa38 100644 --- a/scipost/utils.py +++ b/scipost/utils.py @@ -88,4 +88,40 @@ class Utils(object): emailmessage.send(fail_silently=False) + + @classmethod + def create_and_save_invitation(cls): + invitation = RegistrationInvitation ( + title = cls.reg_inv_form.cleaned_data['title'], + first_name = cls.reg_inv_form.cleaned_data['first_name'], + last_name = cls.reg_inv_form.cleaned_data['last_name'], + email_address = cls.reg_inv_form.cleaned_data['email_address'], + invitation_type = cls.reg_inv_form.cleaned_data['invitation_type'], + ) + Utils.load({'invitation': invitation}) + + @classmethod + def send_registration_invitation_email(cls): + # Generate email activation key and link + salt = "" + for i in range(5): + salt = salt + random.choice(string.ascii_letters) + salt = salt.encode('utf8') + invitationsalt = cls.invitation.last_name + invitationsalt = invitationsalt.encode('utf8') + cls.invitation.invitation_key = hashlib.sha1(salt+invitationsalt).hexdigest() + cls.invitation.key_expires = datetime.datetime.strftime( + datetime.datetime.now() + datetime.timedelta(days=14), "%Y-%m-%d %H:%M:%S") + cls.invitation.save() + email_text = ('Dear ' + title_dict[cls.invitation.title] + ' ' + + cls.invitation.last_name + + ', \n\nYou are invited to register to the SciPost publication portal.' + + ' You can do this by visiting ' + + 'this link within the next 2 weeks: \n\n' + 'https://scipost.org/invitation/' + + cls.invitation.invitation_key + + '\n\nYour registration will thereafter be vetted. Many thanks for your interest. \n\nThe SciPost Team.') + emailmessage = EmailMessage( + 'SciPost registration invitation', email_text, 'jscaux@scipost.org', + [cls.invitation.email_address, 'registration@scipost.org'], reply_to=['registration@scipost.org']) + emailmessage.send(fail_silently=False) diff --git a/scipost/views.py b/scipost/views.py index de4b8482e..ad1f667a3 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -179,6 +179,45 @@ def vet_registration_request_ack(request, contributor_id): return render(request, 'scipost/vet_registration_request_ack.html', context) +def registration_invitations(request): + # List invitations sent; send new ones + if request.method == 'POST': + # Send invitation from form information + reg_inv_form = RegistrationInvitationForm(request.POST) + Utils.load({'reg_inv_form': reg_inv_form}) + if reg_inv_form.is_valid(): + Utils.create_and_save_invitation() + Utils.send_registration_invitation_email() + return HttpResponseRedirect('registration_invitation_sent') + else: + reg_inv_form = RegistrationInvitationForm() + sent_reg_inv_fellows = RegistrationInvitation.objects.filter(invitation_type='F').order_by('last_name') + sent_reg_inv_contrib = RegistrationInvitation.objects.filter(invitation_type='C').order_by('last_name') + context = {'reg_inv_form': reg_inv_form, + 'sent_reg_inv_fellows': sent_reg_inv_fellows, + 'sent_reg_inv_contrib': sent_reg_inv_contrib} + return render(request, 'scipost/registration_invitations.html', context) + + +def accept_invitation(request, key): + invitation = get_object_or_404(RegistrationInvitation, invitation_key=key) + if timezone.now() > invitation.key_expires: + invitation_expired = True + errormessage = 'The invitation key has expired.' + elif invitation.responded: + errormessage = 'This invitation token has already been used.' + else: + form = RegistrationForm() + form.fields['last_name'].initial = invitation.last_name + form.fields['first_name'].initial = invitation.first_name + form.fields['email'].initial = invitation.email_address + errormessage = '' + return render(request, 'scipost/register.html', {'form': form, 'errormessage': errormessage}) + + context = {'errormessage': errormessage} + return render(request, 'scipost/invitation_error.html', context) + + def login_view(request): if request.method == 'POST': username = request.POST['username'] -- GitLab