So I am working with DRF serializers and I came through serializer.save(owner=request.user).
How I understood this is we can pass some fields to the save and it will be saved in the database with other data received from the request.
But when I passed the fields to the save method, it is still giving the errors for those fields that these fields are required
Also the docs show that:
"""Any additional keyword arguments will be included in the validated_data argument when .create() or .update() are called.""""
Please can you explain it.
Related
I have an import that takes data from a foreign API and updates a Django model. The code to do this passes the existing model instance and the incoming data to the serializer class to get an updated instance to save.
For this update I don't want to fail the validation, or the update, if some of the incoming data is wrong, but I would like to notify the errors (warnings) so they can be logged outside of the serializer.
Is there a DRF way of doing this, or any way of doing this?
To be clear: I still want the serializer to validate and raise for other less complex fields, but not the special fields that will need additional processing.
I'm currently over-riding to_internal_value in the serializer so that I can deal with the strangely formed data and record error messages before I let the incoming data through to super().to_internal_value(): https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
My work-around is to add a warnings attribute to the serializer to save the messages in. The to_internal_value over-ride can then selectively remove bad fields from the incoming data and record warning messages in the warnings list.
Update: I think this is going to work but I'm still wondering if there's a better Django built-in approach.
I'm going to answer my own question because I have worked out my intended solution was more reasonable than I thought. If I get a better answer I'll select it instead.
To be able to raise on some invalid fields but not others requires the incoming serialization to be intercepted to avoid the standard behaviour, which is why to_internal_value must be overridden.
To be able to pass back any warnings without raising an exception requires some persistent attribute somewhere, logically on the Serializer object I suppose, because we can't rely on the processing not raising somewhere down the line once we pass execution back to Django ModelSerializer.
This all seems quite solid in retrospect, so I guess I was just being cautious because I hadn't gone this route before. I'd really appreciate anyone raising any gotchas.
Django already has the errors OrderedDict that it uses in the validation exception to formalise per-field error messages. The trouble is, this can't be accessed until is_valid() is called, so another attribute is needed.
Here is a simplified example implementation.
This will work just like the Django ModelSerializer, except the warnings attribute can be referenced after calling is_valid() for reporting back what happened:
class AnObjectSerializer(serializers.ModelSerializer):
"""
Serialize normal fields using Django ModelSerializer magic to raise when invalid.
Intercept incoming crazy foreign fields and record warnings when invalid without raise.
"""
CRAZY_FIELDS = ('foreign_omicron', 'foreign_delta', 'foreign_crazy')
warnings = {} # Some field failures will not invalidate but are reported here
def to_internal_value(self, data):
"""Try to add incoming foreign crazy fields or warn if not."""
foo = data['foo']
for field in self.CRAZY_FIELDS:
value = data.pop(field, None) or {}
value = value.get('foobar') or '' # Dig crazy field out of the incoming blob
value = value.lower() # Normalize crazy field somewhat
# Do not raise serializers.ValidationError to allow AnObject load to continue
# But report errors back to caller for logging in warnings list
if value:
try:
some_external_validation(value)
except serializers.ValidationError:
self.warnings[field] = f'{foo}: foobar "{value}" has a problem'
value = ''
data[field] = value
validated_data = super().to_internal_value(data)
return validated_data
class Meta:
model = AnObject
fields = (
'foo', 'bar', 'baz', 'qux',
'foreign_omicron', 'foreign_delta', 'foreign_crazy',
)
If this is a one time import only you may:
You can iterate over each object , validate it using serializer manually. If the data is valid you may update else you keep a log file with the exceptional objects that failed validation.
A serializer wont do what you are asking, you need to catch the error or you need to modify the validation function for the fields to pass, if you dont want it to fail. he standard purpose of serializer that it will raise error on invalid data.
I have a serializer that accepts two objects, one of which is always constant once a user has authenticated.
The serializer, of course, should not need to know these details; it should know only that it serializes and deserializes an object with two fields.
Further, the user, having authenticated, should only have to pass in the value he wishes to set to the non-constant field via the viewset.
So what I need is a hook somewhere that intercepts the data before the serializer receives it, sets the constant field to the user's set value, and then allows processing to proceed as usual.
Is there any such hook in DRF? The solutions I've seen are all very hacky: context (coupling of view and serialization logic); validations (more coupling); and so forth. In theory what we want is:
data = union(userFields, fixedFieldsForView)
Serializer(data=data)
where the data part is somehow handled in the view only. Note also this is only during creation and update of objects, not reads (which are filtered already by get_queryset).
If the question is about adding additional parameters to the serializer, then yes:
save the sterilizer with the extra arguments serializer.save(extra_arg=value, extra_arg2=value)
override the view's perform_create and perform_update to call the serializer.save
We took the following approach. Feedback welcome.
def get_serializer(self, *args, **kwargs):
if self.request.method == 'POST' and 'data' in kwargs:
kwargs['data']['fixed_value'] = self.get_user_fixed_value()
return super(OurModel, self).get_serializer(*args, **kwargs)
So when a user POSTs to the endpoint, regardless the setting of fixed_value, that value will always be set correctly.
Still feels like a bit of a hack, but works for us for now.
There is a common pattern to return serializer.data after an object has been successfully saved. However, if the to_internal_value returns a Django object (such as in a PrimeryKeyRelatedField), then the Response would produce this error:
raise TypeError(repr(o) + " is not JSON serializable")
A few solutions to this:
Run that data the other way, i.e. FooSerializer(data=serializer.data)
Manually replace it, i.e. serializer.data['field'] = serializer.data['field'].pk
Inherit something somewhere so that the Response can accept Django objects and default to __str__ representation (but probably impossible unless there's a way for isinstance to know if it's a subclass of models.Model)
Don't return the [entire] object. The other side obviously has a copy of the data they sent, so there's no reason to send back the fields that weren't changed by the server.
Which of these solutions is most Pythonic, or is there a better solution not listed here?
There is a common pattern to return serializer.data after an object has been successfully saved.
No, this may be a common anti-pattern.
It returns serializer.validated_data which is different.
In DRF v3.1, I have a nested serializer much like the one detailed in the docs - http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
class SerializerA(serializers.Serializer):
details = DetailsSerializer(required=False)
However, when trying to use this serializer and not supplying the details, I receive the following:
{u'details': [u'This field may not be null.']}
This seems incorrect given the docs?
Has anyone else come across this or can verify this as a bug?
Ok, so Kevin Browns comment is correct. I needed to add allow_null=True.
class SerializerA(serializers.Serializer):
details = DetailsSerializer(required=False, allow_null=True)
The reason for this is that having required=False allows the field details to be absent from the data when constructing the serializer.
e.g.
s = SerializerA(data={})
whereas allow_null permits the the param to be specified but be null.
e.g.
s = SerializerA(data={'details': None})
This opens up another issue with DRF Browsable API, but i'll ask that in a different question.
I am sending over a series of array values from a posted form to an MVC3 Controller. I was hoping the default modelbinder would be able to parse this but I'm having some difficulty with it.
The array is in the following format:
order[0].[type]=some value.
I think this is the reason the model binder is not parsing my values because I'm not getting anything populated in my model.
What would be another way to handle this?
Probably need to post more of your code so I can see what you are doing exactly. However saying this you need to pass the model to the view/partial view on the response you are trying to retrieve on the post request.
If not you will have to iterate through the Form Collection that will be returned and the Actions Methods type e.g. ActionMethodName(FormCollection form), one issue is name versus id its the name of the Kendo UI control that is used to get the value not the id.
1As far as I remember the right format was:
orders[0].OrderID=13;
orders[0].Name="test";
orders[1].OrderID=15;
orders[1].Name="again test";
The indexing should start from 0 and increase by 1.
Check this out: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx