I am using DRF and I'm trying to create an object which has several foreign keys as well as related objects that will need creating in the process.
Here's what a reduced version of my models looks like:
class Race(models.Model):
name = models.CharField(max_length=200)
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='races')
type = models.ForeignKey(Type, on_delete=models.SET_NULL, related_name='races', null=True)
region = models.ForeignKey(Region, on_delete=models.CASCADE, verbose_name=_('region'), related_name='races')
country = models.ForeignKey(Country, on_delete=models.CASCADE, related_name='races')
timezone = models.ForeignKey(Timezone, on_delete=models.SET_NULL, null=True)
class Event(models.Model):
name = models.CharField(max_length=200)
race = models.ForeignKey(Race, on_delete=models.CASCADE, related_name='events')
And then here's my Race serializer:
class RaceSerializer(serializers.ModelSerializer):
owner = UserSerializer(read_only=True)
type = TypeSerializer(read_only=True)
events = EventSerializer(many=True)
country = CountrySerializer()
region = RegionSerializer(read_only=True)
timezone = TimezoneSerializer(read_only=True)
def create(self, validated_data):
with transaction.atomic():
events = validated_data.pop('events', None)
race = Race(**validated_data)
race.save()
for event in events:
Event.objects.create(race=race, **event)
return race
And my view:
class AddRaceView(CreateAPIView):
serializer_class = RaceSerializer
permission_classes = (IsAuthenticated,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
And here's some test data I'm sending with my POST request:
{
"name": "ABC Marathon",
"country": {
"pk": 1,
"name": "United States"
},
"region": {
"pk": 1,
"code": "ME"
},
"timezone": {
"pk": 1,
"code": "EST"
},
"events": [
{
"name": "Marathon"
},
{
"name": "Half Marathon"
}
]
}
So the problem I'm having is passing valid data to the serializer for the foreign keys. I don't want to create new objects for Type, Region, Country, Timezone, only pass the appropriate data so the newly created Race object references existing foreign keys correctly.
Here's what I've tried:
1) Not setting read_only=True on the foreign key serializers. This attempts to create new objects that I don't want.
2) Setting read_only=True on the foreign key serializers (as in the code above). This helps with not attempting to create new Type, Region etc objects, but drops the respective fields from validated_data in the serializer create method. So I have no way to add the existing objects to the Race foreign keys on creation.
3) Using PrimaryKeyForeignField instead of TypeSerializer, RegionSerializer etc. But then when I use RaceSerializer to retrieve race data, I only have a pk under type, region etc and I'd really like to be able to retrieve all fields for the foreign keys.
Can you please advise on what the correct setup would look for something like this? I feel like it's shouldn't be as hard as this.
Thank you!
So in the end I solved this by using RelatedField instead of separate serializers for every foreign key, with the exception of the nested EventSerializer that is really required to write nested Event objects.
Here's the RaceSerializer:
class RaceSerializer(serializers.ModelSerializer):
owner = UserSerializer(read_only=True)
type = TypeField()
country = CountryField()
region = RegionField()
timezone = TimezoneField()
events = EventSerializer(many=True)
race_cal_types = serializers.SerializerMethodField()
def create(self, validated_data):
with transaction.atomic():
events = validated_data.pop('events', None)
race = Race(**validated_data)
race.save()
for event in events:
Event.objects.create(race=race, **event)
return race
And here is the combo of RelatedField and ModelSerializer I'm using for each field in my RaceSerializer, e.g. for the region foreign key:
class RegionSerializer(serializers.ModelSerializer):
class Meta:
model = Region
fields = ('pk', 'name', 'code')
class RegionField(RelatedField):
def get_queryset(self):
return Region.objects.all()
def to_internal_value(self, data):
return self.get_queryset().get(**data)
def to_representation(self, value):
return RegionSerializer(value).data
Each field (type, region, country, timezone) has its own to_internal_value and to_representation methods to serializer/deserialize data the way I need to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With