Skip to content

Story 2376 and 2499: Post Detail, Edit, and Delete. - #2562

Open
jlchilders11 wants to merge 20 commits into
developfrom
jc/2376-post-detail-page
Open

Story 2376 and 2499: Post Detail, Edit, and Delete.#2562
jlchilders11 wants to merge 20 commits into
developfrom
jc/2376-post-detail-page

Conversation

@jlchilders11

@jlchilders11 jlchilders11 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Issue: #2376 and #2499

Summary & Context

Adds several new functions to the Post Detail page/template (filter related posts by tags, show delete and edit buttons if viewed by owner). Implements post editing and post deleting. Builds off of existing work in #2375 Post Creation for the edit page.

Changes

  • Alters how tagging works, to scope directly to PostPages. Implementing as a universal mixin both added tags where they weren't needed, but also caused issues on trying to filter on them as the Page model has a fundamentally different shape than child pages.
  • Alters detail.html to use specified logic for showing edit and delete buttons. The delete button now is shown if the viewer owns the page, the edit button is shown if they own the page and the page is in the edit window. The "page is in moderation" is also shown if the workflow is currently active.
  • Alters related_pages shown on the detail page to preferentially show pages that share a tag, falling back to pages that share a type if not tag is set.
  • Adds V3AllTypeEditView, which handles the logic of editing the page. Controls loading the data, editing the page, and preventing editing by non owners and outside the edit window.
  • Adds V3PostDeleteView, which handles the logic of "deleting" pages at the owners request. Performs a soft delete by unpublishing the page, to replicate existing logic with Entries.

‼️ Risks & Considerations ‼️

Please list any potential risks or areas that need extra attention during review/testing

Self-review Checklist

  • Link this PR to the related GitHub Project ticket

Frontend

  • Tested in light and dark mode
  • Responsive / mobile verified
  • Accessibility checked (keyboard navigation, etc.)
  • Ensure design tokens are used for colors, spacing, typography, etc. – No hardcoded values
  • No console errors or warnings

Summary by CodeRabbit

New Features

  • Added V3 options to create, edit, delete, and manage news posts.
  • Added tagging support and tag-based related post recommendations.
  • Added improved image previews and reusable confirmation dialogs.

Bug Fixes

  • Restricted editing and deletion to authorized post owners within the permitted time window.
  • Improved form validation, error handling, and draft restoration.
  • Updated action links and approval controls for the V3 workflow.
  • Improved visibility of controls for previews and in-progress posts.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: adcb6c15-9cc0-4846-b6e1-a4b55f9b73ae

📥 Commits

Reviewing files that changed from the base of the PR and between bb2ac7a and a4f1959.

📒 Files selected for processing (2)
  • news/views.py
  • templates/news/v3/detail.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • templates/news/v3/detail.html
  • news/views.py

📝 Walkthrough

Walkthrough

V3 news posts now support author-only editing and deletion. Create handling is centralized. PostPage tagging, related-post context, and six-hour edit-window permissions are updated. Routes and templates use the new V3 workflow endpoints.

Changes

V3 news workflows

