From afc0a7998d97e6f3831c01e7c068b2b901662efe Mon Sep 17 00:00:00 2001 From: Anton-Shutik Date: Wed, 11 Jul 2018 17:05:41 +0300 Subject: [PATCH] "Data" key optional in relationships --- rest_framework_json_api/relations.py | 52 +++++++++++++++++++++++++--- rest_framework_json_api/renderers.py | 28 ++++++++------- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 27644919..ccde3df3 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -7,8 +7,8 @@ from django.core.exceptions import ImproperlyConfigured from django.urls import NoReverseMatch from django.utils.translation import ugettext_lazy as _ -from rest_framework.fields import MISSING_ERROR_MESSAGE -from rest_framework.relations import MANY_RELATION_KWARGS, PrimaryKeyRelatedField +from rest_framework.fields import MISSING_ERROR_MESSAGE, SkipField +from rest_framework.relations import MANY_RELATION_KWARGS, PrimaryKeyRelatedField, ManyRelatedField as DRFManyRelatedField from rest_framework.reverse import reverse from rest_framework.serializers import Serializer @@ -29,6 +29,22 @@ ] +class ManyRelatedField(DRFManyRelatedField): + """ + This workaround skips "data" rendering for relationships + in order to save some sql queries and improve performance + """ + + def __init__(self, child_relation=None, *args, **kwargs): + self.render_data = kwargs.pop('render_data', True) + super(ManyRelatedField, self).__init__(child_relation, *args, **kwargs) + + def get_attribute(self, instance): + if self.render_data: + return super(ManyRelatedField, self).get_attribute(instance) + raise SkipField + + class ResourceRelatedField(PrimaryKeyRelatedField): _skip_polymorphic_optimization = True self_link_view_name = None @@ -49,7 +65,7 @@ class ResourceRelatedField(PrimaryKeyRelatedField): 'no_match': _('Invalid hyperlink - No URL match.'), } - def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwargs): + def __init__(self, self_link_view_name=None, related_link_view_name=None, render_data=True, **kwargs): if self_link_view_name is not None: self.self_link_view_name = self_link_view_name if related_link_view_name is not None: @@ -74,6 +90,29 @@ def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwar super(ResourceRelatedField, self).__init__(**kwargs) + @classmethod + def many_init(cls, *args, **kwargs): + """ + This method handles creating a parent `ManyRelatedField` instance + when the `many=True` keyword argument is passed. + + Typically you won't need to override this method. + + Note that we're over-cautious in passing most arguments to both parent + and child classes in order to try to cover the general case. If you're + overriding this method you'll probably want something much simpler, eg: + + @classmethod + def many_init(cls, *args, **kwargs): + kwargs['child'] = cls() + return CustomManyRelatedField(*args, **kwargs) + """ + list_kwargs = {'child_relation': cls(*args, **kwargs)} + for key in kwargs.keys(): + if key in MANY_RELATION_KWARGS + ('render_data',): + list_kwargs[key] = kwargs[key] + return ManyRelatedField(**list_kwargs) + def use_pk_only_optimization(self): # We need the real object to determine its type... return self.get_resource_type_from_included_serializer() is not None @@ -293,7 +332,8 @@ def __new__(cls, *args, **kwargs): return cls.many_init(*args, **kwargs) return super(ResourceRelatedField, cls).__new__(cls, *args, **kwargs) - def __init__(self, child_relation=None, *args, **kwargs): + def __init__(self, child_relation=None, render_data=True, *args, **kwargs): + self.render_data = render_data model = kwargs.pop('model', None) if child_relation is not None: self.child_relation = child_relation @@ -306,11 +346,13 @@ def many_init(cls, *args, **kwargs): list_kwargs = {k: kwargs.pop(k) for k in LINKS_PARAMS if k in kwargs} list_kwargs['child_relation'] = cls(*args, **kwargs) for key in kwargs.keys(): - if key in ('model',) + MANY_RELATION_KWARGS: + if key in ('model', 'render_data') + MANY_RELATION_KWARGS: list_kwargs[key] = kwargs[key] return cls(**list_kwargs) def get_attribute(self, instance): + if not self.render_data: + raise SkipField # check for a source fn defined on the serializer instead of the model if self.source and hasattr(self.parent, self.source): serializer_method = getattr(self.parent, self.source) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index ba6424ee..277b19ea 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -134,10 +134,12 @@ def extract_relationships(cls, fields, resource, resource_instance): if not resolved: continue + relation_data = {} # special case for ResourceRelatedField - relation_data = { - 'data': resource.get(field_name) - } + if field_name in resource: + relation_data.update({ + 'data': resource.get(field_name) + }) field_links = field.get_links( resource_instance, field.related_link_lookup_field) @@ -188,9 +190,15 @@ def extract_relationships(cls, fields, resource, resource_instance): if isinstance(field.child_relation, ResourceRelatedField): # special case for ResourceRelatedField - relation_data = { - 'data': resource.get(field_name) - } + relation_data = {} + + if field_name in resource: + relation_data.update({ + 'data': resource.get(field_name), + 'meta': { + 'count': len(resource.get(field_name)) + } + }) field_links = field.child_relation.get_links( resource_instance, @@ -200,13 +208,7 @@ def extract_relationships(cls, fields, resource, resource_instance): {'links': field_links} if field_links else dict() ) - relation_data.update( - { - 'meta': { - 'count': len(resource.get(field_name)) - } - } - ) + data.update({field_name: relation_data}) continue