Apply query optimizations for posts
This commit is contained in:
parent
2d5801f226
commit
e33497569a
10 changed files with 37 additions and 107 deletions
|
|
@ -124,10 +124,10 @@ export const fetchPostsBySection = (section, next = false) => {
|
||||||
|
|
||||||
switch (section.type) {
|
switch (section.type) {
|
||||||
case RULE_TYPE:
|
case RULE_TYPE:
|
||||||
url = next ? next : `/api/rules/${section.id}/posts/?read=false`;
|
url = next ? next : `/api/rules/${section.id}/posts/`;
|
||||||
break;
|
break;
|
||||||
case CATEGORY_TYPE:
|
case CATEGORY_TYPE:
|
||||||
url = next ? next : `/api/categories/${section.id}/posts/?read=false`;
|
url = next ? next : `/api/categories/${section.id}/posts/`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -304,10 +304,10 @@ describe('post actions', () => {
|
||||||
type: constants.RULE_TYPE,
|
type: constants.RULE_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMock.getOnce('/api/rules/4/posts/?read=false', {
|
fetchMock.getOnce('/api/rules/4/posts/', {
|
||||||
body: {
|
body: {
|
||||||
count: 2,
|
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,
|
previous: null,
|
||||||
results: posts,
|
results: posts,
|
||||||
},
|
},
|
||||||
|
|
@ -325,7 +325,7 @@ describe('post actions', () => {
|
||||||
{ type: actions.REQUEST_POSTS },
|
{ type: actions.REQUEST_POSTS },
|
||||||
{
|
{
|
||||||
type: actions.RECEIVE_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,
|
posts,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -373,10 +373,10 @@ describe('post actions', () => {
|
||||||
type: constants.CATEGORY_TYPE,
|
type: constants.CATEGORY_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMock.getOnce('/api/categories/1/posts/?read=false', {
|
fetchMock.getOnce('/api/categories/1/posts/', {
|
||||||
body: {
|
body: {
|
||||||
count: 2,
|
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,
|
previous: null,
|
||||||
results: posts,
|
results: posts,
|
||||||
},
|
},
|
||||||
|
|
@ -394,7 +394,7 @@ describe('post actions', () => {
|
||||||
{ type: actions.REQUEST_POSTS },
|
{ type: actions.REQUEST_POSTS },
|
||||||
{
|
{
|
||||||
type: actions.RECEIVE_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,
|
posts,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -600,7 +600,7 @@ describe('post actions', () => {
|
||||||
|
|
||||||
const errorMessage = 'Page not found';
|
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);
|
throw new Error(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ describe('post actions', () => {
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: actions.RECEIVE_POSTS,
|
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,
|
posts,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,13 +254,13 @@ describe('selected reducer', () => {
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: postActions.RECEIVE_POSTS,
|
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,
|
posts,
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedState = {
|
const expectedState = {
|
||||||
...defaultState,
|
...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,
|
lastReached: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.db.models import Prefetch
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.generics import (
|
from rest_framework.generics import (
|
||||||
GenericAPIView,
|
GenericAPIView,
|
||||||
|
|
@ -10,7 +11,6 @@ from rest_framework.response import Response
|
||||||
from newsreader.core.pagination import CursorPagination
|
from newsreader.core.pagination import CursorPagination
|
||||||
from newsreader.news.collection.models import CollectionRule
|
from newsreader.news.collection.models import CollectionRule
|
||||||
from newsreader.news.collection.serializers import RuleSerializer
|
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.models import Post
|
||||||
from newsreader.news.core.serializers import PostSerializer
|
from newsreader.news.core.serializers import PostSerializer
|
||||||
|
|
||||||
|
|
@ -24,7 +24,6 @@ class NestedRuleView(ListAPIView):
|
||||||
queryset = CollectionRule.objects.prefetch_related("posts").all()
|
queryset = CollectionRule.objects.prefetch_related("posts").all()
|
||||||
serializer_class = PostSerializer
|
serializer_class = PostSerializer
|
||||||
pagination_class = CursorPagination
|
pagination_class = CursorPagination
|
||||||
filter_backends = [ReadFilter]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
|
|
@ -33,7 +32,9 @@ class NestedRuleView(ListAPIView):
|
||||||
# filtered on the user.
|
# filtered on the user.
|
||||||
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
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)
|
self.check_object_permissions(self.request, rule)
|
||||||
|
|
||||||
return rule.posts.order_by("-publication_date")
|
return rule.posts.order_by("-publication_date")
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ class NestedRuleListViewTestCase(TestCase):
|
||||||
with self.subTest(post=post):
|
with self.subTest(post=post):
|
||||||
self.assertEqual(post["rule"]["id"], rule.pk)
|
self.assertEqual(post["rule"]["id"], rule.pk)
|
||||||
|
|
||||||
def test_unread_posts(self):
|
def test_posts(self):
|
||||||
rule = FeedFactory.create(user=self.user)
|
rule = FeedFactory.create(user=self.user)
|
||||||
|
|
||||||
FeedPostFactory.create_batch(size=10, rule=rule, read=False)
|
FeedPostFactory.create_batch(size=10, rule=rule, read=False)
|
||||||
|
|
@ -210,7 +210,6 @@ class NestedRuleListViewTestCase(TestCase):
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}),
|
reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}),
|
||||||
{"read": "false"},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
@ -221,23 +220,3 @@ class NestedRuleListViewTestCase(TestCase):
|
||||||
for post in data["results"]:
|
for post in data["results"]:
|
||||||
with self.subTest(post=post):
|
with self.subTest(post=post):
|
||||||
self.assertEqual(post["read"], False)
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.db.models import Prefetch
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.generics import (
|
from rest_framework.generics import (
|
||||||
GenericAPIView,
|
GenericAPIView,
|
||||||
|
|
@ -11,18 +12,19 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from newsreader.accounts.permissions import IsPostOwner
|
from newsreader.accounts.permissions import IsPostOwner
|
||||||
from newsreader.core.pagination import CursorPagination
|
from newsreader.core.pagination import CursorPagination
|
||||||
|
from newsreader.news.collection.models import CollectionRule
|
||||||
from newsreader.news.collection.serializers import RuleSerializer
|
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.models import Category, Post
|
||||||
from newsreader.news.core.serializers import CategorySerializer, PostSerializer
|
from newsreader.news.core.serializers import CategorySerializer, PostSerializer
|
||||||
|
|
||||||
|
|
||||||
class ListPostView(ListAPIView):
|
class ListPostView(ListAPIView):
|
||||||
queryset = Post.objects.all()
|
queryset = Post.objects.filter(read=False)
|
||||||
serializer_class = PostSerializer
|
serializer_class = PostSerializer
|
||||||
permission_classes = (IsAuthenticated, IsPostOwner)
|
permission_classes = (IsAuthenticated, IsPostOwner)
|
||||||
pagination_class = CursorPagination
|
pagination_class = CursorPagination
|
||||||
filter_backends = [ReadFilter, SavedFilter]
|
filter_backends = [SavedFilter]
|
||||||
|
|
||||||
|
|
||||||
class DetailPostView(RetrieveUpdateAPIView):
|
class DetailPostView(RetrieveUpdateAPIView):
|
||||||
|
|
@ -63,10 +65,8 @@ class NestedRuleCategoryView(ListAPIView):
|
||||||
|
|
||||||
|
|
||||||
class NestedPostCategoryView(ListAPIView):
|
class NestedPostCategoryView(ListAPIView):
|
||||||
queryset = Category.objects.prefetch_related("rules", "rules__posts").all()
|
|
||||||
serializer_class = PostSerializer
|
serializer_class = PostSerializer
|
||||||
pagination_class = CursorPagination
|
pagination_class = CursorPagination
|
||||||
filter_backends = [ReadFilter]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||||
|
|
@ -75,13 +75,16 @@ class NestedPostCategoryView(ListAPIView):
|
||||||
# filtered on the user.
|
# filtered on the user.
|
||||||
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
|
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)
|
self.check_object_permissions(self.request, category)
|
||||||
|
|
||||||
rules = category.rules.values_list("id", flat=True)
|
return Post.objects.filter(rule__in=category.user_rules, read=False)
|
||||||
queryset = Post.objects.filter(rule__in=rules)
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class CategoryReadView(GenericAPIView):
|
class CategoryReadView(GenericAPIView):
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,6 @@ from rest_framework import filters
|
||||||
from rest_framework.compat import coreapi, coreschema
|
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):
|
class SavedFilter(filters.BaseFilterBackend):
|
||||||
query_param = "saved"
|
query_param = "saved"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ class NestedCategoryPostView(TestCase):
|
||||||
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
|
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):
|
def test_ordering(self):
|
||||||
category = CategoryFactory.create(user=self.user)
|
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[0]["rule"]["id"], guardian_rule.pk)
|
||||||
self.assertEqual(posts[1]["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)
|
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=False)
|
||||||
FeedPostFactory.create_batch(size=10, rule=rule, read=True)
|
FeedPostFactory.create_batch(size=10, rule=rule, read=True)
|
||||||
|
|
@ -514,7 +514,6 @@ class NestedCategoryPostView(TestCase):
|
||||||
reverse(
|
reverse(
|
||||||
"api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
|
"api:news:core:categories-nested-posts", kwargs={"pk": category.pk}
|
||||||
),
|
),
|
||||||
{"read": "false"},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
@ -525,26 +524,3 @@ class NestedCategoryPostView(TestCase):
|
||||||
|
|
||||||
for post in posts:
|
for post in posts:
|
||||||
self.assertEqual(post["read"], False)
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -53,25 +53,23 @@ class PostListViewTestCase(TestCase):
|
||||||
with self.subTest(post=post):
|
with self.subTest(post=post):
|
||||||
self.assertEqual(data["results"][index]["id"], post.pk)
|
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))
|
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
|
||||||
|
|
||||||
FeedPostFactory.create_batch(size=20, rule=rule, read=False)
|
FeedPostFactory.create_batch(size=20, rule=rule, read=False)
|
||||||
FeedPostFactory.create_batch(size=10, rule=rule, read=True)
|
FeedPostFactory.create_batch(size=10, rule=rule, read=True)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(reverse("api:news:core:posts-list"))
|
||||||
reverse("api:news:core:posts-list"), {"read": "true"}
|
|
||||||
)
|
|
||||||
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
posts = data["results"]
|
posts = data["results"]
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(len(data["results"]), 10)
|
self.assertEqual(len(data["results"]), 20)
|
||||||
|
|
||||||
for post in posts:
|
for post in posts:
|
||||||
with self.subTest(post=post):
|
with self.subTest(post=post):
|
||||||
self.assertEqual(post["read"], True)
|
self.assertEqual(post["read"], False)
|
||||||
|
|
||||||
def test_saved_posts(self):
|
def test_saved_posts(self):
|
||||||
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
|
rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue