Skip to content

feat: add programs app with activities, signups, travel grants, and manage UI#22

Merged
JacobCoffee merged 8 commits intomainfrom
phase-6/programs-app
Feb 12, 2026
Merged

feat: add programs app with activities, signups, travel grants, and manage UI#22
JacobCoffee merged 8 commits intomainfrom
phase-6/programs-app

Conversation

@JacobCoffee
Copy link
Owner

Summary

  • Add programs Django app with Activity, ActivitySignup, and TravelGrant models
  • Public-facing views for activity listing, detail, signup (auth-required), and travel grant application
  • Full manage dashboard integration: activity CRUD, travel grant review, sidebar nav, dashboard stat cards
  • Django admin with inline signups and editable grant status
  • Initial migration and 100% test coverage (19 tests)

What's Included

Models

  • Activity - Conference activities (sprints, workshops, tutorials, socials, open spaces) with capacity tracking via spots_remaining property
  • ActivitySignup - User-activity join with unique constraint (one signup per user per activity)
  • TravelGrant - Grant applications with pending/approved/rejected/withdrawn lifecycle, reviewer tracking

Public Views (/programs/)

  • Activity list (active only, conference-scoped)
  • Activity detail with signup form and availability
  • POST-only signup with capacity check
  • Travel grant application form with decimal validation

Manage Dashboard (/manage/)

  • Activity list, create, edit views with signup counts
  • Travel grant list with status filter, review view with reviewer auto-assignment
  • Sidebar navigation section for Programs (Activities + Travel Grants)
  • Dashboard stat cards for activities and travel grants

Admin

  • ActivityAdmin with ActivitySignupInline
  • TravelGrantAdmin with list-editable status

Test plan

  • All 767 tests pass (make ci)
  • 100% test coverage on django_program.programs module
  • Lint, format, type-check all pass
  • Pre-commit hooks pass

🤖 Generated with Claude Code

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Contributor

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 adds a comprehensive programs app to manage conference activities (sprints, workshops, tutorials, socials) and travel grant applications. The implementation includes public-facing views for activity discovery and signup, a full management dashboard for organizers, Django admin integration, and complete test coverage.

Changes:

  • New programs app with Activity, ActivitySignup, and TravelGrant models supporting capacity tracking and grant lifecycle management
  • Public views at /<conference_slug>/programs/ for activity listing, detail, signup (auth-required), and travel grant applications
  • Management dashboard integration at /manage/<conference_slug>/ with activity CRUD, grant review, sidebar navigation, and dashboard stat cards
  • Django admin with inline signups and editable grant status

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/django_program/programs/models.py Defines Activity, ActivitySignup, and TravelGrant models with capacity tracking and unique constraints
src/django_program/programs/views.py Public-facing views for activity listing/detail/signup and travel grant application
src/django_program/programs/urls.py URL routing for programs app under conference slug
src/django_program/programs/admin.py Django admin configuration with inline signups and list-editable grant status
src/django_program/programs/templates/ Public-facing templates for activities and travel grant form
src/django_program/manage/views.py Activity and travel grant management views with dashboard stats integration
src/django_program/manage/urls.py Management URLs for activity CRUD and grant review
src/django_program/manage/forms.py ActivityForm and TravelGrantForm for management interface
src/django_program/manage/templates/ Management dashboard templates with sidebar nav and stat cards
src/django_program/programs/migrations/0001_initial.py Initial migration creating all three models
tests/test_programs/ Comprehensive test coverage (19 tests) for models and views
tests/test_manage/test_programs_views.py Tests for management views including dashboard integration
tests/urls.py URL configuration for test suite

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

Base automatically changed from phase-5/sponsors-app to main February 12, 2026 02:31
JacobCoffee and others added 2 commits February 11, 2026 20:33
…anage UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix race condition in ActivitySignupView with select_for_update
- Fix N+1 queries in activity list views with Count annotation
- Refactor TravelGrantApplyView to use ModelForm validation
- Add server-side validation for negative amounts and empty fields
- Handle duplicate travel grant applications gracefully
- Add tests for negative amount, empty fields, and duplicate grants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 12, 2026 02:38
Copy link
Contributor

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 23 out of 24 changed files in this pull request and generated 6 comments.


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

