-
Notifications
You must be signed in to change notification settings - Fork 8
Direct Attachment Uploads #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
msc5
wants to merge
82
commits into
main
Choose a base branch
from
msc/direct-upload
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
c1c945a
Add basic vue + uppy functionality
msc5 ff04beb
Working on upload feature
msc5 b48bdfb
Fix CSRFToken issue
msc5 142242f
Add a basic attachments table
msc5 cd6137f
Add view and download buttons/views
msc5 559f33b
Some more convenience things, table updating
msc5 f43afcd
Dockerfile and dep
msc5 e0a1ec7
Add terraform
msc5 f320873
Fix some upload logic
msc5 0bfb623
Add bashrc
msc5 da94bc6
Use config/.env.example
msc5 a4bac20
No uv lock
msc5 9ac3bbe
Add uv.lock
msc5 e653bf1
Build vue
msc5 afcdb80
No dev
msc5 eccd4bc
Move deploy to different dep. group
msc5 c36e183
No-sync
msc5 bbeff26
Use secret not secret version
msc5 98cc3f1
Update secrets, config not managed
msc5 d710232
fix iam
msc5 e241bf1
Add whitenoise
msc5 b59522a
Update AWS_STORAGE_BUCKET_NAME
msc5 4077854
AWS_STORAGE_BUCKET_NAME default
msc5 7dd37d6
S3 bucket settings
msc5 8a1eaef
Remove storage backend
msc5 ccbaa51
djm no sync
msc5 183f46c
New App
msc5 c796a96
Working on form file uploads
msc5 cd02177
Add permissions
msc5 006bb85
Crispy Form
msc5 705add4
Add permissions and roles
msc5 d124984
Work on attachments (need to refactor vue state)
msc5 71db85f
Still working on attachments initial value
msc5 4faef06
Working on state and hydration
msc5 77bc6aa
Ok State pattern
msc5 e886bd5
State and serializers
msc5 5256033
Lots of improvements to linking
msc5 9286e1f
Only show navbar items if user is authenticated
msc5 3f7e692
Add basic delete attachment functionality
msc5 85821db
Selectable
msc5 7a492d8
Bundle everything
msc5 6863a9f
Update selection and bundling
msc5 0c211b3
Some tweaks
msc5 d923cff
Fix bootstrap icons
msc5 646de28
Auto select when uploading
msc5 5cfa6bd
Remove bootstrap icons scss
msc5 3b4668f
Add humanize
msc5 ffd365a
Organization
msc5 b75a23e
Datetime formatting
msc5 5e18e65
Update deps
msc5 ce16246
Remove ruff
msc5 4e7a8e9
Update page titles
msc5 4247070
Add vue and ts_ls
msc5 4e46b5e
Tweaks
msc5 f4731dd
Update props
msc5 c268e7f
tableRowClass
msc5 6f845e9
Remove spaces
msc5 b44cd26
Some bugfixes
msc5 888a946
Fix attachment view/download
msc5 8f21db4
Add github routes
msc5 eb46265
Vite config
msc5 e75edf2
Add a bunch of feature flags
msc5 3c89380
Add feature flags
msc5 18cee79
Add all terraform feature flags
msc5 ab52940
Remove terraform lock files
msc5 b4894cd
Feature flags
msc5 806587e
Remove some dep files
msc5 6175f4a
Remove terraform and bashrc
msc5 96247c6
Remove scss
msc5 6a6c9c5
Working on addressing PR comments
msc5 c0805f1
Fix json script
msc5 5767181
Remove json_script
msc5 a1902fe
Some fixes from review
msc5 c2b241c
Remove unused imports
msc5 41658e4
Merge branch 'main' into msc/direct-upload
msc5 dfc9e98
Work on code review
msc5 50e50b5
Code Review
msc5 5e51a94
Code Review
msc5 c70895b
Add comment
msc5 5f73860
Safer jsonify
msc5 40d575b
Use round-trip json parse
msc5 b4445b4
Switch create_presigned_url to PUT instead of POST
msc5 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "terraform": { | ||
| "staging": { | ||
| "cluster_id": "arn:aws:ecs:us-east-1:745415963933:cluster/deploy-staging-cluster", | ||
| "ecr_image_uri": "745415963933.dkr.ecr.us-east-1.amazonaws.com/deploy-staging-ecr:latest", | ||
| "ecr_repository_name": "deploy-staging-ecr", | ||
| "s3_bucket_name": "deploy-staging-staging-deploy.dev.zagaran.com", | ||
| "web_log_group_name": "deploy-staging-web", | ||
| "web_network_configuration_security_group": "sg-07b0e269a067068e6", | ||
| "web_network_configuration_subnet": "subnet-00553ef2b3e969274", | ||
| "web_service_name": "deploy-staging-web", | ||
| "web_task_definition_arn": "arn:aws:ecs:us-east-1:745415963933:task-definition/deploy-staging-web:2", | ||
| "worker_log_group_name": "deploy-staging-worker", | ||
| "worker_service_name": "deploy-staging-worker", | ||
| "worker_task_desired_count": 0 | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.12 |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from django.contrib import admin | ||
|
|
||
| # Register your models here. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class AppConfig(AppConfig): | ||
| default_auto_field = 'django.db.models.BigAutoField' | ||
| name = 'app' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| SAMPLE_OBJECT_PK_URL_KWARG = "sample_object_id" | ||
|
|
||
| # START_FEATURE direct_upload | ||
| ATTACHMENT_PK_URL_KWARG = "attachment_id" | ||
| # END_FEATURE direct_upload |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| from crispy_forms.helper import Layout | ||
| from crispy_forms.layout import Fieldset | ||
| from django import forms | ||
| from django.http import HttpRequest | ||
| from app.models import Attachment, SampleObject | ||
| from common.fields import DirectUploadFileField | ||
| from common.forms import ActionFormMixin, CrispyFormMixin | ||
|
|
||
|
|
||
| class SampleObjectBaseForm(CrispyFormMixin, ActionFormMixin, forms.ModelForm): | ||
| request: HttpRequest | ||
|
|
||
| # START_FEATURE direct_upload | ||
| attachments = DirectUploadFileField(queryset=Attachment.objects.filter(deleted_on=None), required=False) | ||
| # END_FEATURE direct_upload | ||
|
|
||
| class Meta: | ||
| model = SampleObject | ||
| exclude = ['created_by'] | ||
|
|
||
| layout = Layout( | ||
| Fieldset( | ||
| "Details", | ||
| "name", | ||
| "description" | ||
| ), | ||
| # START_FEATURE direct_upload | ||
| "attachments" | ||
| # END_FEATURE direct_upload | ||
| ) | ||
|
|
||
| def __init__(self, request: HttpRequest, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| self.request = request | ||
|
|
||
|
|
||
| class SampleObjectCreateForm(SampleObjectBaseForm): | ||
| action_title = "Create Sample Object" | ||
|
|
||
| def save(self, commit=True): | ||
| self.instance.created_by = self.request.user | ||
| return super().save(commit) | ||
|
|
||
|
|
||
| class SampleObjectEditForm(SampleObjectBaseForm): | ||
| action_title = "Edit {instance}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Generated by Django 5.2.12 on 2026-03-16 18:26 | ||
|
|
||
| import common.models | ||
| import uuid | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| initial = True | ||
|
|
||
| dependencies = [ | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name='Attachment', | ||
| fields=[ | ||
| ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||
| ('created_on', models.DateTimeField(auto_now_add=True)), | ||
| ('updated_on', models.DateTimeField(auto_now=True)), | ||
| ('name', models.CharField(max_length=512)), | ||
| ('file', models.FileField(max_length=1024, upload_to=common.models.get_upload_prefix)), | ||
| ('upload_completed_on', models.DateTimeField(null=True)), | ||
| ('deleted_on', models.DateTimeField(null=True)), | ||
| ], | ||
| options={ | ||
| 'abstract': False, | ||
| }, | ||
| ), | ||
| migrations.CreateModel( | ||
| name='SampleObject', | ||
| fields=[ | ||
| ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||
| ('created_on', models.DateTimeField(auto_now_add=True)), | ||
| ('updated_on', models.DateTimeField(auto_now=True)), | ||
| ('name', models.CharField(max_length=512, unique=True)), | ||
| ('description', models.TextField(blank=True, default='')), | ||
| ], | ||
| options={ | ||
| 'abstract': False, | ||
| }, | ||
| ), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Generated by Django 5.2.12 on 2026-03-16 18:26 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| initial = True | ||
|
|
||
| dependencies = [ | ||
| ('app', '0001_initial'), | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='attachment', | ||
| name='user', | ||
| field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='files', to=settings.AUTH_USER_MODEL), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='sampleobject', | ||
| name='attachments', | ||
| field=models.ManyToManyField(related_name='sample_objects', to='app.attachment'), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='sampleobject', | ||
| name='created_by', | ||
| field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sample_objects', to=settings.AUTH_USER_MODEL), | ||
| ), | ||
| ] |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from django.db import models | ||
|
|
||
| from common.models import TimestampedModel, UploadFile, User | ||
|
|
||
|
|
||
| class SampleObject(TimestampedModel): | ||
| created_by = models.ForeignKey(User, related_name="sample_objects", on_delete=models.PROTECT) | ||
|
|
||
| # START_FEATURE direct_upload | ||
| attachments = models.ManyToManyField("Attachment", related_name="sample_objects") | ||
| # END_FEATURE direct_upload | ||
|
|
||
| name = models.CharField(max_length=512, unique=True) | ||
| description = models.TextField(default="", blank=True) | ||
|
|
||
| def __str__(self) -> str: | ||
| return f'Sample Object {self.name}' | ||
|
|
||
| def get_attachments(self): | ||
| qs = self.attachments.prefetch_related('user') | ||
| return [ | ||
| attachment for attachment in qs | ||
| if not attachment.deleted_on and attachment.upload_completed_on | ||
| ] | ||
|
|
||
|
|
||
| # START_FEATURE direct_upload | ||
| class Attachment(UploadFile): | ||
| pass | ||
| # END_FEATURE direct_upload | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # START_FEATURE direct_upload | ||
| from app.constants import ATTACHMENT_PK_URL_KWARG | ||
| from django.utils.formats import date_format | ||
| from common.serializers import UserSerializer | ||
| from django.urls import reverse | ||
| from rest_framework import serializers | ||
|
|
||
| from app.models import Attachment | ||
|
|
||
|
|
||
| class AttachmentSerializer(serializers.ModelSerializer): | ||
| user = UserSerializer() | ||
| view_url = serializers.SerializerMethodField() | ||
| download_url = serializers.SerializerMethodField() | ||
| delete_url = serializers.SerializerMethodField() | ||
| created_on = serializers.SerializerMethodField() | ||
| upload_completed_on = serializers.SerializerMethodField() | ||
| size = serializers.SerializerMethodField() | ||
| path = serializers.SerializerMethodField() | ||
|
|
||
| class Meta: | ||
| model = Attachment | ||
| exclude = [] | ||
|
|
||
| def get_view_url(self, instance): | ||
| return reverse('attachment_open', kwargs={ATTACHMENT_PK_URL_KWARG: instance.id}) | ||
|
|
||
| def get_download_url(self, instance): | ||
| return reverse('attachment_download', kwargs={ATTACHMENT_PK_URL_KWARG: instance.id}) | ||
|
|
||
| def get_delete_url(self, instance): | ||
| return reverse('attachment_delete', kwargs={ATTACHMENT_PK_URL_KWARG: instance.id}) | ||
|
|
||
| def get_created_on(self, instance): | ||
| return date_format(instance.created_on, format="DATETIME_FORMAT") | ||
|
|
||
| def get_upload_completed_on(self, instance): | ||
| return date_format(instance.upload_completed_on, format="DATETIME_FORMAT") | ||
|
|
||
| def get_size(self, instance): | ||
| return instance.file.size | ||
|
|
||
| def get_path(self, instance): | ||
| return instance.file.name | ||
| # END_FEATURE direct_upload |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| {% extends "base_templates/base.html" %} | ||
| {% load vue %} | ||
|
|
||
|
|
||
| {% block title %}Dashboard{% endblock %} | ||
|
|
||
| {% block head %} | ||
|
|
||
| {% endblock %} | ||
|
msc5 marked this conversation as resolved.
|
||
|
|
||
| {% block body %} | ||
| <div id="app"> | ||
| <h2>Dashboard</h2> | ||
| <p>Hello <b>{{ user.email }}</b>!</p> | ||
|
|
||
| <div class="my-4 card"> | ||
| <div class="card-body"> | ||
| <div class="d-flex gap-3"> | ||
| <h3 class="card-title">Sample Objects</h3> | ||
| <a href="{% url 'sample-object-create' %}"> | ||
| <button class="btn btn-sm btn-success"> | ||
| <i class="bi bi-plus"></i> Create | ||
| </button> | ||
| </a> | ||
| </div> | ||
| <div class="my-2"> | ||
| {% if sample_objects %} | ||
| <table class="table"> | ||
| <thead> | ||
| <tr> | ||
| <th>Name</th> | ||
| <th>Created By</th> | ||
| <th>Created On</th> | ||
| {# START_FEATURE direct_upload #} | ||
| <th>Attachments</th> | ||
| {# END_FEATURE direct_upload #} | ||
| <th>Actions</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {% for sample_object in sample_objects %} | ||
| <tr> | ||
| <td>{{ sample_object.name }}</td> | ||
| <td>{{ sample_object.created_by }}</td> | ||
| <td>{{ sample_object.created_on }}</td> | ||
| {# START_FEATURE direct_upload #} | ||
| <td> | ||
| {% for attachment in sample_object.get_attachments %} | ||
| <span class="badge bg-info">{{ attachment.name }}</span> | ||
| {% endfor %} | ||
| </td> | ||
| <td> | ||
| <div class="d-flex gap-2"> | ||
| <a href="{% url 'sample-object' sample_object.id %}"> | ||
| <button class="btn btn-sm btn-secondary">View</button> | ||
| </a> | ||
| <a href="{% url 'sample-object-edit' sample_object.id %}"> | ||
| <button class="btn btn-sm btn-secondary">Edit</button> | ||
| </a> | ||
| </div> | ||
| </td> | ||
| {# END_FEATURE direct_upload #} | ||
| </tr> | ||
| {% endfor %} | ||
| </tbody> | ||
| </table> | ||
| {% else %} | ||
| <div class="alert alert-light"> | ||
| <i class="bi bi-exclamation-square me-2"></i> | ||
| No Objects | ||
| </div> | ||
| {% endif %} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {# START_FEATURE direct_upload #} | ||
| <div class="my-4 card"> | ||
| <div class="card-body"> | ||
| <h3 class="card-title">All Attachments</h3> | ||
| <file-upload-dashboard | ||
| :files="{{ attachments|jsonify }}" | ||
| storage-backend="{{ settings.DEFAULT_STORAGE_TYPE }}" | ||
| upload-start-url="{% url 'attachment_upload_start' %}" | ||
| :multiple="true" | ||
| ></file-upload-dashboard> | ||
| </div> | ||
| </div> | ||
| {# END_FEATURE direct_upload #} | ||
| </div> | ||
| {% endblock %} | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.