Skip to content

Commit

Permalink
feat(articles) crud articles
Browse files Browse the repository at this point in the history
- user can view articles
- user can create articles
- user can update articles
- user can delete articles
- fix hound errors
- fix travis build error
- fix merge conflict

[Delivers #161254663]
  • Loading branch information
Eyansky committed Nov 9, 2018
1 parent cb7d263 commit 98c6fbd
Show file tree
Hide file tree
Showing 25 changed files with 366 additions and 103 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ install:
script:
- python3 manage.py makemigrations authentication
- python3 manage.py migrate authentication
- python3 manage.py makemigrations articles
- python3 manage.py migrate articles
- python3 manage.py makemigrations article
- python3 manage.py migrate article
- python3 manage.py makemigrations
- python3 manage.py migrate
- coverage run manage.py test
Expand Down
Binary file added article/pexels-photo-46710.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added article/pexels-photo-46710_0Xj3j4y.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added article/pexels-photo-46710_d9gLnYe.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added article/pexels-photo-46710_fIxGucm.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added article/pexels-photo-46710_n1GDsDu.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
File renamed without changes.
104 changes: 104 additions & 0 deletions authors/apps/article/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# this is how the db will be structured.

from django.db import models
from django.utils.translation import pgettext_lazy as _
from django.contrib.auth import get_user_model
from django.core.validators import MaxValueValidator, MinValueValidator

'''Django-autoslug is a reusable Django library
that provides an improved slug field which can automatically:
populate itself from another field and preserve
uniqueness of the value'''
from autoslug import AutoSlugField
from versatileimagefield.fields import VersatileImageField


class Article(models.Model):
user = models.ForeignKey(
get_user_model(),
related_name='author',
on_delete=models.CASCADE,
null=True,
blank=True,
default=None
)
# creates a random identifier for a particular article from the title
# field.
slug = AutoSlugField(
populate_from='title',
blank=True,
null=True,
unique=True)
title = models.CharField(
_('Article field', 'title'),
unique=True,
max_length=128
)
description = models.TextField(
_('Article Field', 'description'),
blank=True,
null=True
)
body = models.TextField(
_('Article Field', 'body'),
blank=True,
null=True
)
image = VersatileImageField(
'Image',
upload_to='article/',
width_field='width',
height_field='height',
blank=True,
null=True
)
height = models.PositiveIntegerField(
'Image Height',
blank=True,
null=True
)
width = models.PositiveIntegerField(
'Image Width',
blank=True,
null=True
)
created_at = models.DateTimeField(
_('Article field', 'created at'),
auto_now_add=True,
editable=False
)
updated_at = models.DateTimeField(
_('Article field', 'updated at'),
auto_now=True
)

class Meta:
app_label = "article"

def __str__(self):
return self.title


class RateArticle(models.Model):
"""
This is the article class. It holds data for the article.
"""
rater = models.ForeignKey(
"authentication.User",
related_name="ratearticle",
on_delete=models.CASCADE) # link with the user who rated
article = models.ForeignKey(
"article.Article",
related_name="ratearticle",
on_delete=models.CASCADE) # link with the article being rated
rate = models.IntegerField(null=False, blank=False,
validators=[
MaxValueValidator(5),
MinValueValidator(1)
]) # rate value column

def __str__(self):
"""
Return a human readable format
"""
return self.rate
84 changes: 84 additions & 0 deletions authors/apps/article/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'''Serializers allow complex data
such as querysets and model instances
to be converted to
native Python datatypes that can then
be easily rendered into JSON, XML or other content types.'''

from rest_framework import serializers
from django.apps import apps
from .models import RateArticle
from authors.apps.profiles.serializers import ProfileListSerializer


TABLE = apps.get_model('article', 'Article')
Profile = apps.get_model('profiles', 'UserProfile')

NAMESPACE = 'article'
fields = ('id', 'slug', 'image', 'title', 'description', 'body', 'user',)


class ArticleSerializer(serializers.ModelSerializer):
update_url = serializers.HyperlinkedIdentityField(
view_name=NAMESPACE + ':update', lookup_field='slug')
delete_url = serializers.HyperlinkedIdentityField(
view_name=NAMESPACE + ':delete', lookup_field='slug')
author = serializers.SerializerMethodField(read_only=True)

class Meta:
model = TABLE

fields = fields + ('author', 'update_url', 'delete_url')

def get_author(self, obj):
try:
serializer = ProfileListSerializer(
instance=Profile.objects.get(user=obj.user)
)
return serializer.data
except BaseException:
return {}

def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get(
'description', instance.description)
instance.body = validated_data.get('body', instance.body)
if validated_data.get('image'):
instance.image = validated_data.get('image', instance.image)

instance.save()

return instance


class ArticleCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TABLE

fields = fields

def create(self, validated_data):
instance = TABLE.objects.create(**validated_data)
validated_data['slug'] = instance.slug

return validated_data


class RateArticleSerializer(serializers.ModelSerializer):
"""
validate rate article
"""
slug = serializers.SlugField()
rate = serializers.IntegerField()

def validate(self, data):
rate = data['rate']
if not rate > 0 or not rate <= 5:
raise serializers.ValidationError(
'invalid rate value should be > 0 or <=5')

return data

class Meta:
model = RateArticle
fields = ("slug", "rate")
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ def setUp(self):
"""
Prepare test environment for each testcase
"""

self.article = {
"title": "How to train your dragon today",
"description": "Ever wonder how?",
"body": "You have to believe in you",
"image": "https://dummyimage.com/600x400/000/fff"
}

self.client = APIClient()
self.article = Article()
self.signup_url = reverse('authentication:register')
Expand Down Expand Up @@ -53,21 +61,25 @@ def setUp(self):
body = "this is a body"
author = self.user
article = Article(
author=author,
user=author,
slug=self.slug,
body=body,
title=title,
description=description)
description=description
)
article.save()
self.rate_details = {
"user": {
"slug": self.slug,
"rate": 3
}
}
self.rate_url = os.environ["URL"]+"api/article/"\
+ self.slug + "/rate/"
self.view_rates_url = os.environ["URL"]+"api/article/rate/"
self.rate_url = os.environ["URL"] + \
"api/article/" + self.slug + "/rate/"
self.view_rates_url = os.environ["URL"] + "api/article/rate/"

