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.md for 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 a Club
  • locations: Sessions may be held at a Location
  • matches: Matches are linked to sessions and rounds
  • finance: Session publishing deducts from the club owner's AccountBalance; session revenue is tracked via Income
  • users: RSVPs link to User accounts