-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(follow profile): add follow and unfollow another user profile
- authenticated user should be able to follow another user - authenticated user should be able to un-follow anothe user - a user should be able to follow themselves - create tests for following - fix initial artcle and artcles mix up - refactor cod - add makmigrations for profiles to fix tests - fix mwerge conflict [Delivers #161254668]
- Loading branch information
1 parent
cb7d263
commit 36ce4a9
Showing
73 changed files
with
685 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,3 +95,4 @@ db.sqlite3 | |
!*/migrations/__init__.py | ||
.vscode/ | ||
*.DS_Store | ||
*migrations |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Empty file.
File renamed without changes.
0
authors/apps/articles/apps.py → authors/apps/article/apps.py
100644 → 100755
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from django.apps import apps | ||
from django.test import TestCase | ||
from django.urls import reverse | ||
from rest_framework.test import APIClient | ||
import factory | ||
from faker import Factory | ||
from django.contrib.auth import get_user_model | ||
|
||
Article = apps.get_model('article', 'Article') | ||
faker = Factory.create() | ||
|
||
|
||
class UserFactory(factory.DjangoModelFactory): | ||
class Meta: | ||
model = get_user_model() | ||
|
||
username = factory.Sequence(lambda n: 'map%d' % n) | ||
email = factory.Sequence(lambda n: 'example_%s@map.com' % n) | ||
password = factory.PostGenerationMethodCall('set_password', '1234abcd') | ||
|
||
|
||
class ArticleFactory(factory.DjangoModelFactory): | ||
class Meta: | ||
model = Article | ||
|
||
user = factory.SubFactory(UserFactory) | ||
title = faker.name() | ||
description = faker.text() | ||
body = faker.text() | ||
slug = factory.Sequence(lambda n: 'map-slug%d' % n) | ||
image = factory.django.ImageField(color='blue') | ||
|
||
|
||
class TestArticles(TestCase): | ||
def setUp(self): | ||
self.user = UserFactory() | ||
self.article = ArticleFactory() | ||
self.client = APIClient() | ||
self.client.force_authenticate(user=self.user) | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Bearer ' + | ||
self.user.token()) | ||
|
||
self.namespace = 'article' | ||
self.body = { | ||
'title': faker.name(), | ||
'description': faker.text(), | ||
'body': faker.text(), | ||
} | ||
self.create_url = reverse(self.namespace + ':create') | ||
self.list_url = reverse(self.namespace + ':list') | ||
self.update_url = reverse( | ||
self.namespace + ':update', | ||
kwargs={ | ||
'slug': self.article.slug}) | ||
self.delete_url = reverse( | ||
self.namespace + ':delete', | ||
kwargs={ | ||
'slug': self.article.slug}) | ||
self.retrieve_url = reverse( | ||
self.namespace + ':detail', | ||
kwargs={ | ||
'slug': self.article.slug}) | ||
|
||
def test_create_article_api(self): | ||
response = self.client.post(self.create_url, self.body, format='json') | ||
self.assertEqual(201, response.status_code) | ||
|
||
def test_retrieve_article_api(self): | ||
response = self.client.get(self.retrieve_url) | ||
self.assertContains(response, self.article) | ||
|
||
def test_list_article_api_with_parameters(self): | ||
self.client.post(self.create_url, self.body, format='json') | ||
response = self.client.get( | ||
self.list_url + '?q=' + self.article.slug[0]) | ||
self.assertContains(response, self.article) | ||
|
||
def test_listing_articles_api(self): | ||
response = self.client.get(self.list_url) | ||
self.assertContains(response, self.article) | ||
|
||
def test_update_article_api(self): | ||
response = self.client.post(self.create_url, self.body, format='json') | ||
self.update_url = reverse( | ||
self.namespace + ':update', | ||
kwargs={ | ||
'slug': response.data.get('slug')}) | ||
response = self.client.put(self.update_url, self.body) | ||
self.assertEqual(200, response.status_code) | ||
|
||
def test_delete_article_api(self): | ||
response = self.client.post(self.create_url, self.body, format='json') | ||
self.delete_url = reverse( | ||
self.namespace + ':delete', | ||
kwargs={ | ||
'slug': response.data.get('slug')}) | ||
|
||
response = self.client.delete(self.delete_url) | ||
self.assertEqual(204, response.status_code) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.