Apply query optimizations for posts

This commit is contained in:
Sonny Bakker 2024-10-13 10:16:57 +02:00
parent 2d5801f226
commit e33497569a
10 changed files with 37 additions and 107 deletions

View file

@ -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;
}

View file

@ -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);
});

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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")

View file

@ -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)

View file

@ -1,3 +1,4 @@
from django.db.models import Prefetch
from rest_framework import status
from rest_framework.generics import (
GenericAPIView,
@ -11,18 +12,19 @@ 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.filters import SavedFilter
from newsreader.news.core.models import Category, Post
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
filter_backends = [ReadFilter, SavedFilter]
filter_backends = [SavedFilter]
class DetailPostView(RetrieveUpdateAPIView):
@ -63,10 +65,8 @@ class NestedRuleCategoryView(ListAPIView):
class NestedPostCategoryView(ListAPIView):
queryset = Category.objects.prefetch_related("rules", "rules__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
@ -75,13 +75,16 @@ 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):

View file

@ -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"

View file

@ -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,9 +503,9 @@ 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)
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)
@ -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)

View file

@ -53,25 +53,23 @@ 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"}
)
response = self.client.get(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))