Code Monkey home page Code Monkey logo

Comments (12)

fffffrolov avatar fffffrolov commented on August 11, 2024 4

I have huge hack for this, but you can use it if need. (It's also cals select_related for expanded fields)

import logging
from rest_framework import (
    generics,
    response,
    status,
    mixins,
    viewsets,
)

logger = logging.getLogger(__name__)


class FlexFieldsMixin:
    """
    This View is used with drf-flex-fields package.
    Automatically select fields for select_related
    for queryset from currently used serializer
    and all serializers in expandable_fields dict.
    Developer must define fields to prefetch by himself in `get_queryset` method.
    """
    def get_expanded_related_fields(self):
        root_serializer = self.get_serializer_class()
        select_related_fields = set()

        for root_field in self.request.query_params.get('expand', '').split(','):
            if not root_field:
                continue
            serializer = root_serializer
            fields_to_select = []
            for field in root_field.split('.'):
                if not hasattr(serializer, 'expandable_fields'):
                    continue
                try:
                    many = serializer.expandable_fields[field][1].get('many')
                except (KeyError, IndexError) as e:
                    logger.warning(e)
                    # Request contains wrong expand key that is not present in expandable_fields
                    continue
                if many:
                    continue
                expandable_serializer = serializer.expandable_fields[field][0]
                if type(expandable_serializer) == str:
                    expandable_serializer = serializer._import_serializer_class(expandable_serializer)
                serializer = expandable_serializer
                fields_to_select.append(field)
                field = '__'.join(fields_to_select)
                select_related_fields.add(field)
        return list(select_related_fields)

    def get_queryset(self):
        queryset = super().get_queryset()
        expanded_fields = self.get_expanded_related_fields()
        if expanded_fields:
            queryset.select_related(*expanded_fields)
        return queryset

    @staticmethod
    def is_field_expanded(request, key):
        """
        Examines request object to return boolean of whether
        passed field is expanded.

        Adds all fields that are included in nested argument
        i.e. `?expand=organizer.user.avatar` means that
        organiser, organizer.user and organizer.user.avatar are expanded

        """
        expanded_fields = set()
        for field in request.query_params.get('expand', '').split(','):
            parts = field.split('.')
            for i in range(1, len(parts) + 1):
                expanded_fields.add('.'.join(parts[:i]))

        return '~all' in expanded_fields or key in expanded_fields


class FlexCreateModelMixin(FlexFieldsMixin, mixins.CreateModelMixin):
    def get_object_flex(self, data):
        """
        Just like default get_object but without url lookup
        Additional db request is needed in order to select related objects
        that were requested by `expand` GET parameter.
        """
        lookup_field = self.lookup_field
        if lookup_field == 'pk':
            lookup_field = 'id'

        queryset = self.filter_queryset(self.get_queryset())
        filter_kwargs = {lookup_field: data[lookup_field]}
        obj = generics.get_object_or_404(queryset, **filter_kwargs)
        self.check_object_permissions(self.request, obj)
        return obj

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        if request.query_params.get('expand'):
            serializer = self.get_serializer_class()(
                instance=self.get_object_flex(
                    serializer.data
                ),
                context={
                    'request': request,
                },
                expand=request.query_params['expand'].split(','),
            )

        return response.Response(
            serializer.data,
            status=status.HTTP_201_CREATED,
            headers=headers,
        )


class FlexUpdateModelMixin(FlexFieldsMixin, mixins.UpdateModelMixin):
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()

        context = {}
        expand = request.query_params.get('expand', '').split(',')
        if expand:
            context['request'] = request

        serializer = self.get_serializer(
            instance,
            data=request.data,
            partial=partial,
            context=context,
        )
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        serializer = self.get_serializer(
            instance,
            context=context,
            expand=expand,
        )
        return response.Response(serializer.data)


class FlexModelViewSet(FlexCreateModelMixin,
                       mixins.RetrieveModelMixin,
                       FlexUpdateModelMixin,
                       mixins.DestroyModelMixin,
                       mixins.ListModelMixin,
                       viewsets.GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

from drf-flex-fields.

Crocmagnon avatar Crocmagnon commented on August 11, 2024 1

Hm, that doesn't sound very DRY, we would need to change all the views to accept this behavior.
Couldn't we use a combination of the to_representation and to_internal_value methods to achieve the desired behavior without requiring changes to existing code ?

See : https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior

Basically the fields and expand parameters would only be used in to_representation, not every time a serializer is instanciated

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

I guess I prevented this for purist reasons - blurs things a little when you are sending query info in a request to mutate server state.

I'll consider a little more, as I see the usefulness in simplifying the client side and reducing requests. Main concern is that clients could cause unintended, additional queries.

from drf-flex-fields.

Crocmagnon avatar Crocmagnon commented on August 11, 2024

I also need this, totally open to discuss the need if you want. But basically, this would allow us to have some related data directly in the POST response rather than having to GET the data by at least one other request.

from drf-flex-fields.

Crocmagnon avatar Crocmagnon commented on August 11, 2024

It would also allow us to omit some useless data from the POST response

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

Yeah, I think we should do this. It's implementation is straightforward (and I recognize that even if I've become a REST purist, there are many ways to build a successful project :).

I can't think of any unexpected side effects? So thinking this can just be the default, no need for an option to enable.

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

So I dug into this deeper, and there is a complication: When the serializer is initialized for the purpose of validating and creating the model instance, you don't want the fields expanded until AFTER the model is created. But this will happen immediately, which replaces your serializer fields that are read/write with an expanded serializer that is read-only.

Essentially this happens:

  1. You instantiate a serializer with the incoming request data
  2. The serializer sees a query param asking a PrimaryKeyRelatedField to be expanded to a nested serializer
  3. The view calls create(), which calls your serializers validation
  4. An error is thrown because the request has passed an primary key, but that field's corresponding validation fails because it's now an expanded, read-only serializer intended to output a nested object

But here's a workaround which requires you to write your own "create" method on the view:

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()

        response_serializer = MySerializer(
           instance, expand=[self.request.query_params.split(',')]
        )

        return Response(response_serializer.data, status=201)

from drf-flex-fields.

bashu avatar bashu commented on August 11, 2024

@kotepillar I fixed it by changing this line: https://github.com/rsinger86/drf-flex-fields/blob/master/rest_flex_fields/serializers.py#L171

to:

return self.context["request"].method in ["GET", "POST"]

from drf-flex-fields.

rjsnh1522 avatar rjsnh1522 commented on August 11, 2024

@frolovroman thanks above code works.
Note: If you are getting error related to custom user model. try putting FlexModelViewSet class in different file as due to circular dependence I was getting getting error.

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

Hm, that doesn't sound very DRY, we would need to change all the views to accept this behavior.
Couldn't we use a combination of the to_representation and to_internal_value methods to achieve the desired behavior without requiring changes to existing code ?

See : https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior

Basically the fields and expand parameters would only be used in to_representation, not every time a serializer is instanciated

Thanks for this insight. I agree that to_representation is definitely the place to alter the fields. I hope to make this update soon.

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

I have a pull request that modifies things to allow using query parameters with non-GET requests:
#50

If anyone wants to test and provide feedback, I'd appreciate it.

from drf-flex-fields.

rsinger86 avatar rsinger86 commented on August 11, 2024

The latest release adds support for modifying fields via query parameters in non-GET requests

from drf-flex-fields.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.