confusion on which serializer to use to update user profile - python

I have a profile model for user to update. I want to have a rest api for that. I have a profile model which is too big than usual. I am working to update the user profile but I am having confusion on which serializer should i use to update the profile. Here is what i have done till now
I created the serializer for user and user profile.
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('token', 'user','current_location', 'permanent_location', 'dob',
'about_me', 'gender_status', 'create_profile_for', 'marital_status',
'height', 'weight', 'body_type', 'complexion',)
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(required=True)
class Meta:
model = User
fields = ('id', 'profile', 'username', 'email', 'first_name', 'last_name',)
def update(self, instance, validated_data):
# instance = super(UserSerializer, self).update(validated_data)
profile_data = validated_data.pop('profile')
#updating user data
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.email = validated_data.get('first_name', instance.email)
#updating profile data
if not instance.profile:
Profile.objects.create(user=instance, **profile_data)
instance.profile.current_location = profile_data.get('current_location', instance.profile.current_location)
instance.profile.permanent_location = profile_data.get('permanent_location', instance.profile.permanent_location)
instance.profile.weight = profile_data.get('weight', instance.profile.weight)
instance.profile.height = profile_data.get('height', instance.profile.height)
instance.profile.about_me = profile_data.get('about_me', instance.profile.about_me)
instance.profile.create_profile_for = profile_data.get('create_profile_for', instance.profile.create_profile_for)
instance.profile.body_type = profile_data.get('body_type', instance.profile.body_type)
instance.save()
return instance
This is my view
class UserProfile(APIView):
serializer_class = UserSerializer
def get(self, request, token=None, format=None):
"""
Returns a list of profile of user
"""
reply={}
try:
profile_instance = Profile.objects.filter(user=self.request.user)
if token:
profile = profile_instance.get(token=token)
reply['data'] = self.serializer_class(profile).data
else:
reply['data'] = self.serializer_class(profile_instance, many=True).data
except:
reply['data']=[]
return Response(reply, status.HTTP_200_OK)
def post(self, request, token=None, format=None):
"""
update a profile
"""
profile=None
if not token is None:
try:
profile = Profile.objects.get(user=request.user, token=token)
except Profile.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
serialized_data = self.serializer_class(profile, data=request.data, partial=True)
reply={}
if not serialized_data.is_valid():
return error.ValidationError(serialized_data.errors).as_response()
else:
profile = serialized_data.save(user=request.user)
reply['data']=self.serializer_class(profile, many=False).data
return Response(reply, status.HTTP_200_OK)
How should i handle my user profile update?
Updated my UserSerializer with update function
UPDATE with Dean Christian Armada solution
class UserProfile(APIView):
serializer_class = ProfileSerializer
def get(self, request, token=None, format=None):
"""
Returns a list of profile of user
"""
reply={}
try:
profile_instance = Profile.objects.filter(user=self.request.user)
if token:
profile = profile_instance.get(token=token)
reply['data'] = self.serializer_class(profile).data
else:
reply['data'] = self.serializer_class(profile_instance, many=True).data
except:
reply['data']=[]
return Response(reply, status.HTTP_200_OK)
def put(self, request, token=None, *args, **kwargs):
"""
update a profile
"""
print('token', token)
if token:
try:
profile = Profile.objects.get(token=token)
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
serialized_data = self.serializer_class(profile, data=request.data)
reply={}
if serialized_data.is_valid():
profile = serialized_data.save(user=request.user)
reply['data'] = self.serializer_class(profile, many=False).data
return Response(reply, status.HTTP_200_OK)
else:
return Response(serialized_data.errors, status.HTTP_400_BAD_REQUEST)
With your code, i get This QueryDict instance is immutable error

It's a standard to use the PUT request method for updating in your current code and situation it is better to use the ProfileSerializer since it has more fields. Also, the way I see it you only have three fields in the User object that can be changed and those three fields I assume is rarely changed.
views.py
def put(self, request, *args, **kwargs):
"""
update a profile
"""
data = request.data
print data
try:
profile = Profile.objects.get(token=data.get('token'))
except:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer = self.serializer_class(profile, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_200_OK)
else:
return Response(serializer.errors,
status.HTTP_400_BAD_REQUEST)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Profile
fields = ('user', 'height', 'weight', 'token')
def to_internal_value(self, data):
first_name = data.pop('first_name', None)
last_name = data.pop('last_name', None)
data = super(ProfileSerializer, self).to_internal_value(data)
data['first_name'] = first_name
data['last_name'] = last_name
return data
def update(self, instance, validated_data):
first_name = validated_data.pop('first_name', None)
last_name = validated_data.pop('last_name', None)
user_inst_fields = {}
if first_name:
user_inst_fields['first_name'] = first_name
if last_name:
user_inst_fields['last_name'] = last_name
if user_inst_fields:
User.objects.update_or_create(pk=instance.user.id,
defaults=user_inst_fields)
profile, created = Profile.objects.update_or_create(
token=instance.token, defaults=validated_data)
return profile
Simply just set the user to read only for it to avoid validation

