diff --git a/commentaries/managers.py b/commentaries/managers.py index 545a1740ffa0c8efca402352431551ad28c6fd18..9a83ae43472c96d61ff16a29b1f459c3e81218fa 100644 --- a/commentaries/managers.py +++ b/commentaries/managers.py @@ -7,3 +7,6 @@ class CommentaryManager(models.Manager): def awaiting_vetting(self, **kwargs): return self.filter(vetted=False, **kwargs) + + def open_for_commenting(self): + return self.filter(open_for_commenting=True) diff --git a/commentaries/migrations/0015_auto_20170726_1615.py b/commentaries/migrations/0015_auto_20170726_1615.py new file mode 100644 index 0000000000000000000000000000000000000000..34a805eeb7f6c3e07632f800f8214f7a3cb2a764 --- /dev/null +++ b/commentaries/migrations/0015_auto_20170726_1615.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 14:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def do_nothing(apps, schema_editor): + return + + +def fill_empty_strings_for_nulls(apps, schema_editor): + Commentary = apps.get_model('commentaries', 'Commentary') + Commentary.objects.filter(arxiv_identifier__isnull=True).update(arxiv_identifier='') + Commentary.objects.filter(arxiv_or_DOI_string__isnull=True).update(arxiv_or_DOI_string='') + Commentary.objects.filter(journal__isnull=True).update(journal='') + Commentary.objects.filter(pages__isnull=True).update(pages='') + Commentary.objects.filter(pub_DOI__isnull=True).update(pub_DOI='') + Commentary.objects.filter(volume__isnull=True).update(volume='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('commentaries', '0014_auto_20170201_1243'), + ] + + operations = [ + migrations.AlterField( + model_name='commentary', + name='arxiv_identifier', + field=models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='arXiv identifier (including version nr)'), + ), + migrations.AlterField( + model_name='commentary', + name='arxiv_or_DOI_string', + field=models.CharField(default='', max_length=100, null=True, verbose_name='string form of arxiv nr or DOI for commentary url'), + ), + migrations.AlterField( + model_name='commentary', + name='journal', + field=models.CharField(blank=True, default='', max_length=300, null=True), + ), + migrations.AlterField( + model_name='commentary', + name='pages', + field=models.CharField(blank=True, default='', max_length=50, null=True), + ), + migrations.AlterField( + model_name='commentary', + name='pub_DOI', + field=models.CharField(blank=True, default='', max_length=200, null=True, verbose_name='DOI of the original publication'), + ), + migrations.AlterField( + model_name='commentary', + name='volume', + field=models.CharField(blank=True, default='', max_length=50, null=True), + ), + migrations.RunPython(fill_empty_strings_for_nulls, do_nothing), + ] diff --git a/commentaries/migrations/0016_auto_20170726_1616.py b/commentaries/migrations/0016_auto_20170726_1616.py new file mode 100644 index 0000000000000000000000000000000000000000..63e1a1ea5a56f469c28a29d391c19122e76cb056 --- /dev/null +++ b/commentaries/migrations/0016_auto_20170726_1616.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 14:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('commentaries', '0015_auto_20170726_1615'), + ] + + operations = [ + migrations.AlterField( + model_name='commentary', + name='arxiv_identifier', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='arXiv identifier (including version nr)'), + preserve_default=False, + ), + migrations.AlterField( + model_name='commentary', + name='arxiv_or_DOI_string', + field=models.CharField(default='', max_length=100, verbose_name='string form of arxiv nr or DOI for commentary url'), + preserve_default=False, + ), + migrations.AlterField( + model_name='commentary', + name='journal', + field=models.CharField(blank=True, default='', max_length=300), + preserve_default=False, + ), + migrations.AlterField( + model_name='commentary', + name='pages', + field=models.CharField(blank=True, default='', max_length=50), + preserve_default=False, + ), + migrations.AlterField( + model_name='commentary', + name='pub_DOI', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='DOI of the original publication'), + preserve_default=False, + ), + migrations.AlterField( + model_name='commentary', + name='volume', + field=models.CharField(blank=True, default='', max_length=50), + preserve_default=False, + ), + ] diff --git a/commentaries/models.py b/commentaries/models.py index 27db6d732b4144dfb15a8ca6c86997a6ac04b7b1..a1c41fdafe2c0f6b3e88388371ed69c39c18e81b 100644 --- a/commentaries/models.py +++ b/commentaries/models.py @@ -25,43 +25,36 @@ class Commentary(TimeStampedModel): discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default=DISCIPLINE_PHYSICS) domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS) - subject_area = models.CharField( - max_length=10, choices=SCIPOST_SUBJECT_AREAS, - default='Phys:QP') + subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, + default='Phys:QP') open_for_commenting = models.BooleanField(default=True) pub_title = models.CharField(max_length=300, verbose_name='title') - arxiv_identifier = models.CharField( - max_length=100, verbose_name="arXiv identifier (including version nr)", - blank=True, null=True) + arxiv_identifier = models.CharField(max_length=100, blank=True, + verbose_name="arXiv identifier (including version nr)") arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)', blank=True) - pub_DOI = models.CharField( - max_length=200, verbose_name='DOI of the original publication', - blank=True, null=True) + pub_DOI = models.CharField(max_length=200, verbose_name='DOI of the original publication', + blank=True) pub_DOI_link = models.URLField( verbose_name='DOI link to the original publication', blank=True) metadata = JSONField(default={}, blank=True, null=True) - arxiv_or_DOI_string = models.CharField( - max_length=100, default='', - verbose_name='string form of arxiv nr or DOI for commentary url') + arxiv_or_DOI_string = models.CharField(max_length=100, + verbose_name='string form of arxiv nr or' + ' DOI for commentary url') author_list = models.CharField(max_length=1000) # Authors which have been mapped to contributors: - authors = models.ManyToManyField( - Contributor, blank=True, - related_name='authors_com') - authors_claims = models.ManyToManyField( - Contributor, blank=True, - related_name='authors_com_claims') - authors_false_claims = models.ManyToManyField( - Contributor, blank=True, - related_name='authors_com_false_claims') - journal = models.CharField(max_length=300, blank=True, null=True) - volume = models.CharField(max_length=50, blank=True, null=True) - pages = models.CharField(max_length=50, blank=True, null=True) - pub_date = models.DateField( - verbose_name='date of original publication', - blank=True, null=True) + authors = models.ManyToManyField('scipost.Contributor', blank=True, + related_name='authors_com') + authors_claims = models.ManyToManyField('scipost.Contributor', blank=True, + related_name='authors_com_claims') + authors_false_claims = models.ManyToManyField('scipost.Contributor', blank=True, + related_name='authors_com_false_claims') + journal = models.CharField(max_length=300, blank=True) + volume = models.CharField(max_length=50, blank=True) + pages = models.CharField(max_length=50, blank=True) + pub_date = models.DateField(verbose_name='date of original publication', + blank=True, null=True) pub_abstract = models.TextField(verbose_name='abstract') objects = CommentaryManager() @@ -72,6 +65,9 @@ class Commentary(TimeStampedModel): def __str__(self): return self.pub_title + def get_absolute_url(self): + return reverse('commentaries:commentary', args=(self.arxiv_or_DOI_string,)) + def title_label(self): context = Context({ 'scipost_url': reverse('commentaries:commentary', args=(self.arxiv_or_DOI_string,)), diff --git a/commentaries/views.py b/commentaries/views.py index 3dc87784707e61820a4f7afc2749d5ddebccb52c..5bf4f0ef87745667375a2fa15a4c79d2dc4ac544 100644 --- a/commentaries/views.py +++ b/commentaries/views.py @@ -17,7 +17,6 @@ from .forms import DOIToQueryForm, ArxivQueryForm, VetCommentaryForm, RequestCom from comments.models import Comment from comments.forms import CommentForm -from scipost.models import Contributor import strings diff --git a/comments/admin.py b/comments/admin.py index 6cfa6fa189234c235205ff884230843cb796e43a..25c767aa4691c951af14d3c43bedf9ee032ec059 100644 --- a/comments/admin.py +++ b/comments/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin +from guardian.admin import GuardedModelAdmin + from .constants import STATUS_VETTED from .models import Comment @@ -13,7 +15,7 @@ def comment_is_vetted(comment): return comment.status is STATUS_VETTED -class CommentAdmin(admin.ModelAdmin): +class CommentAdmin(GuardedModelAdmin): list_display = (comment_opening, 'author', 'date_submitted', comment_is_vetted) date_hierarchy = 'date_submitted' list_filter = ('status',) diff --git a/comments/managers.py b/comments/managers.py index 823b4c0d79a732c224bc62bec58d1f70d1f416fb..37f5baef8c803436d56818518ac4549b85514756 100644 --- a/comments/managers.py +++ b/comments/managers.py @@ -3,7 +3,7 @@ from django.db import models from .constants import STATUS_PENDING -class CommentManager(models.Manager): +class CommentQuerySet(models.QuerySet): def vetted(self): return self.filter(status__gte=1) diff --git a/comments/migrations/0013_auto_20170726_1538.py b/comments/migrations/0013_auto_20170726_1538.py new file mode 100644 index 0000000000000000000000000000000000000000..81672bccbd46993e8e2c62a2b1956c3c934424a0 --- /dev/null +++ b/comments/migrations/0013_auto_20170726_1538.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 13:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0012_auto_20170415_1659'), + ] + + operations = [ + migrations.AlterModelOptions( + name='comment', + options={'permissions': (('can_vet_comments', 'Can vet submitted Comments'),)}, + ), + migrations.AlterField( + model_name='comment', + name='date_submitted', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date submitted'), + ), + migrations.AlterField( + model_name='comment', + name='remarks_for_editors', + field=models.TextField(blank=True, verbose_name='optional remarks for the Editors only'), + ), + ] diff --git a/comments/migrations/0014_auto_20170726_2117.py b/comments/migrations/0014_auto_20170726_2117.py new file mode 100644 index 0000000000000000000000000000000000000000..aabeaf9f993d9f4e0935df4837070193f732bb29 --- /dev/null +++ b/comments/migrations/0014_auto_20170726_2117.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 19:17 +from __future__ import unicode_literals + +from django.db import migrations + +from guardian.shortcuts import assign_perm + +from ..constants import STATUS_PENDING + + +def do_nothing(apps, schema_editor): + return + + +def update_eic_permissions(apps, schema_editor): + """ + Grant EIC of submission related to unvetted comment + permission to vet his submission's comment. + """ + Comment = apps.get_model('comments', 'Comment') + count = 0 + for comment in Comment.objects.filter(status=STATUS_PENDING): + if comment.submission: + eic_user = comment.submission.editor_in_charge.user + assign_perm('comments.can_vet_comments', eic_user, comment) + count += 1 + print('\nGranted permission to %i Editor(s)-in-charge to vet related Comments.' % count) + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0013_auto_20170726_1538'), + ] + + operations = [ + migrations.RunPython(update_eic_permissions, do_nothing), + ] diff --git a/comments/models.py b/comments/models.py index fd52703d222870e5694815c6b39acb632abfbea9..c1c25c9a1e3338297b74c70ecf6e3de5f1cc13d7 100644 --- a/comments/models.py +++ b/comments/models.py @@ -1,12 +1,13 @@ from django.db import models from django.shortcuts import get_object_or_404 +from django.utils import timezone from scipost.behaviors import TimeStampedModel from scipost.models import Contributor from .behaviors import validate_file_extension, validate_max_file_size from .constants import COMMENT_STATUS, STATUS_PENDING -from .managers import CommentManager +from .managers import CommentQuerySet class Comment(TimeStampedModel): @@ -16,10 +17,9 @@ class Comment(TimeStampedModel): status = models.SmallIntegerField(default=STATUS_PENDING, choices=COMMENT_STATUS) 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] - ) + 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('commentaries.Commentary', blank=True, null=True, on_delete=models.CASCADE) @@ -35,6 +35,7 @@ class Comment(TimeStampedModel): 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') is_rem = models.BooleanField(default=False, verbose_name='remark') @@ -46,22 +47,26 @@ class Comment(TimeStampedModel): is_lit = models.BooleanField(default=False, verbose_name='pointer to related literature') is_sug = models.BooleanField(default=False, verbose_name='suggestion for further work') comment_text = models.TextField() - remarks_for_editors = models.TextField(default='', blank=True, + remarks_for_editors = models.TextField(blank=True, verbose_name='optional remarks for the Editors only') - date_submitted = models.DateTimeField('date submitted') + date_submitted = models.DateTimeField('date submitted', default=timezone.now) # Opinions nr_A = models.PositiveIntegerField(default=0) - in_agreement = models.ManyToManyField(Contributor, related_name='in_agreement', blank=True) + in_agreement = models.ManyToManyField('scipost.Contributor', related_name='in_agreement', + blank=True) nr_N = models.PositiveIntegerField(default=0) - in_notsure = models.ManyToManyField(Contributor, related_name='in_notsure', blank=True) + in_notsure = models.ManyToManyField('scipost.Contributor', related_name='in_notsure', + blank=True) nr_D = models.PositiveIntegerField(default=0) - in_disagreement = models.ManyToManyField( - Contributor, - related_name='in_disagreement', - blank=True - ) + in_disagreement = models.ManyToManyField('scipost.Contributor', related_name='in_disagreement', + blank=True) + + objects = CommentQuerySet.as_manager() - objects = CommentManager() + class Meta: + permissions = ( + ('can_vet_comments', 'Can vet submitted Comments'), + ) def __str__(self): return ('by ' + self.author.user.first_name + ' ' + self.author.user.last_name + diff --git a/comments/templates/comments/_vet_comment_form.html b/comments/templates/comments/_vet_comment_form.html new file mode 100644 index 0000000000000000000000000000000000000000..dd01802345af1267abc90e834d7e65bbab50a9a9 --- /dev/null +++ b/comments/templates/comments/_vet_comment_form.html @@ -0,0 +1,62 @@ +{% load bootstrap %} +{% load filename %} +{% load file_extentions %} + +<div class="card card-vetting"> + <div class="card-header"> + + {% if comment.commentary %} + <h2>From Commentary (<a href="{{comment.commentary.get_absolute_url}}">link</a>)</h2> + {% include 'commentaries/_commentary_summary.html' with commentary=comment.commentary %} + {% endif %} + + {% if comment.submission %} + <h2>From Submission (<a href="{{comment.submission.get_absolute_url}}">link</a>)</h2> + {% include 'submissions/_submission_summary_short.html' with submission=comment.submission %} + {% endif %} + + {% if comment.thesislink %} + <h2>From Thesis Link (<a href="{{comment.thesislink.get_absolute_url}}">link</a>)</h2> + {% include 'theses/_thesislink_information.html' with thesislink=comment.thesislink %} + {% endif %} + </div> + <div class="card-block"> + + <h2 class="card-title">The Comment to be vetted:</h2> + + <div class="row"> + <div class="col-md-6"> + {% include 'comments/_comment_identifier_vetting.html' with comment=comment %} + <hr class="small"> + + <h3>Comment text:</h3> + <p>{{ comment.comment_text }}</p> + + {% if comment.file_attachment %} + <h3>Attachment:</h3> + <p> + <a target="_blank" href="{{ comment.file_attachment.url }}"> + {% if comment.file_attachment|is_image %} + <img class="attachment attachment-comment" src="{{ comment.file_attachment.url }}"> + {% else %} + {{ comment.file_attachment|filename }}<br><small>{{ comment.file_attachment.size|filesizeformat }}</small> + {% endif %} + </a> + </p> + {% endif %} + + {% if comment.remarks_for_editors %} + <h3>Remarks for Editors only:</h3> + <p>{{ comment.remarks_for_editors }}</p> + {% endif %} + </div> + <div class="col-md-6"> + <form action="{% url 'comments:vet_submitted_comment' comment_id=comment.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Submit" /> + </form> + </div> + </div> + </div> +</div> diff --git a/comments/templates/comments/vet_submitted_comment.html b/comments/templates/comments/vet_submitted_comment.html new file mode 100644 index 0000000000000000000000000000000000000000..3cfe9f3b2b68106b590acc3c8773477a2301bb1b --- /dev/null +++ b/comments/templates/comments/vet_submitted_comment.html @@ -0,0 +1,19 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} +{% load filename %} +{% load file_extentions %} + +{% block pagetitle %}: vet comments{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">SciPost Comment to vet:</h1> + </div> +</div> + +{% include 'comments/_vet_comment_form.html' with comment=comment form=form %} + +{% endblock content %} diff --git a/comments/templates/comments/vet_submitted_comments.html b/comments/templates/comments/vet_submitted_comments.html deleted file mode 100644 index e3a71b702cb6ab815ad2af54cf9abf1147506ad5..0000000000000000000000000000000000000000 --- a/comments/templates/comments/vet_submitted_comments.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends 'scipost/base.html' %} - -{% load bootstrap %} -{% load filename %} -{% load file_extentions %} - -{% block pagetitle %}: vet comments{% endblock pagetitle %} - -{% block content %} - -{% if not comments_to_vet %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">There are no comments for you to vet.</h1> - </div> -</div> -{% else %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">SciPost Comments to vet:</h1> - </div> -</div> - -<div class="row"> - <div class="col-12"> - {% for comment_to_vet in comments_to_vet %} - <div class="card card-vetting"> - <div class="card-header"> - - {% if comment_to_vet.commentary %} - <h2>From Commentary (<a href="{% url 'commentaries:commentary' arxiv_or_DOI_string=comment_to_vet.commentary.arxiv_or_DOI_string %}">link</a>)</h2> - {% include 'commentaries/_commentary_summary.html' with commentary=comment_to_vet.commentary %} - {% endif %} - - {% if comment_to_vet.submission %} - <h2>From Submission (<a href="{% url 'submissions:submission' arxiv_identifier_w_vn_nr=comment_to_vet.submission.arxiv_identifier_w_vn_nr %}">link</a>)</h2> - {% include 'submissions/_submission_summary_short.html' with submission=comment_to_vet.submission %} - {% endif %} - - {% if comment_to_vet.thesislink %} - <h2>From Thesis Link (<a href="{% url 'theses:thesis' comment_to_vet.thesislink.id %}">link</a>)</h2> - {% include 'theses/_thesislink_information.html' with thesislink=comment_to_vet.thesislink %} - {% endif %} - </div> - <div class="card-block"> - - <h2 class="card-title">The Comment to be vetted:</h2> - - <div class="row"> - <div class="col-md-6"> - {% include 'comments/_comment_identifier_vetting.html' with comment=comment_to_vet %} - <hr class="small"> - - <h3>Comment text:</h3> - <p>{{ comment_to_vet.comment_text }}</p> - - {% if comment_to_vet.file_attachment %} - <h3>Attachment:</h3> - <p> - <a target="_blank" href="{{ comment_to_vet.file_attachment.url }}"> - {% if comment_to_vet.file_attachment|is_image %} - <img class="attachment attachment-comment" src="{{ comment_to_vet.file_attachment.url }}"> - {% else %} - {{ comment_to_vet.file_attachment|filename }}<br><small>{{ comment_to_vet.file_attachment.size|filesizeformat }}</small> - {% endif %} - </a> - </p> - {% endif %} - - {% if comment_to_vet.remarks_for_editors %} - <h3>Remarks for Editors only:</h3> - <p>{{ comment_to_vet.remarks_for_editors }}</p> - {% endif %} - </div> - <div class="col-md-6"> - <form action="{% url 'comments:vet_submitted_comment_ack' comment_id=comment_to_vet.id %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Submit" /> - </form> - </div> - </div> - </div> - </div> - {% endfor %} - </div> -</div> -{% endif %} - -{% endblock content %} diff --git a/comments/templates/comments/vet_submitted_comments_list.html b/comments/templates/comments/vet_submitted_comments_list.html new file mode 100644 index 0000000000000000000000000000000000000000..d58676554085260383dffe0c957a9578d7821357 --- /dev/null +++ b/comments/templates/comments/vet_submitted_comments_list.html @@ -0,0 +1,29 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: vet comments{% endblock pagetitle %} + +{% block content %} + +{% if not comments_to_vet %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">There are no comments for you to vet.</h1> + </div> +</div> +{% else %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">SciPost Comments to vet:</h1> + </div> +</div> + +<div class="row"> + <div class="col-12"> + {% for comment_to_vet in comments_to_vet %} + {% include 'comments/_vet_comment_form.html' with comment=comment_to_vet form=form %} + {% endfor %} + </div> +</div> +{% endif %} + +{% endblock content %} diff --git a/comments/urls.py b/comments/urls.py index 3d1747ee28e087ff4a52d0da5cd0a794032fc935..213fbd1ad0d6a5bcfaa152aaf878469adc2ed086 100644 --- a/comments/urls.py +++ b/comments/urls.py @@ -4,11 +4,15 @@ from . import views urlpatterns = [ # Comments - url(r'^reply_to_comment/(?P<comment_id>[0-9]+)$', views.reply_to_comment, name='reply_to_comment'), - url(r'^reply_to_report/(?P<report_id>[0-9]+)$', views.reply_to_report, name='reply_to_report'), - url(r'^vet_submitted_comments$', views.vet_submitted_comments, name='vet_submitted_comments'), - url(r'^vet_submitted_comment_ack/(?P<comment_id>[0-9]+)$', views.vet_submitted_comment_ack, name='vet_submitted_comment_ack'), - url(r'^express_opinion/(?P<comment_id>[0-9]+)$', views.express_opinion, name='express_opinion'), - url(r'^express_opinion/(?P<comment_id>[0-9]+)/(?P<opinion>[AND])$', views.express_opinion, name='express_opinion'), - url(r'^new_comment/(?P<type_of_object>[a-z]+)/(?P<object_id>[0-9]+)$', views.new_comment, name='new_comment') + url(r'^reports/(?P<report_id>[0-9]+)/reply$', views.reply_to_report, name='reply_to_report'), + url(r'^vet_submitted$', views.vet_submitted_comments_list, name='vet_submitted_comments_list'), + url(r'^new/(?P<type_of_object>[a-z]+)/(?P<object_id>[0-9]+)$', views.new_comment, + name='new_comment'), + url(r'^(?P<comment_id>[0-9]+)/reply$', views.reply_to_comment, name='reply_to_comment'), + url(r'^(?P<comment_id>[0-9]+)/vet$', views.vet_submitted_comment, + name='vet_submitted_comment'), + url(r'^(?P<comment_id>[0-9]+)/express_opinion$', views.express_opinion, + name='express_opinion'), + url(r'^(?P<comment_id>[0-9]+)/express_opinion/(?P<opinion>[AND])$', views.express_opinion, + name='express_opinion'), ] diff --git a/comments/utils.py b/comments/utils.py index be461a1e031d8b08a64c784f055b11460e07b0ab..9fad691fe5fa29525817682fb275218d35171b35 100644 --- a/comments/utils.py +++ b/comments/utils.py @@ -1,7 +1,39 @@ import os +from common.utils import BaseMailUtil + def validate_file_extention(value, allowed_extentions): '''Check if a filefield (value) has allowed extentions.''' ext = os.path.splitext(value.name)[1] # [0] returns path+filename return ext.lower() in allowed_extentions + + +class CommentUtils(BaseMailUtil): + mail_sender = 'comments@scipost.org' + mail_sender_title = 'The SciPost Team' + + @classmethod + def email_comment_vet_accepted_to_author(cls): + """ + Send mail after Comment is vetted: `Accept` + + Requires loading: + comment -- Comment + """ + cls._send_mail(cls, 'comment_vet_accepted', + [cls._context['comment'].author.user.email], + 'SciPost Comment published') + + @classmethod + def email_comment_vet_rejected_to_author(cls, email_response=''): + """ + Send mail after Comment is vetted: `Reject` + + Requires loading: + comment -- Comment + """ + cls._send_mail(cls, 'comment_vet_rejected', + [cls._context['comment'].author.user.email], + 'SciPost Comment rejected', + extra_context={'email_response': email_response}) diff --git a/comments/views.py b/comments/views.py index af5577b3316f4f7d4508ebf52bf1a2e244d9a760..662841c025a96addd05519fea0a79eb128d99452 100644 --- a/comments/views.py +++ b/comments/views.py @@ -1,18 +1,18 @@ from django.utils import timezone from django.shortcuts import get_object_or_404, render, redirect -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import permission_required, login_required from django.contrib import messages -from django.core.mail import EmailMessage from django.core.urlresolvers import reverse -from django.core.exceptions import PermissionDenied -from django.http import HttpResponseRedirect, Http404 +from django.http import HttpResponseRedirect +from django.db import transaction +from guardian.shortcuts import assign_perm, get_objects_for_user import strings from .models import Comment from .forms import CommentForm, VetCommentForm +from .utils import CommentUtils -from scipost.models import Contributor from theses.models import ThesisLink from submissions.utils import SubmissionUtils from submissions.models import Submission, Report @@ -21,227 +21,147 @@ from commentaries.models import Commentary @permission_required('scipost.can_submit_comments', raise_exception=True) def new_comment(request, **kwargs): - if request.method == "POST": - form = CommentForm(request.POST) - if form.is_valid(): - author = Contributor.objects.get(user=request.user) - object_id = int(kwargs["object_id"]) - type_of_object = kwargs["type_of_object"] - new_comment = Comment( - author=author, - is_rem=form.cleaned_data['is_rem'], - is_que=form.cleaned_data['is_que'], - is_ans=form.cleaned_data['is_ans'], - is_obj=form.cleaned_data['is_obj'], - is_rep=form.cleaned_data['is_rep'], - is_val=form.cleaned_data['is_val'], - is_lit=form.cleaned_data['is_lit'], - is_sug=form.cleaned_data['is_sug'], - file_attachment=form.cleaned_data['file_attachment'], - comment_text=form.cleaned_data['comment_text'], - remarks_for_editors=form.cleaned_data['remarks_for_editors'], - date_submitted=timezone.now(), - ) - if type_of_object == "thesislink": - thesislink = ThesisLink.objects.get(id=object_id) - if not thesislink.open_for_commenting: - raise PermissionDenied - new_comment.thesislink = thesislink - redirect_link = reverse('theses:thesis', kwargs={"thesislink_id": thesislink.id}) - elif type_of_object == "submission": - submission = Submission.objects.get(id=object_id) - if not submission.open_for_commenting: - raise PermissionDenied - new_comment.submission = submission - redirect_link = reverse( - 'submissions:submission', - kwargs={"arxiv_identifier_w_vn_nr": submission.arxiv_identifier_w_vn_nr} - ) - elif type_of_object == "commentary": - commentary = Commentary.objects.get(id=object_id) - if not commentary.open_for_commenting: - raise PermissionDenied - new_comment.commentary = commentary - redirect_link = reverse( - 'commentaries:commentary', - kwargs={'arxiv_or_DOI_string': commentary.arxiv_or_DOI_string} - ) + form = CommentForm(request.POST or None) + if form.is_valid(): + object_id = int(kwargs["object_id"]) + type_of_object = kwargs["type_of_object"] + new_comment = form.save(commit=False) + new_comment.author = request.user.contributor + + if type_of_object == "thesislink": + _object = get_object_or_404(ThesisLink.objects.open_for_commenting(), id=object_id) + new_comment.thesislink = _object new_comment.save() - author.nr_comments = Comment.objects.filter(author=author).count() - author.save() - messages.add_message( - request, messages.SUCCESS, strings.acknowledge_submit_comment) - return redirect(redirect_link) - else: - # This view is only accessible by POST request - raise Http404 + elif type_of_object == "submission": + _object = get_object_or_404(Submission.objects.open_for_commenting(), id=object_id) + new_comment.submission = _object + new_comment.save() + _object.add_event_for_eic('A new comment has been added.') + # Add permissions for EIC only, the Vetting-group already has it! + assign_perm('comments.can_vet_comments', _object.editor_in_charge.user, new_comment) + elif type_of_object == "commentary": + _object = get_object_or_404(Commentary.objects.open_for_commenting(), id=object_id) + new_comment.commentary = _object + new_comment.save() -@permission_required('scipost.can_vet_comments', raise_exception=True) -def vet_submitted_comments(request): - contributor = Contributor.objects.get(user=request.user) - comments_to_vet = Comment.objects.filter(status=0).order_by('date_submitted') - form = VetCommentForm() - context = {'contributor': contributor, 'comments_to_vet': comments_to_vet, 'form': form} - return(render(request, 'comments/vet_submitted_comments.html', context)) + messages.success(request, strings.acknowledge_submit_comment) + return redirect(_object.get_absolute_url()) @permission_required('scipost.can_vet_comments', raise_exception=True) -def vet_submitted_comment_ack(request, comment_id): - if request.method == 'POST': - form = VetCommentForm(request.POST) - comment = Comment.objects.get(pk=comment_id) - if form.is_valid(): - if form.cleaned_data['action_option'] == '1': - # accept the comment as is - comment.status = 1 - comment.vetted_by = request.user.contributor - comment.save() - email_text = ('Dear ' + comment.author.get_title_display() + ' ' - + comment.author.user.last_name + - ', \n\nThe Comment you have submitted, ' - 'concerning publication with title ') - if comment.commentary is not None: - email_text += (comment.commentary.pub_title + ' by ' - + comment.commentary.author_list - + ' at Commentary Page https://scipost.org/commentary/' - + comment.commentary.arxiv_or_DOI_string) - comment.commentary.latest_activity = timezone.now() - comment.commentary.save() - elif comment.submission is not None: - email_text += (comment.submission.title + ' by ' - + comment.submission.author_list - + ' at Submission page https://scipost.org/submission/' - + comment.submission.arxiv_identifier_w_vn_nr) - comment.submission.latest_activity = timezone.now() - comment.submission.save() - if not comment.is_author_reply: - SubmissionUtils.load({'submission': comment.submission}) - SubmissionUtils.send_author_comment_received_email() - elif comment.thesislink is not None: - email_text += (comment.thesislink.title + ' by ' + comment.thesislink.author + - ' at Thesis Link https://scipost.org/thesis/' - + str(comment.thesislink.id)) - comment.thesislink.latest_activity = timezone.now() - comment.thesislink.save() - email_text += (', has been accepted and published online.' + - '\n\nWe copy it below for your convenience.' + - '\n\nThank you for your contribution, \nThe SciPost Team.' + - '\n\n' + comment.comment_text) - emailmessage = EmailMessage('SciPost Comment published', email_text, - 'comments@scipost.org', - [comment.author.user.email], - ['comments@scipost.org'], - reply_to=['comments@scipost.org']) - emailmessage.send(fail_silently=False) - elif form.cleaned_data['action_option'] == '2': - # the comment request is simply rejected - comment.status = int(form.cleaned_data['refusal_reason']) - if comment.status == 0: - comment.status == -1 - comment.save() - email_text = ('Dear ' + comment.author.get_title_display() + ' ' - + comment.author.user.last_name - + ', \n\nThe Comment you have submitted, ' - 'concerning publication with title ') - if comment.commentary is not None: - email_text += comment.commentary.pub_title + ' by ' +\ - comment.commentary.author_list - elif comment.submission is not None: - email_text += comment.submission.title + ' by ' +\ - comment.submission.author_list - elif comment.thesislink is not None: - email_text += comment.thesislink.title + ' by ' + comment.thesislink.author - email_text += (', has been rejected for the following reason: ' - + comment.get_status_display() + '.' + - '\n\nWe copy it below for your convenience.' + - '\n\nThank you for your contribution, \n\nThe SciPost Team.') - if form.cleaned_data['email_response_field']: - email_text += '\n\nFurther explanations: ' +\ - form.cleaned_data['email_response_field'] - email_text += '\n\n' + comment.comment_text - emailmessage = EmailMessage('SciPost Comment rejected', email_text, - 'comments@scipost.org', - [comment.author.user.email], - ['comments@scipost.org'], - reply_to=['comments@scipost.org']) - emailmessage.send(fail_silently=False) - - # context = {} - # return render(request, 'comments/vet_submitted_comment_ack.html', context) - context = {'ack_header': 'Submitted Comment vetted.', - 'followup_message': 'Back to ', - 'followup_link': reverse('comments:vet_submitted_comments'), - 'followup_link_label': 'submitted Comments page'} - return render(request, 'scipost/acknowledgement.html', context) +def vet_submitted_comments_list(request): + comments_to_vet = Comment.objects.awaiting_vetting().order_by('date_submitted') + form = VetCommentForm() + context = {'comments_to_vet': comments_to_vet, 'form': form} + return(render(request, 'comments/vet_submitted_comments_list.html', context)) + + +@login_required +@transaction.atomic +def vet_submitted_comment(request, comment_id): + # Method `get_objects_for_user` gets all Comments that are assigned to the user + # or *all* comments if user has the `scipost.can_vet_comments` permission. + comment = get_object_or_404((get_objects_for_user(request.user, 'comments.can_vet_comments') + .awaiting_vetting()), + id=comment_id) + form = VetCommentForm(request.POST or None) + if form.is_valid(): + if form.cleaned_data['action_option'] == '1': + # Accept the comment as is + comment.status = 1 + comment.vetted_by = request.user.contributor + comment.save() + + # Send emails + CommentUtils.load({'comment': comment}) + CommentUtils.email_comment_vet_accepted_to_author() + + # Update `latest_activity` fields + if comment.commentary: + comment.commentary.latest_activity = timezone.now() + comment.commentary.save() + elif comment.submission: + comment.submission.latest_activity = timezone.now() + comment.submission.save() + + # Add events to Submission and send mail to author of the Submission + comment.submission.add_event_for_eic('A Comment has been accepted.') + comment.submission.add_event_for_author('A new Comment has been added.') + if not comment.is_author_reply: + SubmissionUtils.load({'submission': comment.submission}) + SubmissionUtils.send_author_comment_received_email() + elif comment.thesislink: + comment.thesislink.latest_activity = timezone.now() + comment.thesislink.save() + elif form.cleaned_data['action_option'] == '2': + # The comment request is simply rejected + comment.status = int(form.cleaned_data['refusal_reason']) + if comment.status == 0: + comment.status = -1 + comment.save() + + # Send emails + CommentUtils.load({'comment': comment}) + CommentUtils.email_comment_vet_rejected_to_author( + email_response=form.cleaned_data['email_response_field']) + + if comment.submission: + comment.submission.add_event_for_eic('A Comment has been rejected.') + + messages.success(request, 'Submitted Comment vetted.') + if comment.submission and comment.submission.editor_in_charge == request.user.contributor: + # Redirect a EIC back to the Editorial Page! + return redirect(reverse('submissions:editorial_page', + args=(comment.submission.arxiv_identifier_w_vn_nr,))) + elif request.user.has_perm('scipost.can_vet_comments'): + # Redirect vetters back to check for other unvetted comments! + return redirect(reverse('comments:vet_submitted_comments_list')) + return redirect(comment.get_absolute_url()) + + context = { + 'comment': comment, + 'form': form + } + return(render(request, 'comments/vet_submitted_comment.html', context)) @permission_required('scipost.can_submit_comments', raise_exception=True) def reply_to_comment(request, comment_id): comment = get_object_or_404(Comment, pk=comment_id) + # Verify if this is from an author: is_author = False - if comment.submission is not None: - if comment.submission.authors.filter(id=request.user.contributor.id).exists(): - is_author = True - elif comment.commentary is not None: - if comment.commentary.authors.filter(id=request.user.contributor.id).exists(): - is_author = True - elif comment.thesislink is not None: - if comment.thesislink.author == request.user.contributor: - is_author = True - - if request.method == 'POST': - form = CommentForm(request.POST, request.FILES) - if form.is_valid(): - newcomment = Comment( - commentary=comment.commentary, # one of commentary, submission or thesislink will be not Null - submission=comment.submission, - thesislink=comment.thesislink, - is_author_reply=is_author, - in_reply_to_comment=comment, - author=Contributor.objects.get(user=request.user), - is_rem=form.cleaned_data['is_rem'], - is_que=form.cleaned_data['is_que'], - is_ans=form.cleaned_data['is_ans'], - is_obj=form.cleaned_data['is_obj'], - is_rep=form.cleaned_data['is_rep'], - is_cor=form.cleaned_data['is_cor'], - is_val=form.cleaned_data['is_val'], - is_lit=form.cleaned_data['is_lit'], - is_sug=form.cleaned_data['is_sug'], - file_attachment=form.cleaned_data['file_attachment'], - comment_text=form.cleaned_data['comment_text'], - remarks_for_editors=form.cleaned_data['remarks_for_editors'], - date_submitted=timezone.now(), - ) - newcomment.save() - - context = {'ack_header': 'Thank you for contributing a Reply.', - 'ack_message': 'It will soon be vetted by an Editor.', - 'followup_message': 'Back to the ', } - if newcomment.submission is not None: - context['followup_link'] = reverse( - 'submissions:submission', - kwargs={ - 'arxiv_identifier_w_vn_nr': newcomment.submission.arxiv_identifier_w_vn_nr - } - ) - context['followup_link_label'] = ' Submission page you came from' - elif newcomment.commentary is not None: - context['followup_link'] = reverse( - 'commentaries:commentary', - kwargs={'arxiv_or_DOI_string': newcomment.commentary.arxiv_or_DOI_string}) - context['followup_link_label'] = ' Commentary page you came from' - elif newcomment.thesislink is not None: - context['followup_link'] = reverse( - 'theses:thesis', - kwargs={'thesislink_id': newcomment.thesislink.id}) - context['followup_link_label'] = ' Thesis Link page you came from' - return render(request, 'scipost/acknowledgement.html', context) - else: - form = CommentForm() + if comment.submission and not is_author: + is_author = comment.submission.authors.filter(id=request.user.contributor.id).exists() + elif comment.commentary and not is_author: + is_author = comment.commentary.authors.filter(id=request.user.contributor.id).exists() + elif comment.thesislink and not is_author: + is_author = comment.thesislink.author == request.user.contributor + + form = CommentForm(request.POST or None, request.FILES or None) + if form.is_valid(): + newcomment = form.save(commit=False) + # Either one of commentary, submission or thesislink will be not Null + newcomment.commentary = comment.commentary + newcomment.submission = comment.submission + newcomment.thesislink = comment.thesislink + newcomment.is_author_reply = is_author + newcomment.in_reply_to_comment = comment + newcomment.author = request.user.contributor + newcomment.save() + + messages.success(request, '<h3>Thank you for contributing a Reply</h3>' + 'It will soon be vetted by an Editor.') + + if newcomment.submission: + return redirect(newcomment.submission.get_absolute_url()) + elif newcomment.commentary: + return redirect(newcomment.commentary.get_absolute_url()) + elif newcomment.thesislink: + return redirect(newcomment.thesislink.get_absolute_url()) + return redirect(reverse('scipost:index')) context = {'comment': comment, 'is_author': is_author, 'form': form} return render(request, 'comments/reply_to_comment.html', context) @@ -250,49 +170,23 @@ def reply_to_comment(request, comment_id): @permission_required('scipost.can_submit_comments', raise_exception=True) def reply_to_report(request, report_id): report = get_object_or_404(Report, pk=report_id) + # Verify if this is from an author: - is_author = False - if report.submission.authors.filter(id=request.user.contributor.id).exists(): - is_author = True - if is_author and request.method == 'POST': - form = CommentForm(request.POST, request.FILES) - if form.is_valid(): - newcomment = Comment( - submission=report.submission, - is_author_reply=is_author, - in_reply_to_report=report, - author=Contributor.objects.get(user=request.user), - is_rem=form.cleaned_data['is_rem'], - is_que=form.cleaned_data['is_que'], - is_ans=form.cleaned_data['is_ans'], - is_obj=form.cleaned_data['is_obj'], - is_rep=form.cleaned_data['is_rep'], - is_cor=form.cleaned_data['is_cor'], - is_val=form.cleaned_data['is_val'], - is_lit=form.cleaned_data['is_lit'], - is_sug=form.cleaned_data['is_sug'], - file_attachment=form.cleaned_data['file_attachment'], - comment_text=form.cleaned_data['comment_text'], - remarks_for_editors=form.cleaned_data['remarks_for_editors'], - date_submitted=timezone.now(), - ) - newcomment.save() - # return HttpResponseRedirect(reverse('comments:comment_submission_ack')) - context = { - 'ack_header': 'Thank you for contributing a Reply.', - 'ack_message': 'It will soon be vetted by an Editor.', - 'followup_message': 'Back to the ', - 'followup_link': reverse( - 'submissions:submission', - kwargs={ - 'arxiv_identifier_w_vn_nr': newcomment.submission.arxiv_identifier_w_vn_nr - } - ), - 'followup_link_label': ' Submission page you came from' - } - return render(request, 'scipost/acknowledgement.html', context) - else: - form = CommentForm() + is_author = report.submission.authors.filter(user=request.user).exists() + + form = CommentForm(request.POST or None, request.FILES or None) + if form.is_valid(): + newcomment = form.save(commit=False) + newcomment.submission = report.submission + newcomment.is_author_reply = is_author + newcomment.in_reply_to_report = report + newcomment.author = request.user.contributor + newcomment.save() + + messages.success(request, '<h3>Thank you for contributing a Reply</h3>' + 'It will soon be vetted by an Editor.') + return redirect(newcomment.submission.get_absolute_url()) + context = {'report': report, 'is_author': is_author, 'form': form} return render(request, 'comments/reply_to_report.html', context) diff --git a/models.py b/models.py new file mode 100644 index 0000000000000000000000000000000000000000..578b6c71dd5d2f603bab5c7534eb3c3c17473120 --- /dev/null +++ b/models.py @@ -0,0 +1,60 @@ +from django.db import models +from django.core.urlresolvers import reverse + +from staff.behaviors import WhiteLabelClientMixin, TimeStampedMixin + +from .managers import LocationManager + + +class Location(WhiteLabelClientMixin): + """ + Physical location to be related to WCLs. + """ + code = models.CharField(max_length=64) + client = models.ForeignKey('clients.Client', related_name='locations', blank=True, null=True) + subtitle = models.CharField(max_length=128, blank=True) + address = models.CharField(max_length=512) + postal_code = models.CharField(max_length=512, blank=True) + main_phone = models.CharField(max_length=32, blank=True) + city = models.CharField(max_length=512, blank=True) + description = models.TextField(blank=True) + + objects = LocationManager() + + class Meta: + unique_together = ('white_label_client', 'code',) + ordering = ('-code',) + + def __str__(self): + return '%s, %s' % (self.address, self.city) + + def get_absolute_url(self): + return reverse('locations:detailview', args=(self.code,)) + + def get_edit_url(self): + return reverse('locations:editview', args=(self.code,)) + + +class GeoLocation(TimeStampedMixin): + """ + Geocode which links `Location` objects to the 2D map. + """ + location = models.OneToOneField('locations.Location') + latitude = models.CharField(max_length=64) + longitude = models.CharField(max_length=64) + + +class LocationObject(TimeStampedMixin): + """ + An physical object can be assigned to a `Location` object. + """ + location = models.ForeignKey('locations.Location', related_name='location_objects') + code = models.CharField(max_length=64, blank=True) + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + + def __str__(self): + _str = self.name + if self.code: + _str += ' (%s)' % self.code + return _str diff --git a/scipost/migrations/0060_auto_20170726_1612.py b/scipost/migrations/0060_auto_20170726_1612.py new file mode 100644 index 0000000000000000000000000000000000000000..5faee7244032b1e0960daea7615c4adc1512b9d0 --- /dev/null +++ b/scipost/migrations/0060_auto_20170726_1612.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 14:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0059_auto_20170701_1356'), + ] + + operations = [ + migrations.AlterField( + model_name='contributor', + name='address', + field=models.CharField(blank=True, max_length=1000, verbose_name='address'), + ), + ] diff --git a/scipost/models.py b/scipost/models.py index a59ea8df52301965ee597ab3f795dff1048dfe02..2c32f440b4a64de2ab4369b647e419cf5cdd0743 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -53,7 +53,7 @@ class Contributor(models.Model): country_of_employment = CountryField() affiliation = models.CharField(max_length=300, verbose_name='affiliation') address = models.CharField(max_length=1000, verbose_name="address", - default='', blank=True) + blank=True) personalwebpage = models.URLField(verbose_name='personal web page', blank=True) vetted_by = models.ForeignKey('self', on_delete=models.SET(get_sentinel_user), diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 76a4053791473192d3b0d39252ec5c38e691293c..54c211a0ded22fc4951bf4c11f611e28cc562e4f 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -250,7 +250,7 @@ <li><a href="{% url 'commentaries:vet_commentary_requests' %}">Vet Commentary Page requests</a> ({{ nr_commentary_page_requests_to_vet }})</li> {% endif %} {% if perms.scipost.can_vet_comments %} - <li><a href="{% url 'comments:vet_submitted_comments' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> + <li><a href="{% url 'comments:vet_submitted_comments_list' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> {% endif %} {% if perms.scipost.can_vet_thesislink_requests %} <li><a href="{% url 'theses:unvetted_thesislinks' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li> diff --git a/submissions/managers.py b/submissions/managers.py index cba54e4f69b167a34a4a550b2b3a34339991fc49..43c1902c7c538f1cf93f7f462f22370831e9adbe 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -99,6 +99,9 @@ class SubmissionManager(models.Manager): def accepted(self): return self.filter(status=STATUS_ACCEPTED) + def open_for_commenting(self): + return self.filter(open_for_commenting=True) + class SubmissionEventQuerySet(models.QuerySet): def for_author(self): diff --git a/submissions/models.py b/submissions/models.py index 790619f7d3891d2ee7a4f93118c8f52bd5a30c2b..158ae9b3695dec0fa89b0306c1f1b085e79a4323 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -91,7 +91,7 @@ class Submission(models.Model): class Meta: permissions = ( ('can_take_editorial_actions', 'Can take editorial actions'), - ) + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/submissions/templates/submissions/editorial_page.html b/submissions/templates/submissions/editorial_page.html index 069d183b679e7f09def5b4bb8cf87e95253f314b..aee2c35959ff5b4e807262693949cc2563e84567 100644 --- a/submissions/templates/submissions/editorial_page.html +++ b/submissions/templates/submissions/editorial_page.html @@ -171,6 +171,20 @@ </form> </li> <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ submission.reports.awaiting_vetting.count }})</li> + {% with submission.comments.awaiting_vetting as comments %} + {% if comments %} + <li> + Vet submitted Comments: + <ul class="mb-1"> + {% for comment in comments %} + <li><a href="{% url 'comments:vet_submitted_comment' comment.id %}">{{comment}}</a></li> + {% endfor %} + </ul> + </li> + {% else %} + <li>All Comments have been vetted.</li> + {% endif %} + {% endwith %} {% 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 %} diff --git a/submissions/utils.py b/submissions/utils.py index 385dbfc3402e25ff730686360b4f49f6561e8428..8c79eb53ba19d360d087df6144b522977a7a35af 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -298,7 +298,6 @@ class SubmissionUtils(BaseMailUtil): 'Invitation on resubmission', extra_bcc=extra_bcc_list) - @classmethod def send_authors_submission_ack_email(cls): """ Requires loading 'submission' attribute. """ @@ -625,7 +624,6 @@ class SubmissionUtils(BaseMailUtil): emailmessage.attach_alternative(html_version, 'text/html') emailmessage.send(fail_silently=False) - @classmethod def send_refereeing_invitation_email(cls): """ @@ -819,7 +817,6 @@ class SubmissionUtils(BaseMailUtil): emailmessage.attach_alternative(html_version, 'text/html') emailmessage.send(fail_silently=False) - @classmethod def send_ref_reminder_email(cls): """ @@ -906,7 +903,6 @@ class SubmissionUtils(BaseMailUtil): emailmessage.attach_alternative(html_version, 'text/html') emailmessage.send(fail_silently=False) - @classmethod def send_ref_cancellation_email(cls): """ diff --git a/templates/email/comment_vet_accepted.html b/templates/email/comment_vet_accepted.html new file mode 100644 index 0000000000000000000000000000000000000000..de7c50065e5514e37fda15478c01178ffc68ce69 --- /dev/null +++ b/templates/email/comment_vet_accepted.html @@ -0,0 +1,29 @@ +<p>Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}},</p> + +<p> + The Comment you have submitted, concerning publication with title + + {% if comment.commentary %} + {{comment.commentary.pub_title}} by {{comment.commentary.author_list}} (<a href="https://scipost.org{{comment.commentary.get_absolute_url}}">see Commentary Page on SciPost.org</a>) + {% elif comment.submission %} + {{comment.submission.title}} by {{comment.submission.author_list}} (<a href="https://scipost.org{{comment.submission.get_absolute_url}}">see Submission Page on SciPost.org</a>) + {% elif comment.thesislink %} + {{comment.thesislink.title}} by {{comment.thesislink.author}} (<a href="https://scipost.org{{comment.thesislink.get_absolute_url}}">see Thesis Link on SciPost.org</a>) + {% endif %} + has been accepted and published online. +</p> +<p> + We copy it below for your convenience. +</p> +<p> + Thank you for your contribution,<br><br> + The SciPost Team. +</p> +<p> + <br> + Comment: + <br> + {{comment.comment_text|linebreaksbr}} +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/comment_vet_accepted.txt b/templates/email/comment_vet_accepted.txt new file mode 100644 index 0000000000000000000000000000000000000000..79b53c5b8f0629c745ede71599301a9fffdbc838 --- /dev/null +++ b/templates/email/comment_vet_accepted.txt @@ -0,0 +1,21 @@ +Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}} +\n\n + +The Comment you have submitted, concerning publication with title + +{% if comment.commentary %} + {{comment.commentary.pub_title}} by {{comment.commentary.author_list}} at Commentary Page https://scipost.org{{comment.commentary.get_absolute_url}} +{% elif comment.submission %} + {{comment.submission.title}} by {{comment.submission.author_list}} at Submission page https://scipost.org{{comment.submission.get_absolute_url}} +{% elif comment.thesislink %} + {{comment.thesislink.title}} by {{comment.thesislink.author}} at Thesis Link https://scipost.org{{comment.thesislink.get_absolute_url}} +{% endif %} +has been accepted and published online. +\n\nWe copy it below for your convenience. + +\n\nThank you for your contribution, +\nThe SciPost Team. + +'\n\n' +Comment:\n +{{comment.comment_text}} diff --git a/templates/email/comment_vet_rejected.html b/templates/email/comment_vet_rejected.html new file mode 100644 index 0000000000000000000000000000000000000000..e6a5dd598b162e45792a80fe151f31bcb95178d8 --- /dev/null +++ b/templates/email/comment_vet_rejected.html @@ -0,0 +1,34 @@ +<p>Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}},</p> + +<p> + The Comment you have submitted, concerning publication with title + + {% if comment.commentary %} + {{comment.commentary.pub_title}} by {{comment.commentary.author_list}} (<a href="https://scipost.org{{comment.commentary.get_absolute_url}}">see Commentary Page on SciPost.org</a>) + {% elif comment.submission %} + {{comment.submission.title}} by {{comment.submission.author_list}} (<a href="https://scipost.org{{comment.submission.get_absolute_url}}">see Submission Page on SciPost.org</a>) + {% elif comment.thesislink %} + {{comment.thesislink.title}} by {{comment.thesislink.author}} (<a href="https://scipost.org{{comment.thesislink.get_absolute_url}}">see Thesis Link on SciPost.org</a>) + {% endif %} + has been rejected for the following reason: {{comment.get_status_display}}. +</p> +<p> + We copy it below for your convenience. +</p> +<p> + Thank you for your contribution,<br><br> + The SciPost Team. +</p> + +{% if email_response %} + <p>Further explanations: {{email_response}}</p> +{% endif %} + +<p> + <br> + Comment: + <br> + {{comment.comment_text|linebreaksbr}} +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/comment_vet_rejected.txt b/templates/email/comment_vet_rejected.txt new file mode 100644 index 0000000000000000000000000000000000000000..2eed6e1bae03f8e9f8bb083073d4a544d042b2e6 --- /dev/null +++ b/templates/email/comment_vet_rejected.txt @@ -0,0 +1,28 @@ +Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}} +\n\n + +The Comment you have submitted, concerning publication with title + +{% if comment.commentary %} + {{comment.commentary.pub_title}} by {{comment.commentary.author_list}} at Commentary Page https://scipost.org{{comment.commentary.get_absolute_url}} +{% elif comment.submission %} + {{comment.submission.title}} by {{comment.submission.author_list}} at Submission page https://scipost.org{{comment.submission.get_absolute_url}} +{% elif comment.thesislink %} + {{comment.thesislink.title}} by {{comment.thesislink.author}} at Thesis Link https://scipost.org{{comment.thesislink.get_absolute_url}} +{% endif %} + +has been rejected for the following reason: {{comment.get_status_display}}. + +\n\nWe copy it below for your convenience. + +\n\nThank you for your contribution, + +\n\nThe SciPost Team. + +{% if email_response %} + \n\nFurther explanations: {{email_response}} +{% endif %} + +\n\n +Comment:\n +{{comment.comment_text}} diff --git a/theses/managers.py b/theses/managers.py index 68dc44574b8722927314cc063cbd22ef26c02a58..4d2b06986cce30276d4753244d0fb149061f08b6 100644 --- a/theses/managers.py +++ b/theses/managers.py @@ -15,3 +15,6 @@ class ThesisLinkManager(models.Manager): def vetted(self): return self.filter(vetted=True) + + def open_for_commenting(self): + return self.filter(open_for_commenting=True)