Skip to content

Commit

Permalink
Merge 849f626 into 9d95941
Browse files Browse the repository at this point in the history
  • Loading branch information
wrotich committed Nov 14, 2018
2 parents 9d95941 + 849f626 commit 43de8d3
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ install:
script:
- python3 manage.py makemigrations authentication
- python3 manage.py migrate authentication
- python3 manage.py makemigrations profiles
- python3 manage.py migrate profiles
- python3 manage.py makemigrations article
- python3 manage.py migrate article
- python3 manage.py makemigrations profiles
- python3 manage.py makemigrations
- python3 manage.py migrate
- coverage run manage.py test
Expand Down
31 changes: 31 additions & 0 deletions authors/apps/article/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
from django.db import models
from django.utils.translation import pgettext_lazy as _

from authors.apps.authentication.models import User
from authors.apps.core.models import TimestampedModel
from authors.apps.profiles.models import UserProfile

'''Django-autoslug is a reusable Django library
that provides an improved slug field which can automatically:
populate itself from another field and preserve
Expand Down Expand Up @@ -142,3 +146,30 @@ class LikeDislikeArticle(models.Model):
def __str__(self):
"return human readable format"
return self.is_liked


class Comments(TimestampedModel):
"""
This class handles the comments of a single article
"""
parent = models.ForeignKey(
'self',
null=True,
blank=False,
on_delete=models.CASCADE,
related_name='thread')
body = models.TextField(max_length=200)
author = models.ForeignKey(
User,
related_name='comments',
on_delete=models.CASCADE,
blank=True,
null=True)
article = models.ForeignKey(
Article,
related_name='comments',
on_delete=models.CASCADE,
null=True)

def __str__(self):
return self.body
26 changes: 26 additions & 0 deletions authors/apps/article/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import json

from authors.apps.core.renderers import AuthorsJSONRenderer


class CommentsRenderer(AuthorsJSONRenderer):
charset = 'utf-8'
object_name = 'comments'

def render(self, data, media_type=None, renderer_context=None):
"""
Check that the JSONRenderer handles the rendering
of the errors that are thrown from the views
"""
errors = data
if errors is not None:
return super(AuthorsJSONRenderer, self).render(data)

return json.dumps({
self.object_label: data
})


class CommentsThreadsRenderer(AuthorsJSONRenderer):
charset = 'utf-8'
object_name = 'threads'
54 changes: 53 additions & 1 deletion authors/apps/article/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

from rest_framework import serializers
from django.apps import apps
from .models import RateArticle

from authors.apps.profiles.models import UserProfile
from .models import RateArticle, Comments
from authors.apps.profiles.serializers import ProfileListSerializer

TABLE = apps.get_model('article', 'Article')
Expand Down Expand Up @@ -134,3 +136,53 @@ def validate(self, data):
class Meta:
model = RateArticle
fields = ("slug", "rate")


class ThreadSerializer(serializers.ModelSerializer):
def to_representation(self, value):
comment_details = self.parent.parent.__class__(
value, context=self.context)
return comment_details.data

class Meta:
model = Comments
fields = '__all__'


class CommentsSerializer(serializers.ModelSerializer):
"""
Serializer class for comments
"""
author = serializers.SerializerMethodField(read_only=True)
thread = ThreadSerializer(many=True, read_only=True)

class Meta:
model = Comments
fields = ('id', 'body', 'author',
'created_at', 'updated_at', 'thread')

def get_author(self, obj):
try:
serializer = ProfileListSerializer(
instance=UserProfile.objects.get(user=obj.author)
)
return serializer.data
except Exception:
return {}

def create(self, validated_data):
if self.context.get('parent'):
parent = self.context.get('parent', None)
instance = Comments.objects.create(parent=parent, **validated_data)
else:
instance = Comments()
instance.author = self.context['request'].user
instance.article = self.context['article']
instance.body = validated_data.get('body')
instance.save()
return instance

def update(self, instance, validated_data):
instance.body = validated_data.get('body', instance.body)
instance.save()
return instance
226 changes: 226 additions & 0 deletions authors/apps/article/tests/test_article_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework.test import APITestCase
from rest_framework import status


class TestArticleComments(APITestCase):
def setUp(self):
"""
Prepare test environment for each testcase
"""
self.client = APIClient()
self.author_details = {
'user': {
'username': 'admin123',
'email': 'admin@admin.com',
'password': 'admin2018',
}
}

self.login_data = {
"user": {
'email': 'admin@admin.com',
'password': 'admin2018',
}
}

self.new_comment = {
"comments": {
"body": "where are"
}
}
self.wrong_comment = {
"comment": {
}
}
self.my_comment = {
"comment": {
"body": "where are fight with batman"
}
}
self.new_article = {
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "You have to believe"
}

self.login_url = reverse('authentication:login')
self.signup_url = reverse('authentication:register')
self.articles_url = reverse('article:create')

self.client.post(
self.signup_url,
self.author_details,
format='json')

def login(self):
"""login user to get the token"""
response = self.client.post(
self.login_url, self.login_data, format='json')
return response.data.get('token')

def test_add_comment(self):
"""
test add a comment
"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(comments_url, self.new_comment, format='json')
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)

def test_add_comment_to_unavailable_article(self):
"""
Test add comment to an article that does not exist
"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': 'slug'})
response2 = self.client.post(
comments_url, self.new_comment, format='json')
self.assertEqual(response2.status_code, 404)
assert (response2.data['detail'] == "Not found.")

def test_add_comment_without_token(self):
"""
test add comment without authorization
"""
self.token = self.login()
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(comments_url, self.new_comment, format='json')
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)

def test_post_comment_without_body(self):
"""
Test post comment without comment body
"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(
comments_url,
self.wrong_comment,
format='json')
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)

def test_list_comments(self):
"""Test list all comments on an article"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
list_comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.get(list_comments_url)
self.assertEqual(resp.status_code, status.HTTP_200_OK)

def test_list_comments_without_token(self):
"""Test list comments without authorization"""
self.token = self.login()
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
list_comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.get(list_comments_url)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)

def test_list_comments_for_invalid_article(self):
"""Test list comments for an article that does not exist"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
list_comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': 'slug'})
resp = self.client.get(list_comments_url)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)

def test_user_can_update_comment(self):
"""test user should be able to update a comment for a given article"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(
comments_url, {
'comments': {
'body': 'sdfsdfs sdf sdf sdfdfsdf'}}, format='json')
url = reverse(
'article:delete_comment',
kwargs={
'slug': response.data.get('slug'),
'comment_id': resp.data['id']})
response1 = self.client.put(
url, {'body': 'sdfsdfs sdf sdf sdfdfsdf'}, format='json')
self.assertEqual(response1.status_code, status.HTTP_200_OK)

def test_delete_comments(self):
"""test user should be able to delete a comment for a given article"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(comments_url, self.new_comment, format='json')
url = reverse(
'article:delete_comment',
kwargs={
'slug': response.data.get('slug'),
'comment_id': resp.data['id']})
response1 = self.client.delete(url)
self.assertEqual(response1.status_code, status.HTTP_200_OK)

def test_delete_unavailable_comment(self):
"""Test deleting a comment that does not exist"""
self.token = self.login()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.post(
self.articles_url,
self.new_article,
format='json')
comments_url = reverse(
'article:comment_on_an_article', kwargs={
'slug': response.data.get('slug')})
resp = self.client.post(comments_url, self.new_comment, format='json')
url = reverse(
'article:delete_comment',
kwargs={
'slug': resp.data.get('slug'),
'comment_id': 1223})
response1 = self.client.delete(url)
self.assertEqual(response1.status_code, status.HTTP_404_NOT_FOUND)
12 changes: 9 additions & 3 deletions authors/apps/article/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
ArticleDetailAPIView,
ArticleUpdateAPIView,
Rate,
ArticleRate
)
ArticleRate,
CommentsListCreateView,
CommentsUpdateDeleteAPIView)
from .likes_dislike_views import (
Like,
Dislike
Expand Down Expand Up @@ -53,5 +54,10 @@
"article/dislike/<str:slug>/",
Dislike.as_view(),
name='dislike_article'),

path('articles/<slug:slug>/comments',
CommentsListCreateView.as_view(),
name='comment_on_an_article'),
path('articles/<slug:slug>/comments/<int:comment_id>',
CommentsUpdateDeleteAPIView.as_view(),
name='delete_comment'),
]
Loading

0 comments on commit 43de8d3

Please sign in to comment.