Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: 'ListSerializer' object has no attribute 'Meta' #404

Closed
treussart opened this issue May 23, 2021 · 6 comments
Closed

AttributeError: 'ListSerializer' object has no attribute 'Meta' #404

treussart opened this issue May 23, 2021 · 6 comments
Labels
bug Something isn't working fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@treussart
Copy link

Describe the bug
I get this error:

Traceback (most recent call last):
  File "/Users/mtreussart/git/tt/scripts/../src/manage.py", line 21, in <module>
    main()
  File "/Users/mtreussart/git/tt/scripts/../src/manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/management/commands/spectacular.py", line 56, in handle
    schema = generator.get_schema(request=None, public=True)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/generators.py", line 243, in get_schema
    paths=self.parse(request, public),
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/generators.py", line 217, in parse
    operation = view.schema.get_operation(
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 84, in get_operation
    operation['responses'] = self._get_response_bodies()
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 997, in _get_response_bodies
    return {'200': self._get_response_for_code(response_serializers, '200')}
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1042, in _get_response_for_code
    component = self.resolve_serializer(serializer, 'response')
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 1184, in resolve_serializer
    component.schema = self._map_serializer(serializer, direction)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 687, in _map_serializer
    schema = self._map_basic_serializer(serializer, direction)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 756, in _map_basic_serializer
    schema = self._map_serializer_field(field, direction)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 483, in _map_serializer_field
    schema = self._map_serializer_field(field.child, direction, collect_meta)
  File "/Users/mtreussart/.local/share/virtualenvs/back-JrD3H-kk/lib/python3.9/site-packages/drf_spectacular/openapi.py", line 653, in _map_serializer_field
    target = follow_field_source(field.parent.Meta.model, field.source_attrs)
AttributeError: 'ListSerializer' object has no attribute 'Meta'

To Reproduce
This is the problematic field:

groups = serializers.ListSerializer(
    source="all_groups", child=UserGroupField(), required=False, read_only=True
)

For fix the problem I patched openapi.py line 649:

        if isinstance(field, serializers.ReadOnlyField):
            # direct source from the serializer
            assert field.source_attrs, f'ReadOnlyField "{field}" needs a proper source'
            if hasattr(field.parent, "Meta"):
                target = follow_field_source(field.parent.Meta.model, field.source_attrs)
            else:
                if isinstance(field.parent, serializers.ListSerializer):
                     return append_meta(build_array_type(build_basic_type(OpenApiTypes.OBJECT)), meta)
            if callable(target):
                schema = self._map_response_type_hint(target)
            elif isinstance(target, models.Field):
                schema = self._map_model_field(target, direction)
            else:
                assert False, f'ReadOnlyField target "{field}" must be property or model field'
            return append_meta(schema, meta)

Expected behavior
No error. and understand my read-only serializer.

@tfranzel
Copy link
Owner

hi @treussart, nice catch! would you like to do a separate PR for that "scim commit"? i'm not fully sold on the args PR but this i would like to include. if you don't care, i can cherry-pick the commit. i can add a regression test later on.

@tfranzel
Copy link
Owner

tfranzel commented May 31, 2021

Hi @treussart, sry to keep you waiting. while constructing a test something simply didn't sit right with me. i'm sure your fix did it for you, but i believe its not entirely correct. also this wraps with an array one time too many.

i think the core issue is that this piece of code did not account for ModelSerializer being 2 steps removed instead of 1. you simply circumvented that by returning a generic response. i think we can do better.

please confirm these assumptions i made. it was not clear from the example:

  • UserGroupField is a subclass of serializers.ReadOnlyField
  • group is nested inside a ModelSerializer
  • all_groups is a model property?

could you try the following snippet instead? i think this is likely the correct fix.

            ...
            assert field.source_attrs, f'ReadOnlyField "{field}" needs a proper source'

            if is_list_serializer(field.parent):
                field = field.parent

            target = follow_field_source(field.parent.Meta.model, field.source_attrs)

            if callable(target):
            ...

EDIT: i changed the snipped. my previous proposal had one remaining issue. it's even simpler now.

@tfranzel tfranzel added bug Something isn't working fix confirmation pending issue has been fixed and confirmation from issue reporter is pending labels May 31, 2021
@treussart
Copy link
Author

Hi, I test your code and there is a new warning: Warning #0: UserViewSet: UserSerializer: unable to resolve type hint for function "all_groups". Consider using a type hint or @extend_schema_field. Defaulting to string.

    GroupRelation = namedtuple("GroupRelation", "group, direct")

    @property
    def all_groups(self) -> Iterable[GroupRelation]:
        """
        Yields all the groups a user is into, and whether the relationship is direct or not.
        """
        seen = set()
        for group in self.groups.all():
            seen.add(group)
            yield GroupRelation(group=group, direct=True)

@tfranzel
Copy link
Owner

at least we are making progress 😄 i added some hint detection.

@treussart
Copy link
Author

Hi, thanks for the correction, I rebased my branch on master and it's good for me.

@tfranzel
Copy link
Owner

tfranzel commented Jun 1, 2021

awesome! i'll close this then.

@tfranzel tfranzel closed this as completed Jun 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fix confirmation pending issue has been fixed and confirmation from issue reporter is pending
Projects
None yet
Development

No branches or pull requests

2 participants