Django create POST intermediate model - django

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

Related

Initializing drf Serializer

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.

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

Django EmbeddedModelField saying "This field may not be blank" when doing PUT request despite field having "blank=True"

I'm creating a Django application with django-rest-framework and using djongo to connect to MongoDB. I have nested models as such:
class Group(models.Model):
users = models.ArrayModelField(
model_container=User
)
class User(models.Model):
number = models.IntegerField(
default=None,
null=True
)
song = models.EmbeddedModelField(
model_container=Song,
null=True,
blank=True
)
class Meta:
abstract = True
class Song(models.Model):
mp3_file = models.URLField(
default=None,
blank=True,
null=True
)
comments = models.CharField(
max_length=250,
default='',
blank=True
)
class Meta:
abstract = True
When a Group is created, the Users and the Songs are created without any problems. For example, when created, the Group may look like:
{
"name": "Sample",
"users: [
{
"number": null,
"song": {
"mp3_file": null,
"comments": ""
}
}
]
}
This all works fine. However, if I try to do a PUT request and don't change the value of "number", "mp3_file", or "comments", I will get the following error message:
"user": [
{
"number": [
"This field may not be null."
],
"song": {
"mp3_file": [
"This field may not be null."
],
"comments": [
"This field may not be blank."
]
}
}
]
Any thoughts on how to fix this error? I am just using a generic RetrieveUpdateDestroyAPIView as the view for this endpoint.
edit: I have also tried recreating all migrations as well as dropping the table and recreating it, and neither of those approaches helped.
edit:
Here is the view:
class GroupDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = GroupSerializer
queryset = Group.objects.all()
lookup_field = 'group_name'
lookup_url_kwarg = 'group_name'
And the serializers:
class GroupSerializer(serializers.HyperlinkedModelSerializer):
users = UserSerializer(many=True)
def update(self, instance, validated_data):
if validated_data.get('group_name', None) is not None:
instance.__setattr__('group_name', validated_data.get('group_name'))
instance.save()
return instance
class Meta:
model = Group
fields = (
'group_name',
'users'
)
class UserSerializer(serializers.Serializer):
number = serializers.IntegerField()
song = SongSerializer()
class SongSerializer(serializers.Serializer):
mp3_file = serializers.URLField()
comments = serializers.CharField(
max_length=250
)
But part of the issue is that the serializers are never even being reached since the data is validating incorrectly.
try with change:
class UserSerializer(serializers.Serializer):
number = serializers.IntegerField(allow_null=True)
song = SongSerializer()
class SongSerializer(serializers.Serializer):
mp3_file = serializers.URLField(allow_blank=True)
comments = serializers.CharField(
max_length=250, allow_blank=True
)
try rest_meets_djongo, and replace serializers.Serializer to DjongoModelSerializer.

Django ORM - Add data from intermediate table

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