Layer / File(s) Summary
PostPage tagging and permissions
pages/models.py, pages/mixins.py, pages/migrations/*
PostPage gains tag-backed related posts, preview URLs, edit/delete permission context, and a six-hour edit window. Tagging relations are narrowed to PostPage through migrations.
V3 create, edit, and delete backend
news/views.py
Create context and page assignment are centralized. Edit and delete handlers load PostPage records by slug, check permissions, process updates, and unpublish posts.
V3 routes and workflow templates
config/v3_urls.py, news/models.py, templates/news/v3/*, templates/v3/includes/*
New edit and delete routes and URL helpers are wired into the V3 templates. Edit-aware draft handling, confirmation submission, configurable dialog buttons, and persistent file previews are added.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to a4f19

The PR adds post detail, editing, and soft-delete behavior without any supplied evidence of a current merge-blocking issue; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant V3AllTypesEditView
  participant PostPage
  Browser->>V3AllTypesEditView: Submit V3 edit form
  V3AllTypesEditView->>PostPage: Load page by slug
  V3AllTypesEditView->>PostPage: Verify owner and update attributes
  V3AllTypesEditView-->>Browser: Render errors or redirect
Loading

Possibly related PRs

Suggested reviewers: herzog0, ycanales, julioest

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: post detail, editing, and deletion functionality.
Description check ✅ Passed The description covers the issue, context, implementation changes, risks, and checklist, but omits screenshots and leaves risks incomplete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jc/2376-post-detail-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@herzog0
herzog0 requested review from herzog0 and removed request for herzog0 July 30, 2026 13:24
@julioest
julioest self-requested a review July 30, 2026 17:39
Base automatically changed from jc/2375-create-a-post to develop July 30, 2026 18:08
@jlchilders11
jlchilders11 force-pushed the jc/2376-post-detail-page branch from 2b44d55 to 7eab145 Compare July 30, 2026 18:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🧹 Nitpick comments (2)
news/views.py (2)

557-564: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

post_type parameter is unused. Both call sites pass it; drop it (or use it) to keep the helper's contract honest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/views.py` around lines 557 - 564, Remove the unused post_type parameter
from set_page_attrs and update both call sites to stop passing it, keeping the
helper contract and invocations consistent.

Source: Linters/SAST tools


688-689: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_page default is a QuerySet, not a page.

PostPage.objects.none() as the sentinel makes self._page type-unstable and is what lets line 777 call user_can_edit on a QuerySet (see the get() comment). Use _page: PostPage | None = None.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/views.py` around lines 688 - 689, Update V3AllTypesEditView’s _page
class attribute from an empty QuerySet sentinel to an optional PostPage
initialized as None, and preserve the corresponding None handling so _page is
always either a PostPage or None before user_can_edit is called.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@news/views.py`:
- Around line 762-765: Replace the f-string passed to gettext in get_page,
V3AllTypesEditView.post, and V3DeletePostView.get with a stable
named-placeholder msgid, then interpolate the corresponding slug into the
translated result afterward using the existing gettext helper.
- Around line 876-910: Convert V3DeletePostView.get to a POST-only handler, add
LoginRequiredMixin, and replace the direct owner comparison with user_can_delete
authorization. In config/v3_urls.py lines 76-80, retain the route while ensuring
it resolves only the POST-only view. In templates/news/v3/detail.html lines
39-41, replace the confirmation dialog’s primary_url link with a POST form
containing a CSRF token.
- Around line 864-870: Update the edit-post flow around save_revision so edited
revisions are submitted through the same moderation workflow as newly created
posts, ensuring workflow_in_progress is set and the Pending Moderation state
appears in detail.html. Keep the success message accurate for the resulting
moderation behavior and preserve the existing redirect.
- Around line 782-873: Extract the duplicated submission flow from post() into a
shared _handle_submit(...) helper on the create view, including post-type
validation, description remapping, form processing, page updates,
ValidationError handling, and success response. Update both post()
implementations to call this helper while preserving each view’s distinct
workflow-start behavior.
- Around line 552-555: Update error_message_and_render to safely handle an
omitted extra_context before passing it to get_context_data, using an empty
mapping when it is None. Preserve the existing context-merging behavior when
callers provide extra_context so invalid post-type paths re-render instead of
raising TypeError.
- Around line 584-598: Update set_page_attrs to replace the page’s existing tags
rather than append to them: after collecting the selected ContentTag objects,
call page.tags.set(tags) unconditionally so deselected libraries are removed
while an empty selection clears all tags.
- Around line 819-839: Update the POST handling around page lookup in the view’s
post() method to enforce the same ownership and user_can_edit checks as get(),
including the six-hour edit-window restriction, before calling set_page_attrs or
save_revision. Reject unauthorized or expired edits using the existing
response/error path, while preserving the current update flow for permitted
owners.
- Around line 722-737: Update the form construction in the page edit context to
pass prefilled values through the form’s initial data rather than binding
form_data as data, so the initial GET renders without validation errors. Before
assigning the body initial value, convert page.content from its StreamValue
representation to the value expected by the form field; preserve the existing
conditional external_url/content population and related library values.
- Around line 773-780: Update get_page and the get method to use its returned
page: have get_page return None when no PostIndexPage or slug match exists,
otherwise return the resolved page, and in get handle the None case with the
appropriate missing-page response before calling user_can_edit. Ensure
permission checks run only on an actual page instance, never the fallback
QuerySet.

In
`@pages/migrations/0005_remove_postindexpage_tags_remove_postpage_tags_and_more.py`:
- Around line 12-25: Add a data migration before the tag field removals in
migration 0005 to preserve valid PostPage TaggedContent associations and delete
TaggedContent rows referencing non-PostPage content before migration 0006
narrows content_object. Replace the generated RemoveField operations with
explicit schema operations as needed, ensuring the migration safely handles both
existing tagged posts and invalid content types without losing valid
associations.

In `@pages/models.py`:
- Around line 237-242: Update the tag-based related_posts query in the
surrounding method to exclude the current post, eliminate duplicate rows from
multi-tag matches, and limit the result to three posts, matching the fallback
branch’s cap. Preserve the existing fallback behavior when no tags exist.
- Around line 345-356: Update _in_edit_window to retrieve the earliest revision
explicitly with self.revisions.order_by("created_at").first(), then compute the
six-hour edit window using a straightforward elapsed-time comparison while
preserving the existing no-revision false result.

In `@templates/news/v3/create.html`:
- Line 187: Update the view that renders this template to derive a list of
selected related libraries using the submitted POST data’s
getlist("related_libraries") result, then expose it through a dedicated context
variable. Change the related_libraries multiselect include to use that list
instead of form.data.related_libraries, preserving all selections on failed
submissions.
- Line 63: Update the post_type field markup in the create template so edit
forms keep the select disabled while also submitting the selected value through
a hidden input; ensure the hidden input uses the same post_type name and
post_type_selected value expected by V3AllTypesEditView.post() and does not
affect the enabled create flow.

In `@templates/v3/includes/_field_file.html`:
- Around line 24-25: Escape preview_url for safe embedding in JavaScript string
literals in both the x-data initialization and reset handler within the Alpine
component. Ensure quotes, backslashes, and other JavaScript-sensitive characters
are encoded without changing the URL value used by previewUrl or
URL.revokeObjectURL.

---

Nitpick comments:
In `@news/views.py`:
- Around line 557-564: Remove the unused post_type parameter from set_page_attrs
and update both call sites to stop passing it, keeping the helper contract and
invocations consistent.
- Around line 688-689: Update V3AllTypesEditView’s _page class attribute from an
empty QuerySet sentinel to an optional PostPage initialized as None, and
preserve the corresponding None handling so _page is always either a PostPage or
None before user_can_edit is called.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 50362dc3-3f07-4743-a6a0-edd73cadded1

📥 Commits

Reviewing files that changed from the base of the PR and between 3cabc15 and 7eab145.

📒 Files selected for processing (10)
  • config/v3_urls.py
  • news/views.py
  • pages/migrations/0005_remove_postindexpage_tags_remove_postpage_tags_and_more.py
  • pages/migrations/0006_postpage_tags_alter_taggedcontent_content_object.py
  • pages/mixins.py
  • pages/models.py
  • templates/news/v3/create.html
  • templates/news/v3/detail.html
  • templates/v3/includes/_dialog.html
  • templates/v3/includes/_field_file.html

Comment thread news/views.py
Comment thread news/views.py
Comment thread news/views.py
Comment thread news/views.py Outdated
Comment thread news/views.py
Comment thread pages/models.py
Comment thread pages/models.py
Comment thread templates/news/v3/create.html
Comment thread templates/news/v3/create.html Outdated
Comment thread templates/v3/includes/_field_file.html Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
news/views.py (2)

585-598: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Deselecting all related libraries never clears existing tags.

page.tags.set(tags) only runs if tags:. When a user removes every related library on edit, tags is an empty list, the condition is False, and page.tags.set([]) is never called — stale ContentTag values stay attached and continue to bias the related-posts query (tags__in=self.tags.all() in PostPage.get_context).

🐛 Proposed fix
-        if tags:
-            page.tags.set(tags)
+        page.tags.set(tags)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/views.py` around lines 585 - 598, Update the tag synchronization block
in the related-library handling to call page.tags.set(tags) unconditionally,
including when tags is an empty list. Preserve the existing tag creation and
collection logic so removing all related libraries clears stale ContentTag
associations.

689-747: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

self._page can remain an empty QuerySet, and get_v3_context_data crashes when it does.

_page = PostPage.objects.none() (Line 690) is a class-level QuerySet, not a PostPage instance or None. get_v3_context_data (Lines 740-747) unconditionally calls page.tags.all() on self._page before _v3_edit_context's own if not page: return ctx guard (Line 705) ever runs — a QuerySet has no .tags attribute, so this raises AttributeError.

Two paths reach this broken state:

  • GET: get_page (Lines 749-773) returns without setting self._page when the index page or slug lookup fails, and get() (Lines 775-782) still calls super().get() afterward instead of returning early, so the page renders anyway and crashes in get_v3_context_data.
  • POST: post() (Lines 784-883) never calls get_page() and never assigns self._page at all — it does its own separate lookup into a local page variable. Every re-render path inside post() (invalid post type at Lines 805-810, invalid form at Line 882-883, permission failure at Lines 827-833, Library.DoesNotExist at Lines 846-850, ValidationError at Lines 851-872) calls self.get_context_data(...), which hits the same crash.

Visiting /v3/news/edit/<bad-slug>/ (GET) or submitting an invalid edit form (POST) currently returns a 500 instead of a graceful error.

🐛 Proposed fix
 class V3AllTypesEditView(V3AllTypesCreateView):
-    _page = PostPage.objects.none()
+    _page: PostPage | None = None
...
     def get_v3_context_data(self, **kwargs):
         page = self._page
         context = super().get_context_data(**kwargs)
-        context["related_libraries"] = list(
-            page.tags.all().values_list("slug", flat=True)
-        )
+        if page:
+            context["related_libraries"] = list(
+                page.tags.all().values_list("slug", flat=True)
+            )
         context.update(self._v3_edit_context(page))
         return context
...
     def get(self, request, *args, **kwargs):
         slug = kwargs.get("slug", "")
         self.get_page(slug)
 
+        if not self._page:
+            return redirect(reverse("news"))
+
         if self._page and not self._page.user_can_edit(request.user):
             raise PermissionDenied("You do not have permission to edit this page.")
 
         return super().get(request, *args, **kwargs)

post() also needs to assign self._page = page right after the lookup succeeds so its own error re-renders don't hit the same crash.

Also applies to: 749-882

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/views.py` around lines 689 - 747, Fix V3AllTypesEditView’s missing-page
handling so get_v3_context_data never calls tags on the class-level empty
QuerySet: make GET return early when get_page cannot resolve the index or slug,
and assign the successfully loaded page to self._page in post() before any error
re-render. Preserve _v3_edit_context’s existing empty-page behavior and ensure
invalid GET/POST paths render gracefully instead of raising AttributeError.
♻️ Duplicate comments (1)
news/views.py (1)

552-556: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

context is unassigned when extra_context is omitted, causing UnboundLocalError.

The if extra_context: guard prevents the earlier **None crash, but it introduces a new one: when extra_context is None (the default) or an empty dict, context is never assigned, and return self.render_to_response(context) raises UnboundLocalError: local variable 'context' referenced before assignment.

This is reachable from two call sites in this file:

  • Lines 606-611 (V3AllTypesCreateView.post(), invalid post type).
  • Lines 805-810 (V3AllTypesEditView.post(), invalid post type).

Any POST with a missing/unknown post_type still 500s, just via a different exception than before.

🐛 Proposed fix
     def error_message_and_render(self, message: str, extra_context: dict | None = None):
         messages.error(self.request, message)
-        if extra_context:
-            context = self.get_context_data(**extra_context)
+        context = self.get_context_data(**(extra_context or {}))
         return self.render_to_response(context)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/views.py` around lines 552 - 556, Initialize context with the view’s
default context before the extra_context guard in error_message_and_render. When
extra_context is provided, continue merging it through get_context_data, while
None or an empty dict must still render using the initialized context for the
V3AllTypesCreateView.post and V3AllTypesEditView.post call paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@news/views.py`:
- Around line 585-598: Update the tag synchronization block in the
related-library handling to call page.tags.set(tags) unconditionally, including
when tags is an empty list. Preserve the existing tag creation and collection
logic so removing all related libraries clears stale ContentTag associations.
- Around line 689-747: Fix V3AllTypesEditView’s missing-page handling so
get_v3_context_data never calls tags on the class-level empty QuerySet: make GET
return early when get_page cannot resolve the index or slug, and assign the
successfully loaded page to self._page in post() before any error re-render.
Preserve _v3_edit_context’s existing empty-page behavior and ensure invalid
GET/POST paths render gracefully instead of raising AttributeError.

---

Duplicate comments:
In `@news/views.py`:
- Around line 552-556: Initialize context with the view’s default context before
the extra_context guard in error_message_and_render. When extra_context is
provided, continue merging it through get_context_data, while None or an empty
dict must still render using the initialized context for the
V3AllTypesCreateView.post and V3AllTypesEditView.post call paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14b64180-12b2-42b7-b661-832537eb82f8

📥 Commits

Reviewing files that changed from the base of the PR and between 7eab145 and 81b5119.

📒 Files selected for processing (5)
  • news/views.py
  • pages/models.py
  • templates/news/v3/create.html
  • templates/news/v3/detail.html
  • templates/v3/includes/_dialog.html
🚧 Files skipped from review as they are similar to previous changes (4)
  • templates/v3/includes/_dialog.html
  • templates/news/v3/detail.html
  • templates/news/v3/create.html
  • pages/models.py

@herzog0
herzog0 requested review from herzog0 and removed request for herzog0 August 5, 2026 17:02

@julioest julioest left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My man Jeremy. So, I pulled this locally with seeded data and went through it as both an author and a moderator. I ran the code through my testing process a couple times and didn't include the coderabbit comments, here's what I got:

Two things block, both about moderation, both reached by clicking.

A moderator cannot approve anything, on either page. On a PostPage awaiting review there is no admin row at all: get_context sets user_can_edit and user_can_delete but never user_can_approve, and the template gates the row on all three. On a legacy entry the row does render, with Edit and Delete, but no Approve and no Pending badge, because detail.html:23 gates that block on object.workflow_in_progress, which Entry does not have, so Django resolves it to an empty string.

Moderator on a post awaiting review: no admin row at all The author on the same post at the same URL: row renders with the Pending Moderation badge

Same post, same URL. The template works, the moderator context is what is missing.

So a post enters moderation and nothing in the UI moves it forward. Three tests, two fail with a passing control between them so it is not the fixture:

FAIL  moderator_can_approve_a_pending_postpage
PASS  author_sees_pending_badge_but_not_approve
FAIL  moderator_can_approve_a_legacy_entry

Clicking Edit on a legacy entry 500s. That button points at /v3/news/edit/<slug>/, a PostPage-only view, so the lookup misses:

AttributeError: PageQuerySet object has no attribute tags, at news/views.py line 744

That crash sits behind every error path in the edit view. The new related_libraries line in get_v3_context_data reads the _page sentinel before reaching the if not page guard inside _v3_edit_context, and post() never sets _page. _page: PostPage | None = None will not fix it alone, None.tags raises too. The other paths need a typed URL or devtools, since client-side validation catches a blank title first, but the view still has no working error path.

Smaller ones in the line comments: a context key nothing reads, a super() call that skips the create context, the delete view with no LoginRequiredMixin and no v3 flag gate, and a dialog button rendering type="True".

Also the edit URL in the description 404s, it should be /v3/news/edit/<slug>/.

Comment thread templates/news/v3/detail.html Outdated
Comment thread pages/models.py Outdated
Comment thread news/views.py Outdated

def get_v3_context_data(self, **kwargs):
page = self._page
context = super().get_context_data(**kwargs)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be super().get_v3_context_data()? As written, V3Mixin has already flipped _get_v3_initial, so it falls through to AllTypesCreateView and _v3_create_context never runs on the edit view. I instrumented it: zero calls.

That is why _v3_edit_context below re-declares related_libraries_options and publish_at_initial.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Originally this was not calling super to prevent bleeding the create context to the edit, but looking at it closer with the post type disabled there is no need for this separation.

Comment thread news/views.py Outdated
Comment thread news/views.py Outdated
Comment thread templates/v3/includes/_dialog.html Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@news/models.py`:
- Around line 302-306: Update Entry.edit_url and Entry.delete_url to resolve the
V3 action routes when the v3 workflow is active, while preserving legacy route
behavior for non-V3 templates; alternatively, add separate V3 URL helpers and
update the V3 detail template to use them.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5cf490d9-13be-4130-a67f-447e7e2412b1

📥 Commits

Reviewing files that changed from the base of the PR and between 9cb542d and bb2ac7a.

📒 Files selected for processing (4)
  • news/models.py
  • news/views.py
  • pages/models.py
  • templates/news/v3/detail.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • templates/news/v3/detail.html
  • pages/models.py

Comment thread news/models.py
Comment on lines +302 to +306
def edit_url(self):
return reverse("news-update", kwargs={"slug": self.slug})

def delete_url(self):
return reverse("news-delete", kwargs={"slug": self.slug})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 '\.(edit_url|delete_url)\b|edit_url\(\)|delete_url\(\)' \
  news pages templates config

rg -n -C 5 'news-update|news-delete|v3-news-edit|v3-news-delete' \
  news pages templates config

Repository: boostorg/website-v2

Length of output: 16303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- news/models.py Entry and related classes ---'
sed -n '240,325p' news/models.py

printf '%s\n' '--- pages/models.py PostPage and URL helpers ---'
sed -n '220,290p' pages/models.py

printf '%s\n' '--- V3 detail view/context references ---'
rg -n -C 5 'templates/news/v3/detail\.html|detail\.html|PostPage|user_can_edit|user_can_delete' news pages config

Repository: boostorg/website-v2

Length of output: 37728


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- V3 mixin and Entry detail flow ---'
rg -n -C 8 'class V3Mixin|_v3_active|v3_template_name|EntryDetailView' core news config
sed -n '250,355p' news/views.py

printf '%s\n' '--- V3 and legacy URL inclusion ---'
rg -n -C 6 'include\(|v3_urls|news.urls|EntryDetailView|V3AllTypesEditView|V3DeletePostView' config news

Repository: boostorg/website-v2

Length of output: 35204


Route Entry actions through the V3 workflow. When the v3 flag is active, EntryDetailView renders news/v3/detail.html with an Entry object. The template calls object.edit_url and object.delete_url, which resolve legacy routes. Use V3 route names for these actions, or provide separate helpers for legacy and V3 templates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@news/models.py` around lines 302 - 306, Update Entry.edit_url and
Entry.delete_url to resolve the V3 action routes when the v3 workflow is active,
while preserving legacy route behavior for non-V3 templates; alternatively, add
separate V3 URL helpers and update the V3 detail template to use them.

@jlchilders11

Copy link
Copy Markdown
Collaborator Author

@julioest After a discussion with Rob and Henry, we decided that the moderator approval button is not actually appropriate on this page; Moderators who are viewing a non published page will have reached it through the wagtail admin, not through by navigating the page. If they view a preview of the page an approval button there just confuses the issue since they have the admin open.

I've also gone ahead and separated out the workflow for wagtail pages vs. legacy Entries, so now you should end up in the separate flows based on what type of object you are editing.

@jlchilders11
jlchilders11 requested a review from julioest August 10, 2026 20:31
@herzog0
herzog0 self-requested a review August 12, 2026 13:30

@herzog0 herzog0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heya, I haven't looked entirely at the code yet, cause there are a few things that need more attention at this moment.

  1. The "legacy" v3 post page still shows the buttons. I know this page will be deleted later, so nothing to worry about now I guess, just wanted to flag and make it's not passing unnoticed.

  2. My post shows "pending moderation" even though it has been approved and published:

Image
  1. When clicking "Edit" I'm getting an error, both on the server and the webpage. The page loads without any data and without the link field. This is what I see in the server logs (beisdes the "NoneType" object has no attr... it's also worth flagging if the first line pointing to /cms/pages/130/edit/ is correct):
Image
  1. Deleting a page actually only puts it in Draft, so an admin could potentially go and publish it again. Is that intentional?
Image

Let me know if you wanna jump into a call if you're not experiencing those things on your end!

@julioest julioest left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My dawg @jlchilders11, re-tested. The fixes hold up!
Ran into a couple of things I'd like you to try reproducing, because they only turn up when you're logged in as an ordinary author. As a superuser everything looks clean, which is what caught me out the first time round.

Set yourself up as a plain user with no staff or superuser flags, owning a post, inside the 6 hour edit window. Then:

Hit Delete on your own post. I get a 403 and the post is still live afterwards. user_can_delete returns True just before it, so the view's own check is happy about it.

Open that post's edit form, clear the title, save. I get an error page instead of the form back, and whatever I'd typed is gone. Same if the edit window lapses while the form is open, or if a related library can't be found. Saving a valid edit is fine.

Couple of smaller ones while you're in there. On a legacy Entry that's already approved I'm still offered Approve as a moderator, and on a published PostPage with nothing in workflow the author gets a Pending Moderation badge. Both look like they come back to detail.html:24. And deselecting every related library on a post leaves the old ones attached.

@jlchilders11

Copy link
Copy Markdown
Collaborator Author

@herzog0

  1. That is fair, and the intended behavior to my knowledge.
  2. Is the page currently in a workflow? The page should show pending moderation post any edits, even if already approved, since it is actually pending moderation.
  3. Good catch, this was a bug with the link type form, sorted now.
  4. That is intentional. Per our discussion we should consider adding a unique flag for deleted pages, but for now I think this is a "wagtail" way to do soft deletes. Moderators should not be scrubbing through the list to find pages for moderation, they should be looking at the queue.

@jlchilders11
jlchilders11 requested a review from herzog0 August 14, 2026 20:28

@herzog0 herzog0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jlchilders11 , thanks for addressing all of it!
There are just a couple of things I think still need fixing:

  1. That "database" error still happens, but only for Blogpost types (it happens after I click "Edit" and get redirected). All other post types are good. I also can't know exactly what's the error cause, as it's being ssr'd and the server logs are not showing anything.
Image
  1. The "Pending Moderation" label is still there, and to answer your question from before: I don't think they're part of any workflows anymore, cause I have gone to the CMS panel, then approved and published them, so I wouldn't say there anything "pending" anymore.
Image
  1. Not sure if this was introduced in your branch but, if I disable JS, the posts' descriptions won't show up, and their content shows up with HTML tags.
Image

@jlchilders11
jlchilders11 requested a review from herzog0 August 19, 2026 16:35
@jlchilders11

Copy link
Copy Markdown
Collaborator Author

@herzog0 thanks for the catches, those should all be handled now!

@herzog0 herzog0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jlchilders11 thanks for addressing all the issues!
I'm pre-approving, but would appreciate if you address the following before moving the ticket.
We spoke before about deleted users's posts getting back to the list and being potentially approved again, and we dismissed that with the assumption that moderators shouldn't be working directly from that list, and I agree with that.

Only problem is that I found a worrying corner case: the page goes completely back to the approval step if the user first edits the post and submits it to moderation and deletes the post right afterwards. The post goes back to the workflow at the CMS homepage and still asks a moderator for approval.

Image

My code suggestion below takes care of this, as it cancels any pending workflows when the user is deleting the post. Not sure if it's the best approach, but I tested locally and it worked!

Comment thread news/views.py
jlchilders11 and others added 2 commits August 19, 2026 15:22
Co-authored-by: Teodoro B. Mendes <teobmendes@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webpage Integration: Edit Post Webpage Integration: Post Detail Page

3 participants