I have a property inside a Django model, I have to show it inside the serializer. I put the field inside the serializer, but it's not coming up in the response.
class Example(models.Model):
field_1 = models.ForeignKey(
Modelabc, on_delete=models.CASCADE, null=True, related_name="abc"
)
field_2 = models.ForeignKey(
Modelxyz,
on_delete=models.CASCADE,
null=True,
related_name="xyz",
)
name = models.CharField(max_length=25, blank=True)
#property
def fullname(self):
if self.name is not None:
return "%s%s%s" % (self.field_1.name, self.field_2.name, self.name)
return "%s%s" % (self.field_1.name, self.field_2.name)
Serializer is like this:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.ReadonlyField()
class Meta:
model = Example
fields = [
"id",
"fullname",]
When I call the get API for this, the fullname is not being displayed in the api response. What is the issue?
#property attributes are not included in Django Serializer fields as only Django model fields are shown. I normally use the following workaround for this.
Create a SerializerMethodField.
Return object.property from the method.
So, your Serializer class would be:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.SerializerMethodField()
class Meta:
model = OnlineClass
fields = [
"id",
"fullname",
]
def get_fullname(self, object):
return object.fullname
I think, in ExampleSerializer class, the model should be Example not OnlineClass and the fields should contain all the fields inside the model.
Related
Newbie to DRF and have a model called posts. And another called user. The post object looks as follows:
class Post(models.Model):
"""
Post model
"""
title = models.CharField(max_length=250)
body = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='forum_posts')
parent_post = models.ForeignKey('self',
on_delete=models.CASCADE,
blank=True,
null=True)
time_stamp = models.DateTimeField(default=timezone.now)
objects = models.Manager()
The serializer for this model is:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Post
fields = ('id', 'title', 'body', 'parent_post', 'author', 'time_stamp')
extra_kwargs = {'id': {'read_only': True},
'author': {'read_only': True}}
When returning data for this model, I want to add an extra attribute to each object within the query set called "author_username". The username should be the username belonging to the post's author id. I also want to do this without modifying the model to add another attribute such as "author_username" since this'll be redundant (already have an FK for author). So, ideally, the json for an object would look like:
'post_id': 1
'post_title': 'Example post'
'post_body': 'Example post'
'author_id': 1
'parent_post_id': null
'time_stamp': '2022'
'author_username': 'testUser'
How can I go about doing this?
Here's my view:
class PostList(generics.ListCreateAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = models.Post.objects.all()
serializer_class = serializers.PostSerializer
The source argument can be passed to a serializer field to access an attribute from a related model
class PostSerializer(serializers.ModelSerializer):
author_username = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = models.Post
...
You should add a select_related call to your view's queryset
class PostList(generics.ListCreateAPIView):
...
queryset = models.Post.objects.select_related('author')
...
I want many to many fields to be displayed in module serializer instead of id, these are my serializers
class TrainerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', ]
class ModuleSerializer(serializers.ModelSerializer):
trainer = serializers.CharField(source='trainer.username')
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer',
'publish_choice']
class Trainer(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
def __str__(self):
return str(self.user)
class Meta:
ordering = ['pk']
class Module(models.Model):
title = models.CharField(max_length=80, unique=True)
duration = models.IntegerField(verbose_name='Duration in Days/ Weeks', blank=True, null=True)
trainer = models.ManyToManyField(Trainer, blank=True)
detail = models.TextField(verbose_name='Program Details', blank=True, null=True)
notify = models.BooleanField(default=False)
publish_choice = models.CharField(verbose_name='Publish/ Draft',
max_length=80, choices=PUBLISH_CHOICES, default='publish')
and this is the error message
Got AttributeError when attempting to get a value for field trainer on serializer ModuleSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Module instance.
Original exception text was: 'ManyRelatedManager' object has no attribute 'username'.
We have a depth parameter in the serializer MetaClass. we can make use of it like below. depth=1 will retrieve all fields of a relation.
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer', 'publish_choice']
depth = 1
for reference DRF-Documentation on serializers
Its raise exception because serializers.CharField(source='trainer.username') not match ManyRelatedManager in model trainer = models.ManyToManyField(Trainer, blank=True).
If you want get all username instead of id, you can try add Custom type serialzier like this:
class ModuleSerializer(serializers.ModelSerializer):
trainer = serializers.SerializerMethodField()
def get_trainer(self, obj):
results = []
for item in obj.trainers.all():
results.append(item.username)
return results
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer', 'publish_choice']
trainer will return array of username relation with Module
I have two models:
class Restaurant(models.Model):
adress = models.CharField(max_length=240)
name = models.CharField(max_length=140)
class RestaurantReview(models.Model):
review_author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
I use DRF and front-end I need the values of the fields to use in Vue.je templates. Here is my serializer:
class RestaurantReviewSerializer(serializers.ModelSerializer):
restaurant_name = serializers.CharField(source='restaurant.name')
restaurant_adress = serializers.CharField(source='restaurant.adress')
created_at = serializers.SerializerMethodField()
review_author = serializers.StringRelatedField(read_only=True)
class Meta:
model = RestaurantReview
fields = ('id','restaurant_name','restaurant_adress','created_at','review_author')
def get_created_at(self, instance):
return instance.created_at.strftime("%d %B, %Y")
I get the right data I need but my problem is now I can't update/create new models. As suggested I added ('read_only'=True) but the result is the same.
Should I use to_representation to get the same CRUD posibilities than with:
class RestaurantReviewSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantReview
field = fields = '__all__'
But with the benefit to have for exemple 'restaurant' named after its name and not its ID so I can use it in my template?
Follow to comment above.
Use single viewset and override get_serializer_class. No other thing to change.
class RestaurantReviewViewSet(viewsets.ModelViewSet):
queryset = RestaurantReview.objects.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return RestaurantReviewGETSerializer # your above serializer
else:
return RestaurantReviewSerializer # default serializer
I have two models in my models.py. I need to return a json response which includes data from two tables.
How should my view and serializer look like?
class Device(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_name = models.CharField(max_length=200, null=True, blank=True )
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_name)
class StatusActivity(models.Model):
OFFLINE = 1
ONLINE = 2
STATUS = (
(OFFLINE, ('Offline')),
(ONLINE, ('Online')),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_id = models.ForeignKey(Device, related_name='StatusActivity', on_delete=models.CASCADE)
changed_to = models.PositiveSmallIntegerField(choices=STATUS)
modified_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_id)
Expected Response:
{
"device_id":"",
"device_name":"",
"changed_to":"",
"modified_at":"",
}
UPDATE:
I set my views.py and serializer.py as below. I am checking
Serializer.py
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = '__all__'
class StatusActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StatusActivity
fields = '__all__'
class ListSerializer(serializers.Serializer):
# devices = DeviceSerializer(many=True)
# activities = StatusActivitySerializer(many=True)
class Meta:
model = [Device, StatusActivity]
fields = ['device_id', 'device_name', 'changed_to', 'modified_at']
Views.py
class DeviceListView(generics.ListAPIView):
queryset = Device.objects.all()
serializer_class = ListSerializer
class StatusActivityListView(generics.ListAPIView):
queryset = StatusActivity.objects.all()
serializer_class = StatusActivitySerializer
Actually you don't need to have two separated views for this, because you can easily serialize relations from one serializer class.
Take a look at this useful answer: How do I include related model fields using Django Rest Framework?
For your case you can write something like this:
class StatusActivitySerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device_id.device_name')
class Meta:
model = StatusActivity
fields = ('changed_to', 'modified_at', 'device_id', 'device_name')
Something that worth to note:
it's a good idea for ForeignKey field use device name instead of
device_id;
related_name arg should have a name for reverse access. Keep it
meaningful, e.g. status_activities is a good choice.
filter the status activity. you can refer the foreignkey.
eg:
items = []
for statusact in StatusActivity.objects.all():
items.append({
"device_id":statusact.device_id.id,
"device_name":statusact.device_id.device_name,
"changed_to":statusact.changed_to,
"modified_at":statusact.modified_at,
})
I use django rest in my project and until now for list of objects I used ListSerializer, when I needed to have min length and max length of list I googled and reached to ListField.
Before that my code worked fined without any error and misbehavior. Now I use ListField for my list field serializer, But I didn't get when to use ListSerializer? Can someone explain the difference between ListSerializer and FieldSerializer?
My sample code with ListSerializer:
tags = serializers.ListSerializer(child=serializers.CharField(allow_blank=False), required=False)
My sample code with ListField:
open_hour = serializers.ListField(child=serializers.DictField(), max_length=7, min_length=7)
Disclaimer: This answer is not complete
Can someone explain the difference between ListSerializer and
FieldSerializer?
I assume the question is difference between serializers.ListSerializer and serializers.ListField
Suppose we have two models as
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
and serializer as
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class MusicianSerializer(serializers.ModelSerializer):
AlbumSerializer(many=True, source='album_set')
class Meta:
fields = '__all__'
model = Musician
ListSerializer
As stated in official DRF doc
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
For example, we could re-write the MusicianSerializer with ListSerializer as
class MusicianSerializer(serializers.ModelSerializer):
albums = serializers.ListSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
it would produce the results same as before. But, if we are trying to use ListField instead of ListSerializer It will raise an error
'RelatedManager' object is not iterable
When I checked the source code, I found that both ListSerializer and ListField are inherited from the same class (parent and grand parent are same)
I ran into this same problem and I believe I found a solution!
The trick is you need to create a new Serializer that inherits the ListSerializer class and override the to_representation() method to output your desired format.
If you look at the DRF source code for ListSerializer you can see the default to_representation() method looks like the following...
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
Example
models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
serializers
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class AlbumKeyValueSerializer(serializers.ListSerializer):
def to_representation(self, data):
reaction_count_set = {}
for item in data.all():
reaction_count_set[item.name] = item.artist
return reaction_count_set
class MusicianSerializer(serializers.ModelSerializer):
AlbumKeyValueSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician