Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c1c945a
Add basic vue + uppy functionality
msc5 Jan 27, 2026
ff04beb
Working on upload feature
msc5 Jan 27, 2026
b48bdfb
Fix CSRFToken issue
msc5 Mar 2, 2026
142242f
Add a basic attachments table
msc5 Mar 2, 2026
cd6137f
Add view and download buttons/views
msc5 Mar 2, 2026
559f33b
Some more convenience things, table updating
msc5 Mar 2, 2026
f43afcd
Dockerfile and dep
msc5 Mar 3, 2026
e0a1ec7
Add terraform
msc5 Mar 3, 2026
f320873
Fix some upload logic
msc5 Mar 3, 2026
0bfb623
Add bashrc
msc5 Mar 3, 2026
da94bc6
Use config/.env.example
msc5 Mar 3, 2026
a4bac20
No uv lock
msc5 Mar 3, 2026
9ac3bbe
Add uv.lock
msc5 Mar 3, 2026
e653bf1
Build vue
msc5 Mar 3, 2026
afcdb80
No dev
msc5 Mar 3, 2026
eccd4bc
Move deploy to different dep. group
msc5 Mar 3, 2026
c36e183
No-sync
msc5 Mar 3, 2026
bbeff26
Use secret not secret version
msc5 Mar 3, 2026
98cc3f1
Update secrets, config not managed
msc5 Mar 3, 2026
d710232
fix iam
msc5 Mar 3, 2026
e241bf1
Add whitenoise
msc5 Mar 3, 2026
b59522a
Update AWS_STORAGE_BUCKET_NAME
msc5 Mar 3, 2026
4077854
AWS_STORAGE_BUCKET_NAME default
msc5 Mar 3, 2026
7dd37d6
S3 bucket settings
msc5 Mar 3, 2026
8a1eaef
Remove storage backend
msc5 Mar 3, 2026
ccbaa51
djm no sync
msc5 Mar 3, 2026
183f46c
New App
msc5 Mar 4, 2026
c796a96
Working on form file uploads
msc5 Mar 4, 2026
cd02177
Add permissions
msc5 Mar 5, 2026
006bb85
Crispy Form
msc5 Mar 5, 2026
705add4
Add permissions and roles
msc5 Mar 5, 2026
d124984
Work on attachments (need to refactor vue state)
msc5 Mar 5, 2026
71db85f
Still working on attachments initial value
msc5 Mar 5, 2026
4faef06
Working on state and hydration
msc5 Mar 6, 2026
77bc6aa
Ok State pattern
msc5 Mar 6, 2026
e886bd5
State and serializers
msc5 Mar 6, 2026
5256033
Lots of improvements to linking
msc5 Mar 6, 2026
9286e1f
Only show navbar items if user is authenticated
msc5 Mar 6, 2026
3f7e692
Add basic delete attachment functionality
msc5 Mar 6, 2026
85821db
Selectable
msc5 Mar 8, 2026
7a492d8
Bundle everything
msc5 Mar 8, 2026
6863a9f
Update selection and bundling
msc5 Mar 8, 2026
0c211b3
Some tweaks
msc5 Mar 8, 2026
d923cff
Fix bootstrap icons
msc5 Mar 8, 2026
646de28
Auto select when uploading
msc5 Mar 8, 2026
5cfa6bd
Remove bootstrap icons scss
msc5 Mar 8, 2026
3b4668f
Add humanize
msc5 Mar 9, 2026
ffd365a
Organization
msc5 Mar 9, 2026
b75a23e
Datetime formatting
msc5 Mar 9, 2026
5e18e65
Update deps
msc5 Mar 10, 2026
ce16246
Remove ruff
msc5 Mar 10, 2026
4e7a8e9
Update page titles
msc5 Mar 10, 2026
4247070
Add vue and ts_ls
msc5 Mar 10, 2026
4e46b5e
Tweaks
msc5 Mar 10, 2026
f4731dd
Update props
msc5 Mar 10, 2026
c268e7f
tableRowClass
msc5 Mar 10, 2026
6f845e9
Remove spaces
msc5 Mar 10, 2026
b44cd26
Some bugfixes
msc5 Mar 11, 2026
888a946
Fix attachment view/download
msc5 Mar 11, 2026
8f21db4
Add github routes
msc5 Mar 11, 2026
eb46265
Vite config
msc5 Mar 16, 2026
e75edf2
Add a bunch of feature flags
msc5 Mar 16, 2026
3c89380
Add feature flags
msc5 Mar 16, 2026
18cee79
Add all terraform feature flags
msc5 Mar 16, 2026
ab52940
Remove terraform lock files
msc5 Mar 16, 2026
b4894cd
Feature flags
msc5 Mar 17, 2026
806587e
Remove some dep files
msc5 Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .bashrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# START_FEATURE ecs
# If we're on prod, make the terminal red
if [ "$DEPLOY_ENVIRONMENT" == "prod" ]; then
export PS1="\[\e[31m\]\u@\h:\w\$ \[\e[0m\]"
fi

