Skip to content

Commit

Permalink
feat(searchArticles): search articles by author, tags, favourited
Browse files Browse the repository at this point in the history
 - Ensure system users are able to search articles by author, tags, favourited

[Finishes #164069234]
  • Loading branch information
BagzieGracious committed Mar 19, 2019
1 parent e8a6284 commit 2bcea96
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 21 deletions.
10 changes: 1 addition & 9 deletions authors/apps/articles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Generated by Django 2.1.5 on 2019-03-14 13:36
# Generated by Django 2.1.5 on 2019-03-19 08:16

from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
Expand All @@ -11,8 +10,6 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('profiles', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
Expand All @@ -33,8 +30,6 @@ class Migration(migrations.Migration):
('tagList', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, default=list, size=None)),
('favorited', models.BooleanField(default=False)),
('favoritesCount', models.IntegerField(default=0)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='profiles.Profile')),
('favorites', models.ManyToManyField(blank=True, related_name='favorited_articles', to='profiles.Profile')),
],
options={
'ordering': ['-created_at'],
Expand All @@ -47,8 +42,6 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('likes', models.BooleanField(default=False, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='profiles.Profile')),
],
),
migrations.CreateModel(
Expand All @@ -58,7 +51,6 @@ class Migration(migrations.Migration):
('rated_on', models.DateTimeField(auto_now_add=True)),
('score', models.DecimalField(decimal_places=2, max_digits=5)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to='articles.Article')),
('rated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scores', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-score'],
Expand Down
44 changes: 44 additions & 0 deletions authors/apps/articles/migrations/0002_auto_20190319_0816.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 2.1.5 on 2019-03-19 08:16

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('profiles', '0001_initial'),
('articles', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='rating',
name='rated_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scores', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='articlelikesdislikes',
name='article',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article'),
),
migrations.AddField(
model_name='articlelikesdislikes',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='profiles.Profile'),
),
migrations.AddField(
model_name='article',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_articles', to='profiles.Profile'),
),
migrations.AddField(
model_name='article',
name='favorites',
field=models.ManyToManyField(blank=True, related_name='favorited_articles', to='profiles.Profile'),
),
]
14 changes: 10 additions & 4 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ def unfavorite_an_article(self, request_user, slug):

class Article(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Profile, on_delete=models.CASCADE)
author = models.ForeignKey(
Profile,
on_delete=models.CASCADE,
related_name="author_articles"
)
body = models.TextField()
slug = models.SlugField(unique=True, blank=True)
description = models.CharField(max_length=100)
Expand All @@ -52,12 +56,14 @@ class Article(models.Model):
updated_at = models.DateTimeField(auto_now=True, null=True)
image = models.URLField(blank=True)
user_rating = models.CharField(max_length=10, default='0')
tagList = ArrayField(models.CharField(
max_length=200), default=list, blank=True)
tagList = ArrayField(
models.CharField(max_length=200),
default=list,
blank=True,
)
favorites = models.ManyToManyField(Profile, related_name='favorited_articles', blank=True)
favorited = models.BooleanField(default=False)
favoritesCount = models.IntegerField(default=0)

objects = ArticleManager()
class Meta:
ordering = ['-created_at']
Expand Down
6 changes: 3 additions & 3 deletions authors/apps/articles/tests/base_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ def setUp(self):

