ctfy.sdk.admin_resources.competition_boards
client.admin.competition_boards — a competition's extra boards (#1965 G).
A board is a named (rule, params) over the competition's facts. The
primary is the competition's own scoring rule (set with
client.admin.competitions.update); these are the others — a published
standing beside it, or a shadow trial only the organisers can read.
1"""``client.admin.competition_boards`` — a competition's extra boards (#1965 G). 2 3A board is a named ``(rule, params)`` over the competition's facts. The 4primary is the competition's own scoring rule (set with 5``client.admin.competitions.update``); these are the others — a published 6standing beside it, or a ``shadow`` trial only the organisers can read. 7""" 8 9from __future__ import annotations 10 11from typing import Any 12 13from ctfy.core.enums import BoardVisibility, ScoringRuleName 14from ctfy.core.localized import LocalizedText 15from ctfy.sdk._helpers import _raise_for_status 16from ctfy.sdk.base import BaseHttpClient 17from ctfy.server.models import CompetitionBoardInfo, CompetitionBoardsResponse 18 19 20class AdminCompetitionBoardsResource: 21 """Add, list, edit, publish and remove one competition's boards. For its 22 organisers (global admins and the event's own competition admins).""" 23 24 def __init__(self, http: BaseHttpClient) -> None: 25 self._http = http 26 27 def list(self, competition_id: str) -> CompetitionBoardsResponse: 28 """Every board, the primary first and the shadow ones included — 29 the organiser's read of ``GET /competitions/{id}/boards``.""" 30 resp = self._http.request("GET", f"/competitions/{competition_id}/boards") 31 _raise_for_status(resp) 32 return CompetitionBoardsResponse.model_validate(resp.json()) 33 34 def create( 35 self, 36 competition_id: str, 37 *, 38 slug: str, 39 rule: ScoringRuleName, 40 params: dict[str, Any] | None = None, 41 title: str | LocalizedText = "", 42 visibility: BoardVisibility = "shadow", 43 position: int = 0, 44 ) -> CompetitionBoardInfo: 45 """Add a board. It starts ``shadow`` — computed on the real facts and 46 read by organisers alone — unless ``visibility="published"``. 47 ``title`` is ``{locale: text}`` (a bare string is read as ``en``).""" 48 payload: dict[str, Any] = { 49 "slug": slug, 50 "rule": rule, 51 "params": params or {}, 52 "title": title, 53 "visibility": visibility, 54 "position": position, 55 } 56 resp = self._http.request( 57 "POST", f"/admin/competitions/{competition_id}/boards", json=payload 58 ) 59 _raise_for_status(resp) 60 return CompetitionBoardInfo.model_validate(resp.json()) 61 62 def update( 63 self, 64 competition_id: str, 65 slug: str, 66 *, 67 rule: ScoringRuleName | None = None, 68 params: dict[str, Any] | None = None, 69 title: str | LocalizedText | None = None, 70 visibility: BoardVisibility | None = None, 71 position: int | None = None, 72 scoring_change_reason: str | None = None, 73 ) -> CompetitionBoardInfo: 74 """Edit one board. Partial: only the arguments you pass are sent. 75 76 ⚠️ ``params`` **replaces** the board's params, and a new ``rule`` 77 without ``params`` starts from that rule's defaults. Once the 78 competition is live, changing a *published* board's scoring needs 79 ``scoring_change_reason``: the change is recorded and shown on the 80 board (``422 scoring_change_needs_reason`` without one). A shadow 81 board is edited freely.""" 82 payload: dict[str, Any] = {} 83 if rule is not None: 84 payload["rule"] = rule 85 if params is not None: 86 payload["params"] = params 87 if title is not None: 88 payload["title"] = title 89 if visibility is not None: 90 payload["visibility"] = visibility 91 if position is not None: 92 payload["position"] = position 93 if scoring_change_reason is not None: 94 payload["scoring_change_reason"] = scoring_change_reason 95 resp = self._http.request( 96 "PATCH", f"/admin/competitions/{competition_id}/boards/{slug}", json=payload 97 ) 98 _raise_for_status(resp) 99 return CompetitionBoardInfo.model_validate(resp.json()) 100 101 def promote(self, competition_id: str, slug: str) -> CompetitionBoardInfo: 102 """Publish a shadow board: put it on the board page's tabs and every 103 surface. Allowed at any time — it adds a standing, it moves none.""" 104 return self.update(competition_id, slug, visibility="published") 105 106 def delete(self, competition_id: str, slug: str) -> CompetitionBoardsResponse: 107 """Remove one extra board; returns the boards as they now stand.""" 108 resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}/boards/{slug}") 109 _raise_for_status(resp) 110 return CompetitionBoardsResponse.model_validate(resp.json())
21class AdminCompetitionBoardsResource: 22 """Add, list, edit, publish and remove one competition's boards. For its 23 organisers (global admins and the event's own competition admins).""" 24 25 def __init__(self, http: BaseHttpClient) -> None: 26 self._http = http 27 28 def list(self, competition_id: str) -> CompetitionBoardsResponse: 29 """Every board, the primary first and the shadow ones included — 30 the organiser's read of ``GET /competitions/{id}/boards``.""" 31 resp = self._http.request("GET", f"/competitions/{competition_id}/boards") 32 _raise_for_status(resp) 33 return CompetitionBoardsResponse.model_validate(resp.json()) 34 35 def create( 36 self, 37 competition_id: str, 38 *, 39 slug: str, 40 rule: ScoringRuleName, 41 params: dict[str, Any] | None = None, 42 title: str | LocalizedText = "", 43 visibility: BoardVisibility = "shadow", 44 position: int = 0, 45 ) -> CompetitionBoardInfo: 46 """Add a board. It starts ``shadow`` — computed on the real facts and 47 read by organisers alone — unless ``visibility="published"``. 48 ``title`` is ``{locale: text}`` (a bare string is read as ``en``).""" 49 payload: dict[str, Any] = { 50 "slug": slug, 51 "rule": rule, 52 "params": params or {}, 53 "title": title, 54 "visibility": visibility, 55 "position": position, 56 } 57 resp = self._http.request( 58 "POST", f"/admin/competitions/{competition_id}/boards", json=payload 59 ) 60 _raise_for_status(resp) 61 return CompetitionBoardInfo.model_validate(resp.json()) 62 63 def update( 64 self, 65 competition_id: str, 66 slug: str, 67 *, 68 rule: ScoringRuleName | None = None, 69 params: dict[str, Any] | None = None, 70 title: str | LocalizedText | None = None, 71 visibility: BoardVisibility | None = None, 72 position: int | None = None, 73 scoring_change_reason: str | None = None, 74 ) -> CompetitionBoardInfo: 75 """Edit one board. Partial: only the arguments you pass are sent. 76 77 ⚠️ ``params`` **replaces** the board's params, and a new ``rule`` 78 without ``params`` starts from that rule's defaults. Once the 79 competition is live, changing a *published* board's scoring needs 80 ``scoring_change_reason``: the change is recorded and shown on the 81 board (``422 scoring_change_needs_reason`` without one). A shadow 82 board is edited freely.""" 83 payload: dict[str, Any] = {} 84 if rule is not None: 85 payload["rule"] = rule 86 if params is not None: 87 payload["params"] = params 88 if title is not None: 89 payload["title"] = title 90 if visibility is not None: 91 payload["visibility"] = visibility 92 if position is not None: 93 payload["position"] = position 94 if scoring_change_reason is not None: 95 payload["scoring_change_reason"] = scoring_change_reason 96 resp = self._http.request( 97 "PATCH", f"/admin/competitions/{competition_id}/boards/{slug}", json=payload 98 ) 99 _raise_for_status(resp) 100 return CompetitionBoardInfo.model_validate(resp.json()) 101 102 def promote(self, competition_id: str, slug: str) -> CompetitionBoardInfo: 103 """Publish a shadow board: put it on the board page's tabs and every 104 surface. Allowed at any time — it adds a standing, it moves none.""" 105 return self.update(competition_id, slug, visibility="published") 106 107 def delete(self, competition_id: str, slug: str) -> CompetitionBoardsResponse: 108 """Remove one extra board; returns the boards as they now stand.""" 109 resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}/boards/{slug}") 110 _raise_for_status(resp) 111 return CompetitionBoardsResponse.model_validate(resp.json())
Add, list, edit, publish and remove one competition's boards. For its organisers (global admins and the event's own competition admins).
28 def list(self, competition_id: str) -> CompetitionBoardsResponse: 29 """Every board, the primary first and the shadow ones included — 30 the organiser's read of ``GET /competitions/{id}/boards``.""" 31 resp = self._http.request("GET", f"/competitions/{competition_id}/boards") 32 _raise_for_status(resp) 33 return CompetitionBoardsResponse.model_validate(resp.json())
Every board, the primary first and the shadow ones included —
the organiser's read of GET /competitions/{id}/boards.
35 def create( 36 self, 37 competition_id: str, 38 *, 39 slug: str, 40 rule: ScoringRuleName, 41 params: dict[str, Any] | None = None, 42 title: str | LocalizedText = "", 43 visibility: BoardVisibility = "shadow", 44 position: int = 0, 45 ) -> CompetitionBoardInfo: 46 """Add a board. It starts ``shadow`` — computed on the real facts and 47 read by organisers alone — unless ``visibility="published"``. 48 ``title`` is ``{locale: text}`` (a bare string is read as ``en``).""" 49 payload: dict[str, Any] = { 50 "slug": slug, 51 "rule": rule, 52 "params": params or {}, 53 "title": title, 54 "visibility": visibility, 55 "position": position, 56 } 57 resp = self._http.request( 58 "POST", f"/admin/competitions/{competition_id}/boards", json=payload 59 ) 60 _raise_for_status(resp) 61 return CompetitionBoardInfo.model_validate(resp.json())
Add a board. It starts shadow — computed on the real facts and
read by organisers alone — unless visibility="published".
title is {locale: text} (a bare string is read as en).
63 def update( 64 self, 65 competition_id: str, 66 slug: str, 67 *, 68 rule: ScoringRuleName | None = None, 69 params: dict[str, Any] | None = None, 70 title: str | LocalizedText | None = None, 71 visibility: BoardVisibility | None = None, 72 position: int | None = None, 73 scoring_change_reason: str | None = None, 74 ) -> CompetitionBoardInfo: 75 """Edit one board. Partial: only the arguments you pass are sent. 76 77 ⚠️ ``params`` **replaces** the board's params, and a new ``rule`` 78 without ``params`` starts from that rule's defaults. Once the 79 competition is live, changing a *published* board's scoring needs 80 ``scoring_change_reason``: the change is recorded and shown on the 81 board (``422 scoring_change_needs_reason`` without one). A shadow 82 board is edited freely.""" 83 payload: dict[str, Any] = {} 84 if rule is not None: 85 payload["rule"] = rule 86 if params is not None: 87 payload["params"] = params 88 if title is not None: 89 payload["title"] = title 90 if visibility is not None: 91 payload["visibility"] = visibility 92 if position is not None: 93 payload["position"] = position 94 if scoring_change_reason is not None: 95 payload["scoring_change_reason"] = scoring_change_reason 96 resp = self._http.request( 97 "PATCH", f"/admin/competitions/{competition_id}/boards/{slug}", json=payload 98 ) 99 _raise_for_status(resp) 100 return CompetitionBoardInfo.model_validate(resp.json())
Edit one board. Partial: only the arguments you pass are sent.
⚠️ params replaces the board's params, and a new rule
without params starts from that rule's defaults. Once the
competition is live, changing a published board's scoring needs
scoring_change_reason: the change is recorded and shown on the
board (422 scoring_change_needs_reason without one). A shadow
board is edited freely.
102 def promote(self, competition_id: str, slug: str) -> CompetitionBoardInfo: 103 """Publish a shadow board: put it on the board page's tabs and every 104 surface. Allowed at any time — it adds a standing, it moves none.""" 105 return self.update(competition_id, slug, visibility="published")
Publish a shadow board: put it on the board page's tabs and every surface. Allowed at any time — it adds a standing, it moves none.
107 def delete(self, competition_id: str, slug: str) -> CompetitionBoardsResponse: 108 """Remove one extra board; returns the boards as they now stand.""" 109 resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}/boards/{slug}") 110 _raise_for_status(resp) 111 return CompetitionBoardsResponse.model_validate(resp.json())
Remove one extra board; returns the boards as they now stand.