Skip to content

Commit

Permalink
feat(like-dislike): Like and Dislike Articles.
Browse files Browse the repository at this point in the history
- Allow a user to like and unlike  an article.
- Allow a user to dislike and un-dislike an article.
- Allow user to turn like to dislike and reverse.

[starts #162949220]
  • Loading branch information
MbugwaSami committed Jan 29, 2019
1 parent 0de05f5 commit 591cbba
Show file tree
Hide file tree
Showing 21 changed files with 218 additions and 257 deletions.
33 changes: 12 additions & 21 deletions authors/apps/articles/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<<<<<<< HEAD
# Generated by Django 2.1.4 on 2019-01-25 15:01
# Generated by Django 2.1.4 on 2019-01-28 09:25

=======
# Generated by Django 2.1.4 on 2019-01-22 05:48

import cloudinary.models
>>>>>>> feat(articles): user can create articles
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
Expand All @@ -17,37 +11,34 @@ class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]

operations = [
migrations.CreateModel(
<<<<<<< HEAD
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, max_length=253)),
=======
name='Articles',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, max_length=253, unique=True)),
('image', cloudinary.models.CloudinaryField(default='', max_length=255, verbose_name='image')),
>>>>>>> feat(articles): user can create articles
('title', models.CharField(max_length=100)),
('description', models.CharField(max_length=230)),
('body', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
<<<<<<< HEAD
('updated_at', models.DateTimeField(auto_now=True)),
('updated_at', models.DateField(auto_now=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
},
=======
('updated_at', models.DateField(auto_now=True)),
('author', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='author', to=settings.AUTH_USER_MODEL)),
),
migrations.CreateModel(
name='LikeDislike',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vote', models.BooleanField(choices=[(False, 'Dislike'), (True, 'Like')], verbose_name='vote')),
('object_id', models.PositiveIntegerField()),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
>>>>>>> feat(articles): user can create articles
),
]
19 changes: 0 additions & 19 deletions authors/apps/articles/migrations/0002_auto_20190122_0552.py

This file was deleted.

18 changes: 0 additions & 18 deletions authors/apps/articles/migrations/0003_auto_20190122_0621.py

This file was deleted.

18 changes: 0 additions & 18 deletions authors/apps/articles/migrations/0004_auto_20190122_0825.py

This file was deleted.

20 changes: 0 additions & 20 deletions authors/apps/articles/migrations/0005_auto_20190122_0943.py

This file was deleted.

51 changes: 44 additions & 7 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
from django.contrib.contenttypes.fields import GenericRelation, \
GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from .utils import get_unique_slug
from django.db.models.signals import pre_save
from django.db import models
from cloudinary.models import CloudinaryField
from .utils import get_unique_slug

# local imports
from ..authentication.models import User

"""
Articles
"""


# Create your models here.
class LikeDislikeManager(models.Manager):
"""This is model manager for likes and dislikes data"""

def likes(self):
"""
get all the likes for an object
"""
return self.get_queryset().filter(vote=True)

def dislikes(self):
"""
get all the dislikes for an object
"""
return self.get_queryset().filter(vote=False)


class LikeDislike(models.Model):
"""This class defines data for the likes and dislikes."""

LIKE = True
DISLIKE = False

VOTES = (
(DISLIKE, 'Dislike'),
(LIKE, 'Like')
)

vote = models.BooleanField(verbose_name='vote', choices=VOTES)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()

objects = LikeDislikeManager()


