diff --git a/authors/apps/articles/tests/test_filter_search_functionality.py b/authors/apps/articles/tests/test_filter_search_functionality.py new file mode 100644 index 0000000..56e2da4 --- /dev/null +++ b/authors/apps/articles/tests/test_filter_search_functionality.py @@ -0,0 +1,152 @@ +from django.urls import reverse +from rest_framework import status +from ...authentication.tests.base_class import BaseTest + + +class TestFilterSearchFunctionality(BaseTest): + """ + TestFilterSearchFunctionality handles testing of the search and filter + functionality of articles + """ + def setUp(self): + super().setUp() + self.user = self.activated_user() + self.client.force_authenticate(user=self.user) + self.article = self.create_article(self.user) + self.url = reverse('articles:articles') + + def add_tags_to_article(self): + """ + add_tags_to_article method adds a tag to an article + """ + self.article.tag_list = ["okay"] + self.article.save() + + def test_user_can_filter_articles_by_author_username(self): + """ + Test user can filter articles based on author username + """ + username = self.user.username + response = self.client.get(f"{self.url}?author={username}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?author=vdhfvsf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_filter_by_articles_by_title(self): + """ + Test user can filter articles based on article title + """ + title = self.article.title + response = self.client.get(f"{self.url}?title={title}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?title=vdhfvsf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_filter_by_articles_by_tag(self): + """ + Test user can filter articles based on article tag + """ + self.add_tags_to_article() + tag = self.article.tag_list[0] + response = self.client.get(f"{self.url}?tag={tag}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?tag=vdhfvsf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_filter_by_author_username_and_article_title(self): + """ + Test user can filer for articles based on author username and article + title + """ + username = self.user.username + title = self.article.title + response = self.client.get( + f"{self.url}?author={username}&title={title}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?title=vfvsf&author={username}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + response = self.client.get(f"{self.url}?title={title}&author=skjfg") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_filter_by_author_username_and_tag(self): + """ + Test user can filer for articles based on author username and tag + """ + self.add_tags_to_article() + username = self.user.username + tag = self.article.tag_list[0] + response = self.client.get( + f"{self.url}?author={username}&tag={tag}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?tag=vfvsf&author={username}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + response = self.client.get(f"{self.url}?tag={tag}&author=skjfg") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_filter_by_author_username_article_title_and_tag(self): + """ + Test user can filer for articles based on author username, article + title and tag + """ + self.add_tags_to_article() + username = self.user.username + title = self.article.title + tag = self.article.tag_list[0] + response = self.client.get( + f"{self.url}?author={username}&tag={tag}&title={title}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get( + f"{self.url}?tag=vfvsf&author={username}&title={title}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + response = self.client.get( + f"{self.url}?tag={tag}&author=skjfg&title={title}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + response = self.client.get( + f"{self.url}?tag={tag}&author={username}&title=hjsdgfhj" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) + + def test_user_can_search_for_articles_by_keywords(self): + """ + Test user can search for articles based on keywords + """ + username = self.user.username + title = self.article.title + description = self.article.description + body = self.article.body + response = self.client.get(f"{self.url}?search={username}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?search={title}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?search={description}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?search={body}") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + response = self.client.get(f"{self.url}?search=vdhfvsf") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 0) diff --git a/authors/apps/articles/utils/custom_filters.py b/authors/apps/articles/utils/custom_filters.py new file mode 100644 index 0000000..bded488 --- /dev/null +++ b/authors/apps/articles/utils/custom_filters.py @@ -0,0 +1,19 @@ +from django_filters import FilterSet, rest_framework +from ..models import Article + + +class ArticleFilter(FilterSet): + """ + ArtcleFilter is a custom filter class which makes all my filter fields + case insensitive + """ + title = rest_framework.CharFilter('title', + lookup_expr='icontains') + author = rest_framework.CharFilter('author__username', + lookup_expr='icontains') + tag = rest_framework.CharFilter('tag_list', + lookup_expr='icontains') + + class Meta: + model = Article + fields = ("title", "author", "tag", ) diff --git a/authors/apps/articles/views.py b/authors/apps/articles/views.py index f0441e1..6e9c9d1 100644 --- a/authors/apps/articles/views.py +++ b/authors/apps/articles/views.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.response import Response -from rest_framework import generics -from rest_framework import permissions +from rest_framework import generics, permissions, filters +from django_filters.rest_framework import DjangoFilterBackend from . import ( serializers, @@ -13,6 +13,7 @@ from ..profiles import models as profile_model from .models import Rating, Article +from .utils.custom_filters import ArticleFilter class ArticlesApiView (generics.ListCreateAPIView): @@ -23,6 +24,9 @@ class ArticlesApiView (generics.ListCreateAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.ArticleSerializer renderer_classes = (ArticleJSONRenderer,) + filter_backends = (DjangoFilterBackend, filters.SearchFilter, ) + filter_class = ArticleFilter + search_fields = ('author__username', 'description', 'body', 'title', ) def post(self, request): data = request.data diff --git a/authors/settings/base.py b/authors/settings/base.py index 4b1a6bf..5ef8085 100644 --- a/authors/settings/base.py +++ b/authors/settings/base.py @@ -41,6 +41,7 @@ 'rest_framework', 'drf_yasg', 'mailer', + 'django_filters', 'authors.apps.authentication', 'authors.apps.core', @@ -84,18 +85,27 @@ # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators +USER_ATTRIBUTE_SIMILARITY_VALIDATOR = \ + 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator' +MINIMUM_LENGTH_VALIDATOR = \ + 'django.contrib.auth.password_validation.MinimumLengthValidator' +COMMON_PASSWORD_VALIDATOR = \ + 'django.contrib.auth.password_validation.CommonPasswordValidator' +NUMERIC_PASSWORD_VALIDATOR = \ + 'django.contrib.auth.password_validation.NumericPasswordValidator' + AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'NAME': USER_ATTRIBUTE_SIMILARITY_VALIDATOR, }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'NAME': MINIMUM_LENGTH_VALIDATOR, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + 'NAME': COMMON_PASSWORD_VALIDATOR, }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + 'NAME': NUMERIC_PASSWORD_VALIDATOR, }, ] diff --git a/requirements.txt b/requirements.txt index e124c1d..2b754eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ Django==2.1.5 django-cloudinary-storage==0.2.3 django-cors-middleware==1.3.1 django-extensions==2.1.4 +django-filter==2.1.0 djangorestframework==3.9.1 docopt==0.6.2 drf-yasg==1.13.0