Comprehensive documentation of all data models in the Sport Sessions app
Sport Sessions App Data Models Documentation
Brand values alignment: All features and data models should align with Courtex values: Community, Transparency, Accessibility, Integrity, Humility. Sessions should be easy to join, transparent about capacity, and welcoming to all skill levels. See
docs/markdown/brand/copilot-instructions.mdfor full guidance.
Overview
The Sport Sessions app is the core scheduling and attendance management component of Courtex. It manages sessions (socials, training, tournaments), RSVP lists, waitlists, round-based match generation, and player history tracking.
Core Models
1. Session
The Session model represents a sports event (social, training, or tournament) organised by a club on a specific date.
Key Fields:
- uuid: Unique identifier (non-editable)
- name: Optional session name
- club: Foreign key to clubs.Club (nullable; SET_NULL on delete)
- location: Foreign key to locations.Location (nullable; SET_NULL on delete)
- location_text: Optional freeform text for location (used when no structured location is linked)
- date: Date of the session
- start_time: Session start time (default 20:00)
- end_time: Session end time (default 22:00)
- doubles: Whether doubles matches are played (default True)
- singles: Whether singles matches are played (default False)
- price: Optional per-person price in cents/dollars
- max_attendees: Maximum number of attendees (default 30)
- max_waitlist: Maximum waitlist size (default 10)
- waiting_list_enabled: Whether a waitlist is active (default True)
- notes: Optional markdown-formatted session notes
- hidden: Whether the session is hidden from public view
- include_regulars: Whether regular club members are included by default (default True)
- session_type: Type of session — SOCIAL, TRAINING, TOURNAMENT, or OTHER
- allow_non_users: Whether non-registered users can be added to the RSVP list
- status: Current session status (see Status Choices below)
- completed: Legacy boolean flag (use status instead)
Status Choices:
- CREATED — legacy; use DRAFT instead
- DRAFT — created but not finalised
- PUBLISHED — visible to participants, not yet started
- CONFIRMED — confirmed (24 hours before start; costs deducted)
- IN_PROGRESS — session is underway
- FINALISING — legacy; use COMPLETED instead
- COMPLETED — session has ended
- CANCELLED — session was cancelled
- ARCHIVED — stored for historical reference
Inherited from TimeStampedModel:
- created_at, updated_at, deleted_at
Methods:
- is_full(): Returns True if current RSVP count equals max_attendees
- get_rsvp_count(): Returns count of confirmed RSVPs (list='RSVP')
- get_notes_as_html(): Renders notes field from markdown to HTML
- draft(): Moves session from PUBLISHED back to DRAFT
- publish(): Moves session from DRAFT to PUBLISHED; deducts $0.50 from club owner's account balance; raises ValueError if insufficient funds
- start(): Moves session to IN_PROGRESS
- complete(): Moves session to COMPLETED
- cancel(): Cancels the session (from DRAFT, PUBLISHED, CONFIRMED, or IN_PROGRESS)
- archive(): Moves session from COMPLETED to ARCHIVED
- get_attendees(list, attended): Returns filtered RSVP queryset by list type and optional attendance status
- add_rsvp(user, non_user_name, list, attended, paid, payment_method): Adds a new RSVP to the session; raises ValueError if full or IntegrityError if duplicate
- add_waitlist(user, non_user_name): Adds a user to the waitlist; raises errors if already present or waitlist is full/disabled
Round-Based Match Generation Methods:
- create_new_round(duration): Creates a new Round for the session
- create_player_round_history(round_obj): Creates PlayerMatchHistory records for all attendees in a round
- get_next_round_players(match_type, courts, round_obj): Returns eligible players for the next round ordered by match history
- generate_matches(match_type, courts, rsvps, round_obj, algorithm): Creates Match records for a round; supports rsvp_time and random algorithms
- update_player_match_history(round_obj, rsvps): Updates PlayerMatchHistory to record which players played in a round
- generate_next_round_matches(match_type, courts, algorithm): Convenience method that orchestrates all of the above in sequence
2. SessionTemplate
The SessionTemplate model stores reusable configurations for creating sessions quickly.
Key Fields:
- uuid: Unique identifier (non-editable)
- name: Optional template name
- club: Foreign key to clubs.Club (nullable; SET_NULL on delete)
- location: Foreign key to locations.Location (nullable; SET_NULL on delete)
- start_time: Default start time (default 20:00)
- end_time: Default end time (default 22:00)
- doubles, singles, price, max_attendees, max_waitlist, waiting_list_enabled: Same as Session
- notes, hidden, include_regulars, allow_non_users, session_type: Same as Session
Inherited from TimeStampedModel:
- created_at, updated_at, deleted_at
Methods:
- create_session(date): Creates and returns a new Session instance from this template for the given date
3. RSVP
The RSVP model tracks a user's (or non-user's) attendance registration for a session.
Key Fields:
- session: Foreign key to Session (CASCADE on delete)
- user: Foreign key to users.User (nullable; CASCADE on delete) — for registered users
- non_user_name: Optional name for non-registered attendees (e.g., walk-ins added by an organiser)
- is_attending: Legacy boolean (deprecated; use list instead)
- is_waitlist: Legacy boolean (deprecated; use list instead)
- list: Current list status — RSVP, WAITLIST, or None
- attended: Whether the person actually attended the session
- paid: Whether the person has paid
- payment_method: Payment method — cash, bank (Bank Transfer), or other
Meta:
- unique_together = [['session', 'user', 'non_user_name']] — prevents duplicate registrations
Methods:
- move_to_rsvp(): Moves the RSVP from WAITLIST to RSVP if the session is not full; returns True on success, False if full
- remove(): Sets list to None (removes from RSVP or waitlist); automatically promotes the first waitlist person to RSVP
Business Logic (via save()):
- Raises ValueError if adding an RSVP would exceed max_attendees
Signals:
- capture_list_from (pre_save): Captures the current list value before save so it can be stored in the log
- create_rsvp_log_on_save (post_save): Creates an RSVPLog entry recording the transition
- capture_list_from_delete (pre_delete): Captures list value before deletion
- delete_rsvp_log (post_delete): Creates an RSVPLog entry recording the removal
4. RSVPLog
The RSVPLog model is an audit trail of all RSVP state changes for a session.
Key Fields:
- rsvp: Foreign key to RSVP (nullable; SET_NULL on delete)
- user: Foreign key to users.User (nullable; SET_NULL on delete)
- session: Foreign key to Session (nullable; SET_NULL on delete)
- timestamp: Auto-set datetime when the log entry was created
- list_from: Previous list state (RSVP, WAITLIST, or None)
- list_to: New list state (RSVP, WAITLIST, or None)
Methods:
- get_action_description(): Returns a human-readable description of the transition (e.g., "joined the ATTENDANCE list", "moved from the WAITLIST to the ATTENDANCE list")
Business Logic:
- Created automatically by signals on every RSVP save and delete
5. Round
The Round model tracks individual rounds within a session, used for round-driven socials (e.g., DTBA-style rotation).
Key Fields:
- session: Foreign key to Session (CASCADE on delete)
- round_number: Sequential round number (default 1)
- duration: Duration of the round (default 20 minutes)
- start_time: Time the round started (nullable; set when the round begins)
Methods:
- start(): Sets start_time to the current time
6. PlayerMatchHistory
The PlayerMatchHistory model records whether each player played a match in each round, enabling fair rotation across rounds.
Key Fields:
- session: Foreign key to Session (CASCADE on delete)
- round: Foreign key to Round (nullable; CASCADE on delete)
- round_order: Player's position in the round order (default 99; lower = higher priority next round)
- rsvp: Foreign key to RSVP (nullable; CASCADE on delete)
- user: Foreign key to users.User (CASCADE on delete)
- played_match: Whether this player played a match in this round (default False)
Meta:
- unique_together = [['session', 'round', 'user']]
Business Logic:
- Created by Session.create_player_round_history() at the start of each round
- Updated by Session.update_player_match_history() after matches are generated
- Players who did not play in a round are prioritised in the following round's player ordering
7. Waitlist (Deprecated)
The Waitlist model is a legacy model for managing waitlists. It has been superseded by the RSVP.list field with list='WAITLIST'.
Status: Deprecated — do not use for new development
Key Fields:
- session: Foreign key to Session (CASCADE on delete)
- user: Foreign key to users.User (nullable; CASCADE on delete)
- non_user_name: Optional name for non-registered users
Methods:
- move_to_rsvp(): Creates an RSVP record and deletes the waitlist entry (legacy migration helper)
Relationships Summary
Club ──< Session ──< RSVP ──< RSVPLog
│
├──< SessionTemplate
├──< Round ──< PlayerMatchHistory
└──< matches.Match (via session FK)
Related Apps
clubs: Sessions belong to aClublocations: Sessions may be held at aLocationmatches: Matches are linked to sessions and roundsfinance: Session publishing deducts from the club owner'sAccountBalance; session revenue is tracked viaIncomeusers: RSVPs link toUseraccounts