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.
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.
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.
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.
When we receive a Request object in Laravel, is there a way to modify or add data to it? For instance, could I rename a parameter (not the value, but the parameter name itself) to something else? For example, the input might be called fname but I want to change it to first_name. Or could I add new inputs and values that weren't in the original request?
The reason I ask is that I have a method that accepts a Request object, and expects certain input names. I'd like to be able to reuse the method, but the request input names will be different.
If you have an Object you can edit and add new items.
$request->url = $new_url;
$request->new_item = 1;
If the object item not exists, then will create automatically, or if it exists, will modify it.
Tested #marc-garcia answer, and that will not persist through your script execution. This will...
// merge defaults into the request.
// this makes it consistent everywhere (blade, controller...)
request()->merge([
// find the request if it exists, second param is the default value
'reservable' =>request( 'reservable', (self::RESERVABLE_BY_DEFAULT?1:0) )
]);
You may also use request()->replace([...]); but that will remove all other parameters from the request and replace it will the array you provide.
We have an existing Django form that accepts GET requests to allow users to bookmark their resulting query parameters. The form contains many fields, most of which are required. The form uses semi-standard boilerplate for handling the request, substituting GET for POST:
if request.method == 'GET':
form = myForm(request.GET)
if form.isValid()
# Gather fields together into query.
else
form = myForm()
The problem is that the first time the form is loaded, there's nothing in the GET request to populate the required fields with, so most of the form lights up with 'missing field' errors.
Setting initial values doesn't work; apparently, the non-existent values in the GET request override them.
How can we avoid this? I'm pretty certain we're simply not processing things correctly, but I can't find an example of a form that handles GET requests. We want errors to show up if the user hits the "Submit" button while fields are blank or otherwise invalid, but don't want these errors showing up when the form is initially displayed.
The positional argument to the forms.Form subclass informs Django that you intend to process a form rather than just display a blank/default form. Your if request.method == 'GET' isn't making the distinction that you want because regular old web requests by typing a URL in a web browser or clicking a link are also GET requests, so request.method is equal to GET either way.
You need some differentiating mechanism such that you can tell the difference between a form display and a form process.
Ideas:
If your processing is done via. AJAX, you could use if request.is_ajax() as your conditional.
Alternatively, you could include a GET token that signifies that the request is processing. Under this example, first you'd need something in your form:
<input type="hidden" name="action" value="process_form" />
And then you can look for that value in your view:
if 'action' in request.GET and request.GET['action'] == 'process_form':
form = myForm(request.GET)
if form.is_valid():
# form processing code
else:
form = myForm()
I'll also give you the standard, boilerplate point that it's generally preferable not to use GET for form processing if you can help it (precisely because you run into difficulties like this since you're using an anomalous pattern), but if you have a use case where you really need it, then you really need it. You know your needs better than I do. :-)
If your clean page load doesn't have any non form GET params, you can differentiate between a clean page load and a form submit in your view. Instead of the usual
form = YourForm()
if request.POST:
you can do
if request.GET.items():
form = YourForm(request.GET)
if form.is_valid():
...
else:
form = YourForm()
If your clean page load could have other params (eg email link tracking params) you'll need to use the QueryDict methods to test if any of your form params are in the request.
request.GET is and empty dictionary when you first load a clean form. Once you have submitted the form, request.GET will be populated with your fields data, even if the fields contain only empty data.
My first question is this, which I posted as comment:
Why not just use request.POST and the standard way of processing form data?
After considering everything here, perhaps what you are looking for is a way of processing data in your query string to populate a form. You can do that without using request.GET as your form.data.
In my own views, I take advantage of a utility function I created to add initial data to the form from request.GET, but I am not going to share that function here. Here's the signature, though. initial_dict is typically request.GET. model_forms is either a single ModelForm or a list of ModelForm.
def process_initial_data(model_forms, initial_dict):
Nevertheless, I am able to process the form through the standard practice of using request.POST when the form is POSTed. And I don't have to pass around all kinds of information in the URL query string or modify it with JavaScript as the user enters information.