Custom validator for nested field in serializer - python

In my BookSerializer, I have a nested field page:
class PageSerializer(serializers.ModelSerializer):
...
class BookSerializer(serializers.ModelSerializer):
page = PageSerializer()
and the page field validator expects an dictionary as value. But what I want is it should accept an integer as well (page's id). So in the BookSerializer, I tried to override the validate function for the page field but it didn't work:
class BookSerializer(serializers.ModelSerializer):
page = PageSerializer()
def validate_page(self, value):
if isinstance(value, int):
return value
# if value is not an integer, reuse the default validator
# but django said that validate_page is not a function
return super().validate_page()
Seems like the validate_page function is never called because it's a nested field.
Thanks !

Correct way to create custom validation is:
def validate_page(self, value):
if isinstance(value, int):
return value
return value
But it won't be working.
You need override to_internal function on Page serializer:
class PageSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
return get_object_or_404(Page, pk=data)
...
class BookSerializer(serializers.ModelSerializer):
page = PageSerializer()

Related

How to add child instance foreign key IDs as a serializer list field idiomatically?

I've got a many-to-many model like Request ← RequestItem → Item, and I want the response from the Request API endpoint to include a list of Item IDs. I've got a working serializer method like this:
def to_representation(self, instance: Request) -> typing.Dict[str, Any]:
representation: Dict = super().to_representation(instance)
representation["items"] = [
item_id for item_id
in instance.requestitems_set.values_list("item_id", flat=True)
]
return representation
As you can see, this is horrible. What would be an idiomatic way of getting the exact same output?
It can be added quite straightforward like any other field in the serializer
class RequestSerializer(serializers.ModelSerializer):
....
items = serializers.SlugRelatedField(
source='requestitems_set',
slug_field='item_id',
read_only=True,
many=True,
)
And then of course add it to the list of fields
There are few related fields implemented by DRF. But API is open for you to implement your own one. I think this is more readable and clean solution.
class ItemIdRelatedField(serializers.RelatedField):
def to_internal_value(self, data):
pass
# implement if you need it.
def to_representation(self, value):
return value.item_id
And use it as field in serializer like this.
items = ItemIdRelatedField(many=True, source='requestitems_set', queryset=RequestItem.objects.all())
from given info, you could reduce the use of for loop as
def to_representation(self, instance: Request) -> typing.Dict[str, Any]:
representation: Dict = super().to_representation(instance)
representation["items"] = list(instance.requestitems_set.values_list("item_id", flat=True))
return representation

Django import-export choices field

I have a model with choices list (models.py):
class Product(models.Model):
...
UNITS_L = 1
UNITS_SL = 2
UNITS_XL = 3
PRODUCT_SIZE_CHOICES = (
(UNITS_L, _('L')),
(UNITS_SL, _('SL')),
(UNITS_XL), _('XL'),
)
product_size = models.IntegerField(choices=PRODUCT_SIZE_CHOICES)
...
Also I added a new class for exporting needed fields(admin.py):
from import_export import resources, fields
...
Class ProductReport(resources.ModelResource):
product_size = fields.Field()
class Meta:
model = Product
#I want to do a proper function to render a PRODUCT_SIZE_CHOICES(product_size)
def dehydrate_size_units(self, product):
return '%s' % (product.PRODUCT_SIZE_CHOICES[product_size])
fields = ('product_name', 'product_size')
Class ProductAdmin(ExportMixin, admin.ModelAdmin):
resource_class = ProductReport
But this is not working. How can I get a named value of PRODUCT_SIZE_CHOICES in export by Django import-export ?
You can use 'get_FOO_display' to achieve this in the Django Admin:
class ProductReportResource(resources.ModelResource):
product_size = fields.Field(
attribute='get_product_size_display',
column_name=_(u'Product Size')
)
In my case I was trying to get the display from a foreign key choice field, like:
user__gender
After unsuccessfully trying the accepted answer and the other answer by Waket, I found this thread here:
https://github.com/django-import-export/django-import-export/issues/525
From where I tried a couple of options, and the one that finally worked for me is this:
Create the widget somewhere
from import_export.widgets import Widget
class ChoicesWidget(Widget):
"""
Widget that uses choice display values in place of database values
"""
def __init__(self, choices, *args, **kwargs):
"""
Creates a self.choices dict with a key, display value, and value,
db value, e.g. {'Chocolate': 'CHOC'}
"""
self.choices = dict(choices)
self.revert_choices = dict((v, k) for k, v in self.choices.items())
def clean(self, value, row=None, *args, **kwargs):
"""Returns the db value given the display value"""
return self.revert_choices.get(value, value) if value else None
def render(self, value, obj=None):
"""Returns the display value given the db value"""
return self.choices.get(value, '')
In your model resource declare the field using the widget and passing the choices to it, like this:
user__gender = Field(
widget=ChoicesWidget(settings.GENDER_CHOICES),
attribute='user__gender',
column_name="Gènere",
)
Another solution:
class BaseModelResource(resources.ModelResource):
def export_field(self, field, obj):
field_name = self.get_field_name(field)
func_name = 'get_{}_display'.format(field_name)
if hasattr(obj, func_name):
return getattr(obj, func_name)
return super().export_field(field, obj)
class ProductReportResource(BaseModelResource):
...

Python - Unable to modify the serializer.data dictionary in Django Rest Framework

I have tried to add a key serializer.data['test'] = 'asdf', this does not appear to do anything.
I want to transform the representation of a key's value. To do this, I'm trying to use the value to calculate a new value and replace the old one in the dictionary.
This is what I want to accomplish, but I don't know why the value is not being replaced. There are no errors thrown, and the resulting dictionary has no evidence that I've tried to replace anything:
class PlaceDetail(APIView):
def get(self, request, pk, format=None):
place = Place.objects.select_related().get(pk=pk)
serializer = PlaceSerializer(place)
#serializer.data['tags'] = pivot_tags(serializer.data['tags'])
serializer.data['test'] = 'asdf'
print(serializer.data['test'])
return Response(serializer.data)
Terminal: KeyError: 'test'
I have observed by printing that serializer.data is a dictionary.
I have also tested that the syntax I'm trying to use should work:
>>> test = {'a': 'Alpha'}
>>> test
{'a': 'Alpha'}
>>> test['a']
'Alpha'
>>> test['a'] = 'asdf'
>>> test
{'a': 'asdf'}
How can I properly modify the serializer.data dictionary?
The Serializer.data property returns an OrderedDict which is constructed using serializer._data. The return value is not serializer._data itself.
Thus changing the return value of serializer.data does not change serializer._data member. As a consequence, the following calls to serializer.data are not changed.
# In class Serializer(BaseSerializer)
#property
def data(self):
ret = super(Serializer, self).data
return ReturnDict(ret, serializer=self)
# In class ReturnDict(OrderedDict)
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super(ReturnDict, self).__init__(*args, **kwargs)
You can keep a copy of the return value of serializer.data, which is an ordered dictionary, and manipulate it as you wish.
Example:
# keep the return value of serializer.data
serialized_data = serializer.data
# Manipulate it as you wish
serialized_data['test'] = 'I am cute'
# Return the manipulated dict
return Response(serialized_data)
Why:
If you look at the source code of Django Restframework, you will see that in Serializer class,
Serializer._data is just a normal dictionary.
Serializer.data is a method decorated to act like a property. It returns a ReturnDict object, which is a customized class derived from OrderedDict. The returned ReturnDict object is initialized using key/value pairs in Serializer._data.
If Serializer.data returns Serializer._data directly, then your original method will work as you expected. But it won't work since it's returning another dictionary-like object constructed using Serializer._data.
Just keep in mind that the return value of Serializer.data is not Serializer._data, but an ordered dictionary-like object. Manipulating the return value does not change Serializer._data.
I believe the reason why serializer.data does not return serializer._data directly is to avoid accidental change of the data and to return a pretty representation of serializer._data.
You'll want to use SerializerMethodField instead of explicitly overwrite the representations.
Building further on #yuwang's answer, I used SerializerMethodField to modify value of a particular field in the serializer. Here's an example:
The field that I wanted to modify, let us call it is_modifyable. This field is present on the Django Model as models.BooleanField and hence it was not present in the list of fields on serializer definition and simply mentioned in the class Meta: definition under within the serializer definition.
So here's how my code looked before:
# in models.py
# Model definition
class SomeModel(models.Model):
is_modifyable = models.BooleanField(default=True)
# in serializers.py
# Serializer definition
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ('is_modifyable',)
As a result of the above, the value for the field is_modifyable was always fetched on the basis of what the value was in the record of SomeModel object. However, for some testing purpose, I wanted the value of this field to be returned as False during the development phase, hence I modified the code to be as follows:
# in models.py
# Model definition (Left unchanged)
class SomeModel(models.Model):
is_modifyable = models.BooleanField(default=True)
# in serializers.py
# Serializer definition (This was updated)
class SomeModelSerializer(serializers.ModelSerializer):
# This line was added new
is_modifyable = serializers.SerializerMethodField(read_only=True)
class Meta:
model = SomeModel
fields = ('is_modifyable',)
# get_is_modifyable function was added new
def get_is_modifyable(self, obj) -> bool:
"""
Dummy method to always return False for test purpose
Returns: False
"""
return False
Once the above code was in, the API call always returned the value of serializer field is_modifyable as False.

Automatically strip() all values in WTForms?

Is there any way to strip surrounding whitespace from all values in WTForms without adding a filter to every single field?
Currently I'm passing filters=[strip_whitespace] with the function shown below to my fields but having to repeat this for every field is quite ugly.
def strip_whitespace(s):
if isinstance(s, basestring):
s = s.strip()
return s
A solution requiring subclassing of Form would be fine since I'm already doing that in my application.
You can do it in WTForms 2.x by using the bind_field primitive on class Meta. The class Meta paradigm is a way to override WTForms behaviors in contexts such as binding/instantiating fields, rendering fields, and more.
Because anything overridden in class Meta defined on a Form is inherited to any form subclasses, you can use it to set up a base form class with your desired behaviors:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
def my_strip_filter(value):
if value is not None and hasattr(value, 'strip'):
return value.strip()
return value
Now, just inherit MyBaseForm for all your forms and you're good to go.
Unfortunately, I have no enough reputation to comment first response.
But, there is extremely unpleasant bug in that example:
When you do filters.append(smth) then on each form initialization filters growth by 1 element.
As a result, your code works slower and slower until you restart it
Consider Example:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
def my_strip_filter(value):
if value is not None and hasattr(value, 'strip'):
return value.strip()
return value
class MyCustomForm(MyBaseForm):
some_field = StringField(filters=[lambda x: x])
for i in range(100):
MyCustomForm(MultiDict({'some_field': 'erer'}))
print(len(MyCustomForm.some_field.kwargs['filters'])) # print: 101
So the fast fix is to check that this filter not in list:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
if my_strip_filter not in filters:
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
I wouldn't be surprised if you could do it by subclassing form, but my solution was to just create custom Stripped* fields. I think this is at least better than passing filters every time because it is less error prone:
from wtforms import StringField, PasswordField
class Stripped(object):
def process_formdata(self, valuelist):
if valuelist:
self.data = valuelist[0].strip()
else:
self.data = ''
class StrippedStringField(Stripped, StringField): pass
class StrippedPasswordField(Stripped, PasswordField): pass

Can a Django ModelForm display a custom representation of stored values?

In my model, percentage data is stored as fractional values in the 0.0-1.0 range. Because they are percentages, I want the user to view and edit the values in the 0.0-100.0 range.
What is the preferred way of transforming data between its stored value and the displayed value? Note that the format depends on another instance variable.
My model looks like this:
class MyModel(models.Model):
format = models.CharField(choices=(('percentage', 'Percentage'), ('absolute', 'Absolute'))
my_value = models.FloatField()
def _get_my_value_display(self):
if self.format == 'percentage':
return self.my_value * 100
else:
return self.my_value
def _set_my_value_display(self, v):
if self.format == 'percentage':
return v / 100
else:
return self.my_value
my_value_display = property(_get_my_value, _set_my_value)
In the form, I tried to use the display property as a form field, but that doesn't work (Unknown field(s) (my_value_display) specified for MyModel):
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('my_value_display', )
Personally, I'd probably try to create a django.forms.FloatField subclass and a django.forms.TextInput subclass, kind of like this:
class PercentageInput(TextInput):
def render(self, name, value, attrs=None):
try:
value = float(value)
value *= 100
except ValueError:
pass
return super(PercentageInput, self).render(name, value, attrs)
class PercentageField(FloatField):
widget = PercentageInput
def to_python(self, value):
value = super(PercentageField, self).to_python(value)
return value / 100
Then just define your ModelForm this way:
class MyForm(forms.ModelForm):
my_value = PercentageField()
It might fail in a few corner cases, maybe also when the user supplies an invalid value, but the basic idea should be all right.

Categories

Resources