feat: complete bulk actions across all publish tabs + composer stay-on-save#117
Open
cominop wants to merge 42 commits into
Open
feat: complete bulk actions across all publish tabs + composer stay-on-save#117cominop wants to merge 42 commits into
cominop wants to merge 42 commits into
Conversation
added 30 commits
June 27, 2026 12:43
Adds per-row selection checkboxes and WordPress-style bulk action header to all 4 publish tabs (Drafts, Queue, Approvals, Sent). - Drafts: Delete, Send to Queue - Queue: Delete - Approvals: Delete, Approve, Reject, Publish - Sent: Delete - Delete/reject modals with confirmation dialogs - HX-Redirect back to correct tab after action completes - CSRF meta tag for HTMX AJAX requests in base.html No migrations required. Backwards-compatible — existing single-row approve/reject/delete actions are unchanged.
… Queue - publish_queue.html: wire hx-post + hx-trigger on hidden bulk form - publish_approvals.html: hx-trigger submit, add approve branch, early return for delete/reject - publish_drafts.html: add 3 new bulk actions to dropdown - apps/calendar/views.py: add submit_for_approval, add_to_queue, next_available handlers Next Available queries PostingSlot table for first open slot per workspace.
Railway v5 doesn't execute 'railway run' commands remotely, so migrations were never applied to the deployed DB. Added entrypoint.sh that runs 'migrate' + 'collectstatic' before starting gunicorn.
base.html referenced js/unsplash-modal.js but the Unsplash app is not installed in this repo, causing 500 on every page (Missing staticfiles manifest entry). Commented out the references.
- CreatePostRequest: new link_url + alt_text fields
- composer/services.create_post: accepts link_url + alt_text, writes to
PlatformPost.platform_extra as {link_url, alt_text}; alt_text falls
back to MediaAsset.alt_text when caller leaves it blank
- api/routers/posts.py: pass payload.link_url + payload.alt_text through
Pinterest provider already reads content.link_url and content.extra['alt_text']
from PublishContent, which is now populated.
Restored apps/unsplash/ from origin/feature/unsplash-integration:
- views.py, urls.py, models, serializers, services
- templates/integrations/unsplash_modal.html
- static/js/unsplash-modal.js (also copied to repo static/)
Wired up:
- Added 'apps.unsplash' to INSTALLED_APPS
- Added context processor (unsplash_modal) for the modal HTML
- Mounted /api/v1/ in config/urls.py to include apps.unsplash.urls
- Restored {{ unsplash_modal_html }} and unsplash-modal.js references in base.html
The frontend modal was already in templates/composer/compose.html — it
was calling composer:unsplash_search which returned 404 because views.py
source was missing (only stale __pycache__/views.cpython-314.pyc existed).
Modal now renders + search endpoint works.
views.py imports rest_framework.permissions/status/generics and the DRF TokenAuthentication class requires rest_framework.authtoken. None were installed. - requirements.txt: add djangorestframework>=3.15,<4.0 - INSTALLED_APPS: add 'rest_framework' + 'rest_framework.authtoken' - Add REST_FRAMEWORK dict with TokenAuthentication + SessionAuthentication
…issing)" This reverts commit 29a7b9c.
This reverts commit 5c8db22.
Restored changes that were lost during incorrect revert cascade: - apps/unsplash/: full source from origin/feature/unsplash-integration - config/settings/base.py: DRF + apps.unsplash entry + context processor - config/settings/base.py: REST_FRAMEWORK config (TokenAuth+SessionAuth) - config/urls.py: /api/v1/ include for apps.unsplash.urls - config/urls.py: unguard static() so /media/ works in production - templates/base.html: uncomment unsplash-modal.js + modal HTML - requirements.txt: djangorestframework - entrypoint.sh: restore original (no MEDIA_ROOT export, no cp -r media)
Uses cp -rn (recursive, no-clobber) to fill in media files that are missing from the Railway volume without overwriting existing uploads. This is a safe addition to the original entrypoint.sh from 9fccf81.
static() gtk_helper was not producing a working URL pattern in production. Switched to explicit re_path with django.views.static.serve.
…s/composer/ The upstream main branch has Unsplash integrated directly into apps/composer/ (views.py:unsplash_search, views.py:unsplash_import). The separate apps/unsplash/ plugin from feature/unsplash-integration was never part of the deployed build. Removed: - apps/unsplash/ directory and all files - rest_framework + rest_framework.authtoken from INSTALLED_APPS - apps.unsplash from LOCAL_APPS - apps.unsplash.context_processors.unsplash_modal from TEMPLATES - REST_FRAMEWORK config block - /api/v1/ include for apps.unsplash.urls from config/urls.py - djangorestframework from requirements.txt Kept: - media serving fix (re_path + serve) - tzdata for Canada/Eastern timezone - entrypoint.sh with cp -rn media sync - base.html unsplash references (benign) - compose.html inline Alpine modal (untouched — the real Unsplash integration)
…ng is incomplete processing_status view returns JsonResponse when media isn't ready yet. HTMX hx-swap='outerHTML' was swapping this JSON into the DOM as raw text. HX-Reswap: none tells HTMX to leave the existing HTML in place.
The @click handler only handled 'delete' and 'send_to_queue'. All other action values (submit_for_approval, add_to_queue, next_available) were silently ignored — the button did nothing. Added else clause that submits any remaining action value through the hidden HTMX form, matching the same pattern delete already uses.
The bulk action buttons were clearing the form with innerHTML='', which destroyed the CSRF token and current_tab hidden input. Subsequent form submissions failed silently because Django rejected them as CSRF violations. Now re-adds both elements after clearing form for: - Apply button (approve/publish actions) - Delete modal button - Reject modal button
…uote breakage
insertAdjacentHTML with {% csrf_token %} injects HTML that contains double
quotes, which prematurely terminates the @click="..." attribute. Replaced
with querySelector + cloneNode(true) to preserve the CSRF input element
without generating template-level quote conflicts.
… x-data methods Drafts tab (publish_drafts.html): - Apply button → applyBulkAction() + _submitBulkForm() - Delete modal → confirmDelete() - Reject modal → confirmReject() Queue tab (publish_queue.html): - Apply button → applyBulkAction() - Bulk delete modal → confirmDelete() Approvals tab (publish_approvals.html): - Delete modal → confirmApprovalDelete() - Reject modal → confirmApprovalReject() - Shared _submitBulkForm() helper Eliminated all multi-line inline JavaScript from publish tab templates. Every bulk action now calls a named method in the Alpine x-data object.
The approvals bulk_action view returns HTTP 204 (no content), so the tab content was never refreshed after approve/reject/delete. Added an htmx.ajax GET to reload the current filter view into #tab-content after the bulk action completes, matching the active status_filter.
After schedule/publish/queue, the user was dumped to the calendar grid view which forced them to hunt for the post in the grid, switch back to list view, find it, and re-enter the composer. Now redirects to the publish list view (?view=list) so the user lands on the tabbed publish page where they can immediately see their scheduled/queued posts.
Removed the redirect to calendar/list that fired after schedule, publish_now, and add_to_queue actions. Now every save (draft, schedule, publish, queue) displays the "Saved" toast and keeps the user in the composer so they can continue editing without friction.
added 11 commits
June 30, 2026 23:08
The bulk delete modal submitted action=delete but the backend only handled approve and reject. Added a delete handler that iterates over post_ids and calls post.delete() for each, returning proper results.
The dropdown listed Publish but the backend only handled approve, reject, and delete. Added a publish handler that transitions each post's PlatformPost children to scheduled status with scheduled_at=now(), enabling one-click bulk publish from the approvals tab.
- Add Select All checkbox to Approvals and Queue bulk action headers - Refactor Sent tab: replace fragile inline @click handlers with Alpine x-data methods (applyBulkAction, confirmDelete) - All 4 publish tabs now have consistent select-all + bulk action UI
…ting login form Added global htmx:beforeSwap handler that detects 302 redirects to login or login page HTML returned as 200 (server restart mid-request). Prevents HTMX from injecting the login form into the current tab content area.
…e layout Auth pages (login, signup, password reset) now extend the minimal base_entrance.html instead of base.html. This prevents the full app shell (sidebar, channels, navigation) from rendering behind the login form when the session expires or the server restarts.
… layout All pages that extend base_entrance.html now: - login, logout, signup, email_confirm - password_reset, password_reset_done - password_reset_from_key, password_reset_from_key_done - verification_sent Added extra_js block to base_entrance.html needed by signup template.
The auth templates reference CSS variables (--text-primary, --border, etc.) defined in the compiled styles.css. Replaced the Tailwind CDN script with the static styles.css link so all form fields, buttons, and layouts render correctly.
…plates (CSP compliance)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the bulk actions feature across all three publish tabs (Drafts, Queue, Approvals) and fixes the composer redirecting users away after saving.
Changes
Bulk Actions (Drafts, Queue, Approvals)
@clickhandlers into named x-data methods — eliminated 199 lines of inline JavaScriptComposer Stay-on-Save
Production Fixes
static()doesn't register in production — replaced with explicitre_path)tzdatapackage in slim Python image)link_urlandalt_textpassed throughplatform_extraonPlatformPostScreenshots
(to be added)
How to test