Django REST Framework NOT NULL constraint failed

When I try to create a new Job post by posting the following JSON:

{
  "job_title": "Software Developer",
  "job_category": [
    {
      "id": 1,
      "name": "Engineering"
    },
    {
      "id": 2,
      "name": "IT"
    }
  ],
  "job_skills": [
    {
      "id": 1,
      "title": "DJ",
      "category": [
        {
          "id": 1,
          "name": "Web Development"
        },
        {
          "id": 2,
          "name": "Python"
        }
      ]
    },
    {
      "id": 2,
      "title": "RT",
      "category": [
        {
          "id": 3,
          "name": "Frontend Development"
        },
        {
          "id": 4,
          "name": "JavaScript"
        }
      ]
    }
  ],
  "job_salary_range": 80000,
  "job_description": "We are looking for a skilled software developer proficient in Django and React...",
  "job_type": {
    "id": 1,
    "job_type_choices": "FT"
  },
  "job_location": [
    {
      "id": 1,
      "name": "New York"
    },
    {
      "id": 2,
      "name": "San Francisco"
    }
  ],
  "job_level": [
    {
      "id": 1,
      "job_level_choices": "EL"
    },
    {
      "id": 2,
      "job_level_choices": "ML"
    }
  ],
  "job_application_link": "https://example.com/apply",
  "company_name": "ABC Company",
  "company_hq": "New York",
  "company_logo": "https://example.com/logo.png",
  "companys_website": "https://example.com",
  "company_contact_email": "info@example.com",
  "company_description": "ABC Company is a leading software development company...",
  "date_created": "2023-06-09T12:00:00Z",
  "date_updated": "2023-06-09T14:30:00Z"
}

I get the following error:

IntegrityError at /jobs/create

NOT NULL constraint failed: afritechjobsapi_postajob.job_category_id

MODELS.PY

class Category(models.Model):
    name = models.CharField(max_length=50, unique=True, null=True, blank=True)

    def __str__(self):
        return self.name

