How to loop through a jsonfield serializer

Question:

I’ve been trying to loop through the incoming data from a json serializer for a couple of weeks now. Tried a few different approaches, but i can’t seem to loop through the json and return a list with all facility_id’s for example. Ultimately i want to create one leadfacility object for each json item using it’s facility_id and it’s datetime. But I can’t even seem to access the facility_id when using a for loop.
The facilities that are being assigned are already inside the database.

Does anyone know what I’m missing here? How can i loop though "assigned_facilities"? The only thing I am able to return is the entire json data all at once with print(). Or is my json data structured in the wrong way?

class LeadUpdateSerializer(serializers.ModelSerializer):
    assigned_facilities = serializers.JSONField(required=False, allow_null=True, write_only=True)

    def create(self, validated_data):
        
        assigned_facilities = validated_data.pop("assigned_facilities")
        instance = Lead.objects.create(**validated_data)

        for item in assigned_facilities:
            instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get('datetime'))
            
        return instance

json

{
    "assigned_facilities": [{
            "facility_id": "1",
            "datetime": "2018-12-19 09:26:03.478039"
        },
        {
            "facility_id": "1",
            "datetime": "2019-12-19 08:26:03.478039"
        }
    ]
}

models.py

class LeadFacilityAssign(models.Model):
    assigned_facilities = models.ForeignKey(Facility, on_delete=models.CASCADE, related_name='leadfacility')
    lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='leadfacility')
    datetime = models.DateTimeField(null=True, blank=True)

class Facility(models.Model):
    name = models.CharField(max_length=150, null=True, blank=False)

    def __str__(self):
        return self.name

class Lead(models.Model):
    first_name = models.CharField(max_length=40, null=True, blank=True)
    last_name = models.CharField(max_length=40, null=True, blank=True)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

Asked By: Matthias

||

Answers:

you are doing :

for item in assigned_facilities:
        instance.leadfacility.create(assigned_facilities_id=assigned_facilities.get('facility_id'), datetime=assigned_facilities.get('datetime'))

but not using item anywhere.

Answered By: Dimanshu Parihar
for item in assigned_facilities:
    instance.leadfacility.create(assigned_facilities_id=item['facility_id'], datetime=item['datetime'])
        
Answered By: Ben L

When you use pop() it gets and then deletes the data. That is why u are unable to process it in for loop.
Use get() instead of pop(), and ur problem will be solved.
Change this line:

assigned_facilities = validated_data.pop("assigned_facilities")

to:

assigned_facilities = validated_data.get("assigned_facilities")
Answered By: Maz

I’d suggest a different, arguably better approach. Instead of JSONField use a nested model serializer.

class AssignedFacilitySerializer(serializers.ModelSerializer):
    fields = ('assigned_facilities_id', 'datetime')
    model = Facility

class LeadUpdateSerializer(serializers.ModelSerializer):
    assigned_facilities = AssignedFacilitySerializer(many=True)

    def create(self, validated_data):
        assigned_facilities = validated_data.pop("assigned_facilities")
        instance = Lead.objects.create(**validated_data)

        for item in assigned_facilities:
            instance.leadfacility.create(**item)
            
        return instance

You haven’t posted your Facility model, so there may be some improperly named fields, maybe the related_name too (leadfacility).

Answered By: Jovan Vuchkov

I see a few issues in both your models and how you add facilities to a lead in your loop.

Please take a look at https://docs.djangoproject.com/en/4.1/topics/db/models/#extra-fields-on-many-to-many-relationships and edit your models accordingly.

TL;DR

You can then put extra fields on the intermediate model.
This is LeadFacilityAssign in your case.

The intermediate model is associated with the ManyToManyField using the through argument to point to the model that will act as an intermediary.
You are missing this part. Make sure to add a ManyToManyField to Leads.

If you keep reading the docs, you will also find that you can use add(), create(), or set() to create relationships between Lead and Facility using the through model.

You mentioned facilities already exist, so create() is the wrong method.
Please use add() or set() according to your use case and pre-fetch those facilities using their id.

Answered By: gripep