Django 3.0中Session存储表单cleaned_data遇序列化错误的技术问询
解决Django Session分步向导中的"Object of type Country is not JSON serializable"错误
你遇到的核心问题是Django Session默认使用JSON序列化存储数据,但模型实例(比如Country对象)是不可被JSON序列化的。另外你的代码还有几个隐藏坑(比如类属性共享、URL冲突),我一步步帮你解决:
1. 核心问题:模型实例无法序列化到Session
当CommonForm验证通过后,form.cleaned_data里的country、province等字段是对应的模型实例(比如Country对象),而JSON只能序列化基本数据类型(字符串、数字、列表、字典),不能直接存对象。
解决方法:存储模型ID而非实例
修改第一步视图的post方法,把模型实例替换成它们的ID,同时修复类属性共享的问题(类属性wizard_data会被所有请求共享,多个用户访问会互相干扰):
from django.db import models class PostWizardStepOne(View): form_class = CommonForm template_name = "towns/salehslist/ads_main_form.html" def get(self, request, *args, **kwargs): # 从session获取已存数据,用来填充表单初始值(回显用) wizard_data = request.session.get('wizard_data', {}) initial = {} if wizard_data: # 把存储的ID转回模型实例,用于表单初始值 for field in ['country', 'province', 'city', 'category', 'sub_category']: if field in wizard_data: model_class = self.form_class._meta.model._meta.get_field(field).related_model initial[field] = model_class.objects.get(id=wizard_data[field]) form = self.form_class(initial=initial) return render(request, self.template_name, {'form': form}) def post(self, request, *args, **kwargs): form = self.form_class(request.POST) if form.is_valid(): wizard_data = {} # 遍历cleaned_data,模型实例转ID存储,普通值直接存 for k, v in form.cleaned_data.items(): if isinstance(v, models.Model): wizard_data[k] = v.id else: wizard_data[k] = v request.session['wizard_data'] = wizard_data request.session.modified = True # 用reverse生成正确的跳转URL,避免硬编码 return HttpResponseRedirect(reverse('PostWizardSecondStep')) return render(request, self.template_name, {'form': form})
2. 修复URL冲突问题
你的URL配置里两个视图用了同一个路径post/,这会导致PostWizardStepTow永远不会被触发,修改成不同路径:
path('post/step1/', PostWizardStepOne.as_view(), name='PostWizardFirstStep'), path('post/step2/', PostWizardStepTow.as_view(), name='PostWizardSecondStep'), # 后续步骤同理,比如step3
3. 第二步视图中获取并使用Session数据
在第二步视图里,你可以取出Session里的ID,按需转成模型实例,用来填充表单初始值或者后续处理:
class PostWizardStepTow(View): form_class = JobForm template_name = "towns/salehslist/forms/jobPostForm.html" def get(self, request, *args, **kwargs): wizard_data = request.session.get('wizard_data', {}) # 这里可以根据需要给JobForm填充初始值 form = self.form_class() return render(request, self.template_name, {'form': form}) def post(self, request, *args, **kwargs): form = self.form_class(request.POST) if form.is_valid(): # 取出之前的wizard_data,合并当前表单数据 wizard_data = request.session.get('wizard_data', {}) for k, v in form.cleaned_data.items(): if isinstance(v, models.Model): wizard_data[k] = v.id else: wizard_data[k] = v request.session['wizard_data'] = wizard_data request.session.modified = True # 跳转到第三步或者最终保存页 return HttpResponseRedirect(reverse('PostWizardThirdStep')) return render(request, self.template_name, {'form': form})
4. 最终保存数据到数据库
当所有步骤完成后,创建一个视图把Session里的ID转成模型实例,最终创建Job对象并清除Session:
class PostWizardFinalStep(View): def get(self, request, *args, **kwargs): wizard_data = request.session.get('wizard_data', {}) if not wizard_data: return HttpResponseRedirect(reverse('PostWizardFirstStep')) # 把存储的ID转回模型实例,准备创建Job job_data = {} for k, v in wizard_data.items(): field = Job._meta.get_field(k) if field.is_relation: job_data[k] = field.related_model.objects.get(id=v) else: job_data[k] = v # 添加当前登录用户 job_data['posted_by'] = request.user # 创建Job对象 Job.objects.create(**job_data) # 清除Session里的向导数据 del request.session['wizard_data'] return HttpResponseRedirect(reverse('ad_success_page'))
额外注意事项
- 永远不要用类属性存储用户特定数据(比如你之前的
wizard_data类属性),会导致多用户数据混乱,必须存在Session里。 - 如果涉及文件上传(比如
JobImagesForm),文件不能存在Session里,建议临时存储在服务器,或者先创建一个未发布的Job对象,后续步骤逐步更新它,最后标记为正式发布。
内容的提问来源于stack exchange,提问作者Saleh
相关产品推荐
相关产品推荐