JacobCoffee and others added 5 commits February 11, 2026 23:05
…t enhancements

- Receipt model with file upload, approval/flagging workflow, and admin
- PaymentInfo model with encrypted fields (django-fernet-encrypted-fields)
- Disbursement lifecycle: accepted → disbursed with amount/date/processor tracking
- Receipt upload page with sidebar summary, progress bar, drag-and-drop file input
- Payment info page with conditional fields by method, encryption indicators
- Manage receipt review queue with approve/flag actions
- Manage disbursement UI with amount input on grant review page
- Travel grant status page shows receipts/payment/disbursement state
- Bootstrap seeding for travel grants with varied statuses
- MEDIA_URL/MEDIA_ROOT and FIELD_ENCRYPTION_KEY in dev/test settings
- Migrations 0003-0006 for activity enhancements, travel grant model, receipts, disbursement
- 893 tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 12, 2026 05:11
@JacobCoffee JacobCoffee merged commit ba127be into main Feb 12, 2026
16 checks passed
@JacobCoffee JacobCoffee deleted the phase-6/programs-app branch February 12, 2026 05:16
Copy link
Contributor

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 45 changed files in this pull request and generated 9 comments.


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

Comment on lines +454 to +464
receipts = grant.receipts.all()
return render(
request,
self.template_name,
{
"conference": self.conference,
"grant": grant,
"form": form,
"receipts": receipts,
},
)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

On receipt upload validation errors, the template is re-rendered without receipt_total. Since the page displays totals/coverage in both the sidebar and JS, this causes an incorrect $0 total even when receipts already exist. Compute receipt_total in this error path (same as in get()) and include it in the render context.

Copilot uses AI. Check for mistakes.
Comment on lines +401 to +402
"""Sum of airfare and lodging breakdown amounts."""
return self.travel_plans_airfare_amount + self.travel_plans_lodging_amount
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

travel_plans_total is used as the “total” in both applicant and reviewer UIs, but it currently sums only airfare + lodging and ignores transit/visa (and any other breakdown fields). This makes the displayed totals incorrect. Either include all breakdown amounts in the calculation or rename this property to reflect what it actually sums.

Suggested change
"""Sum of airfare and lodging breakdown amounts."""
return self.travel_plans_airfare_amount + self.travel_plans_lodging_amount
"""Sum of all travel plan breakdown amounts (airfare, lodging, transit, visa, etc.)."""
total = Decimal("0")
for field in self._meta.get_fields():
if (
isinstance(field, models.DecimalField)
and field.name.startswith("travel_plans_")
and field.name.endswith("_amount")
):
value = getattr(self, field.name, None)
if value is not None:
total += value
return total

