Access field from intermediary model in Django Rest Framework - django

Having a hard time trying to access a field from an intermediary model in DRF.
Let's see the related models:
class School(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, verbose_name=_(u'Name'))
staff_order = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_(u'Staff ordering'), through='StaffOrder', related_name='school_staff_order')
class User(AbstractUser):
phone = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=150, blank=True, null=True)
about_me = models.CharField(max_length=200, blank=True, null=True)
REQUIRED_FIELDS = ['email']
def __unicode__(self):
return u'{0}'.format(self.username)
class StaffOrder(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
school = models.ForeignKey(School)
order = models.PositiveSmallIntegerField(default=0, verbose_name=_(u'Staff ordering for this school'))
class Meta:
verbose_name_plural = _(u'Staff ordering')
Now what I'm expecting is being able to read order field from StaffOrder in when returning a QuerySet for users (StaffSerializer). Here are the Serializers:
class StaffRoleSerializer(serializers.ModelSerializer):
class Meta:
model = StaffOrder
fields = (
'order',
)
class StaffSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=75, required=True)
email = serializers.CharField(max_length=75, required=True)
order = StaffRoleSerializer(source='school_staff_order')
class Meta:
model = User
What is returned in order for the StaffSerializer is a Manager, instead of the order field from the StaffOrder model related with this User.
A JSON expected response for Staff would be something like this:
[
{
"username": "Denise",
"email": "deniseburton#maximind.com",
"order": 9
},
{
"username": "Ina",
"email": "inaburton#maximind.com",
"order": 4
}
]
I'd love to be able to also write this value from the serializer, but I can do that in the Viewset, but I really need to read this value in the Serializer itself...any idea what I'm missing here?

First you have to understand that one user can have many staff orders. In your models you have defined it that way.
To get the json output you have specified in your question you need to query the StaffOrder objects instead of users.
class StaffOrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
class Meta:
model = StaffOrder
fields = ('order', )
Use this serializer class in a list view:
class StaffOrderListApi(generics.ListAPIView):
serializer_class = StaffOrderSerializer
def get_queryset(self):
# using distinct because same user can have multiple staff orders
# based on your example it seems you want distinct users
return StaffOrder.objects.all().distinct('user')
This will get you exactly the json you want.

Related

Django Rest Framework - Nested Serialization

