OpenTrack APIs
We used to use the FastApi framework, set up by a very smart developer who worked with us one summer (Giovanni Fiorino). However, it's too different from Django and needed lots of synchronous code calling asynchronous code, so we ripped it out
From 2024 onwards, we are trying to use Django ReST API for all JSON APIs, and our usual User/Person/Role mechanisms to secure and limit who does what.
Relevant code and URLs are all in project/drapi. For APIs it is particularly important that we have
- review of code to make sure it's clean, general and makes sense
- unit tests to check each endpoint works with realistic data
- documentation for the public.
Currently we are failing on the third one!
Recording attempts and results on a field unit
One handler covers throws and horizontal jumps plus vertical jumps: api_record_unit_trials in project/comp/api.py, reached at
PUT|POST|PATCH /api/x/<year>/<country>/<slug>/event/<event_id>/<round>/<heat>/record/trials/
with /record/height-trials/ kept as an alias. It picks the length or height processor from the unit itself (does it have trials or heights), so the URL decides nothing. The verb decides how a whole list is applied, and a single object is always a one-row write whatever the verb.
Three body shapes, and the difference matters:
- One attempt — a flat object with
bibandround(lengths) orbibandheight(verticals), e.g.{"bib": "8", "round": 5, "result": "3.87", "polyfield": {"rx": "-0.42", "ry": "7.31"}}. Upserted on bib+round / bib+height via_apply_single_trial, so only that row is touched, on every verb. This is what measuring clients should send. - One result — a flat object with
bibandperformance. Handled by_apply_single_bib_result, replacing that bib's row inresults. Caveat:Unit.save()runscalc_results(), which recomputesperformancefrom that bib's attempts, so on a unit with trials the value you send is overwritten — for those, record the attempt instead. - Whole lists — any of
trials,results,heightspresent switches to bulk mode.POSTandPATCHmerge (upsert by bib+round / bib+height / bib, keeping every row not mentioned);PUTreplaces the list with exactly what was sent. The recording screens send the whole list they hold withPUT, and delete an attempt by leaving it out, which is whyPUThas to stay a replacement.
Verbs are deliberately not identical any more, but POST and PATCH are equivalent for lists: both are additive, and neither can delete. PUT is the destructive one.
polyfield_config (alias config) and polyfield_geometry can ride along in any of those bodies: the view sets them on the mongo unit before dispatch and echoes them in the response. That is how a measuring client says which sector to draw (seated club throw from the hammer circle, etc.). They are also first-class on the unit resource — LengthUnitSerializer declares them as read-only method fields and writes them from self.initial_data in create/update — so a PATCH or PUT on the unit sets them too and they come back in the unit payload. The admin remains the third way.
There is no confirmation flag: PUT replaces because that is what PUT means, and PATCH is the verb for a client that only wants to add or correct a row — a measuring client that wrapped one row in a trials array and expected an append should switch to PATCH (or send the attempt as a bare object). The unit's own natural-key URL follows the same contract: LengthUnitSerializer.update and HeightUnitSerializer.update merge trials when the request is a PATCH and replace them on a PUT.
Anything else about the body is a 400 too: a body with no recognisable key, or a bib+result attempt that omits round/height, is refused rather than answered 200 having changed nothing.