Django rest framework does model field validation before own validation - validation

I don't understand django rest framework validation process.
The doc say that validation is performed entirely on the serializer class.
However,
In the case of a PositiveIntegerField in a Model, when ModelSerializer receive a negative value, the model validation is processed before the custom ModelSerializer validate_myfield method.
class Blop(models.Model):
quantity = models.PositiveSmallIntegerField()
class BlopSerializer(serializers.ModelSerializer):
def validate_quantity(self, value):
return max(15, value)
class Meta:
model = Blop
if quantity if lesser than zero, validate_quantity is never called...
I've also tried :
to use model "clean" method but rest framework doesn't use it.
to disable min value validator in model by setting min_value to None, but it make rest framework to crash
to force MinValueValidator in model field : validators=[MinValueValidator(-math.inf, 'Minimum value is -Infinity')], it work, but it's too ugly
How can I do to have a serializer ensure non negative value in this case ?

I think your problem is that you are invoking the serializer.is_valid() method with no kwargs or raise_exception=False.
With raise_exception=True the serializer raises ValidationError, if you pass invalid data.
For better understanding, lets take a look at the Serializer.to_internal_value method.
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, dict):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
})
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = list(exc.messages)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
As you can see here, firstly, the serializer invokes the field.run_validation method, which uses django-fields validators and they raise DjangoValidationError exception and because of that, your validation method is never invoked.

Related

DRF, use method for read, and field for write

Say you have a quantity field in your order model.
You have get_quantity method which you want to use for read (serializing).
At the same time, you want to use quantity field to create or update.
quantity = serializers.IntegerField(source="get_quantity", required=False)
fails to use quantity when writing.
How to do something like
read_source="get_quantity"
write_source="quantity"
Method - 1: override the to_representation(...) method
class FooSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False) # do not set `source` parameter
def to_representation(self, instance):
rep = super().to_representation(instance)
rep["quantity"] = instance.get_quantity()
return rep
Method - 2: Use two different serializer
class FooReadSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False, source="get_quantity")
class FooWriteSerializer(serializers.Serializer):
quantity = serializers.IntegerField(required=False)

Increasing the count field of model whenever a search is applied

