Add posts list endpoint
This commit is contained in:
parent
8c69e4a27e
commit
21ba53960e
9 changed files with 202 additions and 25 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
|||
14
src/newsreader/news/core/migrations/0008_post_saved.py
Normal file
14
src/newsreader/news/core/migrations/0008_post_saved.py
Normal 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)
|
||||
)
|
||||
]
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class PostSerializer(serializers.ModelSerializer):
|
|||
"url",
|
||||
"rule",
|
||||
"read",
|
||||
"saved",
|
||||
"publicationDate",
|
||||
"remoteIdentifier",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
96
src/newsreader/news/core/tests/endpoints/post/list/tests.py
Normal file
96
src/newsreader/news/core/tests/endpoints/post/list/tests.py
Normal 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)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue