diff --git a/commentaries/factories.py b/commentaries/factories.py index 1f0110a5d0a44caa1ffb3519f8f86a3703a2871d..978352975fff10fa564883dbf0388a2e0ebbcda3 100644 --- a/commentaries/factories.py +++ b/commentaries/factories.py @@ -20,7 +20,7 @@ class CommentaryFactory(factory.django.DjangoModelFactory): discipline = factory.Iterator(SCIPOST_DISCIPLINES, getter=lambda c: c[0]) domain = factory.Iterator(SCIPOST_JOURNALS_DOMAINS, getter=lambda c: c[0]) subject_area = factory.Iterator(SCIPOST_SUBJECT_AREAS[0][1], getter=lambda c: c[0]) - pub_title = factory.Faker('text') + title = factory.Faker('text') pub_DOI = factory.Sequence(lambda n: random_external_doi()) arxiv_identifier = factory.Sequence(lambda n: random_arxiv_identifier_with_version_number()) author_list = factory.Faker('name') @@ -60,6 +60,7 @@ class UnpublishedVettedCommentaryFactory(VettedCommentaryFactory): class UnvettedCommentaryFactory(CommentaryFactory): vetted = False + class UnvettedArxivPreprintCommentaryFactory(CommentaryFactory): vetted = False - pub_DOI = None + pub_DOI = '' diff --git a/commentaries/forms.py b/commentaries/forms.py index 4e77b521a054eae82330c8b8e2f61a2099e01475..ce198737a14d3e2e06dc19a59946bbb4edb85d2c 100644 --- a/commentaries/forms.py +++ b/commentaries/forms.py @@ -90,7 +90,7 @@ class RequestCommentaryForm(forms.ModelForm): class Meta: model = Commentary fields = [ - 'discipline', 'domain', 'subject_area', 'pub_title', + 'discipline', 'domain', 'subject_area', 'title', 'author_list', 'pub_date', 'pub_abstract' ] placeholders = { @@ -272,7 +272,7 @@ class VetCommentaryForm(forms.Form): # Modified actions are not doing anything. Users are redirected to an edit page instead. if self.commentary_is_accepted(): self.commentary.vetted = True - self.commentary.vetted_by = Contributor.objects.get(user=self.user) + self.commentary.vetted_by = self.user.contributor self.commentary.save() return self.commentary elif self.commentary_is_refused(): @@ -289,6 +289,6 @@ class CommentarySearchForm(forms.Form): def search_results(self): """Return all Commentary objects according to search""" return Commentary.objects.vetted( - pub_title__icontains=self.cleaned_data['title'], + title__icontains=self.cleaned_data['title'], pub_abstract__icontains=self.cleaned_data['abstract'], author_list__icontains=self.cleaned_data['author']).order_by('-pub_date') 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/migrations/0017_auto_20170728_1901.py b/commentaries/migrations/0017_auto_20170728_1901.py new file mode 100644 index 0000000000000000000000000000000000000000..4965029020dcd247aa1d12d87794b5e96c09760a --- /dev/null +++ b/commentaries/migrations/0017_auto_20170728_1901.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-28 17:01 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('commentaries', '0016_auto_20170726_1616'), + ] + + operations = [ + migrations.RenameField( + model_name='commentary', + old_name='pub_title', + new_name='title', + ), + ] diff --git a/commentaries/models.py b/commentaries/models.py index 27db6d732b4144dfb15a8ca6c86997a6ac04b7b1..a6176b57643f971bd840014227ff795e9cfb53cd 100644 --- a/commentaries/models.py +++ b/commentaries/models.py @@ -1,11 +1,11 @@ from django.db import models +from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.fields import JSONField from django.core.urlresolvers import reverse from django.template import Template, Context from journals.constants import SCIPOST_JOURNALS_DOMAINS from scipost.behaviors import TimeStampedModel -from scipost.models import Contributor from scipost.constants import SCIPOST_DISCIPLINES, DISCIPLINE_PHYSICS, SCIPOST_SUBJECT_AREAS from .constants import COMMENTARY_TYPES @@ -25,59 +25,58 @@ 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) + title = models.CharField(max_length=300, verbose_name='title') + 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') + # Comments can be added to a Commentary + comments = GenericRelation('comments.Comment', related_query_name='commentaries') + objects = CommentaryManager() class Meta: verbose_name_plural = 'Commentaries' def __str__(self): - return self.pub_title + return self.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,)), - 'pub_title': self.pub_title + 'title': self.title }) - template = Template('<a href="{{scipost_url}}" class="pubtitleli">{{pub_title}}</a>') + template = Template('<a href="{{scipost_url}}" class="pubtitleli">{{title}}</a>') return template.render(context) def parse_links_into_urls(self, commit=False): diff --git a/commentaries/templates/commentaries/_commentary_card_content.html b/commentaries/templates/commentaries/_commentary_card_content.html index a00cd4ca86eda5aeae8d6dd1be4e027158de80b0..51a1a1a1b564cdf0340184576be620ef145512ea 100644 --- a/commentaries/templates/commentaries/_commentary_card_content.html +++ b/commentaries/templates/commentaries/_commentary_card_content.html @@ -1,6 +1,6 @@ <div class="card-block"> <h3 class="card-title"> - <a href="{% url 'commentaries:commentary' commentary.arxiv_or_DOI_string %}">{{ commentary.pub_title }}</a> + <a href="{% url 'commentaries:commentary' commentary.arxiv_or_DOI_string %}">{{ commentary.title }}</a> </h3> <p class="card-text"> by {{ commentary.author_list }}{% if commentary.type == 'published' %}, {{ commentary.journal }} {{ commentary.volume }}, {{ commentary.pages }}{% elif commentary.type == 'preprint' %} - <a href="{{ commentary.arxiv_link }}">{{ commentary.arxiv_link }}</a>{% endif %} diff --git a/commentaries/templates/commentaries/_commentary_summary.html b/commentaries/templates/commentaries/_commentary_summary.html index be9db8316e26049b3d925a5d52d3886e564b0ccc..914dd82eadc074a2b44a9ca2c396aa7837c2fbe3 100644 --- a/commentaries/templates/commentaries/_commentary_summary.html +++ b/commentaries/templates/commentaries/_commentary_summary.html @@ -1,7 +1,7 @@ <table class="commentary summary"> <tr> <td>Title:</td> - <td>{{commentary.pub_title}}</td> + <td>{{commentary.title}}</td> </tr> <tr> <td>Author(s):</td> diff --git a/commentaries/templates/commentaries/commentary_detail.html b/commentaries/templates/commentaries/commentary_detail.html index 5b47e90f8844ea97816973ff8de2271abd9a271c..ad8cee7adb14d5cf208ef086a3dc79d3065cfe17 100644 --- a/commentaries/templates/commentaries/commentary_detail.html +++ b/commentaries/templates/commentaries/commentary_detail.html @@ -6,25 +6,6 @@ {% load scipost_extras %} -<script> - $(document).ready(function(){ - - var comment_text_input = $("#id_comment_text"); - - function set_comment_text(value) { - $("#preview-comment_text").text(value) - } - set_comment_text(comment_text_input.val()); - - comment_text_input.keyup(function(){ - var new_text = $(this).val() - set_comment_text(new_text) - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - }); - - }); -</script> - {% endblock headsup %} {% block content %} @@ -49,7 +30,7 @@ </div> </div> -{% include 'scipost/comments_block.html' %} +{% include 'scipost/comments_block.html' with comments=commentary.comments.vetted type_of_object='Commentary' %} {% include 'comments/new_comment.html' with object_id=commentary.id type_of_object='commentary' open_for_commenting=commentary.open_for_commenting %} diff --git a/commentaries/templates/commentaries/vet_commentary_email_accepted.html b/commentaries/templates/commentaries/vet_commentary_email_accepted.html index 7c5056b8e24c8cdb402c3cfafb8801684f8e2a2f..cc92f551efbeea589b1df655eb6f1610cafda868 100644 --- a/commentaries/templates/commentaries/vet_commentary_email_accepted.html +++ b/commentaries/templates/commentaries/vet_commentary_email_accepted.html @@ -1,6 +1,6 @@ Dear {{commentary.requested_by.get_title}} {{commentary.requested_by.user.last_name}}, -The Commentary Page you have requested, concerning publication with title {{commentary.pub_title}} by {{commentary.author_list}}, has been activated at https://scipost.org/commentary/{{commentary.arxiv_or_DOI_string}}. +The Commentary Page you have requested, concerning publication with title {{commentary.title}} by {{commentary.author_list}}, has been activated at https://scipost.org/commentary/{{commentary.arxiv_or_DOI_string}}. You are now welcome to submit your comments. Thank you for your contribution, diff --git a/commentaries/templates/commentaries/vet_commentary_email_modified.html b/commentaries/templates/commentaries/vet_commentary_email_modified.html index f3ad85365c4ab2f4398dc9b9e644aeece54b7559..fa419f41fc6c3904cedfbe12892b16e16e05bd0d 100644 --- a/commentaries/templates/commentaries/vet_commentary_email_modified.html +++ b/commentaries/templates/commentaries/vet_commentary_email_modified.html @@ -1,6 +1,6 @@ Dear {{commentary.requested_by.get_title}} {{commentary.requested_by.user.last_name}}, -The Commentary Page you have requested, concerning publication with title {{commentary.pub_title}} by {{commentary.author_list}}, has been activated (with slight modifications to your submitted details). +The Commentary Page you have requested, concerning publication with title {{commentary.title}} by {{commentary.author_list}}, has been activated (with slight modifications to your submitted details). You are now welcome to submit your comments. Thank you for your contribution, diff --git a/commentaries/templates/commentaries/vet_commentary_email_rejected.html b/commentaries/templates/commentaries/vet_commentary_email_rejected.html index b9d99fade03b2a34cfcb59728285f97b7e677c61..0273fb35bd9b7ec36071a1e179698f9de15f01eb 100644 --- a/commentaries/templates/commentaries/vet_commentary_email_rejected.html +++ b/commentaries/templates/commentaries/vet_commentary_email_rejected.html @@ -1,6 +1,6 @@ Dear {{commentary.requested_by.get_title}} {{commentary.requested_by.user.last_name}}, -The Commentary Page you have requested, concerning publication with title {{commentary.pub_title}} by {{commentary.author_list}}, has not been activated for the following reason: {{refusal_reason}}. +The Commentary Page you have requested, concerning publication with title {{commentary.title}} by {{commentary.author_list}}, has not been activated for the following reason: {{refusal_reason}}. {% if further_explanation %} Further explanations: diff --git a/commentaries/test_forms.py b/commentaries/test_forms.py index 64d6fda33f5bb8bcb968d89dd17567becdbf6bef..35e65abb64b3b50ed5f3aca96a9a65dbefce65a5 100644 --- a/commentaries/test_forms.py +++ b/commentaries/test_forms.py @@ -101,14 +101,13 @@ class TestVetCommentaryForm(TestCase): add_groups_and_permissions() ContributorFactory.create_batch(5) self.commentary = UnvettedCommentaryFactory.create() - self.user = UserFactory() + self.user = UserFactory.create() self.form_data = { 'action_option': VetCommentaryForm.ACTION_ACCEPT, 'refusal_reason': VetCommentaryForm.REFUSAL_EMPTY, 'email_response_field': 'Lorem Ipsum' } - def test_valid_accepted_form(self): """Test valid form data and return Commentary""" form = VetCommentaryForm(self.form_data, commentary_id=self.commentary.id, user=self.user) @@ -127,7 +126,7 @@ class TestVetCommentaryForm(TestCase): form = VetCommentaryForm(self.form_data, commentary_id=self.commentary.id, user=self.user) self.assertTrue(form.is_valid()) self.assertFalse(Commentary.objects.vetted().exists()) - self.assertTrue(Commentary.objects.awaiting_vetting().exists()) + self.assertTrue(Commentary.objects.awaiting_vetting().count() == 1) # Delete the Commentary form.process_commentary() @@ -156,7 +155,7 @@ class TestVetCommentaryForm(TestCase): def test_process_before_validation(self): """Test response of form on processing before validation""" form = VetCommentaryForm(self.form_data, commentary_id=self.commentary.id, user=self.user) - self.assertRaises(ValueError, form.process_commentary) + self.assertRaises(AttributeError, form.process_commentary) class TestRequestPublishedArticleForm(TestCase): diff --git a/commentaries/views.py b/commentaries/views.py index 3dc87784707e61820a4f7afc2749d5ddebccb52c..d638ff79a70e9cdcc43b8d7acf2250ec48fefc3b 100644 --- a/commentaries/views.py +++ b/commentaries/views.py @@ -3,7 +3,6 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.core.mail import EmailMessage from django.core.urlresolvers import reverse, reverse_lazy -from django.db.models import Q from django.shortcuts import redirect from django.template.loader import render_to_string from django.views.generic.edit import CreateView @@ -17,7 +16,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 @@ -208,12 +206,7 @@ class CommentaryListView(ListView): context = super().get_context_data(**kwargs) # Get newest comments - context['comment_list'] = (Comment.objects.vetted() - .filter(Q(commentary__isnull=False) | - Q(submission__isnull=False) | - Q(thesislink__isnull=False)) - .select_related('author__user', 'submission', 'commentary') - .order_by('-date_submitted')[:10]) + context['comment_list'] = Comment.objects.vetted().order_by('-date_submitted')[:10] # Form into the context! context['form'] = self.form @@ -233,7 +226,6 @@ def commentary_detail(request, arxiv_or_DOI_string): commentary = get_object_or_404(Commentary.objects.vetted(), arxiv_or_DOI_string=arxiv_or_DOI_string) - comments = commentary.comment_set.all() form = CommentForm() try: author_replies = Comment.objects.filter( @@ -241,6 +233,5 @@ def commentary_detail(request, arxiv_or_DOI_string): except Comment.DoesNotExist: author_replies = () context = {'commentary': commentary, - 'comments': comments.filter(status__gte=1).order_by('-date_submitted'), 'author_replies': author_replies, 'form': form} return render(request, 'commentaries/commentary_detail.html', context) 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/factories.py b/comments/factories.py index d74370a136d4a140fd82e8f6d060acf6497526b4..1f76cf6a460001cb60077d1d5e73bdf075dd2c6a 100644 --- a/comments/factories.py +++ b/comments/factories.py @@ -31,16 +31,16 @@ class CommentFactory(factory.django.DjangoModelFactory): class CommentaryCommentFactory(CommentFactory): - commentary = factory.SubFactory(VettedCommentaryFactory) + content_object = factory.SubFactory(VettedCommentaryFactory) class SubmissionCommentFactory(CommentFactory): - submission = factory.SubFactory(EICassignedSubmissionFactory) + content_object = factory.SubFactory(EICassignedSubmissionFactory) class ThesislinkCommentFactory(CommentFactory): - thesislink = factory.SubFactory(VettedThesisLinkFactory) + content_object = factory.SubFactory(VettedThesisLinkFactory) class ReplyCommentFactory(CommentFactory): - in_reply_to_comment = factory.SubFactory(SubmissionCommentFactory) + content_object = factory.SubFactory(SubmissionCommentFactory) diff --git a/comments/forms.py b/comments/forms.py index 8aace94b0c36586c54abc63509f04588f093ff64..94b6fa62fefff353346ba27926df9aaa7f7e1ebb 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -3,9 +3,6 @@ from django import forms from .constants import COMMENT_ACTION_CHOICES, COMMENT_REFUSAL_CHOICES from .models import Comment -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Div, Field, Fieldset, HTML, Submit - class CommentForm(forms.ModelForm): class Meta: @@ -20,34 +17,6 @@ class CommentForm(forms.ModelForm): {'placeholder': 'NOTE: only serious and meaningful Comments will be accepted.'}) self.fields['remarks_for_editors'].widget.attrs.update( {'rows': 3, 'placeholder': '(these remarks will not be publicly visible)'}) - self.helper = FormHelper() - self.helper.layout = Layout( - Div( - Div( - Field('comment_text'), - HTML('<p>In your comment, you can use LaTeX \$...\$ for in-text ' - 'equations or \ [ ... \ ] for on-line equations.</p>'), - HTML('<p id="goodCommenter"><i>Be professional. Only serious and ' - 'meaningful comments will be vetted through.</i></p><br/>'), - Field('remarks_for_editors'), - css_class="col-md-9"), - Div( - Fieldset( - 'Specify categorization(s):', - 'is_cor', 'is_rem', 'is_que', 'is_ans', 'is_obj', - 'is_rep', 'is_val', 'is_lit', 'is_sug', - style="border: 0px; font-size: 90%"), - HTML('<br>'), - Div( - Submit('submit', 'Submit your Comment for vetting', - css_class="submitButton"), - HTML('<p id="goodCommenter"><i>By clicking on Submit, you agree with the ' - '<a href="{% url \'scipost:terms_and_conditions\' %}">' - 'Terms and Conditions</a>.</i></p>'), - ), - css_class="col-md-3"), - css_class="row"), - ) class VetCommentForm(forms.Form): diff --git a/comments/managers.py b/comments/managers.py index 823b4c0d79a732c224bc62bec58d1f70d1f416fb..70e6f20693fb1986569befa36a54852d8e130d1f 100644 --- a/comments/managers.py +++ b/comments/managers.py @@ -3,9 +3,15 @@ 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) def awaiting_vetting(self): return self.filter(status=STATUS_PENDING) + + def regular_comments(self): + return self.filter(is_author_reply=False) + + def author_replies(self): + return self.filter(is_author_reply=True) 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..93abe8af32d17736bc80b3162131d5ca14258454 --- /dev/null +++ b/comments/migrations/0014_auto_20170726_2117.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 19:17 +from __future__ import unicode_literals + +from django.contrib.auth import get_user_model +from django.db import migrations + +from guardian.shortcuts import assign_perm + + +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') + User = get_user_model() + + count = 0 + for comment in Comment.objects.filter(status=0): + if comment.submission: + # Grant Permissions + user = User.objects.get(id=comment.submission.editor_in_charge.user.id) + assign_perm('comments.can_vet_comments', 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/migrations/0015_auto_20170727_1248.py b/comments/migrations/0015_auto_20170727_1248.py new file mode 100644 index 0000000000000000000000000000000000000000..de0a61ca5a5096e1364b319d57e0af4434a5a8e8 --- /dev/null +++ b/comments/migrations/0015_auto_20170727_1248.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-27 10:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('comments', '0014_auto_20170726_2117'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='content_type', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + preserve_default=False, + ), + migrations.AddField( + model_name='comment', + name='object_id', + field=models.PositiveIntegerField(default=1), + preserve_default=False, + ), + migrations.AlterField( + model_name='comment', + name='submission', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments_old', to='submissions.Submission'), + ), + ] diff --git a/comments/migrations/0016_auto_20170728_1901.py b/comments/migrations/0016_auto_20170728_1901.py new file mode 100644 index 0000000000000000000000000000000000000000..d2e4e4a7236ea66e88104c31723f9511dda1cf04 --- /dev/null +++ b/comments/migrations/0016_auto_20170728_1901.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-28 17:01 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0015_auto_20170727_1248'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='in_reply_to_comment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='nested_comments_old', to='comments.Comment'), + ), + ] diff --git a/comments/migrations/0017_auto_20170729_0717.py b/comments/migrations/0017_auto_20170729_0717.py new file mode 100644 index 0000000000000000000000000000000000000000..db86be1347d9f1c75a3c61ef6d07bf5028ae94c9 --- /dev/null +++ b/comments/migrations/0017_auto_20170729_0717.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-29 05:17 +from __future__ import unicode_literals + +from django.contrib.auth import get_user_model +from django.contrib.contenttypes.models import ContentType +from django.db import migrations + +from guardian.shortcuts import assign_perm + + +def update_all_contenttypes(**kwargs): + from django.apps import apps + from django.contrib.contenttypes.management import update_contenttypes + + for app_config in apps.get_app_configs(): + update_contenttypes(app_config, **kwargs) + + +def create_all_permissions(**kwargs): + from django.contrib.auth.management import create_permissions + from django.apps import apps + + for app_config in apps.get_app_configs(): + create_permissions(app_config, **kwargs) + + +def forward(): + update_all_contenttypes() + create_all_permissions() + + +def migrate_comments_from_generic_relations(apps, schema_editor): + """ + Migrate all GenericRelations a Comment has to the oldschool ForeignKey relations. + """ + return + Comment = apps.get_model('comments', 'Comment') + Report = apps.get_model('submissions', 'Report') + Submission = apps.get_model('submissions', 'Submission') + Commentary = apps.get_model('commentaries', 'Commentary') + ThesisLink = apps.get_model('theses', 'ThesisLink') + + all_comments = Comment.objects.all() + for comment in all_comments: + _object = comment.content_object + if isinstance(_object, Comment): + comment.in_reply_to_comment = _object + + # Nested Comments have more relations + if isinstance(_object.content_object, ThesisLink): + comment.thesislink = _object.content_object + elif isinstance(_object.content_object, Submission): + comment.submission = _object.content_object + elif isinstance(_object.content_object, Commentary): + comment.commentary = _object.content_object + elif isinstance(_object, Report): + comment.in_reply_to_report = _object + comment.submission = _object.submission + elif isinstance(_object, ThesisLink): + comment.thesislink = _object + elif isinstance(_object, Submission): + comment.submission = _object + elif isinstance(_object, Commentary): + comment.commentary = _object + else: + raise AttributeError('Comment has no relation to another valid object.') + comment.save() + print('\nMigrated %i comments back to oldschool ForeignKey relations.' % len(all_comments)) + + +def migrate_comments_to_generic_relations(apps, schema_editor): + """ + Migrate all foreignkey relations a Comment has to the new GenericRelation. + """ + forward() + Comment = apps.get_model('comments', 'Comment') + User = get_user_model() + + all_comments = Comment.objects.all() + for comment in all_comments: + if comment.in_reply_to_comment: + _object = comment.in_reply_to_comment + _object_id = comment.in_reply_to_comment.id + _object_type = ContentType.objects.get(app_label="comments", model="comment").id + elif comment.in_reply_to_report: + _object = comment.in_reply_to_report + _object_id = comment.in_reply_to_report.id + _object_type = ContentType.objects.get(app_label="submissions", model="report").id + elif comment.thesislink: + _object = comment.thesislink + _object_id = comment.thesislink.id + _object_type = ContentType.objects.get(app_label="theses", model="thesislink").id + elif comment.submission: + _object = comment.submission + _object_id = comment.submission.id + _object_type = ContentType.objects.get(app_label="submissions", model="submission").id + elif comment.commentary: + _object = comment.commentary + _object_id = comment.commentary.id + _object_type = ContentType.objects.get(app_label="commentaries", model="commentary").id + else: + print('\nNo valid relation for Comment: ', comment.id,) + comment.content_object = _object + comment.content_type_id = _object_type + comment.object_id = _object_id + comment.save() + + # Grant Permissions + if comment.submission: + user = User.objects.get(id=comment.submission.editor_in_charge.user.id) + assign_perm('comments.can_vet_comments', user, comment) + + print('\nMigrated %i comments to GenericRelations.' % len(all_comments)) + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0016_auto_20170728_1901'), + ] + + operations = [ + migrations.RunPython(migrate_comments_to_generic_relations, + migrate_comments_from_generic_relations), + ] diff --git a/comments/migrations/0018_auto_20170729_1617.py b/comments/migrations/0018_auto_20170729_1617.py new file mode 100644 index 0000000000000000000000000000000000000000..c2d3cfed9b3b2ef1f12c2e3431ec846795050927 --- /dev/null +++ b/comments/migrations/0018_auto_20170729_1617.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-29 14:17 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0017_auto_20170729_0717'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='scipost.Contributor'), + ), + migrations.AlterField( + model_name='comment', + name='commentary', + field=models.ForeignKey(blank=True, help_text='Warning: This field is out of service and will be removed in the future.', null=True, on_delete=django.db.models.deletion.CASCADE, to='commentaries.Commentary'), + ), + migrations.AlterField( + model_name='comment', + name='content_type', + field=models.ForeignKey(help_text='Warning: Rather use/edit `content_object` instead or be 100% sure you know what you are doing!', on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + ), + migrations.AlterField( + model_name='comment', + name='in_reply_to_comment', + field=models.ForeignKey(blank=True, help_text='Warning: This field is out of service and will be removed in the future.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='nested_comments_old', to='comments.Comment'), + ), + migrations.AlterField( + model_name='comment', + name='in_reply_to_report', + field=models.ForeignKey(blank=True, help_text='Warning: This field is out of service and will be removed in the future.', null=True, on_delete=django.db.models.deletion.CASCADE, to='submissions.Report'), + ), + migrations.AlterField( + model_name='comment', + name='object_id', + field=models.PositiveIntegerField(help_text='Warning: Rather use/edit `content_object` instead or be 100% sure you know what you are doing!'), + ), + migrations.AlterField( + model_name='comment', + name='submission', + field=models.ForeignKey(blank=True, help_text='Warning: This field is out of service and will be removed in the future.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments_old', to='submissions.Submission'), + ), + migrations.AlterField( + model_name='comment', + name='thesislink', + field=models.ForeignKey(blank=True, help_text='Warning: This field is out of service and will be removed in the future.', null=True, on_delete=django.db.models.deletion.CASCADE, to='theses.ThesisLink'), + ), + ] diff --git a/comments/models.py b/comments/models.py index fd52703d222870e5694815c6b39acb632abfbea9..82ae4cf7bb8e43c962f9fdf99fd3ead00923e41d 100644 --- a/comments/models.py +++ b/comments/models.py @@ -1,12 +1,22 @@ +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.db import models from django.shortcuts import get_object_or_404 +from django.utils import timezone +from django.utils.functional import cached_property + +from guardian.shortcuts import assign_perm 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 + + +WARNING_TEXT = 'Warning: Rather use/edit `content_object` instead or be 100% sure you know what you are doing!' +US_NOTICE = 'Warning: This field is out of service and will be removed in the future.' class Comment(TimeStampedModel): @@ -16,25 +26,41 @@ 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] - ) - # a Comment is either for a Commentary or Submission or a ThesisLink. + file_attachment = models.FileField(upload_to='uploads/comments/%Y/%m/%d/', blank=True, + validators=[validate_file_extension, validate_max_file_size] + ) + + # A Comment is always related to another model + # This construction implicitly has property: `on_delete=models.CASCADE` + content_type = models.ForeignKey(ContentType, help_text=WARNING_TEXT) + object_id = models.PositiveIntegerField(help_text=WARNING_TEXT) + content_object = GenericForeignKey() + + nested_comments = GenericRelation('comments.Comment', related_query_name='comments') + + # -- U/S + # These fields will be removed in the future. + # They still exists only to prevent possible data loss. commentary = models.ForeignKey('commentaries.Commentary', blank=True, null=True, - on_delete=models.CASCADE) + on_delete=models.CASCADE, help_text=US_NOTICE) submission = models.ForeignKey('submissions.Submission', blank=True, null=True, - on_delete=models.CASCADE, related_name='comments') + on_delete=models.CASCADE, related_name='comments_old', + help_text=US_NOTICE) thesislink = models.ForeignKey('theses.ThesisLink', blank=True, null=True, - on_delete=models.CASCADE) - is_author_reply = models.BooleanField(default=False) + on_delete=models.CASCADE, help_text=US_NOTICE) in_reply_to_comment = models.ForeignKey('self', blank=True, null=True, - related_name="nested_comments", - on_delete=models.CASCADE) + related_name="nested_comments_old", + on_delete=models.CASCADE, help_text=US_NOTICE) in_reply_to_report = models.ForeignKey('submissions.Report', blank=True, null=True, - on_delete=models.CASCADE) - author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) + on_delete=models.CASCADE, help_text=US_NOTICE) + # -- End U/S + + # Author info + is_author_reply = models.BooleanField(default=False) + author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, + related_name='comments') 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,27 +72,74 @@ 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 + ' on ' + self.date_submitted.strftime('%Y-%m-%d') + ', ' + self.comment_text[:30]) + @property + def title(self): + """ + This property is (mainly) used to let Comments get the title of the Submission without + annoying logic. + """ + try: + return self.content_object.title + except: + return self.content_type + + @cached_property + def core_content_object(self): + # Import here due to circular import errors + from commentaries.models import Commentary + from submissions.models import Submission, Report + from theses.models import ThesisLink + + to_object = self.content_object + while True: + if (isinstance(to_object, Submission) or isinstance(to_object, Commentary) or + isinstance(to_object, ThesisLink)): + return to_object + elif isinstance(to_object, Report): + return to_object.submission + elif isinstance(to_object, Comment): + # Nested Comment. + to_object = to_object.content_object + else: + raise Exception + + def get_absolute_url(self): + return self.content_object.get_absolute_url().split('#')[0] + '#comment_id' + str(self.id) + + def grant_permissions(self): + # Import here due to circular import errors + from submissions.models import Submission + + to_object = self.core_content_object + if isinstance(to_object, Submission): + # Add permissions for EIC only, the Vetting-group already has it! + assign_perm('comments.can_vet_comments', to_object.editor_in_charge.user, self) + def get_author(self): '''Get author, if and only if comment is not anonymous!!!''' if not self.anonymous: diff --git a/comments/templates/comments/_add_comment_form.html b/comments/templates/comments/_add_comment_form.html index aa5a8bd0abe0f8b1d1b5c64899c88de47f3f9552..b3a51a16b34e172a2155dfb7ff6031e318a541e1 100644 --- a/comments/templates/comments/_add_comment_form.html +++ b/comments/templates/comments/_add_comment_form.html @@ -1,34 +1,62 @@ -<script> - $(document).ready(function(){ +{% load bootstrap %} - var comment_text_input = $("#id_comment_text"); +<script> + $(document).ready(function(){ + var comment_text_input = $("#id_comment_text"); - function set_comment_text(value) { - $("#preview-comment_text").text(value) - } - set_comment_text(comment_text_input.val()) + comment_text_input.on('keyup', function(){ + var new_text = $(this).val() + $("#preview-comment_text").text(new_text) + MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + }).trigger('keyup') + }); +</script> - comment_text_input.keyup(function(){ - var new_text = $(this).val() - set_comment_text(new_text) - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - }) +<form enctype="multipart/form-data" action="{{url}}" method="post"> + {% csrf_token %} + <div class="row"> + <div class="col-md-9"> + {{form.comment_text|bootstrap:'12,12'}} + <p> + In your comment, you can use LaTeX \$...\$ for in-text equations or \ [ ... \ ] for on-line equations. + </p> + <p id="goodCommenter"><em> + Be professional. Only serious and meaningful comments will be vetted through. + </em></p> + </div> + <div class="col-md-3 radio-list"> + <label>Specify categorization(s):</label> + {{form.is_cor|bootstrap}} + {{form.is_rem|bootstrap}} + {{form.is_que|bootstrap}} + {{form.is_ans|bootstrap}} + {{form.is_obj|bootstrap}} + {{form.is_rep|bootstrap}} + {{form.is_val|bootstrap}} + {{form.is_lit|bootstrap}} + {{form.is_sug|bootstrap}} + </div> + </div> - }); -</script> + <div class="row"> + <div class="col-md-9"> + {{form.remarks_for_editors|bootstrap:'12,12'}} + </div> + <div class="col-md-3"> + {{form.file_attachment|bootstrap:'12,12'}} + </div> + </div> -<div class="row"> - <div class="col-12"> - <form enctype="multipart/form-data" action="{{url}}" method="post"> - {% csrf_token %} - {% load crispy_forms_tags %} - {% crispy form %} - </form> - </div> -</div> + <div class="row"> + <div class="col-12"> + <input type="submit" name="submit" value="Submit your Comment for vetting" class="btn btn-primary" id="submit-id-submit"> + <p class="mt-2" id="goodCommenter"><i>By clicking on Submit, you agree with the <a target="_blank" href="{% url 'scipost:terms_and_conditions' %}">Terms and Conditions</a>.</i></p> + </div> + </div> +</form> <div class="row"> - <div class="col-md-10"> + <div class="col-md-9"> <h3>Preview of your comment:</h3> <p id="preview-comment_text"></p> </div> diff --git a/comments/templates/comments/_comment_card_content.html b/comments/templates/comments/_comment_card_content.html index fba6a0aee5a47323a4c8a44090b04290a72b5f62..c6788a79a355818dc3e31f711bdda0027efcdeaf 100644 --- a/comments/templates/comments/_comment_card_content.html +++ b/comments/templates/comments/_comment_card_content.html @@ -1,31 +1,20 @@ <div class="card-block card-comment"> {% block card_block_header %}{% endblock %} <p class="card-text"> - <span class="text-blue"> - <a href="{% if comment.get_author %}{% url 'scipost:contributor_info' comment.get_author.id %}{% else %}javascript:;{% endif %}">{{comment.get_author_str}}</a>: - </span> + <a href="{{comment.author.get_absolute_url}}">{{comment.author.user.first_name}} {{comment.author.user.last_name}}</a>: - {% if comment.submission %} - <a href="{% url 'submissions:submission' comment.submission.arxiv_identifier_w_vn_nr %}#comment_id{{comment.id}}"> - "{{comment.comment_text|slice:'30'}}{% if comment.comment_text|length > 30 %}...{% endif %}" - </a> - </p><p class="card-text pl-md-3"> - in submission on <a href="{% url 'submissions:submission' comment.submission.arxiv_identifier_w_vn_nr %}" class="pubtitleli">{{comment.submission.title}}</a> - <span class="text-muted">by {{comment.submission.author_list}}</span> - {% elif comment.commentary %} - <a href="{% url 'commentaries:commentary' comment.commentary.arxiv_or_DOI_string %}#comment_id{{comment.id}}"> - "{{comment.comment_text|slice:'30'}}{% if comment.comment_text|length > 30 %}...{% endif %}" - </a> - </p><p class="card-text pl-md-3"> - in commentary on <a href="{% url 'commentaries:commentary' comment.commentary.arxiv_or_DOI_string %}" class="pubtitleli">{{comment.commentary.pub_title}}</a> - <span class="text-muted">by {{comment.commentary.author_list}}</span> - {% elif comment.thesislink %} - <a href="{% url 'theses:thesis' comment.thesislink.id %}#comment_id{{comment.id}}"> - "{{comment.comment_text|slice:'30'}}{% if comment.comment_text|length > 30 %}...{% endif %}" - </a> - </p><p class="card-text pl-md-3"> - in thesislink on <a href="{% url 'theses:thesis' comment.thesislink.id %}" class="pubtitleli">{{comment.thesislink.title}}</a> - <span class="text-muted">by {{ comment.thesislink.author }}</span> + <a href="{{comment.get_absolute_url}"> + "{{comment.comment_text|slice:'30'}}{% if comment.comment_text|length > 30 %}...{% endif %}" + </a> + </p><p class="card-text pl-md-3"> + in {{comment.content_type|capfirst}} on <a href="{{comment.content_object.get_absolute_url}}" class="pubtitleli">{{comment.title}}</a> + + {% if comment.content_object.author_list %} + {% comment %} + Using 'by xxx' on non-submission comments here would be ambigious. Does the `by xxx` apply to the + other object (eg. Report), or the Submission, the Comment, etc? + {% endcomment %} + <span class="text-muted">by {{comment.content_object.author_list}}</span> {% endif %} </p> </div> diff --git a/comments/templates/comments/_comment_card_extended_for_author.html b/comments/templates/comments/_comment_card_extended_for_author.html new file mode 100644 index 0000000000000000000000000000000000000000..aaac79e90846156ed8c28b7b195aad7bd4921aef --- /dev/null +++ b/comments/templates/comments/_comment_card_extended_for_author.html @@ -0,0 +1,16 @@ +<div class="card-block card-comment"> + <div class="mb-4 mt-2"> + <div class="d-inline-block mr-1">Nr {{comment.id}}</div> + {% include 'comments/_comment_voting_summary.html' with comment=comment class='small' %} + </div> + + <p>"{{comment.comment_text|linebreaksbr}}"</p> + <p class="card-text">by <a href="{{comment.author.get_absolute_url}}">{{comment.author.user.first_name}} {{comment.author.user.last_name}}</a> in {{comment.content_type|capfirst}} on <a href="{{comment.content_object.get_absolute_url}}" class="pubtitleli">{{comment.title}}</a> {% if comment.content_object.author_list %} <span class="text-muted">by {{comment.content_object.author_list}}</span>{% endif %}</p> + + {% comment %} + Using 'by xxx' on non-submission comments here would be ambigious. Does the `by xxx` apply to the + other object (eg. Report), or the Submission, the Comment, etc? + {% endcomment %} + <p class="card-text text-muted">Comment submitted {{comment.date_submitted}}</p> + <p class="card-text">Status: <span class="{% if comment.status == 1 %} text-success{% elif comment.status == 0 %} text-danger{% endif %}">{{comment.get_status_display}}</span></p> +</div> diff --git a/comments/templates/comments/_comment_categories.html b/comments/templates/comments/_comment_categories.html index fec5707adee9e21640e36ce5c4d8208df2ee2893..e848c6632b726cbb6c94cc1db013cefb6ac53f19 100644 --- a/comments/templates/comments/_comment_categories.html +++ b/comments/templates/comments/_comment_categories.html @@ -1,30 +1,34 @@ -<div class="btn-group category-group {{class}}"> - <label class="btn btn-secondary name">Category:</label> - {% if comment.is_rem %} - <label class="btn btn-secondary"><span class="inner">remark</spin></label> - {% endif %} - {% if comment.is_que %} - <label class="btn btn-secondary"><span class="inner">question</spin></label> - {% endif %} - {% if comment.is_ans %} - <label class="btn btn-secondary"><span class="inner">answer to question</spin></label> - {% endif %} - {% if comment.is_obj %} - <label class="btn btn-secondary"><span class="inner">objection</spin></label> - {% endif %} - {% if comment.is_rep %} - <label class="btn btn-secondary"><span class="inner">reply to objection</spin></label> - {% endif %} - {% if comment.is_cor %} - <label class="btn btn-secondary"><span class="inner">correction</spin></label> - {% endif %} - {% if comment.is_val %} - <label class="btn btn-secondary"><span class="inner">validation or rederivation</spin></label> - {% endif %} - {% if comment.is_lit %} - <label class="btn btn-secondary"><span class="inner">pointer to related literature</spin></label> - {% endif %} - {% if comment.is_sug %} - <label class="btn btn-secondary"><span class="inner">suggestion for further work</spin></label> - {% endif %} -</div> +{% load comment_extras %} + +{% if comment|has_category %} + <div class="btn-group category-group mr-2"> + <label class="btn btn-secondary name">Category:</label> + {% if comment.is_rem %} + <label class="btn btn-secondary"><span class="inner">remark</spin></label> + {% endif %} + {% if comment.is_que %} + <label class="btn btn-secondary"><span class="inner">question</spin></label> + {% endif %} + {% if comment.is_ans %} + <label class="btn btn-secondary"><span class="inner">answer to question</spin></label> + {% endif %} + {% if comment.is_obj %} + <label class="btn btn-secondary"><span class="inner">objection</spin></label> + {% endif %} + {% if comment.is_rep %} + <label class="btn btn-secondary"><span class="inner">reply to objection</spin></label> + {% endif %} + {% if comment.is_cor %} + <label class="btn btn-secondary"><span class="inner">correction</spin></label> + {% endif %} + {% if comment.is_val %} + <label class="btn btn-secondary"><span class="inner">validation or rederivation</spin></label> + {% endif %} + {% if comment.is_lit %} + <label class="btn btn-secondary"><span class="inner">pointer to related literature</spin></label> + {% endif %} + {% if comment.is_sug %} + <label class="btn btn-secondary"><span class="inner">suggestion for further work</spin></label> + {% endif %} + </div> +{% endif %} diff --git a/comments/templates/comments/_comment_identifier.html b/comments/templates/comments/_comment_identifier.html index 3247be4b6f071feae71a2f1279ded6e0973f9f74..5e668b933aefc737db21a22149daf829a8594fce 100644 --- a/comments/templates/comments/_comment_identifier.html +++ b/comments/templates/comments/_comment_identifier.html @@ -1,31 +1,23 @@ +{% load comment_extras %} + <div class="commentid" id="comment_id{{comment.id}}"> <h3> {% if comment.is_author_reply %}Author{% endif %} - {% block comment_author %} - {% if not comment.anonymous %} - <a href="{% url 'scipost:contributor_info' comment.get_author.id %}">{{comment.get_author_str}}</a> - {% else %} - Anonymous - {% endif %} - {% endblock comment_author %} + <a href="{{comment.author.get_absolute_url}}">{{comment.author.user.first_name}} {{comment.author.user.last_name}}</a> on {{comment.date_submitted|date:'Y-m-d'}} </h3> - {% if comment.in_reply_to_comment or comment.in_reply_to_report %} - <h4> - {% if comment.in_reply_to_comment %} - (in reply to <a href="#comment_id{{comment.in_reply_to_comment.id}}">{{comment.in_reply_to_comment.comment.get_author_str}}</a> on {{comment.in_reply_to_comment.date_submitted|date:'Y-m-d'}}) - {% elif comment.in_reply_to_report %} - (in reply to <a href="#report_{{comment.in_reply_to_report.report_nr}}"> - {% if not comment.in_reply_to_report.anonymous %} - {{comment.in_reply_to_report.get_author_str}} - {% else %} - Report {{comment.in_reply_to_report.id}} - {% endif %} + {% if comment|is_reply_to_comment %} + (in reply to <a href="{{comment.get_absolute_url}}">{{comment.content_object.author.user.first_name}} {{comment.content_object.author.user.last_name}}</a> on {{comment.content_object.date_submitted|date:'Y-m-d'}}) + {% elif comment|is_reply_to_report %} + (in reply to <a href="{{comment.get_absolute_url}}"> + + Report {{comment.content_object.report_nr}} + {% if not comment.content_object.anonymous %} + by {{comment.content_object.author.user.first_name}} {{comment.content_object.author.user.last_name}} + {% endif %} - </a> on {{comment.in_reply_to_report.date_submitted|date:'Y-m-d'}}) - {% endif %} - </h4> + </a> on {{comment.content_object.date_submitted|date:'Y-m-d'}}) {% endif %} </div> diff --git a/comments/templates/comments/_comment_identifier_vetting.html b/comments/templates/comments/_comment_identifier_vetting.html deleted file mode 100644 index edf903c7eac54853e71e69c30c5640d01262bf42..0000000000000000000000000000000000000000 --- a/comments/templates/comments/_comment_identifier_vetting.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'comments/_comment_identifier.html' %} - -{% comment %} - - Be careful using this template!!! - It overwrites the anonymity of the writer! - -{% endcomment %} - -{% block comment_author %} - <a href="{% url 'scipost:contributor_info' comment.author.id %}">{{comment.author.user.first_name}} {{comment.author.user.last_name}}</a> -{% endblock comment_author %} diff --git a/comments/templates/comments/_comment_tex_template.html b/comments/templates/comments/_comment_tex_template.html index b6540e82c6d45520961cb04feacc921b65b4e5d0..cb39aa0d3c78f50d5fe9f2d75f618abadc9ce8a5 100644 --- a/comments/templates/comments/_comment_tex_template.html +++ b/comments/templates/comments/_comment_tex_template.html @@ -4,8 +4,8 @@ Received {{comment.date_submitted|date:'d-m-Y'}}\ \\ {% endspaceless %} {% for subcomment in comment.nested_comments.vetted %} - \addcontentsline{toc}{subsection}{\protect\numberline{}{% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} by {% if subcomment.anonymous %}anonymous{% else %}{{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}}{% endif %} } + \addcontentsline{toc}{subsection}{\protect\numberline{}{% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} by {{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}} } - \subsection*{ {% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.parentloop.counter}}.{{forloop.counter}} by {% if subcomment.anonymous %}anonymous{% else %}{{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}}{% endif %} } + \subsection*{ {% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.parentloop.counter}}.{{forloop.counter}} by {{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}} } {% include 'comments/_comment_tex_template.html' with comment=subcomment %} {% endfor %} diff --git a/comments/templates/comments/_comment_voting_form.html b/comments/templates/comments/_comment_voting_form.html index 1563284d7cfd90111e4864b04809f642d3d05b47..b1b8fe06b242a5879ea229b93baeff599bac3bd7 100644 --- a/comments/templates/comments/_comment_voting_form.html +++ b/comments/templates/comments/_comment_voting_form.html @@ -1,3 +1,4 @@ + {% if user.is_authenticated and perms.scipost.can_express_opinion_on_comments %} {% if user.contributor != comment.author %} <div class="btn-group voting-group {{class}}"> diff --git a/comments/templates/comments/_single_comment.html b/comments/templates/comments/_single_comment.html index 1eaaf85a016bf09f61c8b349bd7339cbae35e843..0759c42ee017a67d2bbf6d345f140b5baf7b29aa 100644 --- a/comments/templates/comments/_single_comment.html +++ b/comments/templates/comments/_single_comment.html @@ -2,53 +2,40 @@ {% load filename %} {% load file_extentions %} -<div class="row"> - <div class="col-12"> - <div class="comment" id="comment_id{{comment.id}}"> - - <div class="row"> - <div class="col-12"> - {% include 'comments/_comment_identifier.html' with comment=comment %} - - {% include 'comments/_comment_categories.html' with comment=comment class='mr-2' %} - - {% include 'comments/_comment_voting_form.html' with comment=comment perms=perms user=user %} - </div> - </div> - - - <div class="row"> - <div class="col-12"> - <p> - {{ comment.comment_text|linebreaks }} - - {% 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 %} - </p> - {% if user|is_in_group:'Editorial College' or user|is_in_group:'Editorial Administrators' %} - {% if comment.remarks_for_editors %} - <h3>Remarks for editors:</h3> - <p>{{ comment.remarks_for_editors|linebreaks }}</p> - {% endif %} - {% endif %} - </div> - </div> +<div class="comment"> + + {% include 'comments/_comment_identifier.html' with comment=comment %} + + {% include 'comments/_comment_categories.html' with comment=comment %} - {% for reply in comment.nested_comments.vetted %} - {% include 'comments/_single_comment.html' with comment=reply perms=perms user=user %} - {% endfor %} + {% include 'comments/_comment_voting_form.html' with comment=comment perms=perms user=user %} - {% block comment_footer %}{% endblock %} - </div> - </div> + <p class="my-3 pb-2"> + {{ comment.comment_text|linebreaksbr }} + + {% 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 %} + </p> + {% if user|is_in_group:'Editorial College' or user|is_in_group:'Editorial Administrators' %} + {% if comment.remarks_for_editors %} + <h3>Remarks for editors:</h3> + <p>{{ comment.remarks_for_editors|linebreaks }}</p> + {% endif %} + {% endif %} + + {% for reply in comment.nested_comments.vetted %} + {% include 'comments/_single_comment.html' with comment=reply perms=perms user=user %} + {% endfor %} + + {% block comment_footer %}{% endblock %} </div> diff --git a/comments/templates/comments/_single_comment_with_link.html b/comments/templates/comments/_single_comment_with_link.html index 06cf9a4b95f2bb72bfe3d90b5ed87eac4b8e0b73..552db102a5a95357cd548a96b5b4cb2d3419bacb 100644 --- a/comments/templates/comments/_single_comment_with_link.html +++ b/comments/templates/comments/_single_comment_with_link.html @@ -3,10 +3,6 @@ {% block comment_footer %} {% if user.is_authenticated and perms.scipost.can_submit_comments %} <hr class="small"> - <div class="row"> - <div class="col-12"> - <h3><a href="{% url 'comments:reply_to_comment' comment_id=comment.id %}">Reply to this comment</a></h3> - </div> - </div> + <h3 class="mb-3"><a href="{% url 'comments:reply_to_comment' comment_id=comment.id %}">Reply to this comment</a></h3> {% endif %} {% endblock %} diff --git a/comments/templates/comments/_vet_comment_form.html b/comments/templates/comments/_vet_comment_form.html new file mode 100644 index 0000000000000000000000000000000000000000..204b12d7017b92b141c0e0aeb8c04271136f0e6b --- /dev/null +++ b/comments/templates/comments/_vet_comment_form.html @@ -0,0 +1,50 @@ +{% load bootstrap %} +{% load filename %} +{% load file_extentions %} +{% load comment_extras %} + +<div class="card card-vetting"> + <div class="card-header"> + <h2>From {{comment.core_content_object|get_core_content_type|capfirst}} (<a href="{{comment.get_absolute_url}}" target="_blank">link</a>)</h2> + {% get_summary_template comment.core_content_object %} + </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.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/new_comment.html b/comments/templates/comments/new_comment.html index 4aedb638ec55c389f72c29ca872dd1cbb1aa6609..55d670be89d662dbad2d39a6f85bf65315bd7c63 100644 --- a/comments/templates/comments/new_comment.html +++ b/comments/templates/comments/new_comment.html @@ -2,83 +2,11 @@ {% if user.is_authenticated and open_for_commenting and perms.scipost.can_submit_comments %} -<script> - $(document).ready(function(){ - - var comment_text_input = $("#id_comment_text"); - - function set_comment_text(value) { - $("#preview-comment_text").text(value) - } - set_comment_text(comment_text_input.val()) - - comment_text_input.keyup(function(){ - var new_text = $(this).val() - set_comment_text(new_text) - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - }) - - }); -</script> - <hr> -<div class="row" id="contribute_comment"> - <div class="col-12"> - <h2 class="highlight">Contribute a Comment:</h2> - </div> -</div> -<div class="row"> - <div class="col-12"> - <form enctype="multipart/form-data" - action="{% url 'comments:new_comment' object_id=object_id type_of_object=type_of_object %}" - method="post"> - {% csrf_token %} - <div class="row"> - <div class="col-md-9"> - {{form.comment_text|bootstrap:'12,12'}} - <p> - In your comment, you can use LaTeX \$...\$ for in-text equations or \ [ ... \ ] for on-line equations. - </p> - <p id="goodCommenter"><em> - Be professional. Only serious and meaningful comments will be vetted through. - </em></p> - </div> - <div class="col-md-3 radio-list"> - <label>Specify categorization(s):</label> - {{form.is_cor|bootstrap}} - {{form.is_rem|bootstrap}} - {{form.is_que|bootstrap}} - {{form.is_ans|bootstrap}} - {{form.is_obj|bootstrap}} - {{form.is_rep|bootstrap}} - {{form.is_val|bootstrap}} - {{form.is_lit|bootstrap}} - {{form.is_sug|bootstrap}} - </div> - </div> - - <div class="row"> - <div class="col-md-9"> - {{form.remarks_for_editors|bootstrap:'12,12'}} - </div> - <div class="col-md-3"> - {{form.file_attachment|bootstrap:'12,12'}} - </div> - </div> +<h2 class="highlight" id="contribute_comment">Contribute a Comment:</h2> - <div class="row"> - <div class="col-12"> - <input type="submit" name="submit" value="Submit your Comment for vetting" class="btn btn-primary" id="submit-id-submit"> - <p class="mt-2" id="goodCommenter"><i>By clicking on Submit, you agree with the <a target="_blank" href="{% url 'scipost:terms_and_conditions' %}">Terms and Conditions</a>.</i></p> - </div> - </div> - </form> - </div> - <div class="col-12"> - <h3>Preview of your comment:</h3> - <p id="preview-comment_text"></p> - </div> -</div> +{% url 'comments:new_comment' object_id=object_id type_of_object=type_of_object as url %} +{% include 'comments/_add_comment_form.html' with form=form url=url %} {% endif %} diff --git a/comments/templates/comments/reply_to_comment.html b/comments/templates/comments/reply_to_comment.html index 3937e5d0051a557def52eb7728998e2b5f0ccbf3..26a545ddbe4c0b0c1b984b9dc77a18166e9999ba 100644 --- a/comments/templates/comments/reply_to_comment.html +++ b/comments/templates/comments/reply_to_comment.html @@ -1,5 +1,7 @@ {% extends 'scipost/base.html' %} +{% load comment_extras %} + {% block pagetitle %}: reply to comment{% endblock pagetitle %} {% block content %} @@ -13,25 +15,8 @@ <div class="row"> <div class="col-12"> - {% if comment.commentary %} - <h2>The Commentary concerned:</h2> - {% include 'commentaries/_commentary_summary.html' with commentary=comment.commentary %} - - <h3 class="mt-3">Abstract:</h3> - <p>{{ comment.commentary.pub_abstract }}</p> - {% endif %} - - {% if comment.submission %} - <h2>The Submission concerned:</h2> - - {% include 'submissions/_submission_summary.html' with submission=comment.submission %} - - {% endif %} - - {% if comment.thesislink %} - <h2>The Thesis concerned:</h2> - {% include "theses/_thesislink_information.html" with thesislink=comment.thesislink %} - {% endif %} + <h2>The {{comment.core_content_object|get_core_content_type|capfirst}} concerned:</h2> + {% get_summary_template comment.core_content_object %} </div> </div> @@ -49,7 +34,7 @@ {% include 'comments/_comment_voting_summary.html' with comment=comment %} - <p>{{ comment.comment_text|linebreaks }}</p> + <p>{{ comment.comment_text|linebreaksbr }}</p> </div> </div> </div> diff --git a/comments/templates/comments/submit_comment_form.html b/comments/templates/comments/submit_comment_form.html deleted file mode 100644 index 8251b3daf4faa657f5d34a537139069dbc4c61c1..0000000000000000000000000000000000000000 --- a/comments/templates/comments/submit_comment_form.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load bootstrap %} - -{# This template should eventually be used to create a single template for all comment forms ! #} - - - -{% for field in form %} - {% include 'tags/bootstrap/field.html' with field=field %} -{% endfor %} 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/theses/templatetags/__init__.py b/comments/templatetags/__init__.py similarity index 100% rename from theses/templatetags/__init__.py rename to comments/templatetags/__init__.py diff --git a/comments/templatetags/comment_extras.py b/comments/templatetags/comment_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..60d6707af00baeb81ce772d30f6437a83e2f5cf2 --- /dev/null +++ b/comments/templatetags/comment_extras.py @@ -0,0 +1,92 @@ +from django import template +from django.utils.safestring import mark_safe + +from ..models import Comment + +from commentaries.models import Commentary +from submissions.models import Submission, Report +from theses.models import ThesisLink + +register = template.Library() + + +class CommentTemplateNode(template.Node): + """ + This template node accepts an object being a Submission, Commentary or ThesisLink + and includes its summary page. + """ + def __init__(self, content_object): + self.content_object = content_object + + def render(self, context): + content_object = self.content_object.resolve(context) + if isinstance(content_object, Submission): + t = context.template.engine.get_template('submissions/_submission_summary_short.html') + return t.render(template.Context({'submission': content_object})) + elif isinstance(content_object, Commentary): + t = context.template.engine.get_template('commentaries/_commentary_summary.html') + return t.render(template.Context({'commentary': content_object})) + elif isinstance(content_object, ThesisLink): + t = context.template.engine.get_template('theses/_thesislink_information.html') + return t.render(template.Context({'thesislink': content_object})) + else: + raise template.TemplateSyntaxError( + "The instance type given as an argument is not supported.") + + +@register.filter +def get_core_content_type(content_object): + if isinstance(content_object, Submission): + return 'submission' + elif isinstance(content_object, Commentary): + return 'commentary' + elif isinstance(content_object, ThesisLink): + return 'thesislink' + + +@register.tag +def get_summary_template(parser, token): + """ + This tag includes the summary template of the object, using `CommentTemplateNode` + to determine the template and its context. + """ + try: + tag_name, content_object = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError( + "get_summary_template tag requires exactly two arguments") + content_object = template.Variable(content_object) + return CommentTemplateNode(content_object) + + +@register.filter +def is_reply_to_comment(comment): + return isinstance(comment.content_object, Comment) + + +@register.filter +def is_reply_to_report(comment): + return isinstance(comment.content_object, Report) + + +@register.filter +def has_category(comment): + if comment.is_cor: + return True + elif comment.is_rem: + return True + elif comment.is_que: + return True + elif comment.is_ans: + return True + elif comment.is_obj: + return True + elif comment.is_rep: + return True + elif comment.is_val: + return True + elif comment.is_lit: + return True + elif comment.is_sug: + return True + return False diff --git a/comments/test_views.py b/comments/test_views.py index d80fea2bd5e779336dc0d3ce31a6e77f9ae23a91..c16e256953ec7be3ea87ce46c9e96f16763cb15c 100644 --- a/comments/test_views.py +++ b/comments/test_views.py @@ -1,8 +1,7 @@ from django.test import TestCase, RequestFactory, Client -from django.urls import reverse, reverse_lazy -from django.contrib.auth.models import Group +from django.urls import reverse from django.contrib.messages.storage.fallback import FallbackStorage -from django.core.exceptions import PermissionDenied +from django.http import Http404 from scipost.factories import ContributorFactory from theses.factories import ThesisLinkFactory @@ -20,6 +19,7 @@ from common.helpers.test import add_groups_and_permissions class TestNewComment(TestCase): def setUp(self): add_groups_and_permissions() + ContributorFactory.create_batch(5) def install_messages_middleware(self, request): # I don't know what the following three lines do, but they help make a RequestFactory @@ -33,7 +33,7 @@ class TestNewComment(TestCase): contributor = ContributorFactory() thesislink = ThesisLinkFactory() - valid_comment_data = model_form_data(CommentFactory.build(), CommentForm) + valid_comment_data = model_form_data(CommentFactory, CommentForm) target = reverse('comments:new_comment', kwargs={'object_id': thesislink.id, 'type_of_object': 'thesislink'}) comment_count = Comment.objects.filter(author=contributor).count() @@ -56,7 +56,7 @@ class TestNewComment(TestCase): submission = EICassignedSubmissionFactory() submission.open_for_commenting = True submission.save() - valid_comment_data = model_form_data(CommentFactory.build(), CommentForm) + valid_comment_data = model_form_data(CommentFactory, CommentForm) target = reverse( 'comments:new_comment', kwargs={'object_id': submission.id, 'type_of_object': 'submission'}, @@ -80,13 +80,12 @@ class TestNewComment(TestCase): ) self.assertRedirects(response, expected_redirect_link) - def test_submitting_comment_on_commentary_creates_comment_and_redirects(self): """ Valid Comment gets saved """ contributor = ContributorFactory() commentary = UnpublishedVettedCommentaryFactory() - valid_comment_data = model_form_data(CommentFactory.build(), CommentForm) + valid_comment_data = model_form_data(CommentFactory, CommentForm) target = reverse('comments:new_comment', kwargs={'object_id': commentary.id, 'type_of_object': 'commentary'}) comment_count = Comment.objects.filter(author=contributor).count() @@ -97,7 +96,7 @@ class TestNewComment(TestCase): request.user = contributor.user response = new_comment(request, object_id=commentary.id, type_of_object='commentary') - comment_count = commentary.comment_set.count() + comment_count = commentary.comments.count() self.assertEqual(comment_count, 1) response.client = Client() @@ -105,13 +104,12 @@ class TestNewComment(TestCase): 'commentaries:commentary', kwargs={'arxiv_or_DOI_string': commentary.arxiv_or_DOI_string}) self.assertRedirects(response, expected_redirect_link) - def test_submitting_comment_on_submission_that_is_not_open_for_commenting_should_be_impossible(self): contributor = ContributorFactory() submission = EICassignedSubmissionFactory() submission.open_for_commenting = False submission.save() - valid_comment_data = model_form_data(CommentFactory.build(), CommentForm) + valid_comment_data = model_form_data(CommentFactory, CommentForm) target = reverse( 'comments:new_comment', kwargs={'object_id': submission.id, 'type_of_object': 'submission'}, @@ -123,5 +121,5 @@ class TestNewComment(TestCase): request = RequestFactory().post(target, valid_comment_data) self.install_messages_middleware(request) request.user = contributor.user - with self.assertRaises(PermissionDenied): - response = new_comment(request, object_id=submission.id, type_of_object='submission') + with self.assertRaises(Http404): + new_comment(request, object_id=submission.id, type_of_object='submission') 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..fba5791ba8e8a1b212eee8660dc77d77b7f32bb0 100644 --- a/comments/views.py +++ b/comments/views.py @@ -1,18 +1,17 @@ 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.db import transaction +from guardian.shortcuts import 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 +20,129 @@ 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} - ) - - 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 + form = CommentForm(request.POST or None) + if form.is_valid(): + object_id = int(kwargs["object_id"]) + type_of_object = kwargs["type_of_object"] + if type_of_object == "thesislink": + _object = get_object_or_404(ThesisLink.objects.open_for_commenting(), id=object_id) + elif type_of_object == "submission": + _object = get_object_or_404(Submission.objects.open_for_commenting(), id=object_id) + _object.add_event_for_eic('A new comment has been added.') + elif type_of_object == "commentary": + _object = get_object_or_404(Commentary.objects.open_for_commenting(), id=object_id) -@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)) + new_comment = form.save(commit=False) + new_comment.author = request.user.contributor + new_comment.content_object = _object + new_comment.save() + new_comment.grant_permissions() + + 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 + content_object = comment.content_object + content_object.latest_activity = timezone.now() + content_object.save() + + if isinstance(content_object, Submission): + # Add events to Submission and send mail to author of the Submission + content_object.add_event_for_eic('A Comment has been accepted.') + content_object.add_event_for_author('A new Comment has been added.') + if not comment.is_author_reply: + SubmissionUtils.load({'submission': content_object}) + SubmissionUtils.send_author_comment_received_email() + + 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 # Why's this here?? + comment.save() + + # Send emails + CommentUtils.load({'comment': comment}) + CommentUtils.email_comment_vet_rejected_to_author( + email_response=form.cleaned_data['email_response_field']) + + if isinstance(comment.content_object, Submission): + # Add event if commented to Submission + comment.content_object.add_event_for_eic('A Comment has been rejected.') + + messages.success(request, 'Submitted Comment vetted.') + if isinstance(comment.content_object, Submission): + submission = comment.content_object + if submission.editor_in_charge == request.user.contributor: + # Redirect a EIC back to the Editorial Page! + return redirect(reverse('submissions:editorial_page', + args=(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() + try: + # Submission/Commentary + is_author = comment.content_object.authors.filter(id=request.user.contributor.id).exists() + except AttributeError: + # ThesisLink + is_author = comment.content_object.author == request.user.contributor + + form = CommentForm(request.POST or None, request.FILES or None) + if form.is_valid(): + newcomment = form.save(commit=False) + newcomment.content_object = comment + newcomment.is_author_reply = is_author + newcomment.author = request.user.contributor + newcomment.save() + newcomment.grant_permissions() + + messages.success(request, '<h3>Thank you for contributing a Reply</h3>' + 'It will soon be vetted by an Editor.') + return redirect(newcomment.content_object.get_absolute_url()) context = {'comment': comment, 'is_author': is_author, 'form': form} return render(request, 'comments/reply_to_comment.html', context) @@ -250,49 +151,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.content_object = report + newcomment.is_author_reply = is_author + newcomment.author = request.user.contributor + newcomment.save() + newcomment.grant_permissions() + + 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) @@ -300,18 +175,6 @@ def reply_to_report(request, report_id): @permission_required('scipost.can_express_opinion_on_comments', raise_exception=True) def express_opinion(request, comment_id, opinion): # A contributor has expressed an opinion on a comment - contributor = request.user.contributor comment = get_object_or_404(Comment, pk=comment_id) - comment.update_opinions(contributor.id, opinion) - if comment.submission is not None: - return HttpResponseRedirect('/submission/' + comment.submission.arxiv_identifier_w_vn_nr + - '/#comment_id' + str(comment.id)) - if comment.commentary is not None: - return HttpResponseRedirect('/commentary/' + str(comment.commentary.arxiv_or_DOI_string) + - '/#comment_id' + str(comment.id)) - if comment.thesislink is not None: - return HttpResponseRedirect('/thesis/' + str(comment.thesislink.id) + - '/#comment_id' + str(comment.id)) - else: - # will never call this - return(render(request, 'scipost/index.html')) + comment.update_opinions(request.user.contributor.id, opinion) + return redirect(comment.get_absolute_url()) 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/admin.py b/scipost/admin.py index 2ff4b9b04e9db87a24ae24dea9b0aae359c910f5..36831284095a32ab06999a534bec999cd713bb46 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -123,6 +123,7 @@ class DraftInvitationAdminForm(forms.ModelForm): model = DraftInvitation fields = '__all__' + class DraftInvitationAdmin(admin.ModelAdmin): search_fields = ['first_name', 'last_name', 'email', 'processed'] form = DraftInvitationAdminForm diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index f0c48b82fcd072ae442c9d478b61f52408025418..9de95fd8b9fbd9a03677829ec90a66b3c329c83e 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -4,7 +4,8 @@ from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType from partners.models import Contact -from scipost.models import Contributor +from scipost.models import Contributor, DraftInvitation +from submissions.models import Report class Command(BaseCommand): @@ -35,6 +36,8 @@ class Command(BaseCommand): # Create Permissions content_type = ContentType.objects.get_for_model(Contributor) content_type_contact = ContentType.objects.get_for_model(Contact) + content_type_draft_invitation = ContentType.objects.get_for_model(DraftInvitation) + content_type_report = ContentType.objects.get_for_model(Report) # Supporting Partners can_manage_SPB, created = Permission.objects.get_or_create( @@ -63,6 +66,13 @@ class Command(BaseCommand): content_type=content_type) # Registration and invitations + change_draft_invitation, created = Permission.objects.get_or_create( + codename='change_draftinvitation', + defaults={ + 'name': 'Can vet registration requests', + 'content_type': content_type_draft_invitation + } + ) can_vet_registration_requests, created = Permission.objects.get_or_create( codename='can_vet_registration_requests', name='Can vet registration requests', @@ -122,7 +132,7 @@ class Command(BaseCommand): name='Can request Thesis Links', content_type=content_type) - # Vetting of simple objects + # Vetting of objects can_vet_commentary_requests, created = Permission.objects.get_or_create( codename='can_vet_commentary_requests', name='Can vet Commentary page requests', @@ -139,12 +149,20 @@ class Command(BaseCommand): codename='can_vet_comments', name='Can vet submitted Comments', content_type=content_type) + can_vet_submitted_reports, created = Permission.objects.get_or_create( + codename='can_vet_submitted_reports', + name='Can vet submitted Reports', + content_type=content_type_report) # Submissions can_submit_manuscript, created = Permission.objects.get_or_create( codename='can_submit_manuscript', name='Can submit manuscript', content_type=content_type) + can_read_all_eic_events, created = Permission.objects.get_or_create( + codename='can_read_all_eic_events', + name='Can read all Editor-in-charge events', + content_type=content_type) # Submission handling can_view_pool, created = Permission.objects.get_or_create( @@ -159,10 +177,6 @@ class Command(BaseCommand): codename='can_take_charge_of_submissions', name='Can take charge (become Editor-in-charge) of submissions', content_type=content_type) - can_vet_submitted_reports, created = Permission.objects.get_or_create( - codename='can_vet_submitted_reports', - name='Can vet submitted Reports', - content_type=content_type) # Refereeing can_referee, created = Permission.objects.get_or_create( @@ -221,6 +235,7 @@ class Command(BaseCommand): # Assign permissions to groups SciPostAdmin.permissions.set([ can_manage_registration_invitations, + change_draft_invitation, can_email_group_members, can_email_particulars, can_resend_registration_requests, @@ -228,6 +243,7 @@ class Command(BaseCommand): can_vet_commentary_requests, can_vet_thesislink_requests, can_vet_authorship_claims, + can_vet_submitted_reports, can_vet_comments, can_view_pool, can_assign_submissions, @@ -245,6 +261,7 @@ class Command(BaseCommand): AdvisoryBoard.permissions.set([ can_manage_registration_invitations, + change_draft_invitation, can_attend_VGMs, ]) @@ -259,12 +276,12 @@ class Command(BaseCommand): can_publish_accepted_submission, can_attend_VGMs, can_manage_reports, + can_read_all_eic_events, ]) EditorialCollege.permissions.set([ can_view_pool, can_take_charge_of_submissions, - can_vet_submitted_reports, view_bylaws, can_attend_VGMs, ]) @@ -273,6 +290,7 @@ class Command(BaseCommand): can_vet_commentary_requests, can_vet_thesislink_requests, can_vet_authorship_claims, + can_vet_submitted_reports, can_vet_comments, ]) @@ -291,6 +309,7 @@ class Command(BaseCommand): Ambassadors.permissions.set([ can_manage_registration_invitations, + change_draft_invitation, ]) JuniorAmbassadors.permissions.set([ 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..b12640ca23c0e4a93349e5f742f586829e52875d 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -8,6 +8,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models from django.template import Template, Context from django.utils import timezone +from django.urls import reverse from django_countries.fields import CountryField @@ -53,7 +54,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), @@ -68,6 +69,9 @@ class Contributor(models.Model): def __str__(self): return '%s, %s' % (self.user.last_name, self.user.first_name) + def get_absolute_url(self): + return reverse('scipost:contributor_info', args=(self.id,)) + def get_formal_display(self): return '%s %s %s' % (self.get_title_display(), self.user.first_name, self.user.last_name) diff --git a/scipost/services.py b/scipost/services.py index 9e61ccd731882f140a3095e7d0b7dfb786250892..3df2ce70fb903edf7ea290f81830ce69094d566c 100644 --- a/scipost/services.py +++ b/scipost/services.py @@ -23,7 +23,7 @@ class DOICaller: def _format_data(self): data = self._crossref_data - pub_title = data['title'][0] + title = data['title'][0] author_list = ['{} {}'.format(author['given'], author['family']) for author in data['author']] # author_list is given as a comma separated list of names on the relevant models (Commentary, Submission) author_list = ", ".join(author_list) @@ -33,7 +33,7 @@ class DOICaller: pub_date = self._get_pub_date(data) self.data = { - 'pub_title': pub_title, + 'title': title, 'author_list': author_list, 'journal': journal, 'volume': volume, @@ -86,7 +86,7 @@ class ArxivCaller: def _format_data(self): data = self._arxiv_data - pub_title = data['title'] + title = data['title'] author_list = [author['name'] for author in data['authors']] # author_list is given as a comma separated list of names on the relevant models (Commentary, Submission) author_list = ", ".join(author_list) @@ -95,8 +95,7 @@ class ArxivCaller: pub_date = dateutil.parser.parse(data['published']).date() self.data = { - 'pub_title': pub_title, - 'title': pub_title, # Duplicate for Commentary/Submission cross-compatibility + 'title': title, 'author_list': author_list, 'arxiv_link': arxiv_link, 'pub_abstract': abstract, diff --git a/scipost/templates/scipost/comments_block.html b/scipost/templates/scipost/comments_block.html index 9b9507f085931320986c9eb93c5ac93131b2260a..14d9eb94e68ba256201b29b32c26563290eac3eb 100644 --- a/scipost/templates/scipost/comments_block.html +++ b/scipost/templates/scipost/comments_block.html @@ -4,7 +4,7 @@ <div class="col-12"> <div class="card card-grey"> <div class="card-block"> - <h2 class="card-title">Comments on this publication</h2> + <h2 class="card-title">Comments{% if type_of_object %} on this {{type_of_object}}{% endif %}</h2> <button class="btn btn-secondary" data-toggle="toggle" data-target="#commentslist">Toggle comments view</button> </div> </div> diff --git a/scipost/templates/scipost/draft_registration_invitation.html b/scipost/templates/scipost/draft_registration_invitation.html index b3afdc15d3ececf1e95a18abc3900b0a5791f3f2..7c81a644bbb4352521510044e6030b9a4e8612b6 100644 --- a/scipost/templates/scipost/draft_registration_invitation.html +++ b/scipost/templates/scipost/draft_registration_invitation.html @@ -66,6 +66,7 @@ $(document).ready(function(){ <th>Date drafted</th> <th>Type</th> <th>Drafted by</th> + <th></th> </tr> </thead> <tbody> @@ -77,10 +78,14 @@ $(document).ready(function(){ <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> + {% if draft.drafted_by.user == request.user %} + <a href="{% url 'scipost:edit_draft_reg_inv' draft.id %}">Edit</a> + {% endif %}</td> </tr> {% empty %} <tr> - <td colspan="6">No drafts found.</td> + <td colspan="7">No drafts found.</td> </tr> {% endfor %} </tbody> diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 66207b001f604fe14d31e5234325c6fcb33daaf6..c5534d31391d943a5dbcc8ab59c0de5c7415e9c9 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -58,10 +58,14 @@ <a class="nav-link" data-toggle="tab" href="#theses">Theses</a> </li> <li class="nav-item btn btn-secondary"> - <a class="nav-link" data-toggle="tab" href="#comments">Comments</a> + {% with request.user.contributor.comments.regular_comments.awaiting_vetting.count as count %} + <a class="nav-link" data-toggle="tab" href="#comments">Comments{% if count %} ({{count}} unvetted){% endif %}</a> + {% endwith %} </li> <li class="nav-item btn btn-secondary"> - <a class="nav-link" data-toggle="tab" href="#author-replies">Author Replies</a> + {% with request.user.contributor.comments.author_replies.awaiting_vetting.count as count %} + <a class="nav-link" data-toggle="tab" href="#author-replies">Author Replies{% if count %} ({{count}} unvetted){% endif %}</a> + {% endwith %} </li> </ul> </div> @@ -227,30 +231,33 @@ </ul> {% endif %} - <h3>Email communications</h3> - <ul> - {% if perms.scipost.can_email_group_members %} - <li><a href="{% url 'scipost:email_group_members' %}">Email Group Members</a></li> - {% endif %} - {% if perms.scipost.can_email_particulars %} - <li><a href="{% url 'scipost:send_precooked_email' %}">Send a precooked email</a></li> - <li><a href="{% url 'scipost:email_particular' %}">Email a particular individual/address</a></li> - {% endif %} - {% if perms.scipost.can_manage_mailchimp %} - <li><a href="{% url 'mailing_lists:overview' %}">Manage mailing lists</a></li> - {% endif %} - </ul> + {% if 'SciPost Administrators' in user_groups %} + <h3>Email communications</h3> + <ul> + {% if perms.scipost.can_email_group_members %} + <li><a href="{% url 'scipost:email_group_members' %}">Email Group Members</a></li> + {% endif %} + {% if perms.scipost.can_email_particulars %} + <li><a href="{% url 'scipost:send_precooked_email' %}">Send a precooked email</a></li> + <li><a href="{% url 'scipost:email_particular' %}">Email a particular individual/address</a></li> + {% endif %} + {% if perms.scipost.can_manage_mailchimp %} + <li><a href="{% url 'mailing_lists:overview' %}">Manage mailing lists</a></li> + {% endif %} + </ul> + {% endif %} </div> {% endif %} <div class="col-md-4"> + {% if 'Vetting Editors' in user_groups or perms.scipost.can_vet_submitted_reports %} <h3>Vetting actions</h3> <ul> {% if perms.scipost.can_vet_commentary_requests %} <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> @@ -259,21 +266,19 @@ <li><a href="{% url 'scipost:vet_authorship_claims' %}">Vet Authorship Claims</a> ({{ nr_authorship_claims_to_vet }})</li> {% endif %} {% if perms.scipost.can_vet_submitted_reports %} - <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + <li><a href="{% url 'submissions:vet_submitted_reports_list' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> {% endif %} </ul> + {% endif %} {% if 'Editorial Administrators' in user_groups %} <h3>Editorial Admin actions</h3> <ul> - {% if perms.scipost.can_publish_accepted_submission %} - <li><a href="{% url 'journals:manage_metadata' %}">Manage metadata</a></li> - <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li> - {% endif %} - {% if perms.scipost.can_manage_reports %} - <li><a href="{% url 'submissions:reports_accepted_list' %}">Accepted Reports</a>{% if nr_reports_without_pdf %} ({{nr_reports_without_pdf}} unfinished){% endif %}</li> - <li><a href="{% url 'submissions:treated_submissions_list' %}">Fully treated Submissions</a>{% if nr_treated_submissions_without_pdf %} ({{nr_treated_submissions_without_pdf}} unfinished){% endif %}</li> - {% endif %} + <li><a href="{% url 'submissions:reports_accepted_list' %}">Accepted Reports</a>{% if nr_reports_without_pdf %} ({{nr_reports_without_pdf}} unfinished){% endif %}</li> + <li><a href="{% url 'submissions:latest_events' %}">All events in the last 24 hours</a></li> + <li><a href="{% url 'submissions:treated_submissions_list' %}">Fully treated Submissions</a>{% if nr_treated_submissions_without_pdf %} ({{nr_treated_submissions_without_pdf}} unfinished){% endif %}</li> + <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li> + <li><a href="{% url 'journals:manage_metadata' %}">Manage metadata</a></li> </ul> {% endif %} @@ -577,9 +582,9 @@ <div class="row" id="mycommentslist"> <div class="col-12"> <ul class="list-group list-group-flush"> - {% for own_comment in own_commentsx %} + {% for own_comment in own_comments %} <li class="list-group-item"> - {% include 'comments/_comment_card_extended_content.html' with comment=own_comment %} + {% include 'comments/_comment_card_extended_for_author.html' with comment=own_comment %} </li> {% empty %} <li class="list-group-item"><em>You have not commented yet.</em></li> @@ -606,7 +611,7 @@ <ul class="list-group list-group-flush"> {% for own_reply in own_authorreplies %} <li class="list-group-item"> - {% include 'comments/_comment_card_extended_content.html' with comment=own_reply %} + {% include 'comments/_comment_card_extended_for_author.html' with comment=own_reply %} </li> {% empty %} <li class="list-group-item"><em>You do not have Author Replies yet.</em></li> diff --git a/scipost/test_services.py b/scipost/test_services.py index 727a20213e84256e79fc3d85f875df77f9be56a7..cce23f55eca75dbf7a4a3aa0a845ead809d5c964 100644 --- a/scipost/test_services.py +++ b/scipost/test_services.py @@ -12,7 +12,7 @@ class ArxivCallerTest(TestCase): caller = ArxivCaller('1612.07611v1') self.assertTrue(caller.is_valid) correct_data = { - 'pub_abstract': 'The Berezinskii-Kosterlitz-Thouless (BKT) transitions of the six-state clock\nmodel on the square lattice are investigated by means of the corner-transfer\nmatrix renormalization group method. The classical analogue of the entanglement\nentropy $S( L, T )$ is calculated for $L$ by $L$ square system up to $L = 129$,\nas a function of temperature $T$. The entropy has a peak at $T = T^{*}_{~}( L\n)$, where the temperature depends on both $L$ and boundary conditions. Applying\nthe finite-size scaling to $T^{*}_{~}( L )$ and assuming the presence of BKT\ntransitions, the transition temperature is estimated to be $T_1^{~} = 0.70$ and\n$T_2^{~} = 0.88$. The obtained results agree with previous analyses. It should\nbe noted that no thermodynamic function is used in this study.', 'author_list': ['Roman KrÄmár', 'Andrej Gendiar', 'Tomotoshi Nishino'], 'arxiv_link': 'http://arxiv.org/abs/1612.07611v1', 'pub_title': 'Phase transition of the six-state clock model observed from the\n entanglement entropy', 'pub_date': datetime.date(2016, 12, 22) + 'pub_abstract': 'The Berezinskii-Kosterlitz-Thouless (BKT) transitions of the six-state clock\nmodel on the square lattice are investigated by means of the corner-transfer\nmatrix renormalization group method. The classical analogue of the entanglement\nentropy $S( L, T )$ is calculated for $L$ by $L$ square system up to $L = 129$,\nas a function of temperature $T$. The entropy has a peak at $T = T^{*}_{~}( L\n)$, where the temperature depends on both $L$ and boundary conditions. Applying\nthe finite-size scaling to $T^{*}_{~}( L )$ and assuming the presence of BKT\ntransitions, the transition temperature is estimated to be $T_1^{~} = 0.70$ and\n$T_2^{~} = 0.88$. The obtained results agree with previous analyses. It should\nbe noted that no thermodynamic function is used in this study.', 'author_list': ['Roman KrÄmár', 'Andrej Gendiar', 'Tomotoshi Nishino'], 'arxiv_link': 'http://arxiv.org/abs/1612.07611v1', 'title': 'Phase transition of the six-state clock model observed from the\n entanglement entropy', 'pub_date': datetime.date(2016, 12, 22) } self.assertEqual(caller.data, correct_data) @@ -20,7 +20,7 @@ class ArxivCallerTest(TestCase): caller = ArxivCaller('cond-mat/0612480') self.assertTrue(caller.is_valid) correct_data = { - 'author_list': ['Kouji Ueda', 'Chenglong Jin', 'Naokazu Shibata', 'Yasuhiro Hieida', 'Tomotoshi Nishino'], 'pub_date': datetime.date(2006, 12, 19), 'arxiv_link': 'http://arxiv.org/abs/cond-mat/0612480v2', 'pub_abstract': 'A kind of least action principle is introduced for the discrete time\nevolution of one-dimensional quantum lattice models. Based on this principle,\nwe obtain an optimal condition for the matrix product states on succeeding time\nslices generated by the real-time density matrix renormalization group method.\nThis optimization can also be applied to classical simulations of quantum\ncircuits. We discuss the time reversal symmetry in the fully optimized MPS.', 'pub_title': 'Least Action Principle for the Real-Time Density Matrix Renormalization\n Group' + 'author_list': ['Kouji Ueda', 'Chenglong Jin', 'Naokazu Shibata', 'Yasuhiro Hieida', 'Tomotoshi Nishino'], 'pub_date': datetime.date(2006, 12, 19), 'arxiv_link': 'http://arxiv.org/abs/cond-mat/0612480v2', 'pub_abstract': 'A kind of least action principle is introduced for the discrete time\nevolution of one-dimensional quantum lattice models. Based on this principle,\nwe obtain an optimal condition for the matrix product states on succeeding time\nslices generated by the real-time density matrix renormalization group method.\nThis optimization can also be applied to classical simulations of quantum\ncircuits. We discuss the time reversal symmetry in the fully optimized MPS.', 'title': 'Least Action Principle for the Real-Time Density Matrix Renormalization\n Group' } self.assertEqual(caller.data, correct_data) @@ -39,7 +39,7 @@ class DOICallerTest(TestCase): 'author_list': [ 'R. Vlijm', 'M. Ganahl', 'D. Fioretto', 'M. Brockmann', 'M. Haque', 'H. G. Evertz', 'J.-S. Caux'], 'volume': '92', - 'pub_title': 'Quasi-soliton scattering in quantum spin chains' + 'title': 'Quasi-soliton scattering in quantum spin chains' } self.assertTrue(caller.is_valid) self.assertEqual(caller.data, correct_data) @@ -49,7 +49,7 @@ class DOICallerTest(TestCase): correct_data = { 'pub_date': '2017-04-04', 'journal': 'SciPost Physics', - 'pub_title': 'One-particle density matrix of trapped one-dimensional impenetrable bosons from conformal invariance', + 'title': 'One-particle density matrix of trapped one-dimensional impenetrable bosons from conformal invariance', 'pages': '', 'volume': '2', 'author_list': ['Yannis Brun', 'Jerome Dubail'] diff --git a/scipost/utils.py b/scipost/utils.py index 3af0f72d92b19ed5e9ebedee46971ff3d681ffeb..7fd4653f98d60cb3e99ff7f2ed383e7caf283d97 100644 --- a/scipost/utils.py +++ b/scipost/utils.py @@ -403,13 +403,13 @@ class Utils(BaseMailUtil): 'Contributor to the site.') email_text_html += ( '<p>Your work has been cited in a paper published by SciPost,</p>' - '<p>{{ pub_title }}</p> <p>by {{ pub_author_list }}</p>' + '<p>{{ title }}</p> <p>by {{ pub_author_list }}</p>' '(published as <a href="https://scipost.org/{{ doi_label }}">{{ citation }}</a>).' '</p>' '\n<p>I would hereby like to use this opportunity to quickly introduce ' 'you to the SciPost initiative, and to invite you to become an active ' 'Contributor to the site.</p>') - email_context['pub_title'] = cls.invitation.cited_in_publication.title + email_context['title'] = cls.invitation.cited_in_publication.title email_context['pub_author_list'] = cls.invitation.cited_in_publication.author_list email_context['doi_label'] = cls.invitation.cited_in_publication.doi_label email_context['citation'] = cls.invitation.cited_in_publication.citation() @@ -640,7 +640,7 @@ class Utils(BaseMailUtil): email_text_html += ( '<p>We would like to notify you that ' 'your work has been cited in a paper published by SciPost,</p>' - '<p>{{ pub_title }}</p><p>by {{ pub_author_list }}</p>' + '<p>{{ title }}</p><p>by {{ pub_author_list }}</p>' '<p>(published as <a href="https://scipost.org/{{ doi_label }}">' '{{ citation }}</a>).</p>' '<p>We hope you will find this paper of interest to your own research.</p>' @@ -648,7 +648,7 @@ class Utils(BaseMailUtil): + EMAIL_FOOTER + '<br/>' '\n<p style="font-size: 10px;">Don\'t want to receive such emails? ' '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe) - email_context['pub_title'] = cls.notification.cited_in_publication.title + email_context['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_label email_context['citation'] = cls.notification.cited_in_publication.citation() diff --git a/scipost/views.py b/scipost/views.py index cbaf80452b461c83839133e8c7707353f93fb18f..3d104c216431e10c62d5b766c6b209b6dad9c353 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -20,6 +20,7 @@ from django.views.generic.list import ListView from django.db.models import Prefetch from guardian.decorators import permission_required +from guardian.shortcuts import assign_perm, get_objects_for_user from .constants import SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, SciPost_from_addresses_dict from .decorators import has_contributor @@ -89,7 +90,7 @@ def documentsSearchResults(query): NEEDS UPDATING with e.g. Haystack. """ publication_query = get_query(query, ['title', 'author_list', 'abstract', 'doi_label']) - commentary_query = get_query(query, ['pub_title', 'author_list', 'pub_abstract']) + commentary_query = get_query(query, ['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']) @@ -435,6 +436,9 @@ def draft_registration_invitation(request): invitation = draft_inv_form.save(commit=False) invitation.drafted_by = request.user.contributor invitation.save() + + # Assign permission to 'drafter' to edit the draft afterwards + assign_perm('comments.change_draftinvitation', request.user, invitation) messages.success(request, 'Draft invitation saved.') return redirect(reverse('scipost:draft_registration_invitation')) @@ -478,9 +482,11 @@ def draft_registration_invitation(request): return render(request, 'scipost/draft_registration_invitation.html', context) -@permission_required('scipost.can_manage_registration_invitations', return_403=True) +@login_required def edit_draft_reg_inv(request, draft_id): - draft = get_object_or_404(DraftInvitation, id=draft_id) + draft = get_object_or_404((get_objects_for_user(request.user, 'scipost.change_draftinvitation') + .filter(processed=False)), + id=draft_id) draft_inv_form = DraftInvitationForm(request.POST or None, current_user=request.user, instance=draft) @@ -822,7 +828,7 @@ def personal_page(request): if contributor.is_VE(): nr_commentary_page_requests_to_vet = (Commentary.objects.awaiting_vetting() .exclude(requested_by=contributor).count()) - nr_comments_to_vet = Comment.objects.filter(status=0).count() + nr_comments_to_vet = Comment.objects.awaiting_vetting().count() nr_thesislink_requests_to_vet = ThesisLink.objects.filter(vetted=False).count() nr_authorship_claims_to_vet = AuthorshipClaim.objects.filter(status='0').count() @@ -839,9 +845,7 @@ def personal_page(request): own_submissions = (Submission.objects .filter(authors__in=[contributor], is_current=True) .order_by('-submission_date')) - own_commentaries = (Commentary.objects - .filter(authors__in=[contributor]) - .order_by('-latest_activity')) + own_commentaries = Commentary.objects.filter(authors=contributor).order_by('-latest_activity') own_thesislinks = ThesisLink.objects.filter(author_as_cont__in=[contributor]) nr_submission_authorships_to_claim = (Submission.objects.filter( author_list__contains=contributor.user.last_name) @@ -861,11 +865,10 @@ def personal_page(request): .exclude(author_claims__in=[contributor]) .exclude(author_false_claims__in=[contributor]) .count()) - own_comments = (Comment.objects.select_related('author', 'submission') - .filter(author=contributor, is_author_reply=False) + own_comments = (Comment.objects.filter(author=contributor, is_author_reply=False) + .select_related('author', 'submission') .order_by('-date_submitted')) - own_authorreplies = (Comment.objects - .filter(author=contributor, is_author_reply=True) + own_authorreplies = (Comment.objects.filter(author=contributor, is_author_reply=True) .order_by('-date_submitted')) appellation = contributor.get_title_display() + ' ' + contributor.user.last_name diff --git a/submissions/admin.py b/submissions/admin.py index 1095426dbbb30269de89a0db9bb3979aea60546a..2d88cdc52264761d1405028e361c86c7ec52982f 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -1,14 +1,19 @@ from django.contrib import admin +from django.contrib.contenttypes.admin import GenericTabularInline from django import forms from guardian.admin import GuardedModelAdmin +from comments.models import Comment from submissions.models import Submission, EditorialAssignment, RefereeInvitation, Report,\ EditorialCommunication, EICRecommendation, SubmissionEvent - from scipost.models import Contributor +class CommentsGenericInline(GenericTabularInline): + model = Comment + + def submission_short_title(obj): return obj.submission.title[:30] @@ -33,8 +38,10 @@ class SubmissionAdmin(GuardedModelAdmin): search_fields = ['submitted_by__user__last_name', 'title', 'author_list', 'abstract'] list_display = ('title', 'author_list', 'status', 'submission_date', 'publication',) date_hierarchy = 'submission_date' - list_filter = ('status', 'discipline', 'submission_type', ) + list_filter = ('status', 'discipline', 'submission_type',) form = SubmissionAdminForm + # inlines = (CommentsGenericInline,) + exclude = ('comments', 'comments_old') admin.site.register(Submission, SubmissionAdmin) diff --git a/submissions/managers.py b/submissions/managers.py index cba54e4f69b167a34a4a550b2b3a34339991fc49..232c7ed9da7949148e0775276619a0378a089b64 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): @@ -148,7 +151,7 @@ class EICRecommendationManager(models.Manager): return self.none() -class ReportManager(models.Manager): +class ReportQuerySet(models.QuerySet): def accepted(self): return self.filter(status=STATUS_VETTED) diff --git a/submissions/migrations/0061_auto_20170727_1012.py b/submissions/migrations/0061_auto_20170727_1012.py new file mode 100644 index 0000000000000000000000000000000000000000..af6a59d02f96680652df95d98b9354de9c0fd5fd --- /dev/null +++ b/submissions/migrations/0061_auto_20170727_1012.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-27 08:12 +from __future__ import unicode_literals + +from django.db import migrations + +from guardian.shortcuts import assign_perm + +from ..models import Report + + +def do_nothing(apps, schema_editor): + return + + +def update_eic_permissions(apps, schema_editor): + """ + Grant EIC of submission related to unvetted Reports + permission to vet his submission's Report. + """ + # Report = apps.get_model('submissions', 'Report') -- This doesn't work due to shitty imports + count = 0 + for rep in Report.objects.filter(status='unvetted'): + eic_user = rep.submission.editor_in_charge + assign_perm('submissions.can_vet_submitted_reports', eic_user.user, rep) + count += 1 + print('\nGranted permission to %i Editor(s)-in-charge to vet related Reports.' % count) + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0060_merge_20170726_0945'), + ] + + operations = [ + migrations.RunPython(update_eic_permissions, do_nothing), + ] diff --git a/submissions/migrations/0062_auto_20170727_1032.py b/submissions/migrations/0062_auto_20170727_1032.py new file mode 100644 index 0000000000000000000000000000000000000000..c2f7ac53fdc2513e16c8322d11200e03bfd8a062 --- /dev/null +++ b/submissions/migrations/0062_auto_20170727_1032.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-27 08:32 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0061_auto_20170727_1012'), + ] + + operations = [ + migrations.AlterModelOptions( + name='report', + options={'ordering': ['-date_submitted'], 'permissions': (('can_vet_submitted_reports', 'Can vet submitted Reports'),)}, + ), + ] diff --git a/submissions/models.py b/submissions/models.py index 790619f7d3891d2ee7a4f93118c8f52bd5a30c2b..cd0056ce17b3f73fc836d7f01c704745a6c9e33d 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -1,8 +1,10 @@ import datetime +from django.contrib.postgres.fields import JSONField +from django.contrib.contenttypes.fields import GenericRelation from django.utils import timezone from django.db import models -from django.contrib.postgres.fields import JSONField +from django.db.models import Q from django.urls import reverse from django.utils.functional import cached_property @@ -13,10 +15,11 @@ from .constants import ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL,\ SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC,\ EVENT_GENERAL, EVENT_TYPES, EVENT_FOR_AUTHOR, EVENT_FOR_EIC from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager,\ - ReportManager, SubmissionEventQuerySet + ReportQuerySet, SubmissionEventQuerySet from .utils import ShortSubmissionCycle, DirectRecommendationSubmissionCycle,\ GeneralSubmissionCycle +from comments.models import Comment from scipost.behaviors import TimeStampedModel from scipost.constants import TITLE_CHOICES from scipost.fields import ChoiceArrayField @@ -49,6 +52,7 @@ class Submission(models.Model): secondary_areas = ChoiceArrayField( 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=STATUS_UNASSIGNED) refereeing_cycle = models.CharField(max_length=30, choices=SUBMISSION_CYCLES, @@ -58,6 +62,7 @@ class Submission(models.Model): submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE, blank=True, null=True, default=None) 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") @@ -71,6 +76,9 @@ class Submission(models.Model): related_name='authors_sub_false_claims') abstract = models.TextField() + # Comments can be added to a Submission + comments = GenericRelation('comments.Comment', related_query_name='submissions') + # Arxiv identifiers with/without version number arxiv_identifier_w_vn_nr = models.CharField(max_length=15, default='0000.00000v0') arxiv_identifier_wo_vn_nr = models.CharField(max_length=10, default='0000.00000') @@ -91,7 +99,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) @@ -115,6 +123,16 @@ class Submission(models.Model): pass return header + def comments_set_complete(self): + """ + Return comments to Submission, comments on Reports of Submission and + nested comments related to this Submission. + """ + return Comment.objects.filter(Q(submissions=self) | + Q(reports__submission=self) | + Q(comments__reports__submission=self) | + Q(comments__submissions=self)).distinct() + def _update_cycle(self): """ Append the specific submission cycle to the instance to eventually handle the @@ -322,6 +340,9 @@ class Report(models.Model): report = models.TextField() requested_changes = models.TextField(verbose_name="requested changes", blank=True) + # Comments can be added to a Submission + comments = GenericRelation('comments.Comment', related_query_name='reports') + # Qualities: validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, null=True, blank=True) @@ -343,17 +364,26 @@ class Report(models.Model): anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') pdf_report = models.FileField(upload_to='UPLOADS/REPORTS/%Y/%m/', max_length=200, blank=True) - objects = ReportManager() + objects = ReportQuerySet.as_manager() class Meta: unique_together = ('submission', 'report_nr') default_related_name = 'reports' ordering = ['-date_submitted'] + permissions = ( + ('can_vet_submitted_reports', 'Can vet submitted Reports'), + ) 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]) + def save(self, *args, **kwargs): + # Control Report count per Submission. + if not self.report_nr: + self.report_nr = self.submission.reports.count() + 1 + return super().save(*args, **kwargs) + def get_absolute_url(self): return self.submission.get_absolute_url() + '#report_' + str(self.report_nr) @@ -362,11 +392,13 @@ class Report(models.Model): if self.doi_label: return '10.21468/' + self.doi_label - def save(self, *args, **kwargs): - # Control Report count per Submission. - if not self.report_nr: - self.report_nr = self.submission.reports.count() + 1 - return super().save(*args, **kwargs) + @cached_property + def title(self): + """ + This property is (mainly) used to let Comments get the title of the Submission without + annoying logic. + """ + return self.submission.title @cached_property def is_followup_report(self): diff --git a/submissions/templates/submissions/_refereeing_pack_tex_template.html b/submissions/templates/submissions/_refereeing_pack_tex_template.html index 536202975620088d90e191870ed6d99f45f55df6..707246a319c37864228ca28e761e34e2c3ee3687 100644 --- a/submissions/templates/submissions/_refereeing_pack_tex_template.html +++ b/submissions/templates/submissions/_refereeing_pack_tex_template.html @@ -169,12 +169,12 @@ Submitted to {{submission.get_submitted_to_journal_display}} {{report.requested_changes|linebreaktex}} - {% if report.comment_set.vetted %} + {% if report.comments.vetted %} \newpage \pagestyle{SPstyle} \section*{Comments and Author Replies to this Report} - {% for comment in report.comment_set.vetted %} + {% for comment in report.comments.vetted %} \addcontentsline{toc}{subsection}{\protect\numberline{}{% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} to Report by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } \subsection*{ {% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} to Report by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } diff --git a/submissions/templates/submissions/_single_public_report.html b/submissions/templates/submissions/_single_public_report.html index cc4b233b4540fd5b969181a41377ac181d727a70..efdc5c5144fb7f7dde0959338e2d3c9b751582b4 100644 --- a/submissions/templates/submissions/_single_public_report.html +++ b/submissions/templates/submissions/_single_public_report.html @@ -6,7 +6,7 @@ <h3><a href="{% url 'comments:reply_to_report' report_id=report.id %}">Reply to this Report</a> (authors only)</h3> {% endif %} - {% for reply in report.comment_set.vetted %} + {% for reply in report.comments.vetted %} {% include 'comments/_single_comment_with_link.html' with comment=reply perms=perms user=user %} {% endfor %} {% endblock %} diff --git a/submissions/templates/submissions/_single_public_report_without_comments.html b/submissions/templates/submissions/_single_public_report_without_comments.html index 2c5c69adc82528e3cc09108b9c08eadff738020b..289f2169d1d34bc4bd64517f86e2dea2d29ff11e 100644 --- a/submissions/templates/submissions/_single_public_report_without_comments.html +++ b/submissions/templates/submissions/_single_public_report_without_comments.html @@ -7,14 +7,14 @@ {% if user.contributor == submission.editor_in_charge or user|is_in_group:'Editorial Administrators' and user|is_not_author_of_submission:submission.arxiv_identifier_w_vn_nr %} <div class="reportid"> - <h3>{% if report.anonymous %}(chose public anonymity) {% endif %}<a href="{% url 'scipost:contributor_info' report.author.id %}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> + <h3>{% if report.anonymous %}(chose public anonymity) {% endif %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> on {{ report.date_submitted|date:'Y-n-j' }}</h3> </h3> {% if report.pdf_report %} <a href="{% url 'submissions:report_detail_pdf' report.submission.arxiv_identifier_w_vn_nr report.report_nr %}" target="_blank">Download as PDF</a> {% endif %} {% if perms.scipost.can_manage_reports %} - {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}"{% if not report.pdf_report %}class="btn btn-warning btn-sm"{% endif %}>Update/Compile the Report pdf</a> + {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}">Update/Compile the Report pdf</a> {% endif %} </div> @@ -46,14 +46,14 @@ </div> {% else %} <div class="reportid"> - <h3>{% if report.anonymous %}Anonymous Report {{report.report_nr}}{% else %}<a href="{% url 'scipost:contributor_info' report.author.id %}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %} + <h3>{% if report.anonymous %}Anonymous Report {{report.report_nr}}{% else %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %} on {{ report.date_submitted|date:'Y-n-j' }}</h3> </h3> {% if report.pdf_report %} <a href="{% url 'submissions:report_detail_pdf' report.submission.arxiv_identifier_w_vn_nr report.report_nr %}" target="_blank">Download as PDF</a> {% endif %} {% if perms.scipost.can_manage_reports %} - {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}"{% if not report.pdf_report %}class="btn btn-warning btn-sm"{% endif %}>Update/Compile the Report pdf</a> + {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}">Update/Compile the Report pdf</a> {% endif %} </div> diff --git a/submissions/templates/submissions/editorial_page.html b/submissions/templates/submissions/editorial_page.html index 069d183b679e7f09def5b4bb8cf87e95253f314b..65a024d1e8dcdf5ecf47910a1fd5f345f84e2845 100644 --- a/submissions/templates/submissions/editorial_page.html +++ b/submissions/templates/submissions/editorial_page.html @@ -170,7 +170,36 @@ </div> </form> </li> - <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ submission.reports.awaiting_vetting.count }})</li> + {% with submission.reports.awaiting_vetting as reports %} + {% if reports %} + <li> + Vet submitted Report{{reports|pluralize}}: + <ul class="mb-1"> + {% for report in reports %} + <li><a href="{% url 'submissions:vet_submitted_report' report.id %}">{{report}}</a></li> + {% endfor %} + </ul> + </li> + {% else %} + <li>All Reports have been vetted.</li> + {% endif %} + {% endwith %} + + {% with submission.comments_set_complete.awaiting_vetting as comments %} + {% if comments %} + <li> + Vet submitted Comment{{comments|pluralize}}: + <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/templates/submissions/latest_events.html b/submissions/templates/submissions/latest_events.html new file mode 100644 index 0000000000000000000000000000000000000000..3025ec20dc1effe510f5e3693f80f6ec18700a20 --- /dev/null +++ b/submissions/templates/submissions/latest_events.html @@ -0,0 +1,26 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: Latest events{% endblock pagetitle %} + +{% load scipost_extras %} +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="javascript:;" class="breadcrumb-item">Admin</a> + <span class="breadcrumb-item">All events in the last 24 hours</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1>All events in the last 24 hours</h1> + <h2 class="text-warning">Submission Events are under construction. Please note this list may not be complete!</h2> + <div class="ml-md-5 mt-5">{% include 'submissions/submission_event_list_general.html' with events=events %}</div> + </div> +</div> + + + +{% endblock content %} diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index 4e6188df9338e987679e3648cd003496bd609181..383dfd162c3a4a5880e430d525cb63fa764ce7ce 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -6,27 +6,12 @@ {% load scipost_extras %} {% load submissions_extras %} -{% load filename %} -{% load file_extentions %} <script> $(document).ready(function(){ - $("#invitedreportsbutton").click(function(){ - $("#invitedreportslist").toggle(); - }); - - var comment_text_input = $("#id_comment_text"); - - function set_comment_text(value) { - $("#preview-comment_text").text(value) - } - set_comment_text(comment_text_input.val()) - - comment_text_input.keyup(function(){ - var new_text = $(this).val() - set_comment_text(new_text) - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - }) + $("#invitedreportsbutton").click(function(){ + $("#invitedreportslist").toggle(); + }); }); </script> @@ -201,7 +186,7 @@ {% endif %} -{% include 'scipost/comments_block.html' %} +{% include 'scipost/comments_block.html' with comments=submission.comments.vetted %} {% include 'comments/new_comment.html' with object_id=submission.id type_of_object='submission' open_for_commenting=submission.open_for_commenting %} diff --git a/submissions/templates/submissions/submission_event_list_general.html b/submissions/templates/submissions/submission_event_list_general.html new file mode 100644 index 0000000000000000000000000000000000000000..a9d0b0d84d32f1fbf8845d4150b2e441d230c82a --- /dev/null +++ b/submissions/templates/submissions/submission_event_list_general.html @@ -0,0 +1,17 @@ +<div class="row"> + <div class="col-12"> + <ul class="list-group list-group-flush events-list"> + {% for event in events %} + <li class="list-group-item"> + <div> + <strong>{{event.text}}</strong><br> + Submission: <a href="{{event.submission.get_absolute_url}}">{{event.submission.title}} ({{event.submission.arxiv_identifier_w_vn_nr}})</a><br> + <span class="text-muted">{{event.created}}</span> + </div> + </li> + {% empty %} + <li class="list-group-item">No events found.</li> + {% endfor %} + </ul> + </div> +</div> diff --git a/submissions/templates/submissions/vet_submitted_reports.html b/submissions/templates/submissions/vet_submitted_report.html similarity index 92% rename from submissions/templates/submissions/vet_submitted_reports.html rename to submissions/templates/submissions/vet_submitted_report.html index e37d0cc284bed3faf6bcb5130561ecb3dea0b4ac..64d36c03c9b7da45bd3285c4f4383d5bf3334f7f 100644 --- a/submissions/templates/submissions/vet_submitted_reports.html +++ b/submissions/templates/submissions/vet_submitted_report.html @@ -23,8 +23,7 @@ $(document).ready(function(){ {% block breadcrumb_items %} {{block.super}} - <a href="{% url 'submissions:pool' %}" class="breadcrumb-item">Pool</a> - <span class="breadcrumb-item">Vet Reports</span> + <span class="breadcrumb-item">Vet Report {{report.report_nr}}</span> {% endblock %} {% block content %} @@ -50,7 +49,7 @@ $(document).ready(function(){ <hr class="small"> <h2>Please vet this Report:</h2> - <form action="{% url 'submissions:vet_submitted_reports' %}" method="post"> + <form action="{% url 'submissions:vet_submitted_report' report_to_vet.id %}" method="post"> {% csrf_token %} {{ form.report }} {{ form.action_option|bootstrap }} diff --git a/submissions/templates/submissions/vet_submitted_reports_list.html b/submissions/templates/submissions/vet_submitted_reports_list.html new file mode 100644 index 0000000000000000000000000000000000000000..44697fdc21432a4a36b1278ec934c860a152b40f --- /dev/null +++ b/submissions/templates/submissions/vet_submitted_reports_list.html @@ -0,0 +1,40 @@ +{% extends 'submissions/_pool_base.html' %} + +{% block pagetitle %}: vet reports{% endblock pagetitle %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Vet Reports</span> +{% endblock %} + +{% block content %} + +<h1>SciPost Reports to vet</h1> + +{% if reports_to_vet %} + <table class="table mt-4"> + <thead> + <th>Report #</th> + <th>For submission</th> + <th>Submitted</th> + <th>Actions</th> + </thead> + <tbody> + {% for report in reports_to_vet %} + <tr> + <td>{{report.report_nr}}</td> + <td>{{report.submission.title}} ({{report.submission.arxiv_identifier_w_vn_nr}})</td> + <td>{{report.date_submitted|timesince}} ago</td> + <td><a href="{% url 'submissions:vet_submitted_report' report.id %}">Go to vetting page</a></td> + </tr> + {% endfor %} + </tbody> + </table> +{% else %} + <h2 class="mt-3">There are no Reports for you to vet.</h2> + <p>Go back to my <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> +{% endif %} + +<div class="mb-5"></div> + +{% endblock content %} diff --git a/submissions/urls.py b/submissions/urls.py index c4d8d4fc656accf12e14509b8e9386d804caa865..8aaf200ee76128fc26c635f54bed0c6b6e93402e 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -22,9 +22,16 @@ urlpatterns = [ views.report_detail_pdf, name='report_detail_pdf'), url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/pdf$', views.submission_refereeing_package_pdf, name='refereeing_package_pdf'), - url(r'^treated_submissions$', views.treated_submissions_list, name='treated_submissions_list'), - url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/compile$', + + # Editorial Administration + url(r'^admin/treated$', views.treated_submissions_list, name='treated_submissions_list'), + url(r'^admin/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/compile$', views.treated_submission_pdf_compile, name='treated_submission_pdf_compile'), + url(r'^admin/events/latest$', views.latest_events, name='latest_events'), + url(r'^admin/reports$', views.reports_accepted_list, name='reports_accepted_list'), + url(r'^admin/reports/(?P<report_id>[0-9]+)/compile$', + views.report_pdf_compile, name='report_pdf_compile'), + url(r'^submit_manuscript$', views.RequestSubmission.as_view(), name='submit_manuscript'), url(r'^submit_manuscript/prefill$', views.prefill_using_arxiv_identifier, name='prefill_using_identifier'), @@ -80,13 +87,13 @@ urlpatterns = [ 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'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/submit$', views.submit_report, name='submit_report'), - url(r'^reports/vet_submitted$', views.vet_submitted_reports, name='vet_submitted_reports'), - url(r'^reports/list$', views.reports_accepted_list, name='reports_accepted_list'), - url(r'^reports/(?P<report_id>[0-9]+)/compile$', - views.report_pdf_compile, name='report_pdf_compile'), + url(r'^reports/vet$', views.vet_submitted_reports_list, name='vet_submitted_reports_list'), + url(r'^reports/(?P<report_id>[0-9]+)/vet$', views.vet_submitted_report, + name='vet_submitted_report'), # Voting url(r'^prepare_for_voting/(?P<rec_id>[0-9]+)$', views.prepare_for_voting, name='prepare_for_voting'), 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/submissions/views.py b/submissions/views.py index 74f2761688df8e042d0d1594a158dd6060cc2316..8d5955b8d97c259bce6283ae745a10da1cbf6579 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -15,13 +15,13 @@ from django.views.generic.edit import CreateView from django.views.generic.list import ListView from guardian.decorators import permission_required_or_403 -from guardian.shortcuts import assign_perm +from guardian.shortcuts import assign_perm, get_objects_for_user from .constants import SUBMISSION_STATUS_VOTING_DEPRECATED, STATUS_VETTED, STATUS_EIC_ASSIGNED,\ SUBMISSION_STATUS_PUBLICLY_INVISIBLE, SUBMISSION_STATUS, ED_COMM_CHOICES,\ STATUS_DRAFT from .models import Submission, EICRecommendation, EditorialAssignment,\ - RefereeInvitation, Report, EditorialCommunication + RefereeInvitation, Report, EditorialCommunication, SubmissionEvent from .forms import SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm,\ RecommendationVoteForm, ConsiderAssignmentForm, AssignSubmissionForm,\ SetRefereeingDeadlineForm, RefereeSelectForm, RefereeRecruitmentForm,\ @@ -302,6 +302,16 @@ def treated_submission_pdf_compile(request, arxiv_identifier_w_vn_nr): return render(request, 'submissions/treated_submission_pdf_compile.html', context) +@permission_required('scipost.can_read_all_eic_events', raise_exception=True) +def latest_events(request): + events = (SubmissionEvent.objects.for_eic() + .filter(created__gte=timezone.now() - datetime.timedelta(hours=24))) + context = { + 'events': events + } + return render(request, 'submissions/latest_events.html', context) + + ###################### # Editorial workflow # ###################### @@ -855,7 +865,14 @@ def decline_ref_invitation(request, invitation_key): accepted__isnull=True) form = ConsiderRefereeInvitationForm(request.POST or None, initial={'accept': False}) + context = {'invitation': invitation, 'form': form} if form.is_valid(): + if form.cleaned_data['accept'] == 'True': + # User filled in: Accept + messages.warning(request, 'Please login and go to your personal page if you' + ' want to accept the invitation.') + return render(request, 'submissions/decline_ref_invitation.html', context) + invitation.accepted = False invitation.refusal_reason = form.cleaned_data['refusal_reason'] invitation.save() @@ -870,7 +887,6 @@ def decline_ref_invitation(request, invitation_key): messages.success(request, 'Thank you for informing us that you will not provide a Report.') return redirect(reverse('scipost:index')) - context = {'invitation': invitation, 'form': form} return render(request, 'submissions/decline_ref_invitation.html', context) @@ -1167,6 +1183,10 @@ def submit_report(request, arxiv_identifier_w_vn_nr): SubmissionUtils.email_EIC_report_delivered() SubmissionUtils.email_referee_report_delivered() + # Assign explicit permission to EIC to vet this report + assign_perm('submissions.can_vet_submitted_reports', submission.editor_in_charge.user, + newreport) + # Add SubmissionEvents for the EIC only, as it can also be rejected still submission.add_event_for_eic('%s has submitted a new Report.' % request.user.last_name) @@ -1179,22 +1199,33 @@ def submit_report(request, arxiv_identifier_w_vn_nr): @login_required -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) -def vet_submitted_reports(request): +@permission_required('submissions.can_vet_submitted_reports', raise_exception=True) +def vet_submitted_reports_list(request): """ - Reports with status `unvetted` will be shown one-by-one (oldest first). A user may only - vet reports of submissions he/she is EIC of. + Reports with status `unvetted` will be shown (oldest first). + """ + reports_to_vet = Report.objects.awaiting_vetting().order_by('date_submitted') + context = {'reports_to_vet': reports_to_vet} + return render(request, 'submissions/vet_submitted_reports_list.html', context) + + +@login_required +@transaction.atomic +def vet_submitted_report(request, report_id): + """ + Report with status `unvetted` will be shown. A user may only vet reports of submissions + he/she is EIC of or if he/she is SciPost Admin or Vetting Editor. After vetting an email is sent to the report author, bcc EIC. If report has not been refused, the submission author is also mailed. """ - contributor = request.user.contributor - report_to_vet = (Report.objects.awaiting_vetting() - .select_related('submission') - .filter(submission__editor_in_charge=contributor) - .order_by('date_submitted').first()) + # Method `get_objects_for_user` gets all Reports that are assigned to the user + # or *all* Reports if user is SciPost Admin or Vetting Editor. + report = get_object_or_404((get_objects_for_user(request.user, + 'submissions.can_vet_submitted_reports') + .awaiting_vetting()), id=report_id) - form = VetReportForm(request.POST or None, initial={'report': report_to_vet}) + form = VetReportForm(request.POST or None, initial={'report': report}) if form.is_valid(): report = form.process_vetting(request.user.contributor) @@ -1214,15 +1245,17 @@ def vet_submitted_reports(request): report.submission.add_event_for_author('A new Report has been submitted.') message = 'Submitted Report vetted for <a href="%s">%s</a>.' % ( - reverse('submissions:editorial_page', - args=(report.submission.arxiv_identifier_w_vn_nr,)), - report.submission.arxiv_identifier_w_vn_nr - ) + report.submission.get_absolute_url(), + report.submission.arxiv_identifier_w_vn_nr) messages.success(request, message) - # Redirect instead of render to loose the POST call and make it a GET - return redirect(reverse('submissions:vet_submitted_reports')) - context = {'contributor': contributor, 'report_to_vet': report_to_vet, 'form': form} - return render(request, 'submissions/vet_submitted_reports.html', context) + + if report.submission.editor_in_charge == request.user.contributor: + # Redirect a EIC back to the Editorial Page! + return redirect(reverse('submissions:editorial_page', + args=(report.submission.arxiv_identifier_w_vn_nr,))) + return redirect(reverse('submissions:vet_submitted_reports_list')) + context = {'report_to_vet': report, 'form': form} + return render(request, 'submissions/vet_submitted_report.html', context) @permission_required('scipost.can_prepare_recommendations_for_voting', raise_exception=True) diff --git a/templates/email/comment_vet_accepted.html b/templates/email/comment_vet_accepted.html new file mode 100644 index 0000000000000000000000000000000000000000..60908ef568720835558a91280c070fc2a48b7a0f --- /dev/null +++ b/templates/email/comment_vet_accepted.html @@ -0,0 +1,23 @@ +<p>Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}},</p> + +<p> + The Comment you have submitted, concerning publication with title + + {{comment.core_content_object.title}} by {% if comment.core_content_object.author_list %}{{comment.core_content_object.author_list}}{% elif comment.core_content_object.author %}{{comment.core_content_object.author}}{% endif %} (<a href="https://scipost.org{{comment.get_absolute_url}}">see on SciPost.org</a>) + 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..c717f5fe32e03c5c4c1724f5590c7bc6b2a02a0e --- /dev/null +++ b/templates/email/comment_vet_accepted.txt @@ -0,0 +1,16 @@ +Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}} +\n\n + +The Comment you have submitted, concerning publication with title + +{{comment.core_content_object.title}} by {% if comment.core_content_object.author_list %}{{comment.core_content_object.author_list}}{% elif comment.core_content_object.author %}{{comment.core_content_object.author}}{% endif %} at https://scipost.org{{comment.get_absolute_url}} + +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..678771fb32954fdfb86ebf20402bad97f421ef3c --- /dev/null +++ b/templates/email/comment_vet_rejected.html @@ -0,0 +1,28 @@ +<p>Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}},</p> + +<p> + The Comment you have submitted, concerning publication with title + + {{comment.core_content_object.title}} by {% if comment.core_content_object.author_list %}{{comment.core_content_object.author_list}}{% elif comment.core_content_object.author %}{{comment.core_content_object.author}}{% endif %} (<a href="https://scipost.org{{comment.get_absolute_url}}">see on SciPost.org</a>) + 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..c489d65acf3725c63f1542cfa0e2288a80f0ac22 --- /dev/null +++ b/templates/email/comment_vet_rejected.txt @@ -0,0 +1,22 @@ +Dear {{comment.author.get_title_display}} {{comment.author.user.last_name}} +\n\n + +The Comment you have submitted, concerning publication with title + +{{comment.core_content_object.title}} by {% if comment.core_content_object.author_list %}{{comment.core_content_object.author_list}}{% elif comment.core_content_object.author %}{{comment.core_content_object.author}}{% endif %} at https://scipost.org{{comment.get_absolute_url}} + +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) diff --git a/theses/models.py b/theses/models.py index 40ce6e673a79eeeba2fd06b9cd63b0c163b9f8dc..e15ebe73301fc9b7b5282a5576ba81a7b515ee15 100644 --- a/theses/models.py +++ b/theses/models.py @@ -1,10 +1,10 @@ from django.db import models +from django.contrib.contenttypes.fields import GenericRelation from django.urls import reverse from django.utils import timezone from journals.constants import SCIPOST_JOURNALS_DOMAINS from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS -from scipost.models import Contributor from .constants import THESIS_TYPES from .managers import ThesisLinkManager @@ -13,12 +13,12 @@ from .managers import ThesisLinkManager class ThesisLink(models.Model): """ An URL pointing to a thesis """ requested_by = models.ForeignKey( - Contributor, blank=True, null=True, + 'scipost.Contributor', blank=True, null=True, related_name='thesislink_requested_by', on_delete=models.CASCADE) vetted = models.BooleanField(default=False) vetted_by = models.ForeignKey( - Contributor, blank=True, null=True, + 'scipost.Contributor', blank=True, null=True, on_delete=models.CASCADE) type = models.CharField(choices=THESIS_TYPES, max_length=3) discipline = models.CharField( @@ -36,17 +36,17 @@ class ThesisLink(models.Model): pub_link = models.URLField(verbose_name='URL (external repository)') author = models.CharField(max_length=1000) author_as_cont = models.ManyToManyField( - Contributor, blank=True, + 'scipost.Contributor', blank=True, related_name='author_cont') author_claims = models.ManyToManyField( - Contributor, blank=True, + 'scipost.Contributor', blank=True, related_name='authors_thesis_claims') author_false_claims = models.ManyToManyField( - Contributor, blank=True, + 'scipost.Contributor', blank=True, related_name='authors_thesis_false_claims') supervisor = models.CharField(max_length=1000, default='') supervisor_as_cont = models.ManyToManyField( - Contributor, blank=True, + 'scipost.Contributor', blank=True, verbose_name='supervisor(s)', related_name='supervisor_cont') institution = models.CharField( @@ -56,6 +56,9 @@ class ThesisLink(models.Model): abstract = models.TextField(verbose_name='abstract, outline or summary') latest_activity = models.DateTimeField(default=timezone.now) + # Comments can be added to a ThesisLink + comments = GenericRelation('comments.Comment', related_query_name='theses') + objects = ThesisLinkManager() def __str__(self): diff --git a/theses/templates/theses/_thesislink_card_content.html b/theses/templates/theses/_thesislink_card_content.html index 89fbde9224a364d55fb1851c9e474345a82a8d2c..f8549c9a5e6d471f7b4ec4230ef2f85d789dea98 100644 --- a/theses/templates/theses/_thesislink_card_content.html +++ b/theses/templates/theses/_thesislink_card_content.html @@ -1,5 +1,3 @@ -{% load theses_extras %} - <div class="card-block"> <h3 class="card-title"> <a href="{% url 'theses:thesis' thesislink_id=thesislink.id %}">{{ thesislink.title }}</a> diff --git a/theses/templates/theses/_thesislink_information.html b/theses/templates/theses/_thesislink_information.html index 5bc93fb0a777ad196559e29b2a749fcbf06cc879..204eb9d3fcafbc84a714d08659eb7d782e9a9d13 100644 --- a/theses/templates/theses/_thesislink_information.html +++ b/theses/templates/theses/_thesislink_information.html @@ -1,5 +1,3 @@ -{% load theses_extras %} - <table class="mb-3"> <tr> <td>Title: </td><td> </td><td>{{ thesislink.title }}</td> @@ -18,16 +16,16 @@ </td> </tr> <tr> - <td>Type: </td><td></td><td> {{ thesislink|type }}</td> + <td>Type: </td><td></td><td> {{ thesislink.get_type_display }}</td> </tr> <tr> - <td>Discipline: </td><td></td><td>{{ thesislink|discipline }}</td> + <td>Discipline: </td><td></td><td>{{ thesislink.get_discipline_display }}</td> </tr> <tr> - <td>Domain: </td><td></td><td>{{ thesislink|domain }}</td> + <td>Domain: </td><td></td><td>{{ thesislink.get_domain_display }}</td> </tr> <tr> - <td>Subject area: </td><td></td><td> {{ thesislink|subject_area }} </td> + <td>Subject area: </td><td></td><td> {{ thesislink.get_subject_area_display }} </td> </tr> <tr> <td>URL: </td><td> </td><td><a href="{{ thesislink.pub_link }}" target="_blank">{{ thesislink.pub_link }}</a></td> diff --git a/theses/templates/theses/thesis_detail.html b/theses/templates/theses/thesis_detail.html index 12a49b417caea35912379368119483f12134d5b5..7ed53d775a8bdfde69e842e8803cd6b4d6ec91a9 100644 --- a/theses/templates/theses/thesis_detail.html +++ b/theses/templates/theses/thesis_detail.html @@ -1,41 +1,12 @@ {% extends 'scipost/base.html' %} -{% block pagetitle %}: Thesis Link detail{% endblock pagetitle %} - -{% block headsup %} - {% load scipost_extras %} -<script> - $(document).ready(function(){ - - var comment_text_input = $("#id_comment_text"); - - function set_comment_text(value) { - $("#preview-comment_text").text(value) - } - set_comment_text(comment_text_input.val()) - - comment_text_input.keyup(function(){ - var new_text = $(this).val() - set_comment_text(new_text) - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - }) - - }); -</script> - -{% endblock headsup %} +{% block pagetitle %}: Thesis Link detail{% endblock pagetitle %} {% block content %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">SciPost Thesis Link</h1> - </div> -</div> - -<hr /> +<h1 class="highlight">SciPost Thesis Link</h1> <div class="row"> <div class="col-12"> @@ -43,7 +14,7 @@ </div> </div> -{% include 'scipost/comments_block.html' %} +{% include 'scipost/comments_block.html' with comments=thesislink.comments.vetted type_of_object='ThesisLink' %} {% include 'comments/new_comment.html' with object_id=thesislink.id type_of_object='thesislink' open_for_commenting=thesislink.open_for_commenting %} diff --git a/theses/templates/theses/unvetted_thesislinks.html b/theses/templates/theses/unvetted_thesislinks.html index 6846720a4d81155ee3d888a68a48187bc4df8ad8..7ac0723a3d76971d1d78370466da449ac31fd943 100644 --- a/theses/templates/theses/unvetted_thesislinks.html +++ b/theses/templates/theses/unvetted_thesislinks.html @@ -2,10 +2,6 @@ {% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %} -{% block headsup %} - -{% endblock headsup %} - {% block content %} <div class="row"> diff --git a/theses/templates/theses/vet_thesislink.html b/theses/templates/theses/vet_thesislink.html index 8822dfff11c61f69a0a0ded2b8e5ea12b0004f33..30b1bb1831560e02be8b702764931b10ddff418f 100644 --- a/theses/templates/theses/vet_thesislink.html +++ b/theses/templates/theses/vet_thesislink.html @@ -4,10 +4,6 @@ {% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %} -{% block headsup %} - -{% endblock headsup %} - {% block content %} <div class="row"> diff --git a/theses/templatetags/theses_extras.py b/theses/templatetags/theses_extras.py deleted file mode 100644 index e60c4ecf59d309bd7596b3574c08f021dc1de282..0000000000000000000000000000000000000000 --- a/theses/templatetags/theses_extras.py +++ /dev/null @@ -1,27 +0,0 @@ -from django import template - -register = template.Library() - - -@register.filter -def type(thesislink): - # deprecated method, to be removed in future - return thesislink.get_type_display() - - -@register.filter -def discipline(thesislink): - # deprecated method, to be removed in future - return thesislink.get_discipline_display() - - -@register.filter -def domain(thesislink): - # deprecated method, to be removed in future - return thesislink.get_domain_display() - - -@register.filter -def subject_area(thesislink): - # deprecated method, to be removed in future - return thesislink.get_subject_area_display() diff --git a/theses/views.py b/theses/views.py index 72856c885224b4f0c2534714e737e208ed9b8fbb..ac9558ad9650943b63c1a7f45d565e81bb6bb7de 100644 --- a/theses/views.py +++ b/theses/views.py @@ -4,7 +4,7 @@ from django.utils import timezone from django.shortcuts import get_object_or_404, render from django.contrib.auth.decorators import permission_required from django.contrib import messages -from django.core.urlresolvers import reverse, reverse_lazy +from django.core.urlresolvers import reverse_lazy from django.http import HttpResponseRedirect from django.views.generic.edit import CreateView, UpdateView from django.views.generic.list import ListView @@ -86,24 +86,24 @@ class ThesisListView(ListView): # Context is not saved to View object by default self.pre_context = self.kwargs - # Browse is discipline is given + # Browse if discipline is given if 'discipline' in self.kwargs: self.pre_context['browse'] = True # Queryset for browsing if self.kwargs.get('browse', False): - return self.model.objects.vetted().filter( - discipline=self.kwargs['discipline'], - latest_activity__gte=timezone.now() + datetime.timedelta( - weeks=-int(self.kwargs['nrweeksback'])), - ) + return (self.model.objects.vetted() + .filter(discipline=self.kwargs['discipline'], + latest_activity__gte=timezone.now() + datetime.timedelta( + weeks=-int(self.kwargs['nrweeksback']))) + .order_by('-latest_activity')) # Queryset for searchform is processed by managers form = self.form(self.request.GET) if form.is_valid() and form.has_changed(): - return self.model.objects.search_results(form) + return self.model.objects.search_results(form).order_by('-latest_activity') self.pre_context['recent'] = True - return self.model.objects.vetted() + return self.model.objects.vetted().order_by('-latest_activity') def get_context_data(self, **kwargs): # Update the context data from `get_queryset` @@ -120,10 +120,5 @@ def thesis_detail(request, thesislink_id): thesislink = get_object_or_404(ThesisLink, pk=thesislink_id) form = CommentForm() - comments = thesislink.comment_set - author_replies = comments.filter(is_author_reply=True) - - context = {'thesislink': thesislink, - 'comments': comments.vetted().order_by('date_submitted'), - 'author_replies': author_replies, 'form': form} + context = {'thesislink': thesislink, 'form': form} return render(request, 'theses/thesis_detail.html', context)