Skip to content

"Data" key optional in relationships #443

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions rest_framework_json_api/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
28 changes: 15 additions & 13 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand Down