self.articles_url = os.environ["URL"] + "api/article/"
self.create_articles_url = os.environ["URL"] + "api/article/create"

def test_rate_article_without_token(self):
"""
Expand Down Expand Up @@ -169,3 +181,15 @@ def test_get_rate_article_not_found(self):
0,
response.data["rates"])
self.assertEqual(204, status.HTTP_204_NO_CONTENT)

def test_list_article(self):
'''
test list article
'''

self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
response = self.client.get(
self.articles_url + str(2) + "/",
format='json')

self.assertEqual(200, status.HTTP_200_OK)
44 changes: 44 additions & 0 deletions authors/apps/article/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.urls import path
from rest_framework_swagger.views import get_swagger_view
from .views import (
ArticleCreateAPIView,
ArticleListAPIView,
ArticleDeleteAPIView,
ArticleDetailAPIView,
ArticleUpdateAPIView,
Rate,
ArticleRate
)

schema_view = get_swagger_view(title="Articles")

urlpatterns = [
path(
'article/',
ArticleListAPIView.as_view(),
name='list'),
path(
'article/create',
ArticleCreateAPIView.as_view(),
name='create'),
path(
'article/delete/<slug>/',
ArticleDeleteAPIView.as_view(),
name='delete'),
path(
'article/detail/<slug>/',
ArticleDetailAPIView.as_view(),
name='detail'),
path(
'article/update/<slug>/',
ArticleUpdateAPIView.as_view(),
name='update'),
path(
'article/<str:slug>/rate/',
Rate.as_view(),
name="rate"),
path(
'article/rate/<str:pk>/',
ArticleRate.as_view(),
name="view_rate"),
]
79 changes: 72 additions & 7 deletions authors/apps/articles/views.py → authors/apps/article/views.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,80 @@
from rest_framework import status
from .serializers import (
TABLE,
ArticleSerializer,
ArticleCreateSerializer,
RateArticleSerializer
)
from ..core.permissions import IsOwnerOrReadOnly
from ..authentication.renderers import UserJSONRenderer
from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework import status
from django.db.models import Q
from rest_framework.generics import (
ListAPIView, CreateAPIView,
RetrieveUpdateAPIView,
RetrieveAPIView,
DestroyAPIView,
)
from rest_framework.permissions import (
IsAuthenticatedOrReadOnly, IsAuthenticated
)
from .models import RateArticle, Article
from .serializers import RateArticleSerializer

LOOKUP_FIELD = 'slug'


class ArticleListAPIView(ListAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
serializer_class = ArticleSerializer

def get_queryset(self, *args, **kwargs):
queryset_list = TABLE.objects.all()

query = self.request.GET.get('q')

if query:
queryset_list = queryset_list.filter(
Q(title__icontains=query) |
Q(slug__icontains=query) |
Q(description__icontains=query)
)

return queryset_list.order_by('-id')


class ArticleCreateAPIView(CreateAPIView):
serializer_class = ArticleCreateSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = TABLE.objects.all()

def perform_create(self, serializer):
serializer.save(user=self.request.user)


class ArticleDetailAPIView(RetrieveAPIView):
queryset = TABLE.objects.all()
serializer_class = ArticleSerializer
lookup_field = LOOKUP_FIELD


class ArticleDeleteAPIView(DestroyAPIView):
queryset = TABLE.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
serializer_class = ArticleSerializer
lookup_field = LOOKUP_FIELD


class ArticleUpdateAPIView(RetrieveUpdateAPIView):
permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
queryset = TABLE.objects.all()
serializer_class = ArticleSerializer
lookup_field = LOOKUP_FIELD

def perform_update(self, serializer):
serializer.save(user=self.request.user)


class ArticleRate(APIView):
class ArticleRate(CreateAPIView):
"""
rate class article
"""
Expand Down
Loading

0 comments on commit 98c6fbd

Please sign in to comment.