I'm using UserProfileSerializer to validate fields in patch request:
password = request.data.get('password', '')
if password:
if len(password) < 6:
return Response(status=status.HTTP_400_BAD_REQUEST)
hashed_pass = make_password(password)
serializer = UserProfileSerializer(instance=user,
data={'last_name': request.data.get('last_name', ''),
'password': hashed_pass,
partial=True)
else:
serializer = UserProfileSerializer(instance=user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
and this is my serializer:
class UserProfile(models.Model):
class meta:
abstract = True
password = models.CharField(_('password'), max_length=128, blank=True)
last_name = models.CharField(max_length=30, validators=[MinLengthValidator(3)], blank=True)
class UserProfileSerializer(ModelSerializer):
class Meta:
model = UserProfile
When i'm updating password, the last_name made empty!!
How i prevent it?
This bit of code (which is syntactically invalid (I assume you copy pasted incorrectly)) is to blame
data={'last_name': request.data.get('last_name', ''),
'password': hashed_pass,
partial=True)
If the last name is not in the post data you are setting the last name to blank. You were probably looking for something like:
data={'last_name': request.data.get('last_name', user.last_name),
'password': hashed_pass}
which results in the current last name being preserved.
Related
Unable to add a new customer to the database..
I made a class Named customer that has a one-to-one relationship with a class named User that is an AbstractUser
I want to send the data through rest API so that I can create a new customer in the customer table and a new user that is One To One Related to the customer from the same view.
User Model
class User(AbstractUser):
# Add additional fields here
id = None
email = models.EmailField(max_length=254, primary_key=True)
name = models.CharField(max_length=100)
password = models.CharField(max_length=100)
is_patient = models.BooleanField(default=False)
is_doctor = models.BooleanField(default=False)
is_homesampler = models.BooleanField(default=False)
is_pathologist = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now=True)
first_name = None
last_name = None
username = None
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'password']
objects = CustomUserManager()
def __str__(self):
return self.email
# Ensure that the password is hashed before saving it to the database
def save(self, *args, **kwargs):
self.password = make_password(self.password)
super(User, self).save(*args, **kwargs)
def has_perm(self, perm, obj=None):
return self.is_superuser
User Serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
# fields = (['id', 'username', 'email', 'name'])
fields = '__all__'
customer Model
class customer(models.Model):
user = models.OneToOneField(
get_user_model(), on_delete=models.CASCADE, primary_key=True)
real = models.BooleanField(default=False)
def __str__(self):
return self.user.name
Customer Serializer
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
class Meta:
model = customer
fields = '__all__'
def create(self, validated_data):
user_data = validated_data.pop('user')
user = get_user_model().objects.create(**user_data)
user.is_Patient = True
customer = customer.objects.create(user=user, **validated_data)
return customer
Create Customer View
# Create add customer API
#api_view(['POST'])
def addCustomer(request):
customer_serializer = CustomerSerializer(data=request.data)
if(customer_serializer.is_valid()):
customer_serializer.save()
print(customer_serializer.errors)
return Response({'message': 'okay'})
Body of API Call
{
"email" : "test#test.com",
"password": "Abc"
}
So the question is how can I create a view so that I can create a new user and a customer using just one API Call
Your call body doesn't match CustomerSerializer.
CustomerSerializer fields are "user" and "rest", so you only can pass these two unless you do something like these:
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
user = get_user_model().objects.create(**{
"email": email,
"password": password
})
user.is_Patient = True
customer = customer.objects.create(user=user, **validated_data)
return customer
About the create method, it won't function correctly:
Because:
We shouldn't use create to create a new user instead we should use create_user More
I noticed that you removed the username so this method would be useless :)
After user.is_Patient = True, you forgot to save the user
The correct code would be:
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
user = get_user_model().objects.create(email=email)
user.set_password(password)
user.is_Patient = True
user.save()
customer = customer.objects.create(user=user, **validated_data)
return customer
NOTE 1:
# Ensure that the password is hashed before saving it to the database
def save(self, *args, **kwargs):
self.password = make_password(self.password)
super(User, self).save(*args, **kwargs)
It is the wrong approach to make_password here because whenever you make a change in your user, it would run.
the ideal approach would be using user.set_password("new_pass") whenever you get a new password from the user.
NOTE 2:
When you pass read_only to a serializer, this means would be ignored if you passed it as data to the serializer.
About write_only, it's the opposite of read_only. It would not be returned if you called serializer.data. For example, we only want to write to the password, and we won't want to read it from the serializer, so we made it write_only.
UPDATE: Adding a name
You have to add a write_only field and then pass it to create method
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
name = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real", "name"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
name = validated_data.pop("name")
user = get_user_model().objects.create(email=email, name=name)
user.set_password(password)
user.is_Patient = True
user.save()
customer = customer.objects.create(user=user, **validated_data)
return customer
Hi i'm currently trying to write validation for my update API. i used the model fields in my serializer like so as i only want these fields to be update in the custom User model:
class UpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'email', 'uid', 'nickname', 'eth_address', 'eth_private_key', 'evt_address', 'evt_private_key'
)
My User model:
Class User(AbstractUser):
uid = models.CharField(
"uid", max_length=255, null=True, blank=True)
phone_number = models.CharField(
"Phone number", max_length=255, null=True, blank=True)
nickname = models.CharField(
"Nickname", max_length=255, null=True, blank=True)
status = models.PositiveSmallIntegerField("Status", default=0)
eth_address = models.CharField(
"Eth address", max_length=255, null=True, blank=True)
eth_private_key = models.CharField(
"Eth private key", max_length=255, null=True, blank=True)
evt_address = models.CharField(
"Evt address", max_length=255, null=True, blank=True)
evt_private_key = models.CharField(
"Evt private key", max_length=255, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
# deleted
class Meta:
db_table = "users"
pass
And here is my DRF update model view:
class update_profile(RetrieveUpdateAPIView):
permission_classes = ()
authentication_classes = (FirebaseAuthentication,)
def update(self, request):
user_data = request.data
user = User.objects.get(uid=request.user.uid)
serializer = UpdateUserSerializer(user, data=user_data, partial=True)
if user_data == {}:
raise serializers.ValidationError({'code': 400, 'data': 'Invalid JSON object'})
for key_name in user_data:
if key_name not in ['email', 'uid', 'nickname', 'eth_address', 'eth_private_key', 'evt_address', 'evt_private_key']:
raise serializers.ValidationError({'code': 400, 'data': 'Invalid input ' + key_name})
if serializer.is_valid():
updated_user = serializer.save()
return JsonResponse({'code': 200,'data': updated_user.uid}, status=200)
else:
return JsonResponse({'code': 400,'errors':serializer.errors}, status=400)
My input is as follow on POSTMAN:
{
"email": "test#gmail.com",
"uid": "dqwdqwd3123123",
"nickname":"Alan",
"eth_address": "dwdw",
"eth_private_key": "fwef",
"evt_address": "dwqdqwf",
"evt_private_key": "dwqdqqd"
}
I want to test with conditions of wrong input so i input wrong email like wrongemail.com and it validated correctly:
{
"code": 400,
"errors": {
"email": [
"Enter a valid email address."
]
}
}
But when i try to do wrong input to other type like "nickname": 4123414, it still pass validation even when in the model i set nickname = models.CharField("Nickname", max_length=255, null=True, blank=True) i don't know why the input value of number passed the validation without raising error.
First of all, I think most of the stuff you are doing here, like checking for correct fields, are done by the serializer itself. You just have to use is_valid(raise=True) and django will take care of returning the proper HTTP response (if you are using the default django error handler - read more in the docs)
So here is how your view should look like:
class update_profile(RetrieveUpdateAPIView):
permission_classes = ()
authentication_classes = (FirebaseAuthentication,)
def update(self, request):
user_data = request.data
user = User.objects.get(uid=request.user.uid)
serializer = UpdateUserSerializer(user, data=user_data, partial=True)
if serializer.is_valid(raise_exception=True):
updated_user = serializer.save()
return JsonResponse({'code': 200,'data': updated_user.uid}, status=200)
However, I also think the nick example is actually correct. Number can be cast as string and that's what the serializer is doing -> it changes 4123414 to '4123414'. If you think about it, it's a totally valid nick. You should try other examples, like providing a negative number for status.
I am trying to build a api for updating first and last name for my user. The api runs successfully but database is not updated which is the expected behavior
I have written the following API and trying to pass the patch request to it.
class UserSelfUpdateView(UpdateAPIView):
serializer_class = UserUpdateSerializer
permission_classes = [UserPermissions, ]
def update(self, request: Request, *args, **kwargs):
instance = User.objects.filter(id=self.request.user.id)
serializer = UserUpdateSerializer(instance, data=request.data,)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({'success': True}, status=status.HTTP_200_OK)
The serializer for the above request is:
class UserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields: ('id', 'first_name', 'last_name')
The format in which I am trying to pass my request body is:
{
"first_name": "A",
"last_name": "B"
}
and this is how my model is defined:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=False)
last_name = models.CharField(_('last name'), max_length=30, blank=False)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
'''
Returns the first_name plus the last_name, with a space in between.
'''
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns the short name for the user.
'''
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
When running code with debug pointer results in the no database update but 200 status.
When running code without debug pointer results in 500 status code and following error message in response
AssertionError at /user/me-edit
("Creating a ModelSerializer without either the 'fields' attribute or the 'exclude' attribute has been deprecated since 3.3.0, and is now disallowed. Add an explicit fields = 'all' to the UserUpdateSerializer serializer.",)
Problem resides in the serializer, and concretely, inside this part of the code:
class Meta:
model = User
fields: ('id', 'first_name', 'last_name')
Fields are also a variable defined using =, so the code should look like:
class Meta:
model = User
fields = ('id', 'first_name', 'last_name')
That will help you on solving that AssertionError.
This is my customized User Object in django.
class User(AbstractBaseUser, PermissionsMixin):
mobile = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=255, null=True)
username = models.CharField(max_length=255, null=True)
full_name = models.CharField(max_length=255, blank=True, null=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
location = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
USERNAME_FIELD = 'mobile'
REQUIRED_FIELDS = []
objects = UserManager()
And this is the UserManager,
class UserManager(BaseUserManager):
def create_user(self, mobile, email=None, username=None, full_name=None, password=None, is_staff=False,
is_superuser=False):
if not mobile:
raise ValueError("Can't create User without a mobile number!")
if not password:
raise ValueError("Can't create User without a password!")
user = self.model(
mobile=mobile,
email=self.normalize_email(email),
username=username,
full_name=full_name,
is_staff=is_staff,
is_superuser=is_superuser,
)
user.set_password(password)
user.save(self._db)
return user
This is my UserSerializer Class
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.User
fields = (
'id',
'mobile',
'email',
'username',
'full_name',
'password',
)
And this is the view where I'm trying to register a User.
class RegisterView(views.APIView):
def post(self, request):
serialized = UserSerializer(data=request.data)
if serialized.is_valid():
user = UserManager().create_user(mobile=serialized.mobile, email=serialized.email, username=serialized.email, full_name=serialized.full_name, password=serialized.password)
if user:
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)
I end up getting the following error message,
AttributeError at /api/v1/bouncer/register/
'UserSerializer' object has no attribute 'mobile'
But of course I've a mobile attribute. What am I doing wrong here?
.mobile, .email, ... are not on the Serializer object but on the instance. UserSerializer(data=...) returns a Serializer instance. (not a model instance)
If you want to keep your code the solution is to do:
UserManager().create_user(
mobile=serialized.validated_data['mobile'],
email=serialized.validated_data['email'],
...
But this way, you're not taking advantage of the serializer.
Personally, I would get rid of the UserManager. (By the way, the best way to create a manager is to inherit from django.db.models.Queryset and then do object = UserQueryset.as_manager())
I would write a serializer, like yours:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.User
fields = (
'id', 'mobile', 'email', 'username', 'full_name', 'password',
)
def create(self, validated_data):
password = validated_data.pop('password')
user = super().create(validated_data)
user.set_password(password)
return user
Then in your view, you just have to do:
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(...)
else:
return Response(...)
Also, instead of writing a full function, you could use a generic API view. (CreateAPIView is most likely what you want.)
N.B.: Everything is pseudocode, I have not tested it, but the solution should be extremely similar delta some small changes
I have this user serializer:
class SimpleUser(models.Model):
class meta:
abstract = True
email = models.EmailField(_('email address'), blank=False)
password = models.CharField(_('password'), max_length=128)
first_name = models.EmailField(_('first name'), blank=True)
class UserSerializer(ModelSerializer):
class Meta:
model = SimpleUser
And this is my view:
class UserView(APIView):
def patch(self, request, user_id):
firstname = request.data.get('first_name', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
user = User.objects.get(id=user_id)
serializer = UserSerializer(instance=user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
I send this json request, but only the password and email are updated and first_name not updated.
{
"password":"6524266",
"email":"HH#bb.com",
"first name":"dsfxvxc"
}
I get the status 200 OK and can get the saved object in serializer.save()
What is wrong with my code?
You set firstname as emailfield
first_name = models.EmailField(_('first name'), blank=True)
Changes this to CharField or related fields, something like this,
first_name = models.CharField(max_length=30)