Add posts list endpoint

This commit is contained in:
Sonny Bakker 2021-02-19 22:20:23 +01:00
parent 8c69e4a27e
commit 21ba53960e
9 changed files with 202 additions and 25 deletions

View file

@ -13,11 +13,19 @@ from rest_framework.response import Response
from newsreader.accounts.permissions import IsPostOwner
from newsreader.core.pagination import CursorPagination
from newsreader.news.collection.serializers import RuleSerializer
from newsreader.news.core.filters import ReadFilter
from newsreader.news.core.filters import ReadFilter, SavedFilter
from newsreader.news.core.models import Category, Post
from newsreader.news.core.serializers import CategorySerializer, PostSerializer
class ListPostView(ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsPostOwner)
pagination_class = CursorPagination
filter_backends = [ReadFilter, SavedFilter]
class DetailPostView(RetrieveUpdateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer

View file

@ -30,3 +30,30 @@ class ReadFilter(filters.BaseFilterBackend):
),
)
]
class SavedFilter(filters.BaseFilterBackend):
query_param = "saved"
def filter_queryset(self, request, queryset, view):
key = request.query_params.get(self.query_param, None)
available_values = {"True": True, "true": True, "False": False, "false": False}
if not key or key not in available_values.keys():
return queryset
value = available_values[key]
return queryset.filter(saved=value)
def get_schema_fields(self, view):
return [
coreapi.Field(
name=self.query_param,
required=False,
location="query",
schema=coreschema.String(
title=force_text(self.query_param),
description=force_text(_("Wether posts should be saved or not")),
),
)
]

View file

@ -0,0 +1,14 @@
# Generated by Django 3.1.5 on 2021-02-19 20:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0007_auto_20200706_2312")]
operations = [
migrations.AddField(
model_name="post", name="saved", field=models.BooleanField(default=False)
)
]

View file

@ -14,6 +14,7 @@ class Post(TimeStampedModel):
url = models.URLField(max_length=1024, blank=True, null=True)
read = models.BooleanField(default=False)
saved = models.BooleanField(default=False)
rule = models.ForeignKey(
CollectionRule, on_delete=models.CASCADE, editable=False, related_name="posts"

View file

@ -19,6 +19,7 @@ class PostSerializer(serializers.ModelSerializer):
"url",
"rule",
"read",
"saved",
"publicationDate",
"remoteIdentifier",
)

View file

