Initializing drf Serializer - django

I have two models: User and Employee. I want to return following json via drf:
[ { 'admin': {}, 'employee': [{}, {}] }, { 'admin': {}, 'employee': [{}, {}] } ].
However, I don't understand how to initialize a TeamSerializer with data. What should I pass into TeamSerializer
class Meta:
model = User
fields = [
"first_name",
"last_name",
"email",
"hourly_rate",
"role",
]
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = [
"first_name",
"last_name",
"email",
"hourly_rate",
"role",
]
class TeamSerializer(serializers.Serializer):
admin = AdminSerializer()
employee = EmployeeSerializer()

You will need to create a model for team to specify the relation.
If I understand right, each team has an admin and multiple employees, each employee can be member of one team, and an admin can administer one team. so one to one relation for admin and one to many relation for employee.
The model should look like this:
class Team(models.Model):
admin = models.OneToOneField(get_user_model(), on_delete=models.SET_NULL, null=True)
class Employee(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="employees")
# all the rest of your fields here
Then create a serializer for Team:
class TeamSerializer(serializers.ModelSerializer):
admin = UserSerializer()
employees = EmployeeSerializer()
class Meta:
model = Team
fields = ["admin", "employees"]
I would take that even farder and create a Admin model and AdminSerializer also to provide more information about the admin, but it depends on your needs.

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.

Different write and read operations with DRF

I am using Django Rest Framework for a project and I am running into a problem. When the frontend creates a Team they want to reference all relationships with an ID, but when getting the Team, they want the data from the relationship. How can I achieve this?
models:
class Team(models.Model):
class Meta:
db_table = "team"
team_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
class Organization(models.Model):
class Meta:
db_table = "organization"
organization_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Position(models.Model):
class Meta:
db_table = "position"
position_id = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="positions")
class Player(model.Model):
class Meta:
db_table = "player"
player_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
positions = models.ManyToManyField(Position, related_name="players")
serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True) # This is merely for output. There is no need to create a position when a team is created.
organization = OrganizationSerializer() # Since an organization already exists I'd like to just give an organization_id when creating/editing a team.
# I don't think the other serializers matter here but can add them on request.
So when doing POST or PATCH on a team, I'd like the front end to be able to pass this payload
{
"name": "My Team",
"organization": 1
}
but when doing a GET on a team, I'd like the front end to receive this response.
{
"team_id": 1,
"name": "My Team",
"organization": {
"organization_id": 1,
"name": "My Organization"
},
"positions": [{
"position_id": 1,
"players": [{
"player_id": 1,
"name": "Member 1"
}
]
}
Is there a a way to achieve this?
In such situations define two serializers, one is for read operations and one is for write operations.
class TeamWriteSerializer(serializers.ModelSerializer):
# see, here no nested relationships...
class Meta:
model = Team
fields = ["name", "organization"]
class TeamReadSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True)
organization = OrganizationSerializer()
and now, use these two serializers properly in your views. For example, I hope you are using the ModelViewSet in views,
class TeamModelViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return TeamReadSerializer
else:
return TeamWriteSerializer

Django create POST intermediate model

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

Django REST framework reverse relationship object instance

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')

Representing the other side of a one-to-one relationship in a serializer

I have a one-to-one relationship set up between a User model, and a UserProfile model. The UserProfile model contains additional information about the user, and is created immediately after a User model saves. The OneToOne field is set on the UserProfile.
The serialized User data appears fine when I access any of the views concering the UserProfile, but I would also like the UserProfile data to appear on User views, and I was wondering how to specify this in the UserSerializer.
EDIT: I need the UserProfile data so that I can do an easy lookup on the UserProfile id to change information about the User. I will have the User id client side. Ideally, I'd like to look up the User with that id, find the UserProfile id (amongst other UserProfile data), so that I can then change the data for the UserProfile that is associated with the User.
Example: user 1 is authenticated. I gather some information following registration, so I call the view /api/users/1/, which tells me that the user profile with an id of 20 is associated with that user. I can then update that user profile with the new data. I can't always assume that user id 1 will be associated with user profile id 1.
EDIT 2: Solved this similar to Angela's answer like this:
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(source="userprofile")
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
avatar = models.FileField(
upload_to="uploads",
blank=True
)
age = models.IntegerField(
null=True
)
created_at = models.DateTimeField(
auto_now_add=True
)
current_city = models.CharField(
max_length=255,
blank=True
)
current_country = models.CharField(
max_length=255,
blank=True
)
I think, you should use Two UserProfileSerializer for this purpose as,
#serializer.py
class UserProfileSerializer_Temp(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"user",
"current_city",
"current_country"
)
class UserSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
user_profile = kwargs.pop('user_profile', True)
super().__init__(*args, **kwargs)
if user_profile is False:
self.fields.pop('user_profile', None)
user_profile = UserProfileSerializer_Temp(source='userprofile_set')
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserProfileSerializer_New(UserProfileSerializer_Temp):
user = UserSerializer(user_profile=False)
#views.py
class UserProfileAPI(ModelViewSet):
serializer_class = UserProfileSerializer_New
queryset = UserProfile.objects.all()
class UserAPI(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
What's the relevence of user_profile=False ?
It's act like a simple flag. If we do not pass something like that from UserProfileSerializer_New, it will serializer the UserProfile twice. See this Screenshot --- image
Cureent Response
1 . UserAPI
2 . UserProfileAPI
So to have the userprofile information available for viewing in the user serializer and also have user info in the user serializer, I would recommend you use the serializer source arguements as follows -
Recommended Approach
This is based on the assumption that when you update user profile, you dont want to update any info in the actual user model. You use the user endpoint to do that.
class UserProfileSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name',read_only=True)
last_name = serializers.CharField(source='user.second_name',read_only=True)
...... #(other user fields you want to include)
......
......
class Meta:
model = UserProfile
fields = (
"first_name",
"last_name",
"current_city",
"current_country",
......., # any other user fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
This will give you a nested representation of the user_profile inside your user object, which will also have the source fields, but you can choose to ignore that.
Another approach
Otherwise if you really want nested structures in both the serializers, you can use different serializers, but not sure what your use case is for this.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
......., # any other user_profile only fields
.......
)
class UserSerializer(serializers.ModelSerializer):
user_profile = UserProfileSerializer(partial=True, required=False)
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups",
"user_profile"
)
class UserSerializerNew(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
"first_name",
"last_name",
"groups"
)
class UserProfileSerializerNew(serializers.ModelSerializer):
user = UserSerializerNew(read_only=True)
class Meta:
model = UserProfile
fields = (
"current_city",
"current_country",
"user"
)