I am receiving the error 'GenericRelatedObjectManager' object has no attribute 'lang' when I making a post request.
In this query and relationship I have two models Translation and Tags.
class Translation(models.Model):
"""
Model that stores all translations
"""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
object_id = models.CharField(max_length=50, null=True, blank=True)
content_object = GenericForeignKey()
lang = models.CharField(max_length=5)
field = models.CharField(max_length=255, null=True)
translation = models.TextField(blank=True, null=True)
def __unicode__(self):
return u'%s : %s : %s' % (self.content_object, self.lang, self.field)
class Tags(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
DISEASE = 0
TYPE = [(DISEASE, 'disease')]
type = models.PositiveSmallIntegerField(choices=TYPE)
name = GenericRelation(Translation)
description = GenericRelation(Translation)
I am creating a serializer mixin to send the same data to different serializer (not only the Tag described below):
SerializerTranslationField and TagSerialzer:
class SerializerTranslationField(Field):
def __init__(self, method_name=None, model_name=None, translation_field=None, **kwargs):
self.method_name = method_name
self.translation_field = translation_field
self.model_name = model_name
kwargs['source'] = '*'
kwargs['read_only'] = True
print("KWARGS ----->", kwargs)
super().__init__(**kwargs)
def to_representation(self, value):
content_type = ContentType.objects.get_for_model(self.model_name)
queryset = Translation.objects.filter(content_type=content_type, object_id=value, field=self.translation_field)
result = TranslationSerializer(queryset, required=False, many=True).data
final_result = {k: v for d in result for k, v in d.items()}
print("FINAL RESULT --->", final_result)
return final_result
class TagsSerializer(NestedUpdateMixin,serializers.ModelSerializer):
class Meta:
model = Tags
fields = ['id', 'type', 'name','description']
def __init__(self, *args, **kwargs):
fields = [field for field in get_generic_relation_fields(self)]
# self.__class__ represent the actual class receiving inheritance.
super(self.__class__, self).__init__(*args, **kwargs)
try:
for field in fields:
if self.context['request'].method in ['POST', 'HEAD']:
self.fields[field] = TranslationSerializer(required=False, many=False)
elif self.context['request'].method in ['GET', 'PUT', 'PATCH', 'DELETE']:
self.fields[field] = SerializerTranslationField(
translation_field=field,model_name=self.Meta.model)
except KeyError:
pass
def get_generic_relation_fields(self):
"""
This function returns all the GenericRelation
fields needed to return the values that are
related. ex to Translation or other polymorphic
model.
"""
fields = [f.attname for f in self.Meta.model._meta.get_fields()]
generic_relation_fields = []
for field in fields:
get_type = self.Meta.model._meta.get_field(field)
field_type = get_type.__class__.__name__
if field_type == "GenericRelation":
generic_relation_fields.append(field)
return generic_relation_fields
def translation_create_field(self, object, *values):
"""
object -> gets the object already created:
ex: tags = Tags.objects.create(**validated_data)
>> object == tags
values -> are the field names from validated_data.
"""
print("PAYLOAD In HELPER -->", values)
for item in values:
print("ITEM -->", item)
model = self.Meta.model
content_type = ContentType.objects.get_for_model(model)
# get first key, value pairs from payload
translation_values = list(item.values())[0]
print("TRANSLATION VALUES -->",translation_values)
print("VALUES -->",translation_values)
# for translation in translation_values:
# print("TRANSLATIONS -->",translation)
# loop through translation values
# e.g:[{'en': 'hello', 'es': 'ola'...}]
# translation e.g: {'en': 'hello', ...}
for key, value in translation_values.items():
print("VALUES -->",key, value)
# Iter
payload = {}
payload['field'] = next(iter(item))
payload['lang'] = key
payload['translation'] = value
payload['object_id'] = str(object.id)
payload['content_type'] = content_type
print("PAYLOAD --->", payload)
Translation.objects.create(**payload)
def create(self, validated_data):
# Get all fields that have generic relation with Translation.
fields = [field for field in get_generic_relation_fields(self)]
# Get lists from validated_data.
values_from_validated_data = {field : validated_data.pop(field) for field in fields}
# ex:object == Tags.objects.create(**valdated_data)
object = self.Meta.model.objects.create(**validated_data)
for key, value in values_from_validated_data.items():
payload = {key:value}
# should use method to iterate each dictionary.
translation_create_field(self, object, payload)
return object
def update(self, instance, validated_data):
# display all fields in an array that are GenericRelations.
fields = [field for field in get_generic_relation_fields(self)]
# Get dictionaries from validated_data that are GenericRelations.
values_from_validated_data = {field : self.context['request'].data.get(field) for field in fields}
# Treat the dictionaries removed from the object reasigning into
# the object again by its natural relation.
content_type = ContentType.objects.get_for_model(self.Meta.model)
translation = {}
for key, value in values_from_validated_data.items():
# key will be used as "field_name"
# value will be used as a dictionary -
# with list of translation treated below.
translation['field'] = key
args_from_db = Translation.objects.filter(
object_id=instance,
field=translation['field']
)
args_from_db.delete()
for k, v in value.items():
# k will be used as "lang"
# v will be used as "translation"
translation['lang'] = k
translation['translation'] = v
translation['object_id'] = str(instance.id)
translation['field'] = key
translation['content_type'] = content_type
Translation.objects.update_or_create(**translation)
## ==================================================================
# The code below treats the object itself without the nested values-
# from GenericRelations fields only.
serializers.raise_errors_on_nested_writes('update', self, validated_data)
info = model_meta.get_field_info(instance)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
m2m_fields = []
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value))
else:
setattr(instance, attr, value)
instance.save()
# Note that many-to-many fields are set after updating instance.
# Setting m2m fields triggers signals which could potentially change
# updated instance and we do not want it to collide with .update()
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
return instance
TranslationSerialzer:
class TranslationSerializer(serializers.ModelSerializer):
class Meta:
model = Translation
fields = [
"id",
"object_id",
"lang",
"translation",
"field",
"content_type"
]
def to_representation(self, value):
"""
Display translatios as:
{"en_ire":"All lured!"}
"""
list_of_dicts = {value.lang : value.translation}
return list_of_dicts
def to_internal_value(self, data):
return data
The endpoint on GET AND PUT are working fine as follow:
{
"id": "b705fc65-6710-4218-9e38-9216f2f33197",
"type": 0,
"name": {
"en": "What a good day!",
"pt": "Que dia maravilhoso!"
},
"description": {
"pt_br": "Bom dia",
"en": "Good morning",
"es": "Buenos dias"
}
},
{
"id": "c9d4443f-82f8-40f2-9f29-b46f320b12eb",
"type": 0,
"name": {
"en": "hello",
"es": "ola",
"pt": "oi"
},
"description": {
"pt_br": "Bom dia",
"en": "Good morning",
"es": "Buenos dias"
}
},
. . .
However, when I do the post I receive the error mentioned in the title BUT the if I refresh the page the post appears as normally.
Nevertheless, I can only post if many=False because I cannot pass data to many=True since it does not accept a dictionary with other objects, just a list of object for both name and description.
Èrror I get when I try to pass on many=True:
{
"name": {
"non_field_errors": [
"Expected a list of items but got type \"dict\"."
]
},
"description": {
"non_field_errors": [
"Expected a list of items but got type \"dict\"."
]
}
}
My payload for post:
{
"type": 0,
"name": {
"en": "hello",
"es": "ola",
"pt": "oi"
},
"description":{
"pt_br": "Bom dia",
"en": "Good morning",
"es": "Buenos dias"
}
}
Input expected for many=True on TranslationSerializer:
{
"type": null,
"name": [],
"description": []
}
Input expected for many=False on TranslationSerializer, Where I can make post and receive the 'GenericRelatedObjectManager':
{
"type": null,
"name": {
"object_id": "",
"lang": "",
"translation": "",
"field": "",
"content_type": null
},
"description": {
"object_id": "",
"lang": "",
"translation": "",
"field": "",
"content_type": null
}
}
I think I can solve this issue if I can change from [] on many=True to {} or disable the detail that happens right after the post.
Since it is a unknown area for me, it might be happening because I am creating many objects dynamically and when it is returning on detail it might be conflicting with the to_representation of TranslationSerializer or conflicting with the expected data from the input. In addition, I could not pass this payload using many=True with {} to list objects.