Skip to content

Sponsor Management UI#2972

Draft
JacobCoffee wants to merge 55 commits intomainfrom
sponsor-mgmt-ui
Draft

Sponsor Management UI#2972
JacobCoffee wants to merge 55 commits intomainfrom
sponsor-mgmt-ui

Conversation

@JacobCoffee
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings March 22, 2026 20:37
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a staff-facing Sponsor Management UI under the sponsors app, plus supporting infrastructure for email testing and outbound-notification auditing/logging.

Changes:

  • Add a new /sponsors/manage/ management UI (templates, forms, URLs) for handling sponsorships, packages, benefits, contacts, contracts, and notifications.
  • Persist outbound sponsorship notification sends to a new SponsorshipNotificationLog model and display history in the management UI.
  • Improve local dev ergonomics by adding MailDev + SMTP-backed local email configuration; update sponsor contract email templates; bump django-storages.

Reviewed changes

Copilot reviewed 43 out of 44 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
uv.lock Bumps django-storages from 1.14.4 to 1.14.6 (lockfile update).
pydotorg/settings/local.py Uses SMTP backend when EMAIL_HOST is set (for MailDev), otherwise console backend.
docker-compose.yml Adds a maildev service and wires EMAIL_HOST/EMAIL_PORT into the web service.
apps/sponsors/use_cases.py Logs each sent sponsorship notification to SponsorshipNotificationLog.
apps/sponsors/urls.py Mounts the management UI under sponsors/manage/.
apps/sponsors/templates/sponsors/manage/sponsorship_notify.html UI to preview/send a notification for a single sponsorship.
apps/sponsors/templates/sponsors/manage/sponsorship_list.html Sponsorship list UI with filters, bulk actions, and live search.
apps/sponsors/templates/sponsors/manage/sponsorship_edit.html Edit sponsorship details (package/fee/year).
apps/sponsors/templates/sponsors/manage/sponsorship_detail.html Sponsorship detail/review UI incl. contracts, benefits, assets, communications.
apps/sponsors/templates/sponsors/manage/sponsorship_approve_signed.html Approve sponsorship while uploading a signed contract document.
apps/sponsors/templates/sponsors/manage/sponsorship_approve.html Approve sponsorship (creates draft contract) with quick date helpers.
apps/sponsors/templates/sponsors/manage/sponsor_edit.html Create/edit sponsor company data.
apps/sponsors/templates/sponsors/manage/package_list.html Package list UI grouped by year.
apps/sponsors/templates/sponsors/manage/package_form.html Create/edit a sponsorship package.
apps/sponsors/templates/sponsors/manage/package_confirm_delete.html Confirm-delete UI for packages.
apps/sponsors/templates/sponsors/manage/notification_template_list.html List notification templates in the management UI.
apps/sponsors/templates/sponsors/manage/notification_template_form.html Create/edit notification templates with variable copy helpers.
apps/sponsors/templates/sponsors/manage/notification_template_confirm_delete.html Confirm-delete UI for notification templates.
apps/sponsors/templates/sponsors/manage/notification_history.html Management UI listing sent notifications (log history).
apps/sponsors/templates/sponsors/manage/guide.html End-user guide for PSF sponsorship team workflows.
apps/sponsors/templates/sponsors/manage/dashboard.html Management dashboard summary for a selected year.
apps/sponsors/templates/sponsors/manage/current_year_form.html UI to set the active sponsorship year.
apps/sponsors/templates/sponsors/manage/contract_send.html UI for generating/sending contracts and internal review.
apps/sponsors/templates/sponsors/manage/contract_execute.html UI to upload signed contract and execute/finalize sponsorship.
apps/sponsors/templates/sponsors/manage/contact_form.html Create/edit sponsor contact roles and info.
apps/sponsors/templates/sponsors/manage/clone_year.html Clone packages/benefits from one year to another.
apps/sponsors/templates/sponsors/manage/bulk_notify.html UI to send notifications in bulk across sponsorships.
apps/sponsors/templates/sponsors/manage/benefit_list.html Benefit list UI with filtering/pagination.
apps/sponsors/templates/sponsors/manage/benefit_form.html Create/edit benefit + manage feature configurations and related data.
apps/sponsors/templates/sponsors/manage/benefit_confirm_delete.html Confirm-delete UI for benefits.
apps/sponsors/templates/sponsors/manage/benefit_config_form.html Create/edit benefit feature configuration objects.
apps/sponsors/templates/sponsors/manage/_base.html Management UI shell + global JS table-sorting helper.
apps/sponsors/templates/sponsors/email/sponsor_contract_subject.txt Replaces placeholder subject with a real contract subject template.
apps/sponsors/templates/sponsors/email/sponsor_contract.txt Replaces placeholder contract email body with real copy.
apps/sponsors/models/notifications.py Adds SponsorshipNotificationLog model to persist sent notifications.
apps/sponsors/models/__init__.py Re-exports SponsorshipNotificationLog.
apps/sponsors/migrations/0104_add_notification_log.py Migration creating the notification log table.
apps/sponsors/management/commands/seed_sponsor_manage_data.py Dev-only command to seed realistic data for the management UI.
apps/sponsors/manage/urls.py URL routing for the management UI.
apps/sponsors/manage/forms.py Forms backing the management UI (benefits/packages/sponsorship ops/notifications/etc.).
apps/sponsors/manage/__init__.py Documents that the manage UI is staff/group restricted.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings March 24, 2026 22:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 44 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

JacobCoffee and others added 17 commits March 24, 2026 22:40
… CSV export on detail

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete rewrite covering: dashboard, sponsorship review workflow,
6-step composer wizard, contract lifecycle, benefits & packages with
feature configs, notifications with templates and history, sponsors
& contacts, bulk actions & export, and what's still in Django admin.

Written in direct, human tone for new team member onboarding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard shows cross-year "Expiring Soon" (90-day window with color-coded
countdown) and "Recently Expired" sections with one-click "+ Renewal" buttons
that launch the Composer with sponsor pre-selected and renewal flag set.

Sponsorship list shows expiry tags in the Period column. Detail page shows
Expiring Soon/Expired tags and a "+ Renewal" button for finalized sponsorships.

Adds sponsors_manage templatetags, guide documentation, and 15 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Sync to Sponsorships" button on the benefit edit page that lets
staff push updated benefit data (name, description, value, features) to
all active sponsorships using that benefit. Shows eligible sponsorships
with checkboxes, excludes rejected and expired.

Includes guide documentation and 6 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Regenerate only visible when sponsorship is unlocked
- New contract revision = count of historical contracts (not always 0)
- Regenerate/Cancel buttons use all:unset for consistent sizing
- Add Assets download and Sponsor View buttons to detail page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The generated PDF/DOCX are what was sent to the sponsor, not the
final executed version. "Sent" is accurate, "Final Version" was
misleading for contracts still awaiting signature.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full CRUD for legal clauses under More > Legal Clauses. List view shows
clause text preview, benefit count, and up/down move buttons for ordering.
Edit page shows how many benefits reference the clause. Delete confirms
with impact warning.

Removes legal clause CRUD from the "admin-only" list in the guide.
Includes 10 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Composer contract editor now shows insert buttons for each managed
legal clause below the Legal Clauses textarea. Clicking a clause
button appends its text to the field. Also removes the legal clause
line from the admin-only guide section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an asset browser under More > Assets that lists all sponsor and
sponsorship assets with filters for asset type (text, image, file,
response), related object (sponsor vs sponsorship), submission status
(submitted vs missing), and internal name search. Uses batch-loaded
lookups to avoid N+1 queries on generic relations.

Removes "Detailed asset filtering" from the admin-only guide section.
Includes 6 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Assets grouped by sponsor company with collapsible sections and
submitted/total badge (green/orange/red). Expired and rejected
sponsorship assets excluded. Each row shows package name and
sponsorship status. Companies with missing assets get a Send
Reminder link. Refactored view into helper methods for linting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sponsor-level assets show "company-level" label instead of a Sponsor
tag. Internal name no longer bold to avoid looking like a link.
Sponsorship assets still link to their detail page with package and
status tags.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Sponsors page (More > Sponsors) to browse and search all sponsor
companies with contact count, sponsorship count, location, website, and
quick links to edit or create a sponsorship via the Composer.

Also adds the Assets section to the guide documenting the grouped asset
browser, filters, and Send Reminder feature. Updates sponsor edit to
redirect back to the sponsor directory.

Includes 4 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Revenue Report page (More > Revenue) with:
- Summary cards: total/finalized/approved revenue, deal count + average
- Revenue by Package: full-width horizontal bar chart
- Year-over-Year comparison chart
- Sponsorship detail table with fee, internal value, margin percentage

Dashboard revenue card now links directly to the report.

Sponsorship detail page gets a collapsible Financial Breakdown section
showing fee vs internal value with a coverage bar, and value-by-program
mini bar chart.