I have listed the models and Serializers below.
Models:
class CustomGroup(Group):
description = models.CharField(max_length=150, null=True, blank=True, verbose_name="Human readable name")
def __str__(self):
return self.description or self.name
class Meta:
db_table = "groups"
class User(AbstractBaseUser, PermissionsMixin):
"""
Custom user model that supports email.
"""
groups = models.ManyToManyField(
CustomGroup,
verbose_name=('groups'),
blank=True,
help_text= (
'The groups this user belongs to. A user will get all permissions '
'granted to each of their groups.'
),
related_name="user_set",
related_query_name="user",
through="UserGroup"
)
email = models.EmailField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
tenant = models.ForeignKey(
Tenant, on_delete=models.CASCADE, null=True, db_column="tenant_id", blank=True
)
......
objects = UserManager()
USERNAME_FIELD = "email"
# REQUIRED_FIELDS = ["tenant_id"]
class Meta:
db_table = "users"
class UserGroup(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
group = models.ForeignKey(CustomGroup, on_delete=models.CASCADE)
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, null=True)
class Meta:
db_table = "user_groups"
Serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ("first_name","last_name")
class UserGroupSerializer(serializers.ModelSerializer):
users = UserSerializer(many=True,read_only=True)
class Meta:
model = UserGroup
fields = ('group_id','users')
I want response like this :
{
"group_id":
"users": [
{
"first_name": "",
"last_name":""
}
...
]
...
]
}
I am only getting:
[{"group_id":1}, ...}]
How does the UserSerializer serialize the required user ids from the User model? Since user is defined as a foreign key in UserGroup does it happen automatically? or am i missing any relation between User and UserGroup?
Your UserGroup is only linked to one User and one Group. So you will only be able to access to one user and one group directly.
(Didn't you just mispell user in your serializer ? you wrote users)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ("first_name","last_name")
class UserGroupSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = UserGroup
fields = ('group_id','user')
but this will give you something like
{
"group": "<group_pk>",
"user": {
"first_name": "first_name",
"last_name": "last_name"
}
}
If you want to associate a specific Group with all related user, it takes something different. You would need to retrieve all the Users linked to a UserGroup having the group_id of the current UserGroup.
class UserGroupSerializer(serializers.ModelSerializer):
class Meta:
model = UserGroup
fields = ('group_id', )
def to_representation(self, instance):
data = super().to_representation(instance)
related_users = get_user_model().objects.filter(usergroup__group=instance.group)
data['users'] = UserSerializer(instance=related_users, many=true).data
return data
But, again, this may not be the most efficient way to achieve this as it will probably result in duplicated data. So you should probably consider accessing it from your "GroupSerializer". The same logic would be applied.

Django Rest Framework - how to get only one field from related models set

I have following models:
from django.db import models
class City(models.Model):
name = models.CharField(max_length=30)
last_update = models.DateTimeField(null=True)
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
Now using Django Rest Framework, I would like to create serializer that will return City details along with the list of all BusStops in the city - but I want the list to be only strings with BusStop names, like this:
{
"id": 1
"name": "City"
"last_update": "2019-09-19T22:13:54.851363Z"
"bus_stops": [
"stop1",
"stop2",
"stop3"
]
}
What I've tried so far is following serializers:
from rest_framework import serializers
class BusStopSerializer(serializers.ModelSerializer):
class Meta:
model = BusStop
fields = ('name', )
class CityDetailsSerializer(serializers.ModelSerializer):
busstop_set = BusStopSerializer(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'busstop_set')
But this creates list of objects with 'name' in them. So, how can I create a list with only BusStop names (as strings) in it?
Instead of the extra BusStopSerializer you could use a StringRelatedField:
# models.py
class BusStop(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=True, default='')
def __str__(self):
return self.name
# serializers.py
class CityDetailsSerializer(serializers.ModelSerializer):
bus_stops = StringRelatedField(many=True)
class Meta:
model = City
fields = ('id', 'name', 'last_update', 'bus_stops')
StringRelatedField, as recommended by wfehr, will only work as long as the __str__ method of BusStop only returns the name. An alternative is to use SlugRelatedField which allows you to specify a particular field from the related model, and has no dependency on __str__.
bus_stops = SlugRelatedField(many=True, slug_field='name')

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

related objects queries django rest framework

I have the following models
class STUser(AbstractBaseUser):
email = models.EmailField(unique=True)
name = models.CharField(max_length=255)
companyname = models.CharField(max_length=200, blank=True, null=True)
...
class VenuePermissions(models.Model):
user = models.ForeignKey(STUser, on_delete=models.CASCADE)
venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
signupvaildatestring = models.CharField(max_length=200, blank=True, null=True)
...
I want to grab all the STUser objects and grab all their permissions.
So what I would like is to grab all the VenuePermissions objects. And grab the user and venue object of each venuePermission
Two ways I can do this. use the VenuePermissions_set attribute on STUser but then how do I grab the venue when its just going to be a pk value?
Or focus on the VenuePermissions objects and grab the user and venue from the pk values but how?
I remember nested queries, and I kinda did one in my browse code.
here is an example:
rooms = Room.objects.filter(venue=OuterRef('pk'), sixtyroundseatingoption= True)
venuelist = venuelist.annotate(sixtyrounds=Exists(rooms))
venuelist = venuelist.filter(Q(sixtyrounds = True) | Q(fullbuyoutsixtyroundseatingoption = True))
I've done the set objects in a serializer before
Example serializer:
class RoomAndImageSerializer(serializers.ModelSerializer):
roomimage_set = RoomImageSerializer(many=True, read_only=True)
class Meta:
model = Room
fields = ('pk','name')
any help with this query would be appreciated!
So this is what I am currently trying, I will post an answer if this works:
class VenueUserList(ListAPIView):
serializer_class = VenueUserListSerializer
queryset = VenuePermissions.objects.select_related('user').select_related('venue').filter(signupvaildatestring=None)
class VenueUserListSerializer(serializers.ModelSerializer):
user = UserSerializer()
venue = VenueSerializer()
class Meta:
model = VenuePermissions
fields = ('user', 'venue', 'isvenueviewer', 'isvenueeventplanner', 'isvenueadministrator')
Here is the answer. However I still need to group venues by user. Working on that.
class VenueUserList(ListAPIView):
serializer_class = VenueUserListSerializer
queryset = VenuePermissions.objects.select_related('user').select_related('venue').filter(signupvaildatestring=None)
class VenueUserListSerializer(serializers.ModelSerializer):
user = UserSerializer()
venue = VenueSerializer()
class Meta:
model = VenuePermissions
fields = ('user', 'venue', 'isvenueviewer', 'isvenueeventplanner', 'isvenueadministrator')

Using reverse relationships with django-rest-framework's serializer

My model looks like this:
class User(TimestampedModel):
name = models.CharField(max_length=30, null=False, blank=False)
device = models.CharField(max_length=255, null=False, blank=False)
class Comment(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
And my serializer looks like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class CommentListItemSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Comment
fields = ('user', 'contents', 'rating')
And the view:
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all()
It's almost getting the job done ;). The response I'm getting looks like this:
"results": [
{
"user": {
"name": "Ania"
},
"contents": "Very good",
"rating": 6
},
{
"user": {
"name": "Anuk"
},
"contents": "Not very good",
"rating": 1
}
]
There are two problems with that response.
I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username".
Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name. Since that's unacceptable, how to fix that?
Serializer makes a database query (not a join, but a separate query)
for each user, to get his/her name.
You can use select_related() on the queryset attribute of your view. Then accessing user.name will not result in further database queries.
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all().select_related('user') # use select_related
I don't want to have this nested object "user.name". I'd like to
receive that as a simple string field, for example "username"
You can define a read-only username field in your serializer with source argument. This will return a username field in response.
class CommentListItemSerializer(serializers.ModelSerializer):
# define read-only username field
username = serializers.CharField(source='user.name', read_only=True)
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')
You can add custom functions as fields
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
def username(self):
return self.user.name
class CommentListItemSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')