should i use is_valid() function for validating my user data in put() function in drf?
because when i use it ,is_valid.errors says that model with this username and email is already exists!i can't understand the errors meaning,because i think should be something saved before i want to update
serializers.py
class UserCreateSerializers(ModelSerializer):
class Meta:
model = User
fields = ('name','email','family',"username","password","profile_pic")
def update(self, instance, validated_data):
print("from update try")
#more dry
for i in validated_data.keys():
if hasattr(instance,str(i)) and str(i) !="password":
setattr(instance,str(i),validated_data.get(str(i)))
elif hasattr(instance,str(i)) and str(i) =="password":
instance.set_password(validated_data.get('password'))
setattr(instance,"username",validated_data.get('new_username'))
instance.save()
views.py
def put(self,request):
username = request.data['username']
user = User.objects.get(username = username)
serialized = UserCreateSerializers(data = request.data)
if serialized.is_valid():
serialized.update(user,serialized.validated_data)
return Response(data ={"status":"api_user_update_ok"} , status = status.HTTP_201_CREATED)
else:
print(serialized.errors)
return Response(data = {"status":"api_user_update_failed","error":serialized.errors.get('email')[0]},status = status.HTTP_400_BAD_REQUEST)
client data with put method :
name:user
family:useri
username:user2
password:1234
new_username:user22
email:user#gmail.com
error is :
{'email': [ErrorDetail(string='user with this email already exists.',
code='unique')], 'username': [ErrorDetail(string='user with this
username already exists.', code='unique')]} Bad Request:
/api/v0/registration/signup
and the server response is :
{
"status": "api_user_update_failed",
"error": "user with this email already exists."
}
thanks for your help.
Short answer: yes you should use is_valid() before try to save received data into DB. You see this error because DRF add uniqueness validator to unique fields by default. This behavior described in the doc.
To tell django that you are updating object and don't need uniqueness validator you have to provide instance to serializer:
serialized = UserCreateSerializers(user, data=request.data)
Related
I am creating first rest api in django using django rest framework
I am unable to get object in json format. Serializer always returns empty object {}
models.py
class Shop(models.Model):
shop_id = models.PositiveIntegerField(unique=True)
name = models.CharField(max_length=1000)
address = models.CharField(max_length=4000)
serializers.py
class ShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
views.py
#api_view(['GET'])
def auth(request):
username = request.data['username']
password = request.data['password']
statusCode = status.HTTP_200_OK
try:
user = authenticate(username=username, password=password)
if user:
if user.is_active:
context_data = request.data
shop = model_to_dict(Shop.objects.get(retailer_id = username))
shop_serializer = ShopSerializer(data=shop)
if shop:
try:
if shop_serializer.is_valid():
print('is valid')
print(shop_serializer.data)
context_data = shop_serializer.data
else:
print('is invalid')
print(shop_serializer.errors)
except Exception as e:
print(e)
else:
print('false')
else:
pass
else:
context_data = {
"Error": {
"status": 401,
"message": "Invalid credentials",
}
}
statusCode = status.HTTP_401_UNAUTHORIZED
except Exception as e:
pass
return Response(context_data, status=statusCode)
When i try to print print(shop_data) it always returns empty object
Any help, why object is empty rather than returning Shop object in json format?
Edited:
I have updated the code with below suggestions mentioned. But now, when shop_serializer.is_valid() is executed i get below error
{'shop_id': [ErrorDetail(string='shop with this shop shop_id already exists.', code='unique')]}
With the error it seems it is trying to update the record but it should only get the record and serialize it into json.
You're using a standard Serializer class in this code fragment:
class ShopSerializer(serializers.Serializer):
class Meta:
model = Shop
fields = '__all__'
This class won't read the contend of the Meta subclass and won't populate itself with fields matching the model class. You probably meant to use ModelSerializer instead.
If you really want to use the Serializer class here, you need to populate it with correct fields on your own.
.data - Only available after calling is_valid(), Try to check if serializer is valid than take it's data
I use Django 2.7 to build Rest API application, and having problem to validate/clean the request data from client for get detail transaction (Not Save/update). for example the request data trx_no cannot less than 5 char length. where's the validation class i should create? should I validate on Model.py or using forms, or in serializer?
Here's my models.py:
class mst_trx(models.Model):
trx_no = models.CharField(max_length=20,primary_key=True)
Here's my views.py:
class views_index(APIView):
def post(self,request):
action = request.POST['action']
if action == 'detail' :
resp = detail.as_view()(request)
class detail(APIView):
def dispatch(self,request):
##I want to validate first before get data
try:
detail = mst_trx.objects.select_related().get(pk=request.POST['trx_no'])
except mst_trx.DoesNotExist:
raise Http404("Transaction does not exist")
else:
serializer = TrxDetailSerializer(detail)
return serializer.data
And Here's my serializer.py :
class TrxDetailSerializer(serializers.ModelSerializer):
class Meta:
model = mst_trx
fields = ('trx_no')
Validation logic should be in forms.py file
for e.g.
def clean_columnname(self):
columnname = self.cleaned_data['columnname']
if len(columnname) < 1:
raise ValidationError('Please add some content ...')
elif len(columnname) > 500000:
raise ValidationError('Too many characters ...')
return columnname
I'm confused how to implement methods in serializers and views in DRF:
I have an account model extending AbstractBaseUser. The viewset looks like this:
class AccountViewSet(viewsets.ModelViewSet):
lookup_field = 'username'
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get_permissions(self):
if self.request.method in permissions.SAFE_METHODS:
return (permissions.AllowAny(), TokenHasReadWriteScope())
if self.request.method == 'POST':
return (permissions.AllowAny(), TokenHasReadWriteScope())
return (permissions.IsAuthenticated(), IsAccountOwner(), TokenHasReadWriteScope())
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
Account.objects.create_user(**serializer.validated_data)
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
return Response({
'status': 'Bad request',
'message': 'Account could not be created with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
The serializer like this:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
confirm_password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'email', 'username', 'created_at', 'updated_at',
'first_name', 'last_name', 'tagline', 'password',
'confirm_password',)
read_only_fields = ('created_at', 'updated_at',)
def create(self, validated_data):
return Account.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.save()
password = validated_data.get('password', None)
confirm_password = validated_data.get('confirm_password', None)
if password and confirm_password:
instance.set_password(password)
instance.save()
update_session_auth_hash(self.context.get('request'), instance)
return instance
def validate(self, data):
if data['password'] and data['confirm_password'] and data['password'] == data['confirm_password']:
try:
validate_password(data['password'], user=data['username']):
return data
except ValidationError:
raise serializers.ValidationError("Password is not valid.")
raise serializers.ValidationError("Passwords do not match.")
On the create method for the view, it checks if the serializer is valid then saves it and returns responses depending on the outcome. My first question is when is the serializer create() method called? To me it seems that the method is bypassed altogether by calling create_user (a model method) in the view's create() method. Does it get called at all? What is the point of having it?
Second, I'm having trouble returning a status code from the update method, the instance is saved in the serializer. Will the code inside serializer update() work if the validation fails?
Here is what I have so far:
def update(self, request, pk=None):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
<< what goes here??? >>
return Response(serializer.validated_data, status=status.HTTP_200_OK)
except serializers.ValidationError as e:
return Response({
'status': 'Bad request',
'message': str(e)
}, status=status.HTTP_400_BAD_REQUEST)
return Response({
'status': 'Bad request',
'message': 'Account could not be updated with received data.'
}, status=status.HTTP_400_BAD_REQUEST)
I desperately need some clarification. I'm unsure how to request flows through the view/serializer methods and I'm not sure how I can save the instance in the serializer and decide which response to return in the view at the same time.
EDIT:
I removed the create and update methods and fixed get_permissions for AccountViewSet and I added username validation to validate as you suggested. I also updated the serializer create and update methods, here are the new versions:
def create(self, validated_data):
instance = super(AccountSerializer, self).create(validated_data)
instance.set_password(validated_data['password'])
instance.save()
return instance
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
password = validated_data.get('password', None)
confirm_password = validated_data.get('confirm_password', None)
if password and confirm_password:
instance.set_password(password)
instance.save()
update_session_auth_hash(self.context.get('request'), instance)
else:
instance.save()
return instance
My only questions are is it necessary to call set_password after create? Does't create set the password for the new user? And is it okay to have no code in the view for create and update? Where does serializer.save() get called without the view code and when does the serializer validate run without a call to serializer.is_valid()?
In your create() method in AccountViewSet class, you are creating Account instance when serializer validation passes. Instead, you should be calling serializer.save().
If you have a look at save() method in BaseSerializer class you'll see that it calls either create() or update() method, depending on whether model instance is being created or updated. Since you are not calling serializer.save() in AccountViewSet.create() method, the AccountSerializer.create() method is not being called. Hope this answers your first question.
The answer to your second question too, is a missing serializer.save(). Replace << what goes here??? >> with serializer.save(). This (as I explained above), will call AccountSerializer.update() method.
AccountViewSet:
you do not need .create() and .update() methods, from your example - existing ones should be sufficient
get_permissions() - first "if" is opening your system too wide imho should be removed - you allow anyone to do POST - "aka" create new account, this is ok, but everything else (like GET or PUT) - should be allowed only for the account owner or (if there is a need!) registered users
AccountSerializer:
API will return HTTP400 on failed validation
make sure that id field is readonly, you do not want someone to overwrite existing users
existing create() method can be removed, but I think your's should looks like:
def create(self, validated_data):
instance = super(AccountSerializer, self).create(validated_data)
instance.set_password(validated_data['password'])
instance.save()
return instance
existing update() method... not sure what you wanted, but:
first line is permitting user to change it's username, without validation e.g. username is unique, even more, you do not check at all what is in the username field passed from the request, it may be even empty string or None - fallback on dictionary.get will be called only when key is missing in the dictionary,
if uniqueness of username will be validated on the db level (unique=True in the model's field definition) - you will get strange exception instead of nice error message for the API)
next is validation of the passwords (again) - that was just tested in validate method
after setting user password you save instance.. second time - maybe it is worth to optimize it and have only one save?
if you do not allowing to update all of the fields, maybe it is a good idea to pass update_fields to save() to limit which fields are updated?
validation - just add username validations ;)
Just to quickly understand DRF (very simplified) - Serializers are API's Forms, ViewSets - generic Views, renderers are templates (decide how data is displayed)
--edit--
few more items regarding viewsets, ModelViewSet consist of:
mixins.CreateModelMixin - calling validate & create -> serializer.save -> serializer.create
mixins.RetrieveModelMixin - "get" instance
mixins.UpdateModelMixin - calling validate & update/partial update -> serializer.save -> serializer.update
mixins.DestroyModelMixin - calling instance delete
mixins.ListModelMixin - "get" list of instances (browse)
I want to customize the JSON response when adding a new item to the database it returns the following.
HTTP 400 BAD REQUEST
Content-Type: application/json Vary:
Accept Allow: POST, OPTIONS
{
"nick": [
"Users with this Nick already exists."
]
}
and
{
"nick": [
"Your username is empty"
]
}
I want it to return (This username already exists, please use a different one.)
or
"Username %s already exists", (self.nick)
I used the following sample but does not work if the value is empty or invalid.
def validate_title(self, attrs, source):
"""
Check that the blog post is about Django.
"""
value = attrs[source]
if "django" not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return attrs
this is the JSON that gets sent to the API.
{
"name": "myname",
"nick":"",
"type_account":"1",
"email":"my-email#gmail.com",
"pass_field":"12345"
}
serializers.py
class userSerializer(serializers.ModelSerializer):
class Meta:
model = users
fields = ('nick', 'email', 'pass_field', 'type_account')
def validate_nick(self, attrs, source):
value = attrs[source]
if not value:
raise serializers.ValidationError('Username cannot be empty')
elif self.Meta.model.objects.filter(nick=value).exists():
raise serializers.ValidationError("Username "+value+" is in use")
return attrs
views.py
#api_view(['POST'])
def user_add(request):
"""
Saves a new user on the database
"""
if request.method == 'POST':
serializer = userSerializer(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)
the answer to this question also adds the following as #Fiver answered
class userLoginSerializer(serializers.ModelSerializer):
nick = serializers.CharField(error_messages={'required':'Please Type a Username'})
pass_field = serializers.CharField(error_messages={'required':'Please Type a Password'})
class Meta:
model = users
fields = ('nick', 'pass_field')
I believe something like the following will work:
def validate_nick(self, attrs, source):
"""
Check that 'nick' is not already used or empty.
"""
value = attrs[source]
if not value:
raise serializers.ValidationError("Nick cannot be empty!")
elif self.Meta.model.objects.filter(nick=value).exists():
raise serializers.ValidationError("Username %s already exists", value)
return attrs
It should be possible to alter the error messages at the model level, but unfortunately REST Framework doesn't support that yet. Here is an issue dealing with the problem. It includes a suggested method for overriding the validator in the serializer.
You should use custom error handler. Follow here to setup.
Your Custom error handler should be like this:
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
for field, value in response.data.items():
value = ''.join(value)
response.data = {} # Empty django's custom error
response.data['detail'] =value #customize it how you want
return response
I've got a model with a boolean field that I'd like to deserialize with the Django rest framework and I want the serializer to complain when a field is missing in the post request. Yet, it doesn't. It silently interprets a missing boolean as False.
class UserProfile(models.Model):
"""
Message between two users
"""
user = models.OneToOneField(User, verbose_name="django authentication user", related_name='user_profile')
newsletter = models.BooleanField(null=False)
research = models.BooleanField(null=False)
The model is created with a Serialiser like this:
class UserProfileSerializer(serializers.ModelSerializer):
research = BooleanField(source='research', required=True)
newsletter = BooleanField(source='newsletter', required=True)
class Meta:
model = UserProfile
fields = ('research', 'newsletter')
In my view I'm also creating a user, so I have some manual steps:
def post(self, request, format=None):
userprofile_serializer = UserProfileSerializer(data=request.DATA)
reg_serializer = RegistrationSerializer(data=request.DATA)
phone_serializer = PhoneSerializer(data=request.DATA)
errors = {}
if userprofile_serializer.is_valid() and reg_serializer.is_valid() and phone_serializer.is_valid():
user = reg_serializer.save()
data = reg_serializer.data
user_profile = userprofile_serializer.object
user_profile.user = user
userprofile_serializer.save()
return Response(data, status=status.HTTP_201_CREATED)
errors.update(reg_serializer.errors)
# ...
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
However, the following test case fails, because the rest framework doesn't complain about the missing param but instead inserts a False in from_native
def test_error_missing_flag(self):
data = {'username': "test", 'password': "123test", 'email': 'test#me.com',
'newsletter': 'true', 'uuid': self.uuid}
response = self.client.post(reverse('app_register'), data)
# should complain that 'research' is not found
self.assertTrue('research' in response.data)
If I replace my 'research' field with an Integer field that the serializer fails as expected. Any ideas?
There was an issue with Boolean fields and the required argument. Should now be fixed in master.
See this issue: https://github.com/tomchristie/django-rest-framework/issues/1004
Add
your_field = serializers.NullBooleanField(required=False)
in serializer.
That's it. It'll work :)
For anyone who has read #Tom's accepted answer from 2013 and finds that this still doesn't work, it's because this behavior is intended for HTML form inputs. Here's the original issue.
To use serializers.BooleanField with a JSON payload, convert your request.POST to a Python dict by doing request.POST.dict() and pass it to your serializer while initializing.
Create a new custom class:
from rest_framework import serializers
class RequirableBooleanField(serializers.BooleanField):
default_empty_html = serializers.empty
Now, you can use:
research = RequirableBooleanField(required=True)
or
research = RequirableBooleanField(required=False)