AttributeError: 'ManyToOneRel' object has no attribute 'attname'

236 Views Asked by At

I changed a OnetoOnefield in a PostImage model to a ForeignKey and getting the said error.

Here is what is changed :

class PostImage(models.Model):
# post = models.OneToOneField(Post, on_delete=models.CASCADE, related_name='image', null=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='image', null=True)
image = ProcessedImageField(verbose_name=_('image'), storage=post_image_storage,
                            upload_to=upload_to_post_image_directory,
                            width_field='width',
                            height_field='height',
                            blank=False, null=True, format='JPEG', options={'quality': 80},

The logs of docker-compose :

my-api        | Traceback (most recent call last):
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
my-api        |     response = get_response(request)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
my-api        |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
my-api        |     return view_func(*args, **kwargs)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
my-api        |     return self.dispatch(request, *args, **kwargs)
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
my-api        |     response = self.handle_exception(exc)
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
my-api        |     self.raise_uncaught_exception(exc)
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
my-api        |     raise exc
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
my-api        |     response = handler(request, *args, **kwargs)
my-api        |   File "/opt/my-api-core/my_posts/views/posts/views.py", line 55, in get
my-api        |     return self.get_posts_for_authenticated_user(request)
my-api        |   File "/opt/my-api-core/my_posts/views/posts/views.py", line 92, in get_posts_for_authenticated_user
my-api        |     post_serializer_data = AuthenticatedUserPostSerializer(posts, many=True, context={"request": request}).data
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/serializers.py", line 768, in data
my-api        |     ret = super().data
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/serializers.py", line 253, in data
my-api        |     self._data = self.to_representation(self.instance)
my-api        |   File "/usr/local/lib/python3.8/site-packages/rest_framework/serializers.py", line 686, in to_representation
my-api        |     return [
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 280, in __iter__
my-api        |     self._fetch_all()
my-api        |   File "/usr/local/lib/python3.8/site-packages/cacheops/query.py", line 273, in _fetch_all
my-api        |     return self._no_monkey._fetch_all(self)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
my-api        |     self._result_cache = list(self._iterable_class(self))
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
my-api        |     results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1156, in execute_sql
my-api        |     sql, params = self.as_sql()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 507, in as_sql
my-api        |     extra_select, order_by, group_by = self.pre_sql_setup()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 55, in pre_sql_setup
my-api        |     self.setup_query()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 46, in setup_query
my-api        |     self.select, self.klass_info, self.annotation_col_map = self.get_select()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 227, in get_select
my-api        |     cols = self.get_default_columns()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 671, in get_default_columns
my-api        |     only_load = self.deferred_to_columns()
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1096, in deferred_to_columns
my-api        |     self.query.deferred_to_data(columns, self.query.get_loaded_field_names_cb)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 754, in deferred_to_data
my-api        |     callback(target, model, values)
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 2179, in get_loaded_field_names_cb
my-api        |     target[model] = {f.attname for f in fields}
my-api        |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/query.py", line 2179, in <setcomp>
my-api        |     target[model] = {f.attname for f in fields}
my-api        | AttributeError: 'ManyToOneRel' object has no attribute 'attname'
django.server ERROR    "GET /api/posts/?count=10& HTTP/1.1" 500 195670

Based on the above logs, the function causing the error :

def get_posts_for_authenticated_user(self, request):
        query_params = request.query_params.dict()
        normalize_list_value_in_request_data('circle_id', query_params)
        normalize_list_value_in_request_data('list_id', query_params)

        serializer = GetPostsSerializer(data=query_params)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        circles_ids = data.get('circle_id')
        lists_ids = data.get('list_id')
        max_id = data.get('max_id')
        min_id = data.get('min_id')
        count = data.get('count', 10)
        username = data.get('username')

        user = request.user

        if username:
            if username == user.username:
                posts = user.get_posts(max_id=max_id)
            else:
                posts = user.get_posts_for_user_with_username(username, max_id=max_id, min_id=min_id)
        else:
            posts = user.get_timeline_posts(
                circles_ids=circles_ids,
                lists_ids=lists_ids,
                max_id=max_id,
                min_id=min_id,
                count=count
            )

        posts = posts.order_by('-id')[:count]

        post_serializer_data = AuthenticatedUserPostSerializer(posts, many=True, context={"request": request}).data

        return Response(post_serializer_data, status=status.HTTP_200_OK)

What I have tried :

1.Changed the order_by() from

posts = posts.order_by('-id')[:count]

to

posts = posts.order_by('-created')[:count]

since count was mentioned in the docker logs. But still getting the said error.

2.I even tried clearing the database using the flush command. Recreated the docker containers. Error persists.

1

There are 1 best solutions below

8
VonC On BEST ANSWER

AttributeError: 'ManyToOneRel' object has no attribute 'attname': that should mean that Django tries to access a field attribute that does not exist on a related model.
That error often occurs after changing relationships in models, as you did by changing a OneToOneField to a ForeignKey. (a bit as in "How to Update Django Model Relationship only with Foreign Key Value", even though the focus is on how to update a foreign key relationship in a Django model by directly using the primary key of the related model, rather than fetching the entire related model instance).

When you switch from a OneToOneField to a ForeignKey, Django treats this relationship differently. The ManyToOneRel object is an internal Django construct representing the reverse relation from Post to PostImage.

Since the error is occurring during serialization, make sure AuthenticatedUserPostSerializer correctly handles the post field. If the serializer attempts to access a non-existent attribute on the post field, it could cause this error. Look for any part of the serializer that might be treating post.image as a single instance rather than a set of instances.

You have related_name='image' in your ForeignKey. That means that, on the Post model, Django expects post.image to return multiple PostImage instances. If anywhere in your code (or serializers) you are treating post.image as a single instance (as it was with a OneToOneField), it would cause an error. You might need to adjust this logic or change the related_name to something like 'images' to reflect the multiple instances.

The related_name='image' in your ForeignKey should now reflect that post.image will return a queryset of PostImage instances, not a single instance. Consider changing it to 'images' to better represent the relationship.

class PostImage(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='images', null=True)
    ...

After changing field types, make sure your database schema is updated correctly with Django migrations. Sometimes, inconsistencies between the model and the database can lead to unexpected errors.

Finally, add debugging information in the get_posts_for_authenticated_user method to inspect the posts query.

def get_posts_for_authenticated_user(self, request):
    ...
    posts = posts.order_by('-id')[:count]

    # Debugging: Print or log the query
    print("DEBUG: posts query", posts.query)

    post_serializer_data = AuthenticatedUserPostSerializer(posts, many=True, context={"request": request}).data

    return Response(post_serializer_data, status=status.HTTP_200_OK)

These debugging statements will help you understand what is happening at the database query level. Keep an eye on your console or logs to see the output.


My objective is to allow model PostImage to allow for multiple images upload and link it to a Post. e.g. A single blog post having multiple images.

Correct me if I am wrong, changing the related_name to 'images' with a ForeignKey to Post will allow for multiple instances of PostImages?
Because I do have code logic with the field 'image' inside post as you rightly mentioned. There is also a image = ProcessedImageField right below where I define the ForeignKey.

As a note just before debugging, I just changed 'image' to 'images' as you mentioned and ran a GET request after running migrations. I now get : FieldDoesNotExist: Post has no field named 'image'

The related_name attribute in a ForeignKey relationship specifies the name to use for the reverse relation from the related model back to your model. In your case, by setting related_name='images' in the PostImage model, you are telling Django to use images to access related PostImage instances from a Post instance.
So, after this change, on any Post object, you can access its related PostImage objects using post.images.all().

Yes, changing to a ForeignKey and using related_name='images' will indeed allow multiple instances of PostImage to be associated with a single Post. That is the correct approach for your use case where a blog post may have multiple images.

The error FieldDoesNotExist: Post has no field named 'image' occurs because, in your views or serializers, you are likely still accessing image directly on a Post object. After the change in your model, you should update your code to use images.
Wherever you were accessing post.image, you should now use post.images.all() to get a queryset of all related PostImage objects.

Update any views, serializers, or other parts of your code that previously relied on post.image. For serialization, if you need to include images in your Post serializer, you might use a nested serializer for PostImage.

Your serializer could be:

class PostImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = PostImage
        fields = ['image', ...]  # Include other fields as needed

class PostSerializer(serializers.ModelSerializer):
    images = PostImageSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ['title', 'content', 'images', ...]  # Other Post fields

Run migrations again after any model change:

python manage.py makemigrations
python manage.py migrate