Guide documented. 6 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the revenue report with a full Finances page (More > Finances)
featuring Chart.js interactive charts:
- Revenue Trend: stacked bar (finalized vs approved) across all years
- Status Breakdown: doughnut chart for selected year
- Revenue by Package: horizontal bar chart
- Deal Count & Avg Size: combo bar+line chart showing trends

Renames revenue -> finances throughout (URL, nav, guide, dashboard link).
Sponsorship detail keeps its financial breakdown section with fee vs
internal value coverage bar and per-program mini charts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 05:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 51 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@JacobCoffee
Copy link
Member Author

may be best to split on 6c06c38, merge that, and pull anything after into other PRs

JacobCoffee and others added 2 commits March 25, 2026 09:39
- Use json_script filter instead of |safe for chart data
- Add rel="noopener noreferrer" to all target="_blank" links
- Gate email date range on both start_date and end_date
- Log notification persistence failures instead of silent pass
- Fix test assertions to match actual view behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 14:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 51 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +13
@register.simple_tag
def days_until(target_date, reference_date):
"""Return the number of days between reference_date and target_date."""
if not target_date or not reference_date:
return 0
return (target_date - reference_date).days
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This new days_until template tag isn’t covered by the existing templatetag test suite (apps/sponsors/tests/test_templatetags.py). Adding a small unit test (including edge cases like missing dates and negative deltas) would align with existing coverage and prevent subtle date math regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +167 to +169
def _save_m2m(self):
"""Persist reverse benefit associations after the package is saved."""
super()._save_m2m()
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This form overrides the private ModelForm._save_m2m() hook. Since it’s an internal API, it’s more brittle across Django upgrades. Prefer overriding save() (after super().save(commit=False) + save_m2m()) or implementing save_m2m() instead, and keep all M2M synchronization there.

Suggested change
def _save_m2m(self):
"""Persist reverse benefit associations after the package is saved."""
super()._save_m2m()
def save(self, commit=True):
"""Save the package and then persist benefit associations when committing."""
instance = super().save(commit=commit)
if commit:
self.save_m2m()
return instance
def save_m2m(self):
"""Persist reverse benefit associations after the package is saved."""
super().save_m2m()

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +26
<div class="manage-section-header" style="flex-wrap:wrap;gap:12px;">
<h2>Sent Notifications <span class="badge">{{ paginator.count|default:logs|length }}</span></h2>
<form method="get" style="display:flex;gap:6px;align-items:center;">
<input type="text" name="search" id="history-search" placeholder="Search subject, sponsor, recipient..." value="{{ filter_search }}" autocomplete="off" style="width:260px;padding:6px 10px;border:1px solid #ccc;border-radius:4px;font-size:13px;">
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

{{ paginator.count|default:logs|length }} applies length to paginator.count (an int), which will render as 0/empty rather than the actual total. Use paginator.count (or page_obj.paginator.count) directly when paginated, and fall back to logs|length only when no paginator is present (e.g., via an {% if %} block or a precomputed total_count context var).

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +215
# Persist notification log (best-effort, don't break sending)
try:
sent_by = None
if request and hasattr(request, "user") and getattr(request.user, "is_authenticated", False):
sent_by = request.user
SponsorshipNotificationLog.objects.create(
sponsorship=sponsorship,
subject=getattr(email, "subject", ""),
content=getattr(email, "body", ""),
recipients=", ".join(getattr(email, "to", [])),
contact_types=", ".join(contact_types),
sent_by=sent_by,
)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

New behavior persists SponsorshipNotificationLog entries when sending notifications, but the existing SendSponsorshipNotificationUseCaseTests don’t assert that logs are created (or that fields like subject/recipients/contact_types are recorded). Adding an assertion here would prevent regressions where the history UI silently stops recording sends.

Copilot uses AI. Check for mistakes.
JacobCoffee and others added 4 commits March 25, 2026 09:56
1. Add distinct=True to Count annotations in SponsorListView to prevent
   cross-multiplication of contacts × sponsorships
2. Benefit form checks bound POST year data so changing year + package
   in a single submit works
3. SponsorshipEditForm same fix for year-scoped package filtering
4. Clear package M2M on cloned benefits when clone_packages unchecked
5. Return sent_count from notification use case; show warning when
   zero emails were actually sent instead of false success
6. Show error when PDF generation fails but DOCX succeeds so staff
   know the contract was not finalized

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.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.

2 participants