Skip to content

Commit b9f886a

Browse files
authored
Merge pull request #93 from vitorfs/dev
Release v2.1.1
2 parents 104375b + 9386a0f commit b9f886a

File tree

17 files changed

+188
-56
lines changed

17 files changed

+188
-56
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
Parsifal 2.1.1 (2021-09-28)
2+
===========================
3+
4+
Bugfixes
5+
--------
6+
7+
- Improve study selection resolve all action (#86)
8+
- Fixes error while trying to delete a literature review (#89)
9+
- Fix issue while trying to import a bibtex file. (#91)
10+
11+
112
Parsifal 2.1 (2021-09-12)
213
=========================
314

parsifal/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from parsifal.utils.version import get_version
22

3-
VERSION = (2, 1, 0, "final", 0)
3+
VERSION = (2, 1, 1, "final", 0)
44

55
__version__ = get_version(VERSION)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.test.testcases import TestCase
2+
from django.urls import reverse
3+
4+
from parsifal.apps.authentication.tests.factories import UserFactory
5+
6+
7+
class TestUserModel(TestCase):
8+
@classmethod
9+
def setUpTestData(cls):
10+
cls.user = UserFactory(username="john.doe")
11+
12+
def test_get_absolute_url(self):
13+
expected_url = reverse("reviews", args=(self.user.username,))
14+
self.assertEqual(self.user.get_absolute_url(), expected_url)
15+
self.assertEqual(expected_url, "/john.doe/")

parsifal/apps/reviews/conducting/views.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import bibtexparser
1919
import xlwt
2020
from bibtexparser.bparser import BibTexParser
21-
from bibtexparser.customization import convert_to_unicode
2221

2322
from parsifal.apps.reviews.decorators import author_required
2423
from parsifal.apps.reviews.models import (
@@ -670,7 +669,6 @@ def import_bibtex(request):
670669
if ext in valid_extensions or bibtex_file.content_type == "application/x-bibtex":
671670
try:
672671
parser = BibTexParser(common_strings=True)
673-
parser.customization = convert_to_unicode
674672
bib_database = bibtexparser.load(bibtex_file, parser=parser)
675673
articles = bibtex_to_article_object(bib_database, review, source)
676674
_import_articles(request, source, articles)
@@ -698,7 +696,6 @@ def import_bibtex_raw_content(request):
698696
source = Source.objects.get(pk=source_id)
699697

700698
parser = BibTexParser(common_strings=True)
701-
parser.customization = convert_to_unicode
702699
bib_database = bibtexparser.loads(bibtex_file, parser=parser)
703700
articles = bibtex_to_article_object(bib_database, review, source)
704701
_import_articles(request, source, articles)
@@ -1038,16 +1035,16 @@ def resolve_duplicated(request):
10381035
@login_required
10391036
def resolve_all(request):
10401037
try:
1041-
article_id_list = []
10421038
review_id = request.POST["review-id"]
10431039
review = Review.objects.get(pk=review_id)
10441040
duplicates = review.get_duplicate_articles()
1041+
articles_to_update = []
10451042
for duplicate in duplicates:
10461043
for i in range(1, len(duplicate)):
10471044
duplicate[i].status = Article.DUPLICATED
1048-
duplicate[i].save()
1049-
article_id_list.append(str(duplicate[i].id))
1050-
return HttpResponse(",".join(article_id_list))
1045+
articles_to_update.append(duplicate[i])
1046+
Article.objects.bulk_update(articles_to_update, fields=("status",))
1047+
return HttpResponse(",".join([str(article.pk) for article in articles_to_update]))
10511048
except Exception:
10521049
return HttpResponseBadRequest()
10531050

parsifal/apps/reviews/settings/templates/settings/review_settings.html

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
{% load i18n static %}
44

5-
{% block title %}{% trans "Review Settings" %} · {{ review.title }}{% endblock %}
5+
{% block title %}{% translate "Review Settings" %} · {{ review.title }}{% endblock %}
66

77
{% block javascript %}
88
<script>
99
$(function () {
10+
1011
$("#id_user").change(function () {
1112
if ($(this).val() !== "") {
1213
$("#confirm-transfer").prop("disabled", false);
@@ -15,6 +16,7 @@
1516
$("#confirm-transfer").prop("disabled", true);
1617
}
1718
});
19+
1820
$("#enable-confirm-deletion").click(function () {
1921
if ($(this).is(":checked")) {
2022
$("#confirm-deletion").prop("disabled", false);
@@ -23,6 +25,11 @@
2325
$("#confirm-deletion").prop("disabled", true);
2426
}
2527
});
28+
29+
$("#formDeleteReview").submit(function () {
30+
$("#formDeleteReview button[type='submit']").disable();
31+
});
32+
2633
});
2734
</script>
2835
{% endblock javascript %}
@@ -40,31 +47,31 @@
4047
{% csrf_token %}
4148
<div class="panel panel-default">
4249
<div class="panel-heading">
43-
<h3 class="panel-title">{% trans "Review settings" %}</h3>
50+
<h3 class="panel-title">{% translate "Review settings" %}</h3>
4451
</div>
4552
<div class="panel-body">
4653
{% include 'form_vertical.html' with form=form %}
4754
</div>
4855
<div class="panel-footer">
49-
<button type="submit" class="btn btn-success">{% trans "Save changes" %}</button>
56+
<button type="submit" class="btn btn-success">{% translate "Save changes" %}</button>
5057
</div>
5158
</div>
5259
</form>
5360

5461
<div class="panel panel-danger">
5562
<div class="panel-heading">
56-
<h3 class="panel-title">{% trans "Danger zone" %}</h3>
63+
<h3 class="panel-title">{% translate "Danger zone" %}</h3>
5764
</div>
5865
<ul class="list-group">
5966
<li class="list-group-item">
60-
<button type="button" class="btn btn-danger pull-right" data-toggle="modal" data-target="#transfer-review">{% trans "Transfer" %}</button>
61-
<p><strong>{% trans "Transfer ownership" %}</strong></p>
62-
<p style="margin-bottom: 0;">{% trans "Transfer this review to another user." %}</p>
67+
<button type="button" class="btn btn-danger pull-right" data-toggle="modal" data-target="#transfer-review">{% translate "Transfer" %}</button>
68+
<p><strong>{% translate "Transfer ownership" %}</strong></p>
69+
<p style="margin-bottom: 0;">{% translate "Transfer this review to another user." %}</p>
6370
</li>
6471
<li class="list-group-item">
65-
<button type="button" class="btn btn-danger pull-right" data-toggle="modal" data-target="#delete-review">{% trans "Delete" %}</button>
66-
<p><strong>{% trans "Delete this review" %}</strong></p>
67-
<p style="margin-bottom: 0;">{% trans "Once you delete a review, there is no going back. Please be certain." %}</p>
72+
<button type="button" class="btn btn-danger pull-right" data-toggle="modal" data-target="#delete-review">{% translate "Delete" %}</button>
73+
<p><strong>{% translate "Delete this review" %}</strong></p>
74+
<p style="margin-bottom: 0;">{% translate "Once you delete a review, there is no going back. Please be certain." %}</p>
6875
</li>
6976
</ul>
7077
</div>
@@ -76,41 +83,44 @@ <h3 class="panel-title">{% trans "Danger zone" %}</h3>
7683
<div class="modal-dialog">
7784
<div class="modal-content">
7885
<div class="modal-header">
79-
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
80-
<h4 class="modal-title" id="transfer-review-title">{% trans "Transfer ownership" %}</h4>
86+
<button type="button" class="close" data-dismiss="modal" aria-label="{% translate "Close" %}"><span aria-hidden="true">&times;</span></button>
87+
<h4 class="modal-title" id="transfer-review-title">{% translate "Transfer ownership" %}</h4>
8188
</div>
8289
<div class="modal-body">
83-
<label for="id_user" class="control-label">{% trans "New owner's Parsifal username:" %}</label>
90+
<label for="id_user" class="control-label">{% translate "New owner's Parsifal username:" %}</label>
8491
<input type="text" id="id_user" name="transfer-user" class="form-control">
8592
</div>
8693
<div class="modal-footer">
87-
<button type="submit" id="confirm-transfer" class="btn btn-danger btn-block">{% trans "Transfer ownership" %}</button>
94+
<button type="submit" id="confirm-transfer" class="btn btn-danger btn-block">{% translate "Transfer ownership" %}</button>
8895
</div>
8996
</div>
9097
</div>
9198
</div>
9299
</form>
93100

94-
<form method="post" action="{% url 'delete_review' %}">
101+
<form method="post" action="{% url "delete_review" review.author.username review.name %}" id="formDeleteReview">
95102
{% csrf_token %}
96-
<input type="hidden" name="review-id" value="{{ review.pk }}">
97103
<div class="modal fade" id="delete-review" tabindex="-1" role="dialog" aria-labelledby="delete-review-title" aria-hidden="true">
98104
<div class="modal-dialog">
99105
<div class="modal-content">
100106
<div class="modal-header">
101-
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
102-
<h4 class="modal-title" id="delete-review-title">Are you sure?</h4>
107+
<button type="button" class="close" data-dismiss="modal" aria-label="{% translate "Close" %}"><span aria-hidden="true">&times;</span></button>
108+
<h4 class="modal-title" id="delete-review-title">{% translate "Are you sure?" %}</h4>
103109
</div>
104110
<div class="modal-body">
105-
<p>This action <strong>CANNOT</strong> be undone. This will permanently delete the <strong>{{ review.title }}</strong> review and all associated data.</p>
111+
<p>
112+
{% blocktranslate trimmed with title=review.title %}
113+
This action <strong>CANNOT</strong> be undone. This will permanently delete the <strong>{{ title }}</strong> review and all associated data.
114+
{% endblocktranslate %}
115+
</p>
106116
<div class="checkbox">
107117
<label>
108-
<input type="checkbox" id="enable-confirm-deletion"> I understand the consequences.
118+
<input type="checkbox" id="enable-confirm-deletion"> {% translate "I understand the consequences." %}
109119
</label>
110120
</div>
111121
</div>
112122
<div class="modal-footer">
113-
<button type="submit" id="confirm-deletion" class="btn btn-danger btn-block" disabled>Delete this review</button>
123+
<button type="submit" id="confirm-deletion" class="btn btn-danger btn-block" data-loading="{% translate "Deleting... please wait" %}" disabled>{% translate "Delete this review" %}</button>
114124
</div>
115125
</div>
116126
</div>

parsifal/apps/reviews/settings/tests/__init__.py

Whitespace-only changes.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from django.contrib.auth.models import User
2+
from django.test.testcases import TestCase
3+
from django.urls import reverse
4+
5+
from parsifal.apps.authentication.tests.factories import UserFactory
6+
from parsifal.apps.reviews.models import Review, Source
7+
from parsifal.apps.reviews.tests.factories import DefaultSourceFactory, ReviewFactory, SourceFactory
8+
from parsifal.utils.test import login_redirect_url
9+
10+
11+
class TestDeleteReviewView(TestCase):
12+
@classmethod
13+
def setUpTestData(cls):
14+
cls.author = UserFactory()
15+
cls.co_author = UserFactory()
16+
cls.default_source = DefaultSourceFactory()
17+
cls.review_source = SourceFactory()
18+
cls.review = ReviewFactory(
19+
author=cls.author, co_authors=[cls.co_author], sources=[cls.default_source, cls.review_source]
20+
)
21+
cls.url = reverse("delete_review", args=(cls.review.author.username, cls.review.name))
22+
23+
def test_setup(self):
24+
self.assertEqual(2, User.objects.count())
25+
self.assertEqual(2, Source.objects.count())
26+
self.assertEqual(self.co_author, self.review.co_authors.first())
27+
28+
def test_get_not_allowed(self):
29+
self.client.force_login(self.author)
30+
response = self.client.get(self.url)
31+
self.assertEqual(405, response.status_code)
32+
33+
def test_login_required(self):
34+
response = self.client.post(self.url)
35+
with self.subTest(msg="Test redirect"):
36+
self.assertRedirects(response, login_redirect_url(self.url))
37+
with self.subTest(msg="Test review not deleted"):
38+
self.assertTrue(Review.objects.filter(pk=self.review.pk).exists())
39+
self.assertEqual(2, Source.objects.count())
40+
41+
def test_main_author_required(self):
42+
self.client.force_login(self.co_author)
43+
response = self.client.post(self.url)
44+
with self.subTest(msg="Test status code"):
45+
self.assertEqual(403, response.status_code)
46+
with self.subTest(msg="Test review not deleted"):
47+
self.assertTrue(Review.objects.filter(pk=self.review.pk).exists())
48+
self.assertEqual(2, Source.objects.count())
49+
50+
def test_delete_successful(self):
51+
self.client.force_login(self.author)
52+
response = self.client.post(self.url, follow=True)
53+
54+
with self.subTest(msg="Test post status code"):
55+
self.assertEqual(302, response.redirect_chain[0][1])
56+
57+
with self.subTest(msg="Test post redirect status code"):
58+
self.assertEqual(200, response.status_code)
59+
60+
with self.subTest(msg="Test review deleted"):
61+
self.assertFalse(Review.objects.filter(pk=self.review.pk).exists())
62+
self.assertEqual(1, Source.objects.count())
63+
64+
with self.subTest(msg="Test default source not deleted"):
65+
self.assertTrue(Source.objects.filter(pk=self.default_source.pk).exists())

parsifal/apps/reviews/settings/views.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from django.contrib import messages
22
from django.contrib.auth.decorators import login_required
3+
from django.contrib.auth.mixins import LoginRequiredMixin
34
from django.contrib.auth.models import User
5+
from django.db import transaction
46
from django.http import HttpResponseBadRequest
57
from django.shortcuts import redirect, render
68
from django.urls import reverse as r
79
from django.utils.text import slugify
8-
from django.views.decorators.http import require_POST
10+
from django.utils.translation import gettext
11+
from django.views import View
912

1013
from parsifal.apps.reviews.decorators import main_author_required
14+
from parsifal.apps.reviews.mixins import MainAuthorRequiredMixin, ReviewMixin
1115
from parsifal.apps.reviews.models import Review
1216
from parsifal.apps.reviews.settings.forms import ReviewSettingsForm
1317

@@ -64,17 +68,14 @@ def transfer(request):
6468
return HttpResponseBadRequest("Something went wrong.")
6569

6670

67-
@main_author_required
68-
@login_required
69-
@require_POST
70-
def delete(request):
71-
review_id = request.POST.get("review-id")
72-
review = Review.objects.get(pk=review_id)
73-
sources = review.sources.all()
74-
for source in sources:
75-
if not source.is_default:
76-
review.sources.remove(source)
77-
source.delete()
78-
review.delete()
79-
messages.success(request, "The review was deleted successfully.")
80-
return redirect(r("reviews", args=(review.author.username,)))
71+
class DeleteReviewView(LoginRequiredMixin, MainAuthorRequiredMixin, ReviewMixin, View):
72+
@transaction.atomic()
73+
def post(self, request, *args, **kwargs):
74+
sources = self.review.sources.all()
75+
for source in sources:
76+
if not source.is_default:
77+
self.review.sources.remove(source)
78+
source.delete()
79+
self.review.delete()
80+
messages.success(request, gettext("The review was deleted with success."))
81+
return redirect(request.user)

parsifal/apps/reviews/tests/factories.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from factory.django import DjangoModelFactory
33

44
from parsifal.apps.authentication.tests.factories import UserFactory
5-
from parsifal.apps.reviews.models import Review
5+
from parsifal.apps.reviews.models import Review, Source
66

77

88
class ReviewFactory(DjangoModelFactory):
@@ -13,3 +13,33 @@ class ReviewFactory(DjangoModelFactory):
1313
class Meta:
1414
model = Review
1515
django_get_or_create = ("name",)
16+
17+
@factory.post_generation
18+
def co_authors(self, create, extracted, **kwargs):
19+
if not create:
20+
return
21+
22+
if extracted:
23+
self.co_authors.add(*extracted)
24+
25+
@factory.post_generation
26+
def sources(self, create, extracted, **kwargs):
27+
if not create:
28+
return
29+
30+
if extracted:
31+
self.sources.add(*extracted)
32+
33+
34+
class SourceFactory(DjangoModelFactory):
35+
name = factory.Sequence(lambda n: f"Source #{n}")
36+
url = factory.Sequence(lambda n: f"https://source-{n}.example.com")
37+
is_default = False
38+
39+
class Meta:
40+
model = Source
41+
django_get_or_create = ("name",)
42+
43+
44+
class DefaultSourceFactory(SourceFactory):
45+
is_default = True

parsifal/settings/production.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@
4545
environment=PARSIFAL_ENVIRONMENT,
4646
release=PARSIFAL_RELEASE,
4747
integrations=[DjangoIntegration()],
48-
traces_sample_rate=0.05,
48+
traces_sample_rate=0.01,
4949
send_default_pii=True,
5050
)

0 commit comments

Comments
 (0)