def add_article(self):
self.register_and_login_user()
response = self.client.post(self.articles_url,
self.client.post(self.articles_url,
data=valid_article,
format='json')
self.client.post(self.articles_url,
return self.client.post(self.articles_url,
data=valid_article,
format='json')

def add_tagged_article(self):
self.register_and_login_user()
self.client.post(self.articles_url,
return self.client.post(self.articles_url,
data=valid_article_with_tags,
format='json')

Expand Down
37 changes: 37 additions & 0 deletions authors/apps/articles/tests/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from django.urls import reverse

from rest_framework import status

from authors.apps.articles.tests.base_class import ArticlesBaseTest
from .test_data import valid_article
from ..models import Article


class TestSearchArticle(ArticlesBaseTest):

def test_search_by_keyword(self):
"""
Tests if a user can search articles by keyword
"""
self.add_article()
response = self.client.get(self.articles_url+"?keyword=the")
self.assertIn("the", response.data['articles'][0]['description'])
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_search_by_tag(self):
"""
Tests if a user can search articles by tag
"""
self.add_tagged_article()
response = self.client.get(self.articles_url+"?tag=apps")
self.assertIn("apps", response.data['articles'][0]['tagList'])
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_search_by_author(self):
"""
Tests if a user can search articles by an author
"""
self.add_tagged_article()
response = self.client.get(self.articles_url+"?author=Bagzie12")
self.assertIn("Bagzie12", response.data['articles'][0]['author']['username'])
self.assertEqual(response.status_code, status.HTTP_200_OK)
55 changes: 52 additions & 3 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from itertools import chain
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework import status
from rest_framework import status, filters
from rest_framework import generics, permissions
from . import (
serializers,
Expand All @@ -16,8 +18,54 @@
class ArticlesApiView (generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
serializer_class = serializers.ArticleSerializer
queryset = Article.objects.all()
pagination_class = ArticlesLimitOffsetPagination
queryset = Article.objects.all()
search_fields = (
'title',
'body',
'description',
'tagList',
'author__username',
'favorited_articles'
)

def get_queryset(self):
queryset = self.queryset
tag = self.request.query_params.get('tag', None)
keyword = self.request.query_params.get('keyword', None)
favorite = self.request.query_params.get('favorite', None)
author = self.request.query_params.get('author', None)

queryset_keyword, queryset_tag, queryset_default, \
queryset_favorite, queryset_author = [], [], [], [], []

if keyword:
result = (
Q(title__icontains=keyword) |
Q(body__icontains=keyword) |
Q(description__icontains=keyword)
)
queryset_keyword = queryset.filter(result)

elif tag:
queryset_tag = queryset.filter(tagList__icontains=tag)
elif favorite:
queryset_favorite = \
queryset.filter(favorites__user__username__icontains=favorite)
elif author:
queryset_author = \
queryset.filter(author__user__username__icontains=author)
else:
queryset_default = queryset.all()

queryset = list(chain(
queryset_keyword,
queryset_tag,
queryset_favorite,
queryset_author,
queryset_default
))
return queryset

def post(self, request):
data = request.data.get('article')
Expand Down Expand Up @@ -187,7 +235,8 @@ def get(self, request):
for tag in Article.get_all_tags():
merged += tag
return Response({"tags": set(merged)})



class FavoriteHandlerView(generics.GenericAPIView):
permission_classes = [permissions.IsAuthenticated, ]
renderer_classes = [ArticleJSONRenderer, ]
Expand Down
2 changes: 1 addition & 1 deletion authors/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.1.5 on 2019-03-14 07:12
# Generated by Django 2.1.5 on 2019-03-19 08:16

from django.db import migrations, models

Expand Down
1 change: 1 addition & 0 deletions authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class ChangePasswordAPIView(generics.GenericAPIView):
# Allow any user (authenticated or not) to hit this endpoint.
# then allows users to set password
permission_classes = (AllowAny,)
serializer_class = UserSerializer

def patch(self, request):
try:
Expand Down
2 changes: 1 addition & 1 deletion authors/apps/profiles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.1.5 on 2019-03-14 07:12
# Generated by Django 2.1.5 on 2019-03-19 08:16

from django.conf import settings
from django.db import migrations, models
Expand Down
5 changes: 5 additions & 0 deletions authors/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_filters',

'corsheaders',
'django_extensions',
Expand Down Expand Up @@ -147,6 +148,10 @@
'DEFAULT_AUTHENTICATION_CLASSES': (
'authors.apps.authentication.backends.JWTAuthentication',
),

'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
)
}

SWAGGER_SETTINGS = {
Expand Down

0 comments on commit 2bcea96

Please sign in to comment.