Validations of Model Serializers - django

I have to modify the feature of ModelSerializer, my expectations are as,
I have two fields in my Model. Both are charFields.
class MyModel(models.Model):
name = models.CharField(blank=False,
null=False,
max_length=20)
value = models.CharField(blank=True,
null=True,
max_length=20)
My serializer is as,
class MyModelSerializer(ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
I am have to create the object of MyModel using this serializer.
Now issue is that if I am passing the 'bool' values in my fields, its showing error message that 'Not a valid string.' (As expected by Modelserializer)
{ "name":True, "value":False }
My requirements are to handle the 'bool' value and converted that 'bool' into 'str'. what should be the trick to resolve this.

use the to_internal_value function, for example this works:
class MyModelSerializer(ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
def to_internal_value(self, data):
for field in ('name', 'value'):
if field in data and isinstance(data[field], bool):
data[field] = str(data[field])
return super().to_internal_value(data)

Related

Accepting a different field in POST in Django REST Framework

If I have the following Dango model and Django REST serializer:
# model
class Attribute(models.Model):
name = models.CharField(max_length=50)
code = models.CharField(max_length=50)
value = models.IntegerField(default=0)
# serializer
class AttributeSerializer(serializers.ModelSerializer):
name = serializers.CharField()
code = serializers.CharField()
value = serializers.IntegerField()
class Meta:
model = Attribute
fields = ('name', 'code', 'value', 'group')
Is it possible to accept a different field during the PUT or POST to update the model? for example, could it accept attribute_value and use that to update the value field?
There is to_internal_value function read more on Docs:
Override this to support deserialization, for write operations.
You can override it like this:
def to_internal_value(self, data):
if data.get('attribute_value'):
data['value'] = data.pop('attribute_value')
data = super().to_internal_value(data)
return data

Serialization of child models

I have models:
class CommonEditor(models.Model):
def __str__(self):
return 'Common Atributes Mask'
class Color(models.Model):
name = models.CharField(max_length=25)
editor = models.ForeignKey(CommonEditor, on_delete=models.PROTECT, null=True)
So I make serialization this way:
class ColorSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = '__all__'
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
And then view:
class CommonAttributeAPIView(generics.ListCreateAPIView):
serializer_class = CommonAttributesSerializer
queryset = CommonEditor.objects.all()
I get only pk of my CommonEditor Model. Why can't i get the full Atributes Mask and how can I fix it? Big thanks!
Default name for reverse foreign key relation is modelname_set or in your case color_set. So try to rename color field to color_set:
class CommonAttributesSerializer(serializers.ModelSerializer):
color_set = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color_set')
This can also be achieved via SerializerMethodField and can be seen as follow:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = serializers.SerializerMethodField()
class Meta:
model = CommonEditor
fields = ('pk', 'color')
def get_color(self, common_editor):
return ColorSerializer(common_editor.color_set.all(), many=True).data
Documentation: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
The CommonAttributesSerializer search for a color attribute in CommonEditor's instance, but it couldn't find. In DRF serializer, a parameter called source will says explicitly where to look for the data. So , change the serializer as below:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True, <b>source='color_set'</b>)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
Reference : DRF Fields -source

Validate field not in model by Django rest

I have a model:
class EventTracker(models.Model):
"""
Track events of user's behaviors
"""
class Meta:
verbose_name = "EventTracker"
verbose_name_plural = "EventTrackers"
unique_together = ("application", "label")
application = models.ForeignKey(Application, related_name='events')
label = models.CharField(max_length=50)
count = models.PositiveIntegerField(default=0)
value = models.IntegerField(null=True)
def __str__(self):
return "[{}] {}".format(self.application, self.label)
This is my serializer for this model:
class EventTrackerSerializer(serializers.ModelSerializer):
subscriber_id = serializers.IntegerField(min_value=1)
class Meta:
model = EventTracker
fields = ('id', 'application', 'label', 'count', 'value', 'subscriber_id')
write_only_fields = ('subscriber_id', )
read_only_fields = ('count',)
subscriber_id is a field that doesn't belong to this model. But request data must have subscriber_id to do a thing. So I want to validate it in serializer. I don't know how to validate it. I tried like above, it threw error:
This may be because you have a writable field on the serializer class that is not a valid argument to.....
So what can I do ?
First, you should probably be more explicit about what you want to do. We don't know what that field is for nor if it's readable nether what/how you want to validate it, so I'd do some guesswork.
Assuming it's write only:
subscriber_id = serializers.IntegerField(min_value=1, write_only=True)
Note that the write_only_fields has been removed for some time.
Next, you'll have to write manually the serializer's create/update. Example for the create:
class EventTrackerSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
subscriber_id = validated_data.pop('subscriber_id')
instance = EventTracker.objects.create(**validated_data)
return instance

Serializer ForeignKey results in "Expected a dictionary ..."

My Model:
class Font(ValidateVersionOnSaveMixin, models.Model):
id = models.UUIDField(primary_key=True, editable=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Glyph(ValidateVersionOnSaveMixin, models.Model):
id = models.UUIDField(primary_key=True, editable=True)
unit = models.CharField(max_length=100, blank=False, null=False, unique=True)
font = models.ForeignKey(Font, on_delete=models.CASCADE)
I want to post the following JSON to add a Glyph to an already existing Font (having the fontId as ID) object.
{
fontId: "4a14a055-3c8a-43ba-aab3-221b4244ac73"
id: "40da7a83-a204-4319-9a04-b0a544bf4440"
unit: "aaa"
}
As there is a mismatch between the ForeignKey Field font and the JSON propertyfontId I am adding source='font' in my Serializer:
class FontSerializer(serializers.ModelSerializer):
class Meta:
model = Font
fields = ('id', 'name')
class GlyphSerializer(serializers.ModelSerializer):
fontId = FontSerializer(source='font')
class Meta:
model = Glyph
fields = ('id', 'unit', 'fontId' )
But the result is an BAD REQUEST Error:
{"fontId":{"non_field_errors":["Invalid data. Expected a dictionary, but got str."]}}
Update
The following Serializer gave me the result I was looking for.
class GlyphSerializer(serializers.ModelSerializer):
fontId = serializers.PrimaryKeyRelatedField(
queryset=Font.objects.all(),
required=True,
source='font',
write_only=False
)
class Meta:
model = Glyph
fields = ('id', 'unit', 'version', 'fontId')
You can user model_to_dict method as bellow:
from django.forms.models import model_to_dict
model_to_dict(obj)
You have defined fontId as being a serialized object (FontSerializer). But that serializer in turn is defined as having both an id and a name. Where as your json dictionary is posting only an id. You would have to change that to a dictionary that contains both an id and a name
{
fontId: {id: "4a14a055-3c8a-43ba-aab3-221b4244ac73",name: "some name" },
id: "40da7a83-a204-4319-9a04-b0a544bf4440"
unit: "aaa"
}
The reason you are getting this error is that during deserialization process, DRF calls .is_valid(raise_exception=True) before you can call serializer.save(validated_data). And non_field_errors lists any general validation errors during this process. In your GlyphSerializer, your FontSerializer is a nested serializer, which correlates to a Python dictionary. So it will raise an error like you encountered for any non-dictionary data types.
You could create a subclass of GlyphSerializer called GlyphCreateSerializer
class FontSerializer(serializers.ModelSerializer):
class Meta:
model = Font
fields = ('id', 'name')
class GlyphSerializer(serializers.ModelSerializer):
fontId = FontSerializer(source='font')
class Meta:
model = Glyph
fields = ('id', 'unit', 'fontId' )
class GlyphCreateSerializer(GlyphSerializer):
fontId = serializers.CharField()
And you can use GlyphCreateSerializer for the POST request on your Viewset.

many=True TypeError object is not iterable

I want to fetch the foreign key values in PUT and GET but while using the many=True I am getting error TypeError object is not iterable.
Here are following the my snippets.
I have two models called MasterStatus and MasterType. In MasterType I have foreign key values of MasterStatus.
models.py
class MasterType(models.Model):
id = models.BigIntegerField(primary_key=True)
type_name = models.CharField(max_length=255, blank=True, null=True)
fk_status = models.ForeignKey(MasterStatus)
def __unicode__(self):
return u'%s' % (self.type_name)
class Meta:
managed = False
db_table = 'master_type'
In serializer I am using the many=True to get the nested values of foreignkey. Here I have used PrimaryKeyRelatedField serializer.
serializer.py
class MasterTypeSerializer(serializers.HyperlinkedModelSerializer):
fk_status = serializers.PrimaryKeyRelatedField(queryset=MasterStatus.objects.all(),many=True)
class Meta:
model = MasterType
fields = ('id', 'type_name', 'fk_status', 'last_modified_date', 'last_modified_by')
depth = 2
ForeignKey links to a single MasterStatus instance, therefore it is not many.
Your serializers should look something like this:
class MasterTypeSerializer(serializers.HyperlinkedModelSerializer):
fk_status = serializers.PrimaryKeyRelatedField(
queryset=MasterStatus.objects.all())
class Meta:
model = MasterRepaymentType
class MasterStatusSerializer(serializers.HyperlinkedModelSerializer):
fk_type = serializers.PrimaryKeyRelatedField(
queryset= MasterRepaymentType.objects.all(), many=True)
class Meta:
model = MasterStatus
Note that many is used on the fk_type field as a MasterStatus has many MasterRepaymentType.
Hope this helps.