Skip to content

Commit

Permalink
Proof of Concept for single lookup on HyperlinkRelatedField
Browse files Browse the repository at this point in the history
  • Loading branch information
carltongibson committed May 17, 2017
1 parent 1ca5a9d commit e811365
Showing 1 changed file with 75 additions and 1 deletion.
76 changes: 75 additions & 1 deletion rest_framework/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,41 @@ def get_url(self, obj, view_name, request, format):
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)

def lookup_parameters(self, data):
request = self.context.get('request', None)
try:
http_prefix = data.startswith(('http:', 'https:'))
except AttributeError:
self.fail('incorrect_type', data_type=type(data).__name__)

if http_prefix:
# If needed convert absolute URLs to relative path
data = urlparse.urlparse(data).path
prefix = get_script_prefix()
if data.startswith(prefix):
data = '/' + data[len(prefix):]

data = uri_to_iri(data)

try:
match = resolve(data)
except Resolver404:
self.fail('no_match')

try:
expected_viewname = request.versioning_scheme.get_versioned_viewname(
self.view_name, request
)
except AttributeError:
expected_viewname = self.view_name

if match.view_name != expected_viewname:
self.fail('incorrect_match')

lookup_value = match.kwargs[self.lookup_url_kwarg]
lookup_kwargs = {self.lookup_field: lookup_value}
return lookup_kwargs

def to_internal_value(self, data):
request = self.context.get('request', None)
try:
Expand Down Expand Up @@ -458,7 +493,8 @@ class ManyRelatedField(Field):
default_empty_html = []
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.')
'empty': _('This list may not be empty.'),
'invalid_values': _('Submitted values invalid')
}
html_cutoff = None
html_cutoff_text = None
Expand Down Expand Up @@ -496,6 +532,44 @@ def to_internal_value(self, data):
if not self.allow_empty and len(data) == 0:
self.fail('empty')

# Guard: exit early if no-op
if len(data) == 0:
return []

# What would a single lookup for HyperlinkedRelatedField look like?
if isinstance(self.child_relation, HyperlinkedRelatedField):

# Get Lookup parameters
lookups = [self.child_relation.lookup_parameters(item) for item in data]
if None in lookups:
self.fail('invalid_values')

# 1. Can we just ask for this directly as self.child_relation.lookup_field for all types?
lookup_field = list(lookups[0])[0]
lookup_values = [item[lookup_field] for item in lookups]
lookup_parameters = {
lookup_field + '__in': lookup_values
}

# Do the lookup
objects = list(self.child_relation.get_queryset().filter(**lookup_parameters))

# Check we got the result we wanted
# This didn't work as types didn't match: set([1, 3]) - set(['1', '3']) == set([1, 3])
#
# missing_primary_keys = set(getattr(o, lookup_field) for o in objects) - set(lookup_values)
# if missing_primary_keys:
# self.fail('missing_ids', ids_not_found=list(missing_primary_keys))
#
# Is there an obvious way to cast to the type of the field?
#
# So... (Is this sufficient?)
if len(set(getattr(o, lookup_field) for o in objects)) != len(set(lookup_values)):
self.fail('invalid_values')

return objects
# END

return [
self.child_relation.to_internal_value(item)
for item in data
Expand Down

0 comments on commit e811365

Please sign in to comment.