aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/relations.py
diff options
context:
space:
mode:
authorTom Christie2014-09-12 17:03:42 +0100
committerTom Christie2014-09-12 17:03:42 +0100
commitb73a205cc021983d9a508b447f30e144a1ce4129 (patch)
tree2a414943c68c31867f25b67770142f04abc01dc3 /rest_framework/relations.py
parentf95e7fae38968f58e742b93842bda9110a61b9f7 (diff)
downloaddjango-rest-framework-b73a205cc021983d9a508b447f30e144a1ce4129.tar.bz2
Tests for relational fields (not including many=True)
Diffstat (limited to 'rest_framework/relations.py')
-rw-r--r--rest_framework/relations.py143
1 files changed, 94 insertions, 49 deletions
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index e23a4152..75ec89a8 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,19 +1,24 @@
+from rest_framework.compat import smart_text, urlparse
from rest_framework.fields import Field
from rest_framework.reverse import reverse
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch
from django.db.models.query import QuerySet
-from rest_framework.compat import urlparse
+from django.utils.translation import ugettext_lazy as _
class RelatedField(Field):
def __init__(self, **kwargs):
self.queryset = kwargs.pop('queryset', None)
self.many = kwargs.pop('many', False)
- assert self.queryset is not None or kwargs.get('read_only', False), (
+ assert self.queryset is not None or kwargs.get('read_only', None), (
'Relational field must provide a `queryset` argument, '
'or set read_only=`True`.'
)
+ assert not (self.queryset is not None and kwargs.get('read_only', None)), (
+ 'Relational fields should not provide a `queryset` argument, '
+ 'when setting read_only=`True`.'
+ )
super(RelatedField, self).__init__(**kwargs)
def get_queryset(self):
@@ -25,6 +30,11 @@ class RelatedField(Field):
class StringRelatedField(Field):
+ """
+ A read only field that represents its targets using their
+ plain string representation.
+ """
+
def __init__(self, **kwargs):
kwargs['read_only'] = True
super(StringRelatedField, self).__init__(**kwargs)
@@ -34,10 +44,10 @@ class StringRelatedField(Field):
class PrimaryKeyRelatedField(RelatedField):
- MESSAGES = {
+ default_error_messages = {
'required': 'This field is required.',
'does_not_exist': "Invalid pk '{pk_value}' - object does not exist.",
- 'incorrect_type': 'Incorrect type. Expected pk value, received {data_type}.',
+ 'incorrect_type': 'Incorrect type. Expected pk value, received {data_type}.',
}
def to_internal_value(self, data):
@@ -48,22 +58,33 @@ class PrimaryKeyRelatedField(RelatedField):
except (TypeError, ValueError):
self.fail('incorrect_type', data_type=type(data).__name__)
+ def to_representation(self, value):
+ return value.pk
+
class HyperlinkedRelatedField(RelatedField):
lookup_field = 'pk'
- MESSAGES = {
+ default_error_messages = {
'required': 'This field is required.',
'no_match': 'Invalid hyperlink - No URL match',
'incorrect_match': 'Invalid hyperlink - Incorrect URL match.',
- 'does_not_exist': "Invalid hyperlink - Object does not exist.",
- 'incorrect_type': 'Incorrect type. Expected URL string, received {data_type}.',
+ 'does_not_exist': 'Invalid hyperlink - Object does not exist.',
+ 'incorrect_type': 'Incorrect type. Expected URL string, received {data_type}.',
}
- def __init__(self, **kwargs):
- self.view_name = kwargs.pop('view_name')
+ def __init__(self, view_name, **kwargs):
+ self.view_name = view_name
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
+ self.format = kwargs.pop('format', None)
+
+ # We include these simply for dependancy injection in tests.
+ # We can't add them as class attributes or they would expect an
+ # implict `self` argument to be passed.
+ self.reverse = reverse
+ self.resolve = resolve
+
super(HyperlinkedRelatedField, self).__init__(**kwargs)
def get_object(self, view_name, view_args, view_kwargs):
@@ -77,21 +98,36 @@ class HyperlinkedRelatedField(RelatedField):
lookup_kwargs = {self.lookup_field: lookup_value}
return self.get_queryset().get(**lookup_kwargs)
- def to_internal_value(self, value):
+ def get_url(self, obj, view_name, request, format):
+ """
+ Given an object, return the URL that hyperlinks to the object.
+
+ May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
+ attributes are not configured to correctly match the URL conf.
+ """
+ # Unsaved objects will not yet have a valid URL.
+ if obj.pk is None:
+ return None
+
+ lookup_value = getattr(obj, self.lookup_field)
+ kwargs = {self.lookup_url_kwarg: lookup_value}
+ return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
+
+ def to_internal_value(self, data):
try:
- http_prefix = value.startswith(('http:', 'https:'))
+ http_prefix = data.startswith(('http:', 'https:'))
except AttributeError:
- self.fail('incorrect_type', data_type=type(value).__name__)
+ self.fail('incorrect_type', data_type=type(data).__name__)
if http_prefix:
# If needed convert absolute URLs to relative path
- value = urlparse.urlparse(value).path
+ data = urlparse.urlparse(data).path
prefix = get_script_prefix()
- if value.startswith(prefix):
- value = '/' + value[len(prefix):]
+ if data.startswith(prefix):
+ data = '/' + data[len(prefix):]
try:
- match = resolve(value)
+ match = self.resolve(data)
except Exception:
self.fail('no_match')
@@ -103,41 +139,14 @@ class HyperlinkedRelatedField(RelatedField):
except (ObjectDoesNotExist, TypeError, ValueError):
self.fail('does_not_exist')
-
-class HyperlinkedIdentityField(RelatedField):
- lookup_field = 'pk'
-
- def __init__(self, **kwargs):
- kwargs['read_only'] = True
- kwargs['source'] = '*'
- self.view_name = kwargs.pop('view_name')
- self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
- self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
- super(HyperlinkedIdentityField, self).__init__(**kwargs)
-
- def get_url(self, obj, view_name, request, format):
- """
- Given an object, return the URL that hyperlinks to the object.
-
- May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
- attributes are not configured to correctly match the URL conf.
- """
- # Unsaved objects will not yet have a valid URL.
- if obj.pk is None:
- return None
-
- lookup_value = getattr(obj, self.lookup_field)
- kwargs = {self.lookup_url_kwarg: lookup_value}
- return reverse(view_name, kwargs=kwargs, request=request, format=format)
-
def to_representation(self, value):
request = self.context.get('request', None)
format = self.context.get('format', None)
assert request is not None, (
- "`HyperlinkedIdentityField` requires the request in the serializer"
+ "`%s` requires the request in the serializer"
" context. Add `context={'request': request}` when instantiating "
- "the serializer."
+ "the serializer." % self.__class__.__name__
)
# By default use whatever format is given for the current context
@@ -162,9 +171,45 @@ class HyperlinkedIdentityField(RelatedField):
'model in your API, or incorrectly configured the '
'`lookup_field` attribute on this field.'
)
- raise Exception(msg % self.view_name)
+ raise ImproperlyConfigured(msg % self.view_name)
+
+
+class HyperlinkedIdentityField(HyperlinkedRelatedField):
+ """
+ A read-only field that represents the identity URL for an object, itself.
+
+ This is in contrast to `HyperlinkedRelatedField` which represents the
+ URL of relationships to other objects.
+ """
+
+ def __init__(self, view_name, **kwargs):
+ kwargs['read_only'] = True
+ kwargs['source'] = '*'
+ super(HyperlinkedIdentityField, self).__init__(view_name, **kwargs)
class SlugRelatedField(RelatedField):
- def __init__(self, **kwargs):
- self.slug_field = kwargs.pop('slug_field', None)
+ """
+ A read-write field the represents the target of the relationship
+ by a unique 'slug' attribute.
+ """
+
+ default_error_messages = {
+ 'does_not_exist': _("Object with {slug_name}={value} does not exist."),
+ 'invalid': _('Invalid value.'),
+ }
+
+ def __init__(self, slug_field, **kwargs):
+ self.slug_field = slug_field
+ super(SlugRelatedField, self).__init__(**kwargs)
+
+ def to_internal_value(self, data):
+ try:
+ return self.get_queryset().get(**{self.slug_field: data})
+ except ObjectDoesNotExist:
+ self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
+ except (TypeError, ValueError):
+ self.fail('invalid')
+
+ def to_representation(self, obj):
+ return getattr(obj, self.slug_field)