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
相关产品推荐
相关产品推荐

