Skip to content

Commit

Permalink
Merge pull request #34 from andela/ft-tag-articles-164046260
Browse files Browse the repository at this point in the history
#164046260 Users can add tags to articles
  • Loading branch information
abulojoshua1 committed Mar 21, 2019
2 parents 096cb38 + 5b0a4cd commit 63b3e95
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 8 deletions.
71 changes: 71 additions & 0 deletions authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .utils import generate_unique_slug
from django.db.models import Avg
from cloudinary import CloudinaryImage
from django.utils.text import slugify


class Article(models.Model):
Expand All @@ -25,6 +26,7 @@ class Article(models.Model):
description = models.CharField(max_length=255, null=True)
published = models.BooleanField(default=False)
activated = models.BooleanField(default=True)
tags = models.ManyToManyField('Tag', related_name='articles', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
author = models.ForeignKey(
Expand Down Expand Up @@ -168,3 +170,72 @@ def get_image(self):

def __str__(self):
return "This is rating no: " + str(self.id)


class Tag(models.Model):
tag = models.CharField(max_length=100, db_index=True, unique=True)
slug = models.SlugField(max_length=240, db_index=True, unique=True)

def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.tag)
super().save(*args, **kwargs)

def __str__(self):
return self.slug

objects = models.Manager()

def _create_tag(self, tag_name):
'''
This method checks if the tag exists and returns the tag object
else it creates a new tag object and returns it.
'''
found = Tag.objects.filter(slug=slugify(tag_name))
if len(found) < 1:
Tag.objects.create(tag=tag_name)
return Tag.objects.filter(slug=slugify(tag_name)).first()

def _remove_tags_without_articles(self, tag_name):
'''This method takes a tag name and checks if the tag has
associated articles and if not it deletes the tag from the
database in order to save on database space.
'''
found = Tag.objects.filter(slug=slugify(tag_name))
if found and len(found) > 0:
the_tag = Tag.objects.filter(slug=slugify(tag_name)).first()
its_articles = the_tag.articles.all()
if len(its_articles) < 1:
the_tag.delete()
return True
return False

def _update_article_tags(self, instance, new_tag_list):
'''This method is used to update the tags of an
article and do general tag mainenance.'''
old_tags = instance.tags.all()
old_tag_slugs = [tag.slug for tag in old_tags]
new_tag_slugs = [slugify(tag) for tag in new_tag_list]
# Check for items in the old list that are not in the new
# list and remove them.
tags_to_remove = list(set(old_tag_slugs) - set(new_tag_slugs))
remove_tag_objects = [
tag for tag in old_tags if (
tag.slug in tags_to_remove
)
]
for item in remove_tag_objects:
# Remove tag from article
instance.tags.remove(item)
# Check if tag has any other article else delete the tag.
self._remove_tags_without_articles(item.tag)
new_slugs = list(set(new_tag_slugs) - set(old_tag_slugs))
tags_to_add = [tag for tag in new_tag_list if(
slugify(tag) in new_slugs
)]
for item in tags_to_add:
# Create tag if tag does not exist
the_tag = self._create_tag(item)
# Add the tag to the article instance.
instance.tags.add(the_tag)
return True
8 changes: 8 additions & 0 deletions authors/apps/articles/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@
from rest_framework.utils.serializer_helpers import ReturnDict

from ..authentication.models import User
from ..articles.models import Tag


class ArticleJSONRenderer(JSONRenderer):
'''JSONRenderClass for formatting Article model data into JSON.'''
charset = 'utf-8'

def _single_article_formatting(self, data):
tags = data.pop("tags")
tagList = []
for item in tags:
this_tag = Tag.objects.filter(pk=item).first()
tagList.append(this_tag.tag)
data["tagList"] = tagList

author = User.objects.all().filter(
pk=data['author']).first()
the_data = {
Expand Down
21 changes: 18 additions & 3 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from authors.apps.profiles.serializers import ProfileSerializer

from .models import (Article, Favorite, Like, Snapshot,
ThreadedComment, Rating)
ThreadedComment, Rating, Tag)


class TheArticleSerializer(serializers.ModelSerializer):
Expand All @@ -15,15 +15,25 @@ class Meta:
model = Article
fields = [
'id', 'title', 'body', 'draft', 'slug',
'reading_time', 'average_rating',
'reading_time', 'average_rating', 'tags',
'editing', 'description', 'published', 'activated',
"created_at", "updated_at", 'author'
]
read_only_fields = ['slug']

def create(self, validated_data):
'''Create a new Article instance, given the accepted data.'''
article = Article.objects.create(**validated_data)
article_data = validated_data
tags_data = None
if 'tags' in validated_data.keys():
tags_data = article_data.pop("tags")
article = Article.objects.create(**article_data)

if tags_data:
for item in tags_data:
my_tag = Tag()
found = my_tag._create_tag(item)
article.tags.add(found)
return article

def update(self, instance, validated_data):
Expand All @@ -46,6 +56,11 @@ def update(self, instance, validated_data):
instance.activated = validated_data.get(
'activated', instance.activated
)
if 'tags' in validated_data.keys() and len(validated_data['tags']) > 0:
this_tag = Tag()
new_tags = validated_data['tags']
this_tag._update_article_tags(instance, new_tags)

instance.save()
return instance

Expand Down
7 changes: 5 additions & 2 deletions authors/apps/articles/tests/test_article_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def setUp(self):
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "You have to believe",
"draft": "Giving exellence high priority"
"draft": "Giving exellence high priority",
"tagList": ["dragons", "training"],

}
}

