'GenericRelatedObjectManager' object has no attribute 'lang'

Viewed 101

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.

0 Answers
Related