Skip to content

Commit f220088

Browse files
authored
limit number of posts per request muchdogesec/obstracts#489 (#217)
1 parent c038039 commit f220088

5 files changed

Lines changed: 54 additions & 3 deletions

File tree

history4feed/app/serializers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from .models import AUTO_TITLE_TRAIL, FEED_DESCRIPTION_MAX_LENGTH, Category, Feed, Post, Job, normalize_url, FeedType, title_as_string
33
from django.db import models as django_models
44
from django.utils.translation import gettext_lazy as _
5+
from .settings import history4feed_server_settings
6+
57

68
class TitleField(serializers.CharField):
79
def to_internal_value(self, data):
@@ -155,7 +157,7 @@ class Meta:
155157

156158

157159
class CreatePostsSerializer(serializers.Serializer):
158-
posts = serializers.ListField(child=PostCreateSerializer(), allow_empty=False)
160+
posts = serializers.ListField(child=PostCreateSerializer(), allow_empty=False, max_length=history4feed_server_settings.CREATE_POSTS_MAX_LENGTH)
159161

160162

161163
def create(self, validated_data):

history4feed/app/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
'HISTORY4FEED_NAMESPACE': uuid.UUID("6c6e6448-04d4-42a3-9214-4f0f7d02694e"),
1616
"BRAVE_SEARCH_API_KEY": None,
1717
"FULLTEXT_FETCH_TIMEOUT_SECONDS": 100, # time limit for fulltext fetch tasks
18+
"CREATE_POSTS_MAX_LENGTH": 100, # maximum number of posts that can be created in a single request
1819
}
1920

2021
IMPORT_STRINGS = [
@@ -28,7 +29,7 @@ class History4FeedServerSettings(APISettings):
2829
HISTORY4FEED_NAMESPACE : str|uuid.UUID
2930
BRAVE_SEARCH_API_KEY: str
3031
FULLTEXT_FETCH_TIMEOUT_SECONDS: int
31-
32+
CREATE_POSTS_MAX_LENGTH: int
3233
@property
3334
def user_settings(self):
3435
if not hasattr(self, '_user_settings'):

history4feed/app/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from datetime import datetime
5757
import textwrap
5858
import django.utils
59+
from django.db import transaction
5960

6061
from history4feed.app import serializers
6162

@@ -679,6 +680,7 @@ def get_queryset(self):
679680
},
680681
request=CreatePostsSerializer,
681682
)
683+
@transaction.atomic
682684
def create(self, request, *args, feed_id=None, **kwargs):
683685
job_obj = self.new_create_post_job(request, feed_id)
684686
job_resp = JobSerializer(job_obj).data.copy()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "history4feed"
7-
version = "1.2.1"
7+
version = "1.2.2"
88
authors = [
99
{ name = "dogesec" }
1010
]

tests/src/test_serializers.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,52 @@ def test_create_posts_serializer__existing_url(feed_posts):
2626
assert serializer.errors['posts'][0]['link'][0] == f'Post at `{post.link}` already exists in feed.'
2727

2828

29+
@pytest.mark.django_db
30+
def test_create_posts_serializer__CREATE_POSTS_MAX_LENGTH():
31+
"""Test that CreatePostsSerializer uses CREATE_POSTS_MAX_LENGTH from settings"""
32+
from history4feed.app.settings import history4feed_server_settings
33+
from unittest.mock import patch
34+
import importlib
35+
36+
# Mock the settings value before the serializer class is evaluated
37+
with patch.object(history4feed_server_settings, 'CREATE_POSTS_MAX_LENGTH', 2):
38+
# Reload the serializers module so it picks up the mocked value
39+
import history4feed.app.serializers as serializers_module
40+
importlib.reload(serializers_module)
41+
42+
# Now create the serializer with 3 posts (exceeds limit of 2)
43+
serializer = serializers_module.CreatePostsSerializer(data={
44+
"posts": [
45+
{
46+
"title": "Post 1",
47+
"link": "https://example.com/post1",
48+
"pubdate": datetime.now(UTC).isoformat(),
49+
"author": "Author",
50+
"categories": ["Category1"],
51+
},
52+
{
53+
"title": "Post 2",
54+
"link": "https://example.com/post2",
55+
"pubdate": datetime.now(UTC).isoformat(),
56+
"author": "Author",
57+
"categories": ["Category2"],
58+
},
59+
{
60+
"title": "Post 3",
61+
"link": "https://example.com/post3",
62+
"pubdate": datetime.now(UTC).isoformat(),
63+
"author": "Author",
64+
"categories": ["Category3"],
65+
}
66+
]
67+
})
68+
69+
assert not serializer.is_valid()
70+
assert 'posts' in serializer.errors
71+
assert 'Ensure this field has no more than 2' in str(serializer.errors['posts'][0])
72+
73+
# Reload again to restore the original module state
74+
importlib.reload(serializers_module)
2975

3076
@pytest.mark.django_db
3177
def test_post_serializer_excludes_description():

0 commit comments

Comments
 (0)