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

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.

Related

Django Serializer get name of foreign key

Good day SO.
I saw dozens of SO answer about this but it is not working for me. Please point me on the right direction.
With this model:
class Major(models.Model):
major_name = models.CharField(max_length=100, default='', null=False)
is_active = models.BooleanField(default=True, null=False)
def __str__(self):
return self.major_name
class Minor(models.Model):
major = models.ForeignKey("Major", on_delete=models.CASCADE)
minor_name = models.CharField(max_length=100, default='', null=False)
def __str__(self):
return self.minor_name
I should use this serializer to get the major name in my minor serializer:
class MinorSerializer(serializers.ModelSerializer):
major_name = serializers.CharField(source='major.major_name', read_only=True)
class Meta:
model = Minor
fields = ["id", "minor_name", "major_id", "major_name"]
But major_name is not displayed on my json response.
I also tried to Serialize Major first then call it on my minor:
class MajorSerializer(serializers.ModelSerializer):
class Meta:
model = Major
fields = ["id", "major_name"]
class MinorSerializer(serializers.ModelSerializer):
major_name = MajorSerializer()
class Meta:
model = Minor
fields = ["id", "minor_name", "major_id", "major_name"]
But I got this error message instead "Got KeyError when attempting to get a value for field major_name on serializer MinorSerializer. \nThe serializer field might be named incorrectly and not match any attribute or key on the dict instance.\nOriginal exception text was: 'major_name'."
Note. The same error message if I remove read_only = True on my first answer
Additional Note:
I Tried this, and it gives me the same error message above.
class MinorSerializer(serializers.ModelSerializer):
class Meta:
model = Minor
fields = "__all__"

AttributeError at /api/module/list/

