Django应用URL模式匹配失败:任务状态检查接口404报错
问题:Django+Celery异步任务轮询时URL匹配失败
问题场景
基于Docker部署的Django项目,使用Celery处理音频转录异步任务。用户提交音频文件后,系统启动转录任务,前端持续轮询任务进度,但在转录完成、下载按钮显示前,系统报错无法找到任务状态检查的视图。
相关代码
views.py
def initiate_transcription(request, session_id): file_name = request.session.get('uploaded_file_name') file_path = request.session.get('uploaded_file_path') if request.method == 'GET': if not file_name or not file_path: return redirect(reverse('transcribeSubmit')) if request.method == 'POST': try: if not file_name or not file_path: return redirect(reverse('transcribeSubmit')) audio_language = request.POST.get('audio_language') output_file_type = request.POST.get('output_file_type') if file_name and file_path: print(str("VIEW: "+session_id)) task = transcribe_file_task.delay(file_path, audio_language, output_file_type, 'ai_transcribe_output', session_id) return JsonResponse({'status': 'success', 'task_id': task.id}) except Exception as e: return JsonResponse({'status': 'error', 'error': 'No file uploaded'}) return render(request, 'transcribe/transcribe-complete-en.html') def check_task_status(request, session_id, task_id): task_result = AsyncResult(task_id) if task_result.ready(): transcribed_doc = TranscribedDocument.objects.get(id=session_id) return JsonResponse({ 'status': 'completed', 'output_file_url': transcribed_doc.output_file.url }) else: return JsonResponse({'status': 'pending'})
前端JS代码
form.addEventListener('submit', function(event) { event.preventDefault(); const transcribeField = document.querySelector('.transcribe-output-lang-select') const errorDiv = document.querySelector('.error-transcribe-div'); transcribeField.style.opacity = '0'; setTimeout(function() { transcribeField.style.display = 'none'; transcribingFileField.style.display = 'block'; errorDiv.style.opacity = '0'; errorDiv.style.display = 'none'; }, 300); setTimeout(function() { transcribingFileField.style.opacity = '1' }, 500); const formData = new FormData(form); const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { const response = JSON.parse(xhr.responseText); if (response.status === 'success') { pollTaskStatus(response.task_id); } else { showError('An error occurred while initiating the transcription.'); } } else { showError('An error occurred while uploading the file.'); } }; xhr.onerror = function() { showError('An error occurred while uploading the file.'); }; xhr.open('POST', form.action, true); xhr.send(formData); }); function pollTaskStatus(taskId) { const pollInterval = setInterval(() => { const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { const response = JSON.parse(xhr.responseText); if (response.status === 'completed') { clearInterval(pollInterval); showCompletedUI(response.output_file_url); } } }; xhr.open('GET', `/check_task_status/${taskId}/`, true); xhr.send(); }, 5000); // Poll every 5 seconds } function showCompletedUI(outputFileUrl) { const transcribingText = document.querySelector('.transcribing-text'); transcribingText.textContent = 'Transcript Completed'; const downloadBtn = document.querySelector('.download-btn'); downloadBtn.addEventListener('click', function() { window.location.href = outputFileUrl; this.innerHTML = 'Transcript downloaded!'; setTimeout(() => { this.innerHTML = 'Click to download'; }, 3000); }); } function showError(message) { const errorDiv = document.querySelector('.error-transcribe-div'); errorDiv.textContent = message; errorDiv.style.opacity = '1'; errorDiv.style.display = 'block'; const transcribingFileField = document.querySelector('.transcribing-file-field'); transcribingFileField.style.opacity = '0'; transcribingFileField.style.display = 'none'; } function showTranscriptionComplete(fileUrl) { // Update UI to show transcription is complete console.log('success') const transcribingText = document.querySelector('.transcribing-text'); transcribingText.textContent = 'Transcript Completed'; const orderComplete = document.querySelector('.order-complete-data'); orderComplete.style.opacity = '0'; transcriptComplete = document.querySelector('.transcript-complete'); const transcriptSVG = document.querySelector('.transcript-svg'); setTimeout(function() { orderComplete.style.display = 'none'; transcriptComplete.style.opacity = '1'; transcriptComplete.style.display = 'block'; transcribeLoader.style.opacity = '0'; transcribeLoader.style.display = 'none'; transcriptSVG.style.opacity = '1'; transcriptSVG.style.display = 'block'; }, 300); let blob = new Blob([xhr.response], {type: xhr.getResponseHeader('Content-Type')}); let fileName = xhr.getResponseHeader('Content-Disposition').split('filename=')[1]; console.log(fileName); // Set up download button const downloadBtn = document.querySelector('.download-btn'); downloadBtn.addEventListener('click', function() { download(blob, fileName); this.innerHTML = 'Transcript downloaded!'; setTimeout(() => { this.innerHTML = 'Click to download'; }, 3000); } )};
urls.py
urlpatterns = [ path("", views.transcribeSubmit, name="transcribeSubmit"), path("init-transcription/<str:session_id>/", views.initiate_transcription, name="initiate_transcription"), path("check_task_status/<str:task_id>/", views.check_task_status, name="check_task_status"), ]
错误日志
web-1 | Not Found: /check_task_status/02760416-c2fb-4526-b0d0-d5cdafabf8cf/
排查与解决步骤
1. 核心问题:视图参数与URL模式不匹配
check_task_status视图函数需要接收session_id和task_id两个参数,但当前urls.py中只定义了<str:task_id>一个参数,前端JS也只传递了task_id,导致URL缺少必要参数,Django无法匹配到对应的视图。
修复方案:
- 修改URL模式:更新
urls.py中的check_task_status路径,添加session_id参数:path("check_task_status/<str:session_id>/<str:task_id>/", views.check_task_status, name="check_task_status"), - 前端传递session_id:
- 在
initiate_transcription的POST响应中返回session_id:return JsonResponse({'status': 'success', 'task_id': task.id, 'session_id': session_id}) - 前端接收并传递该参数给轮询函数:
// 提交表单后的回调中 if (response.status === 'success') { pollTaskStatus(response.task_id, response.session_id); } // 修改轮询函数 function pollTaskStatus(taskId, sessionId) { const pollInterval = setInterval(() => { const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { const response = JSON.parse(xhr.responseText); if (response.status === 'completed') { clearInterval(pollInterval); showCompletedUI(response.output_file_url); } } }; xhr.open('GET', `/check_task_status/${sessionId}/${taskId}/`, true); xhr.send(); }, 5000); }
- 在
2. 检查URL前缀(如果使用了命名空间/APP路由)
如果主项目的urls.py中是通过include引入当前APP的路由(比如path('transcribe/', include('transcribe.urls'))),那么前端请求的URL需要加上前缀,即/transcribe/check_task_status/${sessionId}/${taskId}/,否则会出现路径匹配失败。
3. 验证APPEND_SLASH配置
确认settings.py中APPEND_SLASH = True(默认开启),确保Django能自动处理URL末尾的斜杠问题,避免因斜杠缺失或多余导致的匹配失败。
4. 额外验证:Celery任务数据正确性
虽然当前错误是URL匹配,但后续下载依赖TranscribedDocument数据,需确保Celery任务完成后正确创建/更新该模型实例,避免后续下载环节出现问题。
内容的提问来源于stack exchange,提问作者tthheemmaannii
相关产品推荐
相关产品推荐

