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())
Related
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',
]
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)
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
I have a model named Profile which is a wrapper over the User model of Django. Now, I have created a ProfileSerializer in the following way:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'password')
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create_user(**user_data, username=validated_data.get('username'))
profile = Profile.objects.create(user=user, **validated_data)
return profile
def update(self, instance, validated_data):
instance.dob = validated_data.get('dob', instance.dob)
instance.karma = validated_data.get('karma', instance.karma)
instance.username = validated_data.get('username', instance.username)
user_data = validated_data.pop('user')
instance.user.first_name = user_data.get('first_name', instance.user.first_name)
instance.user.last_name = user_data.get('last_name', instance.user.last_name)
instance.user.email = user_data.get('email', instance.user.email)
instance.user.username = instance.username
new_password = user_data.get('password')
if new_password:
instance.user.set_password(new_password)
instance.user.save()
instance.save()
return instance
class Meta:
model = Profile
fields = '__all__'
The problem here is, when I am trying to DELETE a profile, the profile is getting deleted but the user still stays. I need to be able to delete a user as well when a DELETE request is made.
I tried to do some search but I couldn't find and destroy or delete methods for a ModelSerializer.
You can do this on view level with perform_destroy method:
class DetailProfile(RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'username'
lookup_url_kwarg = 'username'
def perform_destroy(self, instance):
user = instance.user
instance.delete()
user.delete()
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