AttributeError at /api/module/list/ - django

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

Related

Django Rest Framework Serializer - return related field

I have a model with a one-to-one relationship with a main model:
class User(models.Model):
id = models.BigIntegerField(primary_key=True)
username = models.CharField(max_length=100, blank=True)
class AggregatedStats(models.Model):
user_id = models.ForeignKey('User', on_delete=models.DO_NOTHING, unique=True)
followers_30d = models.BigIntegerField(blank=True)
I have written the following serializers:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'followers']
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = UserSerializer(source='user.username')
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
I am trying to return the username from the User model, but whatever I try to get it, the best I can do is get the hyperlinked related field from user, but not the actual "username" attribute. How would you return this?
You can simply create a field and return it:
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = SerializerMethodField()
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
def get_username(self, obj):
return obj.user_id.username

Django: error using get_or_create and unique_together

I'm creating an API which list and save transactions. My Transactions Model has a FK to a Category model. My goal when creating a Transaction is to also create the Category if the category is new.
The first time I create the transaction it successfully creates the new category and transaction. The next time I attempt the create a transaction with the existing category, I get an error that the category already exists. In my Transaction serializer I added a create method that should be using get_or_create for the category. However I'm still getting an error on my unique fields. My expectation is that it would be returning the existing Category.
It seems like it's throwing the error before it gets to the create method in the Transaction serializer before it has a chance to use get_or_create.
Models:
class Category(models.Model):
name = models.CharField(max_length=128, null=False)
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
created_time = models.DateTimeField(auto_now_add=True)
modified_time = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('name', 'owner')
class Transaction(models.Model):
date = models.DateField()
payee = models.CharField(max_length=256)
category = models.ForeignKey(Category,
related_name='category',
on_delete=models.CASCADE)
amount = MoneyField(max_digits=19,
decimal_places=2,
default_currency='USD')
created_time = models.DateTimeField(db_index=True,
auto_now_add=True)
modified_time = models.DateTimeField(auto_now=True)
Serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'owner', 'created_time', 'modified_time')
class TransactionSerializer(serializers.ModelSerializer):
balance = serializers.DecimalField(
decimal_places=2, max_digits=19, read_only=True)
category = CategorySerializer(many=False, read_only=False)
class Meta:
model = Transaction
fields = ('id', 'date', 'payee', 'category',
'amount', 'balance', 'created_time', 'modified_time',
'is_cleared', 'paid_or_deposited')
def create(self, validated_data):
category_data = validated_data.pop('category')
category, created = Category.objects.get_or_create(**category_data)
transaction = Transaction.objects.create(category=category,
**validated_data)
return transaction
POST:
{
"date": "2018-12-19",
"payee": "Test",
"category": {"owner": 1, "name": "TEST"},
"amount": "-134"
}
Error:
{
"category": {
"non_field_errors": [
"The fields name, owner must make a unique set."
]
}
}
You're right about not reaching your create() method.
This happens because ModelSerializer by default creates validators based on your model Meta.unique_together value: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Simplest way to disable this type of validators is to override get_unique_together_validators for your serializer:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'owner', 'created_time', 'modified_time')
def get_unique_together_validators(self):
return []
Another solution, which is cleaner is to override Meta.validations of your CategorySerializer*:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'owner', 'created_time', 'modified_time')
validators = []
* be aware that this will disable serializer validators unique_for_date, unique_for_month and unique_for_year that come from model

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

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.

How to use a model field for multiple models in Django Rest Framework

I have a model BstUserActionLog with a foreign key to Django model User. I have another model for user profile information, BstUserProfile. When I do serialize BstUserActionLog with ModelSerializer I do have Django User info serialized as it is supposed to be. But I also need to add BstUserProfile serialized using the same user_id used for User model.
How can I serialize BstUserActionLog with model User and BstUserProfile are both serialized?
From my models.py:
class BstUserActionLog(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
bst_action_type = models.ForeignKey(BstActionType)
action_date = models.DateTimeField(auto_now_add=True)
bst_book = models.ForeignKey(BstBook)
new_value_id = models.IntegerField(blank=True, null=True)
old_value_id = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'bst_user_action_log'
class BstUserProfile(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, unique=True)
website = models.CharField(max_length=200)
picture = models.CharField(max_length=100)
is_avatar_uploaded = models.BooleanField(default=False)
is_cover_uploaded = models.BooleanField(default=False)
class Meta:
managed = False
db_table = 'bst_user_profile'
app_label = 'bst'
From my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','username',)
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3
The key to my solution is SerializerMethodField. With this a new field can be added which is calculated with a method. This method signature contains the object to be serialized. After that a regular method serializer is used to return the serialized object.
From my serializers.py
class BstUserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = BstUserProfile
fields = ('is_avatar_uploaded', 'is_cover_uploaded')
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
user_profile = serializers.SerializerMethodField()
def get_user_profile(self, obj):
try:
user_profile = BstUserProfile.objects.get(user_id=obj.user_id)
return BstUserProfileSerializer(user_profile).data
except Exception as e:
return {}
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'user_profile', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3