Django rest framework updating a OneToOne field - django

I have a User model that inherits from AbstractUser which has an email field. And a profile model that has an OneToOne relation with the User model
class User(AbstractUser):
email = models.EmailField(unique=True)
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
phone = models.CharField(max_length=13, validators=[phone_regex], unique=True, null=True, blank=True)
birth_date = models.DateField(blank=True, null=True)
about = models.TextField(max_length=2000, blank=True)
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}"
view.py
class ProfileViewSet(ModelViewSet):
....
#action(detail=False, methods=["GET", "PUT"], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
profile = Profile.objects.get(user_id=request.user.id)
if request.method == "GET":
serializer = ProfileSerializer(profile)
return Response(serializer.data)
elif request.method == "PUT":
serializer = ProfileSerializer(profile, request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
user = CurrentUserSerializer()
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user_ser = CurrentUserSerializer(instance=instance.user, data=user_data)
if user_ser.is_valid():
user_ser.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
return instance
class Meta:
model = Profile
fields = [
'id',
'user',
'phone',
'birth_date',
'about',
]
Now when I try to update a user profile I get status: 400 Bad Request error
{
"user": {
"email": [
"user with this email already exists."
]
}
}
using patch instead of put or partial=True doesn't change anything I still get this error. What can I do here?

The thing is you are creating new record on data base and It is not permitted
class ProfileSerializer(serializers.ModelSerializer):
user = CurrentUserSerializer()
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
try:
user=User.objects.get(pk=instance.user.id)
except User.DoesNotExist:
raise ValidationError("User already exist")
#now with user instance you can save the email or do whatever
you want
user.email = "xyaz#gmail.com")
user.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
return instance
class Meta:
model = Profile
fields = [
'id',
'user',
'phone',
'birth_date',
'about',
]

Instead of a nested serializer I used serializer fields and my problem was solved. I'm not sure if it's a good approach or not.
class ProfileSerializer(serializers.ModelSerializer):
email = serializers.EmailField(source='user.email')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
user_ser = CurrentUserSerializer(instance=user, data=user_data)
if user_ser.is_valid():
user_ser.save()
instance.phone = validated_data.get('phone', instance.phone)
instance.birth_date = validated_data.get('birth_date', instance.birth_date)
instance.about = validated_data.get('about', instance.about)
instance.save()
class Meta:
model = Profile
fields = [
'id',
'email',
'first_name',
'last_name',
'phone',
'birth_date',
'about',
]

Related

How to update fields of custom user model using Django serializer

I am trying to update the various fields of a user model when the user wants to update it which was created earlier. I am having a custom user model and I am using django rest framework for the update api.
views.py
class UserUpdate(generics.UpdateAPIView):
"""
Update user.
"""
parser_class = (FileUploadParser,)
permission_classes = (AllowAny,)
queryset = User.objects.all()
serializer_class = UserUpdateSerializer
def update(self, request, *args, **kwargs):
instance = self.get_object()
instance.user_id = request.data.get("user_id")
instance.save()
serializer = self.get_serializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
models.py
class User(models.Model):
USER_CHOICES = (
(1, u'ADMIN'),
(2, u'SALES'),
(3, u'KITCHEN'),
(4, u'EMPLOYEE'),
)
image = models.ImageField(upload_to='employees/', null = True, blank = True)
name = models.CharField(max_length=50)
user_id = models.CharField(max_length=30, primary_key = True, blank = False)
email = models.CharField(max_length=50)
password = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
user_group = models.PositiveSmallIntegerField(default=4, choices=USER_CHOICES)
firebase_token = models.CharField(max_length=150, default=None, null = True)
shop = models.ForeignKey(Shop, on_delete=models.SET_DEFAULT, default=None)
phone = PhoneField(blank=False, default=0)
serializers.py
class UserUpdateSerializer(serializers.ModelSerializer):
shop = serializers.CharField()
class Meta:
model = User
fields = ('image', 'url', 'phone', 'name', 'user_id','email', 'password', 'is_active', 'user_group', 'shop')
def update(self, instance, validated_data):
shop = validated_data.pop('shop')
user_id = validated_data.pop("user_id")
print(user_id)
shopId = Shop.objects.filter(name=shop).values('shop_id').first()
if shopId is not None:
shop_id = shopId['shop_id']
try:
if user_id is not None:
instance.name = validated_data.get('name', instance.name)
instance.image = validated_data.get('image', instance.image)
instance.email = validated_data.get('email', instance.email)
instance.phone = validated_data.get('phone', instance.phone)
instance.password = validated_data.get('password', instance.password)
instance.user_group = validated_data.get('user_group', instance.user_group)
instance.shop_id = validated_data.get('shop_id', instance.shop_id)
instance.is_active = True
instance.save()
return instance
else:
print("Test")
except Exception as e:
return e
else:
return None
This is throwing an error saying that the user already exists!
Please assist!
Just Change your serializer like below
class UserUpdateSerializer(serializers.ModelSerializer):
shop = serializers.CharField()
class Meta:
model = User
fields = ('image', 'url', 'phone', 'name', 'user_id','email', 'password', 'is_active', 'user_group', 'shop')
def update(self, instance, validated_data):
shop = validated_data.pop('shop')
shop_obj = Shop.objects.filter(name=shop).first()
if shop_obj:
instance.shop = shop_obj
return super().update(instance, validated_data)
When you are calling UserUpdate API, it will create every new User for every new user_id.
I don't know why you are updating user_id. Any new user_id will create a new User instance.
If you want to update other than user_id, you need to remove the following line.
Remove these lines from UserUpdate API View.
instance.user_id = request.data.get("user_id")
instance.save()
Remove this line from UserUpdateSerializer
instance.user_id = validated_data.get('user_id', instance.user_id)
Add lookup_field = 'user_id' to UserUpdate API view
Add user_id at url, like
path('userupdate/<str:user_id>/', UserUpdate.as_view())

