Skip to content
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

Respect to_field property of ForeignKey relations #2435

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 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
15 changes: 15 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ def __init__(self, *args, **kwargs):
super(MaxLengthValidator, self).__init__(*args, **kwargs)


# ForeignKey's to_fields parameter was changed in Django 1.6 to an array named to_fields
# to_fields is an array but django lets you only set one to_field for ForeignKeys
# https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.ForeignKey.to_field
# see also ForeignKey and ForeignObject in
# https://docs.djangoproject.com/en/1.7/_modules/django/db/models/fields/related/

if django.VERSION >= (1, 6):
def get_to_field(field):
# This should work for casual ForeignKeys
return field.to_fields[0] if len(field.to_fields) else None
else:
def get_to_field(field):
return field.to_field
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have to work on that one. Reading some more of that old code.



# URLValidator only accepts `message` in 1.6+
if django.VERSION >= (1, 6):
from django.core.validators import URLValidator
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def __init__(self, slug_field=None, **kwargs):

def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.slug_field: data})
return self.get_queryset().only(self.slug_field).get(**{self.slug_field: data})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure the only optimization is unrelated and should be removed.

except ObjectDoesNotExist:
self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
except (TypeError, ValueError):
Expand Down
11 changes: 10 additions & 1 deletion rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ class ModelSerializer(Serializer):
models.URLField: URLField,
})
_related_class = PrimaryKeyRelatedField
_related_to_field_class = SlugRelatedField

def create(self, validated_data):
"""
Expand Down Expand Up @@ -1002,8 +1003,16 @@ def get_fields(self):
field_cls = self._get_nested_class(depth, relation_info)
kwargs = get_nested_relation_kwargs(relation_info)
else:
field_cls = self._related_class
kwargs = get_relation_kwargs(field_name, relation_info)
to_field = kwargs.pop('to_field', None)
# it seems that some tests/django initializers are setting
# to_field where it is totally unnecessary
if to_field and to_field != 'id':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The primary key may not be named 'id' so I'm unsure about this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see 2 (outdated diffs) above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some clarification on this? The comment is a bit vauge about what the issue actually is, though I assume it has to do with AutoField considering it involves the id field?

I understand the point of doing if to_field, not the additional check though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it also has to do with what gets into that to_field and makes it to that point. I would remove any 'id' entries in compat/get_to_field making this check here unnecessary. Maybe not the best solution but the to_field irritates me for now. I think for plain ForeignKeys it is safe but for anything else... i dont know.

I added some links to the core issue of that 2 posts above. Thats what i meant.

# using the slug field for now
kwargs['slug_field'] = to_field
field_cls = self._related_to_field_class
else:
field_cls = self._related_class
# `view_name` is only valid for hyperlinked relationships.
if not issubclass(field_cls, HyperlinkedRelatedField):
kwargs.pop('view_name', None)
Expand Down
5 changes: 4 additions & 1 deletion rest_framework/utils/field_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def get_relation_kwargs(field_name, relation_info):
"""
Creates a default instance of a flat relational field.
"""
model_field, related_model, to_many, has_through_model = relation_info
model_field, related_model, to_many, to_field, has_through_model = relation_info
kwargs = {
'queryset': related_model._default_manager,
'view_name': get_detail_view_name(related_model)
Expand All @@ -203,6 +203,9 @@ def get_relation_kwargs(field_name, relation_info):
if to_many:
kwargs['many'] = True

if to_field:
kwargs['to_field'] = to_field
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be kwargs['slug_field'] = to_field ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's being used in serializers.py with the check against id. If we determine that the check isn't actually needed, then I think we could get away with just using slug_field here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's being used in serializers.py with the check against id.

Sorry I didn't understand - you might need to link me to that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I named it to_field because mentally i am still treating it like a ForeignKey object at that point. Pointing it to a slug_field afterwards was just handy


if has_through_model:
kwargs['read_only'] = True
kwargs.pop('queryset', None)
Expand Down
8 changes: 8 additions & 0 deletions rest_framework/utils/model_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six
from rest_framework.compat import get_to_field
from rest_framework.compat import OrderedDict
import inspect

Expand All @@ -26,6 +27,7 @@
'model_field',
'related',
'to_many',
'to_field',
'has_through_model'
])

Expand Down Expand Up @@ -100,6 +102,7 @@ def _get_forward_relationships(opts):
model_field=field,
related=_resolve_model(field.rel.to),
to_many=False,
to_field=get_to_field(field),
has_through_model=False
)

Expand All @@ -109,6 +112,8 @@ def _get_forward_relationships(opts):
model_field=field,
related=_resolve_model(field.rel.to),
to_many=True,
# manytomany do not have to_fields
to_field=None,
has_through_model=(
not field.rel.through._meta.auto_created
)
Expand All @@ -128,6 +133,7 @@ def _get_reverse_relationships(opts):
model_field=None,
related=relation.model,
to_many=relation.field.rel.multiple,
to_field=get_to_field(relation.field),
has_through_model=False
)

Expand All @@ -138,6 +144,8 @@ def _get_reverse_relationships(opts):
model_field=None,
related=relation.model,
to_many=True,
# manytomany do not have to_fields
to_field=None,
has_through_model=(
(getattr(relation.field.rel, 'through', None) is not None)
and not relation.field.rel.through._meta.auto_created
Expand Down