Skip to content

Surface T-shirt size elements in the backoffice admin - #2467

Open
Elena Peña Tapia (ElePT) wants to merge 4 commits into
mainfrom
add-tshirt-admin-elements
Open

Surface T-shirt size elements in the backoffice admin#2467
Elena Peña Tapia (ElePT) wants to merge 4 commits into
mainfrom
add-tshirt-admin-elements

Conversation

@ElePT

@ElePT Elena Peña Tapia (ElePT) commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

The T-shirt size models (ComputeProfile, FunctionSize, Program.default_size) shipped in 0.36.0, but a function's size catalog (its sizes map) and its default size weren't surfaced in the backoffice admin where operators work. This PR adds them, and — as a second step — makes a Fleets function created purely in the admin actually runnable.

Part 1 — Surface the size elements

On the Program (Function) page

  • FunctionSizeInline — a function's whole sizes map (each size key → compute profile) is now editable inline on its own page, mirroring the catalog the upload endpoint's sizes payload builds. It renders as "Sizes (compute profile per size)".
  • default_size field in its own "Default size" fieldset, rendered directly below the sizes catalog. Its dropdown is restricted to the function's own sizes (Program.clean() rejects a default belonging to another function). The fieldset and the inline are placed adjacent (via a small change_form.html override) and named apart so the two are not mistaken for duplicates.
  • sizes_summary changelist column — N (default: s) or -.

On the ComputeProfile page

  • sizes_using column — reference count, so an operator sees a profile is in use before editing/deleting it (the FK is PROTECT).

Both changelist columns are annotated to avoid per-row queries.

Part 2 — Make a backoffice-created Fleets function runnable

A Program created purely in the admin previously got only model defaults, skipping a runnable-critical step the upload use case performs:

  • CE project assignment. ProgramAdminForm.clean() assigns a Code Engine project to a Fleets function (provider's active project, or the deployment default) via CodeEngineProject.objects.assign_to_program, and blocks the save — with the upload endpoint's own message — when none is available. A Fleets function with no active CE project cannot run, so this fails loud rather than persisting a dead function.

Refactor: promoted _no_ce_project_messageno_ce_project_message so the admin reuses the endpoint's exact wording.

Note: default-size seeding is intentionally left out

An earlier version of this PR also seeded a per-function default size (DEFAULT_FUNCTION_SIZEDEFAULT_FUNCTION_SIZE_PROFILE) for a size-less Fleets function, mirroring the upload path. That has been removed.

The reason is a design decision that is not in the codebase yet but is coming: the fallback for a function that declared no sizes should be a single shared platform-default FunctionSize row (no function id), resolved at job creation rather than at function creation, so Job.function_size is never null and billing can always read the size off the job. The upload path's existing seeding is untouched (out of scope) and will be revisited when the platform-default work lands.

Scope

  • Admin-only. No model or migration changes.
  • Does not touch Job.compute_profile (the deprecated string) — that's a separate branch's concern.

Behavioral note (blocking tradeoff)

Because clean() blocks any Fleets save that can't get an active CE project, a legacy/misconfigured Fleets function whose provider has no active project (and no default configured) cannot be saved — even to disable it — until a project is assignable. This is the intended "fail-loud" behavior (mirrors the upload endpoint); the operator assigns a project in the same edit.

Testing

  • gateway/tests/test_tshirt_admin.py (16 tests): inline rendering, default_size dropdown scoping, sizes_summary, sizes_using count; CE auto-assign (provider + default paths), blocking when unconfigured / no project / inactive project, operator-chosen project preserved, Ray no-ops; plus two end-to-end admin client tests proving the real add form blocks a non-runnable function and creates a runnable one.
  • pylint 10.00/10 on both source files; black clean.

Register a function's size catalog (its sizes map) and default size where
operators actually work:

- FunctionSizeInline on ProgramAdmin: edit each size -> compute profile
  inline on the function page, mirroring the upload endpoint's sizes payload.
- default_size field in a new Sizes fieldset, with its dropdown limited to
  the function's own sizes (Program.clean() rejects a default belonging to
  another function).