DRF Foreignkey serialization

I can't save model with Foreignkey field.
Thanks to "azudo" problem solved. Solution below
For example I have simple models:
class User(AbstractUser):
class Meta:
pass
email_validator = EmailValidator()
username = models.CharField('Name', max_length=150, )
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField('Email', blank=True, unique=True, validators=[email_validator], )
...
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='packages')
description = models.CharField('Description', max_length=256, default='description')
weight = models.CharField('weight', max_length=256, default='weight')
...
View (the user is guaranteed to be in the request):
#api_view(["POST"])
def test(request):
data = request.data
data['user'] = User.objects.get(id=request.user.id)
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
My serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PackageSerializer(ModelSerializer):
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def to_representation(self, instance):
self.fields['user'] = UserSerializer(many=False, read_only=True)
self.fields['where_from'] = LocationSerializer(many=False, read_only=True)
self.fields['destination'] = LocationSerializer(many=False, read_only=True)
return super().to_representation(instance)
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user, **validated_data)
return package
json in request:
{
"description": "Some package",
"weight": "12",
}
So, I'have user in database, and want create package for him. But in overridden create in PackageSerializer, validated_data doesn't have user. Please explain what I'm doing wrong.
Versions of django and drf:
django==2.2.4
djangorestframework==3.10.2
Solution:
Serializer:
class PackageSerializer(ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user)
return package
View:
#api_view(["POST"])
def create_package(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
DRF will ignore included fields that are marked as read-only so the caller cannot include read-only data. If you want to include additional attributes simply pass them as keyword args to save:
https://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
e.g.
#api_view(["POST"])
def test(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)

Save two model instances in one updateview

Am trying to update the User model and UserProfile model in one view but it's not working. No error is shown and no changes are made to the objects. What am I not doing right.
Here is my models.py:
class UserProfile(models.Model):
"""User information not related to authentication"""
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
age = models.IntegerField()
# other fields ignored
Here is my serializer.py:
class UserSerializer(ModelSerializer):
first_name = CharField(max_length=20)
last_name = CharField(max_length=20)
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
username = CharField(max_length=32,validators=[UniqueValidator(queryset=User.objects.all())])
password = CharField(min_length=8, write_only=True)
confirm_password = CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
validated_data['username'],
email = validated_data['email'],
first_name = validated_data['first_name'],
last_name = validated_data['last_name']
)
password = validated_data['password']
confirm_password = validated_data['confirm_password']
if password != confirm_password:
raise ValidationError({'password': 'Passwords must match'})
else:
user.set_password(password)
user.save()
return user
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'confirm_password')
class UserProfileSerializer(ModelSerializer):
username = CharField(source='user.username')
first_name = CharField(source='user.first_name')
last_name = CharField(source='user.last_name')
email = CharField(source='user.email')
class Meta:
model = UserProfile
exclude = ('user',)
# fields = '__all__'
# depth = 1
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data.get('username', instance.user.username)
instance.user.email = validated_data.get('email', instance.user.email)
instance.user.first_name = validated_data.get('first_name', instance.user.first_name)
instance.user.last_name = validated_data.get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance
Here is view.py:
class UserProfileUpdate(UpdateAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = 'user'
#Eric
Try changing your update method to this, the actual update data is under validated_data['user']
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data['user'].get('username', instance.user.username)
instance.user.email = validated_data['user'].get('email', instance.user.email)
instance.user.first_name = validated_data['user'].get('first_name', instance.user.first_name)
instance.user.last_name = validated_data['user'].get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance

Password required Django REST API User Serializer PUT request

I a the problem that in my Django Rest API User Serializer: The password field is necessary when making a PUT request. Within a POST request this makes sense, but with PUT is especially if PUT is performed by an administrator, the password field should allowed to be empty.
How can I change my serializer so that the password is necessary for POST, but empty for PUT?
class UserSerializer(serializers.ModelSerializer):
"""The UserSerializer"""
gender = serializers.IntegerField(source='profile.gender', read_only=False)
clinic = serializers.CharField(
source='profile.clinic.code', read_only=False)
title_prefix = serializers.CharField(
source='profile.academic_title_prefix',
allow_blank=True,
read_only=False)
title_suffix = serializers.CharField(
source='profile.academic_title_suffix',
allow_blank=True,
read_only=False)
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())])
username = serializers.CharField(
validators=[UniqueValidator(queryset=User.objects.all())])
password = serializers.CharField(min_length=8, write_only=True)
class Meta:
model = User
fields = ('id', 'url', 'username', 'first_name', 'last_name', 'gender',
'title_prefix', 'title_suffix', 'clinic', 'email',
'is_staff', 'is_superuser', 'date_joined', 'last_login',
'password')
def create(self, validated_data):
"""Create and return a new user and its associated profile."""
user = User.objects.create_user(
validated_data['username'],
validated_data['email'],
validated_data['password'],
)
user.set_password(validated_data['password'])
user.first_name = validated_data['first_name']
user.last_name = validated_data['last_name']
user.is_staff = validated_data['is_staff']
user.is_superuser = validated_data['is_superuser']
user.save()
# create associated profile
profile_data = validated_data.pop('profile')
profile = Profile.objects.create(
user=user,
gender=profile_data['gender'],
clinic=Clinic.objects.get(code=profile_data['clinic']['code']),
academic_title_prefix=profile_data['academic_title_prefix'],
academic_title_suffix=profile_data['academic_title_suffix'],
)
user.profile = profile
return user
def update(self, instance, validated_data):
"""Update and return a existing user and its associated profile."""
instance.first_name = validated_data.get('first_name',
instance.first_name)
instance.last_name = validated_data.get('last_name',
instance.last_name)
# Only Superuser can make Superusers
if self.context['request'].user.is_superuser:
instance.is_staff = validated_data.get('is_staff',
instance.is_staff)
instance.is_superuser = validated_data.get('is_superuser',
instance.is_superuser)
profile_data = validated_data.pop('profile')
profile = Profile.objects.get(user=instance)
profile.gender = profile_data['gender']
profile.clinic = Clinic.objects.get(
code=profile_data['clinic']['code'])
profile.academic_title_prefix = profile_data['academic_title_prefix']
profile.academic_title_suffix = profile_data['academic_title_suffix']
profile.save()
instance.profile = profile
return instance
It's not problem of the serializer. Problem is that DRF requires all fields using PUT method. Method that don't require all fields is PATCH.
You need to override update method in viewset:
def update(self, request, *args, **kwargs):
partial = True # Here I change partial to True
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
If you are using PUT to update the values and only want to ommit validation for password field, then you can try like this for viewset and generic views:
class UserSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
if self.context['request'].method == "PUT":
self.fields.pop('password')
# rest of the code

