Django Rest Framework: Patching a OneToOneField - python

I need help with a PATCH request using Django rest framework.
I have a User model that inherits from AbstractBaseUser which has 2 fields: name and email. The email field is unique.
Then I have a DojoMaster model that has a OneToOne relationship with the User model:
models.py
class DojoMaster(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
phone = models.BigIntegerField()
username = models.CharField(max_length=100, unique=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
I am able to POST a "dojomaster" to the application. Let's say the "dojomaster" POST looks like this:
POST payload
{
"user": {
"name": "XYZ",
"email": "xyz#mail.com",
"password": "2He$8Mv*"
},
"phone": 2685211,
"country": 575,
"username": "iAmXyZ"
}
Now the "dojomaster" wants to change some of these details, so a PATCH request is sent:
PATCH payload
{
"user": {
"name": "XYZ",
"email": "xyz24#mail.com", #change email
"password": "p#55w0rd" #change password
},
"phone": 3972925, #change phone number
"country": 575,
"username": "iAmXyZ"
}
To achieve this, I created the following in my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name', 'email', 'password')
class DojoMasterUpdateSerializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = User.objects.update(**user_data)
instance.user = validated_data.get('user', user)
instance.username = validated_data.get('username', instance.username)
instance.phone = validated_data.get('phone', instance.phone)
instance.country = Country.objects.get(
country=validated_data.get('country', instance.country)
)
instance.save()
return instance
class Meta:
model = DojoMaster
fields = ('user', 'country', 'phone', 'username')
write_only_fields = ('password',)
To use the serializers, I created the following view:
views.py
class DojoMasterUpdateView(generics.UpdateAPIView):
queryset = DojoMaster.objects.all()
serializer_class = DojoMasterUpdateSerializer
However, when I do this, I get a Status: 400 Bad Request error with the following payload:
{
"user": {
"email": [
"user with this email already exists."
]
}
}
I also tried doing with using a PUT request to no success.
How do I PATCH an entity with this type of OneToOneField relationship using DRF? Your help will be much appreciated.

This question is old and you might have already found your answer.
However, if anybody wonders:
the issue is this:
user = User.objects.update(**user_data)
With this, you are not updating one instance of user - you are trying to update all users in the User table with user_data.
Since you are patching the instance of DojoMaster, you should already have the instance of user which belongs to it:
So, your update method can look like this instead:
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user_ser = UserSerializer(instance=instance.user, data=user_data)
if user_ser.is_valid():
user_ser.save()
instance.username = validated_data.get('username', instance.username)
instance.phone = validated_data.get('phone', instance.phone)
instance.save()
return instance

Related

can't send post data to api in django

i can't send post data to api using django rest framework. i used postman to send data only user part is adding to the database , the activity of user is rejecting . i can't figure out problem can anyone help me to solve this problem
sending post data to api
{
"name": "karen",
"username": "karen",
"timezone": "US/Samoa",
"activity_periods": [
{
"log_in": "2020-06-09T21:53:25.120897Z",
"log_out": null
},
{
"log_in": "2020-06-09T22:02:35.289891Z",
"log_out": null
},
{
"log_in": "2020-06-09T22:03:36.425212Z",
"log_out": null
}
]
}
but only the user data is stored the activity is ignored
like this
{
"name": "karen",
"username": "karen",
"timezone": "US/Samoa",
"activity_periods": []
}
how can i add activity data to user...?
models.py
class User(models.Model):
name = models.CharField(max_length=20)
username = models.CharField(max_length=20)
password = models.CharField(max_length=20)
timezone = models.CharField(max_length=32, choices=TIMEZONES, default='UTC')
def __str__(self):
return self.name
class Activity(models.Model):
user = models.ForeignKey(User, related_name="activity_periods",on_delete=models.CASCADE,null=True, blank=True)
log_in = models.DateTimeField(null=True, blank=True)
log_out = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.user.name
serializers.py
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = ['log_in', 'log_out']
class UserSerializer(serializers.ModelSerializer):
# Passing login Logout to User
activity_periods = ActivitySerializer(many=True, read_only=True)
class Meta:
model = User
fields = ['name', 'username','timezone', 'activity_periods']
views.py
class ActivityListView(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
name = "activity-list"
urls.py
path('rest/',views.ActivityListView.as_view())
how can i add activity data to user...?
In your UserSerializer, you have the following line:
activity_periods = ActivitySerializer(many=True, read_only=True)
Since read_only is set to True, when you POST data it will not be written to the database. Try setting it to False instead.

Display fields from foreign key linked model django

So I've created an API with the Django rest framework. I have a simple model relationship which is, User -> Posts. Users are linked to the posts because the user is the AUTHOR of a post. All I want to do is display the username associated with a POST in my rest API. How do I reference other fields from a model that I linked as a foreign key? IF I have a user model that has a username, profile picture and email, how can I display those in my Post model???
Here is an example of what I am trying to do with the models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
image = models.ImageField(default='default.mp4', upload_to='video_thumbnails')
videoFile = models.FileField(default='default.mp4', upload_to='videos')
date_posted = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User , #Something to get the username here, on_delete=models.CASCADE)
Right now this displays
{
"title": "HOLROYD SWIPE ACCESS(CS ROOMS)",
"content": "Yeet",
"image": "http://127.0.0.1:8000/media/video_thumbnails/Screenshot_from_2019-08-03_23-37-50.png",
"videoFile": "http://127.0.0.1:8000/media/videos/Screenshot_from_2019-08-03_23-37-52.png",
"date_posted": "2019-10-22T21:01:07Z",
"user": 1
}
All I want it to do is display the name of the user instead of the USER ID which is 1 in this case.
I JUST want it to look like this instead
{
"title": "HOLROYD SWIPE ACCESS(CS ROOMS)",
"content": "Yeet",
"image": "http://127.0.0.1:8000/media/video_thumbnails/Screenshot_from_2019-08-03_23-37-50.png",
"videoFile": "http://127.0.0.1:8000/media/videos/Screenshot_from_2019-08-03_23-37-52.png",
"date_posted": "2019-10-22T21:01:07Z",
"user": "usernameassociatedwithpost"
}
Here is my serializer
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('title', 'content', 'image', 'videoFile', 'date_posted', 'user' )
and here is my views.py
class VideoList(generics.ListCreateAPIView):
queryset = Video.objects.all()
serializer_class = VideoSerializer
class VideoDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Video.objects.all()
serializer_class = VideoSerializer
If anymore code is needed to answer this, please let me know. Thanks!
You can use SlugRelatedField:
class PostSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field="username", queryset=User.objects.all())
class Meta:
model = Post
fields = ('title', 'content', 'image', 'videoFile', 'date_posted', 'user' )

Django Rest Framework use source to access different model with reverse relation?

I have the following problem, I have the default User model and Profile model. I want to merge them into one serializer but without nesting - It's just ugly. Instead, I want to have all their fields on the first level. So I created the following (for simplicity profile contains just one Bool field and one relation field):
class UserSerializer(serializers.ModelSerializer):
achievements = serializers.PrimaryKeyRelated(many=True, queryset=Achievements.objects.all())
trusted = serializers.BooleanField()
def create(self, validated_data):
user=User.objects.create_user(
password = validated_data['password'],
username = validated_data['username'],
email = validated_data['email'],
)
Profile.objects.update_or_create(user, defaults={
'trusted': validated_data['trusted'],
'achievements': validatd_data['achievements'],
}
)
return user
class Meta:
model = User
fields = ("id", "username", "email", "password", "trusted", "achievements"),
read_only = ("id",)
extra_kwargs = {
'password': {
'write_only': True,
},
}
Profile is connected to a user via a user field containing models.OneToOneField instance.
When I try to list all profiles I get Error that I need to specify source but I have no idea how and documentation mentions only something that dot notation should be used.
Thanks.
source is argument of serializer field. You should do something like this:
class UserSerializer(serializers.ModelSerializer):
achievements = serializers.PrimaryKeyRelated(many=True, queryset=Achievements.objects.all(), source='profile.achievements')
trusted = serializers.BooleanField(source='profile.trusted')
def create(self, validated_data):
user=User.objects.create_user(
password = validated_data['password'],
username = validated_data['username'],
email = validated_data['email'],
)
Profile.objects.update_or_create(user, defaults={
'trusted': validated_data['trusted'],
'achievements': validatd_data['achievements'],
}
)
return user

AttributeError - When using Django NestedFields serializers

I've 2 models:-
class Users(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255)
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users)
The user model & user address model. Following are the 2 serializers.
class UserAddressSerializer(ModelSerializer):
class Meta:
model = UserAddress
exclude = ('id', 'user')
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(many=True)
class Meta:
model = Users
fields = '__all__'
def create(self, validated_data):
address = validated_data.pop('address', [])
user = Users.objects.create(**validated_data)
for ad in address:
UserAddress.objects.create(user=user, **ad)
return user
The data I receive from the client is
{
"first_name": "string",
"last_name": "string",
"address": [{
"line1": "asd",
"country": "asd",
}],
}
This is how I create a new user and its corresponding address.
class UserCreate(GenericAPIView):
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserSerializer(data=data)
if not serializer.is_valid():
return
user = serializer.save()
response = {
'user_id': user.uuid
}
return
Now, upon getting the user details back, I receive an error saying
AttributeError: Got AttributeError when attempting to get a value for field `address` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Users` instance.
Original exception text was: 'Users' object has no attribute 'address'.
This is how I get the details of the user, including the address.
class UserDetails(GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
lookup_field = 'uuid'
def get(self, request, uuid, *args, **kwargs):
user = Users.get_user(uuid)
if not user:
return
serializer = UserSerializer(instance=user)
return
I'd read this example of nested relationship, and am doing exactly the same way. why is the error coming up?
Also, can this code be shorten up more (in a nicer clean way) using some DRF mixins? If yes, then how?
I think the most simple solution for your case is: in model UserAddress add related_name='address'
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users, related_name='address')
# ^^^^^^^^^^^^^^^
or you can add sourse property in serializer:
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(source='useraddress_set', many=True)
Serializer try to find attribute 'address' in the model User, but by default it is modelname underscore set (useraddress_set in your case), and you try other name, so you can set in the model or specify by source.
in the example you can look on models and find the related_name='tracks'

Nested field serializer - Data missing

Related to this Topic
Hi,
I cannot follow the answer at the attached topic, because an ID is missing after serialization.
Model.py
class Owner(models.Model):
name = models.CharField(db_index=True, max_length=200)
class Car(models.Model):
name = models.CharField(db_index=True, max_length=200)
LCVS = models.ForeignKey(Owner)
View.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
Serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = OwnerSerializer()
class Meta:
model = Car
fields = ('id', 'name', 'owner')
def create(self, validated_data):
tmp_owner = Owner.objects.get(id=validated_data["car"]["id"])
car = Car.objects.create(name=self.data['name'],owner=tmp_owner)
return car
Now i send the following request :
Request URL:http://localhost:9000/api/v1/cars
Request Method:POST
Request Paylod :
{
"name": "Car_test",
"ower": {
"id":1,
"name": "Owner_test"
}
}
But, here the validated_data don't contain the owner ID !
Traceback | Local vars
validated_data {u'Owner': OrderedDict([(u'name', u'Owner_test')]), u'name': u'Car_test'}
#Kevin Brown :
Workful ! Thanks
I'll validate your answer but I get a new problem...
Now when I try to put a new Owner, an error raise :
{
"id": [
"This field is required."
]
}
I had to create a new serializer ?
Any AutoFields on your model (which is what the automatically generated id key is) are set to read-only by default when Django REST Framework is creating fields in the background. You can confirm this by doing
repr(CarSerializer())
and seeing the field generated with read_only=True set. You can override this with the extra_kwargs Meta option which will allow you to override it and set read_only=False.
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
"id": {
"read_only": False,
"required": False,
},
}
This will include the id field in the validated_data when you need it.

Categories

Resources