Django项目调用ApplicantEducation查询时抛出DoesNotExist异常的排查
Django 3.1.7 教育信息更新视图异常排查与修复
异常触发根因
根据栈信息定位,抛出DoesNotExist异常的代码行是:user_info = ApplicantEducation.objects.filter(applicant_info = applicant).get()
具体问题点如下:
- 这行查询代码放置在视图函数的请求类型分支判断之前,无差别对所有进入视图的请求执行查询逻辑。当当前登录用户从未创建过任何
ApplicantEducation教育档案时,ORM的.get()方法因为找不到匹配记录,会直接抛出DoesNotExist异常 - 原代码写的异常捕获块,位于GET分支下的表单初始化逻辑内部,根本覆盖不到这行前置查询代码的异常抛出位置——用户首次访问页面时,异常触发点还没走到try块就已经栈报错,完全走不到兜底的空表单初始化逻辑
- 冗余逻辑问题:
.filter(查询条件).get()的写法和直接get(查询条件)完全等价,属于无意义的链式调用 - 业务逻辑bug:POST分支下,当表单校验不通过时,代码会强制重置为无数据的
EducationForm()实例,丢失用户已经填写的所有提交内容,导致校验失败后页面回显的表单是空的 - 能力绕开问题:原代码在GET分支里手动写了一堆initial字段映射来预填表单值,完全绕开了ModelForm通过
instance参数自动预填字段的核心能力,维护成本极高
可落地修复方案
核心调整思路:把原来“必须查询到存在的教育记录才能进页面”的强制逻辑,改成“查得到就做更新,查不到就做新建”的兼容逻辑,具体修改点:
- 用
.first()替代.get()查询教育记录,查不到时返回None而不是抛异常 - 把教育记录的查询逻辑和后续表单初始化、提交逻辑打通,有记录就绑定实例做更新,没记录就新建
- 修正POST校验失败时的表单重置bug,保留用户提交的内容做回显
- 用ModelForm原生的
instance参数替代手动写的initial字段映射,减少冗余代码
修复后的完整视图代码:
def UpdateEducation(request): context = {} user_obj = request.user if not user_obj.is_authenticated: return redirect('login') # 关联查询用户的Applicant、ProfileInfo记录,处理空对象的边界情况 user_id = Applicant.objects.filter(app_id=user_obj.app_id).first() if not user_id: return redirect('profile') applicant = ProfileInfo.objects.filter(user=user_id).first() if not applicant: return redirect('profile') # 尝试查询当前用户的教育档案,查不到返回None,不抛异常 user_info = ApplicantEducation.objects.filter(applicant_info=applicant).first() if request.POST: # 有历史记录就绑定instance做更新,没有就传None走新建流程 form = EducationForm(request.POST, instance=user_info) if form.is_valid(): obj = form.save(commit=False) # 强制绑定当前用户关联的applicant对象,避免前端篡改外键值 obj.applicant_info = applicant obj.save() return redirect('profile') else: # 校验失败时保留用户提交的内容回显,不重置空表单 context['education_form'] = form else: # GET请求:有记录就自动通过instance预填所有字段,无记录就返回空表单 form = EducationForm(instance=user_info) context['education_form'] = form return render(request, 'admission/signup.html', context)
配套代码优化建议
- 模型层修正:
FloatField不支持max_length参数,原代码中marks_percentage = models.FloatField(max_length=5, default=0.0)的写法在执行迁移时会触发Django警告,应该移除max_length=5参数 - 表单层修正:原
EducationForm里的clean方法写了if self.is_valid()的判断是无效逻辑——Django触发clean流程时才会执行is_valid()方法,进clean方法的时候校验流程还没启动,里面调用self.is_valid()会造成递归校验的问题,应该去掉这个无效的if分支,直接读取self.cleaned_data做自定义校验即可 - 代码健壮性:原代码用裸
try...except捕获所有异常,会隐藏包括语法错误、数据库连接错误在内的所有异常信息,不利于生产环境排查问题,用.first()替代.get()的方案可以从根源上避免DoesNotExist类异常,不需要依赖宽泛的异常捕获做兜底
内容的提问来源于stack exchange,提问作者Asif Iqbal
相关产品推荐
相关产品推荐

