Skip to content

Commit

Permalink
feat(tagging-articles): tag articles
Browse files Browse the repository at this point in the history
Users should be able to tag their articles

- Modify articles.models.py
- Add tag relations serializer
- Modify articles.serializers.py
- Modify articles.urls.py, correct endpoint /api/tags
- Modify articles.views.py

[Finishes #162163178]
  • Loading branch information
sekayasin authored and Innocent Asiimwe committed Dec 19, 2018
1 parent fbd47c5 commit 257dffb
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 37 deletions.
25 changes: 20 additions & 5 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

class Article(TimestampedModel):
"""
Create and return an `Article` with a title, description and body.
Create and return an `Article` with a title,
description, body and tags associated with the article.
"""
slug = models.SlugField(
db_index=True,
Expand All @@ -24,7 +25,7 @@ class Article(TimestampedModel):
on_delete=models.CASCADE,
related_name='articles')
score = models.FloatField(default=0)
images = ArrayField(models.URLField(max_length=255),default=list)
images = ArrayField(models.URLField(max_length=255), default=list)
likes = models.IntegerField(
db_index=True,
default=False
Expand All @@ -33,6 +34,9 @@ class Article(TimestampedModel):
db_index=True,
default=False
)
tags = models.ManyToManyField(
'articles.Tag', related_name='articles'
)

def __str__(self):
return self.title
Expand All @@ -46,7 +50,7 @@ class Rating(models.Model):
article_id = models.ForeignKey(Article, on_delete=models.CASCADE)
score = models.IntegerField()


class Impressions(TimestampedModel):
"""
Create an `Impression` with a slug, user details, likes and dislikes.
Expand Down Expand Up @@ -78,6 +82,17 @@ class Report(models.Model):
reported = models.BooleanField(default=False)
reported_at = models.DateTimeField(auto_now_add=True)
reason = models.CharField(max_length=500, blank=False)


def __str__(self):
return self.reason


class Tag(TimestampedModel):
"""
Tag Model to allow authors add tags to their articles they create
"""
tag = models.CharField(max_length=255)
slug = models.SlugField(db_index=True, unique=True)

def __str__(self):
return self.reason
return self.tag
23 changes: 23 additions & 0 deletions authors/apps/articles/relations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import serializers

from .models import Tag


class TagRelatedField(serializers.RelatedField):
"""
A Special Serializer field for Tags.
If a user tags an article with an non existing tag, that new tag will also be created on article creation
"""

def get_queryset(self):
return Tag.objects.all()

def to_internal_value(self, data):
tag, created = Tag.objects.get_or_create(tag=data, slug=data.lower())
return tag

def to_representation(self, value):
"""
Return a tag property
"""
return value.tag
28 changes: 22 additions & 6 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from rest_framework import serializers
from authors.apps.profiles.serializers import GetUserProfileSerializer
from .models import Article, Rating, Report
from authors.apps.profiles.serializers import (
GetUserProfileSerializer)
from .models import (
Article, Impressions)
Article, Rating, Impressions, Report, Tag
)
from .relations import TagRelatedField


class ArticleSerializer(serializers.ModelSerializer):
Expand All @@ -17,18 +16,25 @@ class ArticleSerializer(serializers.ModelSerializer):
createdAt = serializers.SerializerMethodField(method_name='get_created_at')
updatedAt = serializers.SerializerMethodField(method_name='get_updated_at')
score = serializers.FloatField(required=False)
tagList = TagRelatedField(many=True, required=False, source='tags')

class Meta:
model = Article
fields = (
'author', 'body', 'createdAt', 'description',
'slug', 'title', 'updatedAt', 'score', 'images',
'likes', 'dislikes'
'likes', 'dislikes', 'tagList'
)

def create(self, validated_data):
author = self.context.get('author', None)
return Article.objects.create(author=author, **validated_data)
tags = validated_data.pop('tags', [])
article = Article.objects.create(author=author, **validated_data)

for tag in tags:
article.tags.add(tag)

return article

def get_created_at(self, instance):
return instance.created_at.isoformat()
Expand All @@ -42,6 +48,7 @@ class Meta:
model = Rating
fields = ('user', 'article_id', 'score')


class ImpressionSerializer(serializers.ModelSerializer):
"""
Serializes impressions requests and adds to an Article.
Expand Down Expand Up @@ -74,3 +81,12 @@ class ArticleReportSerializer(serializers.ModelSerializer):
class Meta:
model = Report
fields = '__all__'


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('tag',)

def to_representation(self, obj):
return obj.tag
20 changes: 20 additions & 0 deletions authors/apps/articles/tests/test_tag_article.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from rest_framework.test import APIClient
from .base_test import BaseTest
from rest_framework import status


class TestTagArticle(BaseTest):
def test_create_article_with_tags(self):
reponse = self.client.post(
'/api/articles/',
data={
"article": {
"title": "How to fight dragons 8",
"description": "Ever wonder jckvlahvhow?",
"body": "You have kenglto believe",
"tagList": ["dragons", "fight"]
}
},
format='json')
self.assertEqual(reponse.status_code, status.HTTP_201_CREATED)

6 changes: 3 additions & 3 deletions authors/apps/articles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from rest_framework.routers import DefaultRouter
from .views import (
ArticleViewSet, ArticleRetrieve,
LikeArticle, DislikeArticle, RatingsView, ReportArticlesView)

LikeArticle, DislikeArticle, RatingsView, ReportArticlesView, TagListView)

