checkouts implémente un mécanisme d'emprunt de document inspiré des systèmes de gestion de versions (comme SVN). Un utilisateur peut "emprunter" un document pour une durée déterminée, ce qui empêche les autres utilisateurs d'y téléverser de nouveaux fichiers pendant ce temps.
Analogie : c'est le même principe qu'emprunter un livre en bibliothèque — tant qu'il est sorti, personne d'autre ne peut le modifier.
DocumentCheckoutUn seul enregistrement par document emprunté (relation OneToOneField vers Document) :
| Champ | Type | Rôle |
|---|---|---|
document |
OneToOneField → Document | Le document emprunté |
user |
FK → User | L'utilisateur qui a fait l'emprunt |
checkout_datetime |
DateTimeField | Horodatage de l'emprunt (auto) |
expiration_datetime |
DateTimeField | Date/heure d'expiration — doit être dans le futur |
block_new_file |
BooleanField | Si True, bloque le téléversement de nouveaux fichiers par les autres |
La contrainte OneToOneField garantit qu'un document ne peut être emprunté que par un seul utilisateur à la fois.
Quand un DocumentCheckout est supprimé (retour), l'événement émis dépend de qui retourne le document :
| Situation | Événement |
|---|---|
| L'emprunteur retourne lui-même | event_document_checked_in |
| Un autre utilisateur force le retour | event_document_forcefully_checked_in |
| La tâche automatique expire le checkout | event_document_auto_checked_in |
hooks.py)L'app s'injecte dans le flux de téléversement via un hook : avant qu'un nouveau fichier soit accepté pour un document, hook_is_new_file_allowed vérifie :
block_new_file=True → lève NewDocumentFileNotAllowedTéléversement d'un fichier
│
▼
hook_is_new_file_allowed()
│
├── Document non emprunté → ✓ autorisé
├── Document emprunté par moi → ✓ autorisé
└── Document emprunté par quelqu'un d'autre + block_new_file=True → ✗ refusé
Une tâche Celery périodique task_check_expired_check_outs vérifie régulièrement les emprunts expirés et les retourne automatiquement. Elle utilise un verrou distribué (lock_manager) pour éviter les exécutions concurrentes :
# Flux de la tâche
lock = LockingBackend.acquire_lock('task_expired_check_outs')
DocumentCheckout.objects.check_in_expired_check_outs()
# → filtre les checkouts dont expiration_datetime <= now()
# → appelle checkout.delete() sur chacun → émet event_document_auto_checked_in
lock.release()
| Permission | Rôle |
|---|---|
checkout_document |
Emprunter un document |
checkin_document |
Retourner ses propres documents |
checkin_document_override |
Forcer le retour d'un document emprunté par quelqu'un d'autre |
checkout_detail_view |
Voir les détails d'un emprunt |
La séparation checkin_document / checkin_document_override est importante : seul un superviseur devrait avoir le droit de forcer le retour d'un emprunt qui ne lui appartient pas.
Scénario : Alice téléverse la version finale d'un contrat et veut s'assurer que personne ne peut modifier le document pendant sa revue.
1. Alice emprunte le document pour 2 heures
→ DocumentCheckout créé : user=Alice, expiration=+2h, block_new_file=True
2. Bob essaie de téléverser une nouvelle version
→ hook_is_new_file_allowed() → DocumentCheckout existe, user≠Bob, block_new_file=True
→ NewDocumentFileNotAllowed levée → Bob reçoit une erreur
3a. Alice termine et retourne le document manuellement
→ DocumentCheckout.delete(user=Alice) → event_document_checked_in
3b. Ou : les 2 heures s'écoulent
→ task_check_expired_check_outs → checkout.delete() → event_document_auto_checked_in
3c. Ou : un superviseur force le retour
→ DocumentCheckout.delete(user=superviseur) → event_document_forcefully_checked_in
| Fichier | Rôle |
|---|---|
models.py |
DocumentCheckout (OneToOne Document + expiration + block) |
managers.py |
check_in_document(), check_out_document(), expired_check_outs() |
hooks.py |
hook_is_new_file_allowed() — intégration dans le flux de téléversement |
tasks.py |
task_check_expired_check_outs — retour automatique périodique |
permissions.py |
4 permissions dont checkin_document_override pour forcer le retour |