class JobLocations(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name
    
class JobType(models.Model):
    class JobTypeChoices(models.TextChoices):
        CONTRACT = 'CT', 'Contract'
        FULLTIME = 'FT', 'FullTime'
        FREELANCE = 'FL', 'Freelance'
        INTERNSHIP = 'IP', 'Internship'
        PARTTIME = 'PT', 'Parttime'
    
    job_type_choices = models.CharField(max_length=2, choices=JobTypeChoices.choices, default=JobTypeChoices.FULLTIME)


class JobLevel(models.Model):
    class JobLevelChoices(models.TextChoices):
        STUDENT = 'ST', 'Student'
        INTERN = 'IN', 'Intern'
        ENTRYLEVEL = 'EL', 'Entrylevel'
        MIDLEVEL = 'ML', 'Midlevel'
        SENIORLEVEL = 'SL', 'Seniorlevel'
        COFOUNDER = 'CF', 'Cofounder'
        DIRECTOR = 'DC', 'Director'
        MANAGER = 'MG', 'Manager'
        CEO = 'CEO', 'ChiefExecutiveOfficer'
        CTO = 'CTO', 'ChiefTechnologyOfficer'
        CMO = 'CMO', 'ChiefMarketingOfficer'
        CFO = 'CFO', 'Chief Financial Officer'
        COO = 'COO', 'Chief Operating Officer'
    
    job_level_choices = models.CharField(max_length=3, choices=JobLevelChoices.choices, default=JobLevelChoices.ENTRYLEVEL)


class PostAJob(models.Model):
    job_title = models.CharField(max_length=200)
    job_category = models.ForeignKey(Category, on_delete=models.CASCADE)
    job_skills = models.ForeignKey(JobSkills, on_delete=models.SET_NULL, null=True, 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.ForeignKey(JobLocations, on_delete=models.CASCADE, default='')
    job_level = models.ForeignKey(JobLevel, on_delete=models.CASCADE)
    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

SERIALIZERS.PY

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name']

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 = ProfileSerializer()
    #companylogotoo

    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')

        # Create the PostAJob instance
        post_a_job = PostAJob.objects.create(**validated_data)

        #create the related instances for job_category
        category = Category.objects.create(**job_category_data)
        post_a_job.job_category = category

        #create the related instances for job_skills
        for skills_data in job_skills_data:
            skills = JobSkills.objects.create(**skills_data)
            post_a_job.job_skills.add(skills)

        # Create the related instance for job_type
        job_type = JobType.objects.create(**job_type_data)
        post_a_job.job_type = job_type

        # Create the related instances for job_location
        for location_data in job_location_data:
            location = JobLocations.objects.create(**location_data)
            post_a_job.job_location.add(location)

        # Create the related instances for job_level
        for level_data in job_level_data:
            level = JobLevel.objects.create(**level_data)
            post_a_job.job_level.add(level)

        return post_a_job
type or paste code here

VIEWS.PY

@api_view(['POST'])
def post_a_job(request):
    if request.method == 'POST':
        post_a_job_serializer = PostAJobSerializer(data=request.data)
        if post_a_job_serializer.is_valid():
            post_a_job_serializer.save()
            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)

From the research done on similar problems, python - Django REST Framework NOT NULL constraint failed - Stack Overflow - this mostly happens when read-only = True, however this is not the case in my end

This means that the job_category_id returns empty or None.
I were with the same error yesterday, and I found that I missed to include the field itself in( def save_user()) so it returned None

I’m sure you will find the missing part in your code.

Search in the views function responsible for this URL("/jobs/create") and be sure if the field is saved correctly or not , Try to print the (request.data) before (is_valid()) and after it and see what is happen

You will find something incorrect

I tried printing this, it returns the same values (before and after is_valid()), I suspect, it is coming from my def create serializer function but I’m yet to find out what is causing it exactly

Have you tried to add

job_category_id_data = validated_data.pop('job_category_id')

Instead Of / Or with

job_category_data = validated_data.pop('job_category')

Thanks cehceh but that didn’t resolve the issue.

I’ve been able to understand why this is happening- I was creating PostAJob object before setting a category, so the category is null.

I’ve done further alterations to adjust for this

 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')

        #Create the related instances for job_category
        for category_item in job_category_data:
            category = Category.objects.create(**category_item)
            validated_data['job_category'] = category

        # Create the related instances for job_skills
        for skills_item in job_skills_data:
            skills = JobSkills.objects.create(**skills_item)
            validated_data['job_skills'] = skills

        # Create the related instances for job_type
        for type_item in job_type_data:
            type = JobType.objects.create(**type_item)
            validated_data['job_type'] = type

        # Create the related instances for job_location
        for location_item in job_location_data:
            location = JobLocations.objects.create(**location_item)
            validated_data['job_location'] = location

        # Create the related instances for job_level
        for level_item in job_level_data:
            level = JobLevel.objects.create(**level_item)
            validated_data['job_level'] = level

                # Create the PostAJob instance
        post_a_job = PostAJob.objects.create(**validated_data)

        return post_a_job

This creates the post and categories, levels, skills etc when I check from the admin panel however it returns with an error

TypeError at /jobs/ ‘Category’ object is not iterable

and when I try to query/get the job posting, I still get the same error again.

Of course because you must loop and iterate for an instance not for a Field(job_category_data = validated_data.pop('job_category'))

Try to loop for category instance itself

Could I possibly get an example of what you mean?

I mean when you use create() method as in the next line
category = Category.objects.create(**job_category_data) you will have an instance of Category model and you can loop and iterate like

for category_item in category:
      ...Do what you want here

For example: imagine if Category model has 3 fields field1, field2,field3
Then you can loop for an object of your Category model (remember that an instance or an “object” is one row of your model that you’ve just created by create() method)

for item in category:
     var1 = item.field1
     var2 = item.field2
     var3 = item.field3

Hope that make sense to fix your issue