Copilot uses AI. Check for mistakes.
Comment on lines +1665 to +1679
grant = get_object_or_404(TravelGrant, pk=kwargs["pk"], conference=self.conference)
form = DisbursementForm(request.POST)
if form.is_valid() and grant.status == TravelGrant.GrantStatus.ACCEPTED:
grant.status = TravelGrant.GrantStatus.DISBURSED
grant.disbursed_amount = form.cleaned_data["disbursed_amount"]
grant.disbursed_at = timezone.now()
grant.disbursed_by = request.user
grant.save(update_fields=["status", "disbursed_amount", "disbursed_at", "disbursed_by"])
display_name = grant.user.get_full_name() or grant.user.username
messages.success(
request,
f"Grant for {display_name} marked as disbursed (${grant.disbursed_amount}).",
)
else:
messages.error(request, "Could not process disbursement.")
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The disbursement endpoint allows marking a grant as disbursed whenever status is accepted, even if required prerequisites (payment info + at least one approved receipt) are missing. Since the model exposes is_ready_for_disbursement, consider enforcing that here (and return a specific error message when not ready) to prevent accidental premature disbursements.

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +261
{% if grant.status == "accepted" %}
<div class="card" style="margin-bottom: 1.5rem; border-color: #bfdbfe;">
<div style="padding: 0.85rem 1.5rem; background: #eff6ff; border-bottom: 1px solid #bfdbfe;">
<span style="font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #1e40af;">Disbursement</span>
</div>
<div class="card-body">
<form method="post" action="{% url 'manage:travel-grant-disburse' conference.slug grant.pk %}">
{% csrf_token %}
<div style="display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap;">
<div style="flex: 1; min-width: 200px;">
<label style="display: block; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); margin-bottom: 0.35rem;">Disbursement Amount</label>
<input type="number" name="disbursed_amount" step="0.01" value="{{ grant.approved_amount }}" style="width: 100%; padding: 0.55rem 0.75rem; border: 1px solid var(--color-border); border-radius: var(--radius-sm); font-family: var(--font-mono); font-size: 0.9rem;">
</div>
<button type="submit" class="btn btn--primary" onclick="return confirm('Mark this grant as disbursed? This records that payment has been sent.');">
Mark as Disbursed
</button>
</div>
</form>
</div>
</div>
{% endif %}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The “Mark as Disbursed” UI is shown for any accepted grant, even if it isn’t ready for disbursement (no payment info / no approved receipts). Consider gating this section on grant.is_ready_for_disbursement (or otherwise clearly indicating missing prerequisites) to prevent accidental disbursement actions.

Copilot uses AI. Check for mistakes.
Comment on lines +1688 to +1696
pending = (
Receipt.objects.filter(
grant__conference=self.conference,
approved=False,
flagged=False,
)
.select_related("grant__user")
.order_by("?")
.first()
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

order_by("?") does a full-table random sort and can become very slow as receipt volume grows. Consider selecting a pending receipt using a cheaper strategy (e.g., pick the smallest/oldest pending receipt, or select a random PK within the filtered set).

Copilot uses AI. Check for mistakes.
Comment on lines +698 to +700
{% if receipt.receipt_file %}
<a href="{{ receipt.receipt_file.url }}" target="_blank" class="file-link" title="View receipt">View</a>
{% endif %}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This link opens a user-uploaded file in a new tab (target="_blank") without rel="noopener noreferrer", which enables reverse-tabnabbing. Add rel="noopener noreferrer" to external/new-tab links.

Copilot uses AI. Check for mistakes.
Comment on lines +802 to +805
wrapper.addEventListener('drop', function() {
wrapper.style.borderColor = '';
wrapper.style.background = '';
});
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The drag-and-drop drop handler doesn’t call preventDefault(). Dropping a file can trigger the browser’s default behavior (navigating away to open the file), which is a poor UX and can cause data loss. Add an event parameter and call e.preventDefault() (and typically e.stopPropagation()) in the drop handler.

Copilot uses AI. Check for mistakes.
<h2 class="section-heading">Receipt File</h2>
<div style="margin-bottom: 2rem; background: var(--color-bg); border: 1px solid var(--color-border-light); border-radius: var(--radius-sm); padding: 1rem;">
{% if receipt.receipt_file %}
<p style="margin-bottom: 0.5rem;"><a href="{{ receipt.receipt_file.url }}" target="_blank" class="btn btn-sm btn-secondary">Open File</a></p>
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This link opens a user-uploaded file in a new tab (target="_blank") without rel="noopener noreferrer", which enables reverse-tabnabbing. Add rel="noopener noreferrer" to new-tab links.

Suggested change
<p style="margin-bottom: 0.5rem;"><a href="{{ receipt.receipt_file.url }}" target="_blank" class="btn btn-sm btn-secondary">Open File</a></p>
<p style="margin-bottom: 0.5rem;"><a href="{{ receipt.receipt_file.url }}" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-secondary">Open File</a></p>

Copilot uses AI. Check for mistakes.

@pytest.mark.django_db
def test_provide_info_get(client: Client, conference: Conference, user: User):
grant = TravelGrant.objects.create(
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Variable grant is not used.

Copilot uses AI. Check for mistakes.
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.

1 participant