diff --git a/.gitignore b/.gitignore index 7e94fba..1932436 100644 --- a/.gitignore +++ b/.gitignore @@ -95,8 +95,6 @@ designs/ # SQLite3 db.sqlite3 -# Ignore articles/renderers for now -authors/apps/articles/renderers.py # Admin admin.py -.DS_Store +*.DS_Store diff --git a/authors/.DS_Store b/authors/.DS_Store deleted file mode 100644 index 5e38271..0000000 Binary files a/authors/.DS_Store and /dev/null differ diff --git a/authors/apps/.DS_Store b/authors/apps/.DS_Store deleted file mode 100644 index 324150b..0000000 Binary files a/authors/apps/.DS_Store and /dev/null differ diff --git a/authors/apps/articles/migrations/0001_initial.py b/authors/apps/articles/migrations/0001_initial.py index 422f216..f076bc2 100644 --- a/authors/apps/articles/migrations/0001_initial.py +++ b/authors/apps/articles/migrations/0001_initial.py @@ -1,7 +1,9 @@ -# Generated by Django 2.1.2 on 2018-11-06 07:54 +# Generated by Django 2.1.2 on 2018-11-15 09:44 +from django.conf import settings import django.contrib.postgres.fields from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -9,6 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -18,16 +21,57 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(db_index=True, max_length=255)), ('body', models.TextField()), - ('image', models.ImageField(blank=True, height_field=100, null=True, upload_to='photos/%Y/%m/%d/', width_field=100)), + ('images', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=None, null=True, size=None)), ('description', models.CharField(max_length=255)), ('slug', models.SlugField(max_length=40, unique=True)), ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), blank=True, default=None, null=True, size=None)), ('time_to_read', models.IntegerField()), ('time_created', models.DateTimeField(auto_now_add=True, db_index=True)), ('time_updated', models.DateTimeField(auto_now=True, db_index=True)), + ('average_rating', models.IntegerField(default=0)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to=settings.AUTH_USER_MODEL)), ], options={ 'ordering': ('time_created', 'time_updated'), }, ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('body', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='articles.Article')), + ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL)), + ('likes', models.ManyToManyField(blank=True, related_name='comment_likes', to=settings.AUTH_USER_MODEL)), + ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='thread', to='articles.Comment')), + ], + ), + migrations.CreateModel( + name='CommentHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('comment', models.TextField()), + ('date_created', models.DateTimeField(auto_now=True)), + ('parent_comment', models.ForeignKey(db_column='parent_comment', on_delete=django.db.models.deletion.CASCADE, to='articles.Comment')), + ], + ), + migrations.CreateModel( + name='Highlight', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('index_start', models.IntegerField(default=0)), + ('index_stop', models.IntegerField()), + ('highlighted_article_piece', models.CharField(blank=True, max_length=200)), + ('comment', models.CharField(blank=True, max_length=200)), + ('time_created', models.DateTimeField(auto_now_add=True, db_index=True)), + ('time_updated', models.DateTimeField(auto_now=True, db_index=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='highlights', to='articles.Article')), + ('highlighter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='highlights', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('time_updated',), + }, + ), ] diff --git a/authors/apps/articles/migrations/0002_article_author.py b/authors/apps/articles/migrations/0002_article_author.py deleted file mode 100644 index e6df668..0000000 --- a/authors/apps/articles/migrations/0002_article_author.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-06 08:05 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('articles', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='article', - name='author', - field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - preserve_default=False, - ), - ] diff --git a/authors/apps/articles/migrations/0003_auto_20181107_0615.py b/authors/apps/articles/migrations/0003_auto_20181107_0615.py deleted file mode 100644 index 133bfdf..0000000 --- a/authors/apps/articles/migrations/0003_auto_20181107_0615.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-07 06:15 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0002_article_author'), - ] - - operations = [ - migrations.RemoveField( - model_name='article', - name='image', - ), - migrations.AddField( - model_name='article', - name='images', - field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), blank=True, default=None, null=True, size=None), - ), - ] diff --git a/authors/apps/articles/migrations/0004_article_average_rating.py b/authors/apps/articles/migrations/0004_article_average_rating.py deleted file mode 100644 index 57a8d81..0000000 --- a/authors/apps/articles/migrations/0004_article_average_rating.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-09 07:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0003_auto_20181107_0615'), - ] - - operations = [ - migrations.AddField( - model_name='article', - name='average_rating', - field=models.IntegerField(default=0), - ), - ] diff --git a/authors/apps/articles/migrations/0005_comment.py b/authors/apps/articles/migrations/0005_comment.py deleted file mode 100644 index 37bf23a..0000000 --- a/authors/apps/articles/migrations/0005_comment.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-09 14:47 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('articles', '0004_article_average_rating'), - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('body', models.TextField()), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='articles.Article')), - ('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL)), - ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='thread', to='articles.Comment')), - ], - ), - ] diff --git a/authors/apps/articles/migrations/0006_commenthistory.py b/authors/apps/articles/migrations/0006_commenthistory.py deleted file mode 100644 index faa509a..0000000 --- a/authors/apps/articles/migrations/0006_commenthistory.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 11:08 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0005_comment'), - ] - - operations = [ - migrations.CreateModel( - name='CommentHistory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('comment', models.TextField()), - ('date_created', models.DateTimeField(auto_now=True)), - ('parent_comment', models.ForeignKey(db_column='parent_comment', on_delete=django.db.models.deletion.CASCADE, to='articles.Comment')), - ], - ), - ] diff --git a/authors/apps/articles/migrations/0006_highlight.py b/authors/apps/articles/migrations/0006_highlight.py deleted file mode 100644 index 883c4a7..0000000 --- a/authors/apps/articles/migrations/0006_highlight.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 08:26 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('articles', '0005_comment'), - ] - - operations = [ - migrations.CreateModel( - name='Highlight', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('index_start', models.IntegerField(default=0)), - ('index_stop', models.IntegerField()), - ('comment', models.CharField(max_length=200)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), - ('highlighter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/authors/apps/articles/migrations/0007_highlight_highlighted_article_piece.py b/authors/apps/articles/migrations/0007_highlight_highlighted_article_piece.py deleted file mode 100644 index ac42906..0000000 --- a/authors/apps/articles/migrations/0007_highlight_highlighted_article_piece.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 09:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0006_highlight'), - ] - - operations = [ - migrations.AddField( - model_name='highlight', - name='highlighted_article_piece', - field=models.CharField(blank=True, max_length=200), - ), - ] diff --git a/authors/apps/articles/migrations/0008_auto_20181112_1055.py b/authors/apps/articles/migrations/0008_auto_20181112_1055.py deleted file mode 100644 index a186894..0000000 --- a/authors/apps/articles/migrations/0008_auto_20181112_1055.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 10:55 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0007_highlight_highlighted_article_piece'), - ] - - operations = [ - migrations.AlterField( - model_name='highlight', - name='article', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='highlights', to='articles.Article'), - ), - migrations.AlterField( - model_name='highlight', - name='comment', - field=models.CharField(blank=True, max_length=200), - ), - migrations.AlterField( - model_name='highlight', - name='highlighter', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='highlights', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/authors/apps/articles/migrations/0009_auto_20181112_1057.py b/authors/apps/articles/migrations/0009_auto_20181112_1057.py deleted file mode 100644 index ef266ca..0000000 --- a/authors/apps/articles/migrations/0009_auto_20181112_1057.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 10:57 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0008_auto_20181112_1055'), - ] - - operations = [ - migrations.AlterField( - model_name='article', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/authors/apps/articles/migrations/0010_auto_20181112_2048.py b/authors/apps/articles/migrations/0010_auto_20181112_2048.py deleted file mode 100644 index 604a18b..0000000 --- a/authors/apps/articles/migrations/0010_auto_20181112_2048.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 20:48 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0009_auto_20181112_1057'), - ] - - operations = [ - migrations.AlterModelOptions( - name='article', - options={'ordering': ('time_updated',)}, - ), - migrations.AlterModelOptions( - name='highlight', - options={'ordering': ('time_updated',)}, - ), - migrations.AddField( - model_name='highlight', - name='comment_slug', - field=models.SlugField(default='comment', max_length=40, unique=True), - preserve_default=False, - ), - migrations.AddField( - model_name='highlight', - name='time_created', - field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.AddField( - model_name='highlight', - name='time_updated', - field=models.DateTimeField(auto_now=True, db_index=True), - ), - ] diff --git a/authors/apps/articles/migrations/0011_remove_highlight_comment_slug.py b/authors/apps/articles/migrations/0011_remove_highlight_comment_slug.py deleted file mode 100644 index 5a2943c..0000000 --- a/authors/apps/articles/migrations/0011_remove_highlight_comment_slug.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-12 21:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0010_auto_20181112_2048'), - ] - - operations = [ - migrations.RemoveField( - model_name='highlight', - name='comment_slug', - ), - ] diff --git a/authors/apps/articles/migrations/0012_merge_20181114_1252.py b/authors/apps/articles/migrations/0012_merge_20181114_1252.py deleted file mode 100644 index 5f8dc3a..0000000 --- a/authors/apps/articles/migrations/0012_merge_20181114_1252.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-14 12:52 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('articles', '0006_commenthistory'), - ('articles', '0011_remove_highlight_comment_slug'), - ] - - operations = [ - ] diff --git a/authors/apps/articles/models.py b/authors/apps/articles/models.py index 68675df..ed540a4 100644 --- a/authors/apps/articles/models.py +++ b/authors/apps/articles/models.py @@ -1,10 +1,11 @@ -'''articles/models.py''' +"""articles/models.py""" from django.db import models from django.contrib.postgres.fields import ArrayField from authors.apps.authentication.models import User + class Article(models.Model): - '''Model representing articles''' + """Model representing articles""" title = models.CharField(db_index=True, max_length=255) body = models.TextField() images = ArrayField(models.TextField(), default=None, @@ -22,15 +23,15 @@ class Article(models.Model): average_rating = models.IntegerField(default=0) class Meta(): - '''Meta class defining order''' - ordering = ('time_updated',) + """Meta class defining order""" + ordering = ('time_created', 'time_updated',) def save(self, *args, **kwargs): - '''override save from super''' + """override save from super""" super(Article, self).save(*args, **kwargs) def __str__(self): - '''return string representation of object''' + """return string representation of object""" return self.title @@ -45,16 +46,18 @@ class Comment(models.Model): parent = models.ForeignKey( 'self', null=True, blank=False, on_delete=models.CASCADE, related_name='thread') article = models.ForeignKey( - Article, blank=True, null=True, on_delete=models.CASCADE, related_name='comments') + Article, blank=True, null=True, on_delete=models.CASCADE, related_name='comments') author = models.ForeignKey( User, blank=True, null=True, on_delete=models.CASCADE, related_name='comments') body = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + likes = models.ManyToManyField(User, related_name='comment_likes', blank=True) def __str__(self): return self.body + class CommentHistory(models.Model): """ implements comment edit history table @@ -64,6 +67,8 @@ class CommentHistory(models.Model): on_delete=models.CASCADE, db_column='parent_comment') date_created = models.DateTimeField(auto_now=True) + + class Highlight(models.Model): """ Table representing highlights and comments made on articles diff --git a/authors/apps/articles/serializers.py b/authors/apps/articles/serializers.py index a98546b..11c57a7 100644 --- a/authors/apps/articles/serializers.py +++ b/authors/apps/articles/serializers.py @@ -5,7 +5,6 @@ from rest_framework import serializers from django.utils.text import slugify from authors.apps.authentication.serializers import UserSerializer -from ..authentication.models import User from rest_framework.exceptions import PermissionDenied from authors.apps.authentication.models import User from authors.apps.profiles.serializers import ProfileSerializer @@ -138,6 +137,7 @@ class CommentSerializer(serializers.ModelSerializer): author = serializers.SerializerMethodField() article = serializers.ReadOnlyField(source='article.title') thread = RecursiveSerializer(many=True, read_only=True) + likes = serializers.SerializerMethodField(method_name='count_likes') class Meta: model = Comment @@ -149,7 +149,8 @@ class Meta: 'author', 'thread', 'created_at', - 'updated_at' + 'updated_at', + 'likes', ) def update(self, instance, valid_input, **kwargs): @@ -177,12 +178,24 @@ def create(self, valid_input): instance = Comment.objects.create(parent=parent, **valid_input) return instance + def count_likes(self, instance): + """Returns the total likes of a comment""" + request = self.context.get('request') + liked_by_me = False + if request is not None and request.user.is_authenticated: + user_id = request.user.id + liked_by_me = instance.likes.all().filter(id=user_id).count() == 1 + return {'count': instance.likes.count(), 'me': liked_by_me} + + class CommentHistorySerializer(serializers.ModelSerializer): """comment history serializer""" + class Meta: model = CommentHistory fields = ('id', 'comment', 'date_created', 'parent_comment') + class HighlightSerializer(serializers.ModelSerializer): '''Highlight model serializer''' article = ArticleSerializer(read_only=True) @@ -203,7 +216,7 @@ def create(self, validated_data): validated_data["highlighter"] = self.context.get('highlighter') validated_data["article"] = self.context.get('article') highlight_text = validated_data["article"].body[ - validated_data["index_start"]:validated_data["index_stop"]] + validated_data["index_start"]:validated_data["index_stop"]] if not highlight_text: raise serializers.ValidationError("Text doesn't exist on this article") validated_data["highlighted_article_piece"] = highlight_text diff --git a/authors/apps/articles/tests/base.py b/authors/apps/articles/tests/base.py index f64465f..3472845 100644 --- a/authors/apps/articles/tests/base.py +++ b/authors/apps/articles/tests/base.py @@ -32,7 +32,9 @@ def setUp(self): self.HIGHLIGHT_ID = '/api/articles/test-title/highlight/{}/' self.HIGHLIGHT_ID_2 = '/api/articles/test-title1/highlight/{}/' - # define test client + self.LIKE_COMMENT = '/api/articles/{}/comment/{}/like/' + self.DISLIKE_COMMENT = '/api/articles/{}/comment/{}/dislike/' + self.client = APIClient() # define user data for signup @@ -76,6 +78,10 @@ def setUp(self): } # signup and login 2 test users (author and non-article-author) to obtain 2 tokens for tesing + self.test_comment_data = { + "body": "this is a sample comment" + } + self.client.post(self.SIGN_UP_URL, test_user, format="json") response = self.client.post(self.SIGN_IN_URL, test_user, format="json") self.token = "bearer " + json.loads(response.content)["user"]["token"] diff --git a/authors/apps/articles/tests/test_like_comments.py b/authors/apps/articles/tests/test_like_comments.py new file mode 100644 index 0000000..062f2f3 --- /dev/null +++ b/authors/apps/articles/tests/test_like_comments.py @@ -0,0 +1,155 @@ +from .base import BaseTest +from authors.apps.articles.models import Article +from rest_framework.views import status +import json + + +class CommentsLikeDislikeTestCase(BaseTest): + """test class for liking and disliking comments """ + + def get_token(self): + response = self.client.post(self.SIGN_UP_URL, self.fav_test_user, format='json') + response = self.client.post(self.SIGN_IN_URL, self.fav_test_user, format='json') + token = response.data['token'] + return token + + def create_article(self, token, article): + """ Method to create an article""" + return self.client.post(self.ARTICLES, self.test_article_data, + HTTP_AUTHORIZATION='Bearer ' + token, format='json') + + def create_comment(self, token, slug, test_comment_data): + """ Method to create an article then comment""" + self.client.post(self.ARTICLES, self.test_article_data, + HTTP_AUTHORIZATION='Bearer ' + token, format='json') + return self.client.post('/api/articles/test-title12/comment/', self.test_comment_data, + HTTP_AUTHORIZATION='Bearer ' + token, format='json') + + def test_like_comment(self): + """Test test the liking of a comment""" + token = self.get_token() + article_data = { + "title": "test title", + "body": "This is me testing", + "description": "testing", + "time_to_read": 1, + "tags": ["TDD"] + } + + # article created + response = self.create_article(token, article_data) + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + + # creating a comment + slug = response.data['slug'] + test_comment_data = { + "body": "this is a sample comment" + } + response = self.create_comment(token, slug, test_comment_data) + + comment_id = response.data["id"] + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + + # like a comment + response = self.client.put('/api/articles/test-title12/comment/' + str(comment_id) + '/like/', + HTTP_AUTHORIZATION=self.token, format='json') + self.assertEquals(status.HTTP_200_OK, response.status_code) + + def test_unlike_comment(self): + """Test test the liking of a comment""" + token = self.get_token() + article_data = { + "title": "test title", + "body": "This is me testing", + "description": "testing", + "time_to_read": 1, + "tags": ["TDD"] + } + + response = self.create_article(token, article_data) + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + slug = response.data['slug'] + test_comment_data = { + "body": "this is a sample comment" + } + + response = self.create_comment(token, slug, test_comment_data) + comment_id = response.data["id"] + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + + response = self.client.put('/api/articles/test-title12/comment/' + str(comment_id) + '/like/', + HTTP_AUTHORIZATION=self.token, format='json') + self.assertEquals(status.HTTP_200_OK, response.status_code) + + def test_like_missing_article(self): + """Test test the liking of a comment""" + token = self.get_token() + article_data = { + "title": "test title", + "body": "This is me testing", + "description": "testing", + "time_to_read": 1, + "tags": ["TDD"] + } + + response = self.create_article(token, article_data) + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + slug = response.data['slug'] + test_comment_data = { + "body": "this is a sample comment" + } + + response = self.create_comment(token, slug, test_comment_data) + comment_id = response.data["id"] + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + + response = self.client.put('/api/articles/me/comment/' + str(comment_id) + '/dislike/', + HTTP_AUTHORIZATION='Bearer ' + token, + format='json' + ) + self.assertEquals(status.HTTP_404_NOT_FOUND, response.status_code) + + def test_like_missing_comment(self): + """Test test the liking of a comment""" + token = self.get_token() + article_data = { + "title": "test title", + "body": "This is me testing", + "description": "testing", + "time_to_read": 1, + "tags": ["TDD"] + } + + response = self.create_article(token, article_data) + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + slug = response.data['slug'] + test_comment_data = { + "body": "this is a sample comment" + } + + response = self.create_comment(token, slug, test_comment_data) + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + + response = self.client.put('/api/articles/test-title12/comment/99/dislike/', + HTTP_AUTHORIZATION='Bearer ' + token, + format='json' + ) + self.assertEquals(status.HTTP_404_NOT_FOUND, response.status_code) + + def test_like_comment_if_article_does_not_exist(self): + """Test test the liking of a comment in an article that does not exist""" + token = self.get_token() + slug = 'test-title12' + test_comment_data = { + "body": "this is a sample comment" + } + + response = self.create_comment(token, slug, test_comment_data) + comment_id = response.data["id"] + self.assertEquals(status.HTTP_201_CREATED, response.status_code) + # like a comment + response = self.client.put('/api/articles/test-title123/comment/' + str(comment_id) + '/like/', + HTTP_AUTHORIZATION='Bearer ' + token, + format='json' + ) + self.assertEquals(response.data['Error'], 'The article does not exist') diff --git a/authors/apps/articles/urls.py b/authors/apps/articles/urls.py index 84232b8..b364677 100644 --- a/authors/apps/articles/urls.py +++ b/authors/apps/articles/urls.py @@ -1,10 +1,10 @@ '''articles/urls.py''' from django.urls import path + from .views import (ArticlesView, ArticlesFavoriteAPIView, CommentRetrieveUpdateDestroy, CommentsListCreateAPIView, ArticlesFeedAPIView, ArticlesSearchListAPIView, - CommentHistoryAPIView, HighlightCommentView) - + CommentHistoryAPIView, HighlightCommentView, LikeComments) # map http methods to defined methods in ArticlesViews articles_list = ArticlesView.as_view({ @@ -38,6 +38,12 @@ 'get': 'retrieve', }) +# like a comment +like_comment = LikeComments.as_view() + +# like a comment +like_comment = LikeComments.as_view() + urlpatterns = [ path('articles/', articles_list), path('articles/feed/', ArticlesFeedAPIView.as_view()), @@ -49,5 +55,6 @@ path('search/articles/', ArticlesSearchListAPIView.as_view(), name='search'), path('articles//history//', CommentHistoryAPIView.as_view()), path('articles//highlight/', highlights), - path('articles//highlight//', highlights_detal) + path('articles//highlight//', highlights_detal), + path('articles//comment//like/', like_comment, name='like_comment'), ] diff --git a/authors/apps/articles/views.py b/authors/apps/articles/views.py index 5326c3a..b2c2b68 100644 --- a/authors/apps/articles/views.py +++ b/authors/apps/articles/views.py @@ -1,16 +1,17 @@ '''articles/views.py''' +import django_filters from django_filters.rest_framework import DjangoFilterBackend from django.shortcuts import render from django.shortcuts import render, get_object_or_404 from rest_framework.filters import SearchFilter -from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView, ListAPIView +from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView, ListAPIView, UpdateAPIView + from rest_framework.permissions import ( - AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly,) + IsAuthenticated, IsAuthenticatedOrReadOnly, ) from rest_framework.response import Response from rest_framework import status, viewsets, generics from rest_framework.exceptions import NotFound, PermissionDenied from rest_framework.views import APIView -import django_filters from django_filters import rest_framework as filters from django.contrib.postgres.fields import ArrayField @@ -61,7 +62,7 @@ def get_queryset(self): def list(self, request): ''' method to fetch all articles''' serializer_context = {'request': request} - + page = self.paginate_queryset(self.get_queryset()) serializer = self.serializer_class( page, context=serializer_context, many=True) @@ -86,7 +87,7 @@ def update(self, request, slug): '''method updating an article(put)''' article = self.check_article_exists(slug) serializer = self.serializer_class(article, data=request.data, context={ - "email": request.user}, partial=True) + "email": request.user}, partial=True) serializer.is_valid(raise_exception=True) serializer.save() @@ -334,7 +335,7 @@ class Meta: ArrayField: { 'filter_class': django_filters.CharFilter, 'extra': lambda f: { - 'lookup_expr': 'icontains',}, + 'lookup_expr': 'icontains', }, }, } @@ -389,6 +390,7 @@ def get(self, request, *args, **kwargs): return generics.ListAPIView.list(self, request, *args, **kwargs) + class HighlightCommentView(ArticleMetaData, viewsets.ModelViewSet): """ view allowing highlighting and commenting on a specific part of an article @@ -443,3 +445,33 @@ def delete(self, request, slug, id): highlight.delete() return Response(dict(message="Comment deleted"), status=status.HTTP_200_OK) + + +class LikeComments(UpdateAPIView): + """Class for comment likes""" + serializer_class = CommentSerializer + + def update(self, request, *args, **kwargs): + """Method for updating comment likes""" + slug = self.kwargs['slug'] + try: + Article.objects.get(slug=slug) + except Article.DoesNotExist: + return Response({'Error': 'The article does not exist'}, status.HTTP_404_NOT_FOUND) + try: + pk = self.kwargs.get('id') + comment = Comment.objects.get(id=pk) + except Comment.DoesNotExist: + message = {"Error": "A comment with this ID does not exist"} + return Response(message, status.HTTP_404_NOT_FOUND) + # fetch user + user = request.user + # Confirmation user already liked the comment + confirm = bool(user in comment.likes.all()) + if confirm is True: + comment.likes.remove(user.id) + return Response({"Success": "You un-liked this comment"}, status.HTTP_200_OK) + # Adding user like to list of likes + comment.likes.add(user.id) + message = {"Success": "You liked this comment"} + return Response(message, status.HTTP_200_OK) diff --git a/authors/apps/authentication/.DS_Store b/authors/apps/authentication/.DS_Store deleted file mode 100644 index a98f4ee..0000000 Binary files a/authors/apps/authentication/.DS_Store and /dev/null differ diff --git a/authors/apps/authentication/migrations/0001_initial.py b/authors/apps/authentication/migrations/0001_initial.py index 3145f21..62defe9 100644 --- a/authors/apps/authentication/migrations/0001_initial.py +++ b/authors/apps/authentication/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.2 on 2018-10-23 09:07 +# Generated by Django 2.1.2 on 2018-11-11 08:43 from django.db import migrations, models @@ -23,6 +23,7 @@ class Migration(migrations.Migration): ('email', models.EmailField(db_index=True, max_length=254, unique=True)), ('is_active', models.BooleanField(default=True)), ('is_staff', models.BooleanField(default=False)), + ('is_confirmed', models.BooleanField(default=False)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), diff --git a/authors/apps/authentication/migrations/0002_user_is_confirmed.py b/authors/apps/authentication/migrations/0002_user_is_confirmed.py deleted file mode 100644 index feac1d7..0000000 --- a/authors/apps/authentication/migrations/0002_user_is_confirmed.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-01 10:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='is_confirmed', - field=models.BooleanField(default=False), - ), - ] diff --git a/authors/apps/profiles/.DS_Store b/authors/apps/profiles/.DS_Store deleted file mode 100644 index a9eeb2c..0000000 Binary files a/authors/apps/profiles/.DS_Store and /dev/null differ diff --git a/authors/apps/profiles/migrations/0001_initial.py b/authors/apps/profiles/migrations/0001_initial.py index 166cf2e..0b64e5f 100644 --- a/authors/apps/profiles/migrations/0001_initial.py +++ b/authors/apps/profiles/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.2 on 2018-11-08 20:53 +# Generated by Django 2.1.2 on 2018-11-15 09:44 from django.conf import settings from django.db import migrations, models diff --git a/authors/apps/rating/migrations/0001_initial.py b/authors/apps/rating/migrations/0001_initial.py index 5ab7d69..69c2489 100644 --- a/authors/apps/rating/migrations/0001_initial.py +++ b/authors/apps/rating/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.2 on 2018-11-07 16:22 +# Generated by Django 2.1.2 on 2018-11-15 09:44 from django.conf import settings from django.db import migrations, models @@ -10,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('articles', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('articles', '0003_auto_20181107_0615'), ] operations = [