Signals¶
The manuals app uses Django signals to handle file lifecycle management.
These signals ensure that uploaded files do not remain orphaned in storage when records are deleted or updated.
Purpose¶
Signals are used for:
- cleaning up files when models are deleted
- removing replaced files when updated
- preventing storage leaks
- keeping storage consistent with database state
Covered models¶
Signals are defined for:
- ManualMedia
- Manual
- Chapter
Delete behavior¶
ManualMedia deletion¶
When a ManualMedia instance is deleted:
- its associated file is removed from storage
Flow:
- ManualMedia instance is deleted
- post_delete signal is triggered
- file.delete(save=False) is called
Manual deletion¶
When a Manual is deleted:
- its cover image is removed from storage
Flow:
- Manual instance is deleted
- post_delete signal is triggered
- cover_image.delete(save=False) is called
Chapter deletion¶
When a Chapter is deleted:
- its cover image is removed from storage
Flow:
- Chapter instance is deleted
- post_delete signal is triggered
- cover_image.delete(save=False) is called
Update behavior¶
Manual cover replacement¶
When a Manual is updated:
- if the cover image changes, the old file is deleted
Flow:
- pre_save signal is triggered
- existing instance is loaded
- old cover is compared to new cover
- if different → old file is deleted
Chapter cover replacement¶
When a Chapter is updated:
- if the cover image changes, the old file is deleted
Flow:
- pre_save signal is triggered
- existing instance is loaded
- old cover is compared to new cover
- if different → old file is deleted
Mermaid flow¶
Delete flow¶
flowchart TD
A[Model deleted] --> B[post_delete signal]
B --> C{Has file?}
C -->|Yes| D[Delete file from storage]
C -->|No| E[Do nothing]
¶
flowchart TD
A[Model deleted] --> B[post_delete signal]
B --> C{Has file?}
C -->|Yes| D[Delete file from storage]
C -->|No| E[Do nothing]
Update flow¶
flowchart TD
A[Model save triggered] --> B[pre_save signal]
B --> C{Instance exists?}
C -->|No| D[New object → skip]
C -->|Yes| E[Load old instance]
E --> F{File changed?}
F -->|Yes| G[Delete old file]
F -->|No| H[Do nothing]
Why signals are used¶
Signals are appropriate here because:
- file cleanup is model lifecycle related
- it must happen regardless of how the model is modified
- it keeps storage logic centralized and automatic
What signals do not handle¶
Signals do not:
- validate uploads
- enforce permissions
- handle business logic
- manage content relationships (e.g. removing references in HTML/JSON)
Those concerns belong to:
- serializers → validation
- views → request handling
- services → business logic
Relationship to services¶
There is an important distinction:
Signals¶
- low-level lifecycle hooks
- automatic cleanup
- always executed
Services¶
- explicit business operations
- user-triggered workflows
- cross-model logic
Example:
- deleting a file from storage → signal
- cleaning references in HTML/JSON → service
Design principles¶
- keep signals small and predictable
- avoid complex logic inside signals
- do not call external services from signals
- ensure signals are idempotent
- limit signals to infrastructure concerns
Best practices¶
- always guard against missing files
- avoid database-heavy operations inside signals
- test file replacement scenarios explicitly
- ensure signals are registered in apps.py
Future considerations¶
Possible improvements:
- move repeated logic into reusable helper functions
- add logging for file deletion failures
- introduce soft-delete support if needed
- extend cleanup for additional file fields
Summary¶
Signals in the manuals app ensure:
- files are deleted when records are removed
- old files are cleaned up on update
- storage remains consistent
They provide a reliable safety net for file lifecycle management, while keeping business logic in the service layer.