I want many to many fields to be displayed in module serializer instead of id, these are my serializers
class TrainerSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', ]
class ModuleSerializer(serializers.ModelSerializer):
trainer = serializers.CharField(source='trainer.username')
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer',
'publish_choice']
class Trainer(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
def __str__(self):
return str(self.user)
class Meta:
ordering = ['pk']
class Module(models.Model):
title = models.CharField(max_length=80, unique=True)
duration = models.IntegerField(verbose_name='Duration in Days/ Weeks', blank=True, null=True)
trainer = models.ManyToManyField(Trainer, blank=True)
detail = models.TextField(verbose_name='Program Details', blank=True, null=True)
notify = models.BooleanField(default=False)
publish_choice = models.CharField(verbose_name='Publish/ Draft',
max_length=80, choices=PUBLISH_CHOICES, default='publish')
and this is the error message
Got AttributeError when attempting to get a value for field trainer on serializer ModuleSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Module instance.
Original exception text was: 'ManyRelatedManager' object has no attribute 'username'.
We have a depth parameter in the serializer MetaClass. we can make use of it like below. depth=1 will retrieve all fields of a relation.
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer', 'publish_choice']
depth = 1
for reference DRF-Documentation on serializers
Its raise exception because serializers.CharField(source='trainer.username') not match ManyRelatedManager in model trainer = models.ManyToManyField(Trainer, blank=True).
If you want get all username instead of id, you can try add Custom type serialzier like this:
class ModuleSerializer(serializers.ModelSerializer):
trainer = serializers.SerializerMethodField()
def get_trainer(self, obj):
results = []
for item in obj.trainers.all():
results.append(item.username)
return results
class Meta:
model = Module
fields = ['id', 'title', 'duration', 'trainer', 'publish_choice']
trainer will return array of username relation with Module

Django rest framework serializer for membership Model?

How do I use Serializer for something like This
class Language(BaseModel):
name = models.CharField(null=False, blank=False, unique=True, max_length=150)
class Helper(BaseModel):
languages = models.ManyToManyField('Language', blank=True, through="HelperLanguage")
class HelperLanguage(BaseModel):
helper = models.ForeignKey('Helper', on_delete=models.CASCADE)
language = models.ForeignKey('Language', on_delete=models.CASCADE)
read = models.BooleanField()
write = models.BooleanField()
speak = models.BooleanField(default=True)
class LanguageSerializer(ModelSerializer):
class Meta:
model = Language
fields = ["id", "name"]
class HelperLanguageSerializer(ModelSerializer):
language = LanguageSerializer(read_only=True)
class Meta:
model = HelperLanguage
fields = ["id", "language", "read", "write", "speak"]
class HelperPublicSerializer(ModelSerializer):
languages = HelperLanguageSerializer(read_only=True, many=True)
class Meta:
model = Helper
fields = ['id', 'languages']
while using HelperPublicSerialiser for list view I am getting error
Got AttributeError when attempting to get a value for field read on serializer HelperLanguageSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Language instance.
I do understand the problem but couldn't find any solution probably not using membership model right way.

How to create a custom list field when using Django Rest Framework

I have the following model and I would like to add a custom field called choices which will be a list of choices.
class HPIQuestionBank(models.Model):
label = models.CharField(
max_length=200,
db_index=True,
blank=True,
null=True)
template = models.ForeignKey(
HPIFilter, blank=True, null=True, on_delete=models.CASCADE, default='')
I have implemented the following in the serializers.
class CheckBoxesListField(serializers.ListField):
child = serializers.CharField(allow_null = True, allow_blank=True)
class TemplateQuestionBankSerializer(serializers.ModelSerializer):
answer_type = serializers.CharField(allow_null = True, allow_blank=True)
checkboxes = CheckBoxesListField()
hpianswers_set =TemplateAnswerSerializer(many=True)
class Meta:
model = HPIQuestionBank
fields = ['id','label','hpianswers_set','answer_type','checkboxes']
I'm using the serializer on my GET method. When I attempt to make a request I get the following error:
AttributeError at /api/clinic2/history/template/6/
Got AttributeError when attempting to get a value for field `checkboxes` on serializer `TemplateQuestionBankSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `HPIQuestionBank` instance.
Original exception text was: 'HPIQuestionBank' object has no attribute 'checkboxes'.
If you need to read data only you can try:
class Meta:
model = HPIQuestionBank
fields = ['id','label','hpianswers_set','answer_type',]
read_only_fields = ['checkboxes',]
Or you can work with SerializerMethodField as
class TemplateQuestionBankSerializer(serializers.ModelSerializer):
answer_type = serializers.CharField(allow_null = True, allow_blank=True)
checkboxes = SerializerMethodField()
def get_checkboxes(self, instance):
return CheckBoxesListField(instance).data

django queryset annotate all field in childtable

models.py
class Userinfo(models.Model):
useruid = models.BigAutoField(db_column='UserUID', primary_key=True)
useremail = models.CharField(
db_column='UserEmail', unique=True, max_length=100)
userpassword = models.CharField(db_column='UserPassword', max_length=128)
passwordsalt = models.CharField(db_column='PasswordSalt', max_length=128)
class Meta:
managed = False
db_table = 'userinfo'
class Postinfo(models.Model):
postuid = models.BigAutoField(db_column='PostUID',primary_key=True)
useruid = models.ForeignKey(
'Userinfo', db_column='UserUID', on_delete=models.CASCADE)
content = models.TextField(db_column='Content')
class Meta:
managed = False
db_table = 'postinfo'
if i get userlist and user's last post
i think use annotate
models.Userinfo.objects.all().annotate(lastpost="??").order_by("-useruid")
what values in "??"
like this form
[{userinfo1,"lastpost":{postinfofields}},{userinfo2,"lastpost":{postinfofields}},{userinfo3,"lastpost":{postinfofields}}]
can i this query not use forloop?
Serializer.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Postinfo
fields = ('postuid','content')
class UserSerializer(serializers.ModelSerializer):
lastpost = PostSerializer()
class Meta:
model = Userinfo
fields = ['useruid', 'useremail', 'lastpost']
view.py
userinfos = models.Userinfo.objects.all().order_by("-useruid")
result = UserSerializer(userinfos,many=True)
print(result.data)
raise Exception
Got AttributeError when attempting to get a value for field `lastpost` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Userinfo` instance.
Original exception text was: 'Userinfo' object has no attribute 'lastpost'.
if i add read_only=True print this
[OrderedDict([('useuid', 1), ('useremail', 'test')]), OrderedDict([('useruid', 2), ('useremail', 'test2')])]
You can use model's property for this:
class Userinfo(models.Model):
useruid = models.BigAutoField(db_column='UserUID', primary_key=True)
useremail = models.CharField(
db_column='UserEmail', unique=True, max_length=100)
userpassword = models.CharField(db_column='UserPassword', max_length=128)
passwordsalt = models.CharField(db_column='PasswordSalt', max_length=128)
#property
def lastpost(self):
return self.postinfo_set.latest('postuid')
Now you dont need annotation, just use it for example in template like this:
{{ user.lastpost.content }}
UPD
To serialize property with ModelSerializer just add serializer's field 'lastpost':
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Postinfo
fields = ('postuid','content')
class UserSerializer(serializers.ModelSerializer):
lastpost = PostSerializer()
class Meta:
model = Userinfo
fields = ['useruid', 'useremail', 'lastpost']
UPD2
You can also implement logic directly on the serializer level, without model's property. Just use SerializerMethodField:
class UserSerializer(serializers.ModelSerializer):
lastpost = SerializerMethodField()
class Meta:
model = Userinfo
fields = ['useruid', 'useremail', 'lastpost']
def get_lastpos(self, obj):
last = obj.postinfo_set.latest('postuid')
serializer = PostSerializer(last)
return serializer.data