- sizes_summary column on the Program changelist (count + default).
- sizes_using column on ComputeProfile so operators see a profile is
  referenced before editing/deleting it (the FK is PROTECT).

Both changelist columns are annotated to avoid per-row queries.
A Program created purely in the Django admin previously got only model
defaults, skipping two runnable-critical steps the upload use case performs:

- CE project assignment. ProgramAdminForm.clean() now assigns a Code Engine
  project to a Fleets function (provider's active project, or the deployment
  default) via CodeEngineProject.objects.assign_to_program, and BLOCKS the save
  with the upload endpoint's own message when none is available — a Fleets
  function with no active project cannot run.
- Default-size seeding. ProgramAdmin.save_related() seeds the deployment
  default size (DEFAULT_FUNCTION_SIZE -> DEFAULT_FUNCTION_SIZE_PROFILE) for a
  size-less Fleets function, like upload does. Runs after the inline
  FunctionSize formset so an operator's declared sizes are respected; skipped
  when a default is already set. Non-fatal + warns when the profile is
  unregistered.

Reuses upload.py logic via a new module-level seed_default_size() (the use
case method now delegates) and a promoted no_ce_project_message(), keeping one
source of truth. Admin-only; no model or migration changes.
The program change page had two sections both starting with 'Sizes': the
'Sizes' fieldset (which only held the default_size dropdown) and the
'Sizes (compute profile per size)' inline catalog. They read as duplicates.

Rename the fieldset to 'Default size' and, via the change_form template,
render the sizes catalog inline first with the default_size pointer directly
below it, so the catalog is defined before you pick a default from it.
@ElePT

Copy link
Copy Markdown
Collaborator Author

Verified: admin T-shirt size elements

Ran the backoffice admin locally against a seeded SQLite DB and confirmed each new element renders with real data. Full test suite for the feature passes: pytest tests/test_tshirt_admin.py → 21 passed.

Function list — new "Sizes" column (sizes_summary)
A function with sizes shows the count and the default; a size-less function shows -.
vqe-solver3 (default: m), qaoa-runner-.

program_changelist

Compute profile list — new "Sizes using" column (sizes_using)
Counts how many function sizes point at each profile, so operators see usage before editing or deleting one (the FK is PROTECT). The unused 32x256 profile correctly shows 0.

computeprofile_changelist

Function change page — Sizes fieldset, default_size, and the sizes inline
The default_size dropdown is scoped to the function's own sizes. On vqe-solver it lists only vqe-solver (s/m/l), nothing from other functions. The "Sizes (compute profile per size)" inline below maps each size to its compute profile. The Fleets fieldset shows the assigned Code Engine project.

program_change

Remove ProgramAdmin.save_related()/_ensure_default_size() and the module-level
seed_default_size() extraction. A backoffice-created size-less Fleets function no
longer gets a per-function 'm' size seeded from DEFAULT_FUNCTION_SIZE.

That seeding manufactured a function default (a real vendor choice) for a function
whose vendor chose nothing, which collides with the platform-default model we are
moving to: a single shared FunctionSize row (function_size='platform-default', no
function id) resolved at job creation, so Job.function_size is never null and billing
can always read the size off the job. That work lands separately; this PR should not
persist per-function rows the coming migration would have to reconcile.

Keeps the admin UI half and the Code Engine project assignment (the runnable part
that is uncontroversial). Reverts the upload.py seeding extraction but keeps the
no_ce_project_message promotion, since the admin still reuses it. Removes the
now-obsolete save_related seeding tests.
@ElePT
Elena Peña Tapia (ElePT) marked this pull request as ready for review September 11, 2026 10:32
@ElePT
Elena Peña Tapia (ElePT) requested a review from a team as a code owner September 11, 2026 10:32
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