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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:06:02