I have three models Player, Team, and Membership, Where Player and Team have many-to-many relationship using Membership as intermediary model.
class Player(models.Model):
name = models.CharField(max_length=254)
rating = models.FloatField(null=True)
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
update_ts = models.DateTimeField(auto_now_add=True, blank=True)
class Team(models.Model):
name = models.CharField(max_length=254)
rating = models.FloatField(null=True)
players = models.ManyToManyField(
Player,
through='Membership',
through_fields=('team', 'player'))
is_active = models.BooleanField(default=True)
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
update_ts = models.DateTimeField(auto_now_add=True, blank=True)
class Membership(models.Model):
team = models.ForeignKey('Team')
player = models.ForeignKey('Player')
#date_of_joining = models.DateTimeField()
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
update_ts = models.DateTimeField(auto_now_add=True, blank=True)
Now I was required to update this membership using django rest framework. I tried update those using Writable nested serializers by writing a custom .update()
of team serializer.
@transaction.atomic
def update(self, instance, validated_data):
'''
Cutomize the update function for the serializer to update the
related_field values.
'''
if 'memberships' in validated_data:
instance = self._update_membership(instance, validated_data)
# remove memberships key from validated_data to use update method of
# base serializer class to update model fields
validated_data.pop('memberships', None)
return super(TeamSerializer, self).update(instance, validated_data)
def _update_membership(self, instance, validated_data):
'''
Update membership data for a team.
'''
memberships = self.initial_data.get('memberships')
if isinstance(membership, list) and len(memberships) >= 1:
# make a set of incoming membership
incoming_player_ids = set()
try:
for member in memberships:
incoming_player_ids.add(member['id'])
except:
raise serializers.ValidationError(
'id is required field in memberships objects.'
)
Membership.objects.filter(
team_id=instance.id
).delete()
# add merchant member mappings
Membership.objects.bulk_create(
[
Membership(
team_id=instance.id,
player_id=player
)
for player in incoming_player_ids
]
)
return instance
else:
raise serializers.ValidationError(
'memberships is not a list of objects'
)
Now this works well for updating values in database for membership table. Only problem I am facing is that I am not able to update prefetched instance in memory, which on PATCH request to this API updates values in database but API response is showing outdated data.
Next GET request for the same resource gives updated data. Anyone who have worked with many-to-many relation in django and wrote custom update/create methods for writable nested serializers can help me to understand the possible way of solving this problem.
The docs on using to_representation is somewhat short. This method is used by Django Rest Framework 3.0+ to change the representation of your data in an API.
SlugRelatedField. SlugRelatedField may be used to represent the target of the relationship using a field on the target. For example, the following serializer: class AlbumSerializer(serializers. ModelSerializer): tracks = serializers.
I think you have to "reload" the players field with instance.players.all()
, because it the queryset is already evaluated in the is_valid
method of the serializer.
@transaction.atomic
def update(self, instance, validated_data):
'''
Cutomize the update function for the serializer to update the
related_field values.
'''
if 'memberships' in validated_data:
instance = self._update_membership(instance, validated_data)
# remove memberships key from validated_data to use update method of
# base serializer class to update model fields
validated_data.pop('memberships', None)
instance.players.all()
return super(TeamSerializer, self).update(instance, validated_data)
I was able to track down the issue in this case. I was using .prefetch_related()
on queryset which is causing instance to use prefetched data for many to many relations. I have got two solution which may cause a few more database hits to fetch updated data.
.prefetch_related()
One obvious way is not to use .prefetch_related()
Which is not recommended as it will result into high number of DB hits.
instance = self.context['view'].get_queryset().get(
id=instance.id
)
Modified .update()
of TeamSerializer
@transaction.atomic
def update(self, instance, validated_data):
'''
Cutomize the update function for the serializer to update the
related_field values.
'''
if 'memberships' in validated_data:
instance = self._update_membership(instance, validated_data)
# remove memberships key from validated_data to use update method of
# base serializer class to update model fields
validated_data.pop('memberships', None)
# fetch updated model instance after updating many-to-many relations
instance = self.context['view'].get_queryset().get(
id=instance.id
)
return super(TeamSerializer, self).update(instance, validated_data)
If someone have better approach I would appreciate if they can add an answer to this question.
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