i have the following many to many data structure in my django rest app:
class User(Model):
name = models.CharField(max_length=64)
memberships = models.ManyToManyField('Membership', through='UserMembership', related_name='users')
def __str__(self):
return "{}".format(self.name)
class Membership(Model):
name = models.CharField(max_length=64)
def __str__(self):
return "{}".format(self.name)
class UserMembership(Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
membership = models.ForeignKey('Membership', on_delete=models.CASCADE)
reason = models.CharField(max_length=64)
When i want to list all users i get:
{
id: 1,
name: "name-a",
memberships: [
{
id: 1,
name: "member-a"
}, ...
]
}
but i actually want to include the "reason" field
{
id: 1,
name: "name-a",
memberships: [
{
id: 1,
name: "member-a",
reason: "somereason"
}, ...
]
}
But how do i modify the queryset?
User.objects.all().values('members__usermember')
doesnt work unfortunetly...
could anybody support?
EDIT:
Serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'memberships')
class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = Membership
fields = ('id', 'name')
I think that the solution to your problem has to do more with how are you serializing the objects that with the queryset itself.
Are you using django-rest-framework, if so, please add the serializers to the response. Otherwise, comment and add the code to view how are you serializing the models.
Edit to add possible serializers:
Try with something similar to this snipped
class UserMembershipSerializer(serializer.ModelSerializer):
name = serializers.ReadOnlyField(source='membership.name')
class Meta:
model = UserMembership
fields = ('reason', 'name')
class UserSerializer(serializer.ModelSerializer):
memberships = UserMembershipSerializer(source='usermembership_set', many=True)
class Meta:
model = User
fields = ('id', 'name', 'memberships')
Related
I need to extract categories and subcategories in Post serializer, because I need to put pagination, if I put pagination in category view, different amount of posts will come, so I need to put pagination in Post view, I need to return response so that it looks like this
and I want to create rest api to return nested json like this
[
{
"id": 1,
"title": "Taomlar",
"subcat": [
{
id: 2,
title: "Milliy",
post: [
{
id: 1,
title: 'Palov',
summa: 300000,
...
},
{
id: 2,
title: 'Palov',
summa: 300000,
...
},
]
},
]
}
]
models.py
class Category(Base):
title = models.CharField(max_length=200)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
def __str__(self):
return self.title
class Post(Base):
title = models.CharField(max_length=225)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='post')
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
Can anyone please give me the solution for this problem
As much as I understand you need to get each Category with it's related posts.
Overwriting like this :
class CategorySerializer(serializers.ModelSerializer):
posts = serializers.SerializerMethodField()
class Meta:
model = Category
fields = (
'id',
'title',
'posts',
)
def get_posts(self, obj):
serializer = PostSerializer(obj.posts.all(), context=self.context, many = True)
return serializer.data
you can read more about nested serializer in Django Rest Framework
you should make it in this way
class CategorySerializer(serializers.ModelSerializer):
post_serializer = PostSerializer(many=True)
class Meta:
model = Category
fields = ['your_field', 'post_serializer']
this a basic example, try to read more about it to know how you can implement nested relations
I have two serializers, one for Country and one for my model Foo, I want to save the object by using the foreign key for this model but it errors out whenever I try to validate.
I have this
class Actor(TLPWrappedModel, CommentableModel):
label = models.CharField(max_length=56, unique=True)
country_of_origin = models.ForeignKey(Country, on_delete=models.CASCADE)
class FooSerializer(serializers.ModelSerializer):
country_of_origin = CountrySerializer()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
class Country(models.Model):
label = models.CharField(max_length=56, unique=True)
iso_code = models.CharField(max_length=3, unique=True)
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = [
'iso_code',
'label',
]
And this is what I'm trying to do
serializers = FooSerializer(data={'label': 'Foobar',
'country_of_origin': self.country.id})
serializers.is_valid()
print(serializers.errors)
print(serializers.validated_data)
serializers.save()
But I get this error {'country_of_origin': {'non_field_errors': [ErrorDetail(string='Invalid data. Expected a dictionary, but got int.', code='invalid')]}}
is it possible to use the ID of a foreign key to validate and create the object using the serializer?
We can update the to_represent of the FooSerializer to get the desired output
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['country_of_origin'] = CountrySerializer(instance.country_of_origin)
return data
serializers = FooSerializer(data={'label': 'Foobar', 'country_of_origin': self.country})
serializers.is_valid(raise_expection=True)
serializers.save()
In this I have updated the code to assign the self.country as country_of_origin. Also, I am using the raise_expection in the is_valid method. This method will return the errors as 400 response.
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
You can safely drop defining the 'country of origin` in the FooSerializer
contry_of_origin would be an object, and you are passing an id for it.
Do you need a nested serializer? : country_of_origin = CountrySerializer()
For the example that you have given, I would suggest you to change it to PrimaryKeyRelatedField()
Your serializer would look like:
class FooSerializer(serializers.ModelSerializer):
country_of_origin = serializers.PrimaryKeyRelatedField()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
I'm using DRM v3.9.4 and have the following models:
class Book(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
users = models.ManyToManyField("User", related_name="books", through="BookUser")
class BookUser(models.Model):
user = models.ForeignKey("User", on_delete=models.CASCADE)
book = models.ForeignKey("Book", on_delete=models.CASCADE)
permission = models.CharField(...)
class User(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
with the following serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ( "id", ... )
class BookUserSerializer(ModelSerializer):
class Meta:
model = BookUser
fields = ('user', 'permission')
class BookSerializer(ModelSerializer):
users = BookUserSerializer(source='bookuser_set', many=True)
class Meta:
model = Book
fields = ( "id", "users", ... )
extra_kwargs = {
"users": {"allow_empty": True},
}
When reading (GET) a Book everything is fine:
{
id: ...,
users: [
{
user: 'some-uuid',
permission: 'read-only'
},
...
]
}
but when trying to POST using the same payload:
{
id: ...,
users: [
{
user: 'some-uuid',
permission: 'read-only'
},
...
]
}
I get an error:
KeyError: 'users'
looking at the api-guide (https://www.django-rest-framework.org/api-guide/relations/)
seems that by default nested serializers are read-only,
but I can't make it work.
BTW, IMPORTANT: all the users are (and should) already exist, so I expect this POST call to add a record in the intermediate BookUser table, and ofcourse the Book itself, but NOT to actually add new User(s) records.
Any help, guiding or assistance would be appreciate as how to make this thing work
Using drf-writable-nested package you can achieve that:
from drf_writable_nested import WritableNestedModelSerializer
class BookSerializer(WritableNestedModelSerializer):
users = BookUserSerializer(source='bookuser_set', many=True)
class Meta:
model = Book
fields = ( "id", "users", ... )
extra_kwargs = {
"users": {"allow_empty": True},
}
DRF Writable nested
Let's say that we have models like below
class Movie(models.Model):
"""Stores a single movie entry."""
title = models.CharField(max_length=200, blank=False)
class Watchlist(models.Model):
"""Stores a user watchlist."""
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name='watchlist',
on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, related_name='watchlist',
on_delete=models.CASCADE)
added = models.BooleanField(default=False)
Serializer
class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for a custom user model with related user action."""
url = serializers.HyperlinkedIdentityField(
view_name='customuser-detail', lookup_field='username')
watchlist = serializers.HyperlinkedRelatedField(
many=True, view_name='watchlist-detail', read_only=True)
class Meta:
model = CustomUser
fields = ('url', 'username', 'watchlist')
and the view:
class CustomUserViewSet(viewsets.ReadOnlyModelViewSet):
"""
list:
Return a list of all the existing users.
retrieve:
Return the given user with user's watchlist.
"""
queryset = CustomUser.objects.all()
permissions = (IsAdminOrReadOnly)
lookup_field = 'username'
serializer_class = CustomUserSerializer
That all will give us a user and hyperlinked filed to the particular watchlist.
{
"url": "http://127.0.0.1:8000/api/v1/users/John/",
"username": "John",
"favorites": [
"http://127.0.0.1:8000/api/v1/watchlist/2/",
"http://127.0.0.1:8000/api/v1/watchlist/1/"
]
},
but instead of that I would like to get a particular movie instance like that.
{
"url": "http://127.0.0.1:8000/api/v1/users/John/",
"username": "John",
"favorites": [
"http://127.0.0.1:8000/api/v1/movies/33/",
"http://127.0.0.1:8000/api/v1/movies/12/"
]
},
so my question is how can I achieve that? I tried with hyperlinkedrelatedfield but nothing seems to work as expected.
You could use the SerializerMethodField along with reverse.
from rest_framework.reverse import reverse
class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for a custom user model with related user action."""
url = serializers.HyperlinkedIdentityField(
view_name='customuser-detail', lookup_field='username')
favorites = serializers.SerializerMethodField()
def get_favorites(self, obj):
movie_urls = [
reverse("movie-view", args=[watchlist.movie.id], request=self.context['request'])
for watchlist in obj.watchlist.all()
]
return movie_urls
class Meta:
model = CustomUser
fields = ('url', 'username', 'favorites')
Given the model and serializer classes below, when I retrieve Track details, it'll only show the Track title but not the related Artist.
How would I also show the Artist name when retrieving Track details?
models.py
class Artist (models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Track (models.Model):
artist = models.ForeignKey(Artist, blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Artist")
title = models.CharField(max_length=100, verbose_name="Title")
def __str__(self):
return self.title
serializers.py
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = '__all__'
I think you need custom field, try this serializer:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('title', 'artist','artist_name')
artist_name = serializers.SerializerMethodField('get_artists_name')
def get_artists_name(self, obj):
return obj.artist.name
It produce something like this.
[
{
"title": "Don't let me down",
"artist": 2,
"artist_name": "The Beatles"
},
{
"title": "Summertime",
"artist": 1,
"artist_name": "Ella Fitzgerald"
}
]
Try this serializer,
class ArtistSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = '__all__' # or array of fieldnames like ['name_1', 'name_2']
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
Inorder to retrieve Artist details, which is a ForeignKey model, you need to use a nested serializer in django-rest-framework.
By using the TrackSerializer with a nested ArtistSerializer, the retrieved data would look something like this,
{
"title": "Some_Title",
"artist": {
"id": 2, #or id of the artist.
"name": "Artist_name"
}
}
As you can see in the official django rest framework documentations
You should define a serializer field for nested items
First create your Artist (nested item) serializer
class ArtistSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
class Meta:
model = Artist
fields = ('id', 'name')
Then you can use it on related model serializers
class TrackSerializer(serializers.ModelSerializer):
artist = ArtistSerializer()
class Meta:
model = Track
fields = ('title', 'artist')
In the current version of DRF you can simply do this
class TrackSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
model = Track
fields = '__all__'
StringRelatedField may be used to represent the target of the relationship using its __str__ method.
REF