Commit 1b84d06e authored by okhin's avatar okhin 🚴

Merge branch '49-ajouter-des-flags-sur-le-contenu' into 'rp2'

Resolve "Ajouter des flags sur le contenu"

Closes #49

See merge request !43
parents 7d111e42 138f9412
Pipeline #2616 passed with stages
in 3 minutes and 6 seconds
......@@ -6,6 +6,6 @@ router = routers.DefaultRouter()
router.register(r"articles", ArticleViewSet)
router.register(r"articles-by-tag/(?P<filter_tag>.+)", ArticleTag)
router.register(r"articles-search/(?P<search_keywords>.+)", ArticleSearch)
# router.register(r"articles-search/(?P<search_keywords>.+)", ArticleSearch)
urlpatterns = router.urls
......@@ -4,12 +4,13 @@ from rest_framework import viewsets, mixins
from rp.models import Article
from .serializers import ArticleSerializer
from rp.views.articles import ArticleFilterMixin
from .mixins import get_viewset_transition_actions_mixin
ArticleMixin = get_viewset_transition_actions_mixin(Article)
class ArticleViewSet(ArticleMixin, viewsets.ModelViewSet):
class ArticleViewSet(ArticleMixin, ArticleFilterMixin, viewsets.ModelViewSet):
"""
articles:
This viewset describes all the method usable on the API
......@@ -18,6 +19,16 @@ class ArticleViewSet(ArticleMixin, viewsets.ModelViewSet):
The RequestBody is only needed for the create, update and partial_update
functions.
You can add query parameters to do some filtering, Those are :
q="search terms" # to do a plaintext search into the title and extract
# of articles
archive="true|[false]|both" # to filter on the archive flag or to
# disable filtering on this flag.
speak="true|false|[both]" # to filter on the speak flag, or to disable
# filtering on this flag.
quote="true|false|[both]" # to filter on the quote flag, or to disable
# filtering on this flag.
list:
List all known articles in database.
......
import datetime
from random import choice
import pytz
import factory
......@@ -26,6 +27,9 @@ class ArticleFactory(factory.django.DjangoModelFactory):
datetime.datetime(2014, 1, 1, tzinfo=pytz.UTC))
status = FuzzyChoice([s[0] for s in STATUS_CHOICES])
archive = choice([True, False])
quote = choice([True, False])
speak = choice([True, False])
@factory.post_generation
def tags(self, create, extracted, **kwargs):
......
......@@ -51,13 +51,13 @@
{% endfor %}
</p>
{% if article.archive %}
<span class="badge badge-info ml-1">{{ _("Archived") }}</span>
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-floppy-disk" aria-label="{{ _("Archived") }}"></span>
{% endif %}
{% if article.quote %}
<span class="badge badge-info ml-1">{{ _("Quotes us") }}</span>
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-comment" aria-label="{{ _("Quotes us") }}"></span>
{% endif %}
{% if article.speak %}
<span class="badge badge-info ml-1">{{ _("Speaks of us") }}</span>
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-bullhorn" aria-label="{{ _("Speaks about us") }}"></span>
{% endif %}
<p>{{object.extracts |markdown}}</p>
......
......@@ -37,7 +37,7 @@
<ul class="nav nav-integrated">
<li class="nav-item{% if filter_view == 'flux' %} active{% endif %}">
<a class="nav-link" href="{% url 'rp:article-list' filter_view='flux'%}">
Flux <img style="width: 2rem; margin-left: 0.5rem;" src="/static/img/cochon-01.png" alt="">
Flux <span class="glyphicon glyphicon-piggy-bank" aria-hidden="true"></span>
</a>
</li>
<li class="nav-item {% if filter_view == 'draft' %} active{% endif %}">
......@@ -83,6 +83,9 @@
<th>Title</th>
<th>Note</th>
<th>Added&nbsp;by</th>
<th>Archived</th>
<th>Quotes us</th>
<th>Speak of us</th>
<th>Actions<img class="inline-image ml-2" role="img" src="{% static 'img/jedi.svg' %}" /></th>
</tr>
</thead>
......@@ -116,6 +119,9 @@
{% endif %}
</td>
<td>{{article.created_by}}</td>
<td>{{article.archive}}</td>
<td>{{article.quote}}</td>
<td>{{article.speak}}</td>
<td class="actions-cell">
<ul class="actions-list">
{% if filter_view == 'flux' %}
......
......@@ -19,6 +19,52 @@
<div class="row">
<div class="col-md-12 white-bg shadow-effect">
<div class="row">
<div class="col-md-10 offset-1">
<header class="my-4">
<h2 class="header text-center">{{ _("Article flags")|upper }}</h3>
<ul class="nav nav-compressed">
<li class="nav-item col-md-4">
<div class="input-group">
<label for="archive">
{{ _("Archived") }}
</label>
<select name="archive" form="search-form">
<option value="both" {% if archive == "both" %} selected {% endif %}>{{ _("Both") }}</option>
<option value="true" {% if archive == "true" %} selected {% endif %}>{{ _("Yes") }}</option>
<option value="false" {% if archive == "false" %} selected {% endif %}>{{ _("No") }}</option>
</select>
</div>
</li>
<li class="nav-item col-md-4">
<div class="input-group">
<label for="quote">
{{ _("Quoting us") }}
</label>
<select name="quote" form="search-form">
<option value="both" {% if quote == "both" %} selected {% endif %}>{{ _("Both") }}</option>
<option value="true" {% if quote == "true" %} selected {% endif %}>{{ _("Yes") }}</option>
<option value="false" {% if quote == "false" %} selected {% endif %}>{{ _("No") }}</option>
</select>
</div>
</li>
<li class="nav-item col-md-4">
<div class="input-group">
<label for="speak">
{{ _("Speaking of us") }}
</label>
<select name="speak" form="search-form">
<option value="both" {% if speak == "both" %} selected {% endif %}>{{ _("Both") }}</option>
<option value="true" {% if speak == "true" %} selected {% endif %}>{{ _("Yes") }}</option>
<option value="false" {% if speak == "false" %} selected {% endif %}>{{ _("No") }}</option>
</select>
</div>
</li>
</ul>
</header>
</div>
</div>
<div class="row">
<div class="col-md-10 offset-1">
<header class="my-4">
......@@ -65,12 +111,12 @@
{% endif %}
<li class="nav-item ml-auto">
<form method="get" action="{% url 'rp:public-article-list' %}">
<form method="get" action="{% url 'rp:public-article-list' %}" name="search-form" id="search-form">
<div class="input-group">
<input type="search" placeholder="Rechercher" name="q" class="fa fa-search" value="{{ search }}" />
<button type="submit" class="btn btn-default input-group-addon"><i class="fa fa-search"></i></button>
</div>
</form>
</form>
</li>
<li class="nav-item">
......@@ -127,6 +173,15 @@
</p>
<h4><a href="{{article.url}}">{{article.title}}</a></h4>
{% comment %} <p class="subtitle">via Le Monde le {{article.created_at |date:'d/m'}}</p> {% endcomment %}
{% if article.archive %}
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-floppy-disk" aria-label="{{ _("Archived") }}"></span>
{% endif %}
{% if article.quote %}
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-comment" aria-label="{{ _("Quotes us") }}"></span>
{% endif %}
{% if article.speak %}
<span class="badge badge-info ml-1"><span class="glyphicon glyphicon-bullhorn" aria-label="{{ _("Speaks about us") }}"></span>
{% endif %}
<div class="read-more-box">
{{ article.extracts |markdown }}
......
......@@ -10,8 +10,8 @@ from rp.views.articles import ArticleList
class TestArticle(TestCase):
def setUp(self):
self.article = ArticleFactory()
self.newarticle = ArticleFactory(status='NEW')
self.article = ArticleFactory(archive=False, quote=False, speak=False)
self.newarticle = ArticleFactory(status='NEW', archive=False, quote=False, speak=False)
def test_init(self):
assert RpConfig.name == "rp"
......@@ -34,9 +34,9 @@ class TestArticle(TestCase):
assert article.status == 'NEW'
assert article.score == 1
article_again = Article.add_new_url(url='https://www.example.org/article')
assert article_again.status == 'NEW'
assert article_again.score == 2
article2 = Article.add_new_url(url='https://www.example.org/article')
assert article2.status == 'NEW'
assert article2.score == 2
def test_flags(self):
assert not self.article.archive
......@@ -104,15 +104,19 @@ class TestArticleViews(TestCase):
def test_list_en(self):
r = self.client.get('/rp/international')
assert r.context['is_paginated']
assert len(r.context['object_list']) == ArticleList.paginate_by
if r.context['is_paginated']:
assert len(r.context['object_list']) == ArticleList.paginate_by
else:
assert len(r.context['object_list']) == 0
def test_filter_tag(self):
tag = self.articles[0].tags.all()[1]
r = self.client.get('/rp/by-tag/{}'.format(tag.name))
assert r.context['is_paginated']
assert len(r.context['object_list']) == ArticleList.paginate_by
if r.context['is_paginated']:
assert len(r.context['object_list']) == ArticleList.paginate_by
else:
assert len(r.context['object_list']) == 0
r = self.client.get('/rp/by-tag/zogzog')
assert len(r.context['object_list']) == 0
......@@ -126,6 +130,36 @@ class TestArticleViews(TestCase):
r = self.client.get('/rp/', {'q': 'Gargamel was here'})
assert len(r.context['article_list']) == 0
def test_search_view_archived(self):
archive = ArticleFactory(archive=True, lang='FR')
r = self.client.get('/rp/', {'q': '', 'archive': 'true'})
assert len(r.context['article_list']) == 1
assert r.context['article_list'][0] == archive
r = self.client.get('/rp/', {'archive': 'false'})
assert len(r.context['article_list']) == 0
def test_search_view_speaks(self):
speak = ArticleFactory(speak=True, lang='FR')
r = self.client.get('/rp/', {'q': '', 'speak': 'true'})
assert len(r.context['article_list']) == 1
assert r.context['article_list'][0] == speak
r = self.client.get('/rp/', {'speak': 'false'})
assert len(r.context['article_list']) == 0
def test_search_view_quoted(self):
quote = ArticleFactory(quote=True, lang='FR')
r = self.client.get('/rp/', {'q': '', 'quote': 'true'})
assert len(r.context['article_list']) == 1
assert r.context['article_list'][0] == quote
r = self.client.get('/rp/', {'quote': 'false'})
assert len(r.context['article_list']) == 0
def test_detail_view(self):
# Let's find a published article
self.client.force_login(user=self.user)
......@@ -179,11 +213,16 @@ class TestArticleApi(TestCase):
def test_api_filter_search(self):
# text = ' '.join(self.articles[0].extracts.split(' ')[:10])
text = self.articles[0].title
r = self.client.get('/api/articles-search/{}/'.format(text))
r = self.client.get('/api/articles/', {'q': text})
assert r.status_code == 200
assert r.data['count'] == 1
assert r.data['results'][0]['id'] == self.articles[0].id
def test_api_filter_flag_search(self):
r = self.client.get('/api/articles/', {'quote': 'both'})
assert r.status_code == 200
assert r.data['count'] == len(self.articles)
def test_api_tag_push_unauth(self):
a = ArticleFactory(tags=['ZogZog'],)
r = self.client.post('/api/articles/',
......
from url import URL
import re
URL_TRACKERS = [
# UTM
......
......@@ -21,24 +21,73 @@ from rp.forms import TagMultipleChoiceField
from rp.models import Article
class ArticleList(ListView):
class ArticleFilterMixin:
"""
This mixin allows to filter views using various parameters in the query
string.
"""
archive = 'both'
quote = 'both'
speak = 'both'
def get_queryset(self):
qs = super().get_queryset()
# Tags filtering
self.quote = self.request.GET.get('quote', self.quote)
self.speak = self.request.GET.get('speak', self.speak)
self.archive = self.request.GET.get('archive', self.archive)
if self.speak != 'both':
if self.speak == 'true':
qs = qs.filter(speak=True)
else:
qs = qs.filter(speak=False)
if self.quote != 'both':
if self.quote == 'true':
qs = qs.filter(quote=True)
else:
qs = qs.filter(quote=False)
if self.archive != 'both':
if self.archive == 'true':
qs = qs.filter(archive=True)
else:
qs = qs.filter(archive=False)
search_keywords = self.request.GET.get('q', '')
if search_keywords != '':
qs = qs.filter(Q(title__icontains=search_keywords)
| Q(extracts__icontains=search_keywords))
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["search"] = self.request.GET.get('q', '')
context["archive"] = self.request.GET.get('archive', self.archive)
context["speak"] = self.request.GET.get('speak', self.speak)
context["quote"] = self.request.GET.get('quote', self.quote)
return context
class ArticleList(ArticleFilterMixin, ListView):
model = Article
paginate_by = 10
template_name = "rp/article_list_public.html"
filter_lang = None
filter_tag = None
archive = 'false' # We do not want to display archived items by default
def get_queryset(self):
qs = super().get_queryset()
if self.filter_lang in ["EN", "FR"]:
qs = Article.objects.filter(lang=self.filter_lang)
qs = qs.filter(lang=self.filter_lang)
else:
qs = Article.objects.filter()
search_keywords = self.request.GET.get('q', '')
if search_keywords != '':
qs = qs.filter(Q(title__icontains=search_keywords)
| Q(extracts__icontains=search_keywords))
qs = qs.filter()
filter_tag = self.kwargs.get("filter_tag", self.filter_tag)
if filter_tag is not None:
......@@ -53,22 +102,22 @@ class ArticleList(ListView):
num_times=Count('taggit_taggeditem_items')).all()
qs = qs.order_by('-num_times')
context["tags"] = qs
context["search"] = self.request.GET.get('q', '')
return context
class ArticleListFlux(LoginRequiredMixin, ListView):
class ArticleListFlux(LoginRequiredMixin, ArticleFilterMixin, ListView):
model = Article
paginate_by = 10
def get_queryset(self):
filter_view = self.kwargs.get("filter_view", "draft")
qs = super().get_queryset()
if filter_view in ["published", "draft", "rejected"]:
qs = Article.objects.filter(status=filter_view.upper())
qs = qs.filter(status=filter_view.upper())
else:
qs = Article.objects.filter(status="NEW")
qs = qs.filter(status="NEW")
return qs.order_by('-created_at')
......
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