Commit 9aca3d39 authored by okhin's avatar okhin 🚴

Merge branch '36-les-methodes-upvote-et-downvote-sont-bancales' into 'rp2'

Resolve "Les méthodes upvote et downvote sont bancales"

Closes #36

See merge request !34
parents 540fe106 b9a7300a
Pipeline #2587 passed with stages
in 3 minutes
...@@ -73,10 +73,7 @@ class Command(BaseCommand): ...@@ -73,10 +73,7 @@ class Command(BaseCommand):
article.website = website.group(1) article.website = website.group(1)
# Raise the score if needed # Raise the score if needed
if item['note'] > 0: article.score = item['note']
article.und_score_up = item['note']
if item['note'] < 0:
article.und_score_down = abs(item['note'])
article.save() article.save()
article.refresh_from_db() article.refresh_from_db()
......
...@@ -22,23 +22,14 @@ class ArticleSerializer(serializers.ModelSerializer): ...@@ -22,23 +22,14 @@ class ArticleSerializer(serializers.ModelSerializer):
tags = TagListSerializer(help_text=""" tags = TagListSerializer(help_text="""
List of short tags to describe the article (eg."Privacy, Copyright"). List of short tags to describe the article (eg."Privacy, Copyright").
Must be a list of tags, coma separated (or an empty string). Must be a list of tags, coma separated (or an empty string).
""") """, default="")
und_score_up = serializers.IntegerField(
required=False,
help_text="This is used to increase the vote count by this value")
und_score_down = serializers.IntegerField(
required=False,
help_text="This is used to decrease the vote count by this value")
und_score = serializers.IntegerField(
required=False,
help_text="This is the actual computed score for an Article")
class Meta: class Meta:
model = Article model = Article
fields = ('id', 'url', 'title', 'tags', 'extracts', 'status', fields = ('id', 'url', 'title', 'tags', 'extracts', 'status', 'score')
'und_score_up', 'und_score_down', 'und_score')
def create(self, validated_data): def create(self, validated_data):
article = Article.add_new_url(**validated_data) article = Article.add_new_url(**validated_data)
article.save() if article is not None:
article.save()
return article return article
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2019-05-07 14:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rp', '0020_article_original_status'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='und_score_down',
),
migrations.RemoveField(
model_name='article',
name='und_score_up',
),
migrations.AddField(
model_name='article',
name='score',
field=models.IntegerField(default=0),
),
]
...@@ -4,7 +4,6 @@ from django.core import files ...@@ -4,7 +4,6 @@ from django.core import files
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from newspaper import Article as ArticleParser, ArticleException from newspaper import Article as ArticleParser, ArticleException
from django_und.models import VoteMixin
from django_fsm import FSMField, transition, RETURN_VALUE from django_fsm import FSMField, transition, RETURN_VALUE
from io import BytesIO from io import BytesIO
...@@ -38,7 +37,8 @@ EXTRACTS_HELP_TEXT = """Please select short and helpful extracts from the ...@@ -38,7 +37,8 @@ EXTRACTS_HELP_TEXT = """Please select short and helpful extracts from the
article content. You should aim at around 500 characters. Use bracket ellipsis article content. You should aim at around 500 characters. Use bracket ellipsis
[…] to cut parts not required to understand the context.""" […] to cut parts not required to understand the context."""
class Article(VoteMixin):
class Article(models.Model):
#: Logical state (eg. article submitted, published, or rejected) #: Logical state (eg. article submitted, published, or rejected)
# This is unprotected because superuser should be able to change # This is unprotected because superuser should be able to change
# the status from the django admin interface # the status from the django admin interface
...@@ -94,6 +94,9 @@ class Article(VoteMixin): ...@@ -94,6 +94,9 @@ class Article(VoteMixin):
#: Comma separated list of short tags to describe the article (eg: "Privacy", "Copyright"). #: Comma separated list of short tags to describe the article (eg: "Privacy", "Copyright").
tags = TaggableManager(blank=True) tags = TaggableManager(blank=True)
#: Score of the article, modifiedby upvote and downvote methods
score = models.IntegerField(default=0)
class Meta: class Meta:
verbose_name = _("Article") verbose_name = _("Article")
verbose_name_plural = _("Articles") verbose_name_plural = _("Articles")
...@@ -147,14 +150,14 @@ class Article(VoteMixin): ...@@ -147,14 +150,14 @@ class Article(VoteMixin):
@transition(field=status, source='DRAFT', target='DRAFT') @transition(field=status, source='DRAFT', target='DRAFT')
@transition(field=status, source='NEW', @transition(field=status, source='NEW',
target=RETURN_VALUE('NEW', 'DRAFT'), permission="rp.can_vote") target=RETURN_VALUE('NEW', 'DRAFT'), permission="rp.can_vote")
def upvote(self, by=None): def upvote(self):
""" """
Upvote the article score for the given user and remove previous votes. Upvote the article score for the given user and remove previous votes.
If the score crosses the threshold ```ARTICLE_SCORE_THRESHOLD```, If the score crosses the threshold ```ARTICLE_SCORE_THRESHOLD```,
automatically moves the article from _NEW_ to _DRAFT_. automatically moves the article from _NEW_ to _DRAFT_.
""" """
super(Article, self).upvote(by) self.score += 1
if self.und_score >= ARTICLE_SCORE_THRESHOLD: if self.score >= ARTICLE_SCORE_THRESHOLD - 1:
return 'DRAFT' return 'DRAFT'
else: else:
return self.status return self.status
...@@ -169,7 +172,7 @@ class Article(VoteMixin): ...@@ -169,7 +172,7 @@ class Article(VoteMixin):
votes. Draft articles can be downvoted but will not be moved back in votes. Draft articles can be downvoted but will not be moved back in
the _NEW_ queue. the _NEW_ queue.
""" """
super(Article, self).downvote(by) self.score -= 1
@classmethod @classmethod
def add_new_url(by=None, **data): def add_new_url(by=None, **data):
...@@ -184,13 +187,15 @@ class Article(VoteMixin): ...@@ -184,13 +187,15 @@ class Article(VoteMixin):
(article, created) = Article.objects.get_or_create(url=url, (article, created) = Article.objects.get_or_create(url=url,
defaults=data) defaults=data)
# Let's add the tags
if tags:
article.tags.add(','.join([t for t in tags if len(t) > 0]))
# If the article was already there, we should upvote it # If the article was already there, we should upvote it
if not created: if not created:
article.upvote(str(by)) if article.status == "REJECTED":
return None
article.upvote()
# Let's add the tags
if tags:
article.tags.add(','.join([t for t in tags if len(t) > 0]))
try: try:
r = requests.get(url, timeout=0.5) r = requests.get(url, timeout=0.5)
article.original_status = r.status_code article.original_status = r.status_code
......
from django.test import TestCase
from django.urls import reverse
from userprofile.factories import ProfileFactory
from rp.factories import ArticleFactory
from rp.models import Article
class ArticleListTestCase(TestCase):
def setUp(self):
self.article = ArticleFactory(status='NEW')
self.article2 = ArticleFactory(status='NEW')
self.user = ProfileFactory().user
self.user2 = ProfileFactory().user
self.user3 = ProfileFactory().user
def test_empty_upvoted(self):
self.client.force_login(self.user)
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": "flux"
}))
assert response.status_code == 200
# Get the context
context = response.context
assert context["und_votes"]["upvoted"].count() == 0
assert context["und_votes"]["downvoted"].count() == 0
def test_upvoted(self):
self.article.upvote(self.user.username)
self.client.force_login(self.user)
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": "flux"
}))
assert list(
response.context["und_votes"]["upvoted"]) == [self.article.id]
assert list(
response.context["und_votes"]["downvoted"]) == []
def test_downvoted(self):
self.article.downvote(self.user.username)
self.client.force_login(self.user)
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": "flux"
}))
assert list(
response.context["und_votes"]["upvoted"]) == []
assert list(
response.context["und_votes"]["downvoted"]) == [self.article.id]
def test_2users(self):
self.article.downvote(self.user.username)
self.article2.upvote(self.user.username)
self.article.upvote(self.user2.username)
self.client.force_login(self.user)
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": "flux"
}))
assert list(
response.context["und_votes"]["upvoted"]) == [self.article2.id]
assert list(
response.context["und_votes"]["downvoted"]) == [self.article.id]
self.client.force_login(self.user2)
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": "flux"
}))
assert list(
response.context["und_votes"]["upvoted"]) == [self.article.id]
assert list(
response.context["und_votes"]["downvoted"]) == []
def _test_filter_view(self, filter_view):
"""Return list of articles ids in filter_view"""
response = self.client.get(reverse("rp:article-list", kwargs={
"filter_view": filter_view
}))
return sorted(
response.context["object_list"].values_list('id', flat=True))
def test_filter_view(self):
self.article.upvote(self.user.username)
self.article.upvote(self.user2.username)
self.client.force_login(self.user)
assert self._test_filter_view("flux") == sorted(
[self.article.pk, self.article2.pk]
)
assert self._test_filter_view("published") == []
assert self._test_filter_view("draft") == []
assert self._test_filter_view("rejected") == []
self.article.upvote(self.user3.username)
assert self._test_filter_view("flux") == sorted(
[self.article.pk, self.article2.pk]
)
self.article.save()
assert self._test_filter_view("published") == []
assert self._test_filter_view("draft") == [self.article.id]
assert self._test_filter_view("rejected") == []
self.article2.reject()
self.article2.save()
assert self._test_filter_view("published") == []
assert self._test_filter_view("flux") == []
assert self._test_filter_view("draft") == [self.article.id]
assert self._test_filter_view("rejected") == [self.article2.id]
self.article.publish()
self.article.save()
assert self._test_filter_view("published") == [self.article.id]
assert self._test_filter_view("flux") == []
assert self._test_filter_view("draft") == []
assert self._test_filter_view("rejected") == [self.article2.id]
...@@ -39,12 +39,17 @@ class VoteViewTestCase(TestCase): ...@@ -39,12 +39,17 @@ class VoteViewTestCase(TestCase):
response = self.client.post(url_upvote) response = self.client.post(url_upvote)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
article_db = Article.objects.get(id=self.article.id) article_db = Article.objects.get(id=self.article.id)
assert article_db.und_score == 1 assert article_db.score == 1
response = self.client.post(url_upvote)
self.assertEqual(response.status_code, 200)
article_db.refresh_from_db()
assert article_db.score == 2
response = self.client.post(url_downvote) response = self.client.post(url_downvote)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
article_db = Article.objects.get(id=self.article.id) article_db = Article.objects.get(id=self.article.id)
assert article_db.und_score == -1 assert article_db.score == 1
def test_publish(self): def test_publish(self):
url_publish = reverse("api:article-publish", kwargs={ url_publish = reverse("api:article-publish", kwargs={
......
...@@ -19,7 +19,6 @@ from taggit.models import Tag ...@@ -19,7 +19,6 @@ from taggit.models import Tag
from rp.forms import TagMultipleChoiceField from rp.forms import TagMultipleChoiceField
from rp.models import Article from rp.models import Article
from .votes import UDList
class ArticleList(ListView): class ArticleList(ListView):
...@@ -58,7 +57,7 @@ class ArticleList(ListView): ...@@ -58,7 +57,7 @@ class ArticleList(ListView):
return context return context
class ArticleListFlux(LoginRequiredMixin, UDList): class ArticleListFlux(LoginRequiredMixin, ListView):
model = Article model = Article
paginate_by = 10 paginate_by = 10
......
from django.views.generic import ListView
from django.contrib.contenttypes.models import ContentType
class UDList(ListView):
"""
Adds id upvoted by user
"""
def get_context_data(self, **kwargs):
content_type = ContentType.objects.get_for_model(self.model)
queryset = self.get_queryset()
context = super().get_context_data(**kwargs)
context["und_votes"] = {
"upvoted": queryset.filter(
und_votes__username=self.request.user.username,
und_votes__content_type=content_type,
und_votes__score__gt=0
).values_list("id", flat=True),
"downvoted": queryset.filter(
und_votes__username=self.request.user.username,
und_votes__content_type=content_type,
und_votes__score__lt=0
).values_list("id", flat=True)
}
return context
...@@ -19,7 +19,6 @@ CONTRIB_APPS = [ ...@@ -19,7 +19,6 @@ CONTRIB_APPS = [
"rest_framework", # http://www.django-rest-framework.org/ "rest_framework", # http://www.django-rest-framework.org/
"rest_framework.authtoken", "rest_framework.authtoken",
"django_und", # https://github.com/luxcem/django_und
"taggit", "taggit",
"crispy_forms", "crispy_forms",
"django_markdown2", "django_markdown2",
......
...@@ -3,7 +3,6 @@ djangorestframework==3.6.3 ...@@ -3,7 +3,6 @@ djangorestframework==3.6.3
django-extensions==1.7.9 django-extensions==1.7.9
django-imagekit==4.0 django-imagekit==4.0
django-taggit==0.22.0 django-taggit==0.22.0
django-und
Pillow==4.1.0 Pillow==4.1.0
selenium selenium
newspaper3k newspaper3k
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment