Skip to content

Commit

Permalink
Merge 8259351 into e1daff1
Browse files Browse the repository at this point in the history
  • Loading branch information
collinewait committed Sep 20, 2018
2 parents e1daff1 + 8259351 commit 92b7417
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 39 deletions.
63 changes: 59 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,14 @@ The preferred JSON object to be returned by the API should be structured as foll
```source-json
{
"tags": [
"reactjs",
"angularjs"
{
"id": 1,
"tag_name": "reactjs"
},
{
"id": 2,
"tag_name": "angularjs"
}
]
}
```
Expand Down Expand Up @@ -372,7 +378,7 @@ Example request body:
"title": "How to train your dragon",
"description": "Ever wonder how?",
"body": "You have to believe",
"tagList": ["reactjs", "angularjs", "dragons"]
"tags": ["reactjs", "angularjs", "dragons"]
}
}
```
Expand Down Expand Up @@ -453,9 +459,58 @@ Authentication required, returns the Article

No additional parameters required

### Update a tag(s) on an article

`PUT /api/articles/<slug>/`

Example request body:

```source-json
{
"article": {
"title": "How to train your dragon",
"description": "Ever Wonder how?",
"body": "Keep reading...",
"tags": {"remove":["tag1"], "add": ["tag2", "tag3"]}
}
}
```

Authentication required, will return an Article

Required fields: `tags` as a dictionary with either an array of tags to remove or tags to add or both.

### Get Tags

`GET /api/tags`
`GET /api/articles/tags/tag_list/`

Authentication and super user required, returns multiple tags.

### Get a Tag

`GET /api/articles/tags/tag_list/<tag_id>`

Authentication and super user required, returns a tag.

### Delete a Tag

`DELETE /api/articles/tags/tag_list/<tag_id>`

Authentication and super user required.

### Update a Tag

`UPDATE /api/articles/tags/tag_list/<tag_id>`

Example request body:

```source-json
{
"tag_name": "update_to_me"
}
```

Authentication and super user required. returns a tag.

##### Steps to install the project locally.

Expand Down
12 changes: 11 additions & 1 deletion authors/apps/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ class to declare an article
from authors.apps.articles.utils import generate_slug
from authors.apps.authentication.models import User

class Tag(models.Model):
"""
Tag for the article(s). Every tag has unique tag_name.
"""
tag_name = models.CharField(max_length=64, unique=True)

def __str__(self):
return self.tag_name

# noinspection SpellCheckingInspection
class Article(models.Model):
Expand Down Expand Up @@ -39,6 +47,8 @@ class Article(models.Model):

photo_url = models.CharField(max_length=255, null=True)

tags = models.ManyToManyField(Tag, related_name='article_tag')

def __str__(self):
"""
:return: string
Expand All @@ -52,7 +62,7 @@ def save(self, *args, **kwargs):
:param kwargs:
"""
self.slug = generate_slug(Article, self)

super(Article, self).save(*args, **kwargs)

@property
Expand Down
14 changes: 14 additions & 0 deletions authors/apps/articles/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""This module contains custom permissions"""
from rest_framework import permissions


class IsSuperuser(permissions.BasePermission):
"""
Check if it is a super user.
"""

def has_permission(self, request, view):
"""Check if a user trying to access the end point is a super user"""
if request.method in permissions.SAFE_METHODS or request.user.is_superuser:
return True
return False
18 changes: 18 additions & 0 deletions authors/apps/articles/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,21 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
return json.dumps({
'article': data
})


class TagJSONRenderer(JSONRenderer):
"""
Override default renderer to customise output
"""
charset = 'utf-8'

def render(self, data, accepted_media_type=None, renderer_context=None):

if isinstance(data, list):
return json.dumps({
'tags': data
})

return json.dumps({
'tag': data
})
72 changes: 62 additions & 10 deletions authors/apps/articles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,75 @@
from rest_framework.pagination import PageNumberPagination

from authors.apps.articles.exceptions import NotFoundException
from authors.apps.articles.models import Article, Rating
from authors.apps.articles.models import Article, Tag, Rating
from authors.apps.articles.utils import get_date
from authors.apps.authentication.models import User
from authors.apps.profiles.models import UserProfile
from authors.apps.profiles.serializers import UserProfileSerializer


class TagRelatedField(serializers.RelatedField):
"""
Implements a custom relational field by overriding RelatedFied.
returns a list of tag names.
"""

def to_representation(self, value):

return value.tag_name


class TagSerializer(serializers.ModelSerializer):
"""Handles serialization and deserialization of Tag objects."""

class Meta:
model = Tag
fields = "__all__"


class ArticleSerializer(serializers.ModelSerializer):
"""
Define action logic for an article
"""

author = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
user_rating = serializers.CharField(source='author.average_rating', required=False)
user_rating = serializers.CharField(
source='author.average_rating', required=False)
tagList = TagRelatedField(
many=True, required=False, source='tags', queryset=Tag.objects.all())

author = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False)
slug = serializers.CharField(read_only=True)
tags = []

def create(self, validated_data):
"""
:param validated_data:
:return:
"""
return Article.objects.create(**validated_data)
article = Article.objects.create(**validated_data)

for tag in self.tags:
article.tags.add(Tag.objects.get_or_create(
tag_name=tag.replace(" ", "_").lower())[0])
return article

def update(self, instance, validated_data):
"""
:param validated_data:
:return:
"""
for key, val in validated_data.items():
setattr(instance, key, val)

for tag in instance.tags.all():
instance.tags.remove(tag)

for tag in self.tags:
instance.tags.add(Tag.objects.get_or_create(
tag_name=tag.replace(" ", "_").lower())[0])
instance.save()
return instance

@staticmethod
def validate_for_update(data: dict, user, slug):
Expand All @@ -37,7 +84,8 @@ def validate_for_update(data: dict, user, slug):
:return:
"""
try:
article = Article.objects.filter(slug__exact=slug, author__exact=user)
article = Article.objects.filter(
slug__exact=slug, author__exact=user)
if article.count() > 0:
article = article[0]
else:
Expand Down Expand Up @@ -67,7 +115,8 @@ def to_representation(self, instance):
:return:
"""
response = super().to_representation(instance)
profile = UserProfileSerializer(UserProfile.objects.get(user=instance.author), context=self.context).data
profile = UserProfileSerializer(UserProfile.objects.get(
user=instance.author), context=self.context).data

response['author'] = profile
return response
Expand All @@ -79,7 +128,7 @@ class behaviours
model = Article
# noinspection SpellCheckingInspection
fields = ('slug', 'title', 'description', 'body', 'created_at', 'average_rating', 'user_rating',
'updated_at', 'favorited', 'favorites_count', 'photo_url', 'author')
'updated_at', 'favorited', 'favorites_count', 'photo_url', 'author', 'tagList')


class PaginatedArticleSerializer(PageNumberPagination):
Expand Down Expand Up @@ -110,10 +159,12 @@ class RatingSerializer(serializers.ModelSerializer):
"""
Define action logic for an article rating
"""
article = serializers.PrimaryKeyRelatedField(queryset=Article.objects.all())
article = serializers.PrimaryKeyRelatedField(
queryset=Article.objects.all())
rated_at = serializers.DateTimeField(read_only=True)
rated_by = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
score = serializers.DecimalField(required=True, max_digits=4, decimal_places=2)
score = serializers.DecimalField(
required=True, max_digits=4, decimal_places=2)

@staticmethod
def update_request_data(data, slug, user: User):
Expand Down Expand Up @@ -153,7 +204,8 @@ def create(self, validated_data):
score = validated_data.get("score", 0)

try:
rating = Rating.objects.get(rated_by=rated_by, article__slug=article.slug)
rating = Rating.objects.get(
rated_by=rated_by, article__slug=article.slug)
except Rating.DoesNotExist:
return Rating.objects.create(**validated_data)

Expand Down
Loading

0 comments on commit 92b7417

Please sign in to comment.