Eranki
Eranki

Reputation: 807

How to override foreign key null values in django serializer?

I'm serializing a query set to json format using natural_keys. Reference: docs

I'm able to serialize data successfully. In case, there are any foreign keys, then I'm also able to add it's object instead of foreign key. For example:

class Parent(models.Model):
    name = models.CharField()

    def get_natural_keys(self):
        return(
                 {'name': self.name, 'pk': self.pk}
        )

class Child(models.Model):
    name = models.CharField()
    parent = models.ForeignKey(Parent, null=True)

And while querying data:

child = serializers.serialize('json', list(Child.objects.all()), user_natural_foreign_keys=True, use_natural_primary_keys=True)

This will return json:

{
    "model": 'proj.child'
    "pk": 1,
    "fields": {
                "name": "child name",
                "parent": {"id": 1, "name": "parent name"}
              }
}

Till this point every thing is fine. My issue is that when parent foreign key is null in child, it returns None in parent:

fields: {
    "name": "child name",
    "parent": None
}

How I am expecting is:

fields: {
    "name": "child name",
    "parent": {"id": None. "name": None}
}

How can I override the None value to another dictionary? One way is to loop through the list of dictionaries and edit it. But, I don't feel it as best one.

[EDIT]

To make my design more specific:

class Person(models.Model):
    name = models.CharField()
    phone = models.CharField()
 
class Building(modls.Model):
    name = models.CharField()
    address = models.CharField()
    build_by = models.ForeignKey(Person, null=False)
    owner = models.ForeignKey(Person)
    residing_by = models.ForeignKey(Person, null=True)

Firstly, I tried to serialize building object and it had foreign keys in serialized data. However, I was not expecting foreign keys in serialized data. Instead, I want another serialized data in place of foreign key.So, I came across get_natural_keys() using which I can serialize foreign key objects. I can customise get_natural_keys() to return a serialized data.

In the above building model, residing_by can be null at some point of time. For that null value in serialized data, I wanted to overwrite null with another dictionary like {'id': None, 'name': None}.

Upvotes: 1

Views: 1570

Answers (3)

user1600649
user1600649

Reputation:

There's a few issues with your setup:

  • The whole point of natural keys is to avoid auto generated data, like auto incrementing primary keys, so that you can identify a record in other databases (production, staging), which may have different insertion orders. Instead, you return an auto generated primary key as natural key.
  • It seems like you're looking to use the serialization framework for something it's not made for or that other packages like Django REST Framework have done better.
  • Your models are not fit for natural keys, because they only have one field and it's not unique, so there is no way to refer to a record without using a primary key.
  • And finally, I don't understand why you need natural keys to begin with. What made you decide to do that?

This is the scenario where child table is not willing to refer for parent table

I'm not sure what that means. You link the child to the parent or you don't. They're not real children and should obey what you program :). If the parent is required, then do not add null=True to the foreign key so that it throws an error and then you know where your programming problem is.

All in all, I think you made a few assumptions about how things work and how you could solve them, but the solutions you've chosen are not the right fit.

As said, you should first figure out why children can be created without parents, if that is not what you want and fix that. Then re-evaluate how serialization should work, because sticking auto ids in natural keys makes no sense. You probably don't need natural keys. If you did this to change the output format, then as others have suggested, DRF gives you better options, but it also comes with a steep learning curve.

Upvotes: 4

eshaan7
eshaan7

Reputation: 1068

I'd recommend using django-rest-framework serializers for such use cases.


from .models import Parent, Child

from rest_framework import serializers

### Define Serializers

class ParentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Parent
        fields = ['id', 'name']
        
class ChildSerializer(serializers.ModelSerializer):
    parent = ParentSerializer()
    
    class Meta:
        model = Child
        fields = ['id', 'name', 'parent']
        
   def to_representation(self, instance):
        # get representation from ModelSerializer
        ret = super(ChildSerializer, self).to_representation(instance)
        # if parent is None, overwrite
        if not ret.get("parent", None):
            ret["parent"] = {"id": None, "name": None}
        return ret
        
        
### example serialization

childs = ChildSerializer(Child.objects.all(), many=True)

print(childs.data)

"""
Output:
[
    {
        "id": 1,
        "name": "example child name",
        "parent": {
            "id": 1,
            "name": "example parent name"
        }
    },
    #...snip..
]
"""

Upvotes: 1

Wector
Wector

Reputation: 389

When I had a similar issue for me something like that worked:

class Chield(models.Model):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.parent:
            self.parent = Parent()
    name = models.CharField()
    parent = models.ForeignKey(Parent, null=True)

Upvotes: 1

Related Questions