Overwriting serializer with request data including nulls for absent keys - python

Let's imagine there is a serializer like this:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = (
'title',
'description'
)
Where description is nullable. What I want is that the request data completely overwrite the serializer data on PUT request (when updating an existing model instance obviously). If I do:
event_serializer = EventSerializer(event, data=request_data)
It does overwrite everything, but it doesn't nullify description if it is absent from the request. Is there a way to do that without doing manually:
data['description'] = data.get('description', None)

One option is to define the description field on the serializer and use default like:
class EventSerializer(serializers.ModelSerializer):
# Use proper field type here instead of CharField
description = serializers.CharField(default=None)
class Meta:
model = Event
fields = (
'title',
'description'
)
See the documentation as well:
default
If set, this gives the default value that will be used for the
field if no input value is supplied. If not set the default behavior
is to not populate the attribute at all.
May be set to a function or other callable, in which case the value
will be evaluated each time it is used.
Note that setting a default value implies that the field is not
required. Including both the default and required keyword arguments is
invalid and will raise an error.

Related

DRF: Programmatically get default choice from TextChoices field

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

Django Rest Framework: source / re-name without re-confirming the field settings

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 want my django rest framework serializer to accept input but not add to model

I removed some fields from my model, but I want the serializer to still accept the fields as input. How do I have fields the serializer accepts but doesn't use?
class EventBaseSerializer(ModelSerializer):
class Meta:
model = models.Event
fields = ("id", "name")
#unused_fields = ("last_name")
From http://www.django-rest-framework.org/api-guide/serializers/
You can add extra fields to a ModelSerializer or override the default
fields by declaring fields on the class, just as you would for a
Serializer class.
class AccountSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
groups = serializers.PrimaryKeyRelatedField(many=True)`
class Meta:
model = Account
If you want a field to be used for input but not output, you'll need to add this field to the fields list and mark it as write_only likely with extra_kwargs
In my case, I want to get data other than the model has and use them in the serializer methods for something else. But the default "create" method of the serializer try to create the object using those foreign fields too and should give an error like:
Got a TypeError when calling MyModel.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to MyModel.objects.create(). You may need to make the field read-only, or override the MyModelCreateSerializer.create() method to handle this correctly.
I fixed the issue by popping them from the serializer's data after using them as I want and everything continued just fine.
class MyModelCreateSerializer(serializers.ModelSerializer):
foreign_input_1 = serializers.DateField(write_only=True)
foreign_input_2 = serializers.DateField(write_only=True)
class Meta:
model = MyModel
fields = '__all__'
def validate(self, data):
MySecondModel.objects.create(foreign_input_1=data.pop('foreign_input_1'),
foreign_input_1=data.pop('foreign_input_2'))
return data
Don't forget to use the write_only=True parameter on the foreign fields. The serializer will try to read them from the object in any data return operation, like returning the value of the created object.

DRF serializer skips nested serializer validation when many=True

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.

using required=False, still getting required field error

I have a form class which takes a model, meta class of the form is as below,
the problem is that I want to make the patient_signature and worker_signature fields unrequired, I tried removing the class wide required_css_class but that did not help, giving each attribute classes required as True/False is also not helping.
Any suggestions...?
class Meta:
model = Locator
exclude = ('patient','worker', 'mode_of_transmission', 'secondary_telephone_number', 'locator', 'grant', 'thumbnail')
creation_date=forms.DateField(initial=datetime.date.today,
widget=SelectDateWidget(),
label="Creation Date")
patient_signature=forms.CharField(widget=ClientSignatureWidget())
worker_signature=forms.CharField(widget=WorkerSignatureWidget())
required_css_class = 'required'
Assuming you are talking about a ModelForm, you cannot override the fields inside the Meta class. It must be outside.
Also, if the field is required in the model but not in the form, then you must provide a default value, like this:
class LocatorForm:
patient_signature = forms.CharField(widget=forms.HiddenInput(), initial=" ")
class Meta:
...
Alternatively, do not mention that field in the fields list and set a value by overriding the submission of the form's POST.
patient_signature=forms.CharField(widget=ClientSignatureWidget(), required=False)

Categories