djangopostgresqldjango-rest-frameworkbulkinsertupsert

Unique Constraint Failed In Upsert When Calling bulk_create with update_conficts


Im facing a unique constraint failed error with django. The objective of the api is, eithering creating or updating marks of the student, based on subject variation, exam_results and in bulk. For that, I've used bulk create with update conflicts flag.

Here is the current model

class Marks(Common):
    exam_results = models.ForeignKey(
        "ExamResults", on_delete=models.CASCADE, related_name="marks"
    )
    subject_variation = models.ForeignKey(
        "SubjectVariation", on_delete=models.CASCADE, related_name="marks"
    )
    student = models.ForeignKey(
        "Student", on_delete=models.CASCADE, related_name="marks"
    )
    marks_obtained = models.FloatField()
    objects = BaseModelManager()class Marks(Common):

now, when I do bulk create, it asks for unique fields, and since I only want to update marks if there is same exam results, subject variation and student in another instance. So, i add that to unique fields of bulk create.

class MarksUpsertSerializer(serializers.ModelSerializer):
    class Meta:
        model = Marks
        fields = ("exam_results", "subject_variation", "marks_obtained")

class BulkMarksUpsertSerializer(serializers.Serializer):
    marks = MarksUpsertSerializer(many=True)

    def create(self, validated_data):
        marks = [Marks(**item) for item in validated_data["marks"]]
        marks = Marks.objects.bulk_create(
            marks,
            update_conflicts=True,
            update_fields=["marks_obtained"],
            unique_fields=["exam_results", "subject_variation"],
        )
        return marks

but when I do this, it says there is no unique or exclusion constraint matching the ON CONFLICT specification.

I assumed its because there's no constraint thats been provided to say that those fields must be unique together, so I added a constraint on the marks model.

class Meta:
    constraints = [
        models.UniqueConstraint(
            fields=["subject_variation", "exam_results"], name="unique_marks"
        )
    ]

After i make migrations and migrate, the bulk create aspect works, and creates two new instance of marks. Here is the payload for reference.

 {
    "marks": [
    {
        "exam_results" : "1",
        "subject_variation" : "1",
        "marks_obtained" : 40,
    },
    {
        "exam_results" : "1",
        "subject_variation" : "2",
        "marks_obtained" : 20,
    }
    
  ]
}

Now, since I want to update the marks again, I should call this api again. However, I get the error:

{
    "marks": [
        {
            "non_field_errors": [
                "The fields subject_variation, exam_results must make a unique set."
            ]
        },
        {
            "non_field_errors": [
                "The fields subject_variation, exam_results must make a unique set."
            ]
        }
    ]
}

It is supposed to be updating, if those unique constraints are there, but I get this error. How to deal with this? If i comment out the constraints from the model, it seems to work again. It even works when creating new instances. This is working until I makemigration and migrate again, as the meta class gets changed for the model. Then, it goes back to the same error.

Thank you.


Solution

  • It got fixed by disabling the validators in django rest framework,

    class MarksUpsertSerializer(serializers.ModelSerializer):
        class Meta:
            model = Marks
            fields = ("exam_results", "subject_variation", "marks_obtained", "student")
    
    
    class BulkMarksUpsertSerializer(serializers.Serializer):
        marks = MarksUpsertSerializer(many=True, validators=[]) # <-- here
    
    

    by removing the default validators by overwriting with an empty array, the model validator got disabled preventing the following query:

    SELECT 1 AS "a" FROM "academics_marks" WHERE ("academics_marks"."is_active" AND "academics_marks"."exam_results_id" = 1 AND "academics_marks"."subject_variation_id" = 2) LIMIT 1; args=(1, 1, 2); alias=default```