class Article(models.Model):
slug = models.SlugField(max_length=253, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE,
Expand All @@ -22,8 +56,8 @@ class Article(models.Model):
description = models.CharField(max_length=230, blank=False)
body = models.TextField(blank=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

updated_at = models.DateField(auto_now=True)
votes = GenericRelation(LikeDislike, related_query_name='articles')

def __str__(self):
return str(self.title)
Expand Down Expand Up @@ -51,3 +85,6 @@ class Meta:
def slug_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = get_unique_slug(instance, 'title', 'slug')


pre_save.connect(slug_pre_save_receiver, sender=Article)
11 changes: 11 additions & 0 deletions authors/apps/articles/renderers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import json
from rest_framework.renderers import JSONRenderer
from rest_framework.utils.serializer_helpers import ReturnList
import json


class ArticleJSONRenderer(JSONRenderer):
charset = 'utf-8'

def render(self, data, media_type=None, renderer_context=None):
if isinstance(data, ReturnList):
return json.dumps({
Expand All @@ -24,3 +26,12 @@ def render(self, data, media_type=None, renderer_context=None):
'comment': data,
'commentsCount': len(data)
})


class LikeArticleJSONRenderer(JSONRenderer):
charset = 'utf-8'

def render(self, data, media_type=None, renderer_context=None):
return json.dumps({
'likes_dislikes': data
})
7 changes: 7 additions & 0 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .models import Article, Comments
from authors.apps.profiles.models import UserProfile
from .models import Article, LikeDislike
from authors.apps.profiles.serializers import ProfileSerialiazer


Expand Down Expand Up @@ -120,3 +121,9 @@ class Meta:
'article',
'parent',
)


class LikeDislikeSerializer(serializers.ModelSerializer):

class Meta:
model = LikeDislike
3 changes: 0 additions & 3 deletions authors/apps/articles/tests.py

This file was deleted.

15 changes: 11 additions & 4 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@

from django.urls import path

from authors.apps.articles.views.comments import(
CommentsListView,
CommentsRetrieveUpdateDestroy
)
from authors.apps.articles.views.articles import (GetUpdateDeleteArticle,
CreateArticleView)
CreateArticleView,
LikeDislikeArticleView)
from authors.apps.articles.models import Article, LikeDislike

"""
Django 2.0 requires the app_name variable set when using include namespace
"""
app_name = 'articles'

urlpatterns = [

path('', CreateArticleView.as_view(),
name='article_create'),
path('/<slug:slug>', GetUpdateDeleteArticle.as_view(),
name='detail_article'),
path('/<slug>/comments', CommentsListView.as_view(), name='comment'),
path('/<slug>/comments/<int:id>',
CommentsRetrieveUpdateDestroy.as_view(), name='thread'
)
),
path('', CreateArticleView.as_view(), name='article-create'),
path('/<slug>/like',
LikeDislikeArticleView.as_view(vote_type=LikeDislike.LIKE),
name='article-like-like'),
path('/<slug>/dislike',
LikeDislikeArticleView.as_view(vote_type=LikeDislike.DISLIKE),
name='article-like-dislike'),
]
22 changes: 0 additions & 22 deletions authors/apps/articles/views.py

This file was deleted.

55 changes: 52 additions & 3 deletions authors/apps/articles/views/articles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
)
from rest_framework.response import Response
from rest_framework.views import status
from django.shortcuts import get_object_or_404
from django.contrib.contenttypes.models import ContentType

# local imports
from authors.apps.articles.models import Article
from authors.apps.articles.models import Article, LikeDislike
from authors.apps.articles.serializers import (
ArticleSerializer, UpdateArticleSerializer
ArticleSerializer, UpdateArticleSerializer, LikeDislikeSerializer
)

from authors.apps.articles.renderers import ArticleJSONRenderer
from authors.apps.articles.renderers import ArticleJSONRenderer, LikeArticleJSONRenderer
from authors.apps.articles.response_messages import (error_messages,
success_messages)

Expand Down Expand Up @@ -129,3 +131,50 @@ def delete(self, request, *args, **kwargs):
article.delete()
message = success_messages['deleted']
return Response(message, status=status.HTTP_200_OK)


class LikeDislikeArticleView(ListCreateAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = LikeDislikeSerializer
renderer_classes = (LikeArticleJSONRenderer,)
vote_type = None

def post(self, request, slug):
"""""This method posts likes and dislikes"""

article = get_object_or_404(Article, slug=slug)

# message to display after like or dislike
try:
like_dislike = LikeDislike.objects.get(
content_type=ContentType.objects.get_for_model(article),
author=request.user,
object_id=article.id)
if like_dislike.vote is not self.vote_type:
like_dislike.vote = self.vote_type
like_dislike.save(update_fields=['vote'])
else:
like_dislike.delete()
except LikeDislike.DoesNotExist:
article.votes.create(author=request.user, vote=self.vote_type)
article.save()

return Response({
"likes": article.votes.likes().count(),
"dislikes": article.votes.dislikes().count(),
},
content_type="application/json",
status=status.HTTP_201_CREATED
)

def get(self, request, slug):

article = get_object_or_404(Article, slug=slug)

return Response({
"likes": article.votes.likes().count(),
"dislikes": article.votes.dislikes().count(),
},
content_type="application/json",
status=status.HTTP_200_OK
)
Loading

0 comments on commit 591cbba

Please sign in to comment.