From fb5194cee74400ab80968d7f19d2e01ba27777d0 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Tue, 14 Jul 2026 21:52:11 -0400 Subject: [PATCH 01/10] Story 2375: Wagtail Integration for create a post --- news/views.py | 115 ++++++++++++++++++++++++++++++++++++++---------- pages/blocks.py | 14 ++++-- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/news/views.py b/news/views.py index 292044f07..70290ec89 100644 --- a/news/views.py +++ b/news/views.py @@ -5,7 +5,7 @@ import structlog from django.conf import settings -from django.db import IntegrityError +from django.forms import ValidationError from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required @@ -37,8 +37,13 @@ from django.views.generic.detail import SingleObjectMixin from django.views.decorators.http import require_POST from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadData +from wagtail.blocks import Block +from wagtail.images.models import Image from core.mixins import V3Mixin +from pages.blocks import NEWS_BLOCK, BLOG_BLOCK, LINK_BLOCK, VIDEO_BLOCK +from pages.models import PostPage, PostIndexPage +from pages.mixins import ContentTag from users.profile_cards import user_profile_card from .acl import can_approve from .constants import ( @@ -392,11 +397,13 @@ def _v3_create_context(): ], "related_libraries_options": [ ("", "Select"), - ("asio", "Asio"), - ("beast", "Beast"), - ("filesystem", "Filesystem"), - ("json", "JSON"), - ("spirit", "Spirit"), + ] + + [ + ( + library.slug, + library.name, + ) + for library in Library.objects.all().order_by("name") ], "publish_at_initial": localtime(now()).strftime("%Y-%m-%dT%H:%M"), } @@ -514,11 +521,18 @@ class V3AllTypesCreateView(V3Mixin, AllTypesCreateView): v3_template_name = "news/v3/create.html" http_method_names = ["get", "post"] + _POST_BLOCK_MAP: dict[str, tuple[str, Block]] = { + "blog": BLOG_BLOCK, + "news": NEWS_BLOCK, + "link": LINK_BLOCK, + "video": VIDEO_BLOCK, + } + _POST_TYPE_MAP = { - "blog": (BlogPost, V3BlogPostForm), - "news": (News, V3NewsForm), - "link": (Link, V3LinkForm), - "video": (Video, VideoForm), + "blog": V3BlogPostForm, + "news": V3NewsForm, + "link": V3LinkForm, + "video": VideoForm, } def dispatch(self, request, *args, **kwargs): @@ -535,9 +549,10 @@ def get_context_data(self, **kwargs): def post(self, request, *args, **kwargs): post_type = request.POST.get("post_type", "") - type_config = self._POST_TYPE_MAP.get(post_type) + block_config = self._POST_BLOCK_MAP.get(post_type, None) + form_class = self._POST_TYPE_MAP.get(post_type) - if type_config is None: + if block_config is None or form_class is None: messages.error( request, _("Invalid post type selected. Please choose a valid post type."), @@ -545,7 +560,7 @@ def post(self, request, *args, **kwargs): context = self.get_context_data() return self.render_to_response(context) - model_class, form_class = type_config + block_name, block_class = block_config # The v3 create page has two Description textareas — `description` for # Blog/News and `link_description` for Link/Video — so the two don't @@ -561,10 +576,58 @@ def post(self, request, *args, **kwargs): form = form_class(post_data, request.FILES) if form.is_valid(): - form.instance.author = request.user + cleaned_data = form.cleaned_data + # Since the PostIndexPage is limited to one, we can just grab the first + index_page = PostIndexPage.objects.first() + try: - entry = form.save() - except IntegrityError as e: + page = PostPage() + page.owner = request.user + page.title = cleaned_data.get("title") + page.summary = cleaned_data.get("summary", "") + page.publish_at = cleaned_data.get("publish_at") + page.content = [ + ( + block_name, + cleaned_data.get("content") or cleaned_data.get("external_url"), + ) + ] + page.live = False + if image := form.cleaned_data.get("image"): + wagtail_image, created = Image.objects.get_or_create( + title=image.name, + defaults={ + "file": image, + }, + ) + page.image = wagtail_image + tags = [] + if related_library := post_data.get("related_libraries"): + try: + lib = Library.objects.get(slug=related_library) + except Library.DoesNotExist: + messages.error( + request, + _( + "That related library does not exist, please select another." + ), + ) + context = self.get_context_data() + return self.render_to_response(context) + + tag, created = ContentTag.objects.get_or_create( + slug=lib.slug, + defaults={ + "name": lib.name, + }, + ) + tags.append(tag.pk) + index_page.add_child(instance=page) + if tag: + page.tags.add(tag) + page.save_revision(user=request.user) + page.get_workflow().start(obj=page, user=request.user) + except ValidationError as e: if "slug" in str(e): form.add_error( "title", @@ -574,16 +637,22 @@ def post(self, request, *args, **kwargs): form.add_error( None, "An unexpected error occurred. Please try again." ) + messages.error( + request, + _( + "Something went wrong — your draft is saved, so give it another try." + ), + ) context = self.get_context_data(form=form, post_type_selected=post_type) return self.render_to_response(context) - if not entry.is_approved: - send_email_news_needs_moderation(request=request, entry=entry) - else: - send_email_news_posted(request=request, entry=entry) - - messages.success(request, _("The news entry was successfully created.")) - return redirect(entry) + messages.success( + request, + _( + "Your post has been submitted. It'll be reviewed before it goes live, you will receive updates via email." + ), + ) + return redirect(reverse_lazy("news")) context = self.get_context_data(form=form, post_type_selected=post_type) return self.render_to_response(context) diff --git a/pages/blocks.py b/pages/blocks.py index ab4f7a797..1692ade66 100644 --- a/pages/blocks.py +++ b/pages/blocks.py @@ -10,11 +10,17 @@ class PollBlock(StreamBlock): poll_choice = CharBlock(max_length=200) +BLOG_BLOCK = ("blog", MarkdownBlock()) +NEWS_BLOCK = ("news", MarkdownBlock()) +LINK_BLOCK = ("url", URLBlock()) +VIDEO_BLOCK = ("video", EmbedBlock(label="Video")) + + POST_BLOCKS = [ ("rich_text", RichTextBlock()), - ("blog", MarkdownBlock()), - ("news", MarkdownBlock()), - ("url", URLBlock()), - ("video", EmbedBlock(label="Video")), + BLOG_BLOCK, + NEWS_BLOCK, + LINK_BLOCK, + VIDEO_BLOCK, ("poll", PollBlock()), ] From 328513fae3d462b583aa363f07d4da3da89f954a Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Wed, 15 Jul 2026 12:29:02 -0400 Subject: [PATCH 02/10] Update tag logic --- news/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/news/views.py b/news/views.py index 70290ec89..36a330162 100644 --- a/news/views.py +++ b/news/views.py @@ -623,8 +623,8 @@ def post(self, request, *args, **kwargs): ) tags.append(tag.pk) index_page.add_child(instance=page) - if tag: - page.tags.add(tag) + if tags: + page.tags.add(tags) page.save_revision(user=request.user) page.get_workflow().start(obj=page, user=request.user) except ValidationError as e: @@ -652,7 +652,7 @@ def post(self, request, *args, **kwargs): "Your post has been submitted. It'll be reviewed before it goes live, you will receive updates via email." ), ) - return redirect(reverse_lazy("news")) + return redirect(index_page.url) context = self.get_context_data(form=form, post_type_selected=post_type) return self.render_to_response(context) From a85332bf6a56a962494622713a12666b3b4584a1 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Wed, 15 Jul 2026 13:17:12 -0400 Subject: [PATCH 03/10] Code Rabbit Feedback --- news/views.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/news/views.py b/news/views.py index 36a330162..4aac3f7c6 100644 --- a/news/views.py +++ b/news/views.py @@ -579,13 +579,22 @@ def post(self, request, *args, **kwargs): cleaned_data = form.cleaned_data # Since the PostIndexPage is limited to one, we can just grab the first index_page = PostIndexPage.objects.first() + if not index_page: + messages.error( + request, + _( + "An internal database error has occurred. Please contact an admin." + ), + ) + context = self.get_context_data() + return self.render_to_response(context) try: page = PostPage() page.owner = request.user page.title = cleaned_data.get("title") page.summary = cleaned_data.get("summary", "") - page.publish_at = cleaned_data.get("publish_at") + page.go_live_at = cleaned_data.get("publish_at") page.content = [ ( block_name, @@ -594,11 +603,9 @@ def post(self, request, *args, **kwargs): ] page.live = False if image := form.cleaned_data.get("image"): - wagtail_image, created = Image.objects.get_or_create( + wagtail_image = Image.objects.create( title=image.name, - defaults={ - "file": image, - }, + file=image, ) page.image = wagtail_image tags = [] @@ -621,10 +628,10 @@ def post(self, request, *args, **kwargs): "name": lib.name, }, ) - tags.append(tag.pk) + tags.append(tag) index_page.add_child(instance=page) if tags: - page.tags.add(tags) + page.tags.add(*tags) page.save_revision(user=request.user) page.get_workflow().start(obj=page, user=request.user) except ValidationError as e: From 792940dbd17c87e4aed112d0b582f51905021b26 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 17 Jul 2026 13:27:57 -0400 Subject: [PATCH 04/10] PR feedback: Add cron job for periodic publishing of pages, and add image downsizing to form submit --- config/celery.py | 7 +++++++ news/tasks.py | 12 ++++++++++++ news/views.py | 3 +++ 3 files changed, 22 insertions(+) diff --git a/config/celery.py b/config/celery.py index f999ceb4c..3a3ce295e 100644 --- a/config/celery.py +++ b/config/celery.py @@ -137,3 +137,10 @@ def setup_periodic_tasks(sender, **kwargs): crontab(day_of_week="mon", hour=5, minute=15), app.signature("core.tasks.refresh_popular_search_terms"), ) + + # Publish approved Wagtail pages whose go_live_at has passed. + # Executes once an hour. + sender.add_periodic_task( + crontab(hour="*/1"), + app.signature("news.tasks.publish_scheduled_pages"), + ) diff --git a/news/tasks.py b/news/tasks.py index 2e0ac4965..8aa8a4ef6 100644 --- a/news/tasks.py +++ b/news/tasks.py @@ -1,8 +1,11 @@ +from celery import shared_task from textwrap import dedent from openai import OpenAI, OpenAIError import requests import structlog +from django.core.management import call_command + from config.celery import app from config.settings import OPENROUTER_API_KEY, OPENROUTER_URL, SUMMARIZATION_MODEL from news.constants import CONTENT_SUMMARIZATION_THRESHOLD @@ -108,6 +111,15 @@ def generate_summary( return summary +@shared_task +def publish_scheduled_pages(): + """ + Publish Wagtail pages whose approved go_live_at has passed + (and unpublish pages past their expire_at). + """ + call_command("publish_scheduled") + + @app.task(bind=True, max_retries=3, autoretry_for=(OpenAIError,)) def summarize_content( self, content: str, title: str, model: str, max_length: int = 256 diff --git a/news/views.py b/news/views.py index 4aac3f7c6..a166a9586 100644 --- a/news/views.py +++ b/news/views.py @@ -72,6 +72,7 @@ send_email_news_needs_moderation, send_email_news_posted, ) +from news.utils import downsize_uploaded_image from libraries.models import Library @@ -603,6 +604,8 @@ def post(self, request, *args, **kwargs): ] page.live = False if image := form.cleaned_data.get("image"): + if image.size >= settings.DOWNSCALE_IMAGE_THRESHOLD: + image = downsize_uploaded_image(image) wagtail_image = Image.objects.create( title=image.name, file=image, From 5ae08a3d45a1553fe11a3b4e1b1b73890af18adf Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 17 Jul 2026 13:32:38 -0400 Subject: [PATCH 05/10] Fix crontab timing --- config/celery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/celery.py b/config/celery.py index 3a3ce295e..2c2099e3c 100644 --- a/config/celery.py +++ b/config/celery.py @@ -141,6 +141,6 @@ def setup_periodic_tasks(sender, **kwargs): # Publish approved Wagtail pages whose go_live_at has passed. # Executes once an hour. sender.add_periodic_task( - crontab(hour="*/1"), + crontab(hour="*/1", minute=0), app.signature("news.tasks.publish_scheduled_pages"), ) From d469663300a3ee5fc52762181265e673a51f0ffd Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Fri, 17 Jul 2026 16:04:37 -0400 Subject: [PATCH 06/10] PR feedback: correct v3 video form, correct image size restrictions, use safe article functions for summary --- core/validators.py | 3 +++ news/forms.py | 18 ++++++++++++++++++ news/models.py | 3 +-- news/tasks.py | 6 +++--- news/views.py | 5 +++-- templates/news/v3/create.html | 4 ++-- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/core/validators.py b/core/validators.py index 36f7830e9..f331e640d 100644 --- a/core/validators.py +++ b/core/validators.py @@ -46,5 +46,8 @@ def __call__(self, value): # 1 MB max file size max_file_size_validator = MaxFileSizeValidator(max_size=1 * 1024 * 1024) +# 1 MB max file size +downscale_image_file_size_validator = MaxFileSizeValidator(max_size=5 * 1024 * 1024) + # 50 MB allowed for certain large files - to be used on staff-only fields large_file_max_size_validator = MaxFileSizeValidator(max_size=50 * 1024 * 1024) diff --git a/news/forms.py b/news/forms.py index a80d3f761..09ab6985f 100644 --- a/news/forms.py +++ b/news/forms.py @@ -1,9 +1,13 @@ from django import forms + +from core.validators import downscale_image_file_size_validator, max_file_size_validator + from .models import BlogPost, Entry, Link, News, Poll, Video class EntryForm(forms.ModelForm): title = forms.CharField(widget=forms.TextInput(attrs={"size": 100})) + image = forms.ImageField(validators=[max_file_size_validator]) class Meta: model = Entry @@ -48,16 +52,22 @@ class Meta: # v3-only forms: the v3 create page also captures the AI-assisted `summary`. class V3BlogPostForm(BlogPostForm): + image = forms.ImageField(validators=[downscale_image_file_size_validator]) + class Meta(BlogPostForm.Meta): fields = ["title", "publish_at", "content", "summary", "image"] class V3NewsForm(NewsForm): + image = forms.ImageField(validators=[downscale_image_file_size_validator]) + class Meta(NewsForm.Meta): fields = ["title", "publish_at", "content", "summary", "image"] class V3LinkForm(LinkForm): + image = forms.ImageField(validators=[downscale_image_file_size_validator]) + class Meta(LinkForm.Meta): fields = ["title", "publish_at", "external_url", "summary", "image"] @@ -72,3 +82,11 @@ class VideoForm(EntryForm): class Meta: model = Video fields = ["title", "publish_at", "external_url", "image"] + + +class V3VideoForm(EntryForm): + image = forms.ImageField(validators=[downscale_image_file_size_validator]) + + class Meta: + model = Video + fields = ["title", "publish_at", "external_url", "summary", "image"] diff --git a/news/models.py b/news/models.py index ab8de75be..3c815ee79 100644 --- a/news/models.py +++ b/news/models.py @@ -15,7 +15,6 @@ from core.validators import ( attachment_validator, image_validator, - max_file_size_validator, large_file_max_size_validator, ) @@ -112,7 +111,7 @@ class AlreadyApprovedError(Exception): upload_to="news/%Y/%m/", null=True, blank=True, - validators=[image_validator, max_file_size_validator], + validators=[image_validator], ) created_at = models.DateTimeField(default=now) approved_at = models.DateTimeField(null=True, blank=True) diff --git a/news/tasks.py b/news/tasks.py index 8aa8a4ef6..e756c4f93 100644 --- a/news/tasks.py +++ b/news/tasks.py @@ -9,7 +9,7 @@ from config.celery import app from config.settings import OPENROUTER_API_KEY, OPENROUTER_URL, SUMMARIZATION_MODEL from news.constants import CONTENT_SUMMARIZATION_THRESHOLD -from news.helpers import UnsafeURLError, extract_article, safe_get, extract_content +from news.helpers import UnsafeURLError, extract_article, safe_get from news.utils import set_video_thumbnail logger = structlog.get_logger(__name__) @@ -319,11 +319,11 @@ def set_summary_for_link_page(pk: int): external_url = page.external_url try: logger.info(f"Fetching content from {external_url=} for entry.{pk=}") - response = requests.get(external_url, timeout=10) + response = safe_get(external_url, timeout=10) response.raise_for_status() markup = response.text logger.debug(f"Fetched {len(markup)=} for entry.{pk=}...") - content = extract_content(markup) + _title, content = extract_article(markup) logger.info(f"extracted content from {external_url=}, {markup[:100]=}") except requests.RequestException as e: logger.error(f"Error fetching content from {external_url=}: {e=}") diff --git a/news/views.py b/news/views.py index a166a9586..041899be7 100644 --- a/news/views.py +++ b/news/views.py @@ -58,10 +58,11 @@ LinkForm, NewsForm, PollForm, + VideoForm, V3BlogPostForm, V3LinkForm, V3NewsForm, - VideoForm, + V3VideoForm, ) from .models import BlogPost, Entry, Link, News, Poll, Video from .services import news_type_label @@ -533,7 +534,7 @@ class V3AllTypesCreateView(V3Mixin, AllTypesCreateView): "blog": V3BlogPostForm, "news": V3NewsForm, "link": V3LinkForm, - "video": VideoForm, + "video": V3VideoForm, } def dispatch(self, request, *args, **kwargs): diff --git a/templates/news/v3/create.html b/templates/news/v3/create.html index 6f7d9ca2d..078260f63 100644 --- a/templates/news/v3/create.html +++ b/templates/news/v3/create.html @@ -182,7 +182,7 @@

Create Post

- {% include "v3/includes/_field_file.html" with name="image" label="Image" accept="image/png,image/jpeg" preview=True alpine_error="errors.image" error=form.errors.image.0 help_text="This should be a PNG or JPEG format and no longer than 1MB" extra_class="field--file-narrow create-post-page__field-image" %} + {% include "v3/includes/_field_file.html" with name="image" label="Image" accept="image/png,image/jpeg" preview=True alpine_error="errors.image" error=form.errors.image.0 help_text="This should be a PNG or JPEG format and no larger than 5MB" extra_class="field--file-narrow create-post-page__field-image" %} {% include "v3/includes/_field_dropdown.html" with name="related_libraries" label="Related Libraries" options=related_libraries_options placeholder="Select" selected=form.data.related_libraries %} @@ -615,7 +615,7 @@

Create Post

if (file) { const validTypes = ['image/png', 'image/jpeg', 'image/jpg']; if (!validTypes.includes(file.type)) this.errors.image = 'Please choose a PNG or JPEG image.'; - else if (file.size > 1024 * 1024) this.errors.image = 'Image must be 1MB or smaller.'; + else if (file.size > 5 * 1024 * 1024) this.errors.image = 'Image must be 5MB or smaller.'; } if (Object.values(this.errors).some(v => v)) { From 9187549404f0b42df63dbd64169bf7d44a2936b4 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Mon, 20 Jul 2026 10:52:13 -0400 Subject: [PATCH 07/10] Update tests --- news/forms.py | 2 +- news/models.py | 3 ++- news/tests/test_models.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/news/forms.py b/news/forms.py index 09ab6985f..97767c7dd 100644 --- a/news/forms.py +++ b/news/forms.py @@ -7,7 +7,7 @@ class EntryForm(forms.ModelForm): title = forms.CharField(widget=forms.TextInput(attrs={"size": 100})) - image = forms.ImageField(validators=[max_file_size_validator]) + image = forms.ImageField(validators=[max_file_size_validator], required=False) class Meta: model = Entry diff --git a/news/models.py b/news/models.py index 3c815ee79..03af5c76e 100644 --- a/news/models.py +++ b/news/models.py @@ -16,6 +16,7 @@ attachment_validator, image_validator, large_file_max_size_validator, + downscale_image_file_size_validator, ) from . import acl @@ -111,7 +112,7 @@ class AlreadyApprovedError(Exception): upload_to="news/%Y/%m/", null=True, blank=True, - validators=[image_validator], + validators=[image_validator, downscale_image_file_size_validator], ) created_at = models.DateTimeField(default=now) approved_at = models.DateTimeField(null=True, blank=True) diff --git a/news/tests/test_models.py b/news/tests/test_models.py index 28c9b53ca..15d0b2aa1 100644 --- a/news/tests/test_models.py +++ b/news/tests/test_models.py @@ -104,7 +104,7 @@ def test_entry_model_image_file_size(tp): # This should fail (just over 1MB) invalid_image = SimpleUploadedFile( - "too_large.jpg", b"a" * (1 * 1024 * 1024 + 1), content_type="image/jpeg" + "too_large.jpg", b"a" * (5 * 1024 * 1024 + 1), content_type="image/jpeg" ) entry.image = invalid_image # This should raise a ValidationError for file size From 4ad369f866e1c80615f1a6a06aa90e05427330d1 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Mon, 20 Jul 2026 12:15:20 -0400 Subject: [PATCH 08/10] Correct preview logic for unpublished pages --- pages/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pages/models.py b/pages/models.py index 54462aa85..65585a799 100644 --- a/pages/models.py +++ b/pages/models.py @@ -224,7 +224,10 @@ def get_content(self): def get_context(self, request, *args, **kwargs): ctx = super().get_context(request, *args, **kwargs) pages = self.__class__.objects.live().order_by("-first_published_at") - next_objects = pages.filter(first_published_at__gt=self.first_published_at) + if self.live: + next_objects = pages.filter(first_published_at__gt=self.first_published_at) + else: + next_objects = pages ctx["next_post_items"] = [next_objects.last()] ctx["related_posts"] = pages.filter(content__0__type=self.stream_content_type)[ :3 From f6cb4c402e4b380f38ad00c4316dc6271f2ad420 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Mon, 20 Jul 2026 12:39:21 -0400 Subject: [PATCH 09/10] Correct comments and logic on file size validator --- core/validators.py | 4 ++-- news/tests/test_models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/validators.py b/core/validators.py index f331e640d..62894a37a 100644 --- a/core/validators.py +++ b/core/validators.py @@ -37,7 +37,7 @@ def __init__(self, max_size): self.max_size = max_size def __call__(self, value): - if value.size > self.max_size: + if value.size >= self.max_size: raise ValidationError( f"File too large. Size should not exceed {self.max_size / 1024 / 1024} MB." # noqa ) @@ -46,7 +46,7 @@ def __call__(self, value): # 1 MB max file size max_file_size_validator = MaxFileSizeValidator(max_size=1 * 1024 * 1024) -# 1 MB max file size +# 5 MB max file size downscale_image_file_size_validator = MaxFileSizeValidator(max_size=5 * 1024 * 1024) # 50 MB allowed for certain large files - to be used on staff-only fields diff --git a/news/tests/test_models.py b/news/tests/test_models.py index 15d0b2aa1..2cb0b3e96 100644 --- a/news/tests/test_models.py +++ b/news/tests/test_models.py @@ -102,7 +102,7 @@ def test_entry_model_image_file_size(tp): # This should not raise any errors entry.full_clean() - # This should fail (just over 1MB) + # This should fail (just over 5MB) invalid_image = SimpleUploadedFile( "too_large.jpg", b"a" * (5 * 1024 * 1024 + 1), content_type="image/jpeg" ) From 20cd0c901a97de61bccd7fa4ea10c87c136727e3 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Mon, 20 Jul 2026 12:42:53 -0400 Subject: [PATCH 10/10] Update test to match actual boundaries --- news/tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/tests/test_models.py b/news/tests/test_models.py index 2cb0b3e96..ef3be61a5 100644 --- a/news/tests/test_models.py +++ b/news/tests/test_models.py @@ -96,7 +96,7 @@ def test_entry_model_image_file_size(tp): entry = Entry.objects.create(title="😀 Foo Bar Baz!@! +", author=author) valid_image = SimpleUploadedFile( - "test.jpg", b"a" * (1 * 1024 * 1024 - 1), content_type="image/jpeg" + "test.jpg", b"a" * (5 * 1024 * 1024 - 1), content_type="image/jpeg" ) entry.image = valid_image # This should not raise any errors