Comments (12)
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.
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 ?
Basically the fields
and expand
parameters would only be used in to_representation
, not every time a serializer is instanciated
from drf-flex-fields.
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.
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.
It would also allow us to omit some useless data from the POST response
from drf-flex-fields.
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.
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:
- You instantiate a serializer with the incoming request data
- The serializer sees a query param asking a PrimaryKeyRelatedField to be expanded to a nested serializer
- The view calls
create()
, which calls your serializers validation - 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.
@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.
@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.
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 theto_representation
andto_internal_value
methods to achieve the desired behavior without requiring changes to existing code ?Basically the
fields
andexpand
parameters would only be used into_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.
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.
The latest release adds support for modifying fields via query parameters in non-GET requests
from drf-flex-fields.
Related Issues (20)
- FlexFieldsFilterBackend does not handle "GenericForeignKey" HOT 2
- 0.9.9 not available on Pypi? HOT 1
- Expand Argument Not Respected When Empty HOT 1
- Release 1.0.0 available on PyPI but not listed in GIthub Releases, and not tagged
- is_expanded does not take into account the wildcard "*" (default) or setting "WILDCARD_VALUES"
- maximum depth of expansion (dot notation) / avoid infinite recursion HOT 2
- <module_path_to_serializer_class>.RelatedSerializer? HOT 1
- _get_expandable_fields() returns a list but is used by get_schema_fields() to concatenate str
- Swagger does not work when add FlexFieldsFilterBackend in my view
- Potential Denial of Service HOT 2
- Passing context to serializer with `request` param causes `AttributeError` on `query_params` HOT 6
- support Django 4.2 and drf 3.14??? HOT 3
- Applying distinct to `fields`
- Does "deferred field" imply under-the-hood `.defer()`? HOT 2
- Support for a list of expansions with multiple expand parameters instead of separated by a comma. HOT 1
- Support lazily evaluating expanded serializer class from a function
- Custom EXPAND_PARAM not usable in Serializer options HOT 1
- Enhancement Request: Support default expansion and omission of fields per drf view or action HOT 1
- Better notation for deferred fields? HOT 1
- OpenAPI support for query parameters HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from drf-flex-fields.