Image Upload Functionality¶
Overview¶
This document explains how image uploads work in Arctyk ITSM comments and tickets, and how it compares to Jira's approach.
How Jira Handles Uploads¶
Images in Comments/Descriptions¶
- Direct insertion: Users drag/drop, paste, or click "Insert Image" in comment editor
- Hosted on Jira: Images are uploaded to Jira servers and stored with the issue
- Markdown/Wiki syntax: References like
![image.jpg]or URL-based embed - Permissions: Only authorized users can upload; file types restricted (no executables, etc.)
Attachments (File Upload)¶
- Separate from body: Files are attached independently from comment text
- UI: File upload widget shows list of attachments, separate from rich text editor
- Permissions: Attachment download requires read access to issue
- Retention: Files stored indefinitely; can be downloaded or deleted by authorized users
Audit Trail¶
- Upload logged with: User, Timestamp, File size, IP address
- Appears in activity feed: "User uploaded filename.pdf"
- Change history tracks attachment additions/removals
Arctyk Implementation¶
Phase 1: Image Upload (NOW IMPLEMENTED ✅)¶
Features:
- ✅ Drag/drop images directly into TinyMCE editors (comments, descriptions, inline edits)
- ✅ Paste images from clipboard
- ✅ Click "Insert → Image" button in toolbar
- ✅ Images stored in
/media/uploads/comments/{YEAR}/on server - ✅ File validation:
.jpg,.jpeg,.png,.gif,.webp,.svgonly - ✅ Size limit: 5MB per image (configurable in
image_uploadview) - ✅ Authentication required: Only logged-in users can upload
- ✅ Automatic audit logging: ChangeLog captures comment creation with images
File Structure:
media/
uploads/
comments/
2024/
john_smith_20240104_143022_a1b2c3d4.png
jane_doe_20240104_150315_e5f6g7h8.jpg
Configuration (in src/config/settings.py):
TINYMCE_DEFAULT_CONFIG = {
...
"images_upload_url": "/tickets/image-upload/",
"images_upload_credentials": True,
}
API Endpoint (in src/tickets/views.py):
- POST
/tickets/image-upload/- Upload single image - Requires: Authentication (
@login_required) - Content-Type:
multipart/form-datawithfilefield - Response:
{ "location": "/media/uploads/comments/2024/username_timestamp_uuid.jpg" }
TinyMCE Integration:
- Images inserted via TinyMCE's "Insert Image" button or drag/drop
- Uses
/media/uploads/comments/{YEAR}/URLs - Images are embedded as
<img>tags in comment HTML
Phase 2: File Attachments (Future - Optional)¶
If Jira-like file attachment feature is desired:
Proposed Model:
class CommentAttachment(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='attachments')
file = models.FileField(upload_to='attachments/comments/%Y/%m/')
uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
uploaded_at = models.DateTimeField(auto_now_add=True)
file_size = models.BigIntegerField()
file_type = models.CharField(max_length=20) # .pdf, .docx, .zip, etc.
Benefits:
- Separate from comment body text
- Better for large files (PDFs, documents, spreadsheets)
- File listing UI in comment details
- Download/delete operations
- Storage tracking
Usage¶
For End Users¶
In Comment Editor:
- Type your comment text
- To add an image, one of:
- Drag/drop image from file explorer into the editor
- Paste image from clipboard (Ctrl+V / Cmd+V)
- Click Insert → Image in toolbar, select image file
- Image automatically uploads and embeds in comment
- Submit comment - image is saved with the comment
In Ticket Description:
- Same process: drag/drop, paste, or click toolbar button
In Inline Edit (Description):
- Same process for quick edits
For Administrators¶
File Storage Location:
# On server
/code/media/uploads/comments/2024/
# Accessible via web
http://example.com/media/uploads/comments/2024/john_smith_20240104_143022_uuid.jpg
Cleanup (if needed):
# Remove old images (older than 30 days)
find /code/media/uploads/comments -type f -mtime +30 -delete
File Size Limit: Change in src/tickets/views.py → image_upload() function:
Allowed File Types: Change in src/tickets/views.py → image_upload() function:
Security Considerations¶
✅ Currently Implemented¶
- Authentication: Only logged-in users can upload
- File validation: Only image formats allowed
- Size limit: 5MB max (prevents abuse)
- File naming: User + timestamp + UUID (prevents overwrites)
- No execution: Images stored in safe directory (not in CODE path)
⚠️ Future Improvements¶
- Virus scanning: Integrate ClamAV or similar for malware detection
- Rate limiting: Limit uploads per user/hour (prevent spam)
- Quota: Per-user or per-ticket storage quota
- CDN: Consider CloudFront or similar for large deployments
Comparison: Jira vs Arctyk¶
| Feature | Jira | Arctyk (Current) | Arctyk (Optional) |
|---|---|---|---|
| Image in Comments | ✅ Yes | ✅ Yes | — |
| Image in Descriptions | ✅ Yes | ✅ Yes | — |
| Drag/Drop | ✅ Yes | ✅ Yes | — |
| Paste from Clipboard | ✅ Yes | ✅ Yes | — |
| File Attachments | ✅ Yes (separate) | ❌ No | ✅ (planned) |
| Attachment List UI | ✅ Yes | ❌ No | ✅ (planned) |
| Download Files | ✅ Yes | N/A | ✅ (planned) |
| Virus Scanning | ✅ Yes (Enterprise) | ❌ No | ✅ (planned) |
| Audit Trail | ✅ Yes (uploads logged) | ✅ Yes (via ChangeLog) | ✅ (with attachments) |
Testing¶
Manual Test Steps¶
-
Create/Edit Comment:
-
Navigate to ticket detail page
- Scroll to "Add Comment" form
- Drag an image file into the editor
- Verify image appears inline
- Submit comment
-
Verify image persists in saved comment
-
Test Inline Edit:
-
Click "Edit" on ticket description
- Drag image into editor
- Click save
-
Verify image appears in description
-
Test Edit Comment:
-
Click "Edit" on existing comment
- Add another image to the comment
- Submit
-
Verify both images are shown
-
Size/Type Validation:
- Try uploading non-image file (.pdf, .txt) → should fail
- Try uploading very large image (>5MB) → should fail
- Try uploading .jpg, .png, .gif → should succeed
Automated Tests (TODO)¶
Consider adding test cases to src/tickets/tests/:
test_image_upload_success()- Valid image uploadtest_image_upload_large_file()- Reject oversized filetest_image_upload_invalid_type()- Reject non-imagetest_image_upload_unauthenticated()- Reject non-logged-in user
Production Deployment¶
Docker/Gunicorn Setup¶
For production, media files are served via:
Option 1: WhiteNoise (Current)
- Already configured in
STATIC_ROOTsettings - Update Django to serve media via WhiteNoise (if needed)
Option 2: Nginx Reverse Proxy
Option 3: S3/CloudFront (AWS)
# settings.py
if not DEBUG:
AWS_STORAGE_BUCKET_NAME = "my-bucket"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/"
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
Volumes/Persistence¶
Ensure Docker volume mounts media directory:
Future Enhancements¶
- Image Optimization: Auto-resize large images, create thumbnails
- Image Gallery: UI to browse/manage uploaded images
- File Attachments: Separate file upload field for non-image files
- Virus Scanning: ClamAV integration for malware detection
- Rate Limiting: Prevent upload spam
- Inline Previews: Show preview of uploaded images before submit
- Image Metadata: Extract and display EXIF data (if present)
- External Storage: S3, Azure Blob, Google Cloud Storage support
Changelog Integration¶
All image uploads are automatically logged in the ChangeLog system:
# When comment with image is created:
ChangeLog.objects.create(
content_type=ContentType.objects.get_for_model(Comment),
object_id=comment.id,
changed_by=request.user,
change_summary="Created comment with image",
timestamp=timezone.now(),
ip_address=get_client_ip(request),
)
This ensures:
- ✅ Who uploaded the image (user)
- ✅ When it was uploaded (timestamp)
- ✅ From where (IP address)
- ✅ What changed (comment with image content)
Questions?¶
For more details on image handling or to request file attachment support, contact the development team.