evenicoulddoit / django-rest-framework-serializer-extensions

Extensions to help DRY up Django Rest Framework serializers
Other
247 stars 23 forks source link

GenericRelation occurs an error. #21

Closed legshort closed 2 years ago

legshort commented 6 years ago

I guess generic relation is not supported yet.

  File "/Users/xxx/.pyenv/versions/3.6.2/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'GenericRelatedObjectManager' is not JSON serializable
evenicoulddoit commented 6 years ago

Hi @legshort - thanks for this. Would you be able to provide a short reproducible set of what you did to get this error? From the looks of it you're using Generic Foreign Keys?

legshort commented 6 years ago

Hi @evenicoulddoit - yeah that's right. My code is just like a django example This is how my models and serializers look like.

class Photo(Model):
    file = models.ImageField(upload_to=photo_directory)
    content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Post(BaseModel):
    photos = GenericRelation(Photo)

class PostSerializer(SerializerExtensionsMixin, ModelSerializer):
    id = HashIdField(model=models.Post, read_only=True)

    class Meta:
        model = models.Post
        fields = ('id', 'created', 'contents', 'like_count', 'comment_count', 'photos',)
        read_only_fields = ('like_count', 'comment_count',)

This is the error.

Error
Traceback (most recent call last):
  File "/Users/user/repo/drf-server/drf/posts/tests/test_view.py", line 64, in test_should_retrieve_post
    response = self.client.get('/api/posts/{post_id}'.format(post_id=self.encode_id(Post, self.post.id)))
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/test.py", line 291, in get
    response = super(APIClient, self).get(path, data=data, **extra)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/test.py", line 208, in get
    return self.generic('GET', path, **r)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/test.py", line 237, in generic
    method, path, data, content_type, secure, **extra)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/test/client.py", line 404, in generic
    return self.request(**r)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/test.py", line 288, in request
    return super(APIClient, self).request(**kwargs)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/test.py", line 240, in request
    request = super(APIRequestFactory, self).request(**kwargs)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/test/client.py", line 485, in request
    raise exc_value
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/core/handlers/base.py", line 158, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/core/handlers/base.py", line 156, in _get_response
    response = response.render()
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/django/template/response.py", line 106, in render
    self.content = self.rendered_content
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/renderers.py", line 105, in render
    allow_nan=not self.strict, separators=separators
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/utils/json.py", line 28, in dumps
    return json.dumps(*args, **kwargs)
  File "/Users/user/.pyenv/versions/3.6.2/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/Users/user/.pyenv/versions/3.6.2/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/user/.pyenv/versions/3.6.2/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/Users/user/.pyenv/versions/drf-server-3.6.2/lib/python3.6/site-packages/rest_framework/utils/encoders.py", line 68, in default
    return super(JSONEncoder, self).default(obj)
  File "/Users/user/.pyenv/versions/3.6.2/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'GenericRelatedObjectManager' is not JSON serializable

Thanks for the raid response!

evenicoulddoit commented 6 years ago

Thanks for the confirmation. I can't see anywhere in the stack-trace which suggests an error in serializer extensions? Were you trying to expand something at the time? I've not serialized a GenericRelation before, but you'll need to provide a serializer explicitly (otherwise, what should it print out), and you also may need to change the source="photos.all"

legshort commented 6 years ago

I wasn't trying to add expandable_fields, I was preparing it but I think the above code should work fine because it has nothing configuration yet. However, I think I made some mistake and below code works like a charm now!!

class PostSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
    id = HashIdField(model=models.Post, read_only=True)

    class Meta:
        model = models.Post
        fields = ('id', 'created', 'contents', 'like_count', 'comment_count',)
        read_only_fields = ('like_count', 'comment_count',)
        expandable_fields = {
            'photos': {
                'serializer': PhotoSerializer,
                'source': 'photos.all',
                'many': True,
            },
        }

