aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/fields.py
diff options
context:
space:
mode:
authorTom Christie2012-10-04 16:58:18 +0100
committerTom Christie2012-10-04 16:58:18 +0100
commitc91d926b0664981de0fd239a4398dd71367a5911 (patch)
treebd259ce7df6b584e96467d6b22790b2074d308b7 /rest_framework/fields.py
parent55e9cbecac1456f0e1521a4bcceb1ef4f44e5e0b (diff)
downloaddjango-rest-framework-c91d926b0664981de0fd239a4398dd71367a5911.tar.bz2
Initial tests for hyperlinked relationships
Diffstat (limited to 'rest_framework/fields.py')
-rw-r--r--rest_framework/fields.py93
1 files changed, 90 insertions, 3 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index ad2ca589..9dbc1194 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -4,7 +4,8 @@ import inspect
import warnings
from django.core import validators
-from django.core.exceptions import ValidationError
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.urlresolvers import resolve
from django.conf import settings
from django.utils.encoding import is_protected_type, smart_unicode
from django.utils.translation import ugettext_lazy as _
@@ -223,9 +224,9 @@ class RelatedField(WritableField):
into[(self.source or field_name) + '_id'] = self.from_native(value)
-class ManyRelatedField(RelatedField):
+class ManyRelatedMixin(object):
"""
- Base class for related model managers.
+ Mixin to convert a related field to a many related field.
"""
def field_to_native(self, obj, field_name):
value = getattr(obj, self.source or field_name)
@@ -244,6 +245,15 @@ class ManyRelatedField(RelatedField):
into[field_name] = [self.from_native(item) for item in value]
+class ManyRelatedField(ManyRelatedMixin, RelatedField):
+ """
+ Base class for related model managers.
+ """
+ pass
+
+
+### PrimaryKey relationships
+
class PrimaryKeyRelatedField(RelatedField):
"""
Serializes a related field or related object to a pk value.
@@ -283,6 +293,83 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
return [self.to_native(item.pk) for item in queryset.all()]
+### Hyperlinked relationships
+
+class HyperlinkedRelatedField(RelatedField):
+ pk_url_kwarg = 'pk'
+ slug_url_kwarg = 'slug'
+ slug_field = 'slug'
+
+ def __init__(self, *args, **kwargs):
+ try:
+ self.view_name = kwargs.pop('view_name')
+ except:
+ raise ValueError("Hyperlinked field requires 'view_name' kwarg")
+ super(HyperlinkedRelatedField, self).__init__(*args, **kwargs)
+
+ def to_native(self, obj):
+ view_name = self.view_name
+ request = self.context.get('request', None)
+ kwargs = {self.pk_url_kwarg: obj.pk}
+ try:
+ return reverse(view_name, kwargs=kwargs, request=request)
+ except:
+ pass
+
+ slug = getattr(obj, self.slug_field, None)
+
+ if not slug:
+ raise ValidationError('Could not resolve URL for field using view name "%s"', view_name)
+
+ kwargs = {self.slug_url_kwarg: slug}
+ try:
+ return reverse(self.view_name, kwargs=kwargs, request=request)
+ except:
+ pass
+
+ kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
+ try:
+ return reverse(self.view_name, kwargs=kwargs, request=request)
+ except:
+ pass
+
+ raise ValidationError('Could not resolve URL for field using view name "%s"', view_name)
+
+ def from_native(self, value):
+ # Convert URL -> model instance pk
+ try:
+ match = resolve(value)
+ except:
+ raise ValidationError('Invalid hyperlink - No URL match')
+
+ if match.url_name != self.view_name:
+ raise ValidationError('Invalid hyperlink - Incorrect URL match')
+
+ pk = match.kwargs.get(self.pk_url_kwarg, None)
+ slug = match.kwargs.get(self.slug_url_kwarg, None)
+
+ # Try explicit primary key.
+ if pk is not None:
+ return pk
+ # Next, try looking up by slug.
+ elif slug is not None:
+ slug_field = self.get_slug_field()
+ queryset = self.queryset.filter(**{slug_field: slug})
+ # If none of those are defined, it's an error.
+ else:
+ raise ValidationError('Invalid hyperlink')
+
+ try:
+ obj = queryset.get()
+ except ObjectDoesNotExist:
+ raise ValidationError('Invalid hyperlink - object does not exist.')
+ return obj.pk
+
+
+class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField):
+ pass
+
+
class HyperlinkedIdentityField(Field):
"""
A field that represents the model's identity using a hyperlink.