auto-reload for admin + auto validation for all using python
This commit is contained in:
parent
e0ada1e26d
commit
596731a8a7
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
"@vueuse/core": "^14.0.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
|
|||||||
@ -11,6 +11,9 @@ importers:
|
|||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.16
|
specifier: ^4.1.16
|
||||||
version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))
|
version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^14.0.0
|
||||||
|
version: 14.0.0(vue@3.5.22(typescript@5.9.3))
|
||||||
install:
|
install:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
@ -455,6 +458,9 @@ packages:
|
|||||||
'@types/node@24.9.2':
|
'@types/node@24.9.2':
|
||||||
resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==}
|
resolution: {integrity: sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21':
|
||||||
|
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.1':
|
'@vitejs/plugin-vue@6.0.1':
|
||||||
resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}
|
resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@ -528,6 +534,19 @@ packages:
|
|||||||
vue:
|
vue:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vueuse/core@14.0.0':
|
||||||
|
resolution: {integrity: sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
|
'@vueuse/metadata@14.0.0':
|
||||||
|
resolution: {integrity: sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==}
|
||||||
|
|
||||||
|
'@vueuse/shared@14.0.0':
|
||||||
|
resolution: {integrity: sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
alien-signals@3.0.3:
|
alien-signals@3.0.3:
|
||||||
resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==}
|
resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==}
|
||||||
|
|
||||||
@ -1107,6 +1126,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21': {}
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3))':
|
'@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-beta.29
|
'@rolldown/pluginutils': 1.0.0-beta.29
|
||||||
@ -1214,6 +1235,19 @@ snapshots:
|
|||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
vue: 3.5.22(typescript@5.9.3)
|
vue: 3.5.22(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@vueuse/core@14.0.0(vue@3.5.22(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.21
|
||||||
|
'@vueuse/metadata': 14.0.0
|
||||||
|
'@vueuse/shared': 14.0.0(vue@3.5.22(typescript@5.9.3))
|
||||||
|
vue: 3.5.22(typescript@5.9.3)
|
||||||
|
|
||||||
|
'@vueuse/metadata@14.0.0': {}
|
||||||
|
|
||||||
|
'@vueuse/shared@14.0.0(vue@3.5.22(typescript@5.9.3))':
|
||||||
|
dependencies:
|
||||||
|
vue: 3.5.22(typescript@5.9.3)
|
||||||
|
|
||||||
alien-signals@3.0.3: {}
|
alien-signals@3.0.3: {}
|
||||||
|
|
||||||
birpc@2.6.1: {}
|
birpc@2.6.1: {}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ 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'
|
||||||
|
import { useCountdown } from '@vueuse/core'
|
||||||
|
|
||||||
const props = defineProps<{collectionTitle: string, collectionUrl: string, collectionDescription: string}>()
|
const props = defineProps<{collectionTitle: string, collectionUrl: string, collectionDescription: string}>()
|
||||||
|
|
||||||
@ -19,8 +20,6 @@ const userInfo = ref<UserInfo | null>(null)
|
|||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const error = ref<string>('')
|
const error = ref<string>('')
|
||||||
|
|
||||||
// Mock data removed - using API data only
|
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
const isSuperuser = computed(() => {
|
const isSuperuser = computed(() => {
|
||||||
return userInfo.value?.is_superuser || false
|
return userInfo.value?.is_superuser || false
|
||||||
@ -42,7 +41,7 @@ const responsesByPuzzle = computed(() => {
|
|||||||
return grouped
|
return grouped
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
async function initialize() {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
@ -78,6 +77,20 @@ onMounted(async () => {
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
console.log('Loading state set to false')
|
console.log('Loading state set to false')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userInfo.value.is_superuser) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { remaining, start } = useCountdown(60, {
|
||||||
|
onComplete() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmission = async (submissionData: {
|
const handleSubmission = async (submissionData: {
|
||||||
@ -165,6 +178,15 @@ const reloadPage = () => {
|
|||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
|
<div v-if="userInfo?.is_superuser" class="flex justify-center">
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="mb-6 text-base-content/70">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
Auto reload page in {{ remaining }} seconds ...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="isLoading" class="flex justify-center items-center min-h-[400px]">
|
<div v-if="isLoading" class="flex justify-center items-center min-h-[400px]">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
|||||||
@ -26,6 +26,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-primary" @click="autoValidationResponse">
|
||||||
|
<i class="mdi mdi-check-circle mr-1"></i>
|
||||||
|
Auto validation for all responses
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Responses Needing Validation -->
|
<!-- Responses Needing Validation -->
|
||||||
<div v-if="responsesNeedingValidation.length > 0">
|
<div v-if="responsesNeedingValidation.length > 0">
|
||||||
<h3 class="text-lg font-bold mb-4">Responses Needing Validation</h3>
|
<h3 class="text-lg font-bold mb-4">Responses Needing Validation</h3>
|
||||||
@ -268,6 +273,23 @@ const loadData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoValidationResponse = async () => {
|
||||||
|
for (const response of Array.from(responsesNeedingValidation.value)) {
|
||||||
|
const {data, error} = await apiService.autoValidateResponses(response.id)
|
||||||
|
|
||||||
|
if (data && !data.needs_manual_validation) {
|
||||||
|
// Remove from validation list
|
||||||
|
responsesNeedingValidation.value = responsesNeedingValidation.value.filter(
|
||||||
|
r => r.id !== response.id
|
||||||
|
)
|
||||||
|
stats.value.needs_validation -= 1
|
||||||
|
|
||||||
|
} else if (error) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openValidationModal = (response: PuzzleResponse) => {
|
const openValidationModal = (response: PuzzleResponse) => {
|
||||||
validationModal.value.response = response
|
validationModal.value.response = response
|
||||||
validationModal.value.data = {
|
validationModal.value.data = {
|
||||||
|
|||||||
@ -158,6 +158,12 @@ export class ApiService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async autoValidateResponses(responseId: number): Promise<ApiResponse<PuzzleResponse>> {
|
||||||
|
return this.request<PuzzleResponse>(`/submissions/responses/${responseId}/validate/auto`, {
|
||||||
|
method: 'PUT',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async getResponsesNeedingValidation(): Promise<ApiResponse<PuzzleResponse[]>> {
|
async getResponsesNeedingValidation(): Promise<ApiResponse<PuzzleResponse[]>> {
|
||||||
return this.request<PuzzleResponse[]>('/submissions/responses/needs-validation')
|
return this.request<PuzzleResponse[]>('/submissions/responses/needs-validation')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ from django.utils import timezone
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from opus_submitter.submissions.utils import verify_and_validate_ocr_date_for_submission
|
||||||
|
|
||||||
from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem
|
from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
SubmissionIn,
|
SubmissionIn,
|
||||||
@ -196,6 +198,21 @@ def validate_response(request, response_id: int, data: ValidationIn):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/responses/{response_id}/validate/auto", response=PuzzleResponseOut)
|
||||||
|
def validate_response(request, response_id: int):
|
||||||
|
"""Try to auto validate a puzzle response"""
|
||||||
|
|
||||||
|
if not request.user.is_authenticated or not request.user.is_staff:
|
||||||
|
return 403, {"detail": "Admin access required"}
|
||||||
|
|
||||||
|
response = get_object_or_404(PuzzleResponse, id=response_id)
|
||||||
|
|
||||||
|
for file in response.files.all():
|
||||||
|
verify_and_validate_ocr_date_for_submission(file)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get("/responses/needs-validation", response=List[PuzzleResponseOut])
|
@router.get("/responses/needs-validation", response=List[PuzzleResponseOut])
|
||||||
def list_responses_needing_validation(request):
|
def list_responses_needing_validation(request):
|
||||||
"""Get all responses that need manual validation"""
|
"""Get all responses that need manual validation"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user