@ -22,8 +22,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["id"], post.pk)
self.assertEqual(response.status_code, 200)
self.assertEqual(data["id"], post.pk)
self.assertTrue("title" in data)
self.assertTrue("body" in data)
@ -37,8 +37,8 @@ class PostDetailViewTestCase(TestCase):
response = self.client.get(reverse("api:news:core:posts-detail", args=[100]))
data = response.json()
self.assertEquals(response.status_code, 404)
self.assertEquals(data["detail"], "Not found.")
self.assertEqual(response.status_code, 404)
self.assertEqual(data["detail"], "Not found.")
def test_post(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -49,8 +49,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 405)
self.assertEquals(data["detail"], 'Method "POST" not allowed.')
self.assertEqual(response.status_code, 405)
self.assertEqual(data["detail"], 'Method "POST" not allowed.')
def test_patch(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -63,8 +63,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["title"], "This title is very accurate")
self.assertEqual(response.status_code, 200)
self.assertEqual(data["title"], "This title is very accurate")
def test_identifier_cannot_be_changed(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -77,8 +77,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["id"], post.pk)
self.assertEqual(response.status_code, 200)
self.assertEqual(data["id"], post.pk)
def test_rule_cannot_be_changed(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -98,7 +98,7 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEqual(response.status_code, 200)
self.assertTrue(data["rule"], rule.pk)
@ -113,8 +113,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["title"], "This title is very accurate")
self.assertEqual(response.status_code, 200)
self.assertEqual(data["title"], "This title is very accurate")
def test_delete(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -125,8 +125,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 405)
self.assertEquals(data["detail"], 'Method "DELETE" not allowed.')
self.assertEqual(response.status_code, 405)
self.assertEqual(data["detail"], 'Method "DELETE" not allowed.')
def test_post_with_unauthenticated_user_without_category(self):
self.client.logout()
@ -138,7 +138,7 @@ class PostDetailViewTestCase(TestCase):
reverse("api:news:core:posts-detail", args=[post.pk])
)
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)
def test_post_with_unauthenticated_user_with_category(self):
self.client.logout()
@ -150,7 +150,7 @@ class PostDetailViewTestCase(TestCase):
reverse("api:news:core:posts-detail", args=[post.pk])
)
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)
def test_post_with_unauthorized_user_without_category(self):
other_user = UserFactory()
@ -161,7 +161,7 @@ class PostDetailViewTestCase(TestCase):
reverse("api:news:core:posts-detail", args=[post.pk])
)
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)
def test_post_with_unauthorized_user_with_category(self):
other_user = UserFactory()
@ -172,7 +172,7 @@ class PostDetailViewTestCase(TestCase):
reverse("api:news:core:posts-detail", args=[post.pk])
)
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)
def test_post_with_different_user_for_category_and_rule(self):
other_user = UserFactory()
@ -183,7 +183,7 @@ class PostDetailViewTestCase(TestCase):
reverse("api:news:core:posts-detail", args=[post.pk])
)
self.assertEquals(response.status_code, 403)
self.assertEqual(response.status_code, 403)
def test_mark_read(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -196,8 +196,8 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["read"], True)
self.assertEqual(response.status_code, 200)
self.assertEqual(data["read"], True)
def test_mark_unread(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
@ -210,5 +210,33 @@ class PostDetailViewTestCase(TestCase):
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["read"], False)
self.assertEqual(response.status_code, 200)
self.assertEqual(data["read"], False)
def test_mark_saved(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
post = FeedPostFactory(rule=rule, saved=False)
response = self.client.patch(
reverse("api:news:core:posts-detail", args=[post.pk]),
data=json.dumps({"saved": True}),
content_type="application/json",
)
data = response.json()
self.assertEqual(response.status_code, 200)
self.assertEqual(data["saved"], True)
def test_mark_unsaved(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
post = FeedPostFactory(rule=rule, saved=True)
response = self.client.patch(
reverse("api:news:core:posts-detail", args=[post.pk]),
data=json.dumps({"saved": False}),
content_type="application/json",
)
data = response.json()
self.assertEqual(response.status_code, 200)
self.assertEqual(data["saved"], False)

View file

@ -0,0 +1,96 @@
from datetime import datetime
from django.test import TestCase
from django.urls import reverse
import pytz
from newsreader.accounts.tests.factories import UserFactory
from newsreader.news.collection.tests.factories import FeedFactory
from newsreader.news.core.tests.factories import CategoryFactory, FeedPostFactory
class PostListViewTestCase(TestCase):
def setUp(self):
self.user = UserFactory(is_staff=True, password="test")
self.client.force_login(self.user)
def test_simple(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
FeedPostFactory.create_batch(size=3, rule=rule)
response = self.client.get(reverse("api:news:core:posts-list"))
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(len(data["results"]), 3)
def test_ordering(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
posts = [
FeedPostFactory(
title="I'm the first post",
rule=rule,
publication_date=datetime(2019, 5, 20, 16, 7, 38, tzinfo=pytz.utc),
),
FeedPostFactory(
title="I'm the second post",
rule=rule,
publication_date=datetime(2019, 5, 20, 16, 7, 37, tzinfo=pytz.utc),
),
FeedPostFactory(
title="I'm the third post",
rule=rule,
publication_date=datetime(2019, 5, 20, 16, 7, 36, tzinfo=pytz.utc),
),
]
response = self.client.get(reverse("api:news:core:posts-list"))
data = response.json()
self.assertEquals(response.status_code, 200)
for index, post in enumerate(posts, start=0):
with self.subTest(post=post):
self.assertEqual(data["results"][index]["id"], post.pk)
def test_read_posts(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
FeedPostFactory.create_batch(size=20, rule=rule, read=False)
FeedPostFactory.create_batch(size=10, rule=rule, read=True)
response = self.client.get(
reverse("api:news:core:posts-list"), {"read": "true"}
)
data = response.json()
posts = data["results"]
self.assertEquals(response.status_code, 200)
self.assertEquals(len(data["results"]), 10)
for post in posts:
with self.subTest(post=post):
self.assertEqual(post["read"], True)
def test_saved_posts(self):
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
FeedPostFactory.create_batch(size=20, rule=rule, saved=False)
FeedPostFactory.create_batch(size=10, rule=rule, saved=True)
response = self.client.get(
reverse("api:news:core:posts-list"), {"saved": "true"}
)
data = response.json()
posts = data["results"]
self.assertEquals(response.status_code, 200)
self.assertEquals(len(data["results"]), 10)
for post in posts:
with self.subTest(post=post):
self.assertEqual(post["saved"], True)

View file

@ -6,6 +6,7 @@ from newsreader.news.core.endpoints import (
DetailCategoryView,
DetailPostView,
ListCategoryView,
ListPostView,
NestedPostCategoryView,
NestedRuleCategoryView,
)
@ -32,6 +33,7 @@ urlpatterns = [
]
endpoints = [
path("posts/", ListPostView.as_view(), name="posts-list"),
path("posts/<int:pk>/", DetailPostView.as_view(), name="posts-detail"),
path("categories/", ListCategoryView.as_view(), name="categories-list"),
path(