alias djm="uv run --no-sync manage.py"
# END_FEATURE ecs
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ venv.bak/
# Installed packages
node_modules/

# ECS deploy script
.deploy/
.git-copy/

# Compiled Files
static/js/dist*
staticfiles/
staticfiles/*
# START_FEATURE sass_bootstrap
Expand All @@ -64,3 +69,8 @@ staticfiles/*
static/webpack_bundles/
webpack-stats.json
# END_FEATURE django_react

.terraform

# Locally uploaded files
uploads/
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
67 changes: 48 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
# START_FEATURE docker
FROM python:3.11.4-slim-buster
# START_FEATURE ecs
# START_FEATURE vue
# ----------------------------------- NPM ------------------------------------ #

FROM node:24-slim AS node-deps

WORKDIR /app

# Install required system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends bzip2

# Add only files needed for dependency installation
COPY package.json ./

# Install Node dependencies with caching
RUN npm install && npm cache clean --force

# Build vue dist
COPY . /app/
RUN npm run vue-build

# END_FEATURE vue
# ---------------------------------- Python ---------------------------------- #

FROM python:3.12.13-slim-trixie

# Set working directory
WORKDIR /app

ADD requirements.txt /app/requirements.txt

# Install system and python dependencies
RUN set -ex \
&& buildDeps=" \
build-essential \
libpq-dev \
" \
&& deps=" \
postgresql-client \
git \
# (editing)
vim \
# (debug)
curl \
htop \
" \
&& apt-get update && apt-get install -y $buildDeps $deps --no-install-recommends \
&& apt-get update \
&& apt-get install -y $buildDeps $deps --no-install-recommends \
&& pip install --no-cache-dir -r /app/requirements.txt \
&& apt-get purge -y --auto-remove $buildDeps \
$(! command -v gpg > /dev/null || echo 'gnupg dirmngr') \
Expand All @@ -22,29 +55,25 @@ RUN set -ex \
ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH

# START_FEATURE django_react
COPY ./nwb.config.js /app/nwb.config.js
COPY ./package.json /app/package.json
COPY ./package-lock.json /app/package-lock.json
RUN npm install
# END_FEATURE django_react
# START_FEATURE vue
# Copy Node dependencies from node-deps stage
COPY --from=node-deps /app/node_modules /app/node_modules
COPY --from=node-deps /app/package*.json /app/
COPY --from=node-deps /app/static/js/dist/ /app/static/js/dist/
# END_FEATURE vue

# Copy application files and .env.example
COPY . /app/
COPY ./config/.env.example /app/config/.env
COPY .bashrc /root/

# START_FEATURE django_react
RUN ./node_modules/.bin/nwb build --no-vendor
# END_FEATURE django_react

# Compile static assets
# START_FEATURE sass_bootstrap
RUN python manage.py compilescss
# END_FEATURE sass_bootstrap

RUN python manage.py collectstatic --noinput

RUN rm /app/config/.env

EXPOSE 8000

CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "config.wsgi:application"]
# END_FEATURE docker
EXPOSE 8080
CMD ["gunicorn", "--bind", ":8080", "--workers", "15", "config.wsgi:application"]
# END_FEATURE ecs
Empty file added app/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions app/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions app/apps.py
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'
5 changes: 5 additions & 0 deletions app/constants.py
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
46 changes: 46 additions & 0 deletions app/forms.py
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}"
44 changes: 44 additions & 0 deletions app/migrations/0001_initial.py
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,
},
),
]
33 changes: 33 additions & 0 deletions app/migrations/0002_initial.py
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 added app/migrations/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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):
return self.attachments.filter(deleted_on=None)


# START_FEATURE direct_upload
class Attachment(UploadFile):
pass
# END_FEATURE direct_upload
35 changes: 35 additions & 0 deletions app/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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()

class Meta:
model = Attachment
exclude = []

def to_representation(self, instance):
rep = super().to_representation(instance)
rep["view_url"] = reverse('attachment_open', kwargs={
ATTACHMENT_PK_URL_KWARG: instance.id,
})
rep["download_url"] = reverse('attachment_download', kwargs={
ATTACHMENT_PK_URL_KWARG: instance.id,
})
rep["delete_url"] = reverse('attachment_delete', kwargs={
ATTACHMENT_PK_URL_KWARG: instance.id,
})
rep["created_on"] = date_format(instance.created_on, format="DATETIME_FORMAT")
rep["upload_completed_on"] = date_format(instance.upload_completed_on, format="DATETIME_FORMAT")
if instance.file.storage.exists(instance.file.name):
rep["size"] = instance.file.size
rep["path"] = instance.file.name
return rep
# END_FEATURE direct_upload
Loading