some fixes
This commit is contained in:
parent
0e1e77c2dd
commit
f98145d6db
@ -28,7 +28,15 @@ from .api import api
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request: HttpRequest):
|
def home(request: HttpRequest):
|
||||||
return render(request, "index.html", {})
|
from submissions.models import SteamCollection
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"index.html",
|
||||||
|
{
|
||||||
|
"collection": SteamCollection.objects.filter(is_active=True).last(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed, defineProps } from 'vue'
|
||||||
import PuzzleCard from './components/PuzzleCard.vue'
|
import PuzzleCard from '@/components/PuzzleCard.vue'
|
||||||
import SubmissionForm from './components/SubmissionForm.vue'
|
import SubmissionForm from '@/components/SubmissionForm.vue'
|
||||||
import AdminPanel from './components/AdminPanel.vue'
|
import AdminPanel from '@/components/AdminPanel.vue'
|
||||||
import { apiService, errorHelpers } from './services/apiService'
|
import { apiService, errorHelpers } from '@/services/apiService'
|
||||||
import { usePuzzlesStore } from './stores/puzzles'
|
import { usePuzzlesStore } from '@/stores/puzzles'
|
||||||
import { useSubmissionsStore } from './stores/submissions'
|
import { useSubmissionsStore } from '@/stores/submissions'
|
||||||
import type { SteamCollection, PuzzleResponse, UserInfo } from './types'
|
import type { SteamCollection, PuzzleResponse, UserInfo } from '@/types'
|
||||||
|
|
||||||
|
const props = defineProps<{collectionTitle: string, collectionUrl: string, collectionDescription: string}>()
|
||||||
|
|
||||||
// Pinia stores
|
// Pinia stores
|
||||||
const puzzlesStore = usePuzzlesStore()
|
const puzzlesStore = usePuzzlesStore()
|
||||||
const submissionsStore = useSubmissionsStore()
|
const submissionsStore = useSubmissionsStore()
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
const collections = ref<SteamCollection[]>([])
|
|
||||||
const userInfo = ref<UserInfo | null>(null)
|
const userInfo = ref<UserInfo | null>(null)
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const error = ref<string>('')
|
const error = ref<string>('')
|
||||||
@ -63,23 +64,6 @@ onMounted(async () => {
|
|||||||
await puzzlesStore.loadPuzzles()
|
await puzzlesStore.loadPuzzles()
|
||||||
console.log('Puzzles loaded:', puzzlesStore.puzzles.length)
|
console.log('Puzzles loaded:', puzzlesStore.puzzles.length)
|
||||||
|
|
||||||
// Create mock collection from loaded puzzles for display
|
|
||||||
if (puzzlesStore.puzzles.length > 0) {
|
|
||||||
collections.value = [{
|
|
||||||
id: 1,
|
|
||||||
steam_id: '3479142989',
|
|
||||||
title: 'PolyLAN 41',
|
|
||||||
description: 'Puzzle collection for PolyLAN 41 fil rouge',
|
|
||||||
author_name: 'Flame Legrems',
|
|
||||||
total_items: puzzlesStore.puzzles.length,
|
|
||||||
unique_visitors: 31,
|
|
||||||
current_favorites: 1,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
}]
|
|
||||||
console.log('Collection created')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load existing submissions using store
|
// Load existing submissions using store
|
||||||
console.log('Loading submissions...')
|
console.log('Loading submissions...')
|
||||||
await submissionsStore.loadSubmissions()
|
await submissionsStore.loadSubmissions()
|
||||||
@ -204,11 +188,11 @@ const reloadPage = () => {
|
|||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div v-else class="space-y-8">
|
<div v-else class="space-y-8">
|
||||||
<!-- Collection Info -->
|
<!-- Collection Info -->
|
||||||
<div v-if="collections.length > 0" class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="card bg-base-100 shadow-lg">
|
<div class="card bg-base-100 shadow-lg">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-2xl">{{ collections[0].title }}</h2>
|
<h2 class="card-title text-2xl">{{ props.collectionTitle }}</h2>
|
||||||
<p class="text-base-content/70">{{ collections[0].description }}</p>
|
<p class="text-base-content/70">{{ props.collectionDescription }}</p>
|
||||||
<div class="flex flex-wrap gap-4 mt-4">
|
<div class="flex flex-wrap gap-4 mt-4">
|
||||||
<button
|
<button
|
||||||
@click="openSubmissionModal"
|
@click="openSubmissionModal"
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="response in responsesNeedingValidation" :key="response.id">
|
<tr v-for="response in responsesNeedingValidation" :key="response.id">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-bold">{{ response.puzzle_name }}</div>
|
<div class="font-bold">{{ response.puzzle_title }}</div>
|
||||||
<div class="text-sm opacity-50">ID: {{ response.id }}</div>
|
<div class="text-sm opacity-50">ID: {{ response.id }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -110,19 +110,43 @@
|
|||||||
|
|
||||||
<!-- Validation Modal -->
|
<!-- Validation Modal -->
|
||||||
<div v-if="validationModal.show" class="modal modal-open">
|
<div v-if="validationModal.show" class="modal modal-open">
|
||||||
<div class="modal-box">
|
<div class="modal-box w-11/12 max-w-5xl">
|
||||||
<h3 class="font-bold text-lg mb-4">Validate Response</h3>
|
<h3 class="font-bold text-lg mb-4">Validate Response</h3>
|
||||||
|
|
||||||
|
<div v-for="file in validationModal.response.files">
|
||||||
|
<img :src="file.file_url">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="validationModal.response" class="space-y-4">
|
<div v-if="validationModal.response" class="space-y-4">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="mdi mdi-information-outline"></i>
|
<i class="mdi mdi-information-outline"></i>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-bold">{{ validationModal.response.puzzle_name }}</div>
|
<div class="font-bold">{{ validationModal.response.puzzle_title }}</div>
|
||||||
<div class="text-sm">Review and correct the OCR data below</div>
|
<div class="text-sm">Review and correct the OCR data below</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-4">
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Puzzle</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
v-model="validationModal.data.puzzle"
|
||||||
|
class="select select-bordered select-sm w-full"
|
||||||
|
>
|
||||||
|
<option value="">Select puzzle...</option>
|
||||||
|
<option
|
||||||
|
v-for="puzzle in puzzlesStore.puzzles"
|
||||||
|
:key="puzzle.id"
|
||||||
|
:value="puzzle.id"
|
||||||
|
>
|
||||||
|
{{ puzzle.title }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">Cost</span>
|
<span class="label-text">Cost</span>
|
||||||
@ -179,8 +203,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { apiService } from '../services/apiService'
|
import { apiService } from '@/services/apiService'
|
||||||
import type { PuzzleResponse } from '../types'
|
import type { PuzzleResponse } from '@/types'
|
||||||
|
import {usePuzzlesStore} from '@/stores/puzzles'
|
||||||
|
const puzzlesStore = usePuzzlesStore()
|
||||||
|
|
||||||
// Reactive data
|
// Reactive data
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
@ -199,6 +225,7 @@ const validationModal = ref({
|
|||||||
show: false,
|
show: false,
|
||||||
response: null as PuzzleResponse | null,
|
response: null as PuzzleResponse | null,
|
||||||
data: {
|
data: {
|
||||||
|
puzzle_title: '',
|
||||||
validated_cost: '',
|
validated_cost: '',
|
||||||
validated_cycles: '',
|
validated_cycles: '',
|
||||||
validated_area: ''
|
validated_area: ''
|
||||||
@ -244,6 +271,7 @@ const loadData = async () => {
|
|||||||
const openValidationModal = (response: PuzzleResponse) => {
|
const openValidationModal = (response: PuzzleResponse) => {
|
||||||
validationModal.value.response = response
|
validationModal.value.response = response
|
||||||
validationModal.value.data = {
|
validationModal.value.data = {
|
||||||
|
puzzle: response.puzzle || '',
|
||||||
validated_cost: response.cost || '',
|
validated_cost: response.cost || '',
|
||||||
validated_cycles: response.cycles || '',
|
validated_cycles: response.cycles || '',
|
||||||
validated_area: response.area || ''
|
validated_area: response.area || ''
|
||||||
@ -255,6 +283,7 @@ const closeValidationModal = () => {
|
|||||||
validationModal.value.show = false
|
validationModal.value.show = false
|
||||||
validationModal.value.response = null
|
validationModal.value.response = null
|
||||||
validationModal.value.data = {
|
validationModal.value.data = {
|
||||||
|
puzzle: '',
|
||||||
validated_cost: '',
|
validated_cost: '',
|
||||||
validated_cycles: '',
|
validated_cycles: '',
|
||||||
validated_area: ''
|
validated_area: ''
|
||||||
|
|||||||
@ -207,7 +207,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick } from 'vue'
|
||||||
import { ocrService } from '../services/ocrService'
|
import { ocrService } from '@/services/ocrService'
|
||||||
import { usePuzzlesStore } from '@/stores/puzzles'
|
import { usePuzzlesStore } from '@/stores/puzzles'
|
||||||
import type { SubmissionFile, SteamCollectionItem } from '@/types'
|
import type { SubmissionFile, SteamCollectionItem } from '@/types'
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import FileUpload from './FileUpload.vue'
|
import FileUpload from '@/components/FileUpload.vue'
|
||||||
import type { SteamCollectionItem, SubmissionFile } from '@/types'
|
import type { SteamCollectionItem, SubmissionFile } from '@/types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from '@/App.vue'
|
import App from '@/App.vue'
|
||||||
import { pinia } from '@/stores'
|
import { pinia } from '@/stores'
|
||||||
import './style.css'
|
import '@/style.css'
|
||||||
|
|
||||||
const app = createApp(App)
|
// const app = createApp(App)
|
||||||
|
const selector = "#app"
|
||||||
|
const mountData = document.querySelector<HTMLElement>(selector)
|
||||||
|
const app = createApp(App, { ...mountData?.dataset })
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.mount('#app')
|
app.mount(selector)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import type { Submission, SubmissionFile } from '@/types'
|
import type { Submission, SubmissionFile } from '@/types'
|
||||||
import { submissionHelpers } from '@/services/apiService'
|
import { submissionHelpers } from '@/services/apiService'
|
||||||
import { usePuzzlesStore } from './puzzles'
|
import { usePuzzlesStore } from '@/stores/puzzles'
|
||||||
|
|
||||||
export const useSubmissionsStore = defineStore('submissions', () => {
|
export const useSubmissionsStore = defineStore('submissions', () => {
|
||||||
// State
|
// State
|
||||||
@ -16,9 +16,9 @@ export const useSubmissionsStore = defineStore('submissions', () => {
|
|||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
const loadedSubmissions = await submissionHelpers.loadSubmissions(limit, offset)
|
const loadedSubmissions = await submissionHelpers.loadSubmissions(limit, offset)
|
||||||
|
|
||||||
if (offset === 0) {
|
if (offset === 0) {
|
||||||
submissions.value = loadedSubmissions
|
submissions.value = loadedSubmissions
|
||||||
} else {
|
} else {
|
||||||
@ -33,34 +33,34 @@ export const useSubmissionsStore = defineStore('submissions', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createSubmission = async (
|
const createSubmission = async (
|
||||||
files: SubmissionFile[],
|
files: SubmissionFile[],
|
||||||
notes?: string,
|
notes?: string,
|
||||||
manualValidationRequested?: boolean
|
manualValidationRequested?: boolean
|
||||||
): Promise<Submission | undefined> => {
|
): Promise<Submission | undefined> => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
|
||||||
const puzzlesStore = usePuzzlesStore()
|
const puzzlesStore = usePuzzlesStore()
|
||||||
|
|
||||||
const response = await submissionHelpers.createFromFiles(
|
const response = await submissionHelpers.createFromFiles(
|
||||||
files,
|
files,
|
||||||
puzzlesStore.puzzles,
|
puzzlesStore.puzzles,
|
||||||
notes,
|
notes,
|
||||||
manualValidationRequested
|
manualValidationRequested
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
error.value = response.error
|
error.value = response.error
|
||||||
throw new Error(response.error)
|
throw new Error(response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
// Add to local submissions list
|
// Add to local submissions list
|
||||||
submissions.value.unshift(response.data)
|
submissions.value.unshift(response.data)
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to create submission'
|
error.value = err instanceof Error ? err.message : 'Failed to create submission'
|
||||||
@ -89,7 +89,7 @@ export const useSubmissionsStore = defineStore('submissions', () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
isSubmissionModalOpen,
|
isSubmissionModalOpen,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
loadSubmissions,
|
loadSubmissions,
|
||||||
createSubmission,
|
createSubmission,
|
||||||
|
|||||||
@ -32,9 +32,11 @@ def list_puzzles(request):
|
|||||||
@paginate
|
@paginate
|
||||||
def list_submissions(request):
|
def list_submissions(request):
|
||||||
"""Get paginated list of submissions"""
|
"""Get paginated list of submissions"""
|
||||||
return Submission.objects.prefetch_related(
|
return (
|
||||||
"responses__files", "responses__puzzle"
|
Submission.objects.prefetch_related("responses__files", "responses__puzzle")
|
||||||
).filter(user=request.user)
|
.filter(user=request.user)
|
||||||
|
.filter()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/submissions/{submission_id}", response=SubmissionOut)
|
@router.get("/submissions/{submission_id}", response=SubmissionOut)
|
||||||
@ -172,27 +174,29 @@ def validate_response(request, response_id: int, data: ValidationIn):
|
|||||||
if not request.user.is_authenticated or not request.user.is_staff:
|
if not request.user.is_authenticated or not request.user.is_staff:
|
||||||
return 403, {"detail": "Admin access required"}
|
return 403, {"detail": "Admin access required"}
|
||||||
|
|
||||||
try:
|
response = get_object_or_404(PuzzleResponse, id=response_id)
|
||||||
response = PuzzleResponse.objects.select_related("puzzle").get(id=response_id)
|
|
||||||
|
|
||||||
# Update validated values
|
if data.puzzle is not None:
|
||||||
if data.validated_cost is not None:
|
puzzle = get_object_or_404(SteamCollectionItem, id=data.puzzle)
|
||||||
response.validated_cost = data.validated_cost
|
response.puzzle = puzzle
|
||||||
if data.validated_cycles is not None:
|
|
||||||
response.validated_cycles = data.validated_cycles
|
|
||||||
if data.validated_area is not None:
|
|
||||||
response.validated_area = data.validated_area
|
|
||||||
|
|
||||||
# Mark as no longer needing validation if we have all values
|
# Update validated values
|
||||||
if all([response.final_cost, response.final_cycles, response.final_area]):
|
if data.validated_cost is not None:
|
||||||
response.needs_manual_validation = False
|
response.validated_cost = data.validated_cost
|
||||||
|
|
||||||
response.save()
|
if data.validated_cycles is not None:
|
||||||
|
response.validated_cycles = data.validated_cycles
|
||||||
|
|
||||||
return response
|
if data.validated_area is not None:
|
||||||
|
response.validated_area = data.validated_area
|
||||||
|
|
||||||
except PuzzleResponse.DoesNotExist:
|
# Mark as no longer needing validation if we have all values
|
||||||
raise Http404("Response not found")
|
if all([response.final_cost, response.final_cycles, response.final_area]):
|
||||||
|
response.needs_manual_validation = False
|
||||||
|
|
||||||
|
response.save()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get("/responses/needs-validation", response=List[PuzzleResponseOut])
|
@router.get("/responses/needs-validation", response=List[PuzzleResponseOut])
|
||||||
@ -204,6 +208,7 @@ def list_responses_needing_validation(request):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
PuzzleResponse.objects.filter(needs_manual_validation=True)
|
PuzzleResponse.objects.filter(needs_manual_validation=True)
|
||||||
|
.filter(puzzle__collection__is_active=True)
|
||||||
.select_related("puzzle", "submission")
|
.select_related("puzzle", "submission")
|
||||||
.prefetch_related("files")
|
.prefetch_related("files")
|
||||||
)
|
)
|
||||||
@ -216,26 +221,22 @@ def validate_submission(request, submission_id: str):
|
|||||||
if not request.user.is_authenticated or not request.user.is_staff:
|
if not request.user.is_authenticated or not request.user.is_staff:
|
||||||
return 403, {"detail": "Admin access required"}
|
return 403, {"detail": "Admin access required"}
|
||||||
|
|
||||||
try:
|
submission = get_object_or_404(Submission, id=submission_id)
|
||||||
submission = Submission.objects.get(id=submission_id)
|
|
||||||
|
|
||||||
submission.is_validated = True
|
submission.is_validated = True
|
||||||
submission.validated_by = request.user
|
submission.validated_by = request.user
|
||||||
submission.validated_at = timezone.now()
|
submission.validated_at = timezone.now()
|
||||||
submission.save()
|
submission.save()
|
||||||
|
|
||||||
# Also mark all responses as not needing validation
|
# Also mark all responses as not needing validation
|
||||||
submission.responses.update(needs_manual_validation=False)
|
submission.responses.update(needs_manual_validation=False)
|
||||||
|
|
||||||
# Reload with relations
|
# Reload with relations
|
||||||
submission = Submission.objects.prefetch_related(
|
submission = Submission.objects.prefetch_related(
|
||||||
"responses__files", "responses__puzzle"
|
"responses__files", "responses__puzzle"
|
||||||
).get(id=submission.id)
|
).get(id=submission.id)
|
||||||
|
|
||||||
return submission
|
return submission
|
||||||
|
|
||||||
except Submission.DoesNotExist:
|
|
||||||
raise Http404("Submission not found")
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/submissions/{submission_id}")
|
@router.delete("/submissions/{submission_id}")
|
||||||
@ -245,13 +246,9 @@ def delete_submission(request, submission_id: str):
|
|||||||
if not request.user.is_authenticated or not request.user.is_staff:
|
if not request.user.is_authenticated or not request.user.is_staff:
|
||||||
return 403, {"detail": "Admin access required"}
|
return 403, {"detail": "Admin access required"}
|
||||||
|
|
||||||
try:
|
submission = get_object_or_404(Submission, id=submission_id)
|
||||||
submission = Submission.objects.get(id=submission_id)
|
submission.delete()
|
||||||
submission.delete()
|
return {"detail": "Submission deleted successfully"}
|
||||||
return {"detail": "Submission deleted successfully"}
|
|
||||||
|
|
||||||
except Submission.DoesNotExist:
|
|
||||||
raise Http404("Submission not found")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/stats")
|
@router.get("/stats")
|
||||||
@ -270,7 +267,7 @@ def get_stats(request):
|
|||||||
"total_responses": total_responses,
|
"total_responses": total_responses,
|
||||||
"needs_validation": needs_validation,
|
"needs_validation": needs_validation,
|
||||||
"validated_submissions": validated_submissions,
|
"validated_submissions": validated_submissions,
|
||||||
"validation_rate": validated_submissions / total_submissions
|
"validation_rate": (total_responses - needs_validation) / total_responses
|
||||||
if total_submissions > 0
|
if total_responses
|
||||||
else 0,
|
else 0,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-10-30 20:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('submissions', '0007_submission_manual_validation_requested'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='puzzleresponse',
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -327,7 +327,6 @@ class PuzzleResponse(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["submission", "puzzle__order_index"]
|
ordering = ["submission", "puzzle__order_index"]
|
||||||
unique_together = ["submission", "puzzle"]
|
|
||||||
verbose_name = "Puzzle Response"
|
verbose_name = "Puzzle Response"
|
||||||
verbose_name_plural = "Puzzle Responses"
|
verbose_name_plural = "Puzzle Responses"
|
||||||
|
|
||||||
|
|||||||
@ -125,6 +125,7 @@ class SubmissionListOut(Schema):
|
|||||||
class ValidationIn(Schema):
|
class ValidationIn(Schema):
|
||||||
"""Schema for manual validation input"""
|
"""Schema for manual validation input"""
|
||||||
|
|
||||||
|
puzzle: Optional[int] = None
|
||||||
validated_cost: Optional[str] = None
|
validated_cost: Optional[str] = None
|
||||||
validated_cycles: Optional[str] = None
|
validated_cycles: Optional[str] = None
|
||||||
validated_area: Optional[str] = None
|
validated_area: Optional[str] = None
|
||||||
|
|||||||
@ -10,6 +10,6 @@
|
|||||||
{% vite_asset 'src/main.ts' %}
|
{% vite_asset 'src/main.ts' %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app" data-collection-title="{{ collection.title }}" data-collection-url="{{ collection.url }}" data-collection-description="{{ collection.description }}"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user