diff --git a/templates/article/includes/warn_typo.part.html b/templates/article/includes/warn_typo.part.html
new file mode 100644
index 0000000000..bee432cd1f
--- /dev/null
+++ b/templates/article/includes/warn_typo.part.html
@@ -0,0 +1,33 @@
+{% load i18n %}
+
+{% if user.is_authenticated and on_line %}
+
+ {% if user not in authors.all and authors_reachable|length >= 1 %}
+
+
+ {% trans "Signaler une faute dans l'article" %}
+
+
+ {% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/templates/article/view.html b/templates/article/view.html
index 0e3a151c26..e6571395bd 100644
--- a/templates/article/view.html
+++ b/templates/article/view.html
@@ -113,8 +113,10 @@
{% block content %}
{{ article.txt|safe }}
-
{% include "article/includes/pager.part.html" %}
+
+ {% include "article/includes/warn_typo.part.html" with article=article authors=authors%}
+
{% endblock %}
diff --git a/zds/article/tests/tests.py b/zds/article/tests/tests.py
index ddb9ffae13..9310e33494 100644
--- a/zds/article/tests/tests.py
+++ b/zds/article/tests/tests.py
@@ -6,6 +6,9 @@
import datetime
from zds.gallery.factories import ImageFactory, UserGalleryFactory, GalleryFactory
+from django.contrib import messages
+from django.contrib.auth.models import Group
+
try:
import ujson as json_reader
except:
@@ -38,7 +41,6 @@
class ArticleTests(TestCase):
def setUp(self):
-
settings.EMAIL_BACKEND = \
'django.core.mail.backends.locmem.EmailBackend'
self.mas = ProfileFactory().user
@@ -102,6 +104,9 @@ def setUp(self):
self.assertEquals(len(mail.outbox), 1)
mail.outbox = []
+ bot = Group(name=settings.ZDS_APP["member"]["bot_group"])
+ bot.save()
+
def test_delete_image_on_change(self):
"""test que l'image est bien supprimée quand on la change"""
@@ -878,6 +883,115 @@ def test_new_article(self):
self.assertEqual(result.status_code, 200)
self.assertEqual(Article.objects.filter(title='Create a new article test').count(), 1)
+ def test_warn_typo(self):
+ """
+ Add a non-regression test about warning the author(s) of a typo in an article
+ """
+
+ typo_text = u'T\'as fait une faute, t\'es nul'
+
+ # login with author
+ self.assertEqual(
+ self.client.login(
+ username=self.user_author.username,
+ password='hostel77'),
+ True)
+
+ # check if author get error when warning typo on its own tutorial
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=[self.article.pk]),
+ {
+ 'explication': u'ceci est un test',
+ },
+ follow=True)
+ self.assertEqual(result.status_code, 200)
+
+ msgs = result.context['messages']
+ last = None
+ for msg in msgs:
+ last = msg
+ self.assertEqual(last.level, messages.ERROR)
+
+ # login with normal user
+ self.client.logout()
+
+ self.assertEqual(
+ self.client.login(
+ username=self.user.username,
+ password='hostel77'),
+ True)
+
+ # check if user can warn typo in tutorial
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=[self.article.pk]),
+ {
+ 'explication': typo_text,
+ },
+ follow=True)
+ self.assertEqual(result.status_code, 200)
+
+ msgs = result.context['messages']
+ last = None
+ for msg in msgs:
+ last = msg
+ self.assertEqual(last.level, messages.SUCCESS)
+
+ # check PM :
+ sent_pm = PrivateTopic.objects.filter(author=self.user.pk).last()
+ self.assertIn(self.user_author, sent_pm.participants.all()) # author is in participants
+ self.assertIn(typo_text, sent_pm.last_message.text) # typo is in message
+ self.assertIn(self.article.get_absolute_url_online(), sent_pm.last_message.text) # public url is in message
+
+ # Check if we send a wrong pk key
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=["1111"]),
+ {
+ 'explication': typo_text,
+ },
+ follow=False)
+ self.assertEqual(result.status_code, 404)
+
+ # Check if we send no explanation
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=[self.article.pk]),
+ {
+ 'explication': '',
+ },
+ follow=True)
+ self.assertEqual(result.status_code, 200)
+
+ msgs = result.context['messages']
+ last = None
+ for msg in msgs:
+ last = msg
+ self.assertEqual(last.level, messages.ERROR)
+
+ # Check if we send an explanation with only space
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=[self.article.pk]),
+ {
+ 'explication': ' ',
+ },
+ follow=True)
+ self.assertEqual(result.status_code, 200)
+
+ msgs = result.context['messages']
+ last = None
+ for msg in msgs:
+ last = msg
+ self.assertEqual(last.level, messages.ERROR)
+
+ # Check if a guest can not warn the author
+ self.client.logout()
+
+ result = self.client.post(
+ reverse('zds.article.views.warn_typo', args=[self.article.pk]),
+ {
+ 'explication': typo_text,
+ },
+ follow=False)
+ self.assertEqual(result.status_code, 302)
+
def tearDown(self):
if os.path.isdir(settings.ZDS_APP['article']['repo_path']):
shutil.rmtree(settings.ZDS_APP['article']['repo_path'])
diff --git a/zds/article/urls.py b/zds/article/urls.py
index 0797113cef..c93150237d 100644
--- a/zds/article/urls.py
+++ b/zds/article/urls.py
@@ -56,4 +56,6 @@
'zds.article.views.like_reaction'),
url(r'^message/dislike/$',
'zds.article.views.dislike_reaction'),
+ url(r'^message/typo/article/(?P\d+)/$',
+ 'zds.article.views.warn_typo'),
)
diff --git a/zds/article/views.py b/zds/article/views.py
index f1b5a0604d..d01bb572c2 100644
--- a/zds/article/views.py
+++ b/zds/article/views.py
@@ -2,6 +2,8 @@
from datetime import datetime
from operator import attrgetter
+from zds.member.models import Profile
+
try:
import ujson as json_reader
except ImportError:
@@ -130,6 +132,7 @@ def view(request, article_pk, article_slug):
'validation': validation,
'is_js': is_js,
'formJs': form_js,
+ 'on_line': False
})
@@ -165,6 +168,12 @@ def view_online(request, article_pk, article_slug):
.order_by('position')\
.all()
+ # Check if the author is reachable
+ authors_reachable_request = Profile.objects.contactable_members().filter(user__in=article.authors.all())
+ authors_reachable = []
+ for author in authors_reachable_request:
+ authors_reachable.append(author.user)
+
# Retrieve pk of the last reaction. If there aren't reactions
# for the article, we initialize this last reaction at 0.
last_reaction_pk = 0
@@ -211,10 +220,79 @@ def view_online(request, article_pk, article_slug):
'pages': paginator_range(page_nbr, paginator.num_pages),
'nb': page_nbr,
'last_reaction_pk': last_reaction_pk,
- 'form': form
+ 'form': form,
+ 'on_line': True,
+ 'authors_reachable': authors_reachable
})
+@login_required
+@require_POST
+def warn_typo(request, article_pk):
+ """Warn author(s) about a mistake in its (their) article by sending him/her (them) a private message."""
+
+ # Need profile
+ profile = get_object_or_404(Profile, user=request.user)
+
+ # Get article
+ try:
+ article_pk = int(article_pk)
+ except (KeyError, ValueError):
+ raise Http404
+
+ article = get_object_or_404(Article, pk=article_pk)
+
+ # Check if the article is published
+ if article.sha_public is None:
+ raise Http404
+
+ # Check if authors are reachable
+ authors_reachable = Profile.objects.contactable_members().filter(user__in=article.authors.all())
+ authors = []
+ for author in authors_reachable:
+ authors.append(author.user)
+
+ if len(authors) == 0:
+ if article.authors.count() > 1:
+ messages.error(request, _(u"Les auteurs de l'article sont malheureusement injoignables"))
+ else:
+ messages.error(request, _(u"L'auteur de l'article est malheureusement injoignable"))
+ else:
+ # Fetch explanation
+ if 'explication' not in request.POST or not request.POST['explication'].strip():
+ messages.error(request, _(u'Votre proposition de correction est vide'))
+ else:
+ explanation = request.POST['explication']
+ explanation = '\n'.join(['> ' + line for line in explanation.split('\n')])
+
+ # Is the user trying to send PM to himself ?
+ if request.user in article.authors.all():
+ messages.error(request, _(u'Impossible d\'envoyer la correction car vous êtes l\'auteur '
+ u'de cet article !'))
+ else:
+ # Create message :
+ msg = _(u'[{}]({}) souhaite vous proposer une correction pour votre article [{}]({}).\n\n').format(
+ request.user.username,
+ settings.ZDS_APP['site']['url'] + profile.get_absolute_url(),
+ article.title,
+ settings.ZDS_APP['site']['url'] + article.get_absolute_url_online()
+ )
+
+ msg += _(u'Voici son message :\n\n{}').format(explanation)
+
+ # Send it
+ send_mp(request.user,
+ article.authors.all(),
+ _(u"Proposition de correction"),
+ article.title,
+ msg,
+ leave=False)
+ messages.success(request, _(u'Votre correction a bien été proposée !'))
+
+ # return to page :
+ return redirect(article.get_absolute_url_online())
+
+
@can_write_and_read_now
@login_required
def new(request):
diff --git a/zds/member/tests/tests_views.py b/zds/member/tests/tests_views.py
index 883f752e51..7beb8eea36 100644
--- a/zds/member/tests/tests_views.py
+++ b/zds/member/tests/tests_views.py
@@ -55,6 +55,9 @@ def setUp(self):
position_in_category=1)
self.staff = StaffProfileFactory().user
+ bot = Group(name=settings.ZDS_APP["member"]["bot_group"])
+ bot.save()
+
def test_list_members(self):
"""
To test the listing of the members with and without page parameter.