Files
team-tryouts/app/models/document.py
T
cedrick2711 5249331661
CI - Security, Lint & Tests / validate (push) Failing after 1m7s
test docu, non fonctionnel pour l'instant
2026-09-15 12:28:12 -04:00

174 lines
7.0 KiB
Python

"""Documents, versions, and per-user assignments.
The old contract flow stored one uploaded file per player even when the
file was identical for everyone, and Drive received no team/role structure
at all. This module replaces that with three two-level concepts:
- ``Document`` the canonical document (e.g. ``Contrat joueur``).
One row per kind; the file is stored once, never
once per person.
- ``DocumentVersion`` an immutable published revision. Uploading a new
PDF is publishing a new version, which is the twice
a year update cycle.
- ``DocumentAssignment`` a per-person obligation. ``UNIQUE(document_id,
user_id)`` is what guarantees a player signs a
document exactly once, whatever the number of teams
they sit on.
Signing obligations are role-scoped, not team-scoped: the team folder in
Drive is a *filing* concern for the signed copy; the obligation itself is
created once per eligible person.
"""
from app.extensions import db
from app.time_utils import utc_now_naive
#: Every document kind this application can hold.
DOCUMENT_KINDS = (
'player',
'coach',
'manager',
'team',
'code_of_conduct',
'ethics_code',
)
#: Kinds whose completion is an uploaded signed PDF.
SIGNATURE_KINDS = ('player', 'coach', 'manager', 'team')
#: Kinds whose completion is read-and-acknowledge only (no file).
ACKNOWLEDGMENT_KINDS = ('code_of_conduct', 'ethics_code')
#: Kind -> set of roles eligible to complete it. ``team`` and the two
#: codes span roles; the three role contracts are single-role by nature.
DOCUMENT_KIND_ROLES = {
'player': ('player',),
'coach': ('coach',),
'manager': ('manager',),
'team': ('player', 'coach', 'manager'),
'code_of_conduct': ('player', 'coach', 'manager'),
'ethics_code': ('player', 'coach', 'manager'),
}
#: Possible values for DocumentAssignment.status.
ASSIGNMENT_STATUSES = ('pending', 'signed', 'acknowledged')
class Document(db.Model):
"""A canonical document, stored once, completed by eligible people."""
__tablename__ = 'documents'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
kind = db.Column(db.String(30), nullable=False, default='player')
#: Admins choose when the document is downloadable by team members. The
#: old ``document_uploads_enabled`` global toggle is replaced by this
#: per-document switch for the new flow.
is_published = db.Column(db.Boolean, nullable=False, default=False)
current_version_id = db.Column(db.Integer, db.ForeignKey('document_versions.id'), nullable=True)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
created_at = db.Column(db.DateTime, default=utc_now_naive)
versions = db.relationship(
'DocumentVersion',
backref='document',
lazy='dynamic',
order_by='DocumentVersion.version',
foreign_keys='DocumentVersion.document_id',
)
current_version = db.relationship(
'DocumentVersion',
foreign_keys=[current_version_id],
uselist=False,
)
assignments = db.relationship(
'DocumentAssignment',
back_populates='document',
lazy='dynamic',
order_by='DocumentAssignment.created_at',
foreign_keys='DocumentAssignment.document_id',
)
creator = db.relationship('User', foreign_keys=[created_by])
@property
def requires_signature(self):
"""Whether completion means an uploaded signed PDF."""
return self.kind in SIGNATURE_KINDS
def completion_counts(self):
"""(completed, pending) over every assignment of this document.
``completed`` covers both signed and acknowledged assignments.
"""
rows = (
db.session.query(DocumentAssignment.status, db.func.count())
.filter(DocumentAssignment.document_id == self.id)
.group_by(DocumentAssignment.status)
.all()
)
totals = dict(rows)
completed = totals.get('signed', 0) + totals.get('acknowledged', 0)
return completed, totals.get('pending', 0)
def next_version_number(self):
"""The version number a freshly published revision should carry."""
latest = self.versions.order_by(DocumentVersion.version.desc()).first()
return (latest.version + 1) if latest else 1
class DocumentVersion(db.Model):
"""One immutable published revision of a document."""
__tablename__ = 'document_versions'
id = db.Column(db.Integer, primary_key=True)
document_id = db.Column(db.Integer, db.ForeignKey('documents.id'), nullable=False)
version = db.Column(db.Integer, nullable=False)
original_filename = db.Column(db.String(255), nullable=False)
stored_filename = db.Column(db.String(255), nullable=False)
#: ``gdrive://<file-id>`` for Drive storage, or a path relative to the
#: document root for the local backend.
file_path = db.Column(db.String(500), nullable=False)
notes = db.Column(db.Text, nullable=True)
published_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
published_at = db.Column(db.DateTime, default=utc_now_naive)
publisher = db.relationship('User', foreign_keys=[published_by])
__table_args__ = (
db.UniqueConstraint('document_id', 'version', name='unique_document_version'),
)
class DocumentAssignment(db.Model):
"""One person's obligation to sign or acknowledge a document."""
__tablename__ = 'document_assignments'
id = db.Column(db.Integer, primary_key=True)
document_id = db.Column(db.Integer, db.ForeignKey('documents.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
completed_version_id = db.Column(
db.Integer, db.ForeignKey('document_versions.id'), nullable=True
)
status = db.Column(db.String(20), nullable=False, default='pending')
signed_filename = db.Column(db.String(255), nullable=True)
signed_file_path = db.Column(db.String(500), nullable=True)
completed_at = db.Column(db.DateTime, nullable=True)
#: The team whose Drive folder files the signed copy. A member on
#: several teams still files under one canonical team only.
filed_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
created_at = db.Column(db.DateTime, default=utc_now_naive)
document = db.relationship('Document', back_populates='assignments', foreign_keys=[document_id])
user = db.relationship('User', foreign_keys=[user_id], backref='document_assignments')
completed_version = db.relationship('DocumentVersion', foreign_keys=[completed_version_id])
filed_team = db.relationship('OrgTeam', foreign_keys=[filed_team_id])
__table_args__ = (db.UniqueConstraint('document_id', 'user_id', name='unique_document_user'),)
@property
def completed(self):
return self.status in ('signed', 'acknowledged')