This commit is contained in:
2026-05-12 19:35:15 +03:00
parent c451a106a1
commit 0fdb8b8a02
17 changed files with 1351 additions and 8 deletions

15
apps/dashboard/urls.py Normal file
View File

@@ -0,0 +1,15 @@
from django.urls import path
from . import views
app_name = "dashboard"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("my-prints/", views.MyPrintsView.as_view(), name="my_prints"),
# Routes to be added as features land (see plan.md Section 7):
# path("p/<slug:slug>/", views.SubmissionDetailView.as_view(), name="detail"),
# path("p/<slug:slug>/status/", views.SubmissionStatusFragment.as_view(), name="status"),
# path("p/<slug:slug>/confirm/<str:token>/", views.ConfirmEmailView.as_view(), name="confirm"),
# path("p/<slug:slug>/resend/", views.ResendConfirmationView.as_view(), name="resend"),
]

View File

@@ -1,3 +1,87 @@
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Q
from django.views.generic import ListView
# Create your views here.
from apps.submissions.models import Submission
class IndexView(ListView):
"""Public dashboard (plan.md §8).
Lists every submission whose status is one of the four dashboard-visible
states -- `verifying`, `queued`, `printing`, `completed`. Anything in
`identifying`, `processing`, `rejected`, or `failed` is excluded from
the listing (still reachable by direct slug URL by the submitter).
Status-chip filtering via `?status=<value>`; only the four dashboard-
visible values are honoured. Anything else falls back to the unfiltered
list, so the chips stay safe even if someone hand-edits the URL.
"""
model = Submission
template_name = "dashboard/index.html"
context_object_name = "submissions"
paginate_by = 20
def _requested_status(self) -> str:
raw = self.request.GET.get("status", "")
allowed = {str(s) for s in Submission.DASHBOARD_VISIBLE_STATUSES}
return raw if raw in allowed else ""
def get_queryset(self):
qs = Submission.objects.filter(
status__in=Submission.DASHBOARD_VISIBLE_STATUSES
).select_related("requested_filament")
status = self._requested_status()
if status:
qs = qs.filter(status=status)
return qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
# One conditional-aggregate for the chip counts, scoped to the same
# dashboard-visible filter so `total` matches what "All" would list.
ctx["counts"] = Submission.objects.filter(
status__in=Submission.DASHBOARD_VISIBLE_STATUSES
).aggregate(
total=Count("id"),
verifying=Count("id", filter=Q(status=Submission.Status.VERIFYING)),
queued=Count("id", filter=Q(status=Submission.Status.QUEUED)),
printing=Count("id", filter=Q(status=Submission.Status.PRINTING)),
completed=Count("id", filter=Q(status=Submission.Status.COMPLETED)),
)
ctx["active_status"] = self._requested_status()
return ctx
class MyPrintsView(LoginRequiredMixin, ListView):
"""Private listing -- every submission the signed-in user has ever made.
Anonymous users are bounced to allauth's `account_login` (the default
`LoginRequiredMixin.login_url`, configured via `LOGIN_URL`). Guests don't
have a `submitted_by`, so they have nothing to list here anyway.
"""
model = Submission
template_name = "dashboard/my_prints.html"
context_object_name = "submissions"
paginate_by = 50
def get_queryset(self):
# Ordering inherited from `Submission.Meta` (-created_at).
return Submission.objects.filter(
submitted_by=self.request.user
).select_related("requested_filament")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
# Counts for the summary line in the page header. A single
# conditional-aggregate query beats N separate `.count()` calls.
agg = Submission.objects.filter(submitted_by=self.request.user).aggregate(
total=Count("id"),
queued=Count("id", filter=Q(status=Submission.Status.QUEUED)),
printing=Count("id", filter=Q(status=Submission.Status.PRINTING)),
completed=Count("id", filter=Q(status=Submission.Status.COMPLETED)),
)
ctx["counts"] = agg
return ctx