Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
"sass_bootstrap": ["enabled", "disabled"],
"security_settings": ["enabled", "disabled"],
"sentry": ["enabled", "disabled"],
"user_action_tracking": ["enabled", "disabled"]
"user_action_tracking": ["enabled", "disabled"],
"direct_upload": ["enabled", "disabled"],
"vue": ["enabled", "disabled"]
}
2 changes: 1 addition & 1 deletion hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"{% if cookiecutter.pre_commit == 'disabled' %}.pre-commit-config.yaml{% endif %}",
"{% if cookiecutter.django_react == 'disabled' %}config/webpack_loader.py{% endif %}",
"{% if cookiecutter.django_react == 'disabled' %}nwb.config.js{% endif %}",
"{% if cookiecutter.django_react == 'disabled' and cookiecutter.sass_bootstrap == 'disabled' %}package.json{% endif %}",
"{% if cookiecutter.django_react == 'disabled' and cookiecutter.sass_bootstrap == 'disabled' and cookiecutter.vue == 'disabled' %}package.json{% endif %}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also add the vue static directory (which should probably be a subdirectory of a static root) to this, instead of adding the if vue to each individual vue file. Same for vite config.

Could also consider removing the whole app directory if reference_examples is disabled (would require associated settings changes etc)

]


Expand Down
5 changes: 4 additions & 1 deletion {{cookiecutter.project_slug}}/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ venv.bak/
node_modules/

# Compiled Files
static/js/dist*
staticfiles/
staticfiles/*
# START_FEATURE sass_bootstrap
Expand All @@ -65,7 +66,9 @@ static/webpack_bundles/
webpack-stats.json
# END_FEATURE django_react

# Locally uploaded files
media/

# START_FEATURE ecs
.terraform.lock.hcl
.terraform/
# END_FEATURE ecs
1 change: 1 addition & 0 deletions {{cookiecutter.project_slug}}/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
Empty file.
3 changes: 3 additions & 0 deletions {{cookiecutter.project_slug}}/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 {{cookiecutter.project_slug}}/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'
11 changes: 11 additions & 0 deletions {{cookiecutter.project_slug}}/app/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SAMPLE_OBJECT_PK_URL_KWARG = "sample_object_id"
{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}

# START_FEATURE direct_upload
{%- endif %}
ATTACHMENT_PK_URL_KWARG = "attachment_id"
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}
63 changes: 63 additions & 0 deletions {{cookiecutter.project_slug}}/app/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{%- if cookiecutter.crispy_forms == "enabled" %}
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 SampleObject
{%- if cookiecutter.direct_upload == "enabled" %}
from app.models import Attachment
from common.fields import DirectUploadFileField
{%- endif %}
from common.forms import ActionFormMixin, CrispyFormMixin


class SampleObjectBaseForm(CrispyFormMixin, ActionFormMixin, forms.ModelForm):
request: HttpRequest
{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}

# START_FEATURE direct_upload
{%- endif %}
attachments = DirectUploadFileField(queryset=Attachment.objects.filter(deleted_on=None), required=False)
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}

class Meta:
model = SampleObject
exclude = ['created_by']

layout = Layout(
Fieldset(
"Details",
"name",
"description"
),
{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}
# START_FEATURE direct_upload
{%- endif %}
"attachments"
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}
)

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}"
{%- endif %}
48 changes: 48 additions & 0 deletions {{cookiecutter.project_slug}}/app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by Django 5.2 on 2026-03-16 18:26

import uuid
from django.db import migrations, models
{%- if cookiecutter.django_storages == "enabled" %}
import common.models
{%- endif %}


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
{%- if cookiecutter.direct_upload == "enabled" %}
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,
},
),
{%- endif %}
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,
},
),
]
35 changes: 35 additions & 0 deletions {{cookiecutter.project_slug}}/app/migrations/0002_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.2 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 = [
{%- if cookiecutter.direct_upload == "enabled" %}
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'),
),
{%- endif %}
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.
48 changes: 48 additions & 0 deletions {{cookiecutter.project_slug}}/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.db import models

from common.models import TimestampedModel, User
{%- if cookiecutter.django_storages == "enabled" %}
from common.models import UploadFile
{%- endif %}


class SampleObject(TimestampedModel):
created_by = models.ForeignKey(User, related_name="sample_objects", on_delete=models.PROTECT)
{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}

# START_FEATURE direct_upload
{%- endif %}
attachments = models.ManyToManyField("Attachment", related_name="sample_objects")
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}

name = models.CharField(max_length=512, unique=True)
description = models.TextField(default="", blank=True)

def __str__(self) -> str:
return f'Sample Object {self.name}'
{%- if cookiecutter.direct_upload == "enabled" %}

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
]
{%- endif %}

{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}


# START_FEATURE direct_upload
{%- endif %}
class Attachment(UploadFile):
pass
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}
51 changes: 51 additions & 0 deletions {{cookiecutter.project_slug}}/app/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{%- if cookiecutter.direct_upload == "enabled" %}
{%- if cookiecutter.feature_annotations == "on" %}
# START_FEATURE direct_upload
{%- endif %}
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
{%- if cookiecutter.feature_annotations == "on" %}
# END_FEATURE direct_upload
{%- endif %}
{%- endif %}
Loading