urlpatterns = [
path('articles/', ArticleViewSet.as_view()),
path('articles/<slug>', ArticleRetrieve.as_view()),
path('articles/<slug>/ratings/', RatingsView.as_view()),
path('articles/like/<slug>', LikeArticle.as_view()),
path('articles/dislike/<slug>', DislikeArticle.as_view()),
path('<slug>/report_article', ReportArticlesView.as_view())
path('<slug>/report_article', ReportArticlesView.as_view()),
path('tags', TagListView.as_view()),
]
44 changes: 21 additions & 23 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
from rest_framework import mixins, status, viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated
from rest_framework.response import Response
from rest_framework.exceptions import NotFound
from rest_framework.generics import (
RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView,
ListCreateAPIView)
from .models import Article
from authors.apps.profiles.models import UserProfile
from rest_framework.generics import (
RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView,
ListCreateAPIView)
from rest_framework.views import APIView
from .models import Article, Rating

from .renderers import ArticleJSONRenderer
from .serializers import ArticleSerializer, RatingSerializer
from .pagination import PageNumbering
from django.db.models import Avg
from authors.apps.profiles.models import UserProfile
from rest_framework.permissions import (
IsAuthenticatedOrReadOnly, IsAuthenticated)
from rest_framework.response import Response
from rest_framework.exceptions import NotFound
from rest_framework.generics import (
RetrieveUpdateDestroyAPIView,
ListCreateAPIView)
RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView,
ListCreateAPIView, ListAPIView)
from .models import (
Article, Impressions, Report)
Article, Impressions, Rating, Report, Tag)
from .renderers import ArticleJSONRenderer
from .serializers import (
ArticleSerializer, ImpressionSerializer,
RatingSerializer, ArticleReportSerializer)
RatingSerializer, ArticleReportSerializer, TagSerializer)
from .pagination import PageNumbering
from django.db.models import Avg
from authors.apps.profiles.models import UserProfile
from ..authentication.models import User
from django.db.models import Count

Expand Down Expand Up @@ -320,3 +304,17 @@ def post(self, request, slug, **kwargs):
receipient], fail_silently=False)

return Response(serializer.data, status=status.HTTP_201_CREATED)


class TagListView(ListAPIView):
queryset = Tag.objects.all()
permission_classes = (IsAuthenticated,)
serializer_class = TagSerializer

def taglist(self, request):
serializer_data = self.get_queryset()
serializer = self.serializer_class(serializer_data, many=True)

return Response({
'tags': serializer_data
}, status=status.HTTP_200_OK)

0 comments on commit 257dffb

Please sign in to comment.