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

Django REST Framework创建职位时NOT NULL约束失败求助

解决Django REST Framework中NOT NULL constraint failed: job_category_id错误

问题核心原因

  • 模型字段关系不匹配:PostAJob模型中job_category、job_location、job_level定义为ForeignKey(单个关联对象),但传入的JSON是数组(多个关联对象),序列化器也设置了many=True,逻辑完全冲突。
  • 创建顺序错误:序列化器create方法中先弹出job_category等关联数据,再创建PostAJob实例,此时validated_data中无job_category值,触发模型非空约束报错。
  • ForeignKey误用多对多操作:job_skills、job_location是ForeignKey,但代码中调用.add()方法(仅ManyToManyField支持),后续还会引发其他错误。

修复步骤

1. 修正模型字段关系

将需要支持多选择的字段改为ManyToManyField,修改models.py中的PostAJob类:

class PostAJob(models.Model):
    job_title = models.CharField(max_length=200)
    # 改为多对多,支持多个分类
    job_category = models.ManyToManyField(Category)
    # 改为多对多,支持多个技能
    job_skills = models.ManyToManyField(JobSkills, blank=True)
    job_salary_range = models.IntegerField(blank=True)
    job_description = models.TextField()
    job_type = models.ForeignKey(JobType, on_delete=models.CASCADE)
    # 改为多对多,支持多个地点
    job_location = models.ManyToManyField(JobLocations)
    # 改为多对多,支持多个级别
    job_level = models.ManyToManyField(JobLevel)
    job_application_link = models.URLField(max_length=200)
    company_name = models.CharField(max_length=200)
    company_hq = models.CharField(max_length=200)
    company_logo = models.ImageField()
    companys_website = models.URLField(max_length=200)
    company_contact_email = models.EmailField(max_length=200, null=True, blank=True)
    company_description = models.TextField()
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(Profile, on_delete=models.CASCADE)

    def __str__(self):
        return self.job_title

注意:ManyToManyField不需要on_delete参数,同时移除job_location无效的default=''设置。

2. 修正序列化器的create方法

调整创建顺序,先创建PostAJob实例,再处理多对多关联,用get_or_create避免重复创建已存在的关联对象:

class PostAJobSerializer(serializers.ModelSerializer):
    job_category = CategorySerializer(many=True)
    job_skills = JobSkillsSerializer(many=True)
    job_type = JobTypeSerializer()
    job_location = JobLocationsSerializer(many=True)
    job_level = JobLevelSerializer(many=True)
    # created_by设为只读,在视图层赋值
    created_by = ProfileSerializer(read_only=True)

    class Meta:
        model = PostAJob
        fields = ['id', 'job_title', 'job_category', 'job_skills', 'job_salary_range', 'job_description', 'job_type', 'job_location', 'job_level', 'job_application_link',  'company_name', 'company_hq', 'companys_website', 'company_contact_email', 'company_description', 'date_created', 'date_updated', 'created_by']

    def create(self, validated_data):
        # 弹出关联数据
        job_category_data = validated_data.pop('job_category')
        job_skills_data = validated_data.pop('job_skills')
        job_type_data = validated_data.pop('job_type')
        job_location_data = validated_data.pop('job_location')
        job_level_data = validated_data.pop('job_level')
        
        # 创建职位主实例
        post_a_job = PostAJob.objects.create(**validated_data)

        # 处理job_category多对多
        for category_data in job_category_data:
            category, _ = Category.objects.get_or_create(id=category_data.get('id'), defaults=category_data)
            post_a_job.job_category.add(category)

        # 处理job_skills多对多(需确保JobSkills模型的category字段为多对多)
        for skills_data in job_skills_data:
            skills_category_data = skills_data.pop('category')
            skills, _ = JobSkills.objects.get_or_create(id=skills_data.get('id'), defaults=skills_data)
            for cat_data in skills_category_data:
                cat, _ = Category.objects.get_or_create(id=cat_data.get('id'), defaults=cat_data)
                skills.category.add(cat)
            post_a_job.job_skills.add(skills)

        # 处理job_type外键
        job_type, _ = JobType.objects.get_or_create(id=job_type_data.get('id'), defaults=job_type_data)
        post_a_job.job_type = job_type
        post_a_job.save()

        # 处理job_location多对多
        for location_data in job_location_data:
            location, _ = JobLocations.objects.get_or_create(id=location_data.get('id'), defaults=location_data)
            post_a_job.job_location.add(location)

        # 处理job_level多对多
        for level_data in job_level_data:
            level, _ = JobLevel.objects.get_or_create(id=level_data.get('id'), defaults=level_data)
            post_a_job.job_level.add(level)

        return post_a_job

3. 修正视图层的用户赋值

在视图中为created_by字段赋值当前登录用户:

@api_view(['POST'])
def post_a_job(request):
    if request.method == 'POST':
        # 传入request上下文
        post_a_job_serializer = PostAJobSerializer(data=request.data, context={'request': request})
        if post_a_job_serializer.is_valid():
            # 手动赋值创建者
            post_a_job = post_a_job_serializer.save(created_by=request.user.profile)
            return Response(post_a_job_serializer.data, status=status.HTTP_201_CREATED)
        return Response(post_a_job_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

4. 收尾操作

  • 确保JobSkills模型已正确定义(需保证其category字段为多对多关系)。
  • 运行数据库迁移命令更新表结构:
    python manage.py makemigrations
    python manage.py migrate
    

内容的提问来源于stack exchange,提问作者Chuks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 08:19:54