Related

Expected view to be called with URL keyword "pk" added "pk" to urls.py

Getting the error:
AssertionError at /update_profile/ Expected view UpdateProfileView to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_fieldattribute on the view correctly.
when I try to update a user profile.
Here is my urls.py
path('update_profile/', views.UpdateProfileView.as_view(), name='update_profile'),
My UpdateProfileView:
class UpdateProfileView(generics.UpdateAPIView):
# permission_classes = [permissions.IsAuthenticated]
# authentication_classes = (TokenAuthentication,)
# lookup_field = 'username'
queryset = User.objects.all()
serializer_class = UpdateUserSerializer
#action(detail=True, methods=['put'])
def perform_update(self, serializer):
serializer.save(user=self.request.user.id)
UpdateUserSerializer
class UpdateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
class Meta:
model = User
#add 'city', 'country', 'bio'
fields = ['username', 'email', 'password', 'first_name', 'last_name']
def validate_email(self, value):
user = self.context['request'].user
if User.objects.exclude(pk=user.pk).filter(email=value).exists():
raise serializers.ValidationError({"email": "This email is already in use."})
return value
def validate_username(self, value):
user = self.context['request'].user
if User.objects.exclude(pk=user.pk).filter(username=value).exists():
raise serializers.ValidationError({"username": "This username is already in use."})
return value
def update(self, instance, validated_data):
user = self.context['request'].user
if user.pk != instance.pk:
raise serializers.ValidationError({"authorize": "You don't have permission for this user."})
instance.first_name = validated_data['first_name']
instance.last_name = validated_data['last_name']
instance.email = validated_data['email']
instance.username = validated_data['username']
# instance.profile.city = validated_data['city']
# instance.profile.country = validated_data['country']
# instance.profile.bio = validated_data['bio']
instance.save()
return instance
I added the URL argument pk to my urls.py
path('update_profile/<int:pk>', views.UpdateProfileView.as_view(), name='update_profile')
This returns the error page not found at update_profile

I want to hash my password but some fields should not be required

In django validation when I signup, i don't want to validate some fields because they are not required, but if i am creating user from another page there i want those fields, so how to do unrequired some fields
**Serializers.py**
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "password" ,"first_name", "last_name","email", "User_Type","url","sign_up_count", "facebook", "twitter", "linkedin", "instagram", "telegram", "discord", "snapchat"]
def create(self, validated_data):
user = User(
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
username=validated_data['email'],
User_Type=validated_data['User_Type'],
facebook=validated_data['facebook'],
twitter=validated_data['twitter'],
linkedin=validated_data['linkedin'],
instagram=validated_data['instagram'],
telegram=validated_data['telegram'],
discord=validated_data['discord'],
snapchat=validated_data['snapchat'],
)
user.set_password(validated_data['password'])
user.save()
return user
**view.py**
class CreateUserByAdmin(APIView):
def post(self, request):
data = request.data
data.update(username = data.get('email'))
try:
if User.objects.filter(email=data.get('email')).exists():
return Response({'details' : 'User Already Exists'}, 403)
else:
serializer = UserSerializer(data=data)
print("serializer Data == > ", serializer.data)
if serializer.is_valid():
serializer.save()
return Response({'details' : 'User Created'})
else:
return Response({'details' : 'Somthing went Wrong'}, 403)
except:
return Response({'details' : 'Somthing went Wrong1'}, 403)
**view.py**
def post(self, request):
params = request.query_params.get("refer")
try:
refer_by=User.objects.get(url=params)
data = json.dumps(request.data)
data = json.loads(data)
username=AddUser(request,data)
SendAccountVerifcationEmail(data)
refer_by=User.objects.get(url=params)
refer_to=User.objects.get(username=username)
ReferLink(refer_by,refer_to)
return Response({"ok":"ok"})
except:
data = json.dumps(request.data)
data = json.loads(data)
username=AddUser(request,data)
SendAccountVerifcationEmail(data)
return Response({"ok":"ok"})
**helpers.py**
def AddUser(request,data):
serializer = UserSerializer(data=data)
if serializer.is_valid():
serializer.save()
username=request.data["email"]
# user_email=request.data["email"]
user = User.objects.get(email=username)
user.username = username
user.save()
token, created = Token.objects.get_or_create(user=user)
if username:
return username
else:
return "Ok"
when i signup a account i dont want facebook, telegram and more..., but if i am creating user from another page that time i neet it. so how to set unrequired fields for validate
Currently you have one serializer: UserSerializer, and this serializer requires all fields like linkedin etc.
You should leave it as it is or even rename it to WebsiteRegistrationUserSerializer to be more clear what this serializer is used to.
Next step is to declare AdminRegistrationUserSerializer like that:
class AdminRegistrationUserSerializer (serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "password" ,"first_name", "last_name","email", "User_Type", <all_fields_you_want_to_include>]
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
return user
And after that you can change serializer in CreateUserByAdmin to AdminRegistrationUserSerializer

