Django rest custom user model creation fails - django

I have a custom user model as following:
class Librarian(models.Model):
user = models.OneToOneField(User)
phone = models.CharField(max_length=30, blank=True)
# A library has many librarians
which_library = models.ForeignKey('Library', related_name='librarians', on_delete=models.CASCADE)
I have written serializer as following:
class LibrarianSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
password = serializers.CharField(source='user.password')
class Meta:
model = Librarian
#fields = '__all__'
fields = ('id', 'username', 'email', 'password', 'phone', 'which_library')
def update(self, instance, validated_data):
instance.user.email = validated_data.get('user.email', instance.user.email)
instance.user.password = validated_data.get('user.password', instance.user.password)
instance.phone = validated_data.get('phone', instance.phone)
instance.which_library = validated_data.get('which_library', instance.which_library)
instance.save()
return instance
def create(self, validated_data):
print('ok')
return Librarian.objects.create(**validated_data)
It's view:
#api_view(['POST'])
def librarian(request, library_id):
"""
Create a new librarian for a specific library
"""
if request.method == 'POST':
print('View')
serializer = LibrarianSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And I am making a POST request to the corresponding URL with following JSON data:
{
"username": "sample",
"email": "sample#gmail.com",
"password": "12345678",
"phone": "12345",
"which_library": "1"
}
It throws me Cannot assign "{u'username': u'sample', u'password': u'12345678', u'email': u'sample#gmail.com'}": "Librarian.user" must be a "User" instance error.
My goal is to create a Librarian (a user must be automatically created), that's why I am sending username, password, email fields as well. What am I doing wrong?

You need to override create method and create User instance first:
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.set_password(user_data['password'])
user.save()
return Librarian.objects.create(user=user, **validated_data)

Related

Django rest framework updating a OneToOne field

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',
]

Django rest api does not save password

