Skip to content

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:

  1. ManualMedia instance is deleted
  2. post_delete signal is triggered
  3. file.delete(save=False) is called

Manual deletion

When a Manual is deleted:

  • its cover image is removed from storage

Flow:

  1. Manual instance is deleted
  2. post_delete signal is triggered
  3. cover_image.delete(save=False) is called

Chapter deletion

When a Chapter is deleted:

  • its cover image is removed from storage

Flow:

  1. Chapter instance is deleted
  2. post_delete signal is triggered
  3. 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:

  1. pre_save signal is triggered
  2. existing instance is loaded
  3. old cover is compared to new cover
  4. 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:

  1. pre_save signal is triggered
  2. existing instance is loaded
  3. old cover is compared to new cover
  4. 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]

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.