I have a very strange error, this is my serializer class
class user_ser(ModelSerializer):
class Meta:
model = User
fields = '__all__'
depth = 1
whenever I send an API request to update user data, i got default values for is_staff and is_superuser
in the image below I send only email and password
example :
look what I got :(
this is the validated_data for the update method is :
I did not add is_staff or anything else to the request body, so why is that happening.
That's normal behavior; is_staff and is_superuser are default fields in Django used for authorization of admin users. You can view them as columns in the DB.
The real problem comes from using fields = '__all__' in the Meta class.
This is an anti-pattern since you can expose fields you didn't intend to expose. You should explicitly display fields that you intend to use.
Explicit is better than implicit.
Related
I'm building a Django REST API which has access to our existing database with existing users. The purpose of this API is allowing the upcoming mobile application to make requests.
I'm sending a post request to a view with custom authenticator to verify the sent account details.
My existing model:
class LogonAccount(models.Model):
id = models.BigIntegerField(primary_key=True)
email = models.TextField()
two_step_enabled = models.BooleanField()
password = models.TextField(blank=True, null=True)
username = models.TextField(unique=True, blank=True, null=True)
My View and Serializer
class LogonAccountViewSet(viewsets.ModelViewSet):
queryset = LogonAccount.objects.all().order_by('username')
serializer_class = LogonAccountSerializer
class LogonAccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LogonAccount
fields = ('username', 'email')
Sending a post request to this endpoint with username and password in its body keeps returning me a bad request (400) with:
{
"username": [
"logon account with this username already exists."
],
"email": [
"This field is required."
]
}
Making the fields not required in the serializer just changes the error to database constraints (null value in column id)
class LogonAccountSerializer(serializers.HyperlinkedModelSerializer):
username = serializers.CharField(required=False)
email = serializers.CharField(required=False)
class Meta:
model = LogonAccount
fields = ('username', 'email')
I'm not trying to insert data, just trying to validate it.
What am I doing wrong or how do I stop it from trying to insert data?
The error you are getting is not because the DRF is trying to insert data but because of the validations available in the serializer, you are using. You are getting two validation errors:
email is required. Since in your model, the email field is not allowing blank or null values, so in the serializer, this is treated as a required field. So you need to handle it. Either you send that in the post request or make that field non-mandatory.
username is violating a unique constraint. In your model, you have set the username field as unique. So when a serializer is generated this unique field validation is added to that field.
If you want to see the serializer generated for your model serializer and all the fields and validations for the serializer, you can use the following code:
ser = LogonAccountSerializer()
print(repr(ser))
After the comment from #spoontech. The DB operation is performed because you are using viewsets.ModelViewSet. If you just want to use the serializer for validating data, you can use it this way:
serializer = LogonAccountSerializer(request.data)
is_valid = serializer.is_valid()
if is_valid:
# do something using serializer.validated_data
else:
# access errors using serializer.errors()
for example I have a django model as
class User(models.Model):
email = models.EmailField(required=True, unique=True)
Isnt it redundant and against DRY principle to validate again in the ModelSerializer as following?
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def validate_email(self, email):
try:
User.objects.get(email=email)
raise serializers.ValidationError("Email address already used")
except User.DoesNotExist:
return email
The validate_email method feels kind of against the DRY PRINCIPLE and wrong in this context since we have to access Database to validate in this method.
please correct me.
You don't have to validate the data again in the serializer if you have validation already in model level. In your case, the difference is the error message that you'll get from the API.
By default, DRF return {'field_name':['This field must be unique']} response in case of unique validation fail
Quoting #dirkgroten's comment
Note that to override the generic error message, you don't need to re-validate either. Just use the extra_kwargs attribute on the serializer as explained here
I have an API developed with Django Rest Framework. I have a model with some nullable fields, they are defined with the setting
required = False
in the serializer. When I want to update an instance of this model, with a PUT request to the api, I succeed If I send the request parameters as form data, but If I send a json with request payload, the API returns a 400 bad request, stating that my non-required parameters can not be null, as in:
"gender":["This field may not be null."]
When I inspect the requests, the one with form data (which succeeds) sends:
email=abc%40abc.com&first_name=John&gender=&id=13&image_url=http%3A%2F%&last_name=Doe
And the one with json data (which fails with a 400 error) sends:
{
"id":13,
"email":"abc#abc.com",
"first_name":"John",
"last_name":"Doe",
"image_url":"http://...",
"gender":null
}
Any ideas what could be the reason?
EDIT: Model and serializer fields for gender:
In models.py:
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, null=True, blank=True)
In serializers:
gender = serializers.CharField(required=False, source='userprofile.gender')
EDIT:
From the docs:
Note: If your <field_name> is declared on your serializer with the parameter required=False then this validation step will not take place if the field is not included.
So validation step will take place if this fields is included, but still, as it is defined as nullable in the db, it should pass the validation.
When you mark a field as required=False it means that your request data can miss that field and value.
You send this in your request:
email=abc%40abc.com&first_name=John&gender=&id=13&image_url=http%3A%2F%&last_name=Doe
So you're sending a value for gender, I guess your problem is in your models, where gender is not marked as null=True. If you remove gender from your request, this should work.
email=abc%40abc.com&first_name=John&id=13&image_url=http%3A%2F%&last_name=Doe
You can use in serializers.CharField the options default. This way must work
There is a serializer option, allow_null in Django Rest Framework. I had to set it to True for nullable fields in the serializer. It started working after that.
gender = serializers.CharField(required=False, allow_null=True, source='userprofile.gender'
Though I still do not know why I need to set this flag explicitly, as the field is already defined as nullable in the model.
I wanted to make email field unique in default django User model. So i made it unique_together=[('email',)] . Now in serializer I want it to be a read_only field.
But Django Rest Framework 3.0 docs says:
There is a special-case where a read-only field is part of a
unique_together constraint at the model level. In this case the field
is required by the serializer class in order to validate the
constraint, but should also not be editable by the user.
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
One example of this is a read-only relation to the currently
authenticated User which is unique_together with another identifier.
In this case you would declare the user field like so:
user = serializers.PrimaryKeyRelatedField(read_only=True,
default=serializers.CurrentUserDefault())
serializers.CurrentUserDefault() represents the current user.I want to set default as user's email . Isn't serializers.CurrentUserDefault() equivalent to request.user . serializers.CurrentUserDefault().email is giving error 'CurrentUserDefault' object has no attribute 'email' How to set email default as user's email ?
This is what the documentation of CurrentUserDefault says:
A default class that can be used to represent the current user. In
order to use this, the 'request' must have been provided as part of
the context dictionary when instantiating the serializer.
You can either do that or you can provide the email id passed in the context data in your views. Override the function get_serializer_context
def get_serializer_context(self):
context = super(YourClass, self).get_serializer_context()
context['email'] = request.user.email
return context
in your views. Your view should be extended from GenericAPIView at some level of inheritance. Now in your serializer, override your __init__ and get your email data.
def __init__(self, instance = None, data = serializers.empty, *args, **kwargs):
self.email = kwargs['context']['email']
Now you can use that in your serializer.
Please check solution 2 from this answer
The code can be:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email',)
extra_kwargs = {
'email': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a email via request.user by default."""
if 'email' not in validated_data:
validated_data['email'] = self.context['request'].user.email
return super(UserSerializer, self).create(validated_data)
hope it help :)
I'm trying to create an update profile page for my custom User model. In my model, my email field is set to be unique.
class User(UserBase):
...
email = models.EmailField(
max_length=100,
unique=True,
blank=True,
verbose_name='email address',
)
Then in my view I have:
class UpdateProfileView(LoginRequiredMixin, UpdateView):
template_name = 'accounts/update-profile.html'
form_class = UpdateProfileForm
model = User
The only thing that UpdateProfileForm does is check that the old password is different from the new one in the clean method.
My issue is that when I save the form I'm getting the error message User with this Email address already exists.. Since it's an update view and saving a unique field that hasn't changed shouldn't it not throw this error? If this is the correct behavior, then how do I save the form and ignore the email address if it hasn't changed.
Thanks for the help in advance.
Remove blank=True from your User Model definition. The field definition is null=False by default and additionally you specify the field must be unique—it's a important field—so you don't want your form validation to allow blank values. Here is the Django documentation on those attributes. blank is entirely a form validation thing. That alone might fix the error.
Unless you have custom form logic/validation, you don't need the form_class attribute on your UpdateProfileView. From the docs: "These generic views will automatically create a ModelForm". (There is even an UpdateView example).
See if the view works without form_class and if it does then examine your UpdateProfileForm code.
Here are some suggestions/alternatives:
If you stop using Generic Views (i.e. UpdateProfileView), you can then do the following steps in your logic view: if the request is a POST, take the data from the form and update your Model (How to update fields in a model without creating a new record in django?)
Why don't you use a ModelForm instead? https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
Why don't you work with User from django.contrib.auth.models? https://docs.djangoproject.com/en/dev/topics/auth/default/#user-objects
Finally, have you considered working with this already built Django registration app? http://www.michelepasin.org/blog/2011/01/14/setting-up-django-registration/
Never use blank=True and unique=True, its senseless. If you want to make this field is not required in form, just do.
class Form(forms.Form):
...
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['unique_field].required = False
In addition to previous answer, when u use blank=True and unique=True, the "Is already exits ... blah bla" its correct behavior, coz form accepting empty string as value and its already exists. You need to override clean_field method:
class Form(forms.Form):
...
def clean_unique_id(self):
"""
Take new value or if its '' e.g. None take initial value
"""
return self.data['unique_id'] or self.initial['unique_id']