I want to make a user registration form. Everything works and the user is saved, but his password is not stored. When I see the user page in admin it is in the password field -> "Invalid password format or unknown hashing algorithm.". i use default user mode.
this is my code ->
serializers.py
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', )
extra_kwargs = {'password': {'write_only': True},}
def create(self, validated_data):
user = User(
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
views.py
class CreateUser(generics.CreateAPIView):
permission_classes=[AllowAny]
serializer_class=RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
token, _ = Token.objects.get_or_create(user=user)
return Response({
"user": RegisterSerializer(user, context=self.get_serializer_context()).data,
"token": token.key
})

How to upload image in user profile model while has one to one relationship with user model in django rest framework?

I have a UserProfile model which has one to one relationship with User model.
I am facing problem in uploading image by rest-framework API endpoint to nested model.
Problem is: Though I am explicitly using #action() decorator, then also it is calling inbuilt create() method for saving image by POST.
Then I have explicitly mentioned which method to call in urls.py. Now it shows errors.
There are errors in the implementation in serializer.py and views.py.
So if possible then please correct my mistakes and direct me in the correct direction. Its more than two days but still I am struck.
In models.py:
def user_image_upload_file_path(instance, filename):
"""Generates file path for uploading user images"""
extension = filename.split('.')[-1]
file_name = f'{uuid.uuid4()}.{extension}'
date = datetime.date.today()
initial_path = f'pictures/uploads/user/{date.year}/{date.month}/{date.day}/'
full_path = os.path.join(initial_path, file_name)
return full_path
class UserManager(BaseUserManager):
def create_user(self, email, password, username, **extra_kwargs):
"""Creates and saves a new user"""
if not email:
raise ValueError(_('Email cannot be empty'))
user = self.model(email=self.normalize_email(email), **extra_kwargs)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, username, **extra_kwargs):
"""Creates and saves a new user with superuser permission"""
user = self.create_user(
email, password, username, **extra_kwargs)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Creates user model that supports using email as username"""
email = models.EmailField(_('Email'), max_length=255, unique=True)
created_date = models.DateTimeField(
_('Created Date'), default=timezone.now, editable=False)
objects = UserManager()
USERNAME_FIELD = 'email'
def __str__(self):
"""String representation of user model"""
return self.email
class UserProfile(models.Model, Languages):
"""Creates user profile model"""
user = models.OneToOneField(
'User',
related_name='profile',
on_delete=models.CASCADE
)
first_name = models.CharField(
_('First Name'), max_length=255, blank=True)
last_name = models.CharField(
_('Last Name'), max_length=255, blank=True)
image = models.ImageField(
_('Image'),
upload_to=user_image_upload_file_path,
null=True,
blank=True,
max_length=1024
)
#receiver(post_save, sender=User)
def user_is_created(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
else:
instance.profile.save()
In serializer.py:
class UserSerializer(serializers.ModelSerializer):
"""Minimal serializer for supporting user image upload field"""
class Meta:
model = get_user_model()
fields = ('username', )
class UserImageUploadSerializer(serializers.ModelSerializer):
"""Serializer for user profile"""
user = UserSerializer(read_only=True)
class Meta:
model = UserProfile
fields = ('id', 'user', 'image', )
read_only_fields = ('id', 'user', )
In views.py:
class UserImageUploadView(viewsets.ModelViewSet):
serializer_class = serializer.UserImageUploadSerializer
authentication_classes = [authentication.TokenAuthentication, ]
permission_classes = [permissions.IsAuthenticated, ]
queryset = get_user_model().objects.all()
def get_queryset(self):
"""Return object for only authenticated user"""
return self.queryset.filter(id=self.request.user)
#action(detail=True, methods=['POST'], url_path='user-upload-image')
def image_upload(self, pk=None):
"""Save the uploaded picture and profile data"""
user = self.get_object()
profile = user.profile
data = {'user': user, 'id': profile.pk, 'data': request.data}
serializer_ = serializer.UserImageUploadSerializer(data=data)
if serializer_.is_valid():
serializer_.save()
return Response(serializer_.data, status=status.HTTP_200_OK)
else:
return Response(serializer_.errors, status=status.HTTP_400_BAD_REQUEST)
In urls.py:
urlpatterns = [
path('<int:pk>/upload-image/', views.UserImageUploadView.as_view(
{'get': 'list', 'post': 'image_upload', }), name='user-image-upload')
]
To achieve this task I have manually created the image saving feature using APIView.
Please update if any efficient code exists
In my urls.py file:
urlpatterns = [
path('upload-image/', views.UserImageUploadView.as_view(), name='user-image-upload'),
]
In views.py:
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser
from rest_framework.views import APIView
from . import serializer
from core import models
class UserImageUploadView(APIView):
"""View to upload or view image for user"""
serializer_class = serializer.TempSerializer
authentication_classes = [authentication.TokenAuthentication, ]
permission_classes = [permissions.IsAuthenticated, ]
parser_classes = [JSONParser, MultiPartParser]
def get(self, request, format=None):
"""To get user profile picture"""
user = get_user_model().objects.get(email=request.user)
user_profile = models.UserProfile.objects.get(user=user)
# Preparing the data manually as per our serializer
data = {'user': {'username': user.username},
'image': user_profile.image or None}
# Serializing our prepared data
ser = serializer.TempSerializer(
user_profile, data=data, context={"request": request})
# Returning appropriate response
if ser.is_valid():
return_ser_data = {'id': ser.data.get('id'),
'image': ser.data.get('image')}
return Response(return_ser_data, status=status.HTTP_200_OK)
else:
return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
def post(self, request, format=None):
"""To save the profile picture"""
user = get_user_model().objects.get(email=request.user)
user_profile = models.UserProfile.objects.get(user=user)
# Formatting the data to as per our defined serializer
data = {'user': {'username': user.username},
'image': request.data.get('image')}
# Serializing our data
ser = serializer.TempSerializer(
user_profile, data=data, context={"request": request})
if ser.is_valid():
if ser.validated_data:
# Deleting the old image before uploading new image
if user_profile.image:
user_profile.image.delete()
# Saving the model
ser.save(user=user)
return_ser_data = {'id': ser.data.get('id'),
'image': ser.data.get('image')}
return Response(return_ser_data, status=status.HTTP_200_OK)
else:
return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
In serializer.py:
class UserSerializer(serializers.ModelSerializer):
"""Minimal serializer for supporting user image upload field"""
class Meta:
model = get_user_model()
fields = ('username', )
class TempSerializer(serializers.ModelSerializer):
"""Serializer for user image upload"""
user = UserSerializer(read_only=True)
image = serializers.ImageField(allow_null=True, use_url=True)
class Meta:
model = UserProfile
fields = ('id', 'user', 'image')
read_only_fields = ('id', 'user')

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

Invalid username/password error django rest framework custom user serializer

Custom User Model:
class User(AbstractUser):
ROLE_CHOICES = (
('R', 'rider'),
('D', 'driver'),
)
role = models.CharField(max_length=1, choices=ROLE_CHOICES)
phone_number = models.CharField(max_length=10)
cab = models.OneToOneField('Cab', on_delete=models.CASCADE, blank=True, null=True)
Rider serializer:
class RiderSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'phone_number', 'password')
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
username = validated_data.pop('username')
password = validated_data.pop('password')
instance = User(username, **validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
Rider function based view method:
#api_view(['GET', 'POST'])
def rider_list(request):
if request.method == 'GET':
riders = User.objects.filter(role='R')
serializer = RiderSerializer(riders, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = RiderSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(role='R')
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
rider endpoint: /riders/
I am able to create a user object but user authentication fails as password is getting stored as plain text in object.
I have tried using User.objects.create_user(username, password=password, **validated_data) to set password as hashed value but it does not work
I have also tried using make_password method to set hashed password but nothing seems to work.
Please tell me what am i missing. How do i store the hashed password in password field of custom user object.
create() method should be part of serializer class, not part of Meta:
class RiderSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'phone_number', 'password')
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
password = validated_data.pop('password')
instance = User(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
Also you don't need to pop username field. Just pop password and use it in set_password method.