Skip to content

Make projects private by default with a single is_visible flag (#1300) - #1302

Merged
jonfroehlich merged 1 commit into
masterfrom
1300-make-projects-private-by-default
Jun 15, 2026
Merged

Make projects private by default with a single is_visible flag (#1300)#1302
jonfroehlich merged 1 commit into
masterfrom
1300-make-projects-private-by-default

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

Closes #1300.

Summary

Public project visibility was a hodgepodge: an implicit "has a thumbnail AND a publication" check duplicated across views and templates. This replaces it with a single editor-controlled Project.is_visible flag so we can:

  • create a project entry the moment a project begins,
  • add people to it while we're working (keeping the people-tracking accurate for NSF reporting, etc.),
  • keep it hidden from the public until it's ready to go live.

What changed

  • Model (models/project.py): new is_visible field. Project.save() defaults new projects to private (False); can_show_online() now returns bool(is_visible). The field is intentionally nullable with no DB default so the migration adds existing rows as NULL rather than silently hiding every current project on first deploy.
  • One-shot backfill (backfill_project_visibility, wired into docker-entrypoint.sh): resolves legacy NULL rows to their previous visibility (thumbnail + publication). Keyed on is_visible__isnull=True, so it's idempotent and never clobbers a later manual admin choice. Same pattern as backfill_num_pages.
  • Replaced the scattered checks with is_visible=True in the landing page (index), project gallery (project_listing, active + completed), and member views; dropped the now-redundant per-item guards in project_listing.html. Ordering uses nulls_last so a visible project with no publication yet doesn't sort to the top.
  • Individual project page: private projects return 404 for the public but remain previewable by logged-in staff (so you can build a project before going live).
  • Admin: is_visible added to fieldsets / list_display / list_filter, rendered as a clean checkbox ("Visible on website").
  • Supporting: data-health check surfaces is_visible; seed_demo_projects marks demo projects visible. Version bumped to 2.6.0.

Testing

  • 130 tests pass (python manage.py test website --settings=makeabilitylab.settings_test), including 16 new ones in test_project_visibility.py covering the private-by-default behavior, backfill idempotency, gallery/landing filtering, and the staff-preview gate. Added a make_project fixture helper.
  • Backfill verified in the real entrypoint on local dev: "Resolved 18 legacy project(s) — 12 visible, 6 private" — existing visibility preserved.
  • Gate verified live: private project → 404 for anonymous, visible → 200.

UI / accessibility note

No markup changed — the project gallery renders the same unchanged snippet; only which projects appear is different (driven by the queryset). The one visible UI addition is a checkbox on the admin Project form. Pa11y shows no new violations (only the site's pre-existing global footer/color-contrast baseline).

Deploy plan

Merging to master deploys to -test only; production is intentionally not tagged yet so we can validate on makeabilitylab-test.cs.washington.edu first. The entrypoint backfill will preserve existing projects' visibility there.

🤖 Generated with Claude Code

Public project visibility was an implicit "has a thumbnail AND a
publication" heuristic duplicated across views and templates. Replace it
with a single editor-controlled Project.is_visible flag so the team can
create a project the moment it begins, add people while working, and keep
it hidden until it's ready to go live.

- Model: add nullable is_visible field; Project.save() defaults new
  projects to private (False); can_show_online() now returns
  bool(is_visible). The field is intentionally nullable with no DB
  default so the migration adds existing rows as NULL rather than
  silently hiding every current project.
- backfill_project_visibility one-shot command (wired into
  docker-entrypoint.sh) resolves legacy NULL rows to their previous
  visibility (thumbnail+pub). Keyed on is_visible__isnull=True, so it's
  idempotent and never clobbers a later manual admin choice.
- Replace the scattered checks with is_visible=True in index,
  project_listing (active+completed), and member views, and drop the
  now-redundant per-item guards in project_listing.html. Order by
  most-recent-pub with nulls_last so a pub-less visible project doesn't
  sort to the top.
- Individual project page: private projects 404 for the public but
  remain previewable by logged-in staff.
- Admin: is_visible in fieldsets/list_display/list_filter, rendered as a
  clean checkbox ("Visible on website").
- data-health check surfaces is_visible; seed_demo_projects marks demos
  visible.
- Tests: new test_project_visibility.py (default, backfill idempotency,
  listing/page filtering, staff-preview gate) + make_project helper.
- Bump version to 2.6.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonfroehlich
jonfroehlich merged commit 35ada26 into master Jun 15, 2026
1 check passed
@jonfroehlich

Copy link
Copy Markdown
Member Author

Follow-up: full visibility coverage (commit 281fe1b)

After an audit of every place a project can be mentioned, found six surfaces beyond the gallery/landing/member/chip set that still referenced a private project. All now route through is_visible:

Surface Before Now
Awards page showed project name as plain text private project not listed at all (new Award.get_visible_projects)
"Related projects" on a project page filtered on gallery_image only also requires is_visible
People listing chips get_projects_sorted_by_contrib filtered by pub count also filters is_visible=True
News item related projects news.project.all() display filtered to visible (related-news lookup unchanged)
Project-listing umbrella filter counts/names from project_set.all() visible-only (no count inflation / name leak)
Landing carousel private project's banner showed name + 404 link banners on a private project excluded

Intentionally not changed: /view-project-people/ — per discussion this is an internal people-tracking tool and is left exempt.

Each surface has a regression test in test_project_visibility.py (21 visibility tests; 135 total, all green).

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.

Make projects "private" by default when first created

1 participant