diff --git a/comments/factories.py b/comments/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..aaf4cc9698021923b66ba04444a4e612d0650240 --- /dev/null +++ b/comments/factories.py @@ -0,0 +1,14 @@ +import factory + +from django.utils import timezone + +from scipost.factories import ContributorFactory +from .models import Comment + + +class CommentFactory(factory.django.DjangoModelFactory): + class Meta: + model = Comment + + comment_text = factory.Faker('text') + date_submitted = timezone.now() diff --git a/comments/migrations/0006_auto_20170115_1750.py b/comments/migrations/0006_auto_20170115_1750.py new file mode 100644 index 0000000000000000000000000000000000000000..9937694a13ff6a56d62b3a8a7d7da23a9331107b --- /dev/null +++ b/comments/migrations/0006_auto_20170115_1750.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-01-15 16:50 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0005_merge_20161219_2126'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='comment', + name='latest_activity', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/comments/models.py b/comments/models.py index 1e6c1c5d1da23a916f201a3e664b24e001ab0d7d..0c43a91ac3751dcd52e860481d7fca09dc30f12d 100644 --- a/comments/models.py +++ b/comments/models.py @@ -8,7 +8,7 @@ from django.utils.safestring import mark_safe from .models import * from commentaries.models import Commentary -from scipost.models import Contributor +from scipost.models import TimeStampedModel, Contributor from submissions.models import Submission, Report from theses.models import ThesisLink @@ -34,7 +34,7 @@ COMMENT_STATUS = ( comment_status_dict = dict(COMMENT_STATUS) -class Comment(models.Model): +class Comment(TimeStampedModel): """ A Comment is an unsollicited note, submitted by a Contributor, on a particular publication or in reply to an earlier Comment. """ @@ -42,7 +42,7 @@ class Comment(models.Model): vetted_by = models.ForeignKey(Contributor, blank=True, null=True, on_delete=models.CASCADE, related_name='comment_vetted_by') - # a Comment is either for a Commentary or Submission + # a Comment is either for a Commentary or Submission or a ThesisLink. commentary = models.ForeignKey(Commentary, blank=True, null=True, on_delete=models.CASCADE) submission = models.ForeignKey(Submission, blank=True, null=True, on_delete=models.CASCADE) thesislink = models.ForeignKey(ThesisLink, blank=True, null=True, on_delete=models.CASCADE) diff --git a/comments/templates/comments/reply_to_comment.html b/comments/templates/comments/reply_to_comment.html index 19cdddfbe573d4e6bbf83580e3ecf70311ed9652..89f725b9a82535c7f15bb1ecd4b86219741e4d21 100644 --- a/comments/templates/comments/reply_to_comment.html +++ b/comments/templates/comments/reply_to_comment.html @@ -32,21 +32,21 @@ {% if comment.commentary %} <h1>The Commentary concerned:</h1> {{ commentary.header_as_table }} - + <h3>Abstract:</h3> <p>{{ commentary.pub_abstract }}</p> {% endif %} {% if comment.submission %} <h1>The Submission concerned:</h1> {{ submission.header_as_table }} - + <h3>Abstract:</h3> <p>{{ submission.abstract }}</p> {% endif %} {% if comment.thesislink %} <h1>The Thesis concerned:</h1> - {{ thesislink.header_as_table }} - + {% include "theses/_header_as_table.html" with thesislink=thesislink %} + <h3>Abstract:</h3> <p>{{ thesislink.abstract }}</p> {% endif %} diff --git a/common/helpers/__init__.py b/common/helpers/__init__.py index a29cff832e8239e0ffc0cf1db98d51745c4c88c8..bf886509ae9e9ca883b2d8c5591811e5d355dbc1 100644 --- a/common/helpers/__init__.py +++ b/common/helpers/__init__.py @@ -27,4 +27,5 @@ def model_form_data(model, form_class): def filter_keys(dictionary, keys_to_keep): - return {key: dictionary[key] for key in keys_to_keep} + # Field is empty if not on model. + return {key: dictionary.get(key, "") for key in keys_to_keep} diff --git a/scipost/factories.py b/scipost/factories.py index df335f90e80a06400c418c988d7a21b3a773d388..5e22d34f51ad3ca28de962b1876cc840a56f8d13 100644 --- a/scipost/factories.py +++ b/scipost/factories.py @@ -16,6 +16,14 @@ class ContributorFactory(factory.django.DjangoModelFactory): vetted_by = factory.SubFactory('scipost.factories.ContributorFactory', vetted_by=None) +class VettingEditorFactory(ContributorFactory): + @factory.post_generation + def add_to_vetting_editors(self, create, extracted, **kwargs): + if not create: + return + self.user.groups.add(Group.objects.get(name="Vetting Editors")) + + class UserFactory(factory.django.DjangoModelFactory): class Meta: model = get_user_model() diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index d2645698fd4982108e18cd393f6bad8a3bee8bed..01045558655e17da7f9d6bdd07b11819aba58d26 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -273,7 +273,7 @@ <li><a href="{% url 'comments:vet_submitted_comments' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> {% endif %} {% if perms.scipost.can_vet_thesislink_requests %} - <li><a href="{% url 'theses:vet_thesislink_requests' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li> + <li><a href="{% url 'theses:unvetted_thesislinks' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li> {% endif %} {% if perms.scipost.can_vet_authorship_claims %} <li><a href="{% url 'scipost:vet_authorship_claims' %}">Vet Authorship Claims</a> ({{ nr_authorship_claims_to_vet }})</li> diff --git a/strings/__init__.py b/strings/__init__.py index 82d957c9dde2ab9c1606aa3568f778e408e13a84..4aa362a9cb4689d7212cb18ff1284ed5cd7b6635 100644 --- a/strings/__init__.py +++ b/strings/__init__.py @@ -1 +1,5 @@ -acknowledge_request_thesis_link = "Thank you for your request for a Thesis Link. Your request will soon be handled by an editor" +acknowledge_request_thesis_link = ( + "Thank you for your request for a Thesis Link. Your request" + " will soon be handled by an editor." +) +acknowledge_vet_thesis_link = "Thesis Link request vetted." diff --git a/theses/factories.py b/theses/factories.py index bc57863b350e82579b8725bd27884bdeb1382c24..550d5a73e004b69b8e40ac52ed2dc4618e9309c9 100644 --- a/theses/factories.py +++ b/theses/factories.py @@ -18,7 +18,7 @@ class ThesisLinkFactory(factory.django.DjangoModelFactory): author = factory.Faker('name') supervisor = factory.Faker('name') institution = factory.Faker('company') - defense_date = factory.Faker('date_time_this_century') + defense_date = factory.Faker('date') abstract = factory.Faker('text') domain = 'ET' @@ -28,4 +28,3 @@ class VetThesisLinkFormFactory(FormFactory): model = VetThesisLinkForm action_option = VetThesisLinkForm.ACCEPT - # justification = factory.Faker('lorem') diff --git a/theses/forms.py b/theses/forms.py index 05963cb3221ca5cadcc553f653090d316395470b..5b8b8db4cecc499e9d8a4006b1b98face3e2d0d8 100644 --- a/theses/forms.py +++ b/theses/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.core.mail import EmailMessage from .models import * from .helpers import past_years @@ -16,7 +17,7 @@ class RequestThesisLinkForm(forms.ModelForm): } -class VetThesisLinkForm(forms.Form): +class VetThesisLinkForm(RequestThesisLinkForm): MODIFY = 0 ACCEPT = 1 REFUSE = 2 @@ -41,10 +42,62 @@ class VetThesisLinkForm(forms.Form): justification = forms.CharField(widget=forms.Textarea( attrs={'rows': 5, 'cols': 40}), label='Justification (optional)', required=False) - def vet_request(self, thesis_link): - print(self.cleaned_data) - if self.cleaned_data['action_option'] == VetThesisLinkForm.ACCEPT: - print('hoi') + def vet_request(self, thesislink, user): + if int(self.cleaned_data['action_option']) == VetThesisLinkForm.ACCEPT: + thesislink.vetted = True + thesislink.vetted_by = Contributor.objects.get(user=user) + thesislink.save() + + email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' + + thesislink.requested_by.user.last_name + + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' + + thesislink.title + ' by ' + thesislink.author + + ', has been activated at https://scipost.org/thesis/' + + str(thesislink.id) + '.' + + '\n\nThank you for your contribution, \nThe SciPost Team.') + emailmessage = EmailMessage('SciPost Thesis Link activated', email_text, + 'SciPost Theses <theses@scipost.org>', + [thesislink.requested_by.user.email], + ['theses@scipost.org'], + reply_to=['theses@scipost.org']) + emailmessage.send(fail_silently=False) + elif int(self.cleaned_data['action_option']) == VetThesisLinkForm.REFUSE: + email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' + + thesislink.requested_by.user.last_name + + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' + + thesislink.title + ' by ' + thesislink.author + + ', has not been activated for the following reason: ' + + self.cleaned_data['refusal_reason'] + + '.\n\nThank you for your interest, \nThe SciPost Team.') + if self.cleaned_data['justification']: + email_text += '\n\nFurther explanations: ' + \ + self.cleaned_data['justification'] + emailmessage = EmailMessage('SciPost Thesis Link', email_text, + 'SciPost Theses <theses@scipost.org>', + [thesislink.requested_by.user.email], + ['theses@scipost.org'], + reply_to=['theses@scipost.org']) + emailmessage.send(fail_silently=False) + thesislink.delete() + + elif int(self.cleaned_data['action_option']) == VetThesisLinkForm.MODIFY: + thesislink.vetted = True + thesislink.vetted_by = Contributor.objects.get(user=user) + thesislink.save() + email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' + + thesislink.requested_by.user.last_name + + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' + + thesislink.title + ' by ' + thesislink.author_list + + ', has been activated ' + '(with slight modifications to your submitted details) at ' + 'https://scipost.org/thesis/' + str(thesislink.id) + '.' + '\n\nThank you for your contribution, \nThe SciPost Team.') + emailmessage = EmailMessage('SciPost Thesis Link activated', email_text, + 'SciPost Theses <theses@scipost.org>', + [thesislink.requested_by.user.email], + ['theses@scipost.org'], + reply_to=['theses@scipost.org']) + emailmessage.send(fail_silently=False) class ThesisLinkSearchForm(forms.Form): diff --git a/theses/models.py b/theses/models.py index 50ec6b1f47548e9f43d6f53d31d091883f4ed091..fbd5f370d030f2fe3c7e163082cefa39a258efe8 100644 --- a/theses/models.py +++ b/theses/models.py @@ -69,45 +69,6 @@ class ThesisLink(models.Model): def __str__(self): return self.title - def header_as_table(self): - context = Context({ - 'title': self.title, 'author': self.author, - 'pub_link': self.pub_link, 'institution': self.institution, - 'supervisor': self.supervisor, 'defense_date': self.defense_date}) - header = ( - '<table>' - '<tr><td>Title: </td><td> </td><td>{{ title }}</td></tr>' - '<tr><td>Author: </td><td> </td><td>{{ author }}</td></tr>' - '<tr><td>As Contributor: </td><td> </td>') - if self.author_as_cont.all(): - for auth in self.author_as_cont.all(): - header += ( - '<td><a href="/contributor/' + str(auth.id) + '">' + - auth.user.first_name + ' ' + auth.user.last_name + - '</a></td>') - else: - header += '<td>(not claimed)</td>' - header += ( - '</tr>' - '<tr><td>Type: </td><td></td><td>' + self.THESIS_TYPES_DICT[self.type] + - '</td></tr>' - '<tr><td>Discipline: </td><td></td><td>' + - disciplines_dict[self.discipline] + '</td></tr>' - '<tr><td>Domain: </td><td></td><td>' + - journals_domains_dict[self.domain] + '</td></tr>' - '<tr><td>Subject area: </td><td></td><td>' + - subject_areas_dict[self.subject_area] + '</td></tr>' - '<tr><td>URL: </td><td> </td><td><a href="{{ pub_link }}" ' - 'target="_blank">{{ pub_link }}</a></td></tr>' - '<tr><td>Degree granting institution: </td><td> </td>' - '<td>{{ institution }}</td></tr>' - '<tr><td>Supervisor(s): </td><td></td><td>{{ supervisor }}' - '</td></tr>' '<tr><td>Defense date: </td><td> </td>' - '<td>{{ defense_date }}</td></tr>' - '</table>') - template = Template(header) - return template.render(context) - def header_as_li(self): context = Context({ 'id': self.id, 'title': self.title, 'author': self.author, diff --git a/theses/templates/theses/_header_as_table.html b/theses/templates/theses/_header_as_table.html new file mode 100644 index 0000000000000000000000000000000000000000..2a23414649882e3f50f6d2c1e7788ad254de5099 --- /dev/null +++ b/theses/templates/theses/_header_as_table.html @@ -0,0 +1,46 @@ +{% load theses_extras %} + +<table> + <tr> + <td>Title: </td><td> </td><td>{{ thesislink.title }}</td> + </tr> + <tr> + <td>Author: </td><td> </td><td>{{ thesislink.author }}</td> + </tr> + <tr> + <td>As Contributor: </td><td> </td> + {% if thesislink.author_as_cont.all %} + {% for author in thesislink.author_as_cont.all %} + <td><a href= {% url 'scipost:contributor_info' author.id %}> + author.user.first_name author.user.last_name + </a></td> + {% endfor %} + {% else %} + <td>(not claimed)</td> + {% endif %} + </tr> + <tr> + <td>Type: </td><td></td><td> {{ thesislink|type }}</td> + </tr> + <tr> + <td>Discipline: </td><td></td><td>{{ thesislink|discipline }}</td> + </tr> + <tr> + <td>Domain: </td><td></td><td>{{ thesislink|domain }}</td> + </tr> + <tr> + <td>Subject area: </td><td></td><td> {{ thesislink|subject_area }} </td> + </tr> + <tr> + <td>URL: </td><td> </td><td><a href="{{ pub_link }}" target="_blank">{{ thesislink.pub_link }}</a></td> + </tr> + <tr> + <td>Degree granting institution: </td><td> </td><td>{{ thesislink.institution }}</td> + </tr> + <tr> + <td>Supervisor(s): </td><td></td><td>{{ thesislink.supervisor }}</td> + </tr> + <tr> + <td>Defense date: </td><td> </td><td>{{ thesislink.defense_date }}</td> + </tr> +</table> diff --git a/theses/templates/theses/thesis_detail.html b/theses/templates/theses/thesis_detail.html index 541f934e1009668a521c0839c0a54fa77f9748af..45b16160beaa66b2f6f1041d0255a65c99642f2a 100644 --- a/theses/templates/theses/thesis_detail.html +++ b/theses/templates/theses/thesis_detail.html @@ -44,7 +44,8 @@ <h2>Thesis information: </h2> </div> </div> - {{ thesislink.header_as_table }} + {% include "./_header_as_table.html" with thesislink=thesislink %} + {# {{ thesislink.header_as_table }}#} <h3>Abstract:</h3> <p>{{ thesislink.abstract }}</p> diff --git a/theses/templates/theses/unvetted_thesislinks.html b/theses/templates/theses/unvetted_thesislinks.html new file mode 100644 index 0000000000000000000000000000000000000000..f2245e0a6f698670eb439883a4483b8eea19d28c --- /dev/null +++ b/theses/templates/theses/unvetted_thesislinks.html @@ -0,0 +1,26 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %} + +{% block headsup %} + +{% endblock headsup %} + +{% block bodysup %} + + +<h1>Unvetted Thesis Links</h1> +<ul> + {% for thesislink in thesislinks %} + <li> + {{ thesislink.author }} - {{ thesislink.title }} - + <a href = "{% url 'theses:vet_thesislink' pk=thesislink.id %}">vet</a> + </li> + {% empty %} + <li> + No unvetted thesis links. + </li> + {% endfor %} +</ul> + +{% endblock bodysup %} diff --git a/theses/templates/theses/vet_thesislink.html b/theses/templates/theses/vet_thesislink.html new file mode 100644 index 0000000000000000000000000000000000000000..3694c92f0f94b02e57dd15fb4102e2ff4dbba7df --- /dev/null +++ b/theses/templates/theses/vet_thesislink.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %} + +{% block headsup %} + +{% endblock headsup %} + +{% block bodysup %} + +<form action="" method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" value="Update" /> +</form> + +{% endblock bodysup %} diff --git a/theses/templates/theses/vet_thesislink_requests.html b/theses/templates/theses/vet_thesislink_requests.html index c49008776f23329e746b62c1e0d84c9da1f3a178..49172307273f713a950bb4355acb90c6ab8387ff 100644 --- a/theses/templates/theses/vet_thesislink_requests.html +++ b/theses/templates/theses/vet_thesislink_requests.html @@ -16,13 +16,13 @@ <hr> <div class="row"> <div class="col-8"> - {{ thesislink_to_vet.header_as_table }} + {% include "./_header_as_table.html" with thesislink=thesislink_to_vet %} <br /> <h4>Abstract:</h4> <p>{{ thesislink_to_vet.abstract }}</p> </div> <div class="col-4"> - <form method="post"> + <form action= {% url 'theses:vet_thesislink_request' thesislink_to_vet.id %} method="post"> {% csrf_token %} {{ form.as_ul }} <input type="submit" value="Submit" /> diff --git a/theses/templatetags/__init__.py b/theses/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/theses/templatetags/theses_extras.py b/theses/templatetags/theses_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..f3b441f41f64e3bb5a7ad23f852eb3e627fe9dec --- /dev/null +++ b/theses/templatetags/theses_extras.py @@ -0,0 +1,26 @@ +from django import template + +from scipost.constants import SCIPOST_DISCIPLINES, subject_areas_dict, disciplines_dict +from journals.models import journals_domains_dict + +register = template.Library() + + +@register.filter +def type(thesislink): + return thesislink.THESIS_TYPES_DICT[thesislink.type] + + +@register.filter +def discipline(thesislink): + return disciplines_dict[thesislink.discipline] + + +@register.filter +def domain(thesislink): + return journals_domains_dict[thesislink.domain] + + +@register.filter +def subject_area(thesislink): + return subject_areas_dict[thesislink.subject_area] diff --git a/theses/test_forms.py b/theses/test_forms.py index 1456184593dd64e0f63f12574fb5c59b87576e6a..73d294c75e3c7f0908828be27e06deb5920ef005 100644 --- a/theses/test_forms.py +++ b/theses/test_forms.py @@ -24,21 +24,3 @@ class TestRequestThesisLink(TestCase): form = RequestThesisLinkForm(form_data) form.is_valid() self.assertEqual(form.errors['domain'], ['This field is required.']) - - -class TestVetThesisLinkRequests(TestCase): - fixtures = ['permissions', 'groups'] - - def test_thesislink_gets_vetted_when_accepted(self): - thesis_link = ThesisLinkFactory() - form = VetThesisLinkFormFactory() - form.is_valid() - form.vet_request(thesis_link) - self.assertTrue(thesis_link.vetted) - - def test_thesislink_is_not_vetted_when_refused(self): - thesis_link = ThesisLinkFactory() - form = VetThesisLinkFormFactory(action_option=VetThesisLinkForm.REFUSE) - form.is_valid() - form.vet_request(thesis_link) - self.assertFalse(thesis_link.vetted) diff --git a/theses/test_models.py b/theses/test_models.py index 4308ab25f1d22c56e33cb5047cb6f571fe080bf7..9cda6172906f7feeb4a8789f014fb4a09f5f7bf9 100644 --- a/theses/test_models.py +++ b/theses/test_models.py @@ -8,6 +8,8 @@ from .factories import ThesisLinkFactory class ThesisLinkTestCase(TestCase): + fixtures = ['permissions', 'groups'] + def test_domain_cannot_be_blank(self): thesis_link = ThesisLinkFactory() thesis_link.domain = "" diff --git a/theses/test_views.py b/theses/test_views.py index faa7f14d5d621096441faa542af7984e8e629573..c3f1244b1865f5576fab264d50d7ab95ae428a69 100644 --- a/theses/test_views.py +++ b/theses/test_views.py @@ -1,27 +1,53 @@ import re +from django.core import mail from django.core.exceptions import PermissionDenied from django.test import TestCase, RequestFactory from django.test.client import Client from django.contrib.auth.models import Group -from django.urls import reverse +from django.urls import reverse, reverse_lazy +from django.contrib.messages.storage.fallback import FallbackStorage -from .views import RequestThesisLink, VetThesisLinkRequests from scipost.factories import UserFactory, ContributorFactory +from comments.factories import CommentFactory +from comments.forms import CommentForm +from comments.models import Comment +from .views import RequestThesisLink, VetThesisLink, thesis_detail from .factories import ThesisLinkFactory, VetThesisLinkFormFactory from .models import ThesisLink +from .forms import VetThesisLinkForm +from common.helpers import model_form_data class TestThesisDetail(TestCase): fixtures = ['groups', 'permissions'] def test_visits_valid_thesis_detail(self): + """ A visitor does not have to be logged in to view a thesis link. """ thesis_link = ThesisLinkFactory() client = Client() target = reverse('theses:thesis', kwargs={'thesislink_id': thesis_link.id}) response = client.post(target) self.assertEqual(response.status_code, 200) + def test_submitting_comment_creates_comment(self): + """ Valid Comment gets saved """ + + contributor = ContributorFactory() + thesislink = ThesisLinkFactory() + valid_comment_data = model_form_data(CommentFactory.build(), CommentForm) + target = reverse('theses:thesis', kwargs={'thesislink_id': thesislink.id}) + + comment_count = Comment.objects.filter(author=contributor).count() + self.assertEqual(comment_count, 0) + + request = RequestFactory().post(target, valid_comment_data) + request.user = contributor.user + response = thesis_detail(request, thesislink_id=thesislink.id) + + comment_count = Comment.objects.filter(author=contributor).count() + self.assertEqual(comment_count, 1) + class TestRequestThesisLink(TestCase): fixtures = ['groups', 'permissions'] @@ -47,7 +73,8 @@ class TestVetThesisLinkRequests(TestCase): def setUp(self): self.client = Client() - self.target = reverse('theses:vet_thesislink_requests') + self.thesislink = ThesisLinkFactory() + self.target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id}) def test_response_when_not_logged_in(self): response = self.client.get(self.target) @@ -58,33 +85,59 @@ class TestVetThesisLinkRequests(TestCase): A Contributor needs to be in the Vetting Editors group to be able to vet submitted thesis links. ''' - # Create ThesisLink to vet. - ThesisLinkFactory() request = RequestFactory().get(self.target) user = UserFactory() request.user = user self.assertRaises( - PermissionDenied, VetThesisLinkRequests.as_view(), request) + PermissionDenied, VetThesisLink.as_view(), request, pk=self.thesislink.id) def test_response_vetting_editor(self): - # Create ThesisLink to vet. - ThesisLinkFactory() request = RequestFactory().get(self.target) user = UserFactory() user.groups.add(Group.objects.get(name="Vetting Editors")) request.user = user - response = VetThesisLinkRequests.as_view()(request) + response = VetThesisLink.as_view()(request, pk=self.thesislink.id) self.assertEqual(response.status_code, 200) - def test_thesislink_is_vetted_by_correct_contributor(self): - # TODO: how to make sure we are vetting the right thesis link? + def test_thesislink_is_vetted_by_correct_contributor_and_mail_is_sent(self): + contributor = ContributorFactory() + contributor.user.groups.add(Group.objects.get(name="Vetting Editors")) + post_data = model_form_data(ThesisLinkFactory(), VetThesisLinkForm) + post_data["action_option"] = VetThesisLinkForm.ACCEPT + target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id}) + + request = RequestFactory().post(target, post_data) + request.user = contributor.user + + # I don't know what the following three lines do, but they help make a RequestFactory + # work with the messages middleware + setattr(request, 'session', 'session') + messages = FallbackStorage(request) + setattr(request, '_messages', messages) + + response = VetThesisLink.as_view()(request, pk=self.thesislink.id) + self.thesislink.refresh_from_db() + self.assertEqual(self.thesislink.vetted_by, contributor) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, 'SciPost Thesis Link activated') + + def test_thesislink_that_is_refused_is_deleted_and_mail_is_sent(self): contributor = ContributorFactory() contributor.user.groups.add(Group.objects.get(name="Vetting Editors")) - post_data = VetThesisLinkFormFactory().data + post_data = model_form_data(ThesisLinkFactory(), VetThesisLinkForm) + post_data["action_option"] = VetThesisLinkForm.REFUSE + target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id}) - request = RequestFactory().post(self.target, post_data) + request = RequestFactory().post(target, post_data) request.user = contributor.user - response = VetThesisLinkRequests.as_view()(request) + # I don't know what the following three lines do, but they help make a RequestFactory + # work with the messages middleware + setattr(request, 'session', 'session') + messages = FallbackStorage(request) + setattr(request, '_messages', messages) - self.assertTrue(False) + response = VetThesisLink.as_view()(request, pk=self.thesislink.id) + self.assertEqual(ThesisLink.objects.filter(id=self.thesislink.id).count(), 0) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, 'SciPost Thesis Link') diff --git a/theses/urls.py b/theses/urls.py index 3d3c530e497a0ca6794259b9626530d6a8e8fbc9..a0734fdb52195d0ffaa5864a8ed649fe7129630d 100644 --- a/theses/urls.py +++ b/theses/urls.py @@ -9,8 +9,6 @@ urlpatterns = [ url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]+)/$', views.browse, name='browse'), url(r'^(?P<thesislink_id>[0-9]+)/$', views.thesis_detail, name='thesis'), url(r'^request_thesislink$', views.RequestThesisLink.as_view(), name='request_thesislink'), - url(r'^vet_thesislink_requests$', views.VetThesisLinkRequests.as_view(), - name='vet_thesislink_requests'), - url(r'^vet_thesislink_request_ack/(?P<thesislink_id>[0-9]+)$', - views.vet_thesislink_request_ack, name='vet_thesislink_request_ack'), + url(r'^unvetted_thesislinks$', views.UnvettedThesisLinks.as_view(), name='unvetted_thesislinks'), + url(r'^vet_thesislink/(?P<pk>[0-9]+)/$', views.VetThesisLink.as_view(), name='vet_thesislink'), ] diff --git a/theses/views.py b/theses/views.py index 7e782a4b88b20052e4c52217c4544106c246ced6..34c9f035e737fb1aea998b2530b806184b105746 100644 --- a/theses/views.py +++ b/theses/views.py @@ -11,7 +11,8 @@ from django.core.urlresolvers import reverse, reverse_lazy from django.http import HttpResponse, HttpResponseRedirect from django.views.decorators.csrf import csrf_protect from django.db.models import Avg -from django.views.generic.edit import CreateView, FormView +from django.views.generic.edit import CreateView, FormView, UpdateView +from django.views.generic.list import ListView from django.utils.decorators import method_decorator from .models import * @@ -20,7 +21,7 @@ from .forms import * from comments.models import Comment from comments.forms import CommentForm from scipost.forms import TITLE_CHOICES, AuthenticationForm - +import strings title_dict = dict(TITLE_CHOICES) # Convert titles for use in emails @@ -44,108 +45,39 @@ class RequestThesisLink(CreateView): @method_decorator(permission_required( 'scipost.can_vet_thesislink_requests', raise_exception=True), name='dispatch') -class VetThesisLinkRequests(FormView): - form_class = VetThesisLinkForm - template_name = 'theses/vet_thesislink_requests.html' - # TODO: not right yet - success_url = reverse_lazy('theses:vet_thesislink_requests') +class UnvettedThesisLinks(ListView): + model = ThesisLink + template_name = 'theses/unvetted_thesislinks.html' + context_object_name = 'thesislinks' + queryset = ThesisLink.objects.filter(vetted=False) - def get_context_data(self, **kwargs): - context = super(VetThesisLinkRequests, self).get_context_data(**kwargs) - context['thesislink_to_vet'] = self.thesislink_to_vet() - return context - def thesislink_to_vet(self): - return ThesisLink.objects.filter(vetted=False).first() +@method_decorator(permission_required( + 'scipost.can_vet_thesislink_requests', raise_exception=True), name='dispatch') +class VetThesisLink(UpdateView): + model = ThesisLink + form_class = VetThesisLinkForm + template_name = "theses/vet_thesislink.html" + success_url = reverse_lazy('theses:unvetted_thesislinks') def form_valid(self, form): - form.vet_request(self.thesislink_to_vet()) - return super(VetThesisLinkRequests, self).form_valid(form) - - -# @permission_required('scipost.can_vet_thesislink_requests', raise_exception=True) -# def vet_thesislink_requests(request): -# contributor = Contributor.objects.get(user=request.user) -# thesislink_to_vet = ThesisLink.objects.filter( -# vetted=False).first() # only handle one at a time -# form = VetThesisLinkForm() -# context = {'contributor': contributor, 'thesislink_to_vet': thesislink_to_vet, 'form': form} -# return render(request, 'theses/vet_thesislink_requests.html', context) - - -@permission_required('scipost.can_vet_thesislink_requests', raise_exception=True) -def vet_thesislink_request_ack(request, thesislink_id): - if request.method == 'POST': - form = VetThesisLinkForm(request.POST) - thesislink = ThesisLink.objects.get(pk=thesislink_id) - if form.is_valid(): - if form.cleaned_data['action_option'] == '1': - thesislink.vetted = True - thesislink.vetted_by = Contributor.objects.get(user=request.user) - thesislink.save() - email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' - + thesislink.requested_by.user.last_name - + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' - + thesislink.title + ' by ' + thesislink.author - + ', has been activated at https://scipost.org/thesis/' - + str(thesislink.id) + '.' - + '\n\nThank you for your contribution, \nThe SciPost Team.') - emailmessage = EmailMessage('SciPost Thesis Link activated', email_text, - 'SciPost Theses <theses@scipost.org>', - [thesislink.requested_by.user.email], - ['theses@scipost.org'], - reply_to=['theses@scipost.org']) - emailmessage.send(fail_silently=False) - elif form.cleaned_data['action_option'] == '0': - # re-edit the form starting from the data provided - form2 = RequestThesisLinkForm(initial={'title': thesislink.pub_title, - 'pub_ink': thesislink.pub_link, - 'author': thesislink.author, - 'institution': thesislink.institution, - 'defense_date': thesislink.defense_date, - 'abstract': thesislink.abstract}) - thesislink.delete() - email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' - + thesislink.requested_by.user.last_name - + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' - + thesislink.title + ' by ' + thesislink.author_list - + ', has been activated ' - '(with slight modifications to your submitted details) at ' - 'https://scipost.org/thesis/' + str(thesislink.id) + '.' - '\n\nThank you for your contribution, \nThe SciPost Team.') - emailmessage = EmailMessage('SciPost Thesis Link activated', email_text, - 'SciPost Theses <theses@scipost.org>', - [thesislink.requested_by.user.email], - ['theses@scipost.org'], - reply_to=['theses@scipost.org']) - # Don't send email yet... only when option 1 has succeeded! - # emailmessage.send(fail_silently=False) - context = {'form': form2} - return render(request, 'theses/request_thesislink.html', context) - elif form.cleaned_data['action_option'] == '2': - email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' ' - + thesislink.requested_by.user.last_name - + ', \n\nThe Thesis Link you have requested, concerning thesis with title ' - + thesislink.title + ' by ' + thesislink.author - + ', has not been activated for the following reason: ' - + form.cleaned_data['refusal_reason'] - + '.\n\nThank you for your interest, \nThe SciPost Team.') - if form.cleaned_data['justification']: - email_text += '\n\nFurther explanations: ' + \ - form.cleaned_data['justification'] - emailmessage = EmailMessage('SciPost Thesis Link', email_text, - 'SciPost Theses <theses@scipost.org>', - [thesislink.requested_by.user.email], - ['theses@scipost.org'], - reply_to=['theses@scipost.org']) - emailmessage.send(fail_silently=False) - thesislink.delete() - - context = {'ack_header': 'Thesis Link request vetted.', - 'followup_message': 'Return to the ', - 'followup_link': reverse('theses:vet_thesislink_requests'), - 'followup_link_label': 'Thesis Link requests page'} - return render(request, 'scipost/acknowledgement.html', context) + # I totally override the form_valid method. I do not call super. + # This is because, by default, an UpdateView saves the object as instance, + # which it builds from the form data. So, the changes (by whom the thesis link was vetted, etc.) + # would be lost. Instead, we need the form to save with commit=False, then modify + # the vetting fields, and then save. + + # Builds model that reflects changes made during update. Does not yet save. + self.object = form.save(commit=False) + # Process vetting actions (object already gets saved.) + form.vet_request(self.object, self.request.user) + # Save again. + self.object.save() + + messages.add_message( + self.request, messages.SUCCESS, + strings.acknowledge_vet_thesis_link) + return HttpResponseRedirect(self.get_success_url()) def theses(request):