Skip to content

Commit

Permalink
feat(search): add a search endpoint
Browse files Browse the repository at this point in the history
- search articles by specific author/title/tags
- enable article ordering using author/title
- enable a general filter

[Starts #162949228]
  • Loading branch information
kwanj-k committed Feb 4, 2019
1 parent a2a6c08 commit 2826451
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 18 deletions.
2 changes: 1 addition & 1 deletion authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import Article, Comments, LikeDislike
from .models import Article, Comments, LikeDislike, FavoriteArticle
from authors.apps.profiles.models import UserProfile
from taggit_serializer.serializers import (
TagListSerializerField,
Expand Down
3 changes: 2 additions & 1 deletion authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
GetFavouriteArticles)
from authors.apps.articles.views.articles import (GetUpdateDeleteArticle,
CreateArticleView,
SearchFilter,
LikeDislikeArticleView)


"""
Django 2.0 requires the app_name variable set when using include namespace
"""
Expand Down
90 changes: 76 additions & 14 deletions authors/apps/articles/views/articles.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
from rest_framework.generics import (
RetrieveUpdateDestroyAPIView, ListCreateAPIView,
ListAPIView
ListAPIView,
)
from rest_framework.permissions import (
IsAuthenticatedOrReadOnly, IsAuthenticated
Expand All @@ -9,6 +10,11 @@
from rest_framework.views import status
from django.shortcuts import get_object_or_404
from django.contrib.contenttypes.models import ContentType
from rest_framework.permissions import AllowAny
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as filters
from rest_framework.filters import SearchFilter, OrderingFilter
from taggit.models import Tag

# local imports
from authors.apps.articles.models import Article, LikeDislike
Expand All @@ -17,17 +23,14 @@
LikeDislikeSerializer, CustomTagSerializer,
UpdateArticleSerializer,
)

from authors.apps.articles.renderers import (
ArticleJSONRenderer, LikeArticleJSONRenderer,
ArticleJSONRenderer,
LikeArticleJSONRenderer,
TagJSONRenderer
)

from authors.apps.articles.response_messages import (error_messages,
success_messages)
from authors.apps.core.pagination import CustomPagination
from rest_framework import mixins
from taggit.models import Tag


class CreateArticleView(ListCreateAPIView):
Expand Down Expand Up @@ -168,10 +171,10 @@ def post(self, request, slug):
return Response({
"likes": article.votes.likes().count(),
"dislikes": article.votes.dislikes().count(),
},
content_type="application/json",
status=status.HTTP_201_CREATED
)
},
content_type="application/json",
status=status.HTTP_201_CREATED
)

def get(self, request, slug):

Expand All @@ -180,10 +183,10 @@ def get(self, request, slug):
return Response({
"likes": article.votes.likes().count(),
"dislikes": article.votes.dislikes().count(),
},
content_type="application/json",
status=status.HTTP_200_OK
)
},
content_type="application/json",
status=status.HTTP_200_OK
)


class TagView(ListAPIView):
Expand All @@ -193,3 +196,62 @@ class TagView(ListAPIView):
renderer_classes = (TagJSONRenderer,)
serializer_class = CustomTagSerializer
queryset = Tag.objects.all()


class ArticleFilter(filters.FilterSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

author = filters.CharFilter(
field_name='author__username', lookup_expr='icontains')
title = filters.CharFilter(field_name='title', lookup_expr='icontains')
tag_list = filters.CharFilter(field_name='tag_list', method='get_tags')

def get_tags(self, queryset, name, value):

return queryset.filter(tag_list__name__icontains=value)

class Meta:
model = Article
fields = ['author', 'title', 'tag_list']


class MyFilterBackend(filters.DjangoFilterBackend):
def get_filterset_kwargs(self, request, queryset, view):
kwargs = super().get_filterset_kwargs(request, queryset, view)

# merge filterset kwargs provided by view class
if hasattr(view, 'get_filterset_kwargs'):
kwargs.update(view.get_filterset_kwargs(request))

return kwargs


class CustomSearchFilter(ListAPIView):
serializer_class = ArticleSerializer
permission_classes = (AllowAny,)
queryset = Article.objects.all()
filter_backends = (MyFilterBackend, SearchFilter, OrderingFilter)
filterset_class = ArticleFilter
search_fields = (
'tag_list__name',
'author__username',
'title', 'body',
'description'
)
ordering_fields = ('created_at', 'updated_at')

def get_filterset_kwargs(self, request):
title = request.GET.get('title', '')
author = request.GET.get('author', '')
tag_list = request.GET.get('tag_list', '')

if not request.GET._mutable:
request.GET._mutable = True
request.GET['title'] = re.sub("\s\s+", " ", title)
request.GET['author'] = re.sub("\s\s+", " ", author)
request.GET['tag_list'] = re.sub("\s\s+", " ", tag_list)
kwargs = {
'data': request.query_params
}
return kwargs
1 change: 1 addition & 0 deletions authors/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
'rest_framework_social_oauth2',
'taggit',
'taggit_serializer',
'django_filters',

'authors.apps.authentication',
'authors.apps.core',
Expand Down
45 changes: 45 additions & 0 deletions authors/tests/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import json
from rest_framework.views import status
from django.urls import reverse

# local imports
from .base_test import TestBase


class TestFilterSet(TestBase):

def test_get_article_by_author(self):
"""test_get_article_by_author """
self.verify_user()
self.client.get(self.get_verify_url(self.user_data))
self.client.post(
self.article_url,
data=json.dumps(self.valid_article_data),
content_type='application/json'
)
url = reverse('search') + '?author = {}'.format('sam')
response = self.client.get(
url,
content_type='application/json'
)
articles = response.data['results']
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(articles), 1)

def test_get_article_by_tag(self):
"""test_get_article_by_tag """
self.verify_user()
self.client.get(self.get_verify_url(self.user_data))
self.client.post(
self.article_url,
data=json.dumps(self.valid_article_data),
content_type='application/json'
)
url = reverse('search') + '?tagList = {}'.format('young')
response = self.client.get(
url,
content_type='application/json'
)
articles = response.data['results']
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(articles), 1)
6 changes: 4 additions & 2 deletions authors/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from django.urls import include, path
from django.contrib import admin
from rest_framework_swagger.views import get_swagger_view
from authors.apps.articles.views.articles import TagView
from authors.apps.articles.views.articles import TagView, CustomSearchFilter

# set the title for the API.
schema_view = get_swagger_view(title="Authors Haven API")

Expand All @@ -33,5 +34,6 @@
path('auth/', include('rest_framework_social_oauth2.urls')),
path('api/', include('authors.apps.ratings.urls', namespace='ratings')),
path('api/tags', TagView.as_view()),
path('', schema_view)
path('', schema_view),
path('api/search', CustomSearchFilter.as_view(), name='search'),
]
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ django-autoslug==1.9.3
django-taggit==0.23.0
django-taggit-serializer==0.1.7
django-simple-history==2.7.0
django-filter==2.1.0

0 comments on commit 2826451

Please sign in to comment.