You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:
    1. 在initiate_transcription的POST响应中返回session_id:
      return JsonResponse({'status': 'success', 'task_id': task.id, 'session_id': session_id})
      
    2. 前端接收并传递该参数给轮询函数:
      // 提交表单后的回调中
      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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:23:09