I almost switched to drf-tweaks but I like your library much more because it's simple and has more features.

In addition, I think it would be more helpful if source.all and ExternalIdViewMixin are documented otherwise users have to look for them from source code just like me. I will try to help to make the docs as well.

Thanks for the great library, it makes DRF so much easier, looking forward even better and nicer!

legshort commented 6 years ago

Interesting problem pops up with above code, validated_data has photos automatically.

POST: /posts?expand=photos This is my form data looks like {'contents': 'xWczeXyUVKhS'} and this is validated_data looks like {'contents': 'xWczeXyUVKhS', 'photos': {'all': []}.

It seems like read_only=True is not working as expected.

legshort commented 6 years ago

I found the default value for read_only is False so I tested with read_only=True but still validated_data has photos.

legshort commented 6 years ago

When it comes to _writable_fields(), ListSerializer of PhotoSerializer has read_only=False. I guess read_only=True from expandable_fiedls hasn't effected to ListSerializer

evenicoulddoit commented 6 years ago

Hey, thanks for these - a couple of things:

legshort commented 6 years ago
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone

class Photo(models.Model):

    user = models.ForeignKey('users.User', on_delete=models.PROTECT, related_name='photos')

    content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models

class Post(models.Model):
    contents = models.TextField()
    like_count = models.PositiveIntegerField(default=0)
    comment_count = models.PositiveIntegerField(default=0)

    user = models.ForeignKey('users.User', on_delete=models.PROTECT, related_name='posts')
    photos = GenericRelation('photos.Photo')
from rest_framework import serializers
from rest_framework_serializer_extensions.fields import HashIdField
from rest_framework_serializer_extensions.serializers import SerializerExtensionsMixin

from photos.serializers import PhotoSerializer
from posts import models

class PostSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
    id = HashIdField(model=models.Post, read_only=True)

    class Meta:
        model = models.Post
        fields = ('id', 'contents', 'like_count', 'comment_count', )
        read_only_fields = ('like_count', 'comment_count',)
        expandable_fields = {
            'photos': {
                'serializer': PhotoSerializer,
                'source': 'photos.all',
                'many': True,
                'read_only': True
            },
        }

    def create(self, validated_data):
        print(validated_data)

        return super().create(validated_data)
from rest_framework import mixins, viewsets
from rest_framework_serializer_extensions.views import SerializerExtensionsAPIViewMixin, ExternalIdViewMixin

from posts.models import Post
from posts.serializers import PostSerializer

class PostViewSet(ExternalIdViewMixin, SerializerExtensionsAPIViewMixin,
                  mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)
evenicoulddoit commented 5 years ago

@legshort did you ever find a solution to this?

legshort commented 5 years ago

it's been a while, I don't quite remember how I overcome this issue but I will look into it and let you know! thanks for paying attention.

evenicoulddoit commented 5 years ago

Great, thanks. Sorry I've been out of the loop for so long

evenicoulddoit commented 4 years ago

I'm going to close this for now, as I've nothing more to go off.

nicbou commented 3 years ago

...but the problem is not solved

evenicoulddoit commented 3 years ago

Hi @nicbou - in which case I'll reopen the issue, but I can't give you any concrete guidance as to when I'll get time to look into this further at the minute. Did you do any debugging yourself?

nicbou commented 3 years ago

Hello @evenicoulddoit , I realise now that I'm commenting in a different repository, so my answer probably won't make any sense.

For anyone trying to figure out how to serialize a GenericRelation and getting errors, this is how I got it to work: https://github.com/nicbou/timeline/blob/master/backend/source/archive/serializers.py#L24

evenicoulddoit commented 3 years ago

@nicbou thanks for this - I'm sure it'll be helpful to help me diagnose the issue :)

evenicoulddoit commented 2 years ago

@nicbou - just had a chance to parse this. The link you post to doesn't even use the serializer extensions? So, I'm not sure I understand how this relates in any way specifically to this package?