From 344d0908c780b71cff6e228e9ce0451e82c67ee0 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Tue, 8 Oct 2024 21:46:05 +0200 Subject: [PATCH 1/5] Initial commit --- src/newsreader/news/core/endpoints.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index 184515b..39a8ecb 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -1,3 +1,4 @@ +from django.db.models import Prefetch from rest_framework import status from rest_framework.generics import ( GenericAPIView, @@ -11,6 +12,7 @@ from rest_framework.response import Response from newsreader.accounts.permissions import IsPostOwner from newsreader.core.pagination import CursorPagination +from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.serializers import RuleSerializer from newsreader.news.core.filters import ReadFilter, SavedFilter from newsreader.news.core.models import Category, Post @@ -63,10 +65,9 @@ class NestedRuleCategoryView(ListAPIView): class NestedPostCategoryView(ListAPIView): - queryset = Category.objects.prefetch_related("rules", "rules__posts").all() serializer_class = PostSerializer pagination_class = CursorPagination - filter_backends = [ReadFilter] + filter_backends = [ReadFilter] # TODO: remove read filter usage def get_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field @@ -75,13 +76,18 @@ class NestedPostCategoryView(ListAPIView): # filtered on the user. filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - category = get_object_or_404(self.queryset, **filter_kwargs) + rules_queryset = CollectionRule.objects.filter(user=self.request.user) + prefetch = Prefetch("rules", queryset=rules_queryset, to_attr="user_rules") + category_queryset = ( + Category.objects + .prefetch_related(prefetch) + .filter(user=self.request.user) + ) + category = get_object_or_404(category_queryset, **filter_kwargs) + self.check_object_permissions(self.request, category) - rules = category.rules.values_list("id", flat=True) - queryset = Post.objects.filter(rule__in=rules) - - return queryset + return Post.objects.filter(rule__in=category.user_rules, read=False) class CategoryReadView(GenericAPIView): From ad7d18b1e2ee9622f9d33b398b8f468636f58f59 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 09:39:19 +0200 Subject: [PATCH 2/5] Remove option to request read posts --- .../js/pages/homepage/actions/posts.js | 4 +-- .../js/tests/homepage/actions/post.test.js | 14 +++++----- .../js/tests/homepage/reducers/post.test.js | 2 +- .../tests/homepage/reducers/selected.test.js | 4 +-- src/newsreader/news/collection/endpoints.py | 7 +++-- .../tests/endpoints/rule/list/tests.py | 23 +-------------- src/newsreader/news/core/endpoints.py | 5 ++-- src/newsreader/news/core/filters.py | 27 ------------------ .../tests/endpoints/category/list/tests.py | 28 ++----------------- .../core/tests/endpoints/post/list/tests.py | 8 +++--- 10 files changed, 25 insertions(+), 97 deletions(-) diff --git a/src/newsreader/js/pages/homepage/actions/posts.js b/src/newsreader/js/pages/homepage/actions/posts.js index 6a0cd7a..68ebe32 100644 --- a/src/newsreader/js/pages/homepage/actions/posts.js +++ b/src/newsreader/js/pages/homepage/actions/posts.js @@ -124,10 +124,10 @@ export const fetchPostsBySection = (section, next = false) => { switch (section.type) { case RULE_TYPE: - url = next ? next : `/api/rules/${section.id}/posts/?read=false`; + url = next ? next : `/api/rules/${section.id}/posts/`; break; case CATEGORY_TYPE: - url = next ? next : `/api/categories/${section.id}/posts/?read=false`; + url = next ? next : `/api/categories/${section.id}/posts/`; break; } diff --git a/src/newsreader/js/tests/homepage/actions/post.test.js b/src/newsreader/js/tests/homepage/actions/post.test.js index d30e549..6fd5d3d 100644 --- a/src/newsreader/js/tests/homepage/actions/post.test.js +++ b/src/newsreader/js/tests/homepage/actions/post.test.js @@ -304,10 +304,10 @@ describe('post actions', () => { type: constants.RULE_TYPE, }; - fetchMock.getOnce('/api/rules/4/posts/?read=false', { + fetchMock.getOnce('/api/rules/4/posts/', { body: { count: 2, - next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar', previous: null, results: posts, }, @@ -325,7 +325,7 @@ describe('post actions', () => { { type: actions.REQUEST_POSTS }, { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar', posts, }, ]; @@ -373,10 +373,10 @@ describe('post actions', () => { type: constants.CATEGORY_TYPE, }; - fetchMock.getOnce('/api/categories/1/posts/?read=false', { + fetchMock.getOnce('/api/categories/1/posts/', { body: { count: 2, - next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar', previous: null, results: posts, }, @@ -394,7 +394,7 @@ describe('post actions', () => { { type: actions.REQUEST_POSTS }, { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar', posts, }, ]; @@ -600,7 +600,7 @@ describe('post actions', () => { const errorMessage = 'Page not found'; - fetchMock.getOnce(`/api/rules/${rule.id}/posts/?read=false`, () => { + fetchMock.getOnce(`/api/rules/${rule.id}/posts/`, () => { throw new Error(errorMessage); }); diff --git a/src/newsreader/js/tests/homepage/reducers/post.test.js b/src/newsreader/js/tests/homepage/reducers/post.test.js index 249a9f1..59f0e36 100644 --- a/src/newsreader/js/tests/homepage/reducers/post.test.js +++ b/src/newsreader/js/tests/homepage/reducers/post.test.js @@ -81,7 +81,7 @@ describe('post actions', () => { const action = { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', posts, }; diff --git a/src/newsreader/js/tests/homepage/reducers/selected.test.js b/src/newsreader/js/tests/homepage/reducers/selected.test.js index 40561a3..0d1a7ae 100644 --- a/src/newsreader/js/tests/homepage/reducers/selected.test.js +++ b/src/newsreader/js/tests/homepage/reducers/selected.test.js @@ -254,13 +254,13 @@ describe('selected reducer', () => { const action = { type: postActions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', posts, }; const expectedState = { ...defaultState, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', lastReached: false, }; diff --git a/src/newsreader/news/collection/endpoints.py b/src/newsreader/news/collection/endpoints.py index 1b11d31..24918c9 100644 --- a/src/newsreader/news/collection/endpoints.py +++ b/src/newsreader/news/collection/endpoints.py @@ -1,3 +1,4 @@ +from django.db.models import Prefetch from rest_framework import status from rest_framework.generics import ( GenericAPIView, @@ -10,7 +11,6 @@ from rest_framework.response import Response from newsreader.core.pagination import CursorPagination from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.serializers import RuleSerializer -from newsreader.news.core.filters import ReadFilter from newsreader.news.core.models import Post from newsreader.news.core.serializers import PostSerializer @@ -24,7 +24,6 @@ class NestedRuleView(ListAPIView): queryset = CollectionRule.objects.prefetch_related("posts").all() serializer_class = PostSerializer pagination_class = CursorPagination - filter_backends = [ReadFilter] def get_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field @@ -33,7 +32,9 @@ class NestedRuleView(ListAPIView): # filtered on the user. filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - rule = get_object_or_404(self.queryset, **filter_kwargs) + prefetch = Prefetch("posts", queryset=Post.objects.filter(read=False)) + queryset = CollectionRule.objects.prefetch_related(prefetch) + rule = get_object_or_404(queryset, **filter_kwargs) self.check_object_permissions(self.request, rule) return rule.posts.order_by("-publication_date") diff --git a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py index 51334b5..24d4b9a 100644 --- a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py +++ b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py @@ -202,7 +202,7 @@ class NestedRuleListViewTestCase(TestCase): with self.subTest(post=post): self.assertEqual(post["rule"]["id"], rule.pk) - def test_unread_posts(self): + def test_posts(self): rule = FeedFactory.create(user=self.user) FeedPostFactory.create_batch(size=10, rule=rule, read=False) @@ -210,7 +210,6 @@ class NestedRuleListViewTestCase(TestCase): response = self.client.get( reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}), - {"read": "false"}, ) data = response.json() @@ -221,23 +220,3 @@ class NestedRuleListViewTestCase(TestCase): for post in data["results"]: with self.subTest(post=post): self.assertEqual(post["read"], False) - - def test_read_posts(self): - rule = FeedFactory.create(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:collection:rules-nested-posts", kwargs={"pk": rule.pk}), - {"read": "true"}, - ) - - data = response.json() - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) - - for post in data["results"]: - with self.subTest(post=post): - self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index 39a8ecb..6f70ae2 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -14,7 +14,7 @@ from newsreader.accounts.permissions import IsPostOwner from newsreader.core.pagination import CursorPagination from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.serializers import RuleSerializer -from newsreader.news.core.filters import ReadFilter, SavedFilter +from newsreader.news.core.filters import SavedFilter from newsreader.news.core.models import Category, Post from newsreader.news.core.serializers import CategorySerializer, PostSerializer @@ -24,7 +24,7 @@ class ListPostView(ListAPIView): serializer_class = PostSerializer permission_classes = (IsAuthenticated, IsPostOwner) pagination_class = CursorPagination - filter_backends = [ReadFilter, SavedFilter] + filter_backends = [SavedFilter] class DetailPostView(RetrieveUpdateAPIView): @@ -67,7 +67,6 @@ class NestedRuleCategoryView(ListAPIView): class NestedPostCategoryView(ListAPIView): serializer_class = PostSerializer pagination_class = CursorPagination - filter_backends = [ReadFilter] # TODO: remove read filter usage def get_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field diff --git a/src/newsreader/news/core/filters.py b/src/newsreader/news/core/filters.py index 05f9157..9883304 100644 --- a/src/newsreader/news/core/filters.py +++ b/src/newsreader/news/core/filters.py @@ -4,33 +4,6 @@ from rest_framework import filters from rest_framework.compat import coreapi, coreschema -class ReadFilter(filters.BaseFilterBackend): - query_param = "read" - - 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(read=value) - - def get_schema_fields(self, view): - return [ - coreapi.Field( - name=self.query_param, - required=False, - location="query", - schema=coreschema.String( - title=str(self.query_param), - description=str(_("Wether posts should be read or not")), - ), - ) - ] - - class SavedFilter(filters.BaseFilterBackend): query_param = "saved" diff --git a/src/newsreader/news/core/tests/endpoints/category/list/tests.py b/src/newsreader/news/core/tests/endpoints/category/list/tests.py index 34060ea..8af63af 100644 --- a/src/newsreader/news/core/tests/endpoints/category/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/category/list/tests.py @@ -409,7 +409,7 @@ class NestedCategoryPostView(TestCase): reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) ) - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) def test_ordering(self): category = CategoryFactory.create(user=self.user) @@ -503,7 +503,7 @@ class NestedCategoryPostView(TestCase): self.assertEqual(posts[0]["rule"]["id"], guardian_rule.pk) self.assertEqual(posts[1]["rule"]["id"], guardian_rule.pk) - def test_unread_posts(self): + def test_posts(self): category = CategoryFactory.create(user=self.user) rule = FeedFactory(category=category) @@ -514,7 +514,6 @@ class NestedCategoryPostView(TestCase): reverse( "api:news:core:categories-nested-posts", kwargs={"pk": category.pk} ), - {"read": "false"}, ) data = response.json() @@ -525,26 +524,3 @@ class NestedCategoryPostView(TestCase): for post in posts: self.assertEqual(post["read"], False) - - def test_read_posts(self): - category = CategoryFactory.create(user=self.user) - rule = FeedFactory(category=category) - - 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:categories-nested-posts", kwargs={"pk": category.pk} - ), - {"read": "true"}, - ) - - data = response.json() - posts = data["results"] - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) - - for post in posts: - self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/core/tests/endpoints/post/list/tests.py b/src/newsreader/news/core/tests/endpoints/post/list/tests.py index f88e575..bf3f4dd 100644 --- a/src/newsreader/news/core/tests/endpoints/post/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/post/list/tests.py @@ -53,25 +53,25 @@ class PostListViewTestCase(TestCase): with self.subTest(post=post): self.assertEqual(data["results"][index]["id"], post.pk) - def test_read_posts(self): + def test_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"} + reverse("api:news:core:posts-list") ) data = response.json() posts = data["results"] self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) + self.assertEqual(len(data["results"]), 20) for post in posts: with self.subTest(post=post): - self.assertEqual(post["read"], True) + self.assertEqual(post["read"], False) def test_saved_posts(self): rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) From 34546e49cfe0c140b7154deb9e558ba1ca59e32f Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 09:46:59 +0200 Subject: [PATCH 3/5] Apply read filtering --- src/newsreader/news/core/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index 6f70ae2..ed778b4 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -20,7 +20,7 @@ from newsreader.news.core.serializers import CategorySerializer, PostSerializer class ListPostView(ListAPIView): - queryset = Post.objects.all() + queryset = Post.objects.filter(read=False) serializer_class = PostSerializer permission_classes = (IsAuthenticated, IsPostOwner) pagination_class = CursorPagination From 138b847b1862a5ffb770957fc8f4e83fcc7b6a1f Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 09:47:16 +0200 Subject: [PATCH 4/5] Add missing user kwarg --- src/newsreader/news/core/tests/endpoints/category/list/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newsreader/news/core/tests/endpoints/category/list/tests.py b/src/newsreader/news/core/tests/endpoints/category/list/tests.py index 8af63af..610dc58 100644 --- a/src/newsreader/news/core/tests/endpoints/category/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/category/list/tests.py @@ -505,7 +505,7 @@ class NestedCategoryPostView(TestCase): def test_posts(self): category = CategoryFactory.create(user=self.user) - rule = FeedFactory(category=category) + rule = FeedFactory(category=category, user=self.user) FeedPostFactory.create_batch(size=10, rule=rule, read=False) FeedPostFactory.create_batch(size=10, rule=rule, read=True) From 05065c8af4d71c35b003c87b8ea22dd5be3830d9 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 09:50:08 +0200 Subject: [PATCH 5/5] Apply formatting --- src/newsreader/news/core/endpoints.py | 6 ++---- src/newsreader/news/core/tests/endpoints/post/list/tests.py | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index ed778b4..6ed9f39 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -77,10 +77,8 @@ class NestedPostCategoryView(ListAPIView): rules_queryset = CollectionRule.objects.filter(user=self.request.user) prefetch = Prefetch("rules", queryset=rules_queryset, to_attr="user_rules") - category_queryset = ( - Category.objects - .prefetch_related(prefetch) - .filter(user=self.request.user) + category_queryset = Category.objects.prefetch_related(prefetch).filter( + user=self.request.user ) category = get_object_or_404(category_queryset, **filter_kwargs) diff --git a/src/newsreader/news/core/tests/endpoints/post/list/tests.py b/src/newsreader/news/core/tests/endpoints/post/list/tests.py index bf3f4dd..467fd05 100644 --- a/src/newsreader/news/core/tests/endpoints/post/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/post/list/tests.py @@ -59,9 +59,7 @@ class PostListViewTestCase(TestCase): 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") - ) + response = self.client.get(reverse("api:news:core:posts-list")) data = response.json() posts = data["results"]