Attachments
Attachments let participants attach files to a workflow execution — receipts,
signed documents, screenshots or export files. Files are stored through
Django's storage abstraction (FileField), so the package works out of the box
with Django's default local storage and with any other backend the hosting
application configures (S3, Azure, ...) by setting DEFAULT_FILE_STORAGE. No
third-party service is required.
Adding an attachment
Pass any file-like object with a name and size (an UploadedFile from a
request, an in-memory uploaded file, ...):
execution.add_attachment(upload=request.FILES["file"], user=request.user)
Or through the service layer:
from workflow_kit.attachments import add_attachment
attachment = add_attachment(
execution,
upload=uploaded_file,
name="final-quote.pdf", # optional override of the original file name
user=finance_user,
)
An attachment records:
execution— the workflow execution it belongs touploaded_by— who uploaded it (anonymous uploads passNone)file— the stored file (via the default storage backend)name— the display name (original file name unless overridden)content_typeandsize— captured at upload time for listingcreated_at— when it was uploaded
Listing attachments
from workflow_kit.attachments import execution_attachments
for attachment in execution_attachments(execution):
print(attachment.name, attachment.size, attachment.extension)
WorkflowAttachment.extension returns the lower-cased file extension (e.g.
pdf, jpg) without the leading dot. attachment.file.url yields the storage
URL for rendering download links.
Audit and events
Like comments, attachments are part of the execution's history. Uploading a
file appends an attachment_added audit event, emits a
workflow.attachment_added domain event, and appears in
execution.timeline() as an "Attachment added" entry — so external handlers can
watch for file uploads without reaching into the storage backend.
Media configuration
Because attachments write through Django's MEDIA_ROOT, hosting applications
need the standard Django media configuration:
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
and, in development, serve media files (typically with static() in urls.py
behind DEBUG). Production deployments point DEFAULT_FILE_STORAGE at their
object store and keep the same code.