Add public read-only REST API at /api/v1/ (#1268)#1419
Merged
Conversation
Adds a versioned, read-only DRF API over already-public content so external
sites can treat this website as the source of truth instead of duplicating it.
Two consumers drove the design: Project Sidewalk (grants/people/leadership/
publications for a project) and Jon's academic page (a recent-pubs widget).
Endpoints (all GET-only, paginated with a tunable ?page_size, max 100):
- /publications/ filterable by ?project, ?author, ?year, ?type, ?ordering
(default newest-first) -- serves both ?author=jonfroehlich&page_size=5 and
?project=<short_name>.
- /projects/ (visible only), /grants/, /people/ (members only), plus project
sub-resources: /projects/<short_name>/{publications,grants,people,leadership}.
- leadership returns ALL lead roles for all time (current + past), grouped by
type; it queries ProjectRole directly rather than reusing
get_project_leadership() (which drops a person's past lead roles once they
hold any active role).
Design decisions (from #1268 scoping): DRF was already a bundled-but-unused
dependency; public data so no auth and no throttle; cross-origin enabled on
/api/ only via a tiny in-repo ApiCorsMiddleware (no django-cors-headers);
absolute media/page URLs in every payload; Person.email deliberately not
exposed. Projects gated to is_visible=True; people scoped to actual members
(those with a Position).
Code in website/api/ (serializers/views/urls/middleware), mounted by the root
URLconf, configured by the REST_FRAMEWORK block in settings. Full reference in
docs/API.md; pointer in CLAUDE.md. 23 integration tests in
website/tests/test_api.py (full suite: 607 pass). Bumps version to 2.27.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y9JS9XZfD86FdDLJiJFSBk
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.
Closes #1268.
Adds a versioned, read-only DRF API over already-public content so external sites (Project Sidewalk, Jon's academic page) can treat this website as the source of truth instead of duplicating content. The data was already public on the site; this just makes it machine-readable.
Endpoints (
/api/v1/, all GET-only, paginated)/publications/— filters?project=,?author=,?year=,?type=,?ordering=(default newest-first). Powers both drivers:?author=jonfroehlich&page_size=5(recent-pubs widget) and?project=sidewalk./projects/(visible only),/grants/,/people/(lab members only)./projects/<short_name>/{publications,grants,people,leadership}.leadershipreturns all lead roles for all time (current + past), grouped by type, each with anis_activeflag. It queriesProjectRoledirectly rather than reusingget_project_leadership(), which drops a person's past lead roles once they hold any active role.Design decisions (from #1268 scoping)
/api/only via a tiny in-repoApiCorsMiddleware(nodjango-cors-headers), so a browser-side widget can fetch it directly.Person.emaildeliberately not exposed (avoids an email-harvesting surface).is_visible=True;/people/scoped to actual members (those with a Position).Code / docs / tests
website/api/(serializers.py,views.py,urls.py,middleware.py), mounted by the root URLconf;REST_FRAMEWORKblock insettings.py.docs/API.md(endpoints, filters, stability contract); pointer inCLAUDE.md.website/tests/test_api.py(filters, pagination, visibility gating, 404s, absolute URLs, CORS + preflight, all-time leadership incl. the past-role-of-an-active-person case, email-not-exposed). Full suite: 607 pass.Verified against live dev data
?author=jonfroehlich&page_size=5→ 108 pubs, newest-first.Deferred (same pattern later): write endpoints, API keys, throttling, Talks/Posters/Videos resources.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Y9JS9XZfD86FdDLJiJFSBk