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.
Related
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.
Our site is a Vue frontend / DRF backend. In a serializer.validate() method, I need to programmatically determine which option from a TextChoices class has been specified as the default value for a model field.
TextChoices class (abbreviated example):
class PaymentMethod(models.TextChoices):
BANK_TRANSFER = 'Bank Transfer'
CREDIT_CARD = 'Credit Card'
PAYPAL = 'PayPal'
OTHER = 'Other'
The model:
class InvoicePayment(CommonFieldsModelMixin,
StatusFieldModelMixin,
models.Model):
...other fields omitted...
payment_method = models.TextField(
verbose_name=_('Method'),
choices=PaymentMethod.choices,
default=PaymentMethod.OTHER,
)
payment_method_other = models.TextField(
verbose_name=_('Payment Method Other'),
default='',
)
Our users are able to bypass the frontend and post directly to the API, which means they may omit fields from the POST data - either from negligence or because the fields have default values. For the above model, though, payment_method_other is required only if payment_method is "Other". That check is done in the serializer.validate() method.
If "Other" is selected on a form in the frontend, there's no problem because that value is present in validated_data passed to the validate() method. But if a user posts directly to the API and omits payment_method, the default value process is done at the database level (more or less), after the validate() method has executed.
To keep it DRY, and to avoid having mismatched code if the default is changed in the future, I don't want to hard-code the default of "Other" in the validate() method. Instead, I want to access the field definition info (meta data?) and programmatically determine the default that was defined on the model.
One way to do this without much hacking around is to define the default as a property of the model like this:
class InvoicePayment(CommonFieldsModelMixin,
StatusFieldModelMixin,
models.Model):
DEFAULT_PAYMENT_METHOD = PaymentMethod.OTHER
...other fields omitted...
payment_method = models.TextField(
verbose_name=_('Method'),
choices=PaymentMethod.choices,
default=DEFAULT_PAYMENT_METHOD,
)
payment_method_other = models.TextField(
verbose_name=_('Payment Method Other'),
default='',
)
Then you can just access the default easily through the model:
InvoicePayment.DEFAULT_PAYMENT_METHOD
I'd like to change many fields name in DRF ModelSerializer without the need to re-typing the fields.
According a post on SO (ref), one can re-name a field name within the serializer by using source, such as:
newName = serializers.CharField(source='old_name')
However, this method takes away the benefits of using a ModelSerializer as you essentially do the work twice. This become heavy when you have many fields adhering to one internal naming convention but want to display another naming convention within the API.
in my case, I have a model field such as:
product_uid = models.UUIDField(primary_key=False, unique=True, default=uuid.uuid4, editable=False)
In the API, I'd like the field to be called 'uid'.
If I would do the following:
uid = serializers.UUIDField(source=product_uid)
would result in editable=True
Is there a way to reference to a ModelField and keep its definition intact according the Model (as you normally do when using serializers.ModelSerializer) but only change the name, e.g. something like: uid = serializers.ModelField(source=product_uid) ?
If you want your field not to be editable, you can use the read_only parameter (https://www.django-rest-framework.org/api-guide/fields/#read_only)
You can try:
uid = serializers.UUIDField(source=product_uid, read_only=True)
You can also use the ModelSerializer using extra_kwargs :
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['product_uid', 'field_a', 'field_b']
extra_kwargs = {'product_uid': {'source': 'uid'}} # Add `'read_only': True` if needed
If you have many fields, you can generate extra_kwargs programmatically
I have a serializer which refers to another serializer with many=True. Simplified version:
class SerializerA(serializers.Serializer):
id = serializers.CharField()
quantity = serializers.IntegerField()
class SerializerB(serializers.Serializer):
name = serializers.CharField()
items = SerializerA(many=True, required=True)
When I'm getting data without items field specified, serializer.is_valid() for some reason returns True. But if 'many' argument set to False, validation works as expected.
Why is that happening?
I assume you're trying to perform PUT or POST. In this case, DRF do not support nested relations out of the box and if you don't have anything in the field items - validator returns true. It should return an error if you will have something in the items.
To make POST/PUT work you need to override .create() and .update() method of the SerializerB.
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']