Django会话存储表单cleaned_data报错:Country对象无法JSON序列化
解决Django Session中模型实例无法JSON序列化的问题
嘿,我来帮你搞定这个序列化错误!问题出在你把Country模型实例直接存进了Session里——Django默认用JSON来序列化Session数据,而JSON根本不认识Django的模型对象,所以才会抛出Object of type Country is not JSON serializable的错误。另外,你的代码里还有两个隐藏的坑,我一起帮你解决掉。
问题拆解
- Session序列化限制:JSON只能序列化字符串、数字、列表、字典这些基础类型,模型实例是复杂对象,直接存肯定报错。
- 类属性共享风险:你在
PostWizardStepOne里定义的wizard_data = {}是类属性,所有请求会共享这个字典,多个用户同时提交表单时,数据会互相覆盖,这绝对不是你想要的。 - URL路径冲突:你的两个步骤用了同一个
path('post/', ...),Django会按顺序匹配URL,第二个视图永远不会被触发。
分步解决方案
1. 修复URL路径冲突
先把每个步骤的URL改成不同的路径,确保Django能正确匹配到对应的视图:
# urls.py path('post/step1/', PostWizardStepOne.as_view(), name='PostWizardFirstStep'), path('post/step2/', PostWizardStepTow.as_view(), name='PostWizardSecondStep'),
然后在重定向的时候用reverse生成正确的URL(避免硬编码):
from django.urls import reverse # 在PostWizardStepOne的post方法里 return HttpResponseRedirect(reverse('PostWizardSecondStep'))
2. 替换类属性为Session字典
把类属性wizard_data去掉,直接从Session中获取和更新数据,这样每个用户的数据都是独立的:
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获取已有的数据,作为表单初始值 saved_data = request.session.get('wizard_data', {}) # 外键字段用ID设置初始值 initial = {k: v for k, v in saved_data.items()} 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(): # 先获取Session中已有的向导数据,没有就初始化空字典 wizard_data = request.session.get('wizard_data', {}) # 遍历表单清理后的数据,把模型实例转成主键ID for k, v in form.cleaned_data.items(): # 判断是否是模型实例(外键字段) if isinstance(v, models.Model): wizard_data[k] = v.id # 存主键ID,JSON可序列化 else: wizard_data[k] = v # 更新Session request.session['wizard_data'] = wizard_data request.session.modified = True return HttpResponseRedirect(reverse('PostWizardSecondStep')) return render(request, self.template_name, {'form': form})
3. 在后续步骤中使用Session数据
比如第二步视图可以正常获取Session里的ID数据,还能关联表单显示初始值:
class PostWizardStepTow(View): template_name = "towns/salehslist/forms/jobPostForm.html" form_class = JobForm def get(self, request, *args, **kwargs): wizard_data = request.session.get('wizard_data', {}) # 如果需要显示第一步的选择名称,通过ID查询模型 if 'country' in wizard_data: country = Country.objects.get(id=wizard_data['country']) print(f"Selected country: {country.name}") # 把Session数据作为表单初始值 initial = {k: v for k, v in wizard_data.items()} 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 = request.session.get('wizard_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 # 重定向到第三步或者最终保存页面 return HttpResponseRedirect(reverse('PostWizardThirdStep')) return render(request, self.template_name, {'form': form})
4. 最终保存数据到数据库
在最后一步,把Session里的所有ID转换成模型实例,创建完整的Job对象:
class PostWizardFinalStep(View): def post(self, request, *args, **kwargs): # 处理图片表单(如果有) image_form = JobImagesForm(request.POST, request.FILES) if image_form.is_valid(): wizard_data = request.session.get('wizard_data', {}) # 创建Job对象,通过ID关联外键 job = Job.objects.create( posted_by=request.user, employer=wizard_data['employer'], country=Country.objects.get(id=wizard_data['country']), province=Province.objects.get(id=wizard_data['province']), city=City.objects.get(id=wizard_data['city']), category=Category.objects.get(id=wizard_data['category']), sub_category=SubCategory.objects.get(id=wizard_data['sub_category']), title=wizard_data['title'], description=wizard_data['description'], Experience=wizard_data['Experience'], Education=wizard_data['Education'], compensation=wizard_data['compensation'], employment_type=Employment_type.objects.get(id=wizard_data['employment_type']) ) # 保存图片 image = image_form.cleaned_data['image'] JobImages.objects.create(job=job, image=image) # 清除Session中的向导数据 del request.session['wizard_data'] return HttpResponseRedirect(reverse('ad_success')) # 处理表单错误逻辑...
额外提示
- 如果需要在模板中显示第一步选择的名称(比如国家名),可以存一个包含ID和名称的字典,比如:
这样既可以序列化,又能直接在模板中显示名称。wizard_data['country'] = {'id': v.id, 'name': v.name} - 不要用
PickleSerializer来序列化Session(虽然能存模型实例),因为Pickle存在安全风险,可能被恶意利用执行代码。
内容的提问来源于stack exchange,提问作者Saleh
相关产品推荐
相关产品推荐

