feat: submissions app + django-storages + whitenoise
This commit is contained in:
@@ -16,6 +16,9 @@ import os
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
os.environ.setdefault("AWS_REQUEST_CHECKSUM_CALCULATION", "when_required")
|
||||
os.environ.setdefault("AWS_RESPONSE_CHECKSUM_VALIDATION", "when_required")
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
@@ -45,10 +48,12 @@ INSTALLED_APPS = [
|
||||
"noita",
|
||||
"games",
|
||||
"market",
|
||||
"submissions",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
@@ -195,6 +200,16 @@ STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static_source/vite"),
|
||||
]
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.s3.S3Storage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "storages.backends.s3.S3Storage",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
from polylan_submitter.settingsLocal import * # noqa
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
from django.contrib import admin
|
||||
from .models import Submission, SubmissionFile
|
||||
|
||||
|
||||
class SubmissionFileInline(admin.TabularInline):
|
||||
model = SubmissionFile
|
||||
extra = 0
|
||||
fields = [
|
||||
"original_filename",
|
||||
"file_size",
|
||||
"content_type",
|
||||
"ocr_processed",
|
||||
"ocr_error",
|
||||
"created_at",
|
||||
]
|
||||
readonly_fields = [
|
||||
"file_size",
|
||||
"content_type",
|
||||
"ocr_processed",
|
||||
"ocr_error",
|
||||
"created_at",
|
||||
]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request)
|
||||
|
||||
|
||||
@admin.register(Submission)
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ["id_short", "user", "notes_preview", "created_at"]
|
||||
list_filter = ["created_at", "updated_at"]
|
||||
search_fields = ["id", "user__email", "notes"]
|
||||
readonly_fields = ["id", "created_at", "updated_at"]
|
||||
inlines = [SubmissionFileInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related("user")
|
||||
|
||||
@admin.display(description="ID")
|
||||
def id_short(self, obj):
|
||||
return str(obj.id)[:8]
|
||||
|
||||
@admin.display(description="Notes")
|
||||
def notes_preview(self, obj):
|
||||
if obj.notes:
|
||||
return obj.notes[:50] + "..." if len(obj.notes) > 50 else obj.notes
|
||||
return "-"
|
||||
|
||||
|
||||
@admin.register(SubmissionFile)
|
||||
class SubmissionFileAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"original_filename",
|
||||
"submission_id",
|
||||
"file_size",
|
||||
"content_type",
|
||||
"ocr_processed",
|
||||
"created_at",
|
||||
]
|
||||
list_filter = ["content_type", "ocr_processed", "created_at"]
|
||||
search_fields = ["original_filename", "submission__id"]
|
||||
readonly_fields = ["file_size", "content_type", "created_at", "updated_at"]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related("submission")
|
||||
|
||||
@admin.display(description="Submission ID")
|
||||
def submission_id(self, obj):
|
||||
return str(obj.submission.id)[:8]
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SubmissionsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "submissions"
|
||||
@@ -0,0 +1,123 @@
|
||||
# Generated by Django 5.2.7 on 2026-05-24 21:34
|
||||
|
||||
import django.db.models.deletion
|
||||
import submissions.models
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Submission",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"notes",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Optional notes about the submission",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who made the submission (null for anonymous)",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="submissions",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SubmissionFile",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
help_text="Uploaded file",
|
||||
upload_to=submissions.models.submission_file_upload_path,
|
||||
),
|
||||
),
|
||||
(
|
||||
"original_filename",
|
||||
models.CharField(
|
||||
help_text="Original filename as uploaded by user",
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
(
|
||||
"file_size",
|
||||
models.PositiveIntegerField(help_text="File size in bytes"),
|
||||
),
|
||||
(
|
||||
"content_type",
|
||||
models.CharField(help_text="MIME type of the file", max_length=100),
|
||||
),
|
||||
(
|
||||
"ocr_processed",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether OCR has been processed for this file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"ocr_raw_data",
|
||||
models.JSONField(
|
||||
blank=True, help_text="Raw OCR data as JSON", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"ocr_error",
|
||||
models.TextField(
|
||||
blank=True, help_text="OCR processing error message"
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"submission",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="files",
|
||||
to="submissions.submission",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Submission File",
|
||||
"verbose_name_plural": "Submission Files",
|
||||
"ordering": ["file", "created_at"],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import uuid
|
||||
|
||||
from accounts.models import CustomUser
|
||||
|
||||
|
||||
def submission_file_upload_path(instance, filename):
|
||||
ext = filename.split(".")[-1] if "." in filename else ""
|
||||
|
||||
new_filename = f"{instance.id}_{filename}" if ext else str(instance.id)
|
||||
|
||||
return f"submissions/{instance.submission.id}/{new_filename}"
|
||||
|
||||
|
||||
class Submission(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
user = models.ForeignKey(
|
||||
CustomUser,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="User who made the submission (null for anonymous)",
|
||||
related_name="submissions",
|
||||
)
|
||||
|
||||
# Submission metadata
|
||||
notes = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Optional notes about the submission",
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class SubmissionFile(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
submission = models.ForeignKey(
|
||||
Submission,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="files",
|
||||
)
|
||||
|
||||
file = models.FileField(
|
||||
upload_to=submission_file_upload_path,
|
||||
help_text=_("Uploaded file"),
|
||||
)
|
||||
|
||||
original_filename = models.CharField(
|
||||
max_length=255, help_text=_("Original filename as uploaded by user")
|
||||
)
|
||||
file_size = models.PositiveIntegerField(help_text=_("File size in bytes"))
|
||||
content_type = models.CharField(
|
||||
max_length=100,
|
||||
help_text=_("MIME type of the file"),
|
||||
)
|
||||
|
||||
# OCR metadata
|
||||
ocr_processed = models.BooleanField(
|
||||
default=False, help_text="Whether OCR has been processed for this file"
|
||||
)
|
||||
ocr_raw_data = models.JSONField(
|
||||
null=True, blank=True, help_text="Raw OCR data as JSON"
|
||||
)
|
||||
ocr_error = models.TextField(blank=True, help_text="OCR processing error message")
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["file", "created_at"]
|
||||
verbose_name = "Submission File"
|
||||
verbose_name_plural = "Submission Files"
|
||||
|
||||
@property
|
||||
def file_url(self) -> str | None:
|
||||
if self.file:
|
||||
return self.file.url
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.file and not self.file_size:
|
||||
self.file_size = self.file.size
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
@@ -0,0 +1 @@
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1 @@
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user