How to fix "Key (username)=() already exists." error

I want to create a user with type 'learner'using mobile nummber,when i am trying to do this there is an error,
django.db.utils.IntegrityError: duplicate key value violates unique constraint "user_user_username_key"
DETAIL: Key (username)=() already exists.
views.py
class Register(APIView):
def get_permissions(self):
return (AllowAny(),)
def post(self, request, *args, **kwargs):
mobile = request.data.get('mobile')
if mobile:
data = {'mobile': mobile}
serializer = RegisterSerializer(data=data)
if serializer.is_valid(raise_exception = True):
serializer.save()
return Response('sucess')
else:
return Response('error')
serializers.py
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('mobile',)
def create(self, validated_data):
user_data = validated_data.pop('user')
user_data['user_type'] = 'learner'
user_serializer = UserSerializer(data=user_data)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()
user.groups.add(validated_data['group'])
user.save()
return user
instance = self.Meta.model(**validated_data)
instance.user = user
instance.save()
return instance

Update user error with Django API rest framework

I can`t update users because Django gives me this error in postman:
AttributeError at /profesionales/
Got AttributeError when attempting to get a value for field `user` on serializer `ProfesionalesSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'user'.
Request Method: PUT
Request URL: http://127.0.0.1:8000/profesionales/
Django Version: 1.11.6
Python Executable: C:\Users\Ismael\AppData\Local\Programs\Python\Python36\python.exe
Python Version: 3.6.3
Here is my code:
view.py
#Listar todos los profesionales o crear uno
#profesionales/
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request, *args, **kwargs):
instance = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(instance, data=request.data)
if profesionalSerializer.is_valid():
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
else:
return Response(profesionalSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializer(serializers.Serializer):
username = serializers.CharField()
first_name = serializers.CharField(allow_blank=True)
last_name = serializers.CharField(allow_blank=True)
email = serializers.CharField(allow_blank=True)
class Meta:
fields = ('username', 'first_name', 'last_name', 'email')
def create(self, validated_data):
user = User.objects.create(**validated_data)
return user
class ProfesionalesSerializer(serializers.Serializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
num_colegiado = validated_data.get('numColegiado')
user_data = validated_data.pop('user')
user = User.objects.get(**user_data)
profesionales = user.profesionales
if num_colegiado:
profesionales.numColegiado = num_colegiado
profesionales.save()
return instance
model.py
class Profesionales(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dni = models.CharField(max_length=9, blank=True, default='')
numColegiado = models.CharField(max_length=8, blank=True, default='')
valoracionMedia = models.FloatField(blank=True, default=0)
numVotos = models.IntegerField(blank=True, default=0)
def __str__(self):
return self.numColegiado
Ok, I have it - but to be honest, you should rethink your API design.
The problem is that - there is no problem -I mean from yout code I cannot reproduce the error. It is probably I assumed the User model - if you can paste the user model definition would be great (or if this is a standard django user - also mention that).
So firstly I would change the serializers to model serializers:
serializers.py
class UsernameValidator(object):
def set_context(self, serializer_field):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
# Determine the existing instance, if this is an update operation.
self.instance = getattr(serializer_field.parent, 'instance', None)
if not self.instance:
# try to get user from profesionales:
root_instance = getattr(serializer_field.root, 'instance', None)
self.instance = getattr(root_instance, 'user', None)
def __call__(self, value):
if self.instance and User.objects.filter(username=value).exclude(id=self.instance.id).exists():
raise ValidationError('Username already exists.')
if not self.instance and User.objects.filter(username=value).exists():
raise ValidationError('Username already exists.')
class UserSerializer(serializers.ModelSerializer):
username = serializers.CharField(max_length=128, validators=[UsernameValidator()])
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email')
class ProfesionalesSerializer(serializers.ModelSerializer):
user = UserSerializer()
numColegiado = serializers.CharField(allow_blank=False)
class Meta:
model = Profesionales
fields = ('user', 'numColegiado')
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profesional = Profesionales.objects.create(user=user, **validated_data)
return profesional
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
userSerializer = UserSerializer(user, data=user_data)
if userSerializer.is_valid(raise_exception=True):
userSerializer.save()
num_colegiado = validated_data.get('numColegiado')
if num_colegiado:
instance.numColegiado = num_colegiado
instance.save()
return instance
As you can note - I've added the UsernameValidator which is pretty important for API to work properly 0- it basically search for existing user instance and check if username exists or not;
I've also changed the update method - now it is using the UserSerializer explicite; Also corrected some bugs - returning validated_data instead of instance and so on.
At the end views.py:
class ProfesionalesList(APIView):
def get_object(self, pk):
try:
return User.objects.get(username=pk)
except User.DoesNotExist:
raise Http404
def get(self, request ):
usuarios = Profesionales.objects.all()
usuarioSerializer = ProfesionalesSerializer(usuarios, many=True)
return Response(usuarioSerializer.data)
def post(self, request):
profesionalSerializer = ProfesionalesSerializer(data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_201_CREATED)
def put(self, request, *args, **kwargs):
user = self.get_object(request.data.get('user').get('username'))
profesionalSerializer = ProfesionalesSerializer(user.profesionales, data=request.data)
if profesionalSerializer.is_valid(raise_exception=True):
profesionalSerializer.save()
return Response(profesionalSerializer.data, status=status.HTTP_200_OK)
I've shorten the code - using the raise_exception in is_valid method.
Actually - sorry for not following the stackoverflow rules - and do not provide an answer for your actual problem - but I strongly believe that analyzing the example you can figure it out. If you have any more questions - please ask.
On your class ProfesionalesSerializer, you have defined user as an instance of UserSerializer. Instead, user should be a field. You can't use serializers as "fields".
Edit: Ignore this. Turns out you can. See here: http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
(thanks to #opalczynski)

django rest framework url arguments with patch not working

Edit
I suspect the whole problem with my UpdateApiView is with the url. No matter how I change it, will return 404.
url(r'verify-phone/(?P<phone_id>^\d+)$', view.VerifyPhone.as_view(), name='verify-phone'),
it returns
{
"detail": "Not found."
}
[18/Apr/2016 01:39:02] "PATCH /api/verify-phone/phone_id=00980 HTTP/1.1" 404 4941
Why?
views.py
class VerifyPhone(generics.UpdateAPIView):
permission_classes = (AllowAny,)
serializer_class = serializers.VerifyPhoneSerializer
allowed_methods = ['PATCH']
lookup_field = 'phone_id'
def get_queryset(self):
phone_id = self.request.query_params.get('phone_id', None)
queryset = User.objects.filter(phone_id=phone_id)
return queryset
def update(self, request, *args, **kwargs):
print('inside update')
print(request.data)
partial = kwargs.pop('partial', False)
instance = self.get_object()
print(instance)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
print(serializer)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
print('done perform update')
return Response(serializer.data)
serializers.py
class VerifyPhoneSerializer(serializers.ModelSerializer):
regex = r'\d+'
verification_code = serializers.RegexField(regex, max_length=7, min_length=7, allow_blank=False)
phone_id = serializers.HyperlinkedRelatedField(view_name='verify-phone', lookup_field='phone_id', read_only=True)
class Meta:
model = User
fields = ('verification_code', 'phone_id')
def validate(self, data):
verification = api.tokens.verify(data['phone_id'], data['verification_code'])
if verification.response.status_code != 200:
raise serializers.ValidationError("Invalid verification code.")
return data
def update(self, instance, validated_data):
instance.phone_number_validated = True
instance.save()
return instance
Second question Is this correct to get phone_id from the views?
phone_id = serializers.HyperlinkedRelatedField(view_name='verify-phone', lookup_field='phone_id', read_only=True)
Looking at your api url def, I think you should call:
/api/verify-phone/00980
instead of
/api/verify-phone/phone_id=00980
I also think something is wrong with the url def itself (the ^ before \d):
url(r'verify-phone/(?P<phone_id>^\d+)$', view.VerifyPhone.as_view(), name='verify-phone')
should be
url(r'verify-phone/(?P<phone_id>\d+)$', view.VerifyPhone.as_view(), name='verify-phone')
or
url(r'verify-phone/(?P<phone_id>\d{5})$', view.VerifyPhone.as_view(), name='verify-phone')

Categories