I have a search api
class SearchViewSet(RetrieveModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
query = self.request.query_params.get("query")
final_queryset = search(query,queryset,#some logic)
#logic to generate serialiser and return serialiser.data
serialiser = self.get_serializer(final_ueryset, many=True)
search function returns a list of articles i.e
type(final_queryset) is List
And. I wan't to return the articles order_by('count) as well.
Now I wan't to increase the count of top 3 articles from the final_queryset is there a way of doing this.
Add created_at field in your model, then use this field to find top 3 item or latest 3 items like this, and then increase count
queryset = Article.objects.order_by('-created_at')[:3] # it will return a queryset of latest 3 items
for article in queryset:
article.count += 1
article.save()
serializer = ArticleSerializer(article)
Note: I only shared the logic part here, and i can't test it in my local, but it should work and help you to get the idea. Use it on your need.
Figured out a way for doing this
Just simply iterate on the list and increment the count of the objects in the list
class SearchViewSet(RetrieveModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
query = self.request.query_params.get("query")
final_queryset = search(query,queryset,#some logic)
# to increment the count of top 3 entity_aliases
for instance in final_queryset[:3]:
instance.count += 1
instance.save()
#logic to generate serialiser and return serialiser.data
serialiser = self.get_serializer(final_ueryset, many=True)

use generic types in python to refactor code

I have been trying several ways to refactor the following code as these classes are recurring in my app:
class CreateRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = CreateInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['createdAt'] = datetime.utcnow()
# data['createdBy'] = <user> # TODO: <user> input
record = UnitModel(**data)
db_session.add(record)
db_session.commit()
return CreateRecord(record=record)
class UpdateRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = UpdateInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['updatedAt'] = datetime.utcnow()
# data['updatedBy'] = <user> # TODO: <user> input
record = db_session.query(UnitModel).filter_by(id=data['id'])
record.update(data)
db_session.commit()
record = db_session.query(UnitModel).filter_by(id=data['id']).first()
return UpdateRecord(record=record)
class DeleteRecord(Mutation):
record = Field(lambda: Unit)
class Arguments:
input = DeleteInput(required=True)
def mutate(self, info, input):
data = input_to_dictionary(input)
data['deletedAt'] = datetime.utcnow()
# data['deletedBy'] = <user> # TODO: <user> input
data['isDeleted'] = True
record = db_session.query(UnitModel).filter_by(id=data['id'])
record.update(data)
db_session.commit()
record = db_session.query(UnitModel).filter_by(id=data['id']).first()
return DeleteRecord(record=record)
I was thinking of using generic types but I'm kinda' stuck on how to implement it. I've tried creating a master class and in the
def mutate:
method I'd just check if it's a create, update or delete action but I still want to work with generic types before I do that.
Any help is highly appreciated. TIA.
I've solved this particular problem for myself with a mixin class that includes the following method:
from graphene.utils.str_converters import to_snake_case
class MutationResponseMixin(object):
#classmethod
def get_operation_type(cls):
"""
Determine the CRUD type from the mutation class name.
Uses mutation's class name to determine correct operation.
( create / update / delete )
"""
return to_snake_case(cls.__name__).split('_')[0]
This allows me to include a mutation method in the mixin that is shared by create, update, and delete methods and takes conditional action based on value of get_operation_type.
I also needed a way to determine the base record from the mixin's mutation (which in your case would be UnitModel) so my case I ended up declarding it explicitly as an attribute of each mutation class.

Flask WTF validation False on first submit, True on second

I am using the following code in a Flask-wtforms. I have tried with and without various validators in the SelectField but on the first time the user submits the form validation returns false.
I have also tried removing the extra validate method but still leaving a validator in the SelectField and again validation returns False on first submit.
Essentially I want to know if the SelectField is not set to a value of -1 (ie has been populated by the view method and presumably user is happy with the currently active item). I am not sure why the form if valid on second submit even though nothing else has been selected on the form
forms.py
something = SelectField(label = 'Something', choices = [('-1','Select Something')], default=('-1','Select Something'), id='select_something', validators=[validators.DataRequired(message='Nothing selected')])
#NB the line below with no validator also prints "--validate() returned false"
#something = SelectField(label = 'Something', choices = [('-1','Select Something')], default=('-1','Select Something'), id='select_something')
submit = SubmitField('Submit')
def validate(self):
rv = Form.validate(self)
if not rv:
print("--validate() returned false")
return False
#the line below never fired, see fix in following line
#if self.something.data == -1:
if str(self.something.data) == '-1':
logger.debug("--validate() data==-1")
return False
return True
view.py
form = Something(request.form)
if request.method == 'GET':
#get tuple_list from database
...
form.something.choices = tuple_list
form.something.default = tuple_list[0]
if request.method == 'POST' and form.validate():
print('Something added.')
I was using string instead of integers for the first part of each choices tuple (ie should be (1, 'text')) and not setting default correctly (just set default = n where n = integer).
Note the form.process() call as found here: How do you set a default value for a WTForms SelectField?
Fixes below:
views.py
form = Something(request.form)
#get tuple_list from database
...
form.something.choices = tuple_list
form.something.default = tuple_list[0][0] #integer id value
form.process()
if request.method == 'POST' and form.validate():
return self.render_template('it_works.html')
return self.render_template('select_something.html')
forms.py
#no validator used here
something = SelectField(label = 'Something', choices = [], id='select_something')
submit = SubmitField('Submit')
def validate(self):
if len(self.something.choices) == 0:
return False
return True

Django - JSON response with serialized object

I am trying to send a serialized object using JSON. Here is my view code:
if request.is_ajax():
resp = {}
if request.POST:
if form.is_valid():
g = form.save()
resp['graph'] = serializers.serialize('json', [g, ])
resp['success'] = True
else:
resp['errors'] = form.errors
resp['success'] = False
return HttpResponse(simplejson.dumps(resp), mimetype='application/javascript')
return render(request, 'graph/inlines/create.html', {'form':form})
The problem is (rather obviously) that the 'graph' object I am trying to return is being serialized twice (once with serializers.serialize and again when I used simplejson.dumps) and the object is being received as a json string.
I tried just doing this:
resp['graph'] = g
But it throws a server error as the object obviously isn't serialized when I try to use simplejson.dumps.
Is there a way I can tell it to ignore this key when dumping the data? Would appreciate any help.
Rather than serializing the graph queryset to json the first time, use serializers.serialize('python', g) to convert it to a Python dictionary first. Then the whole thing will be converted to json at the end.

Resources