fix(submissions): default to frontend search to prevent wrong 404 on hard refresh
This commit is contained in:
@@ -28,6 +28,7 @@ const gameName = ref("");
|
|||||||
const scopes = ref<ScopeOut[]>([]);
|
const scopes = ref<ScopeOut[]>([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const notAssigned = ref(false);
|
const notAssigned = ref(false);
|
||||||
|
const notFound = ref(false);
|
||||||
|
|
||||||
const isSyncModalOpen = ref(false);
|
const isSyncModalOpen = ref(false);
|
||||||
|
|
||||||
@@ -49,7 +50,15 @@ const loadScopes = async () => {
|
|||||||
queryClient.ensureQueryData(userQuery()),
|
queryClient.ensureQueryData(userQuery()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
gameName.value = games.find((game) => game.path === props.gamePath)?.name ?? "";
|
// Not a game of this event (or one with its own page, not submissions):
|
||||||
|
// the backend just serves the app for any game path, this is our 404.
|
||||||
|
const game = games.find((game) => game.path === props.gamePath && !game.custom_path);
|
||||||
|
if (!game || scopesResponse.error) {
|
||||||
|
notFound.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gameName.value = game.name;
|
||||||
|
|
||||||
const availableScopes = scopesResponse.data ?? [];
|
const availableScopes = scopesResponse.data ?? [];
|
||||||
|
|
||||||
@@ -74,7 +83,7 @@ onMounted(loadScopes);
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-1 bg-base-200 flex flex-col items-center justify-center gap-4 p-4">
|
<div class="flex-1 bg-base-200 flex flex-col items-center justify-center gap-4 p-4">
|
||||||
<div v-if="userInfo.isSuperuser && !loading" class="w-full max-w-md flex justify-end">
|
<div v-if="userInfo.isSuperuser && !loading && !notFound" class="w-full max-w-md flex justify-end">
|
||||||
<button @click="isSyncModalOpen = true" class="btn btn-primary btn-sm">
|
<button @click="isSyncModalOpen = true" class="btn btn-primary btn-sm">
|
||||||
<i class="mdi mdi-sync"></i>
|
<i class="mdi mdi-sync"></i>
|
||||||
{{ t("submissionsSelect.syncScopes") }}
|
{{ t("submissionsSelect.syncScopes") }}
|
||||||
@@ -84,6 +93,15 @@ onMounted(loadScopes);
|
|||||||
<div v-if="loading" class="flex justify-center py-20">
|
<div v-if="loading" class="flex justify-center py-20">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="notFound" class="text-center max-w-md">
|
||||||
|
<i class="mdi mdi-alert-circle-outline text-6xl text-primary"></i>
|
||||||
|
<h2 class="text-2xl font-semibold mt-4">{{ t("errorPage.notFound.title") }}</h2>
|
||||||
|
<p class="text-base-content/70 mt-2">{{ t("errorPage.notFound.message") }}</p>
|
||||||
|
<RouterLink :to="{ name: 'home', params: { eventPath } }" class="btn btn-primary mt-6">
|
||||||
|
<i class="mdi mdi-home mr-2"></i>
|
||||||
|
{{ t("errorPage.goHome") }}
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
<div v-else-if="notAssigned" class="text-center max-w-md">
|
<div v-else-if="notAssigned" class="text-center max-w-md">
|
||||||
<i class="mdi mdi-account-alert-outline text-6xl text-warning"></i>
|
<i class="mdi mdi-account-alert-outline text-6xl text-warning"></i>
|
||||||
<h2 class="text-2xl font-semibold mt-4">{{ t("submissionsSelect.notAssignedTitle") }}</h2>
|
<h2 class="text-2xl font-semibold mt-4">{{ t("submissionsSelect.notAssignedTitle") }}</h2>
|
||||||
@@ -106,7 +124,7 @@ onMounted(loadScopes);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScopeSyncModal v-if="userInfo.isSuperuser" v-model:open="isSyncModalOpen" :event-path="eventPath"
|
<ScopeSyncModal v-if="userInfo.isSuperuser && !notFound" v-model:open="isSyncModalOpen" :event-path="eventPath"
|
||||||
:game-path="gamePath" :game-name="gameName" />
|
:game-path="gamePath" :game-name="gameName" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -368,3 +368,23 @@ class ScopeAdminBroadcastTests(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sent, [{"id": str(self.scope.id), "is_active": False, "assigned": False}]
|
sent, [{"id": str(self.scope.id), "is_active": False, "assigned": False}]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionHomeViewTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.event = Event.objects.create(name="Event", path="event")
|
||||||
|
self.client.force_login(
|
||||||
|
CustomUser.objects.create_user(username="player", password="x")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_any_game_path_serves_the_app(self):
|
||||||
|
# Unknown game / no scope: the frontend shows its own not-found state
|
||||||
|
response = self.client.get(f"/{self.event.path}/submissions/unknown-game")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTemplateUsed(response, "app.html")
|
||||||
|
|
||||||
|
def test_unknown_event_is_404(self):
|
||||||
|
response = self.client.get("/nope/submissions/unknown-game")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|||||||
@@ -1,44 +1,17 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404
|
||||||
from django.shortcuts import redirect, render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
from events.models import Event
|
from events.models import Event
|
||||||
from games.models import Game
|
|
||||||
from .models import Scope
|
from .models import Scope
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def submission_home(request: HttpRequest, event_path: str, game_path: str):
|
def submission_home(request: HttpRequest, event_path: str, game_path: str):
|
||||||
event = get_object_or_404(Event, path=event_path)
|
# Unknown game, no scope assigned, auto-entering the only scope: all
|
||||||
game = get_object_or_404(Game, path=game_path)
|
# handled by the frontend (SubmissionsSelect.vue)
|
||||||
|
get_object_or_404(Event, path=event_path)
|
||||||
scopes = (
|
|
||||||
Scope.objects.filter(
|
|
||||||
event_game__event=event,
|
|
||||||
event_game__game=game,
|
|
||||||
is_active=True,
|
|
||||||
)
|
|
||||||
if request.user.is_superuser
|
|
||||||
else Scope.objects.filter(
|
|
||||||
event_game__event=event,
|
|
||||||
event_game__game=game,
|
|
||||||
assigned_users=request.user,
|
|
||||||
is_active=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if scopes.count() == 1:
|
|
||||||
return redirect(
|
|
||||||
"submissions.view",
|
|
||||||
event_path=event.path,
|
|
||||||
game_path=game.path,
|
|
||||||
scope_id=scopes[0].id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not scopes:
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
return render(request, "app.html")
|
return render(request, "app.html")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user