'How can I show the StringRelatedField instead of the Primary Key while still being able to write-to that field using Django Rest Framework?
Models:
class CrewMember(models.Model):
DEPARTMENT_CHOICES = [
("deck", "Deck"),
("engineering", "Engineering"),
("interior", "Interior")
]
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
email = models.EmailField()
department = models.CharField(max_length=12, choices=DEPARTMENT_CHOICES)
date_of_birth = models.DateField()
join_date = models.DateField()
return_date = models.DateField(null=True, blank=True)
leave_date = models.DateField(null=True, blank=True)
avatar = models.ImageField(null=True, blank=True)
active = models.BooleanField(default=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class RosterInstance(models.Model):
date = models.DateField(default=timezone.now)
deckhand_watchkeeper = models.ForeignKey(CrewMember, on_delete=models.PROTECT, null=True, related_name="deckhand_watches")
night_watchkeeper = models.ForeignKey(CrewMember, on_delete=models.PROTECT, null=True, related_name="night_watches")
def __str__(self):
return self.date.strftime("%d %b, %Y")
Views:
class CrewMemberViewSet(viewsets.ModelViewSet):
queryset = CrewMember.objects.all()
serializer_class = CrewMemberSerializer
filter_backends = [SearchFilter]
search_fields = ["department"]
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.active = False
instance.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class RosterInstanceViewSet(viewsets.ModelViewSet):
queryset = RosterInstance.objects.all()
serializer_class = RosterInstanceSerializer
Serializers:
class CrewMemberSerializer(serializers.ModelSerializer):
class Meta:
model = CrewMember
fields = "__all__"
class RosterInstanceSerializer(serializers.ModelSerializer):
class Meta:
model = RosterInstance
fields = "__all__"
The resulting data looks like this:
{
"id": 2,
"date": "2020-12-09",
"deckhand_watchkeeper": 1,
"night_watchkeeper": 3
}
But I want it to look like this:
{
"id": 2,
"date": "2020-12-09",
"deckhand_watchkeeper": "Joe Soap",
"night_watchkeeper": "John Smith"
}
I can achieve the above output by using StringRelatedField in the RosterInstanceSerializer but then I can no longer add more instances to the RosterInstance model (I believe that is because StringRelatedField is read-only).
Solution 1:[1]
Because StringRelaredField
is always read_only, you can use SlugRelatedField instead:
class RosterInstanceSerializer(serializers.ModelSerializer):
deckhand_watchkeeper = serializers.SlugRelatedField(
slug_field='deckhand_watchkeeper'
)
night_watchkeeper = serializers.SlugRelatedField(
slug_field='night_watchkeeper'
)
class Meta:
model = RosterInstance
fields = ['id', 'date', 'deckhand_watchkeeper', 'night_watchkeeper']
Solution 2:[2]
I was created a WritableStringRelatedField
to do that.
class WritableStringRelatedField(serializers.SlugRelatedField):
def __init__(self, display_field=None, *args, **kwargs):
self.display_field = display_field
# Set what attribute to be represented.
# If `None`, use `Model.__str__()` .
super().__init__(*args, **kwargs)
def to_representation(self, obj):
# This function controls how to representation field.
if self.display_field:
return getattr(obj, self.display_field)
return str(obj)
def slug_representation(self, obj):
# It will be called by `get_choices()`.
return getattr(obj, self.slug_field)
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
# Ensure that field.choices returns something sensible
# even when accessed with a read-only field.
return {}
if cutoff is not None:
queryset = queryset[:cutoff]
return OrderedDict([
(
self.slug_representation(item),
# Only this line has been overridden,
# the others are the same as `super().get_choices()`.
self.display_value(item)
)
for item in queryset
])
Serializers:
class RosterInstanceSerializer(serializers.ModelSerializer):
deckhand_watchkeeper = WritableStringRelatedField(
queryset=CrewMember.objects.all(),
slug_field='id',
label='Deckhand Watchkeeper',
)
night_watchkeeper = WritableStringRelatedField(
queryset=CrewMember.objects.all(),
slug_field='id',
label='Night Watchkeeper',
)
class Meta:
model = RosterInstance
fields = "__all__"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Linh Nguyen |
Solution 2 |