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

Django会话存储表单cleaned_data报错:Country对象无法JSON序列化

解决Django Session中模型实例无法JSON序列化的问题

嘿,我来帮你搞定这个序列化错误!问题出在你把Country模型实例直接存进了Session里——Django默认用JSON来序列化Session数据,而JSON根本不认识Django的模型对象,所以才会抛出Object of type Country is not JSON serializable的错误。另外,你的代码里还有两个隐藏的坑,我一起帮你解决掉。

问题拆解

  1. Session序列化限制:JSON只能序列化字符串、数字、列表、字典这些基础类型,模型实例是复杂对象,直接存肯定报错。
  2. 类属性共享风险:你在PostWizardStepOne里定义的wizard_data = {}是类属性,所有请求会共享这个字典,多个用户同时提交表单时,数据会互相覆盖,这绝对不是你想要的。
  3. 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

相关产品推荐
方舟 Agent Plan

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

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