Skip to content

Commit

Permalink
Refactor content template
Browse files Browse the repository at this point in the history
- move reactions part to a separate template
- add translation capabilities to this template.

Create new templates for reactions templates
- handles url for unauthenticated users.
- like and dislike icon moved to a separate template.

Remove Reaction manager
- it wasn't used anywhere in the application.

Change request method for reaction
- GET allows redirect for unauthenticated users
- make the corresponding change to the javascript function

Changes in tests
- add tests in models for post delete signal.
- refactor code for view tests.
- remove unnecessary part.
  • Loading branch information
abhiabhi94 committed May 15, 2020
1 parent e9913da commit 0aee068
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 80 deletions.
3 changes: 1 addition & 2 deletions comment/manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# flake8: noqa
from comment.manager.comments import *
from comment.manager.reactions import *
from comment.manager.comments import *
10 changes: 0 additions & 10 deletions comment/manager/reactions.py

This file was deleted.

10 changes: 3 additions & 7 deletions comment/models/reactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from comment.manager.reactions import ReactionManager
from comment.models import Comment


Expand All @@ -17,9 +16,6 @@ class Reaction(models.Model):
likes = models.PositiveIntegerField(default=0)
dislikes = models.PositiveIntegerField(default=0)

objects = models.Manager()
comment_objects = ReactionManager()

def _increase_likes(self):
"""Increase likes and save the model"""
self.likes = models.F('likes') + 1
Expand All @@ -32,12 +28,14 @@ def _increase_dislikes(self):

def _decrease_likes(self):
"""Decrease likes and save the model"""
self.refresh_from_db()
if self.likes > 0:
self.likes = models.F('likes') - 1
self.save()

