diff --git a/scipost/forms.py b/scipost/forms.py index f50a5f97b69235e452ee3cc925c08c0fa7080609..3dac8846cc2988ef958cb3a4628e3cc47af9d5fd 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -1,3 +1,6 @@ +import string +import random + from django import forms from django.contrib.auth.models import User, Group @@ -29,14 +32,20 @@ reg_ref_dict = dict(REGISTRATION_REFUSAL_CHOICES) class RegistrationForm(forms.Form): + """ + Use this form to process the registration of new accounts. + Due to the construction of a separate Contributor from the User, + it is difficult to create a 'combined ModelForm'. All fields + are thus separately handled here. + """ title = forms.ChoiceField(choices=TITLE_CHOICES, label='* Title') first_name = forms.CharField(label='* First name', max_length=100) last_name = forms.CharField(label='* Last name', max_length=100) email = forms.EmailField(label='* Email address') - orcid_id = forms.CharField( - label=" ORCID id", max_length=20, - widget=forms.TextInput({'placeholder': 'Recommended. Get one at orcid.org'}), - required=False) + invitation_key = forms.CharField(max_length=40, widget=forms.HiddenInput(), required=False) + orcid_id = forms.CharField(label="ORCID id", max_length=20, required=False, + widget=forms.TextInput( + {'placeholder': 'Recommended. Get one at orcid.org'})) discipline = forms.ChoiceField(choices=SCIPOST_DISCIPLINES, label='* Main discipline') country_of_employment = LazyTypedChoiceField( choices=countries, label='* Country of employment', initial='NL', @@ -54,9 +63,48 @@ class RegistrationForm(forms.Form): required=False) username = forms.CharField(label='* Username', max_length=100) password = forms.CharField(label='* Password', widget=forms.PasswordInput()) - password_verif = forms.CharField(label='* Verify pwd', widget=forms.PasswordInput()) - captcha = ReCaptchaField(attrs={'theme': 'clean'}, - label='* Answer this simple maths question:') + password_verif = forms.CharField(label='* Verify password', widget=forms.PasswordInput()) + captcha = ReCaptchaField(attrs={'theme': 'clean'}, label='*Please verify to continue:') + + def clean_password_verif(self): + if self.cleaned_data['password'] != self.cleaned_data['password_verif']: + self.add_error('password', 'Your passwords must match') + self.add_error('password_verif', 'Your passwords must match') + + def clean_username(self): + if User.objects.filter(username=self.cleaned_data['username']).exists(): + self.add_error('username', 'This username is already in use') + return self.cleaned_data.get('username', '') + + def clean_email(self): + if User.objects.filter(email=self.cleaned_data['email']).exists(): + self.add_error('email', 'This email address is already in use') + return self.cleaned_data.get('email', '') + + def create_and_save_contributor(self, invitation_key=''): + user = User.objects.create_user(**{ + 'first_name': self.cleaned_data['first_name'], + 'last_name': self.cleaned_data['last_name'], + 'email': self.cleaned_data['email'], + 'username': self.cleaned_data['username'], + 'password': self.cleaned_data['password'], + 'is_active': False + }) + contributor, new = Contributor.objects.get_or_create(**{ + 'user': user, + 'invitation_key': invitation_key, + 'title': self.cleaned_data['title'], + 'orcid_id': self.cleaned_data['orcid_id'], + 'country_of_employment': self.cleaned_data['country_of_employment'], + 'address': self.cleaned_data['address'], + 'affiliation': self.cleaned_data['affiliation'], + 'personalwebpage': self.cleaned_data['personalwebpage'], + }) + + if contributor.invitation_key == '': + contributor.generate_key() + contributor.save() + return contributor class DraftInvitationForm(forms.ModelForm): diff --git a/scipost/migrations/0050_auto_20170416_2152.py b/scipost/migrations/0050_auto_20170416_2152.py new file mode 100644 index 0000000000000000000000000000000000000000..b2eb21d5cd61d7582c9d0af0d8a604a5a89420f6 --- /dev/null +++ b/scipost/migrations/0050_auto_20170416_2152.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-16 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0049_editorialcollegefellowship_affiliation'), + ] + + operations = [ + migrations.AlterField( + model_name='contributor', + name='activation_key', + field=models.CharField(blank=True, max_length=40), + ), + migrations.AlterField( + model_name='contributor', + name='invitation_key', + field=models.CharField(blank=True, default='', max_length=40), + preserve_default=False, + ), + migrations.AlterField( + model_name='registrationinvitation', + name='cited_in_submission', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='registration_invitations', to='submissions.Submission'), + ), + migrations.AlterField( + model_name='registrationinvitation', + name='invitation_key', + field=models.CharField(max_length=40, unique=True), + ), + migrations.AlterField( + model_name='registrationinvitation', + name='personal_message', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + ] diff --git a/scipost/models.py b/scipost/models.py index 397181addf5bc8c1015706fc8855fa06f22cad00..ceb2aa42f7646fed59d693fb2d30847429bafc88 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -1,4 +1,7 @@ import datetime +import hashlib +import random +import string from django.contrib.auth.models import User from django.contrib.postgres.fields import ArrayField @@ -37,10 +40,9 @@ class Contributor(models.Model): Permissions determine the sub-types. username, password, email, first_name and last_name are inherited from User. """ - user = models.OneToOneField(User, on_delete=models.CASCADE) - invitation_key = models.CharField(max_length=40, default='', - blank=True, null=True) - activation_key = models.CharField(max_length=40, default='') + user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True) + invitation_key = models.CharField(max_length=40, blank=True) + activation_key = models.CharField(max_length=40, blank=True) key_expires = models.DateTimeField(default=timezone.now) status = models.SmallIntegerField(default=0, choices=CONTRIBUTOR_STATUS) title = models.CharField(max_length=4, choices=TITLE_CHOICES) @@ -74,6 +76,15 @@ class Contributor(models.Model): # Please use get_title_display(). To be removed in future return self.get_title_display() + def is_SP_Admin(self): + return self.user.groups.filter(name='SciPost Administrators').exists() + + def is_MEC(self): + return self.user.groups.filter(name='Editorial College').exists() + + def is_VE(self): + return self.user.groups.filter(name='Vetting Editors').exists() + def is_currently_available(self): unav_periods = UnavailabilityPeriod.objects.filter(contributor=self) @@ -83,6 +94,18 @@ class Contributor(models.Model): return False return True + def generate_key(self, feed=''): + """ + Generate and save a new activation_key for the contributor, given a certain feed. + """ + for i in range(5): + feed += random.choice(string.ascii_letters) + feed = feed.encode('utf8') + 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 private_info_as_table(self): template = Template(''' <table> @@ -289,8 +312,8 @@ class RegistrationInvitation(models.Model): blank=True, null=True) message_style = models.CharField(max_length=1, choices=INVITATION_STYLE, default=INVITATION_FORMAL) - personal_message = models.TextField(blank=True, null=True) - invitation_key = models.CharField(max_length=40, default='') + personal_message = models.TextField(blank=True) + invitation_key = models.CharField(max_length=40, unique=True) key_expires = models.DateTimeField(default=timezone.now) date_sent = models.DateTimeField(default=timezone.now) invited_by = models.ForeignKey(Contributor, @@ -301,7 +324,6 @@ class RegistrationInvitation(models.Model): responded = models.BooleanField(default=False) declined = models.BooleanField(default=False) - def __str__(self): return (self.first_name + ' ' + self.last_name + ' on ' + self.date_sent.strftime("%Y-%m-%d")) diff --git a/scipost/templates/scipost/accept_invitation_error.html b/scipost/templates/scipost/accept_invitation_error.html index f1ab6a7fdba1bfc0ef6dcc27000a942774917a27..e9098a92acbc102891c2391b8e425cbac60d2b0c 100644 --- a/scipost/templates/scipost/accept_invitation_error.html +++ b/scipost/templates/scipost/accept_invitation_error.html @@ -2,16 +2,17 @@ {% block pagetitle %}: accept invitation: error{% endblock pagetitle %} -{% block bodysup %} +{% block content %} -<section> - <h1>Registration Invitation: error</h1> +<div class="row"> + <div class="col-12"> + <h1>Registration Invitation</h1> - <p>Error message: {{ errormessage }}</p> + <p>Error message: <span class="text-danger">{{ errormessage }}</span></p> <p>You can in any case simply fill the <a href="{% url 'scipost:register' %}"> registration form</a> to get access to the site's facilities.</p> + </div> +</div> -</section> - -{% endblock bodysup %} +{% endblock content %} diff --git a/scipost/templates/scipost/acknowledgement.html b/scipost/templates/scipost/acknowledgement.html index eda6a45b789071e2b11523612dacac6358c75678..4749b08daf08778e9038de656467b00239ef98d0 100644 --- a/scipost/templates/scipost/acknowledgement.html +++ b/scipost/templates/scipost/acknowledgement.html @@ -2,16 +2,18 @@ {% block pagetitle %}: acknowledgement {% endblock pagetitle %} -{% block bodysup %} +{% block content %} -<section> - <h3>{{ ack_header }}</h3> - {% if ack_message %} - <p>{{ ack_message }}</p> - {% endif %} - {% if followup_message %} - <p>{{ followup_message }} <a href="{{ followup_link }}">{{ followup_link_label }}</a>.</p> - {% endif %} -</section> +<div class="row"> + <div class="col-12"> + <h2>{{ ack_header }}</h2> + {% if ack_message %} + <p>{{ ack_message }}</p> + {% endif %} + {% if followup_message %} + <p>{{ followup_message }} <a href="{{ followup_link }}">{{ followup_link_label }}</a>.</p> + {% endif %} + </div> +</div> -{% endblock bodysup %} +{% endblock %} diff --git a/scipost/templates/scipost/register.html b/scipost/templates/scipost/register.html index 707c560f9955346fa3129b473d28e529bd43e482..bc4f4e0b9dea28bb24b570520728ab4dc42e1a96 100644 --- a/scipost/templates/scipost/register.html +++ b/scipost/templates/scipost/register.html @@ -10,8 +10,8 @@ <div class="col-12"> <div class="panel"> <h1>Register to SciPost</h1> - {% if welcome_message %} - <h2>{{ welcome_message }}</h2> + {% if invitation %} + <h2>Welcome {{invitation.get_title_display}} {{invitation.last_name}} and thanks in advance for registering (by completing this form)</h2> {% endif %} </div> </div> @@ -28,22 +28,14 @@ </div> <div class="offset-md-1 col-md-7"> - {% if invited %} - <form action="{% url 'scipost:invitation' key=key %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Submit" /> - </form> - {% else %} - <form action="{% url 'scipost:register' %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Submit" /> - </form> - {% endif %} + <form action="{% url 'scipost:register' %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Submit" /> + </form> {% if errormessage %} - <p style="color:red;">{{ errormessage }}</p> + <p class="text-danger">{{ errormessage }}</p> {% endif %} </div> </div> diff --git a/scipost/templates/scipost/request_new_activation_link.html b/scipost/templates/scipost/request_new_activation_link.html index d17db11c8e44c45c80f553477a3c90481d03361e..7111b6dae7fc4101a3d97397ef860962ba901391 100644 --- a/scipost/templates/scipost/request_new_activation_link.html +++ b/scipost/templates/scipost/request_new_activation_link.html @@ -2,11 +2,13 @@ {% block pagetitle %}: request new activation link{% endblock pagetitle %} -{% block bodysup %} +{% block content %} -<section> - <h1>Request a new activation link</h1> - <p>Your previous activation link has expired. <a href="{% url 'scipost:request_new_activation_link' oldkey=oldkey %}">Click here</a> to have us email you a new one.</p> -</section> +<div class="row"> + <div class="col-12"> + <h2>Request a new activation link</h2> + <p>Your previous activation link has expired. <a href="{% url 'scipost:request_new_activation_link' contributor.id contributor.activation_key %}?confirm=1">Click here</a> to have us email you a new one.</p> + </div> +</div> -{% endblock bodysup %} +{% endblock content %} diff --git a/scipost/templates/scipost/unsubscribe.html b/scipost/templates/scipost/unsubscribe.html index c32b358b0aae78a5d9510678719531e29e479708..39752b684e1d468f2232182050994f1ccc44ae1b 100644 --- a/scipost/templates/scipost/unsubscribe.html +++ b/scipost/templates/scipost/unsubscribe.html @@ -2,13 +2,14 @@ {% block pagetitle %}: Unsubscribe{% endblock pagetitle %} -{% block bodysup %} -<section> - <h3>Unsubscribe</h3> - <p>To let us know that you do not want to receive any non-essential email - from SciPost (citation notifications, announcements etc), - <a href="{% url 'scipost:unsubscribe_confirm' key=contributor.activation_key %}">click here</a>. - </p> - <p>You can reset this preference at any time from your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> -</section> -{% endblock bodysup %} +{% block content %} +<div class="row"> + <div class="col-12"> + <h2>Unsubscribe</h2> + <p> + <a href="{% url 'scipost:unsubscribe' contributor.id contributor.activation_key %}?confirm=1">Click here</a> to let us know that you do not want to receive any non-essential email from SciPost (citation notifications, announcements etc). + </p> + <p>You can reset this preference at any time from your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> + </div> +</div> +{% endblock content %} diff --git a/scipost/urls.py b/scipost/urls.py index 03e228e4d5e87652999e203e366aaeb2aa9ca8db..7e2664d51028fab3c4f19b581bbca44ee2a74d57 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -13,19 +13,19 @@ JOURNAL_REGEX = '(?P<doi_string>%s)' % REGEX_CHOICES urlpatterns = [ url(r'^$', views.index, name='index'), - url(r'^base$', views.base, name='base'), # General use pages url(r'^error$', TemplateView.as_view(template_name='scipost/error.html'), name='error'), url(r'^acknowledgement$', TemplateView.as_view(template_name='scipost/acknowledgement.html'), name='acknowledgement'), - ## Info + # Info url(r'^about$', views.AboutView.as_view(), name='about'), url(r'^call$', TemplateView.as_view(template_name='scipost/call.html'), name='call'), url(r'^foundation$', TemplateView.as_view(template_name='scipost/foundation.html'), name='foundation'), - url(r'^tour$', TemplateView.as_view(template_name='scipost/quick_tour.html'), name='quick_tour'), + url(r'^tour$', TemplateView.as_view(template_name='scipost/quick_tour.html'), + name='quick_tour'), url(r'^FAQ$', TemplateView.as_view(template_name='scipost/FAQ.html'), name='FAQ'), url(r'^terms_and_conditions$', TemplateView.as_view(template_name='scipost/terms_and_conditions.html'), @@ -34,8 +34,7 @@ urlpatterns = [ name='privacy_policy'), # Feeds - url(r'^feeds$', views.feeds, #TemplateView.as_view(template_name='scipost/feeds.html'), - name='feeds'), + url(r'^feeds$', views.feeds, name='feeds'), url(r'^rss/news/$', LatestNewsFeedRSS()), url(r'^atom/news/$', LatestNewsFeedAtom()), url(r'^rss/comments/$', LatestCommentsFeedRSS()), @@ -69,21 +68,17 @@ urlpatterns = [ # Contributors: ################ - ## Registration + # Registration url(r'^register$', views.register, name='register'), url(r'^thanks_for_registering$', TemplateView.as_view(template_name='scipost/thanks_for_registering.html'), name='thanks_for_registering'), - url(r'^activation/(?P<key>.+)$', views.activation, name='activation'), - url(r'^request_new_activation_link/(?P<oldkey>.+)$', - views.request_new_activation_link, - name='request_new_activation_link'), - url(r'^already_activated$', - TemplateView.as_view(template_name='scipost/already_activated.html'), - name='already_activated'), - url(r'^unsubscribe/(?P<key>.+)$', views.unsubscribe, name='unsubscribe'), - url(r'^unsubscribe_confirm/(?P<key>.+)$', - views.unsubscribe_confirm, name='unsubscribe_confirm'), + url(r'^activation/(?P<contributor_id>[0-9]+)/(?P<key>.+)/$', + views.activation, name='activation'), + url(r'^activation/(?P<contributor_id>[0-9]+)/(?P<key>.+)/renew$', + views.request_new_activation_link, name='request_new_activation_link'), + url(r'^unsubscribe/(?P<contributor_id>[0-9]+)/(?P<key>.+)$', views.unsubscribe, + name='unsubscribe'), url(r'^vet_registration_requests$', views.vet_registration_requests, name='vet_registration_requests'), url(r'^vet_registration_request_ack/(?P<contributor_id>[0-9]+)$', @@ -116,11 +111,9 @@ urlpatterns = [ 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'), + + # Registration invitations url(r'^invitation/(?P<key>.+)$', views.invitation, name='invitation'), - url(r'^accept_invitation_error$', - TemplateView.as_view(template_name='scipost/accept_invitation_error.html'), - name='accept_invitation_error'), url(r'^mark_draft_inv_as_processed/(?P<draft_id>[0-9]+)$', views.mark_draft_inv_as_processed, name='mark_draft_inv_as_processed'), url(r'^citation_notifications$', @@ -128,7 +121,7 @@ urlpatterns = [ url(r'^process_citation_notification/(?P<cn_id>[0-9]+)$', views.process_citation_notification, name='process_citation_notification'), - ## Authentication + # Authentication url(r'^login/$', views.login_view, name='login'), url(r'^logout$', views.logout_view, name='logout'), url(r'^personal_page$', views.personal_page, name='personal_page'), @@ -139,7 +132,8 @@ 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'^mark_unavailable_period$', views.mark_unavailable_period, + name='mark_unavailable_period'), # Contributor info url(r'^(?P<contributor_id>[0-9]+)$', views.contributor_info, name="contributor_info"), diff --git a/scipost/utils.py b/scipost/utils.py index dc4e597bb044ef3cdef80d70537332928661e5fd..f0f3ebcd37e23b5217169ab87e0c1b386b227c41 100644 --- a/scipost/utils.py +++ b/scipost/utils.py @@ -3,12 +3,14 @@ import hashlib import random import string -from django.contrib.auth.models import User from django.core.mail import EmailMultiAlternatives +from django.core.urlresolvers import reverse from django.template import Context, Template from django.utils import timezone -from .models import Contributor, DraftInvitation, RegistrationInvitation +from .models import DraftInvitation, RegistrationInvitation + +from common.utils import BaseMailUtil SCIPOST_SUMMARY_FOOTER = ( @@ -75,11 +77,9 @@ EMAIL_UNSUBSCRIBE_LINK_HTML = ( ) -class Utils(object): - @classmethod - def load(cls, dict): - for var_name in dict: - setattr(cls, var_name, dict[var_name]) +class Utils(BaseMailUtil): + mail_sender = 'registration@scipost.org' + mail_sender_title = 'SciPost registration' @classmethod def password_mismatch(cls): @@ -117,76 +117,28 @@ class Utils(object): return False @classmethod - def create_and_save_contributor(cls, invitation_key): - user = User.objects.create_user( - first_name=cls.form.cleaned_data['first_name'], - last_name=cls.form.cleaned_data['last_name'], - email=cls.form.cleaned_data['email'], - username=cls.form.cleaned_data['username'], - password=cls.form.cleaned_data['password'] - ) - # Set to inactive until activation via email link - user.is_active = False - user.save() - contributor = Contributor( - user=user, - invitation_key=invitation_key, - title=cls.form.cleaned_data['title'], - orcid_id=cls.form.cleaned_data['orcid_id'], - country_of_employment=cls.form.cleaned_data['country_of_employment'], - address=cls.form.cleaned_data['address'], - affiliation=cls.form.cleaned_data['affiliation'], - personalwebpage=cls.form.cleaned_data['personalwebpage'], - ) - contributor.save() - Utils.load({'contributor': contributor}) + def send_registration_email(cls): + """ + Send mail after registration request has been recieved. + + Requires loading: + contributor -- Contributor + """ + cls._send_mail(cls, 'registration_request_received', + [cls._context['contributor'].user.email], + 'request received') @classmethod - def send_registration_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') - usernamesalt = cls.contributor.user.username - usernamesalt = usernamesalt.encode('utf8') - cls.contributor.activation_key = hashlib.sha1(salt+usernamesalt).hexdigest() - cls.contributor.key_expires = datetime.datetime.strftime( - datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S") - cls.contributor.save() - email_text = ('Dear ' + cls.contributor.get_title_display() + ' ' + - cls.contributor.user.last_name + - ', \n\nYour request for registration to the SciPost publication portal' + - ' has been received. You now need to validate your email by visiting ' + - 'this link within the next 48 hours: \n\n' + 'https://scipost.org/activation/' + - cls.contributor.activation_key + - '\n\nYour registration will thereafter be vetted. Many thanks for your interest.' - '\n\nThe SciPost Team.') - email_text_html = ( - 'Dear {{ title }} {{ last_name }},<br/>' - '\n<p>Your request for registration to the SciPost publication portal' - ' has been received. You now need to validate your email by visiting ' - 'this link within the next 48 hours:</p>' - '<p><a href="https://scipost.org/activation/{{ activation_key }}">' - 'Activate your account</a></p>' - '\n<p>Your registration will thereafter be vetted. Many thanks for your interest.</p>' - '<p>The SciPost Team.</p>') - email_context = Context({ - 'title': cls.contributor.get_title_display(), - 'last_name': cls.contributor.user.last_name, - 'activation_key': cls.contributor.activation_key, - }) - email_text_html += '<br/>' + EMAIL_FOOTER - html_template = Template(email_text_html) - html_version = html_template.render(email_context) - emailmessage = EmailMultiAlternatives( - 'SciPost registration request received', email_text, - 'SciPost registration <registration@scipost.org>', - [cls.contributor.user.email], - ['registration@scipost.org'], - reply_to=['registration@scipost.org']) - emailmessage.attach_alternative(html_version, 'text/html') - emailmessage.send(fail_silently=False) + def send_new_activation_link_email(cls): + """ + Send mail after a new activation link on a Contributor has been generated. + + Requires loading: + contributor -- Contributor + """ + cls._send_mail(cls, 'new_activation_link', + [cls._context['contributor'].user.email], + 'new email activation link') @classmethod def create_draft_invitation(cls): @@ -653,6 +605,9 @@ class Utils(object): email_text += ',\n\n' email_text_html += ',<br/>' if cls.notification.cited_in_publication: + url_unsubscribe = reverse('scipost:unsubscribe', + args=[cls.notification.contributor.id, + cls.notification.contributor.activation_key]) email_text += ( 'We would like to notify you that ' 'your work has been cited in a paper published by SciPost,' @@ -662,8 +617,7 @@ class Utils(object): ').\n\nWe hope you will find this paper of interest to your own research.' '\n\nBest regards,\n\nThe SciPost Team' '\n\nDon\'t want to receive such emails? Unsubscribe by visiting ' - 'https://scipost.org/unsubscribe/' - + cls.notification.contributor.activation_key + '.') + + url_unsubscribe + '.') email_text_html += ( '<p>We would like to notify you that ' 'your work has been cited in a paper published by SciPost,</p>' @@ -674,7 +628,7 @@ class Utils(object): '<p>Best regards,</p><p>The SciPost Team</p><br/>' + EMAIL_FOOTER + '<br/>' '\n<p style="font-size: 10px;">Don\'t want to receive such emails? ' - '<a href="https://scipost.org/unsubscribe/{{ key }}">Unsubscribe</a>.</p>') + '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe) email_context['pub_title'] = cls.notification.cited_in_publication.title email_context['pub_author_list'] = cls.notification.cited_in_publication.author_list email_context['doi_label'] = cls.notification.cited_in_publication.doi_string @@ -683,6 +637,9 @@ class Utils(object): html_template = Template(email_text_html) html_version = html_template.render(email_context) elif cls.notification.cited_in_submission: + url_unsubscribe = reverse('scipost:unsubscribe', + args=[cls.notification.contributor.id, + cls.notification.contributor.activation_key]) email_text += ( 'Your work has been cited in a manuscript submitted to SciPost,' '\n\n' + cls.notification.cited_in_submission.title @@ -691,8 +648,7 @@ class Utils(object): 'commenting on the above submission before the refereeing deadline.\n\n' 'Best regards,\n\nThe SciPost Team' '\n\nDon\'t want to receive such emails? Unsubscribe by visiting ' - 'https://scipost.org/unsubscribe/' - + cls.notification.contributor.activation_key + '.') + + url_unsubscribe + '.') email_text_html += ( '<p>Your work has been cited in a manuscript submitted to SciPost,</p>' '<p>{{ sub_title }} <br>by {{ sub_author_list }},</p>' @@ -704,7 +660,7 @@ class Utils(object): '<p>Best regards,</p><p>The SciPost Team</p><br/>' + EMAIL_FOOTER + '<br/>' '\n<p style="font-size: 10px;">Don\'t want to receive such emails? ' - '<a href="https://scipost.org/unsubscribe/{{ key }}">Unsubscribe</a>.</p>') + '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe) email_context['sub_title'] = cls.notification.cited_in_submission.title email_context['sub_author_list'] = cls.notification.cited_in_submission.author_list email_context['arxiv_identifier_w_vn_nr'] = cls.notification.cited_in_submission.arxiv_identifier_w_vn_nr diff --git a/scipost/views.py b/scipost/views.py index 51442d623dbb2f288fcbacf04f86cbeac2eca7e2..44d609e97024187ec54876a55de1daf3a0a8c827 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -57,20 +57,7 @@ def is_registered(user): return user.groups.filter(name='Registered Contributors').exists() -def is_SP_Admin(user): - return user.groups.filter(name='SciPost Administrators').exists() - - -def is_MEC(user): - return user.groups.filter(name='Editorial College').exists() - - -def is_VE(user): - return user.groups.filter(name='Vetting Editors').exists() - - # Global search - def normalize_query(query_string, findterms=re.compile(r'"([^"]+)"|(\S+)').findall, normspace=re.compile(r'\s{2,}').sub): @@ -104,35 +91,23 @@ def documentsSearchResults(query): Naive implementation based on exact match of query. NEEDS UPDATING with e.g. Haystack. """ - publication_query = get_query(query, - ['title', 'author_list', 'abstract', 'doi_string']) - commentary_query = get_query(query, - ['pub_title', 'author_list', 'pub_abstract']) - submission_query = get_query(query, - ['title', 'author_list', 'abstract']) - thesislink_query = get_query(query, - ['title', 'author', 'abstract', 'supervisor']) - comment_query = get_query(query, - ['comment_text']) - - publication_search_queryset = Publication.objects.filter( - publication_query, - ).order_by('-publication_date') - commentary_search_queryset = Commentary.objects.filter( - commentary_query, - vetted=True, - ).order_by('-pub_date') - submission_search_queryset = Submission.objects.public().filter( - submission_query, - ).order_by('-submission_date') - thesislink_search_list = ThesisLink.objects.filter( - thesislink_query, - vetted=True, - ).order_by('-defense_date') - comment_search_list = Comment.objects.filter( - comment_query, - status__gte='1', - ).order_by('-date_submitted') + publication_query = get_query(query, ['title', 'author_list', 'abstract', 'doi_string']) + commentary_query = get_query(query, ['pub_title', 'author_list', 'pub_abstract']) + submission_query = get_query(query, ['title', 'author_list', 'abstract']) + thesislink_query = get_query(query, ['title', 'author', 'abstract', 'supervisor']) + comment_query = get_query(query, ['comment_text']) + + publication_search_queryset = (Publication.objects.published() + .filter(publication_query).order_by('-publication_date')) + commentary_search_queryset = (Commentary.objects.vetted() + .filter(commentary_query).order_by('-pub_date')) + submission_search_queryset = (Submission.objects.public() + .filter(submission_query).order_by('-submission_date')) + thesislink_search_list = (ThesisLink.objects.vetted() + .filter(thesislink_query).order_by('-defense_date')) + comment_search_list = (Comment.objects.vetted() + .filter(comment_query).order_by('-date_submitted')) + context = {'publication_search_queryset': publication_search_queryset, 'commentary_search_queryset': commentary_search_queryset, 'submission_search_queryset': submission_search_queryset, @@ -200,12 +175,12 @@ def search(request): ############# def index(request): - """ Main page """ - context = {} - context['latest_newsitems'] = NewsItem.objects.all().order_by('-date')[:2] - context['submissions'] = Submission.objects.public().order_by('-submission_date')[:4] - context['publications'] = Publication.objects.published().order_by('-publication_date')[:4] - + '''Main page.''' + context = { + 'latest_newsitems': NewsItem.objects.all().order_by('-date')[:2], + 'submissions': Submission.objects.public().order_by('-submission_date')[:4], + 'publications': Publication.objects.published().order_by('-publication_date')[:4] + } return render(request, 'scipost/index.html', context) @@ -213,11 +188,6 @@ def index(request): # Information ############### -def base(request): - """ Skeleton for pages, used in template inheritance """ - return render(request, 'scipost/base.html') - - def feeds(request): context = {'subject_areas_physics': SCIPOST_SUBJECT_AREAS[0][1]} return render(request, 'scipost/feeds.html', context) @@ -228,193 +198,123 @@ def feeds(request): ################ def register(request): + """ + This public registration view shows and processes the form + that will create new user account requests. After registration + the Contributor will need to activate its account via the mail + sent. After activation the user needs to be vetted by the SciPost + admin. + """ if request.user.is_authenticated(): - return HttpResponseRedirect('personal_page') - if request.method == 'POST': - form = RegistrationForm(request.POST) - Utils.load({'form': form}) - if form.is_valid(): - if Utils.password_mismatch(): - return render(request, 'scipost/register.html', - {'form': form, 'errormessage': 'Your passwords must match'}) - if Utils.username_already_taken(): - return render(request, 'scipost/register.html', - {'form': form, 'errormessage': 'This username is already in use'}) - if Utils.email_already_taken(): - return render(request, 'scipost/register.html', - {'form': form, - 'errormessage': 'This email address is already in use'}) - Utils.create_and_save_contributor('') - Utils.send_registration_email() - # If this email was associated to an invitation, mark it as responded to - try: - invitation = RegistrationInvitation.objects.get( - email=form.cleaned_data['email']) - invitation.responded = True - invitation.save() - except ObjectDoesNotExist: - pass - except MultipleObjectsReturned: - # Delete the first invitation - invitation_to_delete = RegistrationInvitation.objects.filter( - email=form.cleaned_data['email']).first() - invitation_to_delete.delete() - context = {'ack_header': 'Thanks for registering to SciPost.', - 'ack_message': ('You will receive an email with a link to verify ' - 'your email address. ' - 'Please visit this link within 48 hours. ' - 'Your credentials will thereafter be verified. ' - 'If your registration is vetted through by the ' - 'administrators, you will be enabled to contribute.'), - } - return render(request, 'scipost/acknowledgement.html', context) - else: - form = RegistrationForm() - invited = False - context = {'form': form, 'invited': invited} - return render(request, 'scipost/register.html', context) + return redirect(reverse('scipost:personal_page')) + + form = RegistrationForm(request.POST or None) + if form.is_valid(): + contributor = form.create_and_save_contributor() + Utils.load({'contributor': contributor}) + Utils.send_registration_email() + + # Disable invitations related to the new Contributor + (RegistrationInvitation.objects.filter(email=form.cleaned_data['email']) + .update(responded=True)) + + context = { + 'ack_header': 'Thanks for registering to SciPost.', + 'ack_message': ('You will receive an email with a link to verify ' + 'your email address. ' + 'Please visit this link within 48 hours. ' + 'Your credentials will thereafter be verified. ' + 'If your registration is vetted through by the ' + 'administrators, you will be enabled to contribute.'), + } + return render(request, 'scipost/acknowledgement.html', context) + return render(request, 'scipost/register.html', {'form': form, 'invited': False}) def invitation(request, key): - """ Register, by invitation """ + """ + If a scientist has recieved an invitation (RegistrationInvitation) + he/she will finish it's invitation via still view which will prefill + the default registration form. + """ invitation = get_object_or_404(RegistrationInvitation, invitation_key=key) if invitation.responded: errormessage = ('This invitation token has already been used, ' 'or this email address is already associated to a registration.') elif timezone.now() > invitation.key_expires: errormessage = 'The invitation key has expired.' - elif request.method == 'POST': - form = RegistrationForm(request.POST) - Utils.load({'form': form}) - if form.is_valid(): - if Utils.password_mismatch(): - return render(request, 'scipost/register.html', { - 'form': form, 'invited': True, 'key': key, - 'errormessage': 'Your passwords must match'}) - if Utils.username_already_taken(): - return render(request, 'scipost/register.html', - {'form': form, 'invited': True, 'key': key, - 'errormessage': 'This username is already in use'}) - if Utils.email_already_taken(): - return render(request, 'scipost/register.html', - {'form': form, 'invited': True, 'key': key, - 'errormessage': 'This email address is already in use'}) - invitation.responded = True - invitation.save() - Utils.create_and_save_contributor(key) - Utils.send_registration_email() - context = {'ack_header': 'Thanks for registering to SciPost.', - 'ack_message': ('You will receive an email with a link to verify ' - 'your email address. ' - 'Please visit this link within 48 hours. ' - 'Your credentials will thereafter be verified. ' - 'If your registration is vetted through by the ' - 'administrators, you will be enabled to contribute.'), - } - return render(request, 'scipost/acknowledgement.html', context) - else: - errormessage = 'form is invalidly filled' - return render(request, 'scipost/register.html', - {'form': form, 'invited': True, 'key': key, - 'errormessage': errormessage}) else: - form = RegistrationForm() - form.fields['title'].initial = invitation.title - form.fields['last_name'].initial = invitation.last_name - form.fields['first_name'].initial = invitation.first_name - form.fields['email'].initial = invitation.email - errormessage = '' - welcome_message = ('Welcome, ' + invitation.get_title_display() + ' ' - + invitation.last_name + ', and thanks in advance for ' - 'registering (by completing this form)') - return render(request, 'scipost/register.html', { - 'form': form, 'invited': True, 'key': key, - 'errormessage': errormessage, 'welcome_message': welcome_message}) - - context = {'errormessage': errormessage} - return render(request, 'scipost/accept_invitation_error.html', context) - - -def activation(request, key): + context = { + 'invitation': invitation, + 'form': RegistrationForm(initial=invitation.__dict__) + } + return render(request, 'scipost/register.html', context) + return render(request, 'scipost/accept_invitation_error.html', {'errormessage': errormessage}) + + +def activation(request, contributor_id, key): """ After registration, an email verification link is sent. Once clicked, the account is activated. """ - contributor = get_object_or_404(Contributor, activation_key=key) + contributor = get_object_or_404(Contributor, id=contributor_id, activation_key=key) if not contributor.user.is_active: if timezone.now() > contributor.key_expires: - context = {'oldkey': key} - return render(request, 'scipost/request_new_activation_link.html', context) - else: - contributor.user.is_active = True - contributor.user.save() - context = {'ack_header': 'Your email address has been confirmed.', - 'ack_message': ('Your SciPost account will soon be vetted. ' - 'You will soon receive an email from us.'), - } - return render(request, 'scipost/acknowledgement.html', context) - else: - return render(request, 'scipost/already_activated.html') - - -def request_new_activation_link(request, oldkey): - contributor = get_object_or_404(Contributor, activation_key=oldkey) - # Generate a new email activation key and link - salt = "" - for i in range(5): - salt = salt + random.choice(string.ascii_letters) - - salt = salt.encode('utf8') - usernamesalt = contributor.user.username - usernamesalt = usernamesalt.encode('utf8') - contributor.activation_key = hashlib.sha1(salt+usernamesalt).hexdigest() - contributor.key_expires = datetime.datetime.strftime( - datetime.datetime.now() + datetime.timedelta(days=2), "%Y-%m-%d %H:%M:%S") - contributor.save() - email_text = ('Dear ' + contributor.get_title_display() + ' ' + contributor.user.last_name + - ', \n\n' - 'Your request for a new email activation link for registration to the SciPost ' - 'publication portal has been received. You now need to visit this link within ' - 'the next 48 hours: \n\n' - 'https://scipost.org/activation/' + contributor.activation_key + - '\n\nYour registration will thereafter be vetted. Many thanks for your interest.' - '\n\nThe SciPost Team.') - emailmessage = EmailMessage('SciPost registration: new email activation link', - email_text, 'SciPost registration <registration@scipost.org>', - [contributor.user.email, 'registration@scipost.org'], - reply_to=['registration@scipost.org']) - emailmessage.send(fail_silently=False) - context = { - 'ack_header': 'We have emailed you a new activation link.', - 'ack_message': ('Please acknowledge it within its 48 hours validity ' - 'window if you want us to proceed with vetting your registraion.'), - } - return render(request, 'scipost/acknowledgement.html', context) + return redirect(reverse('scipost:request_new_activation_link', kwargs={ + 'contributor_id': contributor_id, + 'key': key + })) + contributor.user.is_active = True + contributor.user.save() + context = {'ack_header': 'Your email address has been confirmed.', + 'ack_message': ('Your SciPost account will soon be vetted. ' + 'You will soon receive an email from us.'), + } + return render(request, 'scipost/acknowledgement.html', context) + messages.success(request, ('<h3>Your email has already been confirmed.</h3>' + 'Please wait for vetting of your registration.' + ' We shall strive to send you an update by email within 24 hours.')) + return redirect(reverse('scipost:index')) -def unsubscribe(request, key): +def request_new_activation_link(request, contributor_id, key): + """ + Once a user tries to activate its account using the email verification link sent + and the key has expired, the user redirected to possibly request a new token. + """ + contributor = get_object_or_404(Contributor, id=contributor_id, activation_key=key) + if request.GET.get('confirm', False): + # Generate a new email activation key and link + contributor.generate_key() + Utils.load({'contributor': contributor}) + Utils.send_new_activation_link_email() + + context = { + 'ack_header': 'We have emailed you a new activation link.', + 'ack_message': ('Please acknowledge it within its 48 hours validity ' + 'window if you want us to proceed with vetting your registraion.'), + } + return render(request, 'scipost/acknowledgement.html', context) + context = {'contributor': contributor} + return render(request, 'scipost/request_new_activation_link.html', context) + + +def unsubscribe(request, contributor_id, key): """ The link to this method is included in all email communications with a Contributor. The key used is the original activation key. At this link, the Contributor can confirm that he/she does not want to receive any non-essential email notifications from SciPost. """ - contributor = get_object_or_404(Contributor, activation_key=key) - context = {'contributor': contributor, } - return render(request, 'scipost/unsubscribe.html', context) - - -def unsubscribe_confirm(request, key): - contributor = get_object_or_404(Contributor, activation_key=key) - contributor.accepts_SciPost_emails = False - contributor.save() - context = {'ack_header': 'Unsubscribe', - 'followup_message': ('We have recorded your preference: you will ' - 'no longer receive non-essential email ' - 'from SciPost. You can go back to your '), - 'followup_link': reverse('scipost:personal_page'), - 'followup_link_label': 'personal page'} - return render(request, 'scipost/acknowledgement.html', context) + contributor = get_object_or_404(Contributor, id=contributor_id, activation_key=key) + if request.GET.get('confirm', False): + contributor.accepts_SciPost_emails = False + contributor.save() + text = ('<h3>We have recorded your preference</h3>' + 'You will no longer receive non-essential email from SciPost.') + messages.success(request, text) + return redirect(reverse('scipost:index')) + return render(request, 'scipost/unsubscribe.html', {'contributor': contributor}) @permission_required('scipost.can_vet_registration_requests', return_403=True) @@ -678,27 +578,17 @@ def registration_invitations(request, draft_id=None): 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') - nr_sent_reg_inv_fellows = sent_reg_inv_fellows.count() sent_reg_inv_contrib = sent_reg_inv.filter(invitation_type='C').order_by('last_name') - nr_sent_reg_inv_contrib = sent_reg_inv_contrib.count() sent_reg_inv_ref = sent_reg_inv.filter(invitation_type='R').order_by('last_name') - nr_sent_reg_inv_ref = sent_reg_inv_ref.count() sent_reg_inv_cited_sub = sent_reg_inv.filter(invitation_type='ci').order_by('last_name') - nr_sent_reg_inv_cited_sub = sent_reg_inv_cited_sub.count() sent_reg_inv_cited_pub = sent_reg_inv.filter(invitation_type='cp').order_by('last_name') - nr_sent_reg_inv_cited_pub = sent_reg_inv_cited_pub.count() resp_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=False) resp_reg_inv_fellows = resp_reg_inv.filter(invitation_type='F').order_by('last_name') - nr_resp_reg_inv_fellows = resp_reg_inv_fellows.count() resp_reg_inv_contrib = resp_reg_inv.filter(invitation_type='C').order_by('last_name') - nr_resp_reg_inv_contrib = resp_reg_inv_contrib.count() resp_reg_inv_ref = resp_reg_inv.filter(invitation_type='R').order_by('last_name') - nr_resp_reg_inv_ref = resp_reg_inv_ref.count() resp_reg_inv_cited_sub = resp_reg_inv.filter(invitation_type='ci').order_by('last_name') - nr_resp_reg_inv_cited_sub = resp_reg_inv_cited_sub.count() resp_reg_inv_cited_pub = resp_reg_inv.filter(invitation_type='cp').order_by('last_name') - nr_resp_reg_inv_cited_pub = resp_reg_inv_cited_pub.count() decl_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=True) @@ -710,25 +600,15 @@ def registration_invitations(request, draft_id=None): context = { 'reg_inv_form': reg_inv_form, 'errormessage': errormessage, 'sent_reg_inv_fellows': sent_reg_inv_fellows, - 'nr_sent_reg_inv_fellows': nr_sent_reg_inv_fellows, 'sent_reg_inv_contrib': sent_reg_inv_contrib, - 'nr_sent_reg_inv_contrib': nr_sent_reg_inv_contrib, 'sent_reg_inv_ref': sent_reg_inv_ref, - 'nr_sent_reg_inv_ref': nr_sent_reg_inv_ref, 'sent_reg_inv_cited_sub': sent_reg_inv_cited_sub, - 'nr_sent_reg_inv_cited_sub': nr_sent_reg_inv_cited_sub, 'sent_reg_inv_cited_pub': sent_reg_inv_cited_pub, - 'nr_sent_reg_inv_cited_pub': nr_sent_reg_inv_cited_pub, 'resp_reg_inv_fellows': resp_reg_inv_fellows, - 'nr_resp_reg_inv_fellows': nr_resp_reg_inv_fellows, 'resp_reg_inv_contrib': resp_reg_inv_contrib, - 'nr_resp_reg_inv_contrib': nr_resp_reg_inv_contrib, 'resp_reg_inv_ref': resp_reg_inv_ref, - 'nr_resp_reg_inv_ref': nr_resp_reg_inv_ref, 'resp_reg_inv_cited_sub': resp_reg_inv_cited_sub, - 'nr_resp_reg_inv_cited_sub': nr_resp_reg_inv_cited_sub, 'resp_reg_inv_cited_pub': resp_reg_inv_cited_pub, - 'nr_resp_reg_inv_cited_pub': nr_resp_reg_inv_cited_pub, 'decl_reg_inv': decl_reg_inv, 'names_reg_contributors': names_reg_contributors, 'existing_drafts': existing_drafts, @@ -842,8 +722,7 @@ def mark_draft_inv_as_processed(request, draft_id): def login_view(request): - redirect_to = request.POST.get('next', - request.GET.get('next', reverse('scipost:personal_page'))) + redirect_to = request.POST.get('next', reverse('scipost:personal_page')) redirect_to = (redirect_to if is_safe_url(redirect_to, request.get_host()) else reverse('scipost:personal_page')) @@ -914,7 +793,7 @@ def personal_page(request): nr_reg_awaiting_validation = 0 nr_submissions_to_assign = 0 nr_recommendations_to_prepare_for_voting = 0 - if is_SP_Admin(contributor.user): + if contributor.is_SP_Admin(): intwodays = now + timezone.timedelta(days=2) # count the number of pending registration requests @@ -928,7 +807,7 @@ def personal_page(request): nr_assignments_to_consider = 0 active_assignments = None nr_reports_to_vet = 0 - if is_MEC(contributor.user): + if contributor.is_MEC(): nr_assignments_to_consider = (EditorialAssignment.objects .filter(to=contributor, accepted=None, deprecated=False) .count()) @@ -940,7 +819,7 @@ def personal_page(request): nr_comments_to_vet = 0 nr_thesislink_requests_to_vet = 0 nr_authorship_claims_to_vet = 0 - if is_VE(request.user): + if contributor.is_VE(): nr_commentary_page_requests_to_vet = Commentary.objects.filter(vetted=False).count() nr_comments_to_vet = Comment.objects.filter(status=0).count() nr_thesislink_requests_to_vet = ThesisLink.objects.filter(vetted=False).count() @@ -1056,19 +935,6 @@ def update_personal_data(request): if user_form.is_valid() and cont_form.is_valid(): user_form.save() cont_form.save() - # request.user.email = user_form.cleaned_data['email'] - # request.user.first_name = user_form.cleaned_data['first_name'] - # request.user.contributor.title = cont_form.cleaned_data['title'] - # request.user.contributor.discipline = cont_form.cleaned_data['discipline'] - # request.user.contributor.expertises = cont_form.cleaned_data['expertises'] - # request.user.contributor.orcid_id = cont_form.cleaned_data['orcid_id'] - # request.user.contributor.country_of_employment = cont_form.cleaned_data['country_of_employment'] - # request.user.contributor.address = cont_form.cleaned_data['address'] - # request.user.contributor.affiliation = cont_form.cleaned_data['affiliation'] - # request.user.contributor.personalwebpage = cont_form.cleaned_data['personalwebpage'] - # request.user.contributor.accepts_SciPost_emails = cont_form.cleaned_data['accepts_SciPost_emails'] - # request.user.save() - # request.user.contributor.save() messages.success(request, 'Your personal data has been updated.') return redirect(reverse('scipost:personal_page')) else: @@ -1248,25 +1114,6 @@ def email_group_members(request): if request.method == 'POST': form = EmailGroupMembersForm(request.POST) if form.is_valid(): - # recipient_emails = [] - # for member in form.cleaned_data['group'].user_set.all(): - # recipient_emails.append(member.email) - # emailmessage = EmailMessage( - # form.cleaned_data['email_subject'], - # form.cleaned_data['email_text'], - # 'SciPost Admin <admin@scipost.org>', - # ['admin@scipost.org'], - # bcc=recipient_emails, - # reply_to=['admin@scipost.org']) - # emailmessage.send(fail_silently=False) - # with mail.get_connection() as connection: - # for member in form.cleaned_data['group'].user_set.all(): - # email_text = ('Dear ' + member.contributor.get_title_display() + ' ' + - # member.last_name + ', \n\n' - # + form.cleaned_data['email_text']) - # mail.EmailMessage(form.cleaned_data['email_subject'], - # email_text, 'SciPost Admin <admin@scipost.org>', - # [member.email], connection=connection).send() group_members = form.cleaned_data['group'].user_set.all() p = Paginator(group_members, 32) for pagenr in p.page_range: @@ -1286,14 +1133,14 @@ def email_group_members(request): email_text += SCIPOST_SUMMARY_FOOTER email_text_html += SCIPOST_SUMMARY_FOOTER_HTML email_text_html += EMAIL_FOOTER + url_unsubscribe = reverse('scipost:unsubscribe', + args=[contributor.id, + contributor.activation_key]) email_text += ('\n\nDon\'t want to receive such emails? ' - 'Unsubscribe by visiting ' - 'https://scipost.org/unsubscribe/' - + member.contributor.activation_key + '.') + 'Unsubscribe by visiting %s.' % url_unsubscribe) email_text_html += ( '<br/>\n<p style="font-size: 10px;">Don\'t want to receive such ' - 'emails? <a href="https://scipost.org/unsubscribe/{{ key }}">' - 'Unsubscribe</a>.</p>') + 'emails? <a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe) email_context = Context({ 'title': member.contributor.get_title_display(), 'last_name': member.last_name, diff --git a/submissions/constants.py b/submissions/constants.py index 13b615f63e148dfc504e4b977fafef9713f655a6..d8d508b8d29c9a65d5f0b02057a9da1cd463fd2f 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -6,7 +6,7 @@ STATUS_AWAITING_ED_REC = 'awaiting_ed_rec' STATUS_REVIEW_CLOSED = 'review_closed' SUBMISSION_STATUS = ( (STATUS_UNASSIGNED, 'Unassigned, undergoing pre-screening'), - (STATUS_RESUBMISSION_SCREENING, 'Resubmission incoming, undergoing pre-screening'), + (STATUS_RESUBMISSION_SCREENING, 'Resubmission incoming'), ('assignment_failed', 'Failed to assign Editor-in-charge; manuscript rejected'), (STATUS_EIC_ASSIGNED, 'Editor-in-charge assigned, manuscript under review'), (STATUS_REVIEW_CLOSED, 'Review period closed, editorial recommendation pending'), @@ -75,6 +75,11 @@ SUBMISSION_TYPE = ( ('Review', 'Review (candid snapshot of current research in a given area)'), ) +NO_REQUIRED_ACTION_STATUSES = SUBMISSION_STATUS_PUBLICLY_INVISIBLE + [ + STATUS_UNASSIGNED, + STATUS_RESUBMISSION_SCREENING +] + ED_COMM_CHOICES = ( ('EtoA', 'Editor-in-charge to Author'), ('EtoR', 'Editor-in-charge to Referee'), diff --git a/submissions/migrations/0040_auto_20170416_2152.py b/submissions/migrations/0040_auto_20170416_2152.py new file mode 100644 index 0000000000000000000000000000000000000000..e8064dfabea16dc62fe3f69e35dd4f5d988a3f0e --- /dev/null +++ b/submissions/migrations/0040_auto_20170416_2152.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-16 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0039_auto_20170416_0948'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('unassigned', 'Unassigned, undergoing pre-screening'), ('resubmitted_incomin', 'Resubmission incoming'), ('assignment_failed', 'Failed to assign Editor-in-charge; manuscript rejected'), ('EICassigned', 'Editor-in-charge assigned, manuscript under review'), ('review_closed', 'Review period closed, editorial recommendation pending'), ('revision_requested', 'Editor-in-charge has requested revision'), ('resubmitted', 'Has been resubmitted'), ('resubmitted_and_rejected', 'Has been resubmitted and subsequently rejected'), ('resubmitted_and_rejected_visible', 'Has been resubmitted and subsequently rejected (still publicly visible)'), ('voting_in_preparation', 'Voting in preparation (eligible Fellows being selected)'), ('put_to_EC_voting', 'Undergoing voting at the Editorial College'), ('awaiting_ed_rec', 'Awaiting Editorial Recommendation'), ('EC_vote_completed', 'Editorial College voting rounded up'), ('accepted', 'Publication decision taken: accept'), ('rejected', 'Publication decision taken: reject'), ('rejected_visible', 'Publication decision taken: reject (still publicly visible)'), ('published', 'Published'), ('withdrawn', 'Withdrawn by the Authors')], default='unassigned', max_length=30), + ), + ] diff --git a/templates/email/new_activation_link.html b/templates/email/new_activation_link.html new file mode 100644 index 0000000000000000000000000000000000000000..3d8def3613888ad4377396c3c11972a053dd946f --- /dev/null +++ b/templates/email/new_activation_link.html @@ -0,0 +1,9 @@ +Dear {{contributor.get_title_display}} {{contributor.user.last_name}},\n\n + +Your request for a new email activation link for registration to the SciPost publication portal has been received. You now need to visit this link within the next 48 hours: \n\n + +{% url 'scipost:activation' contributor.id contributor.activation_key %} +\n\n + +Your registration will thereafter be vetted. Many thanks for your interest.\n +The SciPost Team. diff --git a/templates/email/new_activation_link_html.html b/templates/email/new_activation_link_html.html new file mode 100644 index 0000000000000000000000000000000000000000..8ab56b93af64371f8dda5906ef741bedffa90ddb --- /dev/null +++ b/templates/email/new_activation_link_html.html @@ -0,0 +1,15 @@ +<h3>Dear {{contributor.get_title_display}} {{contributor.user.last_name}},</h3> + +<p> + Your request for a new email activation link for registration to the SciPost publication portal has been received. You now need to visit this link within the next 48 hours: +</p> +<p> + <a href="{% url 'scipost:activation' contributor.id contributor.activation_key %}">Activate your account</a> +</p> + +<p> + Your registration will thereafter be vetted. Many thanks for your interest. + The SciPost Team. +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/registration_request_received.html b/templates/email/registration_request_received.html new file mode 100644 index 0000000000000000000000000000000000000000..d18818fbfc929832f1e7815bf3771bb675e3a313 --- /dev/null +++ b/templates/email/registration_request_received.html @@ -0,0 +1,9 @@ +Dear {{contributor.get_title_display}} {{contributor.user.last_name}},\n\n + +Your request for registration to the SciPost publication portal has been received. You now need to validate your email by visiting this link within the next 48 hours: \n\n + +{% url 'scipost:activation' contributor.id contributor.activation_key %} +\n\n + +Your registration will thereafter be vetted. Many thanks for your interest.\n +The SciPost Team. diff --git a/templates/email/registration_request_received_html.html b/templates/email/registration_request_received_html.html new file mode 100644 index 0000000000000000000000000000000000000000..ca016a1a10a7d626ffa8646284a4e9e9c229fd65 --- /dev/null +++ b/templates/email/registration_request_received_html.html @@ -0,0 +1,15 @@ +<h3>Dear {{contributor.get_title_display}} {{contributor.user.last_name}},</h3> + +<p> + Your request for registration to the SciPost publication portal has been received. You now need to validate your email by visiting this link within the next 48 hours: +</p> +<p> + <a href="{% url 'scipost:activation' contributor.id contributor.activation_key %}">Activate your account</a> +</p> + +<p> + Your registration will thereafter be vetted. Many thanks for your interest. + The SciPost Team. +</p> + +{% include 'email/_footer.html' %}