diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 7758de6ee883377b474cd745ca936f0a56bade95..7121c9a6f2909f450bd3b06d8375248fd894c1a3 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -77,6 +77,7 @@ INSTALLED_APPS = ( 'django_countries', 'django_extensions', 'django_mathjax', + 'ajax_select', 'captcha', 'crispy_forms', 'guardian', @@ -233,7 +234,8 @@ JOURNALS_DIR = 'journals' CROSSREF_LOGIN_ID = '' CROSSREF_LOGIN_PASSWORD = '' -# Google reCaptcha -RECAPTCHA_PUBLIC_KEY = '' -RECAPTCHA_PRIVATE_KEY = '' +# Google reCaptcha with Google's global test keys +# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-v2-what-should-i-do +RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' +RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' NOCAPTCHA = True diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index aa37315a7a78f1c81e72d14a7f9efda60255eff1..5ed76657d6e6eeec5fe8c81e22293d0f595df43d 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -17,11 +17,16 @@ from django.conf import settings from django.conf.urls import include, url from django.contrib import admin -JOURNAL_REGEX = '(?P<doi_string>SciPostPhysProc|SciPostPhys)' +from ajax_select import urls as ajax_select_urls + +from journals.constants import REGEX_CHOICES + +JOURNAL_REGEX = '(?P<doi_string>%s)' % REGEX_CHOICES urlpatterns = [ url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/', include(admin.site.urls)), + url(r'^ajax_select/', include(ajax_select_urls)), url(r'^docs/', include('sphinxdoc.urls')), url(r'^10.21468/%s/' % JOURNAL_REGEX, include('journals.urls.journal', namespace="journal")), url(r'^%s/' % JOURNAL_REGEX, include('journals.urls.journal', namespace="journal")), diff --git a/comments/managers.py b/comments/managers.py index 2875a7030b1cb75cf16b364cd6c409e70e90f0b2..823b4c0d79a732c224bc62bec58d1f70d1f416fb 100644 --- a/comments/managers.py +++ b/comments/managers.py @@ -1,6 +1,11 @@ from django.db import models +from .constants import STATUS_PENDING + class CommentManager(models.Manager): def vetted(self): return self.filter(status__gte=1) + + def awaiting_vetting(self): + return self.filter(status=STATUS_PENDING) diff --git a/comments/migrations/0012_auto_20170415_1659.py b/comments/migrations/0012_auto_20170415_1659.py new file mode 100644 index 0000000000000000000000000000000000000000..7764750cdc1b90b2c43ad4911f858404997d5fcb --- /dev/null +++ b/comments/migrations/0012_auto_20170415_1659.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-15 14:59 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0011_auto_20170326_1447'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='submission', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='submissions.Submission'), + ), + ] diff --git a/comments/models.py b/comments/models.py index b003cc8ab6453183c4ea36257b49f979700318cd..fd52703d222870e5694815c6b39acb632abfbea9 100644 --- a/comments/models.py +++ b/comments/models.py @@ -1,11 +1,8 @@ from django.db import models from django.shortcuts import get_object_or_404 -from commentaries.models import Commentary from scipost.behaviors import TimeStampedModel from scipost.models import Contributor -from submissions.models import Submission, Report -from theses.models import ThesisLink from .behaviors import validate_file_extension, validate_max_file_size from .constants import COMMENT_STATUS, STATUS_PENDING @@ -17,28 +14,26 @@ class Comment(TimeStampedModel): on a particular publication or in reply to an earlier Comment. """ status = models.SmallIntegerField(default=STATUS_PENDING, choices=COMMENT_STATUS) - vetted_by = models.ForeignKey( - Contributor, - blank=True, - null=True, - on_delete=models.CASCADE, - related_name='comment_vetted_by' - ) + vetted_by = models.ForeignKey('scipost.Contributor', blank=True, null=True, + on_delete=models.CASCADE, related_name='comment_vetted_by') file_attachment = models.FileField( upload_to='uploads/comments/%Y/%m/%d/', blank=True, validators=[validate_file_extension, validate_max_file_size] ) # a Comment is either for a Commentary or Submission or a ThesisLink. - commentary = models.ForeignKey(Commentary, blank=True, null=True, on_delete=models.CASCADE) - submission = models.ForeignKey(Submission, blank=True, null=True, on_delete=models.CASCADE) - thesislink = models.ForeignKey(ThesisLink, blank=True, null=True, on_delete=models.CASCADE) + commentary = models.ForeignKey('commentaries.Commentary', blank=True, null=True, + on_delete=models.CASCADE) + submission = models.ForeignKey('submissions.Submission', blank=True, null=True, + on_delete=models.CASCADE, related_name='comments') + thesislink = models.ForeignKey('theses.ThesisLink', blank=True, null=True, + on_delete=models.CASCADE) is_author_reply = models.BooleanField(default=False) in_reply_to_comment = models.ForeignKey('self', blank=True, null=True, related_name="nested_comments", on_delete=models.CASCADE) - in_reply_to_report = models.ForeignKey(Report, blank=True, null=True, + in_reply_to_report = models.ForeignKey('submissions.Report', blank=True, null=True, on_delete=models.CASCADE) - author = models.ForeignKey(Contributor, on_delete=models.CASCADE) + author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) anonymous = models.BooleanField(default=False, verbose_name='Publish anonymously') # Categories: is_cor = models.BooleanField(default=False, verbose_name='correction/erratum') diff --git a/common/helpers/__init__.py b/common/helpers/__init__.py index d1875920b99074217a6ce3d80fa10128bd4e568a..5bab1621fd150b0eaa7904b18cbf5e41cc82f628 100644 --- a/common/helpers/__init__.py +++ b/common/helpers/__init__.py @@ -1,6 +1,7 @@ import random import string + def model_form_data(model, form_class, form_kwargs={}): ''' Returns a dict that can be used to instantiate a form object. @@ -28,15 +29,19 @@ def model_form_data(model, form_class, form_kwargs={}): form_fields = list(form_class(**form_kwargs).fields.keys()) return filter_keys(model_data, form_fields) + def random_arxiv_identifier_with_version_number(): return random_arxiv_identifier_without_version_number() + "v0" + def random_arxiv_identifier_without_version_number(): return random_digits(4) + "." + random_digits(5) + def random_digits(n): return "".join(random.choice(string.digits) for _ in range(n)) + def filter_keys(dictionary, keys_to_keep): # Field is empty if not on model. return {key: dictionary.get(key, "") for key in keys_to_keep} diff --git a/common/utils.py b/common/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..4a778d1002428a3cd1c2da6823a7c137df54f39c --- /dev/null +++ b/common/utils.py @@ -0,0 +1,44 @@ +from django.core.mail import EmailMultiAlternatives +from django.template import loader, Context + + +class BaseMailUtil(object): + mail_sender = 'no-reply@scipost.org' + mail_sender_title = '' + + @classmethod + def load(cls, _dict, request=None): + cls._context = _dict + cls._context['request'] = request + for var_name in _dict: + setattr(cls, var_name, _dict[var_name]) + + def _send_mail(cls, template_name, recipients, subject, extra_bcc=None): + """ + Call this method from a classmethod to send emails. + The template will have context variables defined appended from the `load` method. + + Arguments: + template_name -- The .html template to use in the mail. The name be used to get the + following two templates: + `email/<template_name>.html` (non-HTML) + `email/<template_name>_html.html` + recipients -- List of mailaddresses to send to mail to. + subject -- The subject of the mail. + """ + template = loader.get_template('email/%s.html' % template_name) + html_template = loader.get_template('email/%s_html.html' % template_name) + message = template.render(Context(cls._context)) + html_message = html_template.render(Context(cls._context)) + bcc_list = [cls.mail_sender] + if extra_bcc: + bcc_list += extra_bcc + email = EmailMultiAlternatives( + 'SciPost: ' + subject, # message, + message, + '%s <%s>' % (cls.mail_sender_title, cls.mail_sender), + recipients, + bcc=bcc_list, + reply_to=[cls.mail_sender]) + email.attach_alternative(html_message, 'text/html') + email.send(fail_silently=False) diff --git a/journals/api.py b/journals/api.py index 88a91a4c5d8191d98226d5d8dfba6136f5f4bbd4..d90bee3c499835038763a80b9193102736d86736 100644 --- a/journals/api.py +++ b/journals/api.py @@ -8,7 +8,5 @@ class PublicationList(ListAPIView): """ List all publications that are published. """ - # def get_queryset(self, request, format=None): queryset = Publication.objects.published() serializer_class = PublicationSerializer - # return Response(serializer.data) diff --git a/journals/templates/journals/_publication_single_short_summary.html b/journals/templates/journals/_publication_single_short_summary.html index cce9e6bb3e29bb9791b05f10e01810bd247b2800..7ca8c2165167f03f4664ae6631e2b138643922cc 100644 --- a/journals/templates/journals/_publication_single_short_summary.html +++ b/journals/templates/journals/_publication_single_short_summary.html @@ -1,3 +1,3 @@ <h3 class="pb-0"><a href="{{publication.get_absolute_url}}">{{ publication.title }}</a></h3> +<div class="text-muted mb-1"><span class="d-inline-block">published {{ publication.publication_date }}</span> <span class="d-inline-block">as {{ publication.doi_string }}</span></div> <div>by {{ publication.author_list|truncatechars:30 }}</div> -<div class="text-muted">published {{ publication.publication_date }}</div> diff --git a/journals/templates/journals/journals.html b/journals/templates/journals/journals.html index eb2059959792d64ac2e5424dd8e18a699f2f8ebc..216a7567c81969b670de143d0669bd1706e995c5 100644 --- a/journals/templates/journals/journals.html +++ b/journals/templates/journals/journals.html @@ -21,6 +21,7 @@ <a href="{% url 'scipost:landing_page' 'SciPostPhys' %}">SciPost Physics</a> </h1> <div class="py-2"> + <h3 class="mb-2"><a href="{% url 'scipost:landing_page' 'SciPostPhys' %}">Go direct to SciPost Physics</a></h3> <p>SciPost Physics publishes outstanding-quality research articles in all domains and subject areas of Physics.</p> <p>The journal accepts three types of content: Letters, Articles and Reviews.</p> <p>Letters report broad-interest, significant breakthroughs in Physics, of interest and importance to researchers in multiple subject areas.</p> @@ -31,10 +32,10 @@ <div class="col-md-6"> <h1 class="banner"> - <a href="{% url 'journal:about' 'SciPostPhysProc' %}">SciPost Physics Proceedings</a> + <a href="{% url 'journal:about' 'SciPostPhysProc' %}">SciPost Physics Proceedings</a> <span class="d-inline text-danger">New!</span> </h1> <div class="py-2"> - <p style="color: red;">New!</p> + <h3 class="d-inline-block text-danger mb-2"><b>New!</b> <a href="{% url 'journal:about' 'SciPostPhysProc' %}">Go direct to SciPost Physics Proceedings</a></h3> <p>SciPost Physics Proceedings is a premium-quality, two-way open access, peer-witnessed refereed Journal for the general field of Physics.</p> <p>It aims at providing a high-quality, openly accessible publishing venue for conference/workshop/school proceedings.</p> <p>SciPost Physics Proceedings publishes articles in the domains of Experimental, Theoretical and Computational physics, in all specializations.</p> diff --git a/journals/templates/journals/journals_terms_and_conditions.html b/journals/templates/journals/journals_terms_and_conditions.html index b6b316c7d3a75c9f3dc8e316613d172cf111809d..396b5c67c91e0f05551c496450e3ab405f0fbec5 100644 --- a/journals/templates/journals/journals_terms_and_conditions.html +++ b/journals/templates/journals/journals_terms_and_conditions.html @@ -4,237 +4,247 @@ {% load staticfiles %} -{% block bodysup %} +{% block content %} -<section> - <div class="flex-container"> - <div class="flex-greybox"> - <h1>SciPost Journals: Terms and Conditions</h1> +<div class="row"> + <div class="col-12"> + <h1 class="highlight">SciPost Journals: Terms and Conditions</h1> + <p>These complement the SciPost <a href="{% url 'scipost:terms_and_conditions' %}">Terms and Conditions</a>.</p> </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2>General</h2> + + <h3>SciPost expects the following from submitters:</h3> + <ul> + <li>The manuscript which is submitted for publication has not been published before except in + the form of an abstract or electronic preprint or other similar formats which have not undergone + peer review. It is also not under consideration elsewhere for peer-reviewed publication.</li> + <li>Each submission will automatically be checked for plagiarism: SciPost is a Participating Publisher of Crossref's Similarity Check. In their own interest, authors + should avoid any ambiguous case by clearly quoting and citing original sources.</li> + <li>The submission has been approved by all authors and tacitly or explicitly by the relevant + authorities and/or institutes where the work was carried out.</li> + <li>In the case of acceptance for publication, the authors agree to the terms of the + <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International (CC BY 4.0) License</a>. + This means that you are free to use, reproduce and distribute the articles and related content (unless otherwise noted), + for commercial and noncommercial purposes, subject to the citation of the original source in accordance with the CC-BY license, + in particular section 3a.</li> + <li>The authors have secured the right to reproduce any material in their work which has + already been published elsewhere.</li> + <li>The authors agree with the <a href="#license_and_copyright_agreement">license and copyright agreement</a>.</li> + <li>The authors have agreed to and are in compliance with the <a href="#author_obligations">general author obligations</a>.</li> + <li>The authors of a given manuscript are formally + represented by the author having submitted the manuscript.</li> + <li>During the evaluation phase, Editorial Fellows will follow the + <a href="{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a> + and referees will follow the <a href="#referee_code_of_conduct">code of conduct for referees</a>.</li> + <li>The SciPost Administration reserves the right to remove or censor reports or comments + if they contain inappropriate material or if they are insufficiently substantial in nature or + of insufficient direct relevance to the manuscript under consideration.</li> + <li>The use of general descriptive names, trade names, trademarks etc. in the published + articles of SciPost journals, even if not specifically identified, does not imply that these + names are not protected by the relevant laws and regulations.</li> + <li>The Fellows of the Editorial College do their best to shepherd the refereeing process + as carefully as possible but cannot assume legal responsibility for the outcome and + aftermath of acceptance or rejection.</li> + <li>While the contents of the journals is believed to be true and accurate on the date each + article is published, neither the authors, the Editorial Fellows, Stichting SciPost or any of + its officers and Board members can accept any legal responsibility for any errors or + omissions that may have occurred. SciPost make no guarantee, expressed or implied, with + respect to the material contained herein. Any opinions expressed in SciPost journals are + the views of the authors and are not the views of SciPost.</li> + </ul> + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <h2>Open Access policy</h2> + <p>All SciPost Journals are Open Access which means that all content is freely available without charge to the user or his/her institution. Users are allowed to read, download, copy, distribute, print, search, or link to the full texts of the articles, or use them for any other lawful purpose, without asking prior permission from the publisher or the author. This is in accordance with the BOAI definition of Open Access.</p> + </div> +</div> + +<hr> +<div class="row"> + <div class="col-12"> + + + <h2 id="license_and_copyright_agreement">License and copyright agreement</h2> + <p>The following license and copyright agreement is valid for any article published in any + SciPost journal and web portal.</p> + <h3>Author's certification</h3> + <p>By submitting their manuscript, the authors certify the following:</p> + <ul> + <li>They are authorized by their co-authors to enter into these agreements.</li> + <li>The work described has not been published before except in + the form of an abstract or electronic preprint or other similar formats which have not undergone + peer review; it is not under consideration for publication elsewhere; its publication has been + approved by the responsible authorities, tacitly or explicitly, of the institutes where the work + was performed.</li> + <li>They have secured the right to reproduce any material that has already been published or + copyrighted elsewhere.</li> + <li>They agree to the following license and copyright agreement:</li> + </ul> + <h3>Copyright</h3> + <ul> + <li>The copyright of any article is retained by the author(s). More information on the transfer of + copyright can be found below.</li> + <li>Authors grant SciPost a license to publish the article and identify itself as the original publisher.</li> + <li>Authors grant SciPost commercial rights to produce hardcopy volumes of the journal for purchase by + libraries and individuals.</li> + <li>Authors grant any third party the right to use the article freely under the stipulation that the + original authors are given credit and the appropriate citation details are mentioned.</li> + <li>The article is distributed under the + <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International (CC BY 4.0) License</a>. + Unless otherwise stated, any associated published material is distributed under the same license. + If one or more authors are US government employees, the paper can be published under the + terms of the <a href="http://creativecommons.org/licenses/cc0">Creative Commons CC0 public domain dedication</a>.</li> + </ul> + + <h3 class="mt-3">Creative Commons Attribution 4.0 License</h3> + <h4>You are free to</h4> + <ul> + <li><b>Share</b> - copy and redistribute the material in any medium or format.</li> + <li><b>Adapt</b> - remix, transform, and build upon the material.</li> + </ul> + <p>for any purpose, even commercially. The licensor cannot revoke these freedoms + as long as you follow the license terms.</p> + <h4>Under the following terms:</h4> + <ul> + <li><b>Attribution</b> - You must give appropriate credit, provide a link to the + license, and indicate if changes were made. You may do so in any reasonable + manner, but not in any way that suggests the licensor endorses you or your use.</li> + </ul> + <p><b>No additional restrictions</b> - You may not apply legal terms or technological + measures that legally restrict others from doing anything the license permits.</p> + <h4>Notices:</h4> + <p>You do not have to comply with the license for elements of the material in + the public domain or where your use is permitted by an applicable exception + or limitation.</p> + <p>No warranties are given. The license may not give you all of the permissions + necessary for your intended use. For example, other rights such as publicity, + privacy, or moral rights may limit how you use the material.</p> + + <h4>View the <a href="http://creativecommons.org/licenses/by/4.0/">full legal code</a> + of the license.</h4> + + <h3 class="mt-3">Copyright transfers</h3> + <p>Many authors have strict regulations in their employment contract regarding + their publications. A transfer of copyright to the institution or company + is common as well as the reservation of specific usage rights. + In open-access publications in combination with the Creative Commons License, + a transfer of the copyright to the institution is possible as it belongs + to the author anyway.</p> + + <p>Any usage rights are regulated through the Creative Commons License. + As SciPost uses the Creative Commons Attribution 4.0 License, + anyone (the author, his/her institution/company, the publisher, as well + as the public) is free to copy, distribute, transmit, and adapt the work as + long as the original author is given credit (see above). Therefore, specific + usage rights cannot be reserved by the author or his/her institution/company, + and the publisher cannot include the statement "all rights reserved" + in any published paper.</p> + + <p>A copyright transfer from the author to his/her institution/company + can be expressed in a special "copyright statement" at the end of the + publication. Authors are asked to include the following sentence: + "The author's copyright for this publication has been transferred + to [institution/company]".</p> + + + <h3>Reproduction requests</h3> + <p>All articles published by SciPost are licensed under the Creative + Commons Attribution 4.0 License (see details above) together with + an author copyright. Therefore, there is no need from the publisher's + side to give permission for the reproduction of articles. + We suggest contacting the author to inform him/her about the further + usage of the material. However, as the author decided to publish + the scientific results under the CC-BY licence, he/she consented + to share the work under the condition that the original authors + be given credit.</p> + </div> +</div> + +<hr> +<div class="row"> + <div class="col-12"> + <h2 id="author_obligations">Author obligations</h2> + <ol> + <li>The primary obligation of the author(s) is to present a scientifically + accurate account of the research performed, as concisely and objectively + as possible, and with a discussion on its significance.</li> + <li>A paper should contain sufficient detail and references to original + sources of information to permit peers to reproduce the work.</li> + <li>Conciseness should not come at the expense of scientific accuracy + and completeness.</li> + <li>The abstract should be comprehensive and in faithful + correspondence to the contents of the paper.</li> + <li>Papers must be written in English, and authors should pay + attention to correct spelling and grammar. Insufficient quality of + spelling and grammar constitutes a sufficient reason for rejection.</li> + <li>Authors should cite all publications which have been influential in + performing the reported work, and which can orient the reader to the + earlier work necessary to understand the reported investigation. + Privately obtained information (conversation, correspondence or discussion) + should not be used or reported in the work without explicit permission + from the originator. Information obtained while performing confidential + services such as reporting on manuscripts or grant applications should + be treated similarly.</li> + <li>Fragmentation of research papers is to be avoided. Scientists should + organize publications such that each paper gives a complete account + of a particular project.</li> + <li>Authors should not submit manuscripts describing essentially the same + research to more than one journal.</li> + <li>Criticisms of earlier literature can be justified; personal criticism + shall however never be considered appropriate.</li> + <li>Only persons who have significantly contributed to the research and + to the redaction of the manuscript should be listed as authors. The + submitting author attests to the fact that other named authors have + seen the final version of the paper and have agreed to its submission. + Deceased persons who meet the criteria for co-authorship should be + included, with a footnote reporting the date of death. In no case should + fictitious names be listed as co-authors. The submitter accepts the + responsibility of having included all appropriate persons as co-authors, + and none that are inappropriate.</li> + </ol> + </div> +</div> + +<hr> +<div class="row"> + <div class="col-12"> + <h2 id="referee_code_of_conduct">Referee code of conduct</h2> + <ul> + <li>Contributors asked to referee should promptly accept or decline the + task assigned to them.</li> + <li>Following acceptance, the referee should provide a report within the + allocated refereeing period. It is preferable to deliver a shorter report + within the expected time than no report at all.</li> + <li>A Contributor should not referee a paper authored or co-authored by + someone with whom the referee has a personal or professional (hierarchic) + connection if this has the potential to bias the judgement.</li> + <li>A Contributor should not referee a paper authored or co-authored by + someone with whom the referee has published in the preceding three years.</li> + <li>A Contributor should not agree to referee if there is any doubt on a + possible conflict of interest issuing from close links between the work + to be refereed and the Contributor's own work.</li> + <li>A Contributor who feels insufficiently qualified to fulfill a given + refereeing task should decline it as promptly as possible.</li> + <li>Reports should be objective and evidence-based, and focus primarily on + the scientific validity, significance and originality of the manuscript + under consideration.</li> + <li>Judgements provided should be supported by sufficient evidence in the + form of explanations or references to other works, in order to make them + clearly understandable. Any claim of preexisting material must be + accompanied with the relevant citation.</li> + <li>A Referee should also assess the level of clarity of the manuscript, + as well as its general formatting and level of grammar.</li> + </ul> </div> +</div> - <p>These complement the SciPost <a href="{% url 'scipost:terms_and_conditions' %}">Terms and Conditions</a>.</p> - - <br/> - <hr class="hr12"/> - <h2>General</h2> - - <h3>SciPost expects the following from submitters:</h3> - <ul> - <li>The manuscript which is submitted for publication has not been published before except in - the form of an abstract or electronic preprint or other similar formats which have not undergone - peer review. It is also not under consideration elsewhere for peer-reviewed publication.</li> - <li>Each submission will automatically be checked for plagiarism: SciPost is a Participating Publisher of Crossref's Similarity Check. In their own interest, authors - should avoid any ambiguous case by clearly quoting and citing original sources.</li> - <li>The submission has been approved by all authors and tacitly or explicitly by the relevant - authorities and/or institutes where the work was carried out.</li> - <li>In the case of acceptance for publication, the authors agree to the terms of the - <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International (CC BY 4.0) License</a>. - This means that you are free to use, reproduce and distribute the articles and related content (unless otherwise noted), - for commercial and noncommercial purposes, subject to the citation of the original source in accordance with the CC-BY license, - in particular section 3a.</li> - <li>The authors have secured the right to reproduce any material in their work which has - already been published elsewhere.</li> - <li>The authors agree with the <a href="#license_and_copyright_agreement">license and copyright agreement</a>.</li> - <li>The authors have agreed to and are in compliance with the <a href="#author_obligations">general author obligations</a>.</li> - <li>The authors of a given manuscript are formally - represented by the author having submitted the manuscript.</li> - <li>During the evaluation phase, Editorial Fellows will follow the - <a href="{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a> - and referees will follow the <a href="#referee_code_of_conduct">code of conduct for referees</a>.</li> - <li>The SciPost Administration reserves the right to remove or censor reports or comments - if they contain inappropriate material or if they are insufficiently substantial in nature or - of insufficient direct relevance to the manuscript under consideration.</li> - <li>The use of general descriptive names, trade names, trademarks etc. in the published - articles of SciPost journals, even if not specifically identified, does not imply that these - names are not protected by the relevant laws and regulations.</li> - <li>The Fellows of the Editorial College do their best to shepherd the refereeing process - as carefully as possible but cannot assume legal responsibility for the outcome and - aftermath of acceptance or rejection.</li> - <li>While the contents of the journals is believed to be true and accurate on the date each - article is published, neither the authors, the Editorial Fellows, Stichting SciPost or any of - its officers and Board members can accept any legal responsibility for any errors or - omissions that may have occurred. SciPost make no guarantee, expressed or implied, with - respect to the material contained herein. Any opinions expressed in SciPost journals are - the views of the authors and are not the views of SciPost.</li> - </ul> - <br/> - <hr class="hr12"/> - - <h2>Open Access policy</h2> - <p>All SciPost Journals are Open Access which means that all content is freely available without charge to the user or his/her institution. Users are allowed to read, download, copy, distribute, print, search, or link to the full texts of the articles, or use them for any other lawful purpose, without asking prior permission from the publisher or the author. This is in accordance with the BOAI definition of Open Access.</p> - - <br/> - <hr class="hr12"/> - - <h2 id="license_and_copyright_agreement">License and copyright agreement</h2> - <p>The following license and copyright agreement is valid for any article published in any - SciPost journal and web portal.</p> - <h3>Author's certification</h3> - <p>By submitting their manuscript, the authors certify the following:</p> - <ul> - <li>They are authorized by their co-authors to enter into these agreements.</li> - <li>The work described has not been published before except in - the form of an abstract or electronic preprint or other similar formats which have not undergone - peer review; it is not under consideration for publication elsewhere; its publication has been - approved by the responsible authorities, tacitly or explicitly, of the institutes where the work - was performed.</li> - <li>They have secured the right to reproduce any material that has already been published or - copyrighted elsewhere.</li> - <li>They agree to the following license and copyright agreement:</li> - </ul> - <h3>Copyright</h3> - <ul> - <li>The copyright of any article is retained by the author(s). More information on the transfer of - copyright can be found below.</li> - <li>Authors grant SciPost a license to publish the article and identify itself as the original publisher.</li> - <li>Authors grant SciPost commercial rights to produce hardcopy volumes of the journal for purchase by - libraries and individuals.</li> - <li>Authors grant any third party the right to use the article freely under the stipulation that the - original authors are given credit and the appropriate citation details are mentioned.</li> - <li>The article is distributed under the - <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International (CC BY 4.0) License</a>. - Unless otherwise stated, any associated published material is distributed under the same license. - If one or more authors are US government employees, the paper can be published under the - terms of the <a href="http://creativecommons.org/licenses/cc0">Creative Commons CC0 public domain dedication</a>.</li> - </ul> - <br/> - <h3>Creative Commons Attribution 4.0 License</h3> - <h4>You are free to</h4> - <ul> - <li><b>Share</b> - copy and redistribute the material in any medium or format.</li> - <li><b>Adapt</b> - remix, transform, and build upon the material.</li> - </ul> - <p>for any purpose, even commercially. The licensor cannot revoke these freedoms - as long as you follow the license terms.</p> - <h4>Under the following terms:</h4> - <ul> - <li><b>Attribution</b> - You must give appropriate credit, provide a link to the - license, and indicate if changes were made. You may do so in any reasonable - manner, but not in any way that suggests the licensor endorses you or your use.</li> - </ul> - <p><b>No additional restrictions</b> - You may not apply legal terms or technological - measures that legally restrict others from doing anything the license permits.</p> - <h4>Notices:</h4> - <p>You do not have to comply with the license for elements of the material in - the public domain or where your use is permitted by an applicable exception - or limitation.</p> - <p>No warranties are given. The license may not give you all of the permissions - necessary for your intended use. For example, other rights such as publicity, - privacy, or moral rights may limit how you use the material.</p> - - <h4>View the <a href="http://creativecommons.org/licenses/by/4.0/">full legal code</a> - of the license.</h4> - - <h3>Copyright transfers</h3> - <p>Many authors have strict regulations in their employment contract regarding - their publications. A transfer of copyright to the institution or company - is common as well as the reservation of specific usage rights. - In open-access publications in combination with the Creative Commons License, - a transfer of the copyright to the institution is possible as it belongs - to the author anyway.</p> - - <p>Any usage rights are regulated through the Creative Commons License. - As SciPost uses the Creative Commons Attribution 4.0 License, - anyone (the author, his/her institution/company, the publisher, as well - as the public) is free to copy, distribute, transmit, and adapt the work as - long as the original author is given credit (see above). Therefore, specific - usage rights cannot be reserved by the author or his/her institution/company, - and the publisher cannot include the statement "all rights reserved" - in any published paper.</p> - - <p>A copyright transfer from the author to his/her institution/company - can be expressed in a special "copyright statement" at the end of the - publication. Authors are asked to include the following sentence: - "The author's copyright for this publication has been transferred - to [institution/company]".</p> - - - <h3>Reproduction requests</h3> - <p>All articles published by SciPost are licensed under the Creative - Commons Attribution 4.0 License (see details above) together with - an author copyright. Therefore, there is no need from the publisher's - side to give permission for the reproduction of articles. - We suggest contacting the author to inform him/her about the further - usage of the material. However, as the author decided to publish - the scientific results under the CC-BY licence, he/she consented - to share the work under the condition that the original authors - be given credit.</p> - - <br/> - <hr class="hr12"/> - <h2 id="author_obligations">Author obligations</h2> - <ol> - <li>The primary obligation of the author(s) is to present a scientifically - accurate account of the research performed, as concisely and objectively - as possible, and with a discussion on its significance.</li> - <li>A paper should contain sufficient detail and references to original - sources of information to permit peers to reproduce the work.</li> - <li>Conciseness should not come at the expense of scientific accuracy - and completeness.</li> - <li>The abstract should be comprehensive and in faithful - correspondence to the contents of the paper.</li> - <li>Papers must be written in English, and authors should pay - attention to correct spelling and grammar. Insufficient quality of - spelling and grammar constitutes a sufficient reason for rejection.</li> - <li>Authors should cite all publications which have been influential in - performing the reported work, and which can orient the reader to the - earlier work necessary to understand the reported investigation. - Privately obtained information (conversation, correspondence or discussion) - should not be used or reported in the work without explicit permission - from the originator. Information obtained while performing confidential - services such as reporting on manuscripts or grant applications should - be treated similarly.</li> - <li>Fragmentation of research papers is to be avoided. Scientists should - organize publications such that each paper gives a complete account - of a particular project.</li> - <li>Authors should not submit manuscripts describing essentially the same - research to more than one journal.</li> - <li>Criticisms of earlier literature can be justified; personal criticism - shall however never be considered appropriate.</li> - <li>Only persons who have significantly contributed to the research and - to the redaction of the manuscript should be listed as authors. The - submitting author attests to the fact that other named authors have - seen the final version of the paper and have agreed to its submission. - Deceased persons who meet the criteria for co-authorship should be - included, with a footnote reporting the date of death. In no case should - fictitious names be listed as co-authors. The submitter accepts the - responsibility of having included all appropriate persons as co-authors, - and none that are inappropriate.</li> - </ol> - - <br/> - <hr class="hr12"/> - <h2 id="referee_code_of_conduct">Referee code of conduct</h2> - <ul> - <li>Contributors asked to referee should promptly accept or decline the - task assigned to them.</li> - <li>Following acceptance, the referee should provide a report within the - allocated refereeing period. It is preferable to deliver a shorter report - within the expected time than no report at all.</li> - <li>A Contributor should not referee a paper authored or co-authored by - someone with whom the referee has a personal or professional (hierarchic) - connection if this has the potential to bias the judgement.</li> - <li>A Contributor should not referee a paper authored or co-authored by - someone with whom the referee has published in the preceding three years.</li> - <li>A Contributor should not agree to referee if there is any doubt on a - possible conflict of interest issuing from close links between the work - to be refereed and the Contributor's own work.</li> - <li>A Contributor who feels insufficiently qualified to fulfill a given - refereeing task should decline it as promptly as possible.</li> - <li>Reports should be objective and evidence-based, and focus primarily on - the scientific validity, significance and originality of the manuscript - under consideration.</li> - <li>Judgements provided should be supported by sufficient evidence in the - form of explanations or references to other works, in order to make them - clearly understandable. Any claim of preexisting material must be - accompanied with the relevant citation.</li> - <li>A Referee should also assess the level of clarity of the manuscript, - as well as its general formatting and level of grammar.</li> - </ul> - -</section> - - -{% endblock bodysup %} +{% endblock content %} diff --git a/journals/templatetags/lookup.py b/journals/templatetags/lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..e262a10bf1911590174b222822388bf899d160bd --- /dev/null +++ b/journals/templatetags/lookup.py @@ -0,0 +1,23 @@ +from ajax_select import register, LookupChannel +from ..models import Publication + + +@register('publication_lookup') +class PublicationLookup(LookupChannel): + model = Publication + + def get_query(self, q, request): + return (self.model.objects + .published() + .order_by('-publication_date') + .filter(title__icontains=q)[:10]) + + def format_item_display(self, item): + '''(HTML) format item for displaying item in the selected deck area.''' + return u"<span class='auto_lookup_display'>%s</span>" % item + + def format_match(self, item): + '''(HTML) Format item for displaying in the dropdown.''' + return u"%s (%s)<br><span class='text-muted'>by %s</span>" % (item.title, + item.doi_string, + item.author_list) diff --git a/news/templates/news/news_card_content.html b/news/templates/news/news_card_content.html index c308bb5f3e63d65cc2ea00f99fe18a66aa49f4aa..c034d9e95700453c0e1f6b47e330bf0541ba2b1a 100644 --- a/news/templates/news/news_card_content.html +++ b/news/templates/news/news_card_content.html @@ -1,11 +1,11 @@ -<div class="card-header p-2 border-0"> +<div class="card-block px-0 pt-1 news-item"> <h3 class="card-title mb-0">{{news.headline}}</h3> -</div> -<div class="card-block px-2 pt-1"> - <h4 class="text-muted font-weight-bold">{{news.date|date:'Y-n-j'}}</h4> - <div>{{news.blurb|linebreaks}}</div> + <div class="px-1 mt-1"> + <h4 class="text-muted font-weight-bold">{{news.date|date:'Y-n-j'}}</h4> + <div>{{news.blurb|linebreaks}}</div> - {% if news.followup_link %} - <a href="{{news.followup_link}}">{{news.followup_link_text}}</a> - {% endif %} + {% if news.followup_link %} + <a href="{{news.followup_link}}">{{news.followup_link_text}}</a> + {% endif %} + </div> </div> diff --git a/requirements.txt b/requirements.txt index a5406b89e2df28e0fe9829ed55487fbc1455a010..ab8676cb6d7f18193124c876281fe70288093f81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ alabaster==0.7.9 Babel==2.3.4 Django==1.10.3 +django_ajax_selects==1.5.2 django-countries==4.0 django-crispy-forms==1.6.1 django-debug-toolbar==1.7 diff --git a/scipost/admin.py b/scipost/admin.py index d4f2fc40aa0397c46abfdbe6f28476cf39902db5..af7ea2f7840bc90c5f207a131569457c6694055d 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -100,6 +100,9 @@ admin.site.register(DraftInvitation, DraftInvitationAdmin) class RegistrationInvitationAdmin(admin.ModelAdmin): search_fields = ['first_name', 'last_name', 'email', 'invitation_key'] + list_display = ['__str__', 'invitation_type', 'invited_by', 'responded'] + list_filter = ['invitation_type', 'message_style', 'responded', 'declined'] + date_hierarchy = 'date_sent' admin.site.register(RegistrationInvitation, RegistrationInvitationAdmin) diff --git a/scipost/constants.py b/scipost/constants.py index 2956972ceba3d9945703e54d685f3a3789a88c73..790f06d6669469d03a4d664a8a4d0a865a6d0095 100644 --- a/scipost/constants.py +++ b/scipost/constants.py @@ -125,7 +125,7 @@ subject_areas_dict = {} for k in subject_areas_raw_dict.keys(): subject_areas_dict.update(dict(subject_areas_raw_dict[k])) - +CONTRIBUTOR_NORMAL = 1 CONTRIBUTOR_STATUS = ( # status determine the type of Contributor: # 0: newly registered (unverified; not allowed to submit, comment or vote) @@ -137,7 +137,7 @@ CONTRIBUTOR_STATUS = ( # -3: barred from SciPost (abusive behaviour) # -4: disabled account (deceased) (0, 'newly registered'), - (1, 'normal user'), + (CONTRIBUTOR_NORMAL, 'normal user'), (-1, 'not a professional scientist'), (-2, 'other account already exists'), (-3, 'barred from SciPost'), diff --git a/scipost/forms.py b/scipost/forms.py index 6fa06cc261eec4e8f2e7999757031b7950c0394f..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 @@ -7,8 +10,9 @@ from django_countries.widgets import CountrySelectWidget from django_countries.fields import LazyTypedChoiceField from captcha.fields import ReCaptchaField +from ajax_select.fields import AutoCompleteSelectField from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Div, Field, HTML, Submit +from crispy_forms.layout import Layout, Div, Field, HTML from .constants import SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES from .models import Contributor, DraftInvitation, RegistrationInvitation,\ @@ -16,8 +20,6 @@ from .models import Contributor, DraftInvitation, RegistrationInvitation,\ UnavailabilityPeriod, PrecookedEmail from journals.models import Publication -from submissions.constants import SUBMISSION_STATUS_PUBLICLY_UNLISTED -from submissions.models import Submission REGISTRATION_REFUSAL_CHOICES = ( @@ -30,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', @@ -55,13 +63,54 @@ 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): + cited_in_submission = AutoCompleteSelectField('submissions_lookup', required=False) + cited_in_publication = AutoCompleteSelectField('publication_lookup', required=False) + class Meta: model = DraftInvitation fields = ['title', 'first_name', 'last_name', 'email', @@ -69,32 +118,11 @@ class DraftInvitationForm(forms.ModelForm): 'cited_in_submission', 'cited_in_publication' ] - def __init__(self, *args, **kwargs): - super(DraftInvitationForm, self).__init__(*args, **kwargs) - self.fields['cited_in_submission'] = forms.ModelChoiceField( - queryset=Submission.objects.all().exclude( - status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED).order_by('-submission_date'), - required=False) - self.fields['cited_in_publication'] = forms.ModelChoiceField( - queryset=Publication.objects.all().order_by('-publication_date'), - required=False) - self.helper = FormHelper() - self.helper.layout = Layout( - Div( - Div( - Field('title'), Field('first_name'), Field('last_name'), - Field('email'), Field('invitation_type'), - css_class="col-6"), - Div( - Submit('submit', 'Save draft'), - css_class="col-6"), - css_class="row"), - Div(Field('cited_in_submission'),), - Div(Field('cited_in_publication'),), - ) - class RegistrationInvitationForm(forms.ModelForm): + cited_in_submission = AutoCompleteSelectField('submissions_lookup', required=False) + cited_in_publication = AutoCompleteSelectField('publication_lookup', required=False) + class Meta: model = RegistrationInvitation fields = ['title', 'first_name', 'last_name', 'email', @@ -104,33 +132,20 @@ class RegistrationInvitationForm(forms.ModelForm): ] def __init__(self, *args, **kwargs): + 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) self.fields['personal_message'].widget.attrs.update( {'placeholder': ('NOTE: a personal phrase or two.' ' The bulk of the text will be auto-generated.')}) - self.fields['cited_in_submission'] = forms.ModelChoiceField( - queryset=Submission.objects.all().exclude( - status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED).order_by('-submission_date'), - required=False) + + self.fields['cited_in_publication'] = forms.ModelChoiceField( queryset=Publication.objects.all().order_by('-publication_date'), required=False) - self.helper = FormHelper() - self.helper.layout = Layout( - Div( - Div( - Field('title'), Field('first_name'), Field('last_name'), - Field('email'), Field('invitation_type'), - css_class="col-6"), - Div( - Field('message_style'), - Field('personal_message'), - Submit('submit', 'Send invitation'), - css_class="col-6"), - css_class="row"), - Div(Field('cited_in_submission'),), - Div(Field('cited_in_publication'),), - ) class ModifyPersonalMessageForm(forms.Form): @@ -142,6 +157,18 @@ class UpdateUserDataForm(forms.ModelForm): model = User fields = ['email', 'first_name', 'last_name'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['last_name'].widget.attrs['readonly'] = True + + def clean_last_name(self): + '''Make sure the `last_name` cannot be saved via this form.''' + instance = getattr(self, 'instance', None) + if instance and instance.last_name: + return instance.last_name + else: + return self.cleaned_data['last_name'] + class UpdatePersonalDataForm(forms.ModelForm): class Meta: @@ -154,13 +181,15 @@ class UpdatePersonalDataForm(forms.ModelForm): class VetRegistrationForm(forms.Form): - promote_to_registered_contributor = forms.BooleanField(required=False, - label='Accept registration') - refuse = forms.BooleanField(required=False) + decision = forms.ChoiceField(widget=forms.RadioSelect, + choices=((True, 'Accept registration'), (False, 'Refuse'))) refusal_reason = forms.ChoiceField(choices=REGISTRATION_REFUSAL_CHOICES, required=False) email_response_field = forms.CharField(widget=forms.Textarea(), label='Justification (optional)', required=False) + def promote_to_registered_contributor(self): + return bool(self.cleaned_data.get('decision', False)) + class AuthenticationForm(forms.Form): username = forms.CharField(label='Username', max_length=100) diff --git a/scipost/management/commands/export_contributors.py b/scipost/management/commands/export_contributors.py new file mode 100644 index 0000000000000000000000000000000000000000..153ace0457f0ce289e232cecbe259e0a62933075 --- /dev/null +++ b/scipost/management/commands/export_contributors.py @@ -0,0 +1,52 @@ +import csv +from datetime import datetime + +from django.core.management.base import BaseCommand + +from ...constants import CONTRIBUTOR_NORMAL +from ...models import Contributor + + +class Command(BaseCommand): + """ + Use this command to export the Contributor table. One could filter the export + by simply using the --group argument. + + For example, one could run: + $ ./manage.py export_contributors --group 'Registered Contributors' + """ + def add_arguments(self, parser): + parser.add_argument( + '--group', + dest='group', + default=False, + type=str, + help='Filter the contributors by their group name' + ) + + def handle(self, *args, **kwargs): + # File variables + filename = 'export_%s_contributors_%s.csv' % (datetime.now().strftime('%Y_%m_%d_%H_%M'), + kwargs.get('group', '')) + filename = filename.replace(' ', '_') + fieldnames = ['first_name', 'last_name', 'email_address'] + + # Query + queryset = Contributor.objects.filter(user__is_active=True, + status=CONTRIBUTOR_NORMAL, + accepts_SciPost_emails=True) + if kwargs['group']: + queryset = queryset.filter(user__groups__name=kwargs['group']) + + # Open + write the file + with open(filename, 'w', newline='') as _file: + writer = csv.writer(_file, quotechar='|', quoting=csv.QUOTE_MINIMAL) + writer.writerow(fieldnames) + n = 0 + for contributor in queryset: + user = contributor.user + writer.writerow([user.first_name, user.last_name, user.email]) + n += 1 + self.stdout.write(self.style.SUCCESS('Successfully wrote %i Contributors to file %s.' % ( + n, filename + ))) 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 a69f42bb09f00794fe559d3a1470270d6e0d4d44..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> @@ -282,14 +305,15 @@ class RegistrationInvitation(models.Model): default=INVITATION_CONTRIBUTOR) cited_in_submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, - blank=True, null=True) + blank=True, null=True, + related_name='registration_invitations') cited_in_publication = models.ForeignKey('journals.Publication', on_delete=models.CASCADE, 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 +325,7 @@ class RegistrationInvitation(models.Model): declined = models.BooleanField(default=False) def __str__(self): - return (self.invitation_type + ' ' + self.first_name + ' ' + self.last_name + return (self.first_name + ' ' + self.last_name + ' on ' + self.date_sent.strftime("%Y-%m-%d")) diff --git a/scipost/static/scipost/about.js b/scipost/static/scipost/about.js index 748aaa14a672c770beaba534a7f9d1306956bd8c..fb7e771813f7dec0608ee8e38ac504302061530b 100644 --- a/scipost/static/scipost/about.js +++ b/scipost/static/scipost/about.js @@ -5,6 +5,9 @@ $(function() { var el = $($(this).attr('data-target')); el.toggle(); + // Switch texts of link + $('[href="' + $(this).attr('href') + '"]').toggle(); + // Reset active search after closing the box if(!el.is(':visible')) { $('.all-specializations .specialization') @@ -33,7 +36,7 @@ $(function() { .on('search-specialization', function() { // Reset: searching multiple specializations is not supported $('.search-contributors.active-search').removeClass('active-search'); - $('.contributor-col.active').removeClass('active'); + $('.contributor.active').removeClass('active'); $('.specialization.active-search').not(this).removeClass('active-search'); var el = $(this); @@ -44,7 +47,7 @@ $(function() { // Add class to specialized Contributors var code = el.attr('data-specialization'); $('.single[data-specialization="' + code + '"]') - .parents('.contributor-col') + .parents('.contributor') .addClass('active'); } }); diff --git a/scipost/static/scipost/assets/css/_about.scss b/scipost/static/scipost/assets/css/_about.scss index c7c0b58d2f159bb6de4ea6e180f7aaf6a8598f86..0ed307a0191279d4e1ddaecb3551976a3cac4d0a 100644 --- a/scipost/static/scipost/assets/css/_about.scss +++ b/scipost/static/scipost/assets/css/_about.scss @@ -1,18 +1,18 @@ -.search-contributors.active-search .contributor-col { +.search-contributors.active-search .contributor { display: none; &.active { - display: block; + display: inline-block; } } .contributor { - margin: 0.25rem 0; - padding: 0.05rem 0.75rem; - border-radius: 0.15rem; -webkit-transition: all 0.05s ease-in-out; -o-transition: all 0.05s ease-in-out; transition: all 0.05s ease-in-out; + padding-left: 0.5rem; + padding-right: 0.5rem; + border: 0; &.hover-active { background-color: rgba(104, 132, 194, 0.3); diff --git a/scipost/static/scipost/assets/css/_cards.scss b/scipost/static/scipost/assets/css/_cards.scss index ad4e4c7d5a163a758cd4f4afaa2a735d06c68841..a8625fded65fb44c56709bf4d3829627cf888a69 100644 --- a/scipost/static/scipost/assets/css/_cards.scss +++ b/scipost/static/scipost/assets/css/_cards.scss @@ -30,10 +30,12 @@ line-height: 1.5; } -.card.card-news { - .card-header { +.card-news { + .news-item .card-title { background-color: $scipost-darkblue; color: $scipost-light; + padding: 0.5rem; + border-radius: 1.4px; } } @@ -56,3 +58,14 @@ } } } + +.card.radio-option { + label { + cursor: pointer; + } + + &.checked, + &:hover { + border-color: $scipost-darkblue; + } +} diff --git a/scipost/static/scipost/assets/css/_general.scss b/scipost/static/scipost/assets/css/_general.scss index 74b43d229c4bd51dd0377b3a424c96b2762b1783..e1758c796a62474c882946a3ea259288fc6dc0a1 100644 --- a/scipost/static/scipost/assets/css/_general.scss +++ b/scipost/static/scipost/assets/css/_general.scss @@ -13,3 +13,13 @@ .bg-transparent { background-color: transparent; } + +body #MathJax_Message { + left: 1rem; + bottom: 1rem; + background-color: #f9f9f9; + border: 1px solid #f4f4f4; + border-radius: 2px; + color: #002b49; + opacity: 0.9; +} diff --git a/scipost/static/scipost/assets/css/_tables.scss b/scipost/static/scipost/assets/css/_tables.scss index 3dd838b350a0bd2008141567cad90f4637a6df2b..851ef97467fe1cc81ff3c3a867511b57278a32ba 100644 --- a/scipost/static/scipost/assets/css/_tables.scss +++ b/scipost/static/scipost/assets/css/_tables.scss @@ -11,6 +11,7 @@ background-color: #f4f4f4; } +table.commentary td, table.submission td { padding: 0.1em 0.7em; diff --git a/scipost/templates/scipost/_draft_registration_tables.html b/scipost/templates/scipost/_draft_registration_tables.html new file mode 100644 index 0000000000000000000000000000000000000000..84b73ce78a71408b64b688fb5ffd2747760d9f26 --- /dev/null +++ b/scipost/templates/scipost/_draft_registration_tables.html @@ -0,0 +1,357 @@ +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Invitations sent (response pending)</h2> + + <h3>Editorial Fellows ({{sent_reg_inv_fellows|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_sent_reg_inv_fellows">view/hide</a></h3> + + <table class="table" id="table_sent_reg_inv_fellows" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for fellow in sent_reg_inv_fellows %} + <tr> + <td>{{ fellow.last_name }}</td> + <td>{{ fellow.first_name }}</td> + <td>{{ fellow.email }}</td> + <td>{{ fellow.date_sent }} </td> + <td>{{ fellow.get_invitation_type_display }}</td> + <td>{{ fellow.invited_by.user.first_name }} {{ fellow.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Normal Contributors ({{sent_reg_inv_contrib|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_sent_reg_inv_contrib">view/hide</a></h3> + <table class="table" id="table_sent_reg_inv_contrib" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for fellow in sent_reg_inv_contrib %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + + <h3>Referees ({{sent_reg_inv_ref|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_sent_reg_inv_ref">view/hide</a></h3> + <table class="table" id="table_sent_reg_inv_ref" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in sent_reg_inv_ref %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Cited in sub ({{sent_reg_inv_cited_sub|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_sent_reg_inv_cited_sub">view/hide</a></h3> + <table class="table" id="table_sent_reg_inv_cited_sub" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in sent_reg_inv_cited_sub %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + + <h3>Cited in pub ({{sent_reg_inv_cited_pub|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_sent_reg_inv_cited_pub">view/hide</a></h3> + <table class="table" id="table_sent_reg_inv_cited_pub" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in sent_reg_inv_cited_pub %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Invitations sent (responded)</h2> + + <h3>Editorial Fellows ({{resp_reg_inv_fellow|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_resp_reg_inv_fellow">view/hide</a></h3> + <table class="table" id="table_resp_reg_inv_fellow" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in resp_reg_inv_fellow %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Normal Contributors ({{resp_reg_inv_contrib|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_resp_reg_inv_contrib">view/hide</a></h3> + <table class="table" id="table_resp_reg_inv_contrib" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in resp_reg_inv_contrib %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Referees ({{resp_reg_inv_ref|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_resp_reg_inv_ref">view/hide</a></h3> + <table class="table" id="table_resp_reg_inv_ref" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in resp_reg_inv_ref %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Cited in sub ({{resp_reg_inv_cited_sub|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_resp_reg_inv_cited_sub">view/hide</a></h3> + <table class="table" id="table_resp_reg_inv_cited_sub" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in resp_reg_inv_cited_sub %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Cited in pub ({{resp_reg_inv_cited_pub|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_resp_reg_inv_cited_pub">view/hide</a></h3> + <table class="table" id="table_resp_reg_inv_cited_pub" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in resp_reg_inv_cited_pub %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + + <h3>Declined ({{decl_reg_inv|length}}) - <a href="javascript:void(0)" data-toggle="toggle" data-target="#table_decl_reg_inv">view/hide</a></h3> + <table class="table" id="table_decl_reg_inv" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for invitation in decl_reg_inv %} + <tr> + <td>{{ invitation.last_name }}</td> + <td>{{ invitation.first_name }}</td> + <td>{{ invitation.email }}</td> + <td>{{ invitation.date_sent }} </td> + <td>{{ invitation.get_invitation_type_display }}</td> + <td>{{ invitation.invited_by.user.first_name }} {{ invitation.invited_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No invitations found.</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">List of already-registered contributors ({{names_reg_contributors|length}}) <small><a href="javascript:void(0)" data-toggle="toggle" data-target="#registered_contributors">view/hide</a></small></h3> + <div class="card-columns" id="registered_contributors" style="display: none;"> + {% for first_name, last_name in names_reg_contributors %} + <div class="card border-0"> + {{ last_name }}, {{ first_name }} + </div> + {% endfor %} + </div> + </div> +</div> diff --git a/scipost/templates/scipost/_personal_page_base.html b/scipost/templates/scipost/_personal_page_base.html new file mode 100644 index 0000000000000000000000000000000000000000..a60e5b5b1129a08d124e3bc4a5c52d9719eee89f --- /dev/null +++ b/scipost/templates/scipost/_personal_page_base.html @@ -0,0 +1,11 @@ +{% extends 'scipost/base.html' %} + +{% block breadcrumb %} + <nav class="breadcrumb py-md-2 px-0"> + <div class="container"> + {% block breadcrumb_items %} + <a href="{% url 'scipost:personal_page' %}" class="breadcrumb-item">Personal Page</a> + {% endblock %} + </div> + </nav> +{% endblock %} diff --git a/scipost/templates/scipost/about.html b/scipost/templates/scipost/about.html index ab5c6d10034b11280a7df11746f494f3715272fe..0e033b30f9eb394b6e905252cf07512e6f8bc852 100644 --- a/scipost/templates/scipost/about.html +++ b/scipost/templates/scipost/about.html @@ -129,7 +129,6 @@ <div class="col-md-3"> <ul> <li>Prof. <a target="_blank" href="http://www.ens-lyon.fr/PHYSIQUE/presentation/annuaire/maillet-jean-michel">J. M. Maillet</a><br/>(ENS Lyon)</li> - <li>Prof. <a target="_blank" href="http://people.sissa.it/~mussardo/">G. Mussardo</a><br/>(SISSA)</li> <li>Prof. <a target="_blank" href="http://www.ru.nl/ssi/members/theo_rasing/">T. Rasing</a><br/>(Radboud Univ. Nijmegen)</li> <li>Prof. <a target="_blank" href="http://atomchip.org/general-information/people/schmiedmayer/">J. Schmiedmayer</a><br/>(TU Vienna)</li> </ul> @@ -160,11 +159,12 @@ {% if codes %} <div class="row"> <div class="col-12"> - <a href="#editorial_college_{{ college|lower }}" class="d-block mb-1" data-toggle="toggle-show" data-target="#specializations-{{college|lower}}">Show Fellows by specialization</a> - <div id="specializations-{{college|lower}}" class="all-specializations" style="border: 1px solid; display: none;"> + <a href="#editorial_college_{{ college|lower }}" data-toggle="toggle-show" data-target="#specializations-{{college|lower}}">Show Fellows by specialization</a> + <a href="#editorial_college_{{ college|lower }}" style="display: none;" data-toggle="toggle-show" data-target="#specializations-{{college|lower}}">Show full list of Fellows</a> + <div id="specializations-{{college|lower}}" class="all-specializations mt-2" style="border: 1px solid; display: none;"> <div class="row"> <div class="col-12"> - <p class="text-muted">Hover to highlight, click to select</p> + <p class="text-muted">Hover to highlight or click to select</p> </div> <div class="col-md-6"> {% with total_count=codes|length %} @@ -184,20 +184,14 @@ {% endif %} <div class="row search-contributors" data-contributors="{{ college|lower }}"> - <div class="col-md-4"> - {% with total_count=college.current_fellows|length %} + <div class="col-12"> + <div class="card-columns"> {% for fellowship in college.current_fellows %} - <div class="col-12 contributor-col"> - <div class="contributor"> - {% include 'scipost/_contributor_short.html' with contributor=fellowship.contributor %} - </div> + <div class="card contributor"> + {% include 'scipost/_contributor_short.html' with contributor=fellowship.contributor %} </div> - {% if forloop.counter|is_modulo_one_third:total_count %} - </div> - <div class="col-md-4"> - {% endif %} {% endfor %} - {% endwith %} + </div> </div> </div> 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/change_password.html b/scipost/templates/scipost/change_password.html index 23648deddf01f64e241c4f8baeb42f0bdba7089e..c0e94de5a9504fcd7bc707c9e250539acf6bfbab 100644 --- a/scipost/templates/scipost/change_password.html +++ b/scipost/templates/scipost/change_password.html @@ -1,29 +1,31 @@ {% extends 'scipost/base.html' %} +{% load bootstrap %} + {% block pagetitle %}: change password{% endblock pagetitle %} -{% block bodysup %} +{% block content %} {% if ack %} - <section> - <h1>Your SciPost password has been successfully changed</h1> - </section> + <div class="row"> + <div class="col-12"> + <h1>Your SciPost password has been successfully changed</h1> + </div> + </div> {% else %} - <section> - <h1>Change your SciPost password</h1> - <form action="{% url 'scipost:change_password' %}" method="post"> - {% csrf_token %} - <table> - <ul> - {{ form.as_table }} - </ul> - </table> - <input type="submit" value="Change" /> - </form> - {% if errormessage %} - <p>{{ errormessage }}</p> - {% endif %} - </section> + <div class="row"> + <div class="col-lg-8 offset-lg-2"> + <h1 class="highlight">Change your SciPost password</h1> + {% if errormessage %} + <p class="text-danger">{{ errormessage }}</p> + {% endif %} + <form action="{% url 'scipost:change_password' %}" method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input type="submit" class="btn btn-secondary" value="Change" /> + </form> + </div> + </div> {% endif %} -{% endblock bodysup %} +{% endblock content %} diff --git a/scipost/templates/scipost/citation_notifications.html b/scipost/templates/scipost/citation_notifications.html index 65bcd3e8f6a8b38b1079caf63d43917ce895e2c4..49c6616840c6dd543d95e78c5b4fa3cb724c30a0 100644 --- a/scipost/templates/scipost/citation_notifications.html +++ b/scipost/templates/scipost/citation_notifications.html @@ -1,29 +1,36 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} {% block pagetitle %}: citation notifications{% endblock pagetitle %} -{% block bodysup %} +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Pool</span> +{% endblock %} +{% block content %} -<section> - <div class="flex-greybox"> - <h1>Citation notifications to process</h1> - </div> - {% if errormessage %} - <h3 style="color: red;">{{ errormessage }}</h3> - {% endif %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Citation notifications to process</h1> + {% if errormessage %} + <h3 class="text-danger">{{ errormessage }}</h3> + {% endif %} - {% if unprocessed_notifications %} - <ul> - {% for un in unprocessed_notifications %} - <li>{{ un }} <a href="{% url 'scipost:process_citation_notification' cn_id=un.id %}">Process this notification</a></li> - {% endfor %} - </ul> - {% else %} - <h3>There are no citation notifications to process.</h3> - <p>Return to your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> - {% endif %} + <ul> + {% for un in unprocessed_notifications %} + <li> + {{ un }} <a href="{% url 'scipost:process_citation_notification' cn_id=un.id %}">Process this notification</a> + </li> + {% empty %} + <li>There are no citation notifications to process.</li> + {% endfor %} + </ul> + <p>Return to your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> + </div> +</div> -{% endblock bodysup %} + + +{% endblock content %} diff --git a/scipost/templates/scipost/draft_registration_invitation.html b/scipost/templates/scipost/draft_registration_invitation.html index 6c73c5ec64c580faba9176d924fccf2aa16eace2..b3afdc15d3ececf1e95a18abc3900b0a5791f3f2 100644 --- a/scipost/templates/scipost/draft_registration_invitation.html +++ b/scipost/templates/scipost/draft_registration_invitation.html @@ -1,268 +1,93 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} + +{% load bootstrap %} {% block pagetitle %}: registration invitations{% endblock pagetitle %} -{% block bodysup %} +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Pool</span> +{% endblock %} -{% load scipost_extras %} +{% block content %} <script> - $(document).ready(function(){ - - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - - $('select#id_invitation_type').on('change', function() { - switch ($('select#id_invitation_type').val()) { - case "ci": - $("#div_id_cited_in_submission").show(); - $("#div_id_cited_in_publication").hide(); - break; - case "cp": - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").show(); - break; - default: - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - } - }); - }); +$(document).ready(function(){ + + $('#id_invitation_type').on('change', function() { + switch ($(this).val()) { + case "ci": + $("#id_cited_in_submission").parents('.form-group').show(); + $("#id_cited_in_publication").parents('.form-group').hide(); + break; + case "cp": + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').show(); + break; + default: + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').hide(); + } + }).trigger('change'); +}); </script> -<section> - <div class="flex-greybox"> - <h1>Draft a registration invitation</h1> - </div> -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Draft a new invitation:</h2> - </div> - {% if errormessage %} - <h3 style="color: red;">{{ errormessage }}</h3> - {% endif %} - <form action="{% url 'scipost:draft_registration_invitation' %}" method="post"> - {% csrf_token %} - {% load crispy_forms_tags %} - {% crispy draft_inv_form %} - </form> -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Existing drafts (to be processed by Admin):</h2> - </div> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date drafted</td><td>Type</td><td>Drafted by</td></tr> - {% for draft in existing_drafts %} - <tr> - <td>{{ draft.last_name }}</td> - <td>{{ draft.first_name }}</td> - <td>{{ draft.email }}</td> - <td>{{ draft.date_drafted }} </td> - <td>{{ draft.invitation_type }}</td> - <td>{{ draft.drafted_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Invitations sent (response pending) EF ({{ nr_sent_reg_inv_fellows }}), C ({{ nr_sent_reg_inv_contrib }}), R ({{ nr_sent_reg_inv_ref }}), cited in sub ({{ nr_sent_reg_inv_cited_sub }}), cited in pub ({{ nr_sent_reg_inv_cited_pub }}):</h2> - </div> - <hr class="hr6"/> - <h3>Editorial Fellows ({{ nr_sent_reg_inv_fellows }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_fellows %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Normal Contributors ({{ nr_sent_reg_inv_contrib }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_contrib %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Referees ({{ nr_sent_reg_inv_ref }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_ref %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Cited in sub ({{ nr_sent_reg_inv_cited_sub }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_cited_sub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Cited in pub ({{ nr_sent_reg_inv_cited_pub }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_cited_pub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> -</section> - <hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Invitations sent (responded) EF ({{ nr_resp_reg_inv_fellows }}), C ({{ nr_resp_reg_inv_contrib }}), R ({{ nr_resp_reg_inv_ref }}), cited in sub ({{ nr_resp_reg_inv_cited_sub }}), cited in pub ({{ nr_resp_reg_inv_cited_pub }}):</h2> - </div> - <hr class="hr6"/> - <h3>Editorial Fellows ({{ nr_resp_reg_inv_fellows }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_fellows %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Normal Contributors ({{ nr_resp_reg_inv_contrib}})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_contrib %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Referees ({{ nr_resp_reg_inv_ref }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_ref %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Cited in sub ({{ nr_resp_reg_inv_cited_sub }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_cited_sub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Cited in pub ({{ nr_resp_reg_inv_cited_pub }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_cited_pub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Declined</h3> - <table class="tableofInviteesDeclined"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in decl_reg_inv %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - -</section> - - -<hr class="hr12"/> - -<section> - <div class="flex-greybox"> - <h2>List of already-registered contributors:</h3> - </div> - <p> - {% for first_name, last_name in names_reg_contributors %} - {{ first_name }} {{ last_name }}, - {% endfor %} - </p> -</section> - -{% endblock bodysup %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Draft a registration invitation</h1> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Draft a new invitation</h2> + {% if errormessage %} + <h3 class="text-danger">{{ errormessage }}</h3> + {% endif %} + + <form action="{% url 'scipost:draft_registration_invitation' %}" method="post"> + {% csrf_token %} + {{draft_inv_form.media}} + {{draft_inv_form|bootstrap}} + <input type="submit" class="btn btn-secondary"/> + </form> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Existing drafts (to be processed by Admin) ({{existing_drafts|length}}) <small><a href="javascript:void(0)" data-toggle="toggle" data-target="#table_existing_drafts">view/hide</a></small></h2> + <table class="table" id="table_existing_drafts" style="display: none;"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date drafted</th> + <th>Type</th> + <th>Drafted by</th> + </tr> + </thead> + <tbody> + {% for draft in existing_drafts %} + <tr> + <td>{{ draft.last_name }}</td> + <td>{{ draft.first_name }}</td> + <td>{{ draft.email }}</td> + <td>{{ draft.date_drafted }} </td> + <td>{{ draft.get_invitation_type_display }}</td> + <td>{{ draft.drafted_by.user.first_name }} {{ draft.drafted_by.user.last_name }}</td> + </tr> + {% empty %} + <tr> + <td colspan="6">No drafts found.</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% include 'scipost/_draft_registration_tables.html' %} + +{% endblock %} diff --git a/scipost/templates/scipost/edit_draft_reg_inv.html b/scipost/templates/scipost/edit_draft_reg_inv.html index fd3e872cba4410dca215921871304c9a00ec1d7b..14a601fcce56ce0a207a5f8fa1d1e500ac24a2b1 100644 --- a/scipost/templates/scipost/edit_draft_reg_inv.html +++ b/scipost/templates/scipost/edit_draft_reg_inv.html @@ -1,56 +1,51 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} {% block pagetitle %}: edit draft reg inv{% endblock pagetitle %} -{% block bodysup %} +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'scipost:registration_invitations' %}" class="breadcrumb-item">Registration Invitations</a> + <span class="breadcrumb-item">Pool</span> +{% endblock %} + +{% load bootstrap %} + +{% block content %} <script> - $(document).ready(function(){ - - switch ($('select#id_invitation_type').val()) { - case "ci": - $("#div_id_cited_in_submission").show(); - $("#div_id_cited_in_publication").hide(); - break; - case "cp": - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").show(); - break; - default: - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - } - - $('select#id_invitation_type').on('change', function() { - switch ($('select#id_invitation_type').val()) { - case "ci": - $("#div_id_cited_in_submission").show(); - $("#div_id_cited_in_publication").hide(); - break; - case "cp": - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").show(); - break; - default: - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - } - }); - }); +$(document).ready(function(){ + + $('#id_invitation_type').on('change', function() { + switch ($(this).val()) { + case "ci": + $("#id_cited_in_submission").parents('.form-group').show(); + $("#id_cited_in_publication").parents('.form-group').hide(); + break; + case "cp": + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').show(); + break; + default: + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').hide(); + } + }).trigger('change'); +}); </script> -<section> - <div class="flex-greybox"> - <h1>Edit a draft registration invitation</h1> - </div> - {% if errormessage %} - <h3 style="color: red;">{{ errormessage }}</h3> - {% endif %} - <form action="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}" method="post"> - {% csrf_token %} - {% load crispy_forms_tags %} - {% crispy draft_inv_form %} - </form> -</section> - -{% endblock bodysup %} +<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"> + {% csrf_token %} + {{draft_inv_form.media}} + {{draft_inv_form|bootstrap}} + <input type="submit" class="btn btn-secondary"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/scipost/templates/scipost/index.html b/scipost/templates/scipost/index.html index 1c43a0a99ad538b5a1eadced63d2c39974155a03..696aff8f4f84a389bb1032b836ccb30793721c4c 100644 --- a/scipost/templates/scipost/index.html +++ b/scipost/templates/scipost/index.html @@ -5,129 +5,162 @@ {% block content %} <div class="row"> - {% if latest_newsitems %} - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title mb-0"><a href="{% url 'news:news' %}">News</a><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-14x14.png' %}" alt="Feed logo" width="14"></a></h1> - <h4 class="card-subtitle mb-0 pb-0 text-muted">Latest news and announcements.</h4> - </div> - {% for item in latest_newsitems %} - <div class="card card-news bg-transparent m-2"> - {% include 'news/news_card_content.html' with news=item %} + <div class="col-12"> + <div class="card-deck mb-2"> + <!-- News --> + <div class="card card-grey card-news"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'news:news' %}">News</a><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-14x14.png' %}" alt="Feed logo" width="14"></a></h1> + <h4 class="card-subtitle mb-0 pb-0 text-muted">Latest news and announcements.</h4> + <ul class="list-group list-group-flush"> + {% for item in latest_newsitems %} + <li class="list-group-item"> + {% include 'news/news_card_content.html' with news=item %} + </li> + {% empty %} + <li class="list-group-item"> + No current newsitems found. + </li> + {% endfor %} + <ul> </div> - {% endfor %} - </div> - </div> - {% endif %} - - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title mb-0"><a href="{% url 'scipost:about' %}">About SciPost</a></h1> - <h4 class="card-subtitle mb-2 text-muted">SciPost is a complete scientific publication portal managed by active professional scientists.</h4> - <p>It is purely online-based and offers openly, globally and perpetually accessible science.</p> - <h3><a href="{% url 'scipost:about' %}#advisory_board">Advisory Board</a></h3> - <h3><a href="{% url 'scipost:call' %}">A call for openness</a></h3> - <h3><a href="{% url 'scipost:quick_tour' %}">Quick Tour</a></h3> - <h3><a href="{% url 'scipost:FAQ' %}">Frequently asked questions</a></h3> - <h3><a href="{% url 'scipost:about' %}">Read more</a></h3> - <h4><em>In the press:</em></h4> - <ul> - <li><a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016.pdf' %}">FOM expres 5/2016</a> (<a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016_eng.pdf' %}">English vn</a>)</li> - <li><a href="http://www.uva.nl/en/news-events/news/uva-news/content/news/2016/10/open-access-platform-scipost-launches-inaugural-edition-of-first-journal.html">Inaugural issue 10/2016</a></li> - </ul> - </div> - </div> - </div> + </div><!-- End news --> - {% if not user.is_authenticated %} - <div class="col-md-6 col-lg-3"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title mb-0"><a href="{% url 'scipost:register' %}">Register</a></h1> - <p>Professional scientists (PhD students and above) can become Contributors to SciPost by filling the - <a href="{% url 'scipost:register' %}">registration form</a>.</p> - <h4>Registered contributors can among others:</h4> - <ul> - <li>Submit manuscripts to SciPost Journals</li> - <li>Post reports and comments</li> - <li>Express opinions on contributions</li> - <li>Subscribe to feeds</li> - <li>Use productivity tools</li> - </ul> - </div> - </div> - </div> - {% endif %} - - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title"><a href="{% url 'journals:journals' %}">Latest Publications</a></h1> - <h2 class="banner"> - <a href="{% url 'scipost:landing_page' 'SciPostPhys' %}">SciPost Physics</a> - </h2> - {% if issue and publications %} - <h4 class="card-text text-center mt-2">A selection from the {% if issue.is_current %}current{% else %}last{% endif %} issue:</h4> - {% if issue %} - <div class="text-muted text-center mt-2">Vol. {{ issue.in_volume.number }} issue {{ issue.number }} {{issue.period_as_string}}</div> - {% endif %} - </div> - <div class="card-block"> + + + {% if not user.is_authenticated %} + <!-- Register --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'scipost:register' %}">Register</a></h1> + <p>Professional scientists (PhD students and above) can become Contributors to SciPost by filling the + <a href="{% url 'scipost:register' %}">registration form</a>.</p> + <h4>Registered contributors can among others:</h4> + <ul> + <li>Submit manuscripts to SciPost Journals</li> + <li>Post reports and comments</li> + <li>Express opinions on contributions</li> + <li>Subscribe to feeds</li> + <li>Use productivity tools</li> + </ul> + </div> + </div><!-- End register --> + {% else %} + <!-- About --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'scipost:about' %}">About SciPost</a></h1> + <h4 class="card-subtitle mb-2 text-muted">SciPost is a complete scientific publication portal managed by active professional scientists.</h4> + <p>It is purely online-based and offers openly, globally and perpetually accessible science.</p> + <h3><a href="{% url 'scipost:about' %}#advisory_board">Advisory Board</a></h3> + <h3><a href="{% url 'scipost:call' %}">A call for openness</a></h3> + <h3><a href="{% url 'scipost:quick_tour' %}">Quick Tour</a></h3> + <h3><a href="{% url 'scipost:FAQ' %}">Frequently asked questions</a></h3> + <h3><a href="{% url 'scipost:about' %}">Read more</a></h3> + <h4><em>In the press:</em></h4> + <ul> + <li><a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016.pdf' %}">FOM expres 5/2016</a> (<a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016_eng.pdf' %}">English vn</a>)</li> + <li><a href="http://www.uva.nl/en/news-events/news/uva-news/content/news/2016/10/open-access-platform-scipost-launches-inaugural-edition-of-first-journal.html">Inaugural issue 10/2016</a></li> + </ul> + </div> + </div><!-- End about --> + {% endif %} + + <!-- Latest submissions --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'journals:journals' %}">Latest Submissions</a></h1> + <h4 class="card-subtitle text-muted">A selection of the last submissions</h4> + <ul class="list-group list-group-flush"> + {% for submission in submissions %} + <li class="list-group-item"> + {% include 'submissions/_submission_card_content_sparse.html' with submission=submission %} + </li> + {% endfor %} + </ul> + </div> + </div><!-- End latest submissions --> + + <!-- Latest publications --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'journals:journals' %}">Latest Publications</a></h1> + <h4 class="card-subtitle text-muted">A selection of the last publications</h4> <ul class="list-group list-group-flush"> {% for publication in publications %} <li class="list-group-item"> - <div class="card-block"> + <div class="card-block px-0"> {% include 'journals/_publication_single_short_summary.html' with publication=publication %} </div> </li> {% endfor %} </ul> - {% endif %} <p class="text-center mt-3" style="color: red;"><em>If you support and believe in our mission, <br/>be a pioneer and submit a manuscript!</em></p> - </div> - </div> - </div> - </div> + </div> + </div><!-- End latest publications --> + </div><!-- End deck --> - <div class="row"> - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title"><a href="{% url 'journals:journals' %}">Journals</a></h1> - <h4 class="card-subtitle m text-muted">SciPost publishes a portfolio of high-quality two-way open access scientific journals.</h4> - <p>All SciPost Journals implement the stringent <a href="/FAQ#pwr">peer-witnessed refereeing</a> principle.</p> - <p>All Journals are fully managed by professional scientists.</p> - <h3><a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a></h3> - <h3><a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a></h3> - - <br/> - - <p>View and comment on (login required) recent <a href="{% url 'submissions:submissions' %}">Submissions</a></p> - </div> - </div> - </div> - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title"><a href="{% url 'commentaries:commentaries' %}">Commentaries</a></h1> - <p>SciPost Commentaries allow Contributors to comment and build on all existing literature.</p> - <br/> - <h3><a href="{% url 'commentaries:howto' %}">SciPost Commentaries how-to</a></h3> - <h3><a href="{% url 'commentaries:request_commentary' %}">Request a new Commentary Page</a></h3> - </div> - </div> - </div> - <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title"><a href="{% url 'theses:theses' %}">Theses</a></h1> - <p>SciPost Theses allow Contributors to find Master's, Ph.D. and Habilitation theses relevant to their work.</p> - <br/> - <h3><a href="{% url 'theses:request_thesislink' %}">Request a new Thesis Link</a></h3> - </div> - </div> + <div class="card-deck"> + + <!-- Journals --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title"><a href="{% url 'journals:journals' %}">Journals</a></h1> + <h4 class="card-subtitle m text-muted">SciPost publishes a portfolio of high-quality two-way open access scientific journals.</h4> + <p>All SciPost Journals implement the stringent <a href="/FAQ#pwr">peer-witnessed refereeing</a> principle.</p> + <p>All Journals are fully managed by professional scientists.</p> + <h3><a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a></h3> + <h3><a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a></h3> + + <br/> + + <p>View and comment on (login required) recent <a href="{% url 'submissions:submissions' %}">Submissions</a></p> + </div> + </div><!-- End journals --> + + + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title"><a href="{% url 'commentaries:commentaries' %}">Commentaries</a></h1> + <p>SciPost Commentaries allow Contributors to comment and build on all existing literature.</p> + <br/> + <h3><a href="{% url 'commentaries:howto' %}">SciPost Commentaries how-to</a></h3> + <h3><a href="{% url 'commentaries:request_commentary' %}">Request a new Commentary Page</a></h3> + </div> + </div><!-- End commentaries --> + + <!-- Theses --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title"><a href="{% url 'theses:theses' %}">Theses</a></h1> + <p>SciPost Theses allow Contributors to find Master's, Ph.D. and Habilitation theses relevant to their work.</p> + <br/> + <h3><a href="{% url 'theses:request_thesislink' %}">Request a new Thesis Link</a></h3> + </div> + </div><!-- End theses --> + + {% if not user.is_authenticated %} + <!-- About --> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title mb-0"><a href="{% url 'scipost:about' %}">About SciPost</a></h1> + <h4 class="card-subtitle mb-2 text-muted">SciPost is a complete scientific publication portal managed by active professional scientists.</h4> + <p>It is purely online-based and offers openly, globally and perpetually accessible science.</p> + <h3><a href="{% url 'scipost:about' %}#advisory_board">Advisory Board</a></h3> + <h3><a href="{% url 'scipost:call' %}">A call for openness</a></h3> + <h3><a href="{% url 'scipost:quick_tour' %}">Quick Tour</a></h3> + <h3><a href="{% url 'scipost:FAQ' %}">Frequently asked questions</a></h3> + <h3><a href="{% url 'scipost:about' %}">Read more</a></h3> + <h4><em>In the press:</em></h4> + <ul> + <li><a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016.pdf' %}">FOM expres 5/2016</a> (<a href="{% static 'scipost/press/SciPost_in_FOM_expres_mei_2016_eng.pdf' %}">English vn</a>)</li> + <li><a href="http://www.uva.nl/en/news-events/news/uva-news/content/news/2016/10/open-access-platform-scipost-launches-inaugural-edition-of-first-journal.html">Inaugural issue 10/2016</a></li> + </ul> + </div> + </div><!-- End about --> + {% endif %} + + </div><!-- End deck --> </div> </div> diff --git a/scipost/templates/scipost/login.html b/scipost/templates/scipost/login.html index f52e1cc6a656030c50b797c16ff4ea3297aff19b..b7a3a64d5351fc4188fe78886cdfa02bab68a4ec 100644 --- a/scipost/templates/scipost/login.html +++ b/scipost/templates/scipost/login.html @@ -2,12 +2,13 @@ {% block pagetitle %}: login{% endblock pagetitle %} -{% block bodysup %} + {% load bootstrap %} -<div class="container"> - <div class="row my-4"> +{% block content %} + +<div class="row my-4"> <div class="col-md-6"> <h1 class="mb-md-2">Log in to SciPost</h1> <form action="{% url 'scipost:login' %}" method="post"> @@ -32,7 +33,6 @@ <a href="{% url 'scipost:register' %}">registration form</a>.</p> </div> - </div> </div> -{% endblock bodysup %} +{% endblock %} diff --git a/scipost/templates/scipost/logout.html b/scipost/templates/scipost/logout.html deleted file mode 100644 index 6a8fd267f1622e40001310a7c4c1b9e9937c2083..0000000000000000000000000000000000000000 --- a/scipost/templates/scipost/logout.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'scipost/base.html' %} - -{% block pagetitle %}: logged out{% endblock pagetitle %} - -{% block bodysup %} - -<section> - <h1>Your are now logged out of SciPost.</h1> - <p>Keep contributing!</p> -</section> - -{% endblock bodysup %} diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 9d2c6a70dd2fafcb36deb25bb6cdc38004dd22f8..8705a78593b3c7957978b87a47df21b763cf2e59 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -40,7 +40,9 @@ {% endif %} {% if perms.scipost.can_referee %} <li class="nav-item btn btn-secondary"> - <a class="nav-link" data-toggle="tab" href="#refereeing">Refereeing</a> + {% with pending_count=pending_ref_tasks|length %} + <a class="nav-link" data-toggle="tab" href="#refereeing">Refereeing {% if nr_ref_inv_to_consider|add:pending_count %}({{nr_ref_inv_to_consider|add:pending_count}}){% endif %}</a> + {% endwith %} </li> {% endif %} <li class="nav-item btn btn-secondary"> 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/registration_invitations.html b/scipost/templates/scipost/registration_invitations.html index 0b5b8cb50a7d1072c853a8658c2bba0aeb8fabda..e3a2467cbc19fa56f5e97c654b084e41665d5bd3 100644 --- a/scipost/templates/scipost/registration_invitations.html +++ b/scipost/templates/scipost/registration_invitations.html @@ -1,296 +1,119 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} {% block pagetitle %}: registration invitations{% endblock pagetitle %} -{% block bodysup %} - {% load scipost_extras %} +{% load bootstrap %} -<script> - $(document).ready(function(){ +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Registration invitations</span> +{% endblock %} - switch ($('select#id_invitation_type').val()) { - case "ci": - $("#div_id_cited_in_submission").show(); - $("#div_id_cited_in_publication").hide(); - break; - case "cp": - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").show(); - break; - default: - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - } +{% block content %} - $('select#id_invitation_type').on('change', function() { - switch ($('select#id_invitation_type').val()) { - case "ci": - $("#div_id_cited_in_submission").show(); - $("#div_id_cited_in_publication").hide(); - break; - case "cp": - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").show(); - break; - default: - $("#div_id_cited_in_submission").hide(); - $("#div_id_cited_in_publication").hide(); - } - }); - }); +<script> +$(document).ready(function(){ + + $('#id_invitation_type').on('change', function() { + switch ($(this).val()) { + case "ci": + $("#id_cited_in_submission").parents('.form-group').show(); + $("#id_cited_in_publication").parents('.form-group').hide(); + break; + case "cp": + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').show(); + break; + default: + $("#id_cited_in_submission").parents('.form-group').hide(); + $("#id_cited_in_publication").parents('.form-group').hide(); + } + }).trigger('change'); +}); </script> -<section> - <div class="flex-greybox"> - <h1>Registration Invitations</h1> - </div> - {% if request.user|is_in_group:'SciPost Administrators' %} - <h3>Perform a <a href="{% url 'scipost:registration_invitations_cleanup' %}">cleanup</a> of existing invitations.</h3> - {% endif %} -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Send a new invitation:</h2> - </div> - {% if errormessage %} - <h3 style="color: red;">{{ errormessage }}</h3> - {% endif %} - <form action="{% url 'scipost:registration_invitations' %}" method="post"> - {% csrf_token %} - {% load crispy_forms_tags %} - {% crispy reg_inv_form %} - </form> -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Existing drafts (to be processed by Admin):</h2> - </div> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date drafted</td><td>Type</td><td>Drafted by</td></tr> - {% for draft in existing_drafts %} - <tr> - <td>{{ draft.last_name }}</td> - <td>{{ draft.first_name }}</td> - <td>{{ draft.email }}</td> - <td>{{ draft.date_drafted }} </td> - <td>{{ draft.invitation_type }}</td> - <td>{{ draft.drafted_by.user.last_name }}</td> - <td><a href="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}">Edit</a></td> - <td><a href="{% url 'scipost:registration_invitations_from_draft' draft_id=draft.id %}">Process</a></td> - <td><a href="{% url 'scipost:mark_draft_inv_as_processed' draft_id=draft.id %}">Mark as processed</a></td> - {% for ac in draft|associated_contributors %} - <td><a href="{% url 'scipost:map_draft_reg_inv_to_contributor' draft_id=draft.id contributor_id=ac.id %}">Map to {{ ac.user.first_name }} {{ ac.user.last_name }}</a></td> - {% endfor %} - </tr> - {% endfor %} - </table> -</section> -<hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Invitations sent (response pending) EF ({{ nr_sent_reg_inv_fellows }}), C ({{ nr_sent_reg_inv_contrib }}), R ({{ nr_sent_reg_inv_ref }}), cited in sub ({{ nr_sent_reg_inv_cited_sub }}), cited in pub ({{ nr_sent_reg_inv_cited_pub }}):</h2> - </div> - <hr class="hr6"/> - <h3>Editorial Fellows ({{ nr_sent_reg_inv_fellows }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_fellows %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - <td><a href="{% url 'scipost:edit_invitation_personal_message' invitation_id=fellow.id %}">Edit msg</a></td> - <td><a href="{% url 'scipost:renew_registration_invitation' invitation_id=fellow.id %}">Renew</a> ({{ fellow.nr_reminders }}) {% if fellow.date_last_reminded %}(last: {{ fellow.date_last_reminded|date:"Y-m-d" }}){% endif %}</td> - <td><a href="{% url 'scipost:mark_reg_inv_as_declined' invitation_id=fellow.id %}">Declined</a></td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Normal Contributors ({{ nr_sent_reg_inv_contrib }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_contrib %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - <td><a href="{% url 'scipost:edit_invitation_personal_message' invitation_id=fellow.id %}">Edit msg</a></td> - <td><a href="{% url 'scipost:renew_registration_invitation' invitation_id=fellow.id %}">Renew</a> ({{ fellow.nr_reminders }}) {% if fellow.date_last_reminded %}(last: {{ fellow.date_last_reminded|date:"Y-m-d" }}){% endif %}</td> - <td><a href="{% url 'scipost:mark_reg_inv_as_declined' invitation_id=fellow.id %}">Declined</a></td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Referees ({{ nr_sent_reg_inv_ref }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_ref %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Cited in sub ({{ nr_sent_reg_inv_cited_sub }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_cited_sub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - <td><a href="{% url 'scipost:renew_registration_invitation' invitation_id=fellow.id %}">Renew</a> ({{ fellow.nr_reminders }}) {% if fellow.date_last_reminded %}(last: {{ fellow.date_last_reminded|date:"Y-m-d" }}){% endif %}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Cited in pub ({{ nr_sent_reg_inv_cited_pub }})</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in sent_reg_inv_cited_pub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - <td><a href="{% url 'scipost:renew_registration_invitation' invitation_id=fellow.id %}">Renew</a> ({{ fellow.nr_reminders }}) {% if fellow.date_last_reminded %}(last: {{ fellow.date_last_reminded|date:"Y-m-d" }}){% endif %}</td> - </tr> - {% endfor %} - </table> -</section> - <hr class="hr12"/> -<section> - <div class="flex-greybox"> - <h2>Invitations sent (responded) EF ({{ nr_resp_reg_inv_fellows }}), C ({{ nr_resp_reg_inv_contrib }}), R ({{ nr_resp_reg_inv_ref }}), cited in sub ({{ nr_resp_reg_inv_cited_sub }}), cited in pub ({{ nr_resp_reg_inv_cited_pub }}):</h2> - </div> - <hr class="hr6"/> - <h3>Editorial Fellows ({{ nr_resp_reg_inv_fellows }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_fellows %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - <hr class="hr6"/> - <h3>Normal Contributors ({{ nr_resp_reg_inv_contrib}})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_contrib %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Referees ({{ nr_resp_reg_inv_ref }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_ref %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Cited in sub ({{ nr_resp_reg_inv_cited_sub }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_cited_sub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Cited in pub ({{ nr_resp_reg_inv_cited_pub }})</h3> - <table class="tableofInviteesResponded"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in resp_reg_inv_cited_pub %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - - <hr class="hr6"/> - <h3>Declined</h3> - <table class="tableofInviteesDeclined"> - <tr><td>Last name</td><td>First name</td><td>Email</td><td>Date sent</td><td>Type</td><td>Invited by</td></tr> - {% for fellow in decl_reg_inv %} - <tr> - <td>{{ fellow.last_name }}</td> - <td>{{ fellow.first_name }}</td> - <td>{{ fellow.email }}</td> - <td>{{ fellow.date_sent }} </td> - <td>{{ fellow.invitation_type }}</td> - <td>{{ fellow.invited_by.user.last_name }}</td> - </tr> - {% endfor %} - </table> - -</section> - - -<hr class="hr12"/> - -<section> - <div class="flex-greybox"> - <h2>List of already-registered contributors:</h3> - </div> - <p> - {% for first_name, last_name in names_reg_contributors %} - {{ first_name }} {{ last_name }}, - {% endfor %} - </p> -</section> - -{% endblock bodysup %} +<div class="row"> + <div class="col-12"> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title">Registration Invitations</h1> + {% if request.user|is_in_group:'SciPost Administrators' %} + <h3>Perform a <a href="{% url 'scipost:registration_invitations_cleanup' %}">cleanup</a> of existing invitations.</h3> + {% endif %} + </div> + </div> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Send a new invitation</h2> + {% if errormessage %} + <h3 class="text-danger">{{ errormessage }}</h3> + {% endif %} + <form action="{% url 'scipost:registration_invitations' %}" method="post"> + {% csrf_token %} + {{reg_inv_form.media}} + {{reg_inv_form|bootstrap}} + <input type="submit" class="btn btn-secondary"> + </form> + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <h2 class="highlight">Existing drafts (to be processed by Admin) ({{existing_drafts|length}}) <small><a href="javascript:void(0)" data-toggle="toggle" data-target="#table_existing_drafts">view/hide</a></small></h2> + <table class="table" id="table_existing_drafts"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date drafted</th> + <th>Type</th> + <th>Drafted by</th> + <th colspan="2">Actions</th> + </tr> + </thead> + <tbody> + {% for draft in existing_drafts %} + <tr> + <td>{{ draft.last_name }}</td> + <td>{{ draft.first_name }}</td> + <td>{{ draft.email }}</td> + <td>{{ draft.date_drafted }} </td> + <td>{{ draft.get_invitation_type_display }}</td> + <td>{{ draft.drafted_by.user.first_name }} {{ draft.drafted_by.user.last_name }}</td> + <td> + <a href="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}">Edit</a> | + <a href="{% url 'scipost:registration_invitations_from_draft' draft_id=draft.id %}">Process</a> | + <a href="{% url 'scipost:mark_draft_inv_as_processed' draft_id=draft.id %}">Mark as processed</a> + </td> + <td> + <ul> + {% for ac in draft|associated_contributors %} + <li> + <a href="{% url 'scipost:map_draft_reg_inv_to_contributor' draft_id=draft.id contributor_id=ac.id %}">Map to {{ ac.user.first_name }} {{ ac.user.last_name }}</a>> + </li> + {% empty %} + <li>No associated contributors found.</li> + {% endfor %} + </ul> + </td> + </tr> + {% empty %} + <tr> + <td colspan="8">No drafts found.</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% include 'scipost/_draft_registration_tables.html' %} + + +{% endblock %} diff --git a/scipost/templates/scipost/registration_invitations_cleanup.html b/scipost/templates/scipost/registration_invitations_cleanup.html index edbb4c85a5fcd7bcec4fb7705695f283c1c587aa..539c00d807dae937ee87b2376f359458b5a9c72f 100644 --- a/scipost/templates/scipost/registration_invitations_cleanup.html +++ b/scipost/templates/scipost/registration_invitations_cleanup.html @@ -1,44 +1,63 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} {% block pagetitle %}: registration invitations cleanup{% endblock pagetitle %} -{% block bodysup %} - - -<section> - <div class="flex-greybox"> - <h1>Registration Invitations Cleanup</h1> - </div> -</section> - -<hr class="hr12"/> - -<section> - {% if invs_to_cleanup %} - <h3>Email duplicates (a contributor exists with the email address in these invitations):</h3> - <table class="tableofInvitees"> - <tr><td>Last name</td><td>First name</td> - <td>Email</td><td>Date sent</td> - <td>Type</td><td>Invited by</td></tr> - {% for inv in invs_to_cleanup %} - <tr> - <td>{{ inv.last_name }}</td> - <td>{{ inv.first_name }}</td> - <td>{{ inv.email }}</td> - <td>{{ inv.date_sent }} </td> - <td>{{ inv.invitation_type }}</td> - <td>{{ inv.invited_by.user.last_name }}</td> - <td> - <a href="{% url 'scipost:remove_registration_invitation' invitation_id=inv.id %}">Remove</a> - </td> - </tr> - {% endfor %} - </table> - {% else %} - <h3>There were no duplicate emails found in the sets of Contributors/Invitations.</h3> - {% endif %} - <hr class="hr6"/> - <h3>Return to the <a href="{% url 'scipost:registration_invitations' %}">Registration Invitations</a> page.</h3> -</section> - -{% endblock bodysup %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'scipost:registration_invitations' %}" class="breadcrumb-item">Registration invitations</a> + <span class="breadcrumb-item">Cleanup</span> +{% endblock %} + +{% block content %} + + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Registration Invitations Cleanup</h1> + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <h3>Email duplicates (a contributor exists with the email address in these invitations)</h3> + <table class="table"> + <thead> + <tr> + <th>Last name</th> + <th>First name</th> + <th>Email</th> + <th>Date sent</th> + <th>Type</th> + <th>Invited by</th> + </tr> + </thead> + <tbody> + {% for inv in invs_to_cleanup %} + <tr> + <td>{{ inv.last_name }}</td> + <td>{{ inv.first_name }}</td> + <td>{{ inv.email }}</td> + <td>{{ inv.date_sent }} </td> + <td>{{ inv.invitation_type }}</td> + <td>{{ inv.invited_by.user.last_name }}</td> + <td> + <a href="{% url 'scipost:remove_registration_invitation' invitation_id=inv.id %}">Remove</a> + </td> + </tr> + {% empty %} + <tr> + <td colspan="7"> + There were no duplicate emails found in the sets of Contributors/Invitations. + </td> + </tr> + {% endfor %} + </tbody> + </table> + + <p>Return to the <a href="{% url 'scipost:registration_invitations' %}">Registration Invitations</a> page.</p> + </div> +</div> + +{% endblock content %} 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/templates/scipost/update_personal_data.html b/scipost/templates/scipost/update_personal_data.html index fbbddf08ce073846acba50604ec321390d9c215c..1c3875d45e6994f439e882566b975c5e63702004 100644 --- a/scipost/templates/scipost/update_personal_data.html +++ b/scipost/templates/scipost/update_personal_data.html @@ -2,7 +2,9 @@ {% block pagetitle %}: update personal data{% endblock pagetitle %} -{% block bodysup %} +{% load bootstrap %} + +{% block content %} <script> @@ -81,18 +83,16 @@ $(document).ready(function(){ </script> -<section> - <h1>Update your personal data</h1> - <form action="{% url 'scipost:update_personal_data' %}" method="post"> - {% csrf_token %} - <table> - <ul> - {{ user_form.as_table }} - {{ cont_form.as_table }} - </ul> - </table> - <input type="submit" value="Update" /> - </form> -</section> +<div class="row"> + <div class="col-lg-10 offset-lg-1"> + <h1 class="highlight">Update your personal data</h1> + <form action="{% url 'scipost:update_personal_data' %}" method="post"> + {% csrf_token %} + {{user_form|bootstrap}} + {{cont_form|bootstrap}} + <input type="submit" class="btn btn-secondary" value="Update" /> + </form> + </div> +</div> -{% endblock bodysup %} +{% endblock content %} diff --git a/scipost/templates/scipost/vet_registration_requests.html b/scipost/templates/scipost/vet_registration_requests.html index 222c4fa9e6f2f5906eb47abd444ae3b6cc906f60..8b5630bff03f7b936b70fb4b25a051229a162d7e 100644 --- a/scipost/templates/scipost/vet_registration_requests.html +++ b/scipost/templates/scipost/vet_registration_requests.html @@ -1,78 +1,60 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} {% block pagetitle %}: registrations to vet{% endblock pagetitle %} -{% block bodysup %} -<script> +{% load bootstrap %} -$( document ).ready(function() { - function toggleBoxes(specificTarget){ - if (typeof specificTarget === 'undefined') { - $('select#id_refusal_reason').parent().toggle(); - $('textarea#id_email_response_field').parent().toggle(); - } else { - for (i = 0; i < specificTarget.length; i++){ - $(specificTarget[i]).toggle() - } - } - } - toggleBoxes() +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Registration requests to vet</span> +{% endblock %} - $("input:checkbox").on('click', function(event) { - var clickedBox = $(this) - var boxes = $(this).closest('div').find('input:checkbox') - var specificTarget = $(this).closest('div').find('p').slice(2,4) - if (!clickedBox.is(":checked")){ - if ( clickedBox.prop('name') == 'refuse' && $(specificTarget[0]).is(":visible") ){ - toggleBoxes(specificTarget) - } - } - else { - if (clickedBox.attr("name") == "promote_to_registered_contributor"){ - $(boxes[1]).prop('checked', false); - if ($(specificTarget[0]).is(":visible")){ - toggleBoxes(specificTarget) - } - } - else if (clickedBox.attr("name") == "refuse"){ - $(boxes[0]).prop('checked', false); - if (!$(specificTarget[0]).is(":visible")){ - toggleBoxes(specificTarget) - } +{% block content %} + +<script> +$(function() { + $('[name="decision"]').on('click change', function(){ + if($(this).filter(':checked').val() == 'False') { + $('#id_refusal_reason, #id_email_response_field').parents('.form-group').show(); + } else { + $('#id_refusal_reason, #id_email_response_field').parents('.form-group').hide(); } - } - }) + }).trigger('change'); }); </script> -<section> - {% if not contributors_to_vet %} - <h1>There are no Registration requests for you to vet.</h1> - <p>Back to your <a href="{% url 'scipost:personal_page' %}">personal page</a>. - {% else %} - - <h1>SciPost Registration requests to vet:</h1> - <p>These Contributors are currently not registered (submitting, commenting and voting disabled).</p> - <p>Use this page to promote them to give them registered status, or refuse registration.</p> - - {% for contributor_to_vet in contributors_to_vet %} - <br> - <hr> - <div class="flex-container"> - <div class="flex-whitebox"> - {{ contributor_to_vet.private_info_as_table }} +<div class="row"> + <div class="col-12"> + <div class="card card-grey"> + <div class="card-block"> + <h1 class="card-title">SciPost Registration requests to vet:</h1> + <p class="card-text mb-0"> + These Contributors are currently not registered (submitting, commenting and voting disabled).<br> + Use this page to promote them to give them registered status, or refuse registration. + </p> + </div> + </div> </div> - <div class="flex-whitebox"> - <form action="{% url 'scipost:vet_registration_request_ack' contributor_id=contributor_to_vet.id %}" method="post"> - {% csrf_token %} - {{ form.as_p }} - <input type="submit" value="Submit" /> - </form> +</div> + +{% for contributor_to_vet in contributors_to_vet %} + {% if not forloop.first %}<hr class="small">{% endif %} + <div class="row"> + <div class="col-md-4"> + {{ contributor_to_vet.private_info_as_table }} + </div> + <div class="col-md-8"> + <form action="{% url 'scipost:vet_registration_request_ack' contributor_id=contributor_to_vet.id %}" method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input type="submit" class="btn btn-secondary" /> + </form> + </div> </div> - </div> - {% endfor %} +{% empty %} + <h1>There are no Registration requests for you to vet.</h1> + <p>Back to your <a href="{% url 'scipost:personal_page' %}">personal page</a>. +{% endfor %} - {% endif %} -</section> -{% endblock bodysup %} +{% 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 0e2fd0e176ed61e8a2ec1e840eb3e44724b726f1..ce6d28389dae03ed225d0ab5eee2ea6d64b4ef88 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -1,11 +1,8 @@ -import datetime -import hashlib -import random import re -import string from django.utils import timezone from django.shortcuts import get_object_or_404, render +from django.contrib import messages from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group @@ -16,7 +13,6 @@ from django.core.mail import EmailMessage, EmailMultiAlternatives from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.urlresolvers import reverse from django.db.models import Q -from django.http import HttpResponseRedirect from django.shortcuts import redirect from django.template import Context, Template from django.utils.http import is_safe_url @@ -41,9 +37,8 @@ from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_ from commentaries.models import Commentary from comments.models import Comment -from journals.models import Publication, Issue +from journals.models import Publication from news.models import NewsItem -from submissions.constants import SUBMISSION_STATUS_PUBLICLY_UNLISTED from submissions.models import Submission, EditorialAssignment, RefereeInvitation,\ Report, EICRecommendation from theses.models import ThesisLink @@ -57,20 +52,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 +86,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,14 +170,12 @@ def search(request): ############# def index(request): - """ Main page """ - context = {} - context['latest_newsitems'] = NewsItem.objects.all().order_by('-date')[:2] - context['issue'] = Issue.objects.get_last_filled_issue(in_volume__in_journal__name='SciPostPhys') - if context['issue']: - context['publications'] = context['issue'].publication_set.filter(doi_string__isnull=False - ).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) @@ -215,11 +183,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) @@ -230,193 +193,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}, request) + 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) - - -def unsubscribe(request, key): + 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 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}, request) + 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) @@ -432,71 +325,67 @@ def vet_registration_requests(request): @permission_required('scipost.can_vet_registration_requests', return_403=True) def vet_registration_request_ack(request, contributor_id): # process the form - if request.method == 'POST': - form = VetRegistrationForm(request.POST) - contributor = Contributor.objects.get(pk=contributor_id) - if form.is_valid(): - if form.cleaned_data['promote_to_registered_contributor']: - contributor.status = 1 - contributor.vetted_by = request.user.contributor - contributor.save() - group = Group.objects.get(name='Registered Contributors') - contributor.user.groups.add(group) - # Verify if there is a pending refereeing invitation - pending_ref_inv_exists = True - try: - pending_ref_inv = RefereeInvitation.objects.get( - invitation_key=contributor.invitation_key, cancelled=False) - pending_ref_inv.referee = contributor - pending_ref_inv.save() - except RefereeInvitation.DoesNotExist: - pending_ref_inv_exists = False - - email_text = ('Dear ' + contributor.get_title_display() + ' ' - + contributor.user.last_name + - ', \n\nYour registration to the SciPost publication portal ' - 'has been accepted. ' - 'You can now login at https://scipost.org and contribute. \n\n') - if pending_ref_inv_exists: - email_text += ( - 'Note that you have pending refereeing invitations; please navigate to ' - 'https://scipost.org/submissions/accept_or_decline_ref_invitations ' - '(login required) to accept or decline them.\n\n') - email_text += 'Thank you very much in advance, \nThe SciPost Team.' - emailmessage = EmailMessage('SciPost registration accepted', email_text, - 'SciPost registration <registration@scipost.org>', - [contributor.user.email], - bcc=['registration@scipost.org'], - reply_to=['registration@scipost.org']) - emailmessage.send(fail_silently=False) - else: - ref_reason = int(form.cleaned_data['refusal_reason']) - email_text = ('Dear ' + contributor.get_title_display() + ' ' - + contributor.user.last_name + - ', \n\nYour registration to the SciPost publication portal ' - 'has been turned down, the reason being: ' - + reg_ref_dict[ref_reason] + '. You can however still view ' - 'all SciPost contents, just not submit papers, ' - 'comments or votes. We nonetheless thank you for your interest.' - '\n\nThe SciPost Team.') - if form.cleaned_data['email_response_field']: - email_text += ('\n\nFurther explanations: ' - + form.cleaned_data['email_response_field']) - emailmessage = EmailMessage('SciPost registration: unsuccessful', - email_text, - 'SciPost registration <registration@scipost.org>', - [contributor.user.email], - bcc=['registration@scipost.org'], - reply_to=['registration@scipost.org']) - emailmessage.send(fail_silently=False) - contributor.status = form.cleaned_data['refusal_reason'] - contributor.save() - - context = {'ack_header': 'SciPost Registration request vetted.', - 'followup_message': 'Back to ', - 'followup_link': reverse('scipost:vet_registration_requests'), - 'followup_link_label': 'Registration requests page'} - return render(request, 'scipost/acknowledgement.html', context) + form = VetRegistrationForm(request.POST or None) + contributor = Contributor.objects.get(pk=contributor_id) + if form.is_valid(): + if form.promote_to_registered_contributor(): + contributor.status = 1 + contributor.vetted_by = request.user.contributor + contributor.save() + group = Group.objects.get(name='Registered Contributors') + contributor.user.groups.add(group) + # Verify if there is a pending refereeing invitation + pending_ref_inv_exists = True + try: + pending_ref_inv = RefereeInvitation.objects.get( + invitation_key=contributor.invitation_key, cancelled=False) + pending_ref_inv.referee = contributor + pending_ref_inv.save() + except RefereeInvitation.DoesNotExist: + pending_ref_inv_exists = False + + email_text = ('Dear ' + contributor.get_title_display() + ' ' + + contributor.user.last_name + + ', \n\nYour registration to the SciPost publication portal ' + 'has been accepted. ' + 'You can now login at https://scipost.org and contribute. \n\n') + if pending_ref_inv_exists: + email_text += ( + 'Note that you have pending refereeing invitations; please navigate to ' + 'https://scipost.org/submissions/accept_or_decline_ref_invitations ' + '(login required) to accept or decline them.\n\n') + email_text += 'Thank you very much in advance, \nThe SciPost Team.' + emailmessage = EmailMessage('SciPost registration accepted', email_text, + 'SciPost registration <registration@scipost.org>', + [contributor.user.email], + bcc=['registration@scipost.org'], + reply_to=['registration@scipost.org']) + emailmessage.send(fail_silently=False) + else: + ref_reason = int(form.cleaned_data['refusal_reason']) + email_text = ('Dear ' + contributor.get_title_display() + ' ' + + contributor.user.last_name + + ', \n\nYour registration to the SciPost publication portal ' + 'has been turned down, the reason being: ' + + reg_ref_dict[ref_reason] + '. You can however still view ' + 'all SciPost contents, just not submit papers, ' + 'comments or votes. We nonetheless thank you for your interest.' + '\n\nThe SciPost Team.') + if form.cleaned_data['email_response_field']: + email_text += ('\n\nFurther explanations: ' + + form.cleaned_data['email_response_field']) + emailmessage = EmailMessage('SciPost registration: unsuccessful', + email_text, + 'SciPost registration <registration@scipost.org>', + [contributor.user.email], + bcc=['registration@scipost.org'], + reply_to=['registration@scipost.org']) + emailmessage.send(fail_silently=False) + contributor.status = form.cleaned_data['refusal_reason'] + contributor.save() + + messages.success(request, 'SciPost Registration request vetted.') + return redirect(reverse('scipost:vet_registration_requests')) @permission_required('scipost.can_draft_registration_invitations', return_403=True) @@ -529,11 +418,8 @@ def draft_registration_invitation(request): 'at the relevant Submission\'s Editorial Page. ') else: Utils.create_draft_invitation() - context = {'ack_header': 'Draft invitation saved.', - 'followup_message': 'Return to the ', - 'followup_link': reverse('scipost:draft_registration_invitation'), - 'followup_link_label': 'drafting page'} - return render(request, 'scipost/acknowledgement.html', context) + messages.success(request, 'Draft invitation saved.') + return redirect(reverse('scipost:draft_registration_invitation')) else: errormessage = 'The form was not filled validly.' @@ -542,58 +428,37 @@ def draft_registration_invitation(request): 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).order_by('last_name') - names_reg_contributors = Contributor.objects.filter( - status=1).order_by('user__last_name').values_list( - 'user__first_name', 'user__last_name') + names_reg_contributors = (Contributor.objects.filter(status=1).order_by('user__last_name') + .values_list('user__first_name', 'user__last_name')) existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name') context = { 'draft_inv_form': draft_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, @@ -684,7 +549,8 @@ def registration_invitations(request, draft_id=None): draft_to_delete = RegistrationInvitation.objects.filter( email=reg_inv_form.cleaned_data['email']).first() draft_to_delete.delete() - return HttpResponseRedirect('registration_invitation_sent') + messages.success(request, 'Registration Invitation sent') + return redirect(reverse('scipost:registration_invitations')) else: errormessage = 'The form was not filled validly.' @@ -707,27 +573,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) @@ -739,25 +595,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, @@ -871,8 +717,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')) @@ -895,7 +740,8 @@ def login_view(request): def logout_view(request): logout(request) - return render(request, 'scipost/logout.html') + messages.success(request, '<h3>Keep contributing!</h3>You are now logged out of SciPost.') + return redirect(reverse('scipost:index')) def mark_unavailable_period(request): @@ -942,7 +788,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 @@ -956,7 +802,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()) @@ -968,7 +814,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() @@ -1044,27 +890,23 @@ def personal_page(request): @login_required def change_password(request): - if request.method == 'POST': - form = PasswordChangeForm(request.POST) - ack = False - if form.is_valid(): - if not request.user.check_password(form.cleaned_data['password_prev']): - return render( - request, 'scipost/change_password.html', - {'form': form, - 'errormessage': 'The currently existing password you entered is incorrect'}) - if form.cleaned_data['password_new'] != form.cleaned_data['password_verif']: - return render(request, 'scipost/change_password.html', { - 'form': form, - 'errormessage': 'Your new password entries must match'}) - request.user.set_password(form.cleaned_data['password_new']) - request.user.save() - ack = True - context = {'ack': ack, 'form': form} - else: - form = PasswordChangeForm() - context = {'ack': False, 'form': form} - return render(request, 'scipost/change_password.html', context) + form = PasswordChangeForm(request.POST or None) + ack = False + if form.is_valid(): + if not request.user.check_password(form.cleaned_data['password_prev']): + return render( + request, 'scipost/change_password.html', + {'form': form, + 'errormessage': 'The currently existing password you entered is incorrect'}) + if form.cleaned_data['password_new'] != form.cleaned_data['password_verif']: + return render(request, 'scipost/change_password.html', { + 'form': form, + 'errormessage': 'Your new password entries must match'}) + request.user.set_password(form.cleaned_data['password_new']) + request.user.save() + ack = True + + return render(request, 'scipost/change_password.html', {'ack': ack, 'form': form}) def reset_password_confirm(request, uidb64=None, token=None): @@ -1083,33 +925,15 @@ def reset_password(request): @login_required def update_personal_data(request): contributor = Contributor.objects.get(user=request.user) - if request.method == 'POST': - user_form = UpdateUserDataForm(request.POST) - cont_form = UpdatePersonalDataForm(request.POST) - if user_form.is_valid() and cont_form.is_valid(): - 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() - context = {'ack_header': 'Your personal data has been updated.', - 'followup_message': 'Return to your ', - 'followup_link': reverse('scipost:personal_page'), - 'followup_link_label': 'personal page'} - return render(request, 'scipost/acknowledgement.html', context) + user_form = UpdateUserDataForm(request.POST or None, instance=request.user) + cont_form = UpdatePersonalDataForm(request.POST or None, instance=contributor) + if user_form.is_valid() and cont_form.is_valid(): + user_form.save() + cont_form.save() + messages.success(request, 'Your personal data has been updated.') + return redirect(reverse('scipost:personal_page')) else: user_form = UpdateUserDataForm(instance=contributor.user) - # Prevent exploit of gaining view on self-authored submissions by changing surname. - user_form.fields['last_name'].widget.attrs['disabled'] = True - # Surname can only be changed through the admin. cont_form = UpdatePersonalDataForm(instance=contributor) return render(request, 'scipost/update_personal_data.html', {'user_form': user_form, 'cont_form': cont_form}) @@ -1285,25 +1109,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: @@ -1323,14 +1128,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/admin.py b/submissions/admin.py index 1415e4438db01586a40c7baa064707c753fc0d67..a78b839e3b17109a1c12713edec35f47591f4e87 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -32,7 +32,10 @@ admin.site.register(EditorialAssignment, EditorialAssignmentAdmin) class RefereeInvitationAdmin(admin.ModelAdmin): search_fields = ['submission__title', 'submission__author_list', 'referee__user__last_name', - 'first_name', 'last_name', 'email_address', ] + 'first_name', 'last_name', 'email_address'] + list_display = ('referee', submission_short_title, 'accepted', ) + list_filter = ('accepted', 'fulfilled', 'cancelled',) + date_hierarchy = 'date_invited' admin.site.register(RefereeInvitation, RefereeInvitationAdmin) diff --git a/submissions/constants.py b/submissions/constants.py index 477c4e18352facc9e707b89fae3d999efbcf900e..d937973d28acfbcd1e85fce61eb2ba14c0c928de 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -1,11 +1,17 @@ - +STATUS_UNASSIGNED = 'unassigned' +STATUS_RESUBMISSION_INCOMING = 'resubmitted_incoming' +STATUS_REVISION_REQUESTED = 'revision_requested' +STATUS_EIC_ASSIGNED = 'EICassigned' +STATUS_AWAITING_ED_REC = 'awaiting_ed_rec' +STATUS_REVIEW_CLOSED = 'review_closed' SUBMISSION_STATUS = ( - ('unassigned', 'Unassigned, undergoing pre-screening'), + (STATUS_UNASSIGNED, 'Unassigned, undergoing pre-screening'), + (STATUS_RESUBMISSION_INCOMING, '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'), + (STATUS_EIC_ASSIGNED, 'Editor-in-charge assigned, manuscript under review'), + (STATUS_REVIEW_CLOSED, 'Review period closed, editorial recommendation pending'), # If revisions required: resubmission creates a new Submission object - ('revision_requested', 'Editor-in-charge has requested revision'), + (STATUS_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', @@ -13,6 +19,7 @@ SUBMISSION_STATUS = ( # If acceptance/rejection: ('voting_in_preparation', 'Voting in preparation (eligible Fellows being selected)'), ('put_to_EC_voting', 'Undergoing voting at the Editorial College'), + (STATUS_AWAITING_ED_REC, 'Awaiting Editorial Recommendation'), ('EC_vote_completed', 'Editorial College voting rounded up'), ('accepted', 'Publication decision taken: accept'), ('rejected', 'Publication decision taken: reject'), @@ -22,36 +29,43 @@ SUBMISSION_STATUS = ( ('withdrawn', 'Withdrawn by the Authors'), ) -SUBMISSION_STATUS_OUT_OF_POOL = [ +SUBMISSION_HTTP404_ON_EDITORIAL_PAGE = [ 'assignment_failed', - 'resubmitted', 'published', 'withdrawn', 'rejected', 'rejected_visible', ] -# Submissions which should not appear in search lists -SUBMISSION_STATUS_PUBLICLY_UNLISTED = [ - 'unassigned', - 'assignment_failed', - 'resubmitted', - 'resubmitted_rejected', - 'resubmitted_rejected_visible', - 'rejected', - 'published', - 'withdrawn', +SUBMISSION_STATUS_OUT_OF_POOL = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [ + 'resubmitted' +] + + +# Submissions which are allowed/required to submit a EIC Recommendation +SUBMISSION_EIC_RECOMMENDATION_REQUIRED = [ + STATUS_EIC_ASSIGNED, + STATUS_REVIEW_CLOSED, + STATUS_AWAITING_ED_REC ] # Submissions which should not be viewable (except by admins, Fellows and authors) SUBMISSION_STATUS_PUBLICLY_INVISIBLE = [ - 'unassigned', + STATUS_UNASSIGNED, + STATUS_RESUBMISSION_INCOMING, 'assignment_failed', 'resubmitted_rejected', 'rejected', 'withdrawn', ] +# Submissions which should not appear in search lists +SUBMISSION_STATUS_PUBLICLY_UNLISTED = SUBMISSION_STATUS_PUBLICLY_INVISIBLE + [ + 'resubmitted', + 'resubmitted_rejected_visible', + 'published' +] + # Submissions for which voting on a related recommendation is deprecated: SUBMISSION_STATUS_VOTING_DEPRECATED = [ 'rejected', @@ -65,6 +79,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_INCOMING +] + ED_COMM_CHOICES = ( ('EtoA', 'Editor-in-charge to Author'), ('EtoR', 'Editor-in-charge to Referee'), @@ -159,3 +178,12 @@ REPORT_STATUSES = ( (STATUS_NOT_USEFUL, 'Rejected (not useful)'), (STATUS_NOT_ACADEMIC, 'Rejected (not academic in style)') ) + +CYCLE_DEFAULT = 'default' +CYCLE_SHORT = 'short' +CYCLE_DIRECT_REC = 'direct_rec' +SUBMISSION_CYCLES = ( + (CYCLE_DEFAULT, 'Default cycle'), + (CYCLE_SHORT, 'Short cycle'), + (CYCLE_DIRECT_REC, 'Direct editorial recommendation'), +) diff --git a/submissions/factories.py b/submissions/factories.py index 1abdf6974e4c7caa28ae6ebcb49a9eb1096df83a..d67299983ce9f4c0221e1dc9d05018289950e8b2 100644 --- a/submissions/factories.py +++ b/submissions/factories.py @@ -1,8 +1,10 @@ import factory from scipost.factories import ContributorFactory +from journals.constants import SCIPOST_JOURNAL_PHYSICS from common.helpers import random_arxiv_identifier_with_version_number +from .constants import STATUS_UNASSIGNED, STATUS_EIC_ASSIGNED, STATUS_RESUBMISSION_INCOMING from .models import Submission @@ -12,7 +14,7 @@ class SubmissionFactory(factory.django.DjangoModelFactory): author_list = factory.Faker('name') submitted_by = factory.SubFactory(ContributorFactory) - submitted_to_journal = 'SciPost Physics' + submitted_to_journal = SCIPOST_JOURNAL_PHYSICS title = factory.Faker('bs') abstract = factory.Faker('text') arxiv_link = factory.Faker('uri') @@ -21,6 +23,15 @@ class SubmissionFactory(factory.django.DjangoModelFactory): class EICassignedSubmissionFactory(SubmissionFactory): - status = 'EICassigned' + status = STATUS_EIC_ASSIGNED editor_in_charge = factory.SubFactory(ContributorFactory) open_for_commenting = True + + +class UnassignedSubmissionFactory(SubmissionFactory): + status = STATUS_UNASSIGNED + + +class ResubmittedScreeningSubmissionFactory(SubmissionFactory): + status = STATUS_RESUBMISSION_INCOMING + editor_in_charge = factory.SubFactory(ContributorFactory) diff --git a/submissions/forms.py b/submissions/forms.py index 6bb7af39bb29008fd9eb097450b5178d4a7f4402..73f7f6fe50718aec0e89944e9908b6edfbb26385 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -2,7 +2,7 @@ from django import forms from django.core.validators import RegexValidator from .constants import ASSIGNMENT_BOOL, ASSIGNMENT_REFUSAL_REASONS,\ - REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES + REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES, SUBMISSION_CYCLES from .models import Submission, RefereeInvitation, Report, EICRecommendation from scipost.constants import SCIPOST_SUBJECT_AREAS @@ -261,3 +261,22 @@ class RecommendationVoteForm(forms.Form): css_class='flex-Fellowactionbox'), css_class='flex-container') ) + + +class SubmissionCycleChoiceForm(forms.ModelForm): + referees_reinvite = forms.ModelMultipleChoiceField(queryset=RefereeInvitation.objects.none(), + widget=forms.CheckboxSelectMultiple({ + 'checked': 'checked'}), + required=False, label='Reinvite referees') + + class Meta: + model = Submission + fields = ('refereeing_cycle',) + widgets = {'refereeing_cycle': forms.RadioSelect} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['refereeing_cycle'].default = None + other_submission = self.instance.other_versions().first() + if other_submission: + self.fields['referees_reinvite'].queryset = other_submission.referee_invitations.all() diff --git a/submissions/managers.py b/submissions/managers.py index c8654d6d84800a6ee9b16c5cdba84bd81361e4ca..ddff884029fae485b0589446aa9b947d341ee5cc 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -1,20 +1,60 @@ from django.db import models from django.db.models import Q -from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY_UNLISTED +from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY_UNLISTED,\ + SUBMISSION_STATUS_PUBLICLY_INVISIBLE, STATUS_UNVETTED, STATUS_VETTED,\ + STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC,\ + SUBMISSION_HTTP404_ON_EDITORIAL_PAGE class SubmissionManager(models.Manager): + def user_filter(self, user): + """ + Prevent conflic of interest by filtering submissions possible related to user. + This filter should be inherited by other filters. + """ + try: + return (self.exclude(authors=user.contributor) + .exclude(Q(author_list__icontains=user.last_name), + ~Q(authors_false_claims=user.contributor))) + except AttributeError: + return self.none() + def get_pool(self, user): - return self.exclude(status__in=SUBMISSION_STATUS_OUT_OF_POOL)\ - .exclude(is_current=False)\ - .exclude(authors=user.contributor)\ - .exclude(Q(author_list__icontains=user.last_name), - ~Q(authors_false_claims=user.contributor))\ - .order_by('-submission_date') + """ + This filter will return submission currently in an active submission cycle. + """ + return (self.user_filter(user) + .exclude(is_current=False) + .exclude(status__in=SUBMISSION_STATUS_OUT_OF_POOL) + .order_by('-submission_date')) + + def filter_editorial_page(self, user): + """ + This filter returns a subgroup of the `get_pool` filter, to allow opening and editing + certain submissions that are officially out of the submission cycle i.e. due + to resubmission, but should still have the possibility to be opened by the EIC. + """ + return (self.user_filter(user) + .exclude(status__in=SUBMISSION_HTTP404_ON_EDITORIAL_PAGE) + .order_by('-submission_date')) def public(self): - return self.filter(is_current=True).exclude(status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED) + """ + List only all public submissions. Should be used as a default filter! + + Implement: Use this filter to also determine, using a optional user argument, + if the query should be filtered or not as a logged in EdCol Admin + should be able to view *all* submissions. + """ + return self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED) + + def public_overcomplete(self): + """ + This query contains an overcomplete set of public submissions, i.e. also containing + submissions with status "published" or "resubmitted". + """ + return self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_INVISIBLE) class EditorialAssignmentManager(models.Manager): @@ -33,12 +73,31 @@ class EICRecommendationManager(models.Manager): are not related to the Contributor, by checking last_name and author_list of the linked Submission. """ - return self.exclude(submission__authors=user.contributor)\ - .exclude(Q(submission__author_list__icontains=user.last_name), - ~Q(submission__authors_false_claims=user.contributor)) + try: + return self.exclude(submission__authors=user.contributor)\ + .exclude(Q(submission__author_list__icontains=user.last_name), + ~Q(submission__authors_false_claims=user.contributor)) + except AttributeError: + return self.none() def filter_for_user(self, user, **kwargs): """ - Return list of EICRecommendation which are owned linked to an author owned Submission. + Return list of EICRecommendation's which are owned/assigned author through the + related submission. """ - return self.filter(submission__authors=user.contributor).filter(**kwargs) + try: + return self.filter(submission__authors=user.contributor).filter(**kwargs) + except AttributeError: + return self.none() + + +class ReportManager(models.Manager): + def accepted(self): + return self.filter(status__gte=STATUS_VETTED) + + def awaiting_vetting(self): + return self.filter(status=STATUS_UNVETTED) + + def rejected(self): + return self.filter(status__in=[STATUS_UNCLEAR, STATUS_INCORRECT, + STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC]) diff --git a/submissions/migrations/0036_auto_20170415_1055.py b/submissions/migrations/0036_auto_20170415_1055.py new file mode 100644 index 0000000000000000000000000000000000000000..f2835bce0ec4f7fb1c3ec46e609bda7bfea953cd --- /dev/null +++ b/submissions/migrations/0036_auto_20170415_1055.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-15 08:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0035_auto_20170407_0954'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='refereeing_cycle', + field=models.CharField(choices=[('default', 'Default cycle'), ('short', 'Short cycle'), ('direct_rec', 'Direct editorial recommendation')], default='default', max_length=30), + ), + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('unassigned', 'Unassigned, undergoing pre-screening'), ('resubmitted_incomin', 'Resubmission incoming, undergoing pre-screening'), ('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'), ('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/submissions/migrations/0037_auto_20170415_1659.py b/submissions/migrations/0037_auto_20170415_1659.py new file mode 100644 index 0000000000000000000000000000000000000000..4cdd6ab53537eea8f3884876bb7e4f3907a4b98c --- /dev/null +++ b/submissions/migrations/0037_auto_20170415_1659.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-15 14:59 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0036_auto_20170415_1055'), + ] + + operations = [ + migrations.AlterField( + model_name='refereeinvitation', + name='submission', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='referee_invitations', to='submissions.Submission'), + ), + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('unassigned', 'Unassigned, undergoing pre-screening'), ('resubmitted_incomin', 'Resubmission incoming, undergoing pre-screening'), ('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_editorial_recommendation', '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/submissions/migrations/0038_auto_20170415_1701.py b/submissions/migrations/0038_auto_20170415_1701.py new file mode 100644 index 0000000000000000000000000000000000000000..9c05245587c16cd1071a262df440a0e725bedeff --- /dev/null +++ b/submissions/migrations/0038_auto_20170415_1701.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-15 15:01 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0037_auto_20170415_1659'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('unassigned', 'Unassigned, undergoing pre-screening'), ('resubmitted_incomin', 'Resubmission incoming, undergoing pre-screening'), ('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/submissions/migrations/0039_auto_20170416_0948.py b/submissions/migrations/0039_auto_20170416_0948.py new file mode 100644 index 0000000000000000000000000000000000000000..8a00e0853e02543954eacc23a1a0f8a3e0e92436 --- /dev/null +++ b/submissions/migrations/0039_auto_20170416_0948.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-16 07:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0038_auto_20170415_1701'), + ] + + operations = [ + migrations.AlterField( + model_name='eicrecommendation', + name='submission', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='eicrecommendations', to='submissions.Submission'), + ), + ] 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/submissions/migrations/0041_auto_20170418_1022.py b/submissions/migrations/0041_auto_20170418_1022.py new file mode 100644 index 0000000000000000000000000000000000000000..ab707138a9dde9e2331b3e297af47c442a009e10 --- /dev/null +++ b/submissions/migrations/0041_auto_20170418_1022.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-04-18 08:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0040_auto_20170416_2152'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('unassigned', 'Unassigned, undergoing pre-screening'), ('resubmitted_incoming', '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/submissions/models.py b/submissions/models.py index 0a14e4c8c8e683dc0473b6755d4819287a7d0a85..1e15662cff72c521acc024ccbf6c2f0e056a34a3 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -5,11 +5,16 @@ from django.db import models, transaction from django.contrib.postgres.fields import JSONField from django.urls import reverse -from .constants import ASSIGNMENT_REFUSAL_REASONS, SUBMISSION_STATUS, ASSIGNMENT_NULLBOOL,\ +from .constants import ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL,\ SUBMISSION_TYPE, ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC,\ - RANKING_CHOICES, REPORT_REC, REPORT_REFUSAL_CHOICES,\ - REPORT_STATUSES, STATUS_UNVETTED -from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager + RANKING_CHOICES, REPORT_REC, SUBMISSION_STATUS, STATUS_UNASSIGNED,\ + REPORT_STATUSES, STATUS_UNVETTED, STATUS_RESUBMISSION_INCOMING,\ + SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC,\ + SUBMISSION_EIC_RECOMMENDATION_REQUIRED +from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager,\ + ReportManager +from .utils import ShortSubmissionCycle, DirectRecommendationSubmissionCycle,\ + GeneralSubmissionCycle from scipost.behaviors import ArxivCallable from scipost.constants import TITLE_CHOICES @@ -29,8 +34,8 @@ class Submission(ArxivCallable, models.Model): author_list = models.CharField(max_length=1000, verbose_name="author list") discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS) - editor_in_charge = models.ForeignKey(Contributor, related_name='EIC', blank=True, null=True, - on_delete=models.CASCADE) + editor_in_charge = models.ForeignKey('scipost.Contributor', related_name='EIC', blank=True, + null=True, on_delete=models.CASCADE) is_current = models.BooleanField(default=True) is_resubmission = models.BooleanField(default=False) list_of_changes = models.TextField(blank=True, null=True) @@ -44,21 +49,24 @@ class Submission(ArxivCallable, models.Model): models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), blank=True, null=True) # Status set by Editors - status = models.CharField(max_length=30, choices=SUBMISSION_STATUS, default='unassigned') + status = models.CharField(max_length=30, choices=SUBMISSION_STATUS, default=STATUS_UNASSIGNED) + refereeing_cycle = models.CharField(max_length=30, choices=SUBMISSION_CYCLES, + default=CYCLE_DEFAULT) subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, verbose_name='Primary subject area', default='Phys:QP') submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE, blank=True, null=True, default=None) - submitted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE) + submitted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) + # Replace this by foreignkey? submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT, - verbose_name="Journal to be submitted to") #Replace this by foreignkey? + verbose_name="Journal to be submitted to") title = models.CharField(max_length=300) # Authors which have been mapped to contributors: - authors = models.ManyToManyField(Contributor, blank=True, related_name='authors_sub') - authors_claims = models.ManyToManyField(Contributor, blank=True, + authors = models.ManyToManyField('scipost.Contributor', blank=True, related_name='authors_sub') + authors_claims = models.ManyToManyField('scipost.Contributor', blank=True, related_name='authors_sub_claims') - authors_false_claims = models.ManyToManyField(Contributor, blank=True, + authors_false_claims = models.ManyToManyField('scipost.Contributor', blank=True, related_name='authors_sub_false_claims') abstract = models.TextField() @@ -80,6 +88,14 @@ class Submission(ArxivCallable, models.Model): ('can_take_editorial_actions', 'Can take editorial actions'), ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._update_cycle() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self._update_cycle() + def __str__(self): header = (self.arxiv_identifier_w_vn_nr + ', ' + self.title[:30] + ' by ' + self.author_list[:30]) @@ -88,14 +104,30 @@ class Submission(ArxivCallable, models.Model): else: header += ' (deprecated version ' + str(self.arxiv_vn_nr) + ')' try: - header += ' (published as ' + self.publication.citation() + ')' + header += ' (published as %s (%s))' % (self.publication.doi_string, + self.publication.publication_date.strftime('%Y')) except Publication.DoesNotExist: pass return header + def _update_cycle(self): + """ + Append the specific submission cycle to the instance to eventually handle the + complete submission cycle outside the submission instance itself. + """ + if self.refereeing_cycle == CYCLE_SHORT: + self.cycle = ShortSubmissionCycle(self) + elif self.refereeing_cycle == CYCLE_DIRECT_REC: + self.cycle = DirectRecommendationSubmissionCycle(self) + else: + self.cycle = GeneralSubmissionCycle(self) + def get_absolute_url(self): return reverse('submissions:submission', args=[self.arxiv_identifier_w_vn_nr]) + def eic_recommendation_required(self): + return self.status not in SUBMISSION_EIC_RECOMMENDATION_REQUIRED + @property def reporting_deadline_has_passed(self): return timezone.now() > self.reporting_deadline @@ -103,10 +135,13 @@ class Submission(ArxivCallable, models.Model): @transaction.atomic def finish_submission(self): if self.is_resubmission: + # If submissions is a resubmission, the submission needs to be prescreened + # by the EIC to choose which of the available submission cycle to assign self.mark_other_versions_as_deprecated() self.copy_authors_from_previous_version() self.copy_EIC_from_previous_version() self.set_resubmission_defaults() + self.status = STATUS_RESUBMISSION_INCOMING else: self.authors.add(self.submitted_by) @@ -167,13 +202,13 @@ class Submission(ArxivCallable, models.Model): # Underneath: All very inefficient methods as they initiate a new query def count_accepted_invitations(self): - return self.refereeinvitation_set.filter(accepted=True).count() + return self.referee_invitations.filter(accepted=True).count() def count_declined_invitations(self): - return self.refereeinvitation_set.filter(accepted=False).count() + return self.referee_invitations.filter(accepted=False).count() def count_pending_invitations(self): - return self.refereeinvitation_set.filter(accepted=None).count() + return self.referee_invitations.filter(accepted=None).count() def count_invited_reports(self): return self.reports.filter(status=1, invited=True).count() @@ -196,8 +231,8 @@ class Submission(ArxivCallable, models.Model): ###################### class EditorialAssignment(models.Model): - submission = models.ForeignKey(Submission, on_delete=models.CASCADE) - to = models.ForeignKey(Contributor, on_delete=models.CASCADE) + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE) + to = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None) # attribute `deprecated' becomes True if another Fellow becomes Editor-in-charge deprecated = models.BooleanField(default=False) @@ -216,9 +251,10 @@ class EditorialAssignment(models.Model): class RefereeInvitation(models.Model): - submission = models.ForeignKey(Submission, on_delete=models.CASCADE) - referee = models.ForeignKey(Contributor, related_name='referee', blank=True, null=True, - on_delete=models.CASCADE) + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, + related_name='referee_invitations') + referee = models.ForeignKey('scipost.Contributor', related_name='referee', blank=True, + null=True, on_delete=models.CASCADE) # Why is this blank/null=True 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='') @@ -226,7 +262,7 @@ class RefereeInvitation(models.Model): # if Contributor not found, person is invited to register invitation_key = models.CharField(max_length=40, default='') date_invited = models.DateTimeField(default=timezone.now) - invited_by = models.ForeignKey(Contributor, related_name='referee_invited_by', + invited_by = models.ForeignKey('scipost.Contributor', related_name='referee_invited_by', blank=True, null=True, on_delete=models.CASCADE) nr_reminders = models.PositiveSmallIntegerField(default=0) date_last_reminded = models.DateTimeField(blank=True, null=True) @@ -242,6 +278,20 @@ class RefereeInvitation(models.Model): self.submission.title[:30] + ' by ' + self.submission.author_list[:30] + ', invited on ' + self.date_invited.strftime('%Y-%m-%d')) + @property + def referee_str(self): + if self.referee: + return str(self.referee) + return self.last_name + ', ' + self.first_name + + def reset_content(self): + self.nr_reminders = 0 + self.date_last_reminded = None + self.accepted = None + self.refusal_reason = None + self.fulfilled = False + self.cancelled = False + ########### # Reports: @@ -250,15 +300,16 @@ class RefereeInvitation(models.Model): class Report(models.Model): """ Both types of reports, invited or contributed. """ status = models.SmallIntegerField(choices=REPORT_STATUSES, default=STATUS_UNVETTED) - submission = models.ForeignKey(Submission, related_name='reports', on_delete=models.CASCADE) - vetted_by = models.ForeignKey(Contributor, related_name="report_vetted_by", + submission = models.ForeignKey('submissions.Submission', related_name='reports', + on_delete=models.CASCADE) + vetted_by = models.ForeignKey('scipost.Contributor', related_name="report_vetted_by", blank=True, null=True, on_delete=models.CASCADE) # `invited' filled from RefereeInvitation objects at moment of report submission invited = models.BooleanField(default=False) # `flagged' if author of report has been flagged by submission authors (surname check only) flagged = models.BooleanField(default=False) date_submitted = models.DateTimeField('date submitted') - author = models.ForeignKey(Contributor, on_delete=models.CASCADE) + author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) qualification = models.PositiveSmallIntegerField( choices=REFEREE_QUALIFICATION, verbose_name="Qualification to referee this: I am ") @@ -282,6 +333,8 @@ class Report(models.Model): verbose_name='optional remarks for the Editors only') anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') + objects = ReportManager() + def __str__(self): return (self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' + self.submission.title[:50] + ' by ' + self.submission.author_list[:50]) @@ -296,8 +349,8 @@ class EditorialCommunication(models.Model): Each individual communication between Editor-in-charge to and from Referees and Authors becomes an instance of this class. """ - submission = models.ForeignKey(Submission, on_delete=models.CASCADE) - referee = models.ForeignKey(Contributor, related_name='referee_in_correspondence', + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE) + referee = models.ForeignKey('scipost.Contributor', related_name='referee_in_correspondence', blank=True, null=True, on_delete=models.CASCADE) comtype = models.CharField(max_length=4, choices=ED_COMM_CHOICES) timestamp = models.DateTimeField(default=timezone.now) @@ -318,7 +371,8 @@ class EditorialCommunication(models.Model): # From the Editor-in-charge of a Submission class EICRecommendation(models.Model): - submission = models.ForeignKey(Submission, on_delete=models.CASCADE) + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, + related_name='eicrecommendations') date_submitted = models.DateTimeField('date submitted', default=timezone.now) remarks_for_authors = models.TextField(blank=True, null=True) requested_changes = models.TextField(verbose_name="requested changes", blank=True, null=True) diff --git a/submissions/templates/submissions/_form_submission_cycle_choice.html b/submissions/templates/submissions/_form_submission_cycle_choice.html new file mode 100644 index 0000000000000000000000000000000000000000..3cfbd7176a6859a1a4a429a51acb45723fd894e9 --- /dev/null +++ b/submissions/templates/submissions/_form_submission_cycle_choice.html @@ -0,0 +1,85 @@ +{% load bootstrap %} +<form method="post" action="{% url 'submissions:cycle_confirmation' submission.arxiv_identifier_w_vn_nr %}"> + {% csrf_token %} + <div class="row"> + <div class="col-12"> + <h3>This submission is a resubmission, please choose which submission cycle to proceed with</h3> + </div> + </div> + + <!-- Refereeing cycle --> + <div class="form-group row"> + <label class="col-form-label col-md-2 ">Refereeing cycle</label> + <div class="col-md-10"> + {% for field in form.refereeing_cycle %} + <div class="radio" data-reinvite="{% if field.choice_value == 'direct_rec' %}0{% else %}1{% endif %}"> + <label for="{{field.id_for_label}}" class="mb-0"> + {{field.tag}} + {{field.choice_label}} + </label> + </div> + <p class="help-block text-muted"> + {{ field.help_text|safe }} + {% if field.choice_value == 'short' %} + Run a speedy refereeing round: two weeks, with option of reinviting previous referees + {% elif field.choice_value == 'direct_rec' %} + Immediately write an editorial recommendation. + {% else %} + Run a new full refereeing round: four weeks as usual, can invite previous referees and/or new ones. + {% endif %} + </p> + + {% for error in field.errors %} + <span class="help-block {{ form.error_css_class }}">{{ error }}</span> + {% endfor %} + {% endfor %} + </div> + </div><!-- end refereeing cycle --> + + <!-- Reinvite referees --> + <div id="id_referees_reinvite_block" style="display: none;"> + <div class="row"> + <div class="col-12"> + <h3>The following referees were also invited in the last submission</h3> + <h4 class="text-muted">Please choose who you want to reinvite (an invitation will be automatically emailed to the referee)</h4> + </div> + </div> + <div class="form-group row"> + <label class="col-form-label col-md-2 ">Reinvite referees</label> + <div class="col-md-10"> + <ul class="list-group list-group-flush"> + {% for referee in form.referees_reinvite.field.queryset %} + <li class="list-group-item py-1"> + <label for="{{form.referees_reinvite.name}}_{{forloop.counter0}}" class="mb-0"> + <input checked="checked" id="{{form.referees_reinvite.name}}_{{forloop.counter0}}" name="{{form.referees_reinvite.name}}" type="checkbox" value="{{referee.id}}"> + <div class="d-inline-block" style="vertical-align: top;"> + {{referee.referee_str}} + <br> + <span class="text-muted">Originally invited on {{referee.date_invited}}</span> + </div> + </label> + </li> + {% endfor %} + </ul> + </div> + </div><!-- end reinvite referees --> + </div> + <input type="submit" class="btn btn-primary" value="Confirm choice"> +</form> + +<script> + $(function(){ + $('[name="{{form.refereeing_cycle.name}}"]').on('click change', function(){ + var reinvite = $('[name="{{form.refereeing_cycle.name}}"]:checked').parents('.radio').attr('data-reinvite') == 1; + + // Show/hide invitation block + if(reinvite > 0) { + $('#id_referees_reinvite_block').show(); + } else { + $('#id_referees_reinvite_block').hide(); + } + }).trigger('change'); + + + }); +</script> diff --git a/submissions/templates/submissions/_required_actions_block.html b/submissions/templates/submissions/_required_actions_block.html new file mode 100644 index 0000000000000000000000000000000000000000..59c82998dc34bb6c21c31804385d8b72ffbacea1 --- /dev/null +++ b/submissions/templates/submissions/_required_actions_block.html @@ -0,0 +1,14 @@ +{% if submission.cycle.has_required_actions %} + <div class="card {% if submission.cycle.get_required_actions %}card-danger text-white{% else %}card-outline-success text-success{% endif %}"> + <div class="card-block"> + <h3 class="card-title pt-0">Required actions:</h3> + <ul class="mb-0"> + {% for action in submission.cycle.get_required_actions %} + <li>{{action.1}}</li> + {% empty %} + <li>No actions required</li> + {% endfor %} + </ul> + </div> + </div> +{% endif %} diff --git a/submissions/templates/submissions/_submission_card_base.html b/submissions/templates/submissions/_submission_card_base.html index 355d2f2e137b96c0c708c7b9b25def988627f611..c1c516251ddf66dce7924472050f3d45c4dc1582 100644 --- a/submissions/templates/submissions/_submission_card_base.html +++ b/submissions/templates/submissions/_submission_card_base.html @@ -1,5 +1,5 @@ -<div class="card-block"> - <h3 class="card-title"> +<div class="card-block {% block cardblock_class_block %}{% endblock %}"> + <h3 class="card-title {% block title_class_block %}{% endblock %}"> <a href="{% url 'submissions:submission' submission.arxiv_identifier_w_vn_nr %}">{{submission.title}}</a> </h3> diff --git a/submissions/templates/submissions/_submission_card_content.html b/submissions/templates/submissions/_submission_card_content.html index ed9d01a7aef68db99bbdce1b013860b2d2c1c1e9..e22ce33c7322cc4d396ab30040545bb570db0151 100644 --- a/submissions/templates/submissions/_submission_card_content.html +++ b/submissions/templates/submissions/_submission_card_content.html @@ -4,5 +4,10 @@ {{block.super}} <p class="card-text">by {{submission.author_list}}</p> <p class="card-text text-muted">Version {{submission.arxiv_vn_nr}} ({% if submission.is_current %}current version{% else %}deprecated version {{submission.arxiv_vn_nr}}{% endif %})</p> - <p class="card-text text-muted">Submitted {{submission.submission_date}} to {{submission.get_submitted_to_journal_display}} - latest activity: {{submission.latest_activity}}</p> + <p class="card-text text-muted"> + {% if submission.publication %} + Published as <a href="{{submission.publication.get_absolute_url}}">{{submission.publication.in_issue.in_volume.in_journal.get_abbreviation_citation}} <strong>{{submission.publication.in_issue.in_volume.number}}</strong>, {{submission.publication.get_paper_nr}} ({{submission.publication.publication_date|date:'Y'}})</a> + {% else %} + Submitted {{submission.submission_date}} to {{submission.get_submitted_to_journal_display}} + {% endif %} - latest activity: {{submission.latest_activity}}</p> {% endblock %} diff --git a/submissions/templates/submissions/_submission_card_content_sparse.html b/submissions/templates/submissions/_submission_card_content_sparse.html new file mode 100644 index 0000000000000000000000000000000000000000..906b925abf41d3c1e98390859516711c5f665232 --- /dev/null +++ b/submissions/templates/submissions/_submission_card_content_sparse.html @@ -0,0 +1,14 @@ +{% extends 'submissions/_submission_card_base.html' %} + +{% block cardblock_class_block %}px-0{% endblock %} +{% block title_class_block %}pb-0 mb-0{% endblock %} + +{% block card_block_footer %} + {{block.super}} + + <div class="text-muted mb-1"> + <span class="d-inline-block">Submitted {{submission.submission_date}}</span> + <span class="d-inline-block">to {{submission.get_submitted_to_journal_display}}</span> + </div> + <div>by {{submission.author_list}}</div> +{% endblock %} diff --git a/submissions/templates/submissions/_submission_card_fellow_content.html b/submissions/templates/submissions/_submission_card_fellow_content.html index ac70f328dc94adadfb72beb81d9d836b3146f045..6af2e213aa06bba299eb2d3c13987b10b0b3fbfa 100644 --- a/submissions/templates/submissions/_submission_card_fellow_content.html +++ b/submissions/templates/submissions/_submission_card_fellow_content.html @@ -1,19 +1,7 @@ -{% extends 'submissions/_submission_card_base.html' %} +{% extends 'submissions/_submission_card_fellow_content_sparse.html' %} {% block card_block_footer %} {{block.super}} - <p class="card-text">by {{submission.author_list}}</p> - <p class="card-text text-muted">Version {{submission.arxiv_vn_nr}} ({% if submission.is_current %}current version{% else %}deprecated version {{submission.arxiv_vn_nr}}{% endif %})</p> - <p class="card-text text-muted">Submitted {{submission.submission_date}} to {{submission.get_submitted_to_journal_display}} - latest activity: {{submission.latest_activity}}</p> - - <!-- Fellow specific info --> - {% if submission.status == 'unassigned' %} - <p class="card-text text-danger">Status: {{ submission.get_status_display }}. You can volunteer to become Editor-in-charge by <a href="{% url 'submissions:volunteer_as_EIC' submission.arxiv_identifier_w_vn_nr %}">clicking here</a>.</p> - {% else %} - <p class="card-text">Editor-in-charge: <em>{{ submission.editor_in_charge }}</em></p> - <p class="card-text">Status: {{ submission.get_status_display }}</p> - {% endif %} - {% include 'submissions/_submission_refereeing_status.html' with submission=submission %} {% endblock %} diff --git a/submissions/templates/submissions/_submission_card_fellow_content_sparse.html b/submissions/templates/submissions/_submission_card_fellow_content_sparse.html new file mode 100644 index 0000000000000000000000000000000000000000..8fbd9b1aeb82e3675874435338df0f7a40cdb66a --- /dev/null +++ b/submissions/templates/submissions/_submission_card_fellow_content_sparse.html @@ -0,0 +1,13 @@ +{% extends 'submissions/_submission_card_content.html' %} + +{% block card_block_footer %} + {{block.super}} + + <!-- Fellow specific info --> + {% if submission.status == 'unassigned' %} + <p class="card-text text-danger">Status: {{ submission.get_status_display }}. You can volunteer to become Editor-in-charge by <a href="{% url 'submissions:volunteer_as_EIC' submission.arxiv_identifier_w_vn_nr %}">clicking here</a>.</p> + {% else %} + <p class="card-text">Editor-in-charge: <em>{{ submission.editor_in_charge }}</em></p> + <p class="card-text">Status: {{ submission.get_status_display }}</p> + {% endif %} +{% endblock %} diff --git a/submissions/templates/submissions/_submission_card_in_pool.html b/submissions/templates/submissions/_submission_card_in_pool.html index 9b5c3d28fff551614e67c8c4f0abb8df4bbf761a..4f248454545bd8ed3e8f2405e6ad15bf43532f84 100644 --- a/submissions/templates/submissions/_submission_card_in_pool.html +++ b/submissions/templates/submissions/_submission_card_in_pool.html @@ -21,17 +21,7 @@ {% get_obj_perms request.user for submission as "sub_perms" %} {% if "can_take_editorial_actions" in sub_perms or is_ECAdmin %} - {% if submission|required_actions %} - <div class="required-actions"> - <h3 class="pt-0">Required actions:</h3> - <ul> - {% for todoitem in submission|required_actions %} - <li>{{ todoitem }}</li> - {% endfor %} - </ul> - </div> - {% endif %} - + {% include 'submissions/_required_actions_block.html' with submission=submission %} <h4> <a href="{% url 'submissions:editorial_page' submission.arxiv_identifier_w_vn_nr %}">Go to this Submission's Editorial Page</a> </h4> diff --git a/submissions/templates/submissions/_submission_refereeing_status.html b/submissions/templates/submissions/_submission_refereeing_status.html index 68736208cf500daa45206848b5e6ae54ae8019e9..94e9ba3cbd35ab3eccca9bcba9e3fc9c4d9585d7 100644 --- a/submissions/templates/submissions/_submission_refereeing_status.html +++ b/submissions/templates/submissions/_submission_refereeing_status.html @@ -1,4 +1,6 @@ -<div class="card-block"> - <p class="card-text">Nr referees invited: {{submission.refereeinvitation_set.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span></p> - <p class="card-text">Nr reports obtained: {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.count_refused_resports}}, nr awaiting vetting: {{submission.count_awaiting_vetting}}</p> -</div> +{% if submission.refereeing_cycle != 'direct_rec' %} + <div class="card-block"> + <p class="card-text">Nr referees invited: {{submission.referee_invitations.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span></p> + <p class="card-text">Nr reports obtained: {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.count_refused_resports}}, nr awaiting vetting: {{submission.count_awaiting_vetting}}</p> + </div> +{% endif %} diff --git a/submissions/templates/submissions/_submission_status_block.html b/submissions/templates/submissions/_submission_status_block.html index 7e56189de30e81b8257272c914aa0a4e17e2ddda..f4812d1d3a9a41215a6f8d8c637707088e4d4e41 100644 --- a/submissions/templates/submissions/_submission_status_block.html +++ b/submissions/templates/submissions/_submission_status_block.html @@ -1,4 +1,4 @@ -<h3 class="d-inline-block">Current status:</h3> +<h4 class="d-inline-block">Current status:</h4> <div class="d-inline"> <span class="label label-secondary">{{submission.get_status_display}}</span> {% if submission.publication %} diff --git a/submissions/templates/submissions/accept_or_decline_ref_invitations.html b/submissions/templates/submissions/accept_or_decline_ref_invitations.html index 12465e7f61e2510bd50d5b474c0dad6df54fe8c5..932ecaa432168d1e5e9369b07dd5d572c23e56fe 100644 --- a/submissions/templates/submissions/accept_or_decline_ref_invitations.html +++ b/submissions/templates/submissions/accept_or_decline_ref_invitations.html @@ -2,56 +2,50 @@ {% block pagetitle %}: accept or decline refereeing invitations{% endblock pagetitle %} -{% block bodysup %} +{% load bootstrap %} + +{% block content %} <script> $(document).ready(function(){ - $('#ref_reason').hide(); - - $('#id_accept').on('change', function() { - if ($('#id_accept_1').is(':checked')) { - $('#ref_reason').show(); + $('[name="accept"]').on('change', function() { + if($('[name="accept"]:checked').val() == 'False') { + $('#id_refusal_reason').parents('.form-group').show(); } else { - $('#ref_reason').hide(); + $('#id_refusal_reason').parents('.form-group').hide(); } - }); + }).trigger('change'); }); </script> - -<section> - {% if not invitation_to_consider %} - <h1>There are no Refereeing Invitations for you to consider.</h1> - - {% else %} - - <div class="flex-greybox"> - <h1>SciPost Submission which you are asked to Referee (see below to accept/decline):</h1> - </div> - <br> - <hr> - {% include 'submissions/_submission_summary.html' with submission=invitation_to_consider.submission %} - - <br/> - <hr> - <div class="flex-greybox"> - <h1>Accept or Decline this Refereeing Invitation</h1> - </div> - <h3>Please let us know if you can provide us with a Report for this Submission:</h3> - <form action="{% url 'submissions:accept_or_decline_ref_invitation_ack' invitation_id=invitation_to_consider.id %}" method="post"> - {% csrf_token %} - {{ form.accept }} - <div id="ref_reason"> - <p>Please select a reason for declining this invitation:</p> - {{ form.refusal_reason }} +{% if not invitation_to_consider %} + <div class="row"> + <div class="col-12"> + <h1>There are no Refereeing Invitations for you to consider.</h1> + </div> </div> - <input type="submit" value="Submit" /> - </form> - - {% endif %} -</section> +{% else %} + <div class="row"> + <div class="col-12"> + <h1 class="highlight">SciPost Submission which you are asked to Referee (see below to accept/decline):</h1> + {% include 'submissions/_submission_summary.html' with submission=invitation_to_consider.submission %} + </div> + </div> + <div class="row"> + <div class="col-12"> + <h2 class="highlight">Accept or Decline this Refereeing Invitation</h2> + <h3>Please let us know if you can provide us with a Report for this Submission:</h3> + <p class="text-muted">We will expect your report by <b>{{invitation_to_consider.submission.reporting_deadline}}</b></p> + <form action="{% url 'submissions:accept_or_decline_ref_invitation_ack' invitation_id=invitation_to_consider.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-secondary" value="Submit" /> + </form> + </div> + </div> +{% endif %} -{% endblock bodysup %} +{% endblock content %} diff --git a/submissions/templates/submissions/assignments.html b/submissions/templates/submissions/assignments.html index ed4ba37f61f4da762b7137600fcbae98dbc24c82..e94a003d88658ee021faa3881e201d6d3b16e55e 100644 --- a/submissions/templates/submissions/assignments.html +++ b/submissions/templates/submissions/assignments.html @@ -1,11 +1,13 @@ -{% extends 'scipost/base.html' %} - -{% load guardian_tags %} -{% load scipost_extras %} -{% load submissions_extras %} +{% extends 'submissions/_pool_base.html' %} {% block pagetitle %}: Assignments{% endblock pagetitle %} +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'submissions:pool' %}" class="breadcrumb-item">Pool</a> + <span class="breadcrumb-item">Your assignments</span> +{% endblock %} + {% block content %} <script> @@ -22,7 +24,6 @@ $(document).ready(function(){ } }); }); - </script> {% if assignments_to_consider %} @@ -53,34 +54,20 @@ $(document).ready(function(){ <h1 class="highlight">Your current assignments:</h1> </div> </div> - <div class="row"> - <div class="col-12"> - <ul class="list-group list-group-flush"> - {% for assignment in current_assignments %} - <li class="list-group-item"> - {% include 'submissions/_submission_card_fellow_content.html' with submission=assignment.submission %} - <div class="card-block"> - {% with actions=assignment.submission|required_actions %} - <div class="required-actions{% if not actions %} no-actions{% endif %}"> - <h3>{% if actions %}Required actions{% else %}No required actions{% endif %}</h3> - {% if actions %} - <ul> - {% for todoitem in assignment.submission|required_actions %} - <li>{{ todoitem }}</li> - {% endfor %} - </ul> - {% endif %} - </div> - {% endwith %} - <h4 class="d-block mt-2"> - <a href="{% url 'submissions:editorial_page' arxiv_identifier_w_vn_nr=assignment.submission.arxiv_identifier_w_vn_nr %}">Go to this Submission's Editorial Page</a> - </h4> - </div> - </li> - {% endfor %} - </ul> + {% for assignment in current_assignments %} + {% if not forloop.first %}<hr class="small">{% endif %} + <div class="row "> + <div class="col-lg-8"> + {% include 'submissions/_submission_card_fellow_content.html' with submission=assignment.submission %} + </div> + <div class="col-lg-4"> + {% include 'submissions/_required_actions_block.html' with submission=submission %} + <h4 class="d-block mt-2"> + <a href="{% url 'submissions:editorial_page' arxiv_identifier_w_vn_nr=assignment.submission.arxiv_identifier_w_vn_nr %}">Go to this Submission's Editorial Page</a> + </h4> + </div> </div> - </div> + {% endfor %} {% else %} <div class="row"> <div class="col-12"> diff --git a/submissions/templates/submissions/editorial_page.html b/submissions/templates/submissions/editorial_page.html index fb3a8d7f9625743ac5b37c8addcfcf75c1ec235c..c2a20c722a204e2b42e55e0f762038b6af899185 100644 --- a/submissions/templates/submissions/editorial_page.html +++ b/submissions/templates/submissions/editorial_page.html @@ -3,7 +3,6 @@ {% block pagetitle %}: editorial page for submission{% endblock pagetitle %} {% load scipost_extras %} -{% load submissions_extras %} {% load bootstrap %} {% block breadcrumb_items %} @@ -93,100 +92,101 @@ </div> </div> -<div class="row"> +<div class="row"><!-- Status --> <div class="col-md-12"> {% include 'submissions/_submission_status_block.html' with submission=submission %} </div> -</div> +</div><!-- End status --> <div class="row"> <div class="col-md-10 col-lg-8"> - <div class="card {% if submission|required_actions %}card-danger text-white{% else %}card-outline-success text-success{% endif %}"> - <div class="card-block"> - <h3 class="card-title pt-0">Required actions:</h3> - <ul class="mb-0"> - {% for todoitem in submission|required_actions %} - <li>{{ todoitem }}</li> - {% empty %} - <li>No actions required</li> - {% endfor %} - </ul> - </div> - </div> + {% include 'submissions/_required_actions_block.html' with submission=submission %} </div> </div> -<div class="row"> - <div class="col-12"> - <h3>Refereeing status summary:</h3> - {% include 'submissions/_submission_refereeing_status.html' with submission=submission %} +{% if submission.status == 'resubmitted_incoming' %} + <div class="row"> + <div class="col-12"> + {% include 'submissions/_form_submission_cycle_choice.html' with form=cycle_choice_form submission=submission %} + </div> </div> -</div> +{% else %} -<div class="row"> - <div class="col-12"> - <h3 class="mb-2">Detail of refereeing invitations:</h3> - {% include 'submissions/_submission_refereeing_invitations.html' with submission=submission invitations=ref_invitations %} - </div> -</div> + {% if submission.refereeing_cycle != 'direct_rec' %} + <div class="row"> + <div class="col-12"> + <h3>Refereeing status summary:</h3> + {% include 'submissions/_submission_refereeing_status.html' with submission=submission %} + </div> + </div> -<hr> + <div class="row"> + <div class="col-12"> + <h3 class="mb-2">Detail of refereeing invitations:</h3> + {% include 'submissions/_submission_refereeing_invitations.html' with submission=submission invitations=ref_invitations %} + </div> + </div> + {% endif %} -{% if not submission.is_current %} -<div class="row"> - <div class="col-12"> - <div class="card card-outline-warning"> - <div class="card-block"> - <h3 class="mb-3 font-weight-bold">BEWARE: This is not the editorial page for the current version!</h3> - <p class="mb-0"> - The tools here are thus available only for exceptional circumstances (e.g. vetting a late report on a deprecated version). - <br>Please go to the current version's page using the link at the top. - </p> + <hr> + + {% if not submission.is_current %} + <div class="row"> + <div class="col-12"> + <div class="card card-outline-warning"> + <div class="card-block"> + <h3 class="mb-3 font-weight-bold">BEWARE: This is not the editorial page for the current version!</h3> + <p class="mb-0"> + The tools here are thus available only for exceptional circumstances (e.g. vetting a late report on a deprecated version). + <br>Please go to the current version's page using the link at the top. + </p> + </div> </div> </div> </div> -</div> -{% endif %} + {% endif %} -<div class="row"> - <div class="col-12"> - <h3>Actions:</h3> - <ul> - <li> - <a href="{% url 'submissions:select_referee' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Select an additional referee</a> (bear in mind flagged referees if any) - </li> - <li>Extend the refereeing deadline (currently {{ submission.reporting_deadline|date:'Y-m-d' }}) by - <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=2 %}">2 days</a>, - <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=7 %}">1 week</a> or - <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=14 %}">2 weeks</a> - {% if submission.reporting_deadline_has_passed %} - <span class="ml-1 label label-sm label-outline-danger">THE REPORTING DEADLINE HAS PASSED</span> + <div class="row"> + <div class="col-12"> + <h3>Actions:</h3> + <ul> + {% if submission.refereeing_cycle != 'direct_rec' %} + <li> + <a href="{% url 'submissions:select_referee' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Select an additional referee</a> (bear in mind flagged referees if any) + </li> + <li>Extend the refereeing deadline (currently {{ submission.reporting_deadline|date:'Y-m-d' }}) by + <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=2 %}">2 days</a>, + <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=7 %}">1 week</a> or + <a href="{% url 'submissions:extend_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr days=14 %}">2 weeks</a> + {% if submission.reporting_deadline_has_passed %} + <span class="ml-1 label label-sm label-outline-danger">THE REPORTING DEADLINE HAS PASSED</span> + {% endif %} + </li> + <li> + Set refereeing deadline: + <form class="form-inline" action="{% url 'submissions:set_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}" method="post"> + {% csrf_token %}{{ set_deadline_form|bootstrap_inline:'0,12' }} + <div class="ml-2 form-group row"> + <div class="col-12"> + <input class="btn btn-secondary" type="submit" value="Set deadline"/> + </div> + </div> + </form> + </li> + <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + {% if not submission.reporting_deadline_has_passed %} + <li><a href="{% url 'submissions:close_refereeing_round' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Close the refereeing round</a> (deactivates submission of new Reports and Comments)</li> + {% endif %} {% endif %} - </li> - <li> - Set refereeing deadline: - <form class="form-inline" action="{% url 'submissions:set_refereeing_deadline' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}" method="post"> - {% csrf_token %}{{ set_deadline_form|bootstrap_inline:'0,12' }} - <div class="ml-2 form-group row"> - <div class="col-12"> - <input class="btn btn-secondary" type="submit" value="Set deadline"/> - </div> - </div> - </form> - </li> - <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> - {% if not submission.reporting_deadline_has_passed %} - <li><a href="{% url 'submissions:close_refereeing_round' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Close the refereeing round</a> (deactivates submission of new Reports and Comments)</li> - {% endif %} - <li>Communicate with a Referee: see the communication block below.</li> - <li> - <a href="{% url 'submissions:eic_recommendation' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Formulate an Editorial Recommendation</a> - <p>If you recommend revisions, this will be communicated directly to the Authors, who will be asked to resubmit. - <br/>If you recommend acceptance or rejection, this will be put to the Editorial College for ratification.</p> - </li> - </ul> + <li> + <a href="{% url 'submissions:eic_recommendation' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Formulate an Editorial Recommendation</a> + <p>If you recommend revisions, this will be communicated directly to the Authors, who will be asked to resubmit. + <br/>If you recommend acceptance or rejection, this will be put to the Editorial College for ratification.</p> + </li> + </ul> + </div> </div> -</div> +{% endif %} <div class="row"> <div class="col-12"> diff --git a/submissions/templates/submissions/new_submission.html b/submissions/templates/submissions/new_submission.html index 71fb9dcc12c2ea218e0749b1d1b2c3e578da911b..ba80d4cb1826f76836ba35354c8836d075c9e9dd 100644 --- a/submissions/templates/submissions/new_submission.html +++ b/submissions/templates/submissions/new_submission.html @@ -2,84 +2,75 @@ {% block pagetitle %}: submit manuscript{% endblock pagetitle %} -{% block bodysup %} +{% load bootstrap %} + +{% block content %} <script> $(document).ready(function(){ - $("#id_submission_type").closest('tr').hide() - $('select#id_submitted_to_journal').on('change', function (){ - var selection = $(this).val(); - switch(selection){ - case "SciPost Physics": - $("#id_submission_type").closest('tr').show() - break; - default: - $("#id_submission_type").closest('tr').hide() - } -}); + var selection = $(this).val(); + switch(selection){ + case "SciPostPhys": + $("#id_submission_type").parents('.form-group').show() + break; + default: + $("#id_submission_type").parents('.form-group').hide() + } + }).trigger('change'); var isresub = $("#id_is_resubmission").val(); switch(isresub){ case "True": - $("#id_author_comments").closest('tr').show() - $("#id_list_of_changes").closest('tr').show() + $("#id_author_comments, #id_list_of_changes").parents('.form-group').show(); break; default: - $("#id_author_comments").closest('tr').hide() - $("#id_list_of_changes").closest('tr').hide() + $("#id_author_comments, #id_list_of_changes").parents('.form-group').hide(); } }); </script> -<section> - <div class="flex-greybox"> - <h1>Submit a manuscript to SciPost</h1> - </div> - - <p>Before submitting, make sure you agree with the - <a href="{% url 'journals:journals_terms_and_conditions' %}"> - SciPost Journals Terms and Conditions</a>.</p> - <p>You should also make sure you understand the - <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr"> - refereeing procedure</a> and its open aspect.</p> - <p>In particular, make sure you are familiar with the - <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement"> - license and copyright agreement</a> - and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations"> - author obligations</a>.</p> - <p>Please prepare your manuscript according to the - <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>.</p> - - - {% if perms.scipost.can_submit_manuscript %} - - {% if form.arxiv_link.value %} - <form id="full_submission_form" action="{% url 'submissions:submit_manuscript' %}" method="post"> - {% csrf_token %} - <table> - <ul> - {{ form.as_table }} - </ul> - </table> - <p>By clicking on Submit, you state that you have read and agree with - the <a href="{% url 'journals:journals_terms_and_conditions' %}"> - SciPost Journals Terms and Conditions</a>, the - <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement"> - license and copyright agreement</a> - and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations"> - author obligations</a>.</p> - <input type="submit" value="Submit"/> - </form> - {% endif %} - - {% else %} - <h3>You are currently not allowed to submit a manuscript.</h3> - {% endif %} - -</section> - - -{% endblock bodysup %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Submit a manuscript to SciPost</h1> + <p class="mb-1"> + Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. + </p> + <p class="mb-1"> + You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. + </p> + <p class="mb-1"> + In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations"> author obligations</a>. + </p> + <p> + Please prepare your manuscript according to the <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>. + </p> + </div> +</div> + + +<div class="row mb-5"> + <div class="col-12"> + {% if perms.scipost.can_submit_manuscript %} + + {% if form.arxiv_link.value %} + <form id="full_submission_form" action="{% url 'submissions:submit_manuscript' %}" method="post"> + {% csrf_token %} + {{form|bootstrap}} + <p> + By clicking on Submit, you state that you have read and agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>, the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations">author obligations</a>. + </p> + <input type="submit" class="btn btn-secondary"/> + </form> + {% endif %} + + {% else %} + <h3>You are currently not allowed to submit a manuscript.</h3> + {% endif %} + </div> +</div> + + +{% endblock content %} diff --git a/submissions/templates/submissions/pool.html b/submissions/templates/submissions/pool.html index c557b6b443fe8f3c483788a29743c2c126535a5f..3f2911a9c524f928f65d6129b95f97943d34914e 100644 --- a/submissions/templates/submissions/pool.html +++ b/submissions/templates/submissions/pool.html @@ -45,7 +45,7 @@ <div class="col-12" id="undergoing_rec_{{rec.id}}"> <div class="card card-outline-secondary"> - {% include 'submissions/_submission_card_fellow_content.html' with submission=rec.submission %} + {% include 'submissions/_submission_card_fellow_content_sparse.html' with submission=rec.submission %} </div> <div class="card card-outline-secondary"> @@ -131,7 +131,7 @@ <div class="row"> <div class="col-12" id="prepare_rec_{{rec.id}}"> <div class="card card-outline-secondary"> - {% include 'submissions/_submission_card_fellow_content.html' with submission=rec.submission %} + {% include 'submissions/_submission_card_fellow_content_sparse.html' with submission=rec.submission %} </div> <div class="card card-outline-secondary"> diff --git a/submissions/templates/submissions/prefill_using_identifier.html b/submissions/templates/submissions/prefill_using_identifier.html index 41f3dd90c645969b1b5752d8c4166a787f00c905..5af8f507c3b2805aaff6ce3bb87a12e15cab0ab5 100644 --- a/submissions/templates/submissions/prefill_using_identifier.html +++ b/submissions/templates/submissions/prefill_using_identifier.html @@ -1,9 +1,10 @@ {% extends 'scipost/base.html' %} -{% block pagetitle %}: submit manuscript{% endblock pagetitle %} +{% load bootstrap %} -{% block bodysup %} +{% block pagetitle %}: submit manuscript{% endblock pagetitle %} +{% block content %} <script> $(document).ready(function(){ @@ -34,53 +35,50 @@ $(document).ready(function(){ }); </script> -<section> - <div class="flex-greybox"> - <h1>Submit a manuscript to SciPost</h1> - </div> - - <p>Before submitting, make sure you agree with the - <a href="{% url 'journals:journals_terms_and_conditions' %}"> - SciPost Journals Terms and Conditions</a>.</p> - <p>You should also make sure you understand the - <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr"> - refereeing procedure</a> and its open aspect.</p> - <p>In particular, make sure you are familiar with the - <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement"> - license and copyright agreement</a> - and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations"> - author obligations</a>.</p> - <p>Please prepare your manuscript according to the - <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>.</p> - - - {% if perms.scipost.can_submit_manuscript %} - - <div class="flex-greybox"> - <h3><em>Please provide the arXiv identifier for your Submission:</em></h3> - <p> - <em>(give the identifier without prefix but with version number, as per the placeholder)</em> - </p> - <form action="{% url 'submissions:prefill_using_identifier' %}" method="post"> - {% csrf_token %} - {{ form.as_p }} - <input type="submit" value="Query arXiv"/> - </form> - </div> - <br/> - <!-- {% if errormessage %} - <h3 style="color: red;">Error: {{ errormessage }}</h3> - {% endif %} --> - - {% if resubmessage %} - <h3 style="color: green;">{{ resubmessage }}</h3> - {% endif %} - - {% else %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Submit a manuscript to SciPost</h1> + <p class="mb-1"> + Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. + </p> + <p class="mb-1"> + You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. + </p> + <p class="mb-1"> + In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations"> author obligations</a>. + </p> + <p> + Please prepare your manuscript according to the <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>. + </p> + </div> +</div> + +{% if perms.scipost.can_submit_manuscript %} + +<div class="row"> + <div class="col-12"> + <div class="card card-grey"> + <div class='card-block'> + <h3>Please provide the arXiv identifier for your Submission</h3> + <p><em>(give the identifier without prefix but with version number, as per the placeholder)</em></p> + <form action="{% url 'submissions:prefill_using_identifier' %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-secondary" value="Query arXiv"/> + </form> + </div> + </div> + </div> +</div> + + + {% if resubmessage %} + <h3 class="text-success">{{ resubmessage }}</h3> + {% endif %} + +{% else %} <h3>You are currently not allowed to submit a manuscript.</h3> - {% endif %} - -</section> +{% endif %} -{% endblock bodysup %} +{% endblock content %} diff --git a/submissions/templates/submissions/submit_report.html b/submissions/templates/submissions/submit_report.html index 75df1341db37d23d4b3887593c5de63ab06e865c..72a253c84058ac31f315ed052402b541edd3d08b 100644 --- a/submissions/templates/submissions/submit_report.html +++ b/submissions/templates/submissions/submit_report.html @@ -1,4 +1,9 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Submit a report</span> +{% endblock %} {% load bootstrap %} diff --git a/submissions/templatetags/lookup.py b/submissions/templatetags/lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..0c73d76071d7ac88453dc094807492ff4f6347f6 --- /dev/null +++ b/submissions/templatetags/lookup.py @@ -0,0 +1,22 @@ +from ajax_select import register, LookupChannel +from ..models import Submission + + +@register('submissions_lookup') +class SubmissionLookup(LookupChannel): + model = Submission + + def get_query(self, q, request): + return (self.model.objects + .public() + .order_by('-submission_date') + .filter(title__icontains=q) + .prefetch_related('publication')[:10]) + + def format_item_display(self, item): + '''(HTML) format item for displaying item in the selected deck area.''' + return u"<span class='auto_lookup_display'>%s</span>" % item + + def format_match(self, item): + '''(HTML) Format item for displaying in the dropdown.''' + return u"%s<br><span class='text-muted'>by %s</span>" % (item.title, item.author_list) diff --git a/submissions/templatetags/submissions_extras.py b/submissions/templatetags/submissions_extras.py index 2da991f6d3ce3c33c022055bee26bbbbc5d7c597..83a792a917a31309c6bb14fe9976a15be9e4337b 100644 --- a/submissions/templatetags/submissions_extras.py +++ b/submissions/templatetags/submissions_extras.py @@ -24,58 +24,3 @@ def is_viewable_by_authors(recommendation): return recommendation.submission.status in ['revision_requested', 'resubmitted', 'accepted', 'rejected', 'published', 'withdrawn'] - - -@register.filter(name='required_actions') -def required_actions(submission): - """ - This method returns a list of required actions on a Submission. - Each list element is a textual statement. - """ - if (submission.status in SUBMISSION_STATUS_OUT_OF_POOL - or submission.status == 'revision_requested' - or submission.eicrecommendation_set.exists()): - return [] - todo = [] - for comment in submission.comment_set.all(): - if comment.status == 0: - todo.append('A Comment from %s has been delivered but is not yet vetted. ' - 'Please vet it.' % comment.author) - nr_ref_inv = submission.refereeinvitation_set.count() - if (submission.is_resubmission and nr_ref_inv == 0 - and not submission.eicrecommendation_set.exists()): - todo.append('This resubmission requires attention: either (re)invite referees ' - 'or formulate an Editorial Recommendation.') - if nr_ref_inv == 0 and not submission.is_resubmission: - todo.append('No Referees have yet been invited. ' - 'At least 3 should be.') - elif nr_ref_inv < 3 and not submission.is_resubmission: - todo.append('Only %s Referees have been invited. ' - 'At least 3 should be.' % nr_ref_inv) - for ref_inv in submission.refereeinvitation_set.all(): - refname = ref_inv.last_name + ', ' + ref_inv.first_name - if ref_inv.referee: - refname = str(ref_inv.referee) - timelapse = timezone.now() - ref_inv.date_invited - timeleft = submission.reporting_deadline - timezone.now() - if (ref_inv.accepted is None and not ref_inv.cancelled - and timelapse > datetime.timedelta(days=3)): - todo.append('Referee %s has not responded for %s days. ' - 'Consider sending a reminder ' - 'or cancelling the invitation.' % (refname, str(timelapse.days))) - if (ref_inv.accepted and not ref_inv.fulfilled and not ref_inv.cancelled - and timeleft < datetime.timedelta(days=7)): - todo.append('Referee %s has accepted to send a Report, ' - 'but not yet delivered it (with %s days left). ' - 'Consider sending a reminder or cancelling the invitation.' - % (refname, str(timeleft.days))) - if submission.reporting_deadline < timezone.now(): - todo.append('The refereeing deadline has passed. Please either extend it, ' - 'or formulate your Editorial Recommendation if at least ' - 'one Report has been received.') - reports = submission.reports.all() - for report in reports: - if report.status == 0: - todo.append('The Report from %s has been delivered but is not yet vetted. ' - 'Please vet it.' % report.author) - return todo diff --git a/submissions/test_models.py b/submissions/test_models.py index 2e9cb5f6ba351402af656aec1be5d9ac257bc5c0..1bc015369576f76d287e2a4af2709a801279e201 100644 --- a/submissions/test_models.py +++ b/submissions/test_models.py @@ -1 +1,10 @@ from django.test import TestCase + +# from .factories import ResubmittedScreeningSubmissionFactory +# +# +# class NewSubmissionStatusTest(TestCase): +# '''Do tests to check the submission status cycle.''' +# def test_resubmitted_submission(self): +# '''New resubmission in pre-screening.''' +# submission = ResubmittedScreeningSubmissionFactory() diff --git a/submissions/test_views.py b/submissions/test_views.py index bb177e9292a3dc57e2c9a01a91c3a161e47ffb89..8b797db80ccb9ddac353a6174482359509d428c3 100644 --- a/submissions/test_views.py +++ b/submissions/test_views.py @@ -27,6 +27,7 @@ class PrefillUsingIdentifierTest(TestCase): self.assertEqual(response.status_code, 200) + class SubmitManuscriptTest(TestCase): fixtures = ['permissions', 'groups', 'contributors'] diff --git a/submissions/urls.py b/submissions/urls.py index 57c4240a5e94626f2835b5b34a7712c30d894515..042f2f393d35300d05d06f8433619dfde5aae436 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -50,7 +50,7 @@ urlpatterns = [ views.recruit_referee, name='recruit_referee'), url(r'^send_refereeing_invitation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/(?P<contributor_id>[0-9]+)$', views.send_refereeing_invitation, name='send_refereeing_invitation'), - url(r'^accept_or_decline_ref_invitations$', + url(r'^accept_or_decline_ref_invitations/$', views.accept_or_decline_ref_invitations, name='accept_or_decline_ref_invitations'), url(r'^accept_or_decline_ref_invitation/(?P<invitation_id>[0-9]+)$', views.accept_or_decline_ref_invitation_ack, name='accept_or_decline_ref_invitation_ack'), @@ -69,6 +69,8 @@ urlpatterns = [ views.communication, name='communication'), url(r'^eic_recommendation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', views.eic_recommendation, name='eic_recommendation'), + url(r'^cycle/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/submit$', views.cycle_form_submit, + name='cycle_confirmation'), # Reports url(r'^submit_report/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', views.submit_report, name='submit_report'), diff --git a/submissions/utils.py b/submissions/utils.py index 5687a3f35f856a97fd4429324ce60d02b70c879e..fb2039cc6df596f19c5dbce7b37985c794f9ee11 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -2,24 +2,257 @@ import datetime from django.core.mail import EmailMessage, EmailMultiAlternatives from django.template import Context, Template +from django.utils import timezone + +from .constants import NO_REQUIRED_ACTION_STATUSES,\ + STATUS_REVISION_REQUESTED, STATUS_EIC_ASSIGNED,\ + STATUS_RESUBMISSION_INCOMING, STATUS_AWAITING_ED_REC from scipost.utils import EMAIL_FOOTER +from common.utils import BaseMailUtil -from submissions.models import EditorialAssignment +class BaseSubmissionCycle: + """ + The submission cycle may take different approaches. All steps within a specific + cycle are handles by the class related to the specific cycle chosen. This class + is meant as an abstract blueprint for the overall submission cycle and its needed + actions. + """ + default_days = 28 + may_add_referees = True + may_reinvite_referees = True + minimum_referees = 3 + name = None + required_actions = [] + submission = None + updated_action = False -class SubmissionUtils(object): + def __init__(self, submission): + self.submission = submission + + def __str__(self): + return self.submission.get_refereeing_cycle_display() + + def _update_actions(self): + """ + Create the list of required_actions for the current submission to be used on the + editorial page. + """ + self.required_actions = [] + if self.submission.status in NO_REQUIRED_ACTION_STATUSES: + '''Submission does not appear in the pool, no action required.''' + return False + + if self.submission.status == STATUS_REVISION_REQUESTED: + ''''Editor-in-charge has requested revision''' + return False + + if self.submission.eicrecommendations.exists(): + '''A Editorial Recommendation has already been submitted. Cycle done.''' + return False + + if self.submission.status == STATUS_RESUBMISSION_INCOMING: + """ + Submission is a resubmission and the EIC still has to determine which + cycle to proceed with. + """ + self.required_actions.append(('choose_cycle', + 'Choose the submission cycle to proceed with.',)) + return False + + comments_to_vet = self.submission.comments.awaiting_vetting().count() + if comments_to_vet > 0: + '''There are comments on the submission awaiting vetting.''' + if comments_to_vet > 1: + text = '%i Comment\'s have' % comments_to_vet + else: + text = 'One Comment has' + text += ' been delivered but is not yet vetted. Please vet it.' + self.required_actions.append(('vet_comments', text,)) + + nr_ref_inv = self.submission.referee_invitations.count() + if nr_ref_inv < self.minimum_referees: + """ + The submission cycle does not meet the criteria of a minimum of + `self.minimum_referees` referees yet. + """ + text = 'No' if nr_ref_inv == 0 else 'Only %i' % nr_ref_inv + text += ' Referees have yet been invited.' + text += ' At least %i should be.' % self.minimum_referees + self.required_actions.append(('invite_referees', text,)) + + reports_awaiting_vetting = self.submission.reports.awaiting_vetting().count() + if reports_awaiting_vetting > 0: + '''There are reports on the submission awaiting vetting.''' + if reports_awaiting_vetting > 1: + text = '%i Reports have' % reports_awaiting_vetting + else: + text = 'One Report has' + text += ' been delivered but is not yet vetted. Please vet it.' + self.required_actions.append(('vet_reports', text,)) + + return True + + def reinvite_referees(self, referees, request=None): + """ + Reinvite referees if allowed. This method does not check if it really is + a reinvitation or just a new invitation. + """ + if self.may_reinvite_referees: + SubmissionUtils.load({'submission': self.submission}) + for referee in referees: + invitation = referee + invitation.pk = None # Duplicate, do not remove the old invitation + invitation.submission = self.submission + invitation.reset_content() + invitation.date_invited = timezone.now() + invitation.save() + SubmissionUtils.load({'invitation': invitation}, request) + SubmissionUtils.reinvite_referees_email() + + def update_deadline(self, period=None): + delta_d = period or self.default_days + deadline = timezone.now() + datetime.timedelta(days=delta_d) + self.submission.reporting_deadline = deadline + self.submission.save() + + def get_required_actions(self): + '''Return list of the submission its required actions''' + if not self.updated_action: + self._update_actions() + self.updated_action = True + return self.required_actions + + def has_required_actions(self): + """ + Certain submission statuses will not show the required actions block. + The decision to show this block is taken by this method. + """ + return self.submission.status not in NO_REQUIRED_ACTION_STATUSES + + def update_status(self): + """ + Implement: + Let the submission status be centrally handled by this method. This makes sure + the status cycle is clear and makes sure the cycle isn't broken due to unclear coding + elsewhere. The next status to go to should ideally be determined on all the + available in the submission with only few exceptions to explicilty force a new status code. + """ + raise NotImplementedError + + +class BaseRefereeSubmissionCycle(BaseSubmissionCycle): + """ + This *abstract* submission cycle adds the specific actions needed for submission cycles + that require referees to be invited. + """ + def update_status(self): + if self.submission.status == STATUS_RESUBMISSION_INCOMING: + self.submission.status = STATUS_EIC_ASSIGNED + self.submission.save() + + def _update_actions(self): + continue_update = super()._update_actions() + if not continue_update: + return False + + for ref_inv in self.submission.referee_invitations.all(): + if not ref_inv.cancelled: + if ref_inv.accepted is None: + '''An invited referee may have not responsed yet.''' + timelapse = timezone.now() - ref_inv.date_invited + if timelapse > datetime.timedelta(days=3): + text = ('Referee %s has not responded for %i days. ' + 'Consider sending a reminder or cancelling the invitation.' + % (ref_inv.referee_str, timelapse.days)) + self.required_actions.append(('referee_no_response', text,)) + elif ref_inv.accepted and not ref_inv.fulfilled: + '''A referee has not fulfilled its duty and the deadline is closing in.''' + timeleft = self.submission.reporting_deadline - timezone.now() + if timeleft < datetime.timedelta(days=7): + text = ('Referee %s has accepted to send a Report, ' + 'but not yet delivered it ' % ref_inv.referee_str) + if timeleft.days < 0: + text += '(%i days overdue). ' % (- timeleft.days) + elif timeleft.days == 1: + text += '(with 1 day left). ' + else: + text += '(with %i days left). ' % timeleft.days + text += 'Consider sending a reminder or cancelling the invitation.' + self.required_actions.append(('referee_no_delivery', text,)) + + if self.submission.reporting_deadline < timezone.now(): + text = ('The refereeing deadline has passed. Please either extend it, ' + 'or formulate your Editorial Recommendation if at least ' + 'one Report has been received.') + self.required_actions.append(('deadline_passed', text,)) + + return True + + +class GeneralSubmissionCycle(BaseRefereeSubmissionCycle): + """ + The default submission cycle assigned to all 'regular' submissions and resubmissions + which are explicitly assigned to go trough the default cycle by the EIC. + It's a four week cycle with full capabilities i.e. invite referees, vet reports, etc. etc. + """ + pass - @classmethod - def load(cls, _dict): - for var_name in _dict: - setattr(cls, var_name, _dict[var_name]) + +class ShortSubmissionCycle(BaseRefereeSubmissionCycle): + """ + This cycle is used if the EIC has explicitly chosen to do a short version of the general + submission cycle. The deadline is within two weeks instead of the default four weeks. + + This cycle is only available for resubmitted submissions! + """ + default_days = 14 + may_add_referees = False + minimum_referees = 1 + pass + + +class DirectRecommendationSubmissionCycle(BaseSubmissionCycle): + """ + This cycle is used if the EIC has explicitly chosen to immediately write an + editorial recommendation. + + This cycle is only available for resubmitted submissions! + """ + may_add_referees = False + may_reinvite_referees = False + minimum_referees = 0 + + def update_status(self): + if self.submission.status == STATUS_RESUBMISSION_INCOMING: + self.submission.status = STATUS_AWAITING_ED_REC + self.submission.save() + + def _update_actions(self): + continue_update = super()._update_actions() + if not continue_update: + return False + + # No EIC Recommendation has been formulated yet + text = 'Formulate an Editorial Recommendation.' + self.required_actions.append(('need_eic_rec', text,)) + + return True + + +class SubmissionUtils(BaseMailUtil): + mail_sender = 'submissions@scipost.org' + mail_sender_title = 'SciPost Editorial Admin' @classmethod def deprecate_other_assignments(cls): """ Called when a Fellow has accepted or volunteered to become EIC. """ + # Import here due to circular import error + from .models import EditorialAssignment + assignments_to_deprecate = (EditorialAssignment.objects .filter(submission=cls.assignment.submission, accepted=None) .exclude(to=cls.assignment.to)) @@ -33,12 +266,30 @@ class SubmissionUtils(object): Called when the pre-screening has failed. Requires loading 'submission' attribute. """ + # Import here due to circular import error + from .models import EditorialAssignment + assignments_to_deprecate = (EditorialAssignment.objects .filter(submission=cls.submission, accepted=None)) for atd in assignments_to_deprecate: atd.deprecated = True atd.save() + @classmethod + def reinvite_referees_email(cls): + """ + Email to be sent to referees when they are being reinvited by the EIC. + + Requires context to contain: + - `invitation` + """ + extra_bcc_list = [cls._context['invitation'].submission.editor_in_charge.user.email] + cls._send_mail(cls, 'submission_cycle_reinvite_referee', + [cls._context['invitation'].email_address], + 'Invitation on resubmission', + extra_bcc=extra_bcc_list) + + @classmethod def send_authors_submission_ack_email(cls): """ Requires loading 'submission' attribute. """ @@ -233,64 +484,9 @@ class SubmissionUtils(object): @classmethod def send_EIC_reappointment_email(cls): """ Requires loading 'submission' attribute. """ - email_text = ('Dear ' + cls.submission.editor_in_charge.get_title_display() + ' ' - + cls.submission.editor_in_charge.user.last_name - + ', \n\nThe authors of the SciPost Submission\n\n' - + cls.submission.title + ' by ' - + cls.submission.author_list + - '\n\nhave resubmitted their manuscript. ' - '\n\nAs Editor-in-charge, you can take your editorial actions ' - 'from the editorial page ' - 'https://scipost.org/submission/editorial_page/' - + cls.submission.arxiv_identifier_w_vn_nr - + ' (also accessible from your personal page ' - 'https://scipost.org/personal_page under the Editorial Actions tab). ' - '\n\nYou can either take an immediate acceptance/rejection decision, ' - 'or run a new refereeing round, in which case you ' - 'should now invite at least 3 referees; you might want to ' - 'make sure you are aware of the ' - 'detailed procedure described in the Editorial College by-laws at ' - 'https://scipost.org/EdCol_by-laws.' - '\n\nMany thanks in advance for your collaboration,' - '\n\nThe SciPost Team.') - email_text_html = ( - '<p>Dear {{ title }} {{ last_name }},</p>' - '<p>The authors of the SciPost Submission</p>' - '<p>{{ sub_title }}</p>' - '\n<p>by {{ author_list }}</p>' - '\n<p>have resubmitted their manuscript.</p>' - '\n<p>As Editor-in-charge, you can take your editorial actions ' - 'from the submission\'s <a href="https://scipost.org/submission/editorial_page/' - '{{ arxiv_identifier_w_vn_nr }}">editorial page</a>' - ' (also accessible from your ' - '<a href="https://scipost.org/personal_page">personal page</a> ' - 'under the Editorial Actions tab).</p>' - '\n<p>You can either take an immediate acceptance/rejection decision, ' - 'or run a new refereeing round, in which case you ' - 'should now invite at least 3 referees; you might want to ' - 'make sure you are aware of the ' - 'detailed procedure described in the ' - '<a href="https://scipost.org/EdCol_by-laws">Editorial College by-laws</a>.</p>' - '<p>Many thanks in advance for your collaboration,</p>' - '<p>The SciPost Team.</p>') - email_context = Context({ - 'title': cls.submission.editor_in_charge.get_title_display(), - 'last_name': cls.submission.editor_in_charge.user.last_name, - 'sub_title': cls.submission.title, - 'author_list': cls.submission.author_list, - 'arxiv_identifier_w_vn_nr': cls.submission.arxiv_identifier_w_vn_nr, - }) - email_text_html += '<br/>' + EMAIL_FOOTER - html_template = Template(email_text_html) - html_version = html_template.render(email_context) - emailmessage = EmailMultiAlternatives( - 'SciPost: resubmission received', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [cls.submission.editor_in_charge.user.email], - bcc=['submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.attach_alternative(html_version, 'text/html') - emailmessage.send(fail_silently=False) + cls._send_mail(cls, 'submission_eic_reappointment', + [cls._context['submission'].editor_in_charge.user.email], + 'resubmission received') @classmethod def send_author_prescreening_passed_email(cls): @@ -312,7 +508,7 @@ class SubmissionUtils(object): 'Submission Page; you will be informed by email of any such Report or ' 'Comment being delivered). In order to facilitate the work of the ' 'Editorial College, we recommend limiting these replies to short ' - 'to-the-point clarifications of any issue raised on your manuscript.\n\n ' + 'to-the-point clarifications of any issue raised on your manuscript.\n\n' 'Please wait for the Editor-in-charge\'s Editorial Recommendation ' 'before any resubmission of your manuscript.' '\n\nTo facilitate metadata handling, we recommend that all authors ' @@ -674,108 +870,32 @@ class SubmissionUtils(object): @classmethod def email_referee_response_to_EIC(cls): - """ Requires loading 'invitation' attribute. """ - email_text = ('Dear ' + cls.invitation.submission.editor_in_charge.get_title_display() + ' ' + - cls.invitation.submission.editor_in_charge.user.last_name + ',' - '\n\nReferee ' + cls.invitation.referee.get_title_display() + ' ' + - cls.invitation.referee.user.last_name + ' has ') - email_text_html = ( - '<p>Dear {{ EIC_title }} {{ EIC_last_name }},</p>' - '<p>Referee {{ ref_title }} {{ ref_last_name }} has ') - email_subject = 'SciPost: referee declines to review' - if cls.invitation.accepted: - email_text += 'accepted ' - email_text_html += 'accepted ' - email_subject = 'SciPost: referee accepts to review' - elif not cls.invitation.accepted: - email_text += ('declined (due to reason: ' - + cls.invitation.get_refusal_reason_display() + ') ') - email_text_html += 'declined (due to reason: {{ reason }}) ' - - email_text += ('to referee Submission\n\n' - + cls.invitation.submission.title + ' by ' - + cls.invitation.submission.author_list + '.') - email_text_html += ( - 'to referee Submission</p>' - '<p>{{ sub_title }}</p>\n<p>by {{ author_list }}.</p>') - if not cls.invitation.accepted: - email_text += ('\n\nPlease invite another referee from the Submission\'s editorial page ' - 'at https://scipost.org/submissions/editorial_page/' - + cls.invitation.submission.arxiv_identifier_w_vn_nr + '.') - email_text_html += ( - '\n<p>Please invite another referee from the Submission\'s ' - '<a href="https://scipost.org/submissions/editorial_page/' - '{{ arxiv_identifier_w_vn_nr }}">editorial page</a>.</p>') - email_text += ('\n\nMany thanks for your collaboration,' - '\n\nThe SciPost Team.') - email_text_html += ('<p>Many thanks for your collaboration,</p>' - '<p>The SciPost Team.</p>') - email_context = Context({ - 'EIC_title': cls.invitation.submission.editor_in_charge.get_title_display(), - 'EIC_last_name': cls.invitation.submission.editor_in_charge.user.last_name, - 'ref_title': cls.invitation.referee.get_title_display(), - 'ref_last_name': cls.invitation.referee.user.last_name, - 'sub_title': cls.invitation.submission.title, - 'author_list': cls.invitation.submission.author_list, - 'arxiv_identifier_w_vn_nr': cls.invitation.submission.arxiv_identifier_w_vn_nr, - }) - if cls.invitation.refusal_reason: - email_context['reason'] = cls.invitation.get_refusal_reason_display - email_text_html += '<br/>' + EMAIL_FOOTER - html_template = Template(email_text_html) - html_version = html_template.render(email_context) - emailmessage = EmailMultiAlternatives( - email_subject, email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [cls.invitation.submission.editor_in_charge.user.email], - bcc=['submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.attach_alternative(html_version, 'text/html') - emailmessage.send(fail_silently=False) + '''Requires loading `invitation` attribute.''' + if cls._context['invitation'].accepted: + email_subject = 'referee accepts to review' + else: + email_subject = 'referee declines to review' + cls._send_mail(cls, 'referee_response_to_EIC', + [cls._context['invitation'].submission.editor_in_charge.user.email], + email_subject) + + @classmethod + def email_referee_in_response_to_decision(cls): + '''Requires loading `invitation` attribute.''' + if cls._context['invitation'].accepted: + email_subject = 'confirmation accepted invitation' + else: + email_subject = 'confirmation declined invitation' + cls._send_mail(cls, 'referee_in_response_to_decision', + [cls._context['invitation'].referee.user.email], + email_subject) @classmethod def email_EIC_report_delivered(cls): """ Requires loading 'report' attribute. """ - email_text = ('Dear ' + cls.report.submission.editor_in_charge.get_title_display() + ' ' - + cls.report.submission.editor_in_charge.user.last_name + ',' - '\n\nReferee ' + cls.report.author.get_title_display() + ' ' - + cls.report.author.user.last_name + - ' has delivered a Report for Submission\n\n' - + cls.report.submission.title + ' by ' - + cls.report.submission.author_list + '.' - '\n\nPlease vet this Report via your personal page at ' - 'https://scipost.org/personal_page under the Editorial Actions tab.' - '\n\nMany thanks for your collaboration,' - '\n\nThe SciPost Team.') - email_text_html = ( - '<p>Dear {{ EIC_title }} {{ EIC_last_name }},</p>' - '<p>Referee {{ ref_title }} {{ ref_last_name }} ' - 'has delivered a Report for Submission</p>' - '<p>{{ sub_title }}</p>\n<p>by {{ author_list }}.</p>' - '\n<p>Please vet this Report via your ' - '<a href="https://scipost.org/personal_page">personal page</a> ' - 'under the Editorial Actions tab.</p>' - '<p>Many thanks for your collaboration,</p>' - '<p>The SciPost Team.</p>') - email_context = Context({ - 'EIC_title': cls.report.submission.editor_in_charge.get_title_display(), - 'EIC_last_name': cls.report.submission.editor_in_charge.user.last_name, - 'ref_title': cls.report.author.get_title_display(), - 'ref_last_name': cls.report.author.user.last_name, - 'sub_title': cls.report.submission.title, - 'author_list': cls.report.submission.author_list, - }) - email_text_html += '<br/>' + EMAIL_FOOTER - html_template = Template(email_text_html) - html_version = html_template.render(email_context) - emailmessage = EmailMultiAlternatives( - 'SciPost: Report delivered', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [cls.report.submission.editor_in_charge.user.email], - bcc=['submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.attach_alternative(html_version, 'text/html') - emailmessage.send(fail_silently=False) + cls._send_mail(cls, 'report_delivered_eic', + [cls._context['report'].submission.editor_in_charge.user.email], + 'Report delivered') @classmethod def acknowledge_report_email(cls): diff --git a/submissions/views.py b/submissions/views.py index 54f3c34fd51b956cf78a103060a65c98e4e8f6f7..99eb79e517db0ff148d5620b5000dd9b9abb1151 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -4,10 +4,9 @@ import feedparser from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.models import Group -from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.db import transaction -from django.http import HttpResponseRedirect +from django.http import Http404 from django.shortcuts import get_object_or_404, render, redirect from django.template import Template, Context from django.utils import timezone @@ -16,7 +15,7 @@ from guardian.decorators import permission_required_or_403 from guardian.mixins import PermissionRequiredMixin from guardian.shortcuts import assign_perm -from .constants import SUBMISSION_STATUS_PUBLICLY_UNLISTED, SUBMISSION_STATUS_VOTING_DEPRECATED,\ +from .constants import SUBMISSION_STATUS_VOTING_DEPRECATED,\ SUBMISSION_STATUS_PUBLICLY_INVISIBLE, SUBMISSION_STATUS, ED_COMM_CHOICES from .models import Submission, EICRecommendation, EditorialAssignment,\ RefereeInvitation, Report, EditorialCommunication @@ -24,10 +23,10 @@ from .forms import SubmissionIdentifierForm, SubmissionForm, SubmissionSearchFor RecommendationVoteForm, ConsiderAssignmentForm, AssignSubmissionForm,\ SetRefereeingDeadlineForm, RefereeSelectForm, RefereeRecruitmentForm,\ ConsiderRefereeInvitationForm, EditorialCommunicationForm,\ - EICRecommendationForm, ReportForm, VetReportForm, VotingEligibilityForm + EICRecommendationForm, ReportForm, VetReportForm, VotingEligibilityForm,\ + SubmissionCycleChoiceForm from .utils import SubmissionUtils -from comments.models import Comment from scipost.forms import ModifyPersonalMessageForm, RemarkForm from scipost.models import Contributor, Remark, RegistrationInvitation @@ -165,14 +164,12 @@ class SubmissionCreateView(PermissionRequiredMixin, CreateView): assignment = EditorialAssignment( submission=submission, to=submission.editor_in_charge, - accepted=True, - date_created=timezone.now(), - date_answered=timezone.now(), + accepted=True ) assignment.save() # Send emails - SubmissionUtils.load({'submission': submission}) + SubmissionUtils.load({'submission': submission}, self.request) SubmissionUtils.send_authors_resubmission_ack_email() SubmissionUtils.send_EIC_reappointment_email() else: @@ -180,12 +177,10 @@ class SubmissionCreateView(PermissionRequiredMixin, CreateView): SubmissionUtils.load({'submission': submission}) SubmissionUtils.send_authors_submission_ack_email() - context = {'ack_header': 'Thank you for your Submission to SciPost', - 'ack_message': 'Your Submission will soon be handled by an Editor. ', - 'followup_message': 'Return to your ', - 'followup_link': reverse('scipost:personal_page'), - 'followup_link_label': 'personal page'} - return render(self.request, 'scipost/acknowledgement.html', context) + text = ('<h3>Thank you for your Submission to SciPost</h3>' + 'Your Submission will soon be handled by an Editor.') + messages.success(self.request, text) + return redirect(reverse('scipost:personal_page')) def mark_previous_submissions_as_deprecated(self, previous_submissions): for sub in previous_submissions: @@ -208,7 +203,7 @@ class SubmissionListView(ListView): paginate_by = 10 def get_queryset(self): - queryset = Submission.objects.public() + queryset = Submission.objects.public_overcomplete().filter(is_current=True) if 'to_journal' in self.kwargs: queryset = queryset.filter( latest_activity__gte=timezone.now() + datetime.timedelta(days=-60), @@ -219,7 +214,8 @@ class SubmissionListView(ListView): nrweeksback = self.kwargs['nrweeksback'] queryset = queryset.filter( discipline=discipline, - latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback))) + latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback)) + ) elif 'Submit' in self.request.GET: queryset = queryset.filter( title__icontains=self.request.GET.get('title_keyword', ''), @@ -264,51 +260,45 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) try: is_author = request.user.contributor in submission.authors.all() + is_author_unchecked = (not is_author and not + (request.user.contributor in submission.authors_false_claims.all()) and + (request.user.last_name in submission.author_list)) except AttributeError: is_author = False + is_author_unchecked = False if (submission.status in SUBMISSION_STATUS_PUBLICLY_INVISIBLE - and not request.user.groups.filter(name='SciPost Administrators').exists() - and not request.user.groups.filter(name='Editorial Administrators').exists() - and not request.user.groups.filter(name='Editorial College').exists() - and not is_author): - raise PermissionDenied + and not request.user.groups.filter(name__in=['SciPost Administrators', + 'Editorial Administrators', + 'Editorial College']).exists() + and not is_author): + raise Http404 other_versions = Submission.objects.filter( arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr ).exclude(pk=submission.id) form = CommentForm() - reports = submission.reports.all() - try: - author_replies = Comment.objects.filter(submission=submission, - is_author_reply=True, - status__gte=1) - except Comment.DoesNotExist: - author_replies = () - # To check in template whether the user can submit a report: - try: - is_author = request.user.contributor in submission.authors.all() - is_author_unchecked = (not is_author and not - (request.user.contributor in submission.authors_false_claims.all()) and - (request.user.last_name in submission.author_list)) - except AttributeError: - is_author = False - is_author_unchecked = False + invited_reports = submission.reports.accepted().filter(invited=True) + contributed_reports = submission.reports.accepted().filter(invited=False) + comments = submission.comments.vetted().filter(is_author_reply=False).order_by('-date_submitted') + author_replies = submission.comments.vetted().filter(is_author_reply=True).order_by('-date_submitted') + try: recommendation = (EICRecommendation.objects.filter_for_user(request.user) .get(submission=submission)) - except (EICRecommendation.DoesNotExist, AttributeError): + except EICRecommendation.DoesNotExist: recommendation = None - comments = submission.comment_set.all() + context = {'submission': submission, 'other_versions': other_versions, 'recommendation': recommendation, - 'comments': (comments.filter(status__gte=1, is_author_reply=False) - .order_by('-date_submitted')), - 'invited_reports': reports.filter(status__gte=1, invited=True), - 'contributed_reports': reports.filter(status__gte=1, invited=False), - 'author_replies': author_replies, 'form': form, - 'is_author': is_author, 'is_author_unchecked': is_author_unchecked} + 'comments': comments, + 'invited_reports': invited_reports, + 'contributed_reports': contributed_reports, + 'author_replies': author_replies, + 'form': form, + 'is_author': is_author, + 'is_author_unchecked': is_author_unchecked} return render(request, 'submissions/submission_detail.html', context) @@ -336,25 +326,24 @@ def pool(request): All members of the Editorial College have access. """ submissions_in_pool = (Submission.objects.get_pool(request.user) - .prefetch_related('refereeinvitation_set', 'remark_set', 'comment_set')) + .prefetch_related('referee_invitations', 'remark_set', 'comments')) recommendations_undergoing_voting = (EICRecommendation.objects .get_for_user_in_pool(request.user) .filter(submission__status__in=['put_to_EC_voting'])) recommendations_to_prepare_for_voting = (EICRecommendation.objects .get_for_user_in_pool(request.user) - .filter(submission__status__in= - ['voting_in_preparation'])) + .filter( + submission__status__in=['voting_in_preparation'])) contributor = Contributor.objects.get(user=request.user) assignments_to_consider = EditorialAssignment.objects.filter( to=contributor, accepted=None, deprecated=False) consider_assignment_form = ConsiderAssignmentForm() recs_to_vote_on = (EICRecommendation.objects.get_for_user_in_pool(request.user) - .filter(eligible_to_vote__in=[contributor]) - .exclude(recommendation=-1) - .exclude(recommendation=-2) - .exclude(voted_for__in=[contributor]) - .exclude(voted_against__in=[contributor]) - .exclude(voted_abstain__in=[contributor]) + .filter(eligible_to_vote=contributor) + .exclude(recommendation__in=[-1, -2]) + .exclude(voted_for=contributor) + .exclude(voted_against=contributor) + .exclude(voted_abstain=contributor) .exclude(submission__status__in=SUBMISSION_STATUS_VOTING_DEPRECATED)) rec_vote_form = RecommendationVoteForm() remark_form = RemarkForm() @@ -514,15 +503,17 @@ def volunteer_as_EIC(request, arxiv_identifier_w_vn_nr): arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) errormessage = None if submission.status == 'assignment_failed': - errormessage = 'This Submission has failed pre-screening and has been rejected.' - context = {'errormessage': errormessage} - return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) + errormessage = '<h3>Thank you for considering.</h3>' + errormessage += 'This Submission has failed pre-screening and has been rejected.' + messages.warning(request, errormessage) + return redirect(reverse('submissions:pool')) if submission.editor_in_charge: - errormessage = (submission.editor_in_charge.get_title_display() + ' ' + - submission.editor_in_charge.user.last_name + - ' has already agreed to be Editor-in-charge of this Submission.') - context = {'errormessage': errormessage} - return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) + errormessage = '<h3>Thank you for considering.</h3>' + errormessage += (submission.editor_in_charge.get_title_display() + ' ' + + submission.editor_in_charge.user.last_name + + ' has already agreed to be Editor-in-charge of this Submission.') + messages.warning(request, errormessage) + return redirect(reverse('submissions:pool')) contributor = Contributor.objects.get(user=request.user) assignment = EditorialAssignment(submission=submission, to=contributor, @@ -549,8 +540,9 @@ def volunteer_as_EIC(request, arxiv_identifier_w_vn_nr): SubmissionUtils.send_EIC_appointment_email() SubmissionUtils.send_author_prescreening_passed_email() - context = {'assignment': assignment} - return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) + messages.success(request, 'Thank you for becoming Editor-in-charge of this submission.') + return redirect(reverse('submissions:editorial_page', + args=[submission.arxiv_identifier_w_vn_nr])) @login_required @@ -615,14 +607,15 @@ def assignments(request): @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) def editorial_page(request, arxiv_identifier_w_vn_nr): - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.filter_editorial_page(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) other_versions = (Submission.objects .filter(arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr) .exclude(pk=submission.id)) ref_invitations = RefereeInvitation.objects.filter(submission=submission) nr_reports_to_vet = (Report.objects - .filter(status=0, submission__editor_in_charge=request.user.contributor) + .filter(status=0, submission=submission, + submission__editor_in_charge=request.user.contributor) .count()) communications = (EditorialCommunication.objects .filter(submission=submission).order_by('timestamp')) @@ -631,16 +624,37 @@ def editorial_page(request, arxiv_identifier_w_vn_nr): .get(submission=submission)) except EICRecommendation.DoesNotExist: recommendation = None - context = {'submission': submission, - 'other_versions': other_versions, - 'recommendation': recommendation, - 'set_deadline_form': SetRefereeingDeadlineForm(), - 'ref_invitations': ref_invitations, - 'nr_reports_to_vet': nr_reports_to_vet, - 'communications': communications} + context = { + 'submission': submission, + 'other_versions': other_versions, + 'recommendation': recommendation, + 'set_deadline_form': SetRefereeingDeadlineForm(), + 'cycle_choice_form': SubmissionCycleChoiceForm(instance=submission), + 'ref_invitations': ref_invitations, + 'nr_reports_to_vet': nr_reports_to_vet, + 'communications': communications + } return render(request, 'submissions/editorial_page.html', context) +@login_required +@permission_required_or_403('can_take_editorial_actions', + (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) +def cycle_form_submit(request, arxiv_identifier_w_vn_nr): + submission = get_object_or_404(Submission.objects.get_pool(request.user), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + form = SubmissionCycleChoiceForm(request.POST or None, instance=submission) + if form.is_valid(): + submission = form.save() + submission.cycle.update_status() + submission.cycle.update_deadline() + submission.cycle.reinvite_referees(form.cleaned_data['referees_reinvite'], request) + messages.success(request, ('<h3>Your choice has been confirmed</h3>' + 'The new cycle will be <em>%s</em>' + % submission.get_refereeing_cycle_display())) + return redirect(reverse('submissions:editorial_page', args=[submission.arxiv_identifier_w_vn_nr])) + + @login_required @permission_required_or_403('can_take_editorial_actions', (Submission, 'arxiv_identifier_w_vn_nr', 'arxiv_identifier_w_vn_nr')) @@ -766,7 +780,6 @@ def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id date_invited=timezone.now(), invited_by=request.user.contributor) invitation.save() - # raise SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.send_refereeing_invitation_email() return redirect(reverse('submissions:editorial_page', @@ -795,8 +808,14 @@ def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): @login_required @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitations(request): - contributor = Contributor.objects.get(user=request.user) - invitation = RefereeInvitation.objects.filter(referee=contributor, accepted=None).first() + """ + RefereeInvitations need to be either accepted or declined by the invited user + using this view. The decision will be taken one invitation at a time. + """ + invitation = RefereeInvitation.objects.filter(referee__user=request.user, accepted=None).first() + if not invitation: + messages.success(request, 'There are no Refereeing Invitations for you to consider.') + return redirect(reverse('scipost:personal_page')) form = ConsiderRefereeInvitationForm() context = {'invitation_to_consider': invitation, 'form': form} return render(request, 'submissions/accept_or_decline_ref_invitations.html', context) @@ -806,18 +825,18 @@ def accept_or_decline_ref_invitations(request): @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitation_ack(request, invitation_id): invitation = get_object_or_404(RefereeInvitation, pk=invitation_id) - if request.method == 'POST': - form = ConsiderRefereeInvitationForm(request.POST) - if form.is_valid(): - invitation.date_responded = timezone.now() - if form.cleaned_data['accept'] == 'True': - invitation.accepted = True - else: - invitation.accepted = False - invitation.refusal_reason = form.cleaned_data['refusal_reason'] - invitation.save() - SubmissionUtils.load({'invitation': invitation}) - SubmissionUtils.email_referee_response_to_EIC() + form = ConsiderRefereeInvitationForm(request.POST or None) + if form.is_valid(): + invitation.date_responded = timezone.now() + if form.cleaned_data['accept'] == 'True': + invitation.accepted = True + else: + invitation.accepted = False + invitation.refusal_reason = form.cleaned_data['refusal_reason'] + invitation.save() + SubmissionUtils.load({'invitation': invitation}, request) + SubmissionUtils.email_referee_response_to_EIC() + SubmissionUtils.email_referee_in_response_to_decision() context = {'invitation': invitation} return render(request, 'submissions/accept_or_decline_ref_invitation_ack.html', context) @@ -939,28 +958,25 @@ def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): context = {'errormessage': errormessage, 'comtype': comtype} return render(request, 'submissions/communication.html', context) - if request.method == 'POST': - form = EditorialCommunicationForm(request.POST) - if form.is_valid(): - communication = EditorialCommunication(submission=submission, - comtype=comtype, - timestamp=timezone.now(), - text=form.cleaned_data['text']) - if referee_id is not None: - referee = get_object_or_404(Contributor, pk=referee_id) - communication.referee = referee - communication.save() - SubmissionUtils.load({'communication': communication}) - SubmissionUtils.send_communication_email() - if comtype == 'EtoA' or comtype == 'EtoR' or comtype == 'EtoS': - return redirect(reverse('submissions:editorial_page', - kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) - elif comtype == 'AtoE' or comtype == 'RtoE': - return redirect(reverse('scipost:personal_page')) - elif comtype == 'StoE': - return redirect(reverse('submissions:pool')) - else: - form = EditorialCommunicationForm() + form = EditorialCommunicationForm(request.POST or None) + if form.is_valid(): + communication = EditorialCommunication(submission=submission, + comtype=comtype, + timestamp=timezone.now(), + text=form.cleaned_data['text']) + if referee_id is not None: + referee = get_object_or_404(Contributor, pk=referee_id) + communication.referee = referee + communication.save() + SubmissionUtils.load({'communication': communication}) + SubmissionUtils.send_communication_email() + if comtype == 'EtoA' or comtype == 'EtoR' or comtype == 'EtoS': + return redirect(reverse('submissions:editorial_page', + kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) + elif comtype == 'AtoE' or comtype == 'RtoE': + return redirect(reverse('scipost:personal_page')) + elif comtype == 'StoE': + return redirect(reverse('submissions:pool')) context = {'submission': submission, 'comtype': comtype, 'referee_id': referee_id, 'form': form} return render(request, 'submissions/communication.html', context) @@ -972,51 +988,52 @@ def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): def eic_recommendation(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission.objects.get_pool(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) - if submission.status not in ['EICassigned', 'review_closed']: - errormessage = ('This submission\'s current status is: ' + - submission.get_status_display() + '. ' - 'An Editorial Recommendation is not required.') - return render(request, 'scipost/error.html', {'errormessage': errormessage}) - if request.method == 'POST': - form = EICRecommendationForm(request.POST) - if form.is_valid(): - recommendation = EICRecommendation( - submission=submission, - date_submitted=timezone.now(), - remarks_for_authors=form.cleaned_data['remarks_for_authors'], - requested_changes=form.cleaned_data['requested_changes'], - remarks_for_editorial_college=form.cleaned_data['remarks_for_editorial_college'], - recommendation=form.cleaned_data['recommendation'], - voting_deadline=timezone.now() + datetime.timedelta(days=7), - ) - recommendation.save() - # If recommendation is to accept or reject, - # it is forwarded to the Editorial College for voting - # If it is to carry out minor or major revisions, - # it is returned to the Author who is asked to resubmit - if (recommendation.recommendation == 1 or - recommendation.recommendation == 2 or - recommendation.recommendation == 3 or - recommendation.recommendation == -3): - submission.status = 'voting_in_preparation' - elif (recommendation.recommendation == -1 or - recommendation.recommendation == -2): - submission.status = 'revision_requested' - SubmissionUtils.load({'submission': submission, - 'recommendation': recommendation}) - SubmissionUtils.send_author_revision_requested_email() - submission.open_for_reporting = False - submission.save() + if submission.eic_recommendation_required(): + messages.warning(request, ('<h3>An Editorial Recommendation is not required</h3>' + 'This submission\'s current status is: <em>%s</em>' + % submission.get_status_display())) + return redirect(reverse('scipost:editorial_page', + args=[submission.arxiv_identifier_w_vn_nr])) + + form = EICRecommendationForm(request.POST or None) + if form.is_valid(): + recommendation = EICRecommendation( + submission=submission, + date_submitted=timezone.now(), + remarks_for_authors=form.cleaned_data['remarks_for_authors'], + requested_changes=form.cleaned_data['requested_changes'], + remarks_for_editorial_college=form.cleaned_data['remarks_for_editorial_college'], + recommendation=form.cleaned_data['recommendation'], + voting_deadline=timezone.now() + datetime.timedelta(days=7), + ) + recommendation.save() + # If recommendation is to accept or reject, + # it is forwarded to the Editorial College for voting + # If it is to carry out minor or major revisions, + # it is returned to the Author who is asked to resubmit + if (recommendation.recommendation == 1 or + recommendation.recommendation == 2 or + recommendation.recommendation == 3 or + recommendation.recommendation == -3): + submission.status = 'voting_in_preparation' + elif (recommendation.recommendation == -1 or + recommendation.recommendation == -2): + submission.status = 'revision_requested' + SubmissionUtils.load({'submission': submission, + 'recommendation': recommendation}) + SubmissionUtils.send_author_revision_requested_email() + submission.open_for_reporting = False + submission.save() + + # The EIC has fulfilled this editorial assignment. + assignment = get_object_or_404(EditorialAssignment, + submission=submission, to=request.user.contributor) + assignment.completed = True + assignment.save() + messages.success(request, 'Your Editorial Recommendation has been succesfully submitted') + return redirect(reverse('submissions:editorial_page', + kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) - # The EIC has fulfilled this editorial assignment. - assignment = get_object_or_404(EditorialAssignment, - submission=submission, to=request.user.contributor) - assignment.completed = True - assignment.save() - return redirect(reverse('submissions:editorial_page', - kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) - else: - form = EICRecommendationForm() context = {'submission': submission, 'form': form} return render(request, 'submissions/eic_recommendation.html', context) @@ -1030,72 +1047,63 @@ def eic_recommendation(request, arxiv_identifier_w_vn_nr): @permission_required('scipost.can_referee', raise_exception=True) @transaction.atomic def submit_report(request, arxiv_identifier_w_vn_nr): - submission = get_object_or_404(Submission.objects.get_pool(request.user), + submission = get_object_or_404(Submission.objects.all(), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) # Check whether the user can submit a report: is_author = request.user.contributor in submission.authors.all() is_author_unchecked = (not is_author and not (request.user.contributor in submission.authors_false_claims.all()) and (request.user.last_name in submission.author_list)) - invited = RefereeInvitation.objects.filter(submission=submission, - referee=request.user.contributor).exists() + try: + invitation = RefereeInvitation.objects.get(submission=submission, + referee=request.user.contributor) + except RefereeInvitation.DoesNotExist: + invitation = None + errormessage = None - if not invited and timezone.now() > submission.reporting_deadline + datetime.timedelta(days=1): + if not invitation and timezone.now() > submission.reporting_deadline + datetime.timedelta(days=1): errormessage = ('The reporting deadline has passed. You cannot submit' ' a Report anymore.') if is_author: errormessage = 'You are an author of this Submission and cannot submit a Report.' if is_author_unchecked: errormessage = ('The system flagged you as a potential author of this Submission. ' - 'Please go to your personal page under the Submissions tab to clarify this.') + 'Please go to your personal page under the Submissions tab' + ' to clarify this.') if errormessage: - context = {'errormessage': errormessage} - return render(request, 'submissions/submit_report_ack.html', context) + messages.warning(request, errormessage) + return redirect(reverse('scipost:personal_page')) + + form = ReportForm(request.POST or None) + if form.is_valid(): + author = request.user.contributor + newreport = form.save(commit=False) + newreport.submission = submission + newreport.author = request.user.contributor + if invitation: + invitation.fulfilled = True + newreport.invited = True + invitation.save() - if request.method == 'POST': - form = ReportForm(request.POST) - if form.is_valid(): - author = Contributor.objects.get(user=request.user) - if invited: - invitation = RefereeInvitation.objects.get(submission=submission, - referee=request.user.contributor) - invitation.fulfilled = True - invitation.save() - flagged = False - if submission.referees_flagged is not None: - if author.user.last_name in submission.referees_flagged: - flagged = True - newreport = Report( - submission=submission, - author=author, - invited=invited, - flagged=flagged, - qualification=form.cleaned_data['qualification'], - strengths=form.cleaned_data['strengths'], - weaknesses=form.cleaned_data['weaknesses'], - report=form.cleaned_data['report'], - requested_changes=form.cleaned_data['requested_changes'], - validity=form.cleaned_data['validity'], - significance=form.cleaned_data['significance'], - originality=form.cleaned_data['originality'], - clarity=form.cleaned_data['clarity'], - formatting=form.cleaned_data['formatting'], - grammar=form.cleaned_data['grammar'], - recommendation=form.cleaned_data['recommendation'], - remarks_for_editors=form.cleaned_data['remarks_for_editors'], - anonymous=form.cleaned_data['anonymous'], - date_submitted=timezone.now(), - ) - newreport.save() - author.nr_reports = Report.objects.filter(author=author).count() - author.save() - SubmissionUtils.load({'report': newreport}) - SubmissionUtils.email_EIC_report_delivered() - request.session['arxiv_identifier_w_vn_nr'] = arxiv_identifier_w_vn_nr - return HttpResponseRedirect(reverse('submissions:submit_report_ack')) + if submission.referees_flagged is not None: + if author.user.last_name in submission.referees_flagged: + newreport.flagged = True + + newreport.date_submitted = timezone.now() + newreport.save() + + # Update user stats + author.nr_reports = Report.objects.filter(author=author).count() + author.save() + SubmissionUtils.load({'report': newreport}, request) + SubmissionUtils.email_EIC_report_delivered() + + # Why is this session update? + request.session['arxiv_identifier_w_vn_nr'] = arxiv_identifier_w_vn_nr + + messages.success(request, 'Thank you for your Report') + return redirect(reverse('scipost:personal_page')) - else: - form = ReportForm() context = {'submission': submission, 'form': form} return render(request, 'submissions/submit_report.html', context) @@ -1114,33 +1122,31 @@ def vet_submitted_reports(request): @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def vet_submitted_report_ack(request, report_id): - if request.method == 'POST': - form = VetReportForm(request.POST) - report = Report.objects.get(pk=report_id) - if form.is_valid(): - report.vetted_by = request.user.contributor - if form.cleaned_data['action_option'] == '1': - # accept the report as is - report.status = 1 - report.save() - report.submission.latest_activity = timezone.now() - report.submission.save() - elif form.cleaned_data['action_option'] == '2': - # the report is simply rejected - report.status = int(form.cleaned_data['refusal_reason']) - report.save() - # email report author - SubmissionUtils.load({'report': report, - 'email_response': form.cleaned_data['email_response_field']}) - SubmissionUtils.acknowledge_report_email() # email report author, bcc EIC - if report.status == 1: - SubmissionUtils.send_author_report_received_email() - context = {'ack_header': 'Submitted Report vetted.', - 'followup_message': 'Return to the ', - 'followup_link': reverse('submissions:editorial_page', - kwargs={'arxiv_identifier_w_vn_nr': report.submission.arxiv_identifier_w_vn_nr}), - 'followup_link_label': 'Submission\'s Editorial Page'} - return render(request, 'scipost/acknowledgement.html', context) + report = get_object_or_404(Report, pk=report_id, + submission__editor_in_charge=request.user.contributor) + form = VetReportForm(request.POST or None) + if form.is_valid(): + report.vetted_by = request.user.contributor + if form.cleaned_data['action_option'] == '1': + # accept the report as is + report.status = 1 + report.save() + report.submission.latest_activity = timezone.now() + report.submission.save() + elif form.cleaned_data['action_option'] == '2': + # the report is simply rejected + report.status = int(form.cleaned_data['refusal_reason']) + report.save() + # email report author + SubmissionUtils.load({'report': report, + 'email_response': form.cleaned_data['email_response_field']}) + SubmissionUtils.acknowledge_report_email() # email report author, bcc EIC + if report.status == 1: + SubmissionUtils.send_author_report_received_email() + messages.success(request, 'Submitted Report vetted.') + return redirect(reverse('submissions:editorial_page', + args=[report.submission.arxiv_identifier_w_vn_nr])) + return redirect(reverse('submissions:vet_submitted_reports')) @permission_required('scipost.can_prepare_recommendations_for_voting', raise_exception=True) @@ -1202,31 +1208,30 @@ def prepare_for_voting(request, rec_id): @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) @transaction.atomic def vote_on_rec(request, rec_id): - if request.method == 'POST': - recommendation = get_object_or_404((EICRecommendation.objects - .get_for_user_in_pool(request.user)), id=rec_id) - form = RecommendationVoteForm(request.POST) - if form.is_valid(): - if form.cleaned_data['vote'] == 'agree': - recommendation.voted_for.add(request.user.contributor) - recommendation.voted_against.remove(request.user.contributor) - recommendation.voted_abstain.remove(request.user.contributor) - elif form.cleaned_data['vote'] == 'disagree': - recommendation.voted_for.remove(request.user.contributor) - recommendation.voted_against.add(request.user.contributor) - recommendation.voted_abstain.remove(request.user.contributor) - elif form.cleaned_data['vote'] == 'abstain': - recommendation.voted_for.remove(request.user.contributor) - recommendation.voted_against.remove(request.user.contributor) - recommendation.voted_abstain.add(request.user.contributor) - if form.cleaned_data['remark']: - remark = Remark(contributor=request.user.contributor, - recommendation=recommendation, - date=timezone.now(), - remark=form.cleaned_data['remark']) - remark.save() - recommendation.save() - return redirect(reverse('submissions:pool')) + recommendation = get_object_or_404((EICRecommendation.objects + .get_for_user_in_pool(request.user)), id=rec_id) + form = RecommendationVoteForm(request.POST or None) + if form.is_valid(): + if form.cleaned_data['vote'] == 'agree': + recommendation.voted_for.add(request.user.contributor) + recommendation.voted_against.remove(request.user.contributor) + recommendation.voted_abstain.remove(request.user.contributor) + elif form.cleaned_data['vote'] == 'disagree': + recommendation.voted_for.remove(request.user.contributor) + recommendation.voted_against.add(request.user.contributor) + recommendation.voted_abstain.remove(request.user.contributor) + elif form.cleaned_data['vote'] == 'abstain': + recommendation.voted_for.remove(request.user.contributor) + recommendation.voted_against.remove(request.user.contributor) + recommendation.voted_abstain.add(request.user.contributor) + if form.cleaned_data['remark']: + remark = Remark(contributor=request.user.contributor, + recommendation=recommendation, + date=timezone.now(), + remark=form.cleaned_data['remark']) + remark.save() + recommendation.save() + return redirect(reverse('submissions:pool')) return redirect(reverse('submissions:pool')) @@ -1272,14 +1277,8 @@ def fix_College_decision(request, rec_id): """ recommendation = get_object_or_404((EICRecommendation.objects .get_for_user_in_pool(request.user)), pk=rec_id) - if recommendation.recommendation == 1: - # Publish as Tier I (top 10%) - recommendation.submission.status = 'accepted' - elif recommendation.recommendation == 2: - # Publish as Tier II (top 50%) - recommendation.submission.status = 'accepted' - elif recommendation.recommendation == 3: - # Publish as Tier III (meets criteria) + if recommendation.recommendation in [1, 2, 3]: + # Publish as Tier I, II or III recommendation.submission.status = 'accepted' elif recommendation.recommendation == -3: # Reject diff --git a/templates/email/_footer.html b/templates/email/_footer.html new file mode 100644 index 0000000000000000000000000000000000000000..8dbca35955bcdbb9777d99b3f11f28b6b52ea7ed --- /dev/null +++ b/templates/email/_footer.html @@ -0,0 +1,21 @@ +{% load staticfiles %} + +<a href="https://scipost.org"><img src="//scipost.org{% static 'scipost/images/logo_scipost_with_bgd_small.png' %}" width="64px"></a> +<br/> +<div style="background-color: #f0f0f0; color: #002B49; align-items: center;"> + <div style="display: inline-block; padding: 8px;"> + <a href="{{request.get_host}}{% url 'journals:journals' %}">Journals</a> + </div> + <div style="display: inline-block; padding: 8px;">' + <a href="{{request.get_host}}{% url 'submissions:submissions' %}">Submissions</a> + </div> + <div style="display: inline-block; padding: 8px;"> + <a href="{{request.get_host}}{% url 'commentaries:commentaries' %}">Commentaries</a> + </div> + <div style="display: inline-block; padding: 8px;"> + <a href="{{request.get_host}}{% url 'theses:theses' %}">Theses</a> + </div> + <div style="display: inline-block; padding: 8px;"> + <a href="{{request.get_host}}{% url 'scipost:login' %}">Login</a> + </div> +</div> diff --git a/templates/email/new_activation_link.html b/templates/email/new_activation_link.html new file mode 100644 index 0000000000000000000000000000000000000000..755aaddd9f0aae366ba9884b84bbc11624b4746f --- /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 + +{{request.get_host}}{% 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..48a6e019acecf05d6122fb79e8ddf6ce99525fae --- /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="{{request.get_host}}{% 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/referee_in_response_to_decision.html b/templates/email/referee_in_response_to_decision.html new file mode 100644 index 0000000000000000000000000000000000000000..563f0956c294831980e22356293ffa4d6432d5f2 --- /dev/null +++ b/templates/email/referee_in_response_to_decision.html @@ -0,0 +1,12 @@ +Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}},\n\n + +We hereby confirm your choice to {% if invitation.accepted %}accept{% else %}decline (due to reason: {{invitation.get_refusal_reason_display}}){% endif %} to referee Submission\n\n + +{{invitation.submission.title}} by {{invitation.submission.author_list}}\n\n + +{% if invitation.accepted %} +Many thanks for your collaboration,\n +{% else %} +Nonetheless, we thank you very much for considering this refereeing invitation,\n +{% endif %} +The SciPost Team. diff --git a/templates/email/referee_in_response_to_decision_html.html b/templates/email/referee_in_response_to_decision_html.html new file mode 100644 index 0000000000000000000000000000000000000000..c62074285a39e77a183e5909a0defe6affdade7d --- /dev/null +++ b/templates/email/referee_in_response_to_decision_html.html @@ -0,0 +1,19 @@ +<h3>Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}},</h3> + +<p> + We hereby confirm your choice to {% if invitation.accepted %}accept{% else %}decline (due to reason: {{invitation.get_refusal_reason_display}}){% endif %} to referee Submission +</p> +<p> + "<span style="color: grey;">{{invitation.submission.title}} by {{invitation.submission.author_list}}</span>". +</p> + +<p> + {% if invitation.accepted %} + Many thanks for your collaboration, + {% else %} + Nonetheless, we thank you very much for considering this refereeing invitation, + {% endif %} + The SciPost Team. +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/referee_response_to_EIC.html b/templates/email/referee_response_to_EIC.html new file mode 100644 index 0000000000000000000000000000000000000000..c3e7b18856e90284714941a74d3a529b134c5c5b --- /dev/null +++ b/templates/email/referee_response_to_EIC.html @@ -0,0 +1,12 @@ +Dear {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}},\n\n + +Referee {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}} has {% if invitation.accepted %}accepted{% else %}declined (due to reason: {{invitation.get_refusal_reason_display}}){% endif %} to referee Submission\n\n + +{{invitation.submission.title}} by {{invitation.submission.author_list}}\n\n + +{% if not invitation.accepted %} +Please invite another referee from the Submission\'s editorial page at {{request.get_host}}{% url 'submissions:editorial_page' invitation.submission.arxiv_identifier_w_vn_nr %}.\n\n +{% endif %} + +Many thanks for your collaboration,\n +The SciPost Team. diff --git a/templates/email/referee_response_to_EIC_html.html b/templates/email/referee_response_to_EIC_html.html new file mode 100644 index 0000000000000000000000000000000000000000..8a8ea2958a74a03a1419863f1d3c56733c7a6a8b --- /dev/null +++ b/templates/email/referee_response_to_EIC_html.html @@ -0,0 +1,21 @@ +<h3>Dear {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}},</h3> + +<p> + Referee {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}} has {% if invitation.accepted %}accepted{% else %}declined (due to reason: {{invitation.get_refusal_reason_display}}){% endif %} to referee Submission +</p> +<p> + "<span style="color: grey;">{{invitation.submission.title}} by {{invitation.submission.author_list}}</span>". +</p> + +{% if not invitation.accepted %} + <p> + Please invite another referee from the Submission's <a href="{{request.get_host}}{% url 'submissions:editorial_page' invitation.submission.arxiv_identifier_w_vn_nr %}">editorial page</a>. + </p> +{% endif %} + +<p> + Many thanks for your collaboration, + 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..47d9124523bc8588a5b54536a7661618ebef5029 --- /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 + +{{request.get_host}}{% 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..c9eb3ff312a5e7e7f9525017f935cb9c4845ed51 --- /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="{{request.get_host}}{% 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/report_delivered_eic.html b/templates/email/report_delivered_eic.html new file mode 100644 index 0000000000000000000000000000000000000000..4d326dd5d8105f4b24be2dca147eac53a36b1b5a --- /dev/null +++ b/templates/email/report_delivered_eic.html @@ -0,0 +1,11 @@ +Dear {{report.submission.editor_in_charge.get_title_display}} {{report.submission.editor_in_charge.user.last_name}},\n\n + +Referee {{report.author.get_title_display}} {{report.author.user.last_name}} has delivered a Report for Submission "{{report.submission.title}} by {{report.submission.author_list}}". +\n\n + +Please vet this Report via your personal page under the Editorial Actions tab.\n +{{request.get_host}}{% url 'scipost:personal_page' %} +\n\n\n + +Many thanks in advance for your collaboration,\n +The SciPost Team. diff --git a/templates/email/report_delivered_eic_html.html b/templates/email/report_delivered_eic_html.html new file mode 100644 index 0000000000000000000000000000000000000000..0a94697c09c2a8c7d68da68728a0b22b3b2111d2 --- /dev/null +++ b/templates/email/report_delivered_eic_html.html @@ -0,0 +1,14 @@ +<h3>Dear {{report.submission.editor_in_charge.get_title_display}} {{report.submission.editor_in_charge.user.last_name}},</h3> + +<p> + Referee {{report.author.get_title_display}} {{report.author.user.last_name}} has delivered a Report for Submission "<span style="color: grey;">{{report.submission.title}} by {{report.submission.author_list}}</span>". +</p> +<p> + Please vet this Report via your <a href="{{request.get_host}}{% url 'scipost:personal_page' %}">personal page</a> under the Editorial Actions tab. +</p> +<p> + Many thanks in advance for your collaboration, + The SciPost Team. +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/submission_cycle_reinvite_referee.html b/templates/email/submission_cycle_reinvite_referee.html new file mode 100644 index 0000000000000000000000000000000000000000..3ae8ab59c207d451b19c0b3ad4e0da54de4ebd63 --- /dev/null +++ b/templates/email/submission_cycle_reinvite_referee.html @@ -0,0 +1,15 @@ +Dear {{invitation.get_title_display}} {{invitation.last_name}},\n\n\n + +The authors of submission\n\n + +{{invitation.submission.title}} by {{invitation.submission.author_list}} \n\n + +have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.last_name}}, we would like to invite you to quickly review this new version.\n +Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days).\n\n + +If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report).\n\n + +You might want to make sure you are familiar with our refereeing code of conduct and with the refereeing procedure.\n\n + +We would be extremely grateful for your contribution, and thank you in advance for your consideration.\n +The SciPost Team. diff --git a/templates/email/submission_cycle_reinvite_referee_html.html b/templates/email/submission_cycle_reinvite_referee_html.html new file mode 100644 index 0000000000000000000000000000000000000000..ec0eef3e764fb7e23b78723b275554efa664370b --- /dev/null +++ b/templates/email/submission_cycle_reinvite_referee_html.html @@ -0,0 +1,28 @@ +<h3>Dear {{invitation.get_title_display}} {{invitation.last_name}},</h3> + +<p> + The authors of submission +</p> + +<p> + {{invitation.submission.title}} + <br> + by {{invitation.submission.author_list}} + <br> + (<a href="{{request.get_host}}{{invitation.submission.get_absolute_url}}">see on SciPost.org</a>) +<p> + have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.last_name}}, we would like to invite you to quickly review this new version. + Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days). +</p> +<p> + If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report). +</p> +<p> + You might want to make sure you are familiar with our refereeing code of conduct and with the refereeing procedure. +</p> +<p> + We would be extremely grateful for your contribution, and thank you in advance for your consideration.<br> + The SciPost Team. +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/submission_eic_reappointment.html b/templates/email/submission_eic_reappointment.html new file mode 100644 index 0000000000000000000000000000000000000000..f5429e97b1fd9f0b19d1e566875bda11ae5b9540 --- /dev/null +++ b/templates/email/submission_eic_reappointment.html @@ -0,0 +1,17 @@ +Dear {{submission.editor_in_charge.get_title_display}} {{submission.editor_in_charge.user.last_name}},\n\n + +The authors of the SciPost Submission\n\n +{{submission.title}} +\n\n +by {{submission.author_list}} +\n\n +have resubmitted their manuscript.\n\n + +As Editor-in-charge, you can take your editorial actions from the submission\'s editorial page: {{request.get_host}}{% url 'submissions:editorial_page' submission.arxiv_identifier_w_vn_nr %}.\n +(also accessible from your personal page under the Editorial Actions tab), see {{request.get_host}}{% url 'scipost:personal_page' %}. \n\n + +You can either take an immediate acceptance/rejection decision, quickly consult previous referees or run a new refereeing round, in which case you should now invite at least 3 referees; you might want to make sure you are aware of the detailed procedure described in the Editorial College by-laws. See {{request.get_host}}{% url 'scipost:EdCol_by-laws' %}. +\n\n + +Many thanks in advance for your collaboration,\n +The SciPost Team. diff --git a/templates/email/submission_eic_reappointment_html.html b/templates/email/submission_eic_reappointment_html.html new file mode 100644 index 0000000000000000000000000000000000000000..41b1ff0ce9ad308359476785e9c7dc94c6c1d30c --- /dev/null +++ b/templates/email/submission_eic_reappointment_html.html @@ -0,0 +1,18 @@ +<h3>Dear {{submission.editor_in_charge.get_title_display}} {{submission.editor_in_charge.user.last_name}},</h3> + +<p> + The authors of the SciPost Submission "<span style="color: grey;">{{submission.title}} by {{submission.author_list}}</span>" have resubmitted their manuscript. +</p> +<p> + As Editor-in-charge, you can take your editorial actions from the submission\'s <a href="{{request.get_host}}{% url 'submissions:editorial_page' submission.arxiv_identifier_w_vn_nr %}">editorial page</a>. + (also accessible from your <a href="{{request.get_host}}{% url 'scipost:personal_page' %}">personal page</a> under the Editorial Actions tab). +</p> +<p> + You can either take an immediate acceptance/rejection decision, quickly consult previous referees or run a new refereeing round, in which case you should now invite at least 3 referees; you might want to make sure you are aware of the detailed procedure described in the <a href="{{request.get_host}}{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a>. +</p> +<p> + Many thanks in advance for your collaboration, + The SciPost Team. +</p> + +{% include 'email/_footer.html' %}