Expand Down Expand Up @@ -207,8 +209,9 @@ def test_update_article_successfully(self):
"article": {
"title": "I am a champion",
"description": "In God we trust.",
"body": "One step infron of the other",
"body": "One step infront of the other",
"draft": "Giving exellence high priority",
"tagList": ["andela", "The OG"],
"activated": True
}
}
Expand Down
64 changes: 64 additions & 0 deletions authors/apps/articles/tests/test_models_vol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.test import TestCase

from authors.apps.articles.models import Tag, Article
from authors.apps.authentication.models import User

class ArticleModelTestCase(TestCase):

def setUp(self):
self.user_one = User.objects.create(
email = "jake@jake.jake",
password = "jakejake",
username = "Jake"
)
self.article_one = Article.objects.create(
title = "I am the OG",
body = "This is an article by the OG",
author = self.user_one
)

def test_article_creation(self):

self.assertEqual(
self.article_one.__str__(),
"I am the OG"
)


class TagModelTestCase(TestCase):

def setUp(self):
self.user_one = User.objects.create(
email = "jake@jake.jake",
password = "jakejake",
username = "Jake"
)
self.article_one = Article.objects.create(
title = "I am the OG",
body = "This is an article by the OG",
author = self.user_one
)

def test_tag_creation(self):
my_tag = Tag.objects.create(tag = "Etomovich")
my_tag.save()
self.assertEqual(
my_tag.slug,
"etomovich"
)

def test_delete_tags_without_articles(self):
my_tag = Tag(tag = "Etomovich")
my_tag.save()
remove_tag = my_tag._remove_tags_without_articles("Etomovich")
self.assertTrue(remove_tag)

def test_fail_to_delete_tags_with_articles(self):
a_tag = Tag.objects.create(tag = "Andela")
self.article_one.tags.add(a_tag)
remove_tag = a_tag._remove_tags_without_articles("Andela")
self.assertFalse(remove_tag)
remove_tag = a_tag._remove_tags_without_articles("Etomovich")
self.assertFalse(remove_tag)


25 changes: 22 additions & 3 deletions authors/apps/articles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ArticleRatingSerializer)
from .renderers import ArticleJSONRenderer, CommentJSONRenderer
from .permissions import CanCreateComment, CanEditComment
from .models import Article, Like, ThreadedComment, Favorite, Rating
from .models import Article, Like, ThreadedComment, Favorite, Rating, Tag
from authors.apps.core.views import BaseManageView
from rest_framework.views import APIView, status
from rest_framework.response import Response
Expand All @@ -30,6 +30,16 @@ def post(self, request, *args, **kwargs):

def create(self, request, *args, **kwargs):
payload = request.data.get('article', {})
the_tags = payload.get("tagList", None)
tags_pk = []
if the_tags:
the_tags = payload.pop("tagList")
tag_object = Tag()
for item in the_tags:
my_tag = tag_object._create_tag(item)
tags_pk.append(my_tag.pk)
payload["tags"] = tags_pk

# Decode token
this_user = request.user
payload['author'] = this_user.pk
Expand All @@ -54,9 +64,9 @@ def list(self, request, *args, **kwargs):
# Decode token
reply_not_found = {}
paginator = self.pagination_class()
page = paginator.paginate_queryset(self.queryset, request)
published_articles = Article.objects.filter(
published=True, activated=True)
page = paginator.paginate_queryset(published_articles, request)

if (len(published_articles) < 1):
reply_not_found["detail"] = "No articles have been found."
Expand Down Expand Up @@ -107,7 +117,6 @@ class UpdateAnArticleView(mixins.UpdateModelMixin,

def _edit_article(self, request, the_data):
if self.get_object().author == request.user:

if self.get_object().activated is False:
invalid_entry = {
"detail": "This article does not exist."
Expand All @@ -118,6 +127,16 @@ def _edit_article(self, request, the_data):
)

article_obj = self.get_object()
this_tags = the_data.get("tagList", None)
tags_pk = []
if this_tags:
this_tags = the_data.pop("tagList")
tag_object = Tag()
for item in this_tags:
the_tag = tag_object._create_tag(item)
tags_pk.append(the_tag.pk)
the_data["tags"] = tags_pk

serialized = self.serializer_class(
article_obj, data=the_data, partial=True
)
Expand Down

0 comments on commit 63b3e95

Please sign in to comment.