def _decrease_dislikes(self):
"""Decrease dislikes and save the model"""
self.refresh_from_db()
if self.dislikes > 0:
self.dislikes = models.F('dislikes') - 1
self.save()
Expand Down Expand Up @@ -162,7 +160,7 @@ def set_reaction(cls, user, comment, reaction_type):
reaction (str): the reaction that needs to be added.
Returns:
bool: Returns True if a reaction is updated successfully
None
"""
reaction_type = cls.clean_reaction_type(reaction_type=reaction_type)
reaction_obj = Reaction.objects.get(comment=comment)
Expand All @@ -187,8 +185,6 @@ def set_reaction(cls, user, comment, reaction_type):
reaction_type=reaction_type
)

return True


@receiver(post_delete, sender=ReactionInstance)
def delete_reaction_instance(sender, instance, using, **kwargs):
Expand Down
3 changes: 1 addition & 2 deletions comment/static/js/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ $(function() {
* @param {Number} dislikes - dislike count
*/
var changeReactionCount = function($parent, likes, dislikes) {
console.log($parent);
$parent.find('.js-like-number')[0].textContent = likes;
$parent.find('.js-dislike-number')[0].textContent = dislikes;
}
Expand All @@ -273,7 +272,7 @@ $(function() {
var $csrfToken = $element.attr('data-csrf');
$.ajax({
headers: { 'X-CSRFToken': $csrfToken },
method: "POST",
method: "GET",
url: $thisURL,
contentType: 'application/json',
success: function commentReactionDone(data, textStatus, jqXHR) {
Expand Down
33 changes: 9 additions & 24 deletions comment/templates/comment/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,23 @@
<small>
<a class="{% block user_name %}{% endblock user_name %}" href="{% get_profile_url obj %}">{{ obj.user }}</a> |
{% if obj.is_edited %}
<span class="font-italic" title="Edited">Edited: {{ obj.edited|timesince }} ago</span>
<span class="font-italic" title="{% trans 'Edited' %}">{% blocktrans with edited_time=obj.edited|timesince %}Edited: {{ edited_time }} ago{% endblocktrans %}</span>
{% else %}
<span class="font-italic" title="Posted">{{ obj.posted|timesince }} ago</span>
<span class="font-italic" title="{% trans 'Posted' %}">{% blocktrans with posted_time=obj.posted|timesince %}{{ posted_time }} ago{% endblocktrans %}</span>
{% endif %}
{% if obj.user == user %}
| <a class="js-comment-edit {% block edit_link_cls %}btn btn-link{% endblock edit_link_cls %}" href="{% url 'comment:edit' pk=obj.id %}?comments_per_page={{comments_per_page}}&model_name={% get_model_name model_object %}&model_id={{ model_object.id }}&app_name={% get_app_name model_object %}&oauth={{oauth}}">edit</a>
<button class="js-comment-delete {% block delete_btn_cls %}btn btn-link{% endblock delete_btn_cls %}" data-url="{% url 'comment:delete' pk=obj.id %}?comments_per_page={{comments_per_page}}&model_name={% get_model_name model_object %}&model_id={{ model_object.id }}&app_name={% get_app_name model_object %}&oauth={{oauth}}">delete</button>
| <a class="js-comment-edit {% block edit_link_cls %}btn btn-link{% endblock edit_link_cls %}" href="{% url 'comment:edit' pk=obj.id %}?comments_per_page={{comments_per_page}}&model_name={% get_model_name model_object %}&model_id={{ model_object.id }}&app_name={% get_app_name model_object %}&oauth={{oauth}}">{% trans "edit" %}</a>
<button class="js-comment-delete {% block delete_btn_cls %}btn btn-link{% endblock delete_btn_cls %}" data-url="{% url 'comment:delete' pk=obj.id %}?comments_per_page={{comments_per_page}}&model_name={% get_model_name model_object %}&model_id={{ model_object.id }}&app_name={% get_app_name model_object %}&oauth={{oauth}}">{% trans "delete" %}</button>
{% endif %}
{% if is_parent %}
| <span class="js-reply-number text-dark">{{ obj.replies.count }}</span>
{% with reply_count=obj.replies.count %}
| <span class="js-reply-number text-dark">{{ reply_count }}</span>
<a class="js-reply-link {% block reply_link_cls %}btn btn-link ml-1{% endblock reply_link_cls %}" href="#">
Repl{% if obj.replies.count <= 1 %}y{% else %}ies{% endif %}
{% blocktrans with plural_str=reply_count|pluralize:"y,ies" %} Repl{{ plural_str }} {% endblocktrans %}
{% endwith %}
</a>
{% endif %}
</small>
<div class="js-comment-reactions mt-2">
<span class="js-like-number text-dark">{{ obj.likes }}</span>
<a class="js-comment-reaction {% block like_link_cls %}" {% endblock like_link_cls %} href="{% url 'comment:react' comment_id=obj.id reaction='like' %}" data-csrf="{{ csrf_token }}">
{% block like_img_icon %}
<svg class="comment-reaction-icon reaction-like" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={% if obj|add_one_arg:user|has_reacted:"like" %}"#ffc966"{% else %}"none"{% endif %} stroke="#427297" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
{% endblock like_img_icon %}
</a>
<span class="ml-4 js-dislike-number text-dark">{{ obj.dislikes }}</span>
<a class="js-comment-reaction {% block dislike_link_cls %}" {% endblock dislike_link_cls %} href="{% url 'comment:react' comment_id=obj.id reaction='dislike' %}" data-csrf="{{ csrf_token }}">
{% block dislike_img_icon %}
<svg class="comment-reaction-icon reaction-dislike" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={% if obj|add_one_arg:user|has_reacted:"dislike" %}"#ffc966"{% else %}"none"{% endif %} stroke="#427297" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>
</svg>
{% endblock dislike_img_icon %}
</a>
</div>
{% include "comment/reactions.html" with comment=obj %}
</footer>
</div>
6 changes: 6 additions & 0 deletions comment/templates/comment/dislike_icon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% load comment_tags %}
{% block dislike_img_icon %}
<svg class="comment-reaction-icon reaction-dislike" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={% if comment|add_one_arg:user|has_reacted:"dislike" %}"#ffc966"{% else %}"none"{% endif %} stroke="#427297" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
{% endblock dislike_img_icon %}
6 changes: 6 additions & 0 deletions comment/templates/comment/like_icon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% load comment_tags %}
{% block like_img_icon %}
<svg class="comment-reaction-icon reaction-like" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill={% if comment|add_one_arg:user|has_reacted:"like" %}"#ffc966"{% else %}"none"{% endif %} stroke="#427297" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
</svg>
{% endblock like_img_icon %}
25 changes: 25 additions & 0 deletions comment/templates/comment/reactions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% load i18n %}
<div class="js-comment-reactions mt-2">
<span class="js-like-number text-dark">{{ comment.likes }}</span>
{% url 'comment:react' comment_id=comment.id reaction='like' as like_url %}
<a title="{% trans "like" %}" class="{% block like_link_cls %} {% endblock like_link_cls %}
{% if user.is_authenticated %}
js-comment-reaction" href="{{ like_url }}" data-csrf="{{ csrf_token }}"
{% else %}
" href="{{ login_url }}/?next={{ like_url }}"
{% endif %}
>
{% include "comment/like_icon.html" %}
</a>
<span class="ml-4 js-dislike-number text-dark">{{ comment.dislikes }}</span>
{% url 'comment:react' comment_id=comment.id reaction='dislike' as dislike_url %}
<a title="{% trans "dislike" %}" class="{% block dislike_link_cls %} {% endblock dislike_link_cls %}
{% if user.is_authenticated %}
js-comment-reaction" href="{{ dislike_url }}" data-csrf="{{ csrf_token }}"
{% else %}
" href="{{ login_url }}/?next={{ dislike_url }}"
{% endif %}
>
{% include "comment/dislike_icon.html" %}
</a>
</div>
26 changes: 21 additions & 5 deletions comment/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from time import sleep

from django.core.exceptions import ValidationError
from django.db import IntegrityError

from comment.models import Comment, Reaction
from comment.tests.base import BaseCommentTest
Expand Down Expand Up @@ -126,8 +127,23 @@ def test_user_can_create_reaction(self):
instance = self.create_reaction(self.user_2, self.child_comment_1, 'like')
self.assertIsNotNone(instance)

def test_comment_property_likes_increment_and_decrement(self):
"""Test decrement and increment on likes property with subsequent request."""
def test_unique_togetherness_of_user_and_reaction_type(self):
"""Test Integrity error is raised when one user is set to have more than 1 reaction type for the same comment"""
self.create_reaction(self.user_2, self.child_comment_1, 'like')
self.assertRaises(IntegrityError, self.create_reaction, self.user_2, self.child_comment_1, 'dislike')

def test_post_delete_reaction_instance_signal(self):
"""Test reaction count is decreased when an instance is deleted"""
comment = self.child_comment_1
instance = self.create_reaction(self.user_2, self.child_comment_1, 'like')
comment.refresh_from_db()
self.assertEqual(comment.likes, 1)
instance.delete()
comment.refresh_from_db()
self.assertEqual(comment.likes, 0)

def test_comment_property_likes_increase_and_decrease(self):
"""Test decrease and increase on likes property with subsequent request."""
comment = self.child_comment_2
self.create_reaction(self.user_2, comment, 'like')
comment.refresh_from_db()
Expand All @@ -140,8 +156,8 @@ def test_comment_property_likes_increment_and_decrement(self):
comment.refresh_from_db()
self.assertEqual(comment.likes, 1)

def test_comment_property_dislikes_increment_and_decrement(self):
"""Test decrement and increment on dislikes property with subsequent request."""
def test_comment_property_dislikes_increase_and_decrease(self):
"""Test decrease and increase on dislikes property with subsequent request."""
comment = self.child_comment_3
self.create_reaction(self.user_1, comment, 'dislike')
comment.refresh_from_db()
Expand Down Expand Up @@ -180,5 +196,5 @@ def test_set_reaction(self):
self.assertEqual(comment.likes, 0)

def test_set_reaction_on_incorrect_reaction(self):
"""Test ValidationError is raised for incorrect when incorrect reactions are passed"""
"""Test ValidationError is raised when incorrect reaction type is passed"""
self.assertRaises(ValidationError, self.set_reaction, self.user_1, self.child_comment_5, 'likes')
2 changes: 0 additions & 2 deletions comment/tests/test_template_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ class MockUser:
user = MockUser()
comment = self.parent_comment_1
comment_and_user = add_one_arg(comment, user)
self.client.logout()
self.assertEqual(False, has_reacted(comment_and_user, 'like'))

def test_has_reacted_on_incorrect_reaction(self):
Expand All @@ -154,7 +153,6 @@ def test_has_reacted_on_correct_reaction_for_authenticated_users(self):

# check for other users
user = self.user_2
self.client.force_login(user)
comment_and_user = add_one_arg(comment, user)

self.assertEqual(False, has_reacted(comment_and_user, 'like'))
Expand Down
49 changes: 32 additions & 17 deletions comment/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,33 @@ def get_url(self, obj_id, action):
'reaction': action
})

def request(self, url, method='get', is_ajax=True):
"""
A utility function to return perform client requests
Args:
url (str): The url to perform that needs to be requested.
method (str, optional): The HTTP method name. Defaults to 'get'.
is_ajax (bool, optional): Whether AJAX request is to be performed or not. Defaults to True.
Raises:
ValueError: When a invalid HTTP method name is passed.
Returns:
`Any`: Response from the request.
"""
request_method = getattr(self.client, method.lower(), None)
if not request_method:
raise ValueError('This is not a valid request method')
if is_ajax:
return request_method(url, **{
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'
})
return request_method(url)

def test_set_reaction_for_authenticated_users(self):
"""Test whether users can create/change reactions using view"""
url = self.get_url(self.comment.id, 'like')
user = self.user_2
self.client.force_login(user)
response = self.client.post(url, **{
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'
})
response = self.request(url)
data = {
'status': 0,
'likes': 1,
Expand All @@ -196,34 +215,30 @@ def test_set_reaction_for_unauthenticated_users(self):
"""Test whether unauthenticated users can create/change reactions using view"""
url = self.get_url(self.comment.id, 'dislike')
self.client.logout()
response = self.client.post(url, **{
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'
})
response = self.request(url)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
self.assertEqual(response.url, '/login?next={}'.format(url))

def test_get_request(self):
"""Test whether GET requests are allowed or not"""
def test_post_request(self):
"""Test whether POST requests are allowed or not"""
url = self.get_url(self.comment.id, 'like')
response = self.client.get(url)
response = self.request(url, method='post')
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

def test_non_ajax_requests(self):
"""Test response if non AJAX requests are sent"""
url = self.get_url(self.comment.id, 'like')
response = self.client.post(url)
response = self.request(url, is_ajax=False)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_incorrect_comment_id(self):
"""Test response when an incorrect comment id is passed"""
url = self.get_url(102_876, 'like')
response = self.client.post(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = self.request(url)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_incorrect_reaction(self):
"""Test response when incorrect reaction is passed"""
url = self.get_url(self.comment.id, 'likes')
response = self.client.post(url, **{
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'
})
response = self.request(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
18 changes: 7 additions & 11 deletions comment/views/reactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_GET

from comment.models import Comment, ReactionInstance


@require_POST
@require_GET
@login_required
def react(request, comment_id, reaction):
"""
Expand All @@ -25,15 +25,11 @@ def react(request, comment_id, reaction):
return HttpResponseBadRequest(_('This is not a valid reaction'))

comment = get_object_or_404(Comment, id=comment_id)
ReactionInstance.set_reaction(user=request.user, comment=comment, reaction_type=reaction)
response = {
'status': 1,
'msg': _('Sorry we could not update this reaction. Please try again.')
'status': 0,
'likes': comment.likes,
'dislikes': comment.dislikes,
'msg': _('Your reaction has been updated successfully')
}
if ReactionInstance.set_reaction(user=request.user, comment=comment, reaction_type=reaction):
response.update({
'status': 0,
'likes': comment.likes,
'dislikes': comment.dislikes,
'msg': _('Your reaction has been updated successfully')
})
return JsonResponse(response)

0 comments on commit 0aee068

Please sign in to comment.