Django REST Framework - int() argument must be a string, a bytes-like object or a number, not 'SimpleLazyObject'

I'm using Django 2.0 and Django REST Framework to write REST API.
my settings.py contains settings for DRF
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
]
}
and every request must be signed using any of the DEFAULT_AUTHENTICATION_CLASSES method.
In contacts/serializers.py
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
instance = Contact.objects.create(**validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
print(validated_data) gives following data
{'first_name': 'Anshuman', 'last_name': 'Upadhyay', 'date_of_birth': datetime.date(2018, 5, 15), 'contactphonenumber_set': [], 'user_id': <SimpleLazyObject: <User: anuj>>}
The user_id is SimpleLazyObject thus giving error on save
TypeError: int() argument must be a string, a bytes-like object or a number, not 'SimpleLazyObject'
The error is on the line instance = Contact.objects.create(**validated_data)
How can I pass authenticated user to user field?
Edit 2: contacts/models.py
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
contacts/views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
You could use self.context for this http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context
If you are using GenericAPIView context is passed automatically
First of all you don't need to pass user_id to your serializer and then you need to update your ContactSerializer to look like this.
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
instance = Contact.objects.create(user=self.context['request'].user, **validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
You shouldn't overwrite perform_create
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
Assuming your Contact model contains an FK relation with AuthUserModel. So, your instance creation statement must be like this,
user = get some user instance from `validated` data
instance = Contact.objects.create(user=user,**validated_data)
This is the general solution for your question, If you add your Contact model, we could help you more
UPDATE-1
Change your create() as below,
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
user = validated_data.pop('user') # change 1
instance = Contact.objects.create(user=user, **validated_data) # # change 2
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
Update-2
You could use user = serializers.CurrentUserDefault() in serializer as below,
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
user = serializers.CurrentUserDefault()
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
user = validated_data.pop('user_id')
instance = Contact.objects.create(user=user, **validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
CurrentUserDefault() is do the same job as self.context.get('request').user