From d1746e2f3c3c2250ffdf7b71f2a77df3edccea61 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 18 Oct 2012 22:02:44 +0100 Subject: Allow callables in dotted notation like Field(source='foo.bar') --- rest_framework/fields.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bb9a523d..663a168d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -70,6 +70,8 @@ class Field(object): value = obj for component in self.source.split('.'): value = getattr(value, component) + if is_simple_callable(value): + value = value() else: value = getattr(obj, field_name) return self.to_native(value) -- cgit v1.2.3 From c341799344ab394322a589ce44328f245910e651 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 18 Oct 2012 22:19:54 +0100 Subject: Apply readonly on RelatedField --- rest_framework/fields.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 663a168d..162eed26 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -224,6 +224,9 @@ class RelatedField(WritableField): return self.to_native(value) def field_from_native(self, data, field_name, into): + if self.readonly: + return + value = data.get(field_name) into[(self.source or field_name) + '_id'] = self.from_native(value) -- cgit v1.2.3 From 643d3491a65237fef6932ef8833472c243ad7ee8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 18 Oct 2012 23:48:52 +0100 Subject: First pass at pastebin tutorial --- rest_framework/fields.py | 80 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 9 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bb9a523d..14422b27 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -7,6 +7,7 @@ from django.core import validators from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve from django.conf import settings +from django.forms import widgets from django.utils.encoding import is_protected_type, smart_unicode from django.utils.translation import ugettext_lazy as _ from rest_framework.reverse import reverse @@ -105,10 +106,14 @@ class WritableField(Field): 'required': _('This field is required.'), 'invalid': _('Invalid value.'), } + widget = widgets.TextInput def __init__(self, source=None, readonly=False, required=None, - validators=[], error_messages=None): + validators=[], error_messages=None, widget=None, + help_text=None, initial=None): + super(WritableField, self).__init__(source=source) + self.readonly = readonly if required is None: self.required = not(readonly) @@ -124,6 +129,15 @@ class WritableField(Field): self.validators = self.default_validators + validators + # These attributes are ony used for HTML forms. + self.initial = initial + self.help_text = help_text and smart_unicode(help_text) or '' + + widget = widget or self.widget + if isinstance(widget, type): + widget = widget() + self.widget = widget + def validate(self, value): if value in validators.EMPTY_VALUES and self.required: raise ValidationError(self.error_messages['required']) @@ -157,9 +171,12 @@ class WritableField(Field): try: native = data[field_name] except KeyError: - if self.required: - raise ValidationError(self.error_messages['required']) - return + if getattr(self, 'missing_value', None) is not None: + native = self.missing_value + else: + if self.required: + raise ValidationError(self.error_messages['required']) + return value = self.from_native(native) if self.source == '*': @@ -394,20 +411,19 @@ class HyperlinkedIdentityField(Field): class BooleanField(WritableField): type_name = 'BooleanField' + widget = widgets.CheckboxInput default_error_messages = { 'invalid': _(u"'%s' value must be either True or False."), } + empty = False + missing_value = False # Fill in missing value not supplied by html form def from_native(self, value): - if value in (True, False): - # if value is 1 or 0 than it's equal to True or False, but we want - # to return a true bool for semantic reasons. - return bool(value) if value in ('t', 'True', '1'): return True if value in ('f', 'False', '0'): return False - raise ValidationError(self.error_messages['invalid'] % value) + return bool(value) class CharField(WritableField): @@ -427,6 +443,52 @@ class CharField(WritableField): return smart_unicode(value) +class ChoiceField(WritableField): + type_name = 'ChoiceField' + widget = widgets.Select + default_error_messages = { + 'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'), + } + + def __init__(self, choices=(), *args, **kwargs): + super(ChoiceField, self).__init__(*args, **kwargs) + self.choices = choices + + def _get_choices(self): + return self._choices + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + def validate(self, value): + """ + Validates that the input is in self.choices. + """ + super(ChoiceField, self).validate(value) + if value and not self.valid_value(value): + raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) + + def valid_value(self, value): + """ + Check to see if the provided value is a valid choice. + """ + for k, v in self.choices: + if isinstance(v, (list, tuple)): + # This is an optgroup, so look inside the group for options + for k2, v2 in v: + if value == smart_unicode(k2): + return True + else: + if value == smart_unicode(k): + return True + return False + + class EmailField(CharField): type_name = 'EmailField' -- cgit v1.2.3 From dab177e29e45b657bb43705979c8e601d5a1b31b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 19 Oct 2012 09:20:54 +0100 Subject: Drop help_text --- rest_framework/fields.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 14422b27..0990eadc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -110,7 +110,7 @@ class WritableField(Field): def __init__(self, source=None, readonly=False, required=None, validators=[], error_messages=None, widget=None, - help_text=None, initial=None): + initial=None): super(WritableField, self).__init__(source=source) @@ -131,7 +131,6 @@ class WritableField(Field): # These attributes are ony used for HTML forms. self.initial = initial - self.help_text = help_text and smart_unicode(help_text) or '' widget = widget or self.widget if isinstance(widget, type): -- cgit v1.2.3 From 93f1aa4f69df85add114c9730a01b50d013a844a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 21 Oct 2012 17:41:05 +0100 Subject: Remove `initial` kwarg, add `default`. --- rest_framework/fields.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 0990eadc..29940946 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -107,10 +107,11 @@ class WritableField(Field): 'invalid': _('Invalid value.'), } widget = widgets.TextInput + default = None def __init__(self, source=None, readonly=False, required=None, validators=[], error_messages=None, widget=None, - initial=None): + default=None): super(WritableField, self).__init__(source=source) @@ -128,10 +129,9 @@ class WritableField(Field): self.error_messages = messages self.validators = self.default_validators + validators + self.default = default or self.default - # These attributes are ony used for HTML forms. - self.initial = initial - + # Widgets are ony used for HTML forms. widget = widget or self.widget if isinstance(widget, type): widget = widget() @@ -170,8 +170,8 @@ class WritableField(Field): try: native = data[field_name] except KeyError: - if getattr(self, 'missing_value', None) is not None: - native = self.missing_value + if self.default is not None: + native = self.default else: if self.required: raise ValidationError(self.error_messages['required']) @@ -415,7 +415,11 @@ class BooleanField(WritableField): 'invalid': _(u"'%s' value must be either True or False."), } empty = False - missing_value = False # Fill in missing value not supplied by html form + + # Note: we set default to `False` in order to fill in missing value not + # supplied by html form. TODO: Fix so that only html form input gets + # this behavior. + default = False def from_native(self, value): if value in ('t', 'True', '1'): -- cgit v1.2.3 From c7a0d52fd7e22fbc4a01ff900bd3b2c1215e984d Mon Sep 17 00:00:00 2001 From: Ian Strachan Date: Mon, 22 Oct 2012 22:24:26 +0100 Subject: #314 Fix for manytomany field being required in the payload even though the field is specified as readonly in the serializer --- rest_framework/fields.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f610d6aa..6ed37823 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -256,6 +256,9 @@ class ManyRelatedMixin(object): return [self.to_native(item) for item in value.all()] def field_from_native(self, data, field_name, into): + if self.readonly: + return + try: # Form data value = data.getlist(self.source or field_name) -- cgit v1.2.3 From fc4614a89c5c2bbdeb3626e2f16e3a2cd6445e3e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 12:46:41 +0100 Subject: Whitespace --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6ed37823..85e6ee31 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -258,7 +258,7 @@ class ManyRelatedMixin(object): def field_from_native(self, data, field_name, into): if self.readonly: return - + try: # Form data value = data.getlist(self.source or field_name) -- cgit v1.2.3 From 6e4ab09aae8295e4ef722d59894bc2934435ae46 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 20:21:45 +0000 Subject: readonly -> read_only --- rest_framework/fields.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 85e6ee31..c0e527e5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -111,17 +111,17 @@ class WritableField(Field): widget = widgets.TextInput default = None - def __init__(self, source=None, readonly=False, required=None, + def __init__(self, source=None, read_only=False, required=None, validators=[], error_messages=None, widget=None, default=None): super(WritableField, self).__init__(source=source) - self.readonly = readonly + self.read_only = read_only if required is None: - self.required = not(readonly) + self.required = not(read_only) else: - assert not readonly, "Cannot set required=True and readonly=True" + assert not read_only, "Cannot set required=True and read_only=True" self.required = required messages = {} @@ -166,7 +166,7 @@ class WritableField(Field): Given a dictionary and a field name, updates the dictionary `into`, with the field and it's deserialized value. """ - if self.readonly: + if self.read_only: return try: @@ -240,7 +240,7 @@ class RelatedField(WritableField): return self.to_native(value) def field_from_native(self, data, field_name, into): - if self.readonly: + if self.read_only: return value = data.get(field_name) @@ -256,7 +256,7 @@ class ManyRelatedMixin(object): return [self.to_native(item) for item in value.all()] def field_from_native(self, data, field_name, into): - if self.readonly: + if self.read_only: return try: -- cgit v1.2.3 From f4edd9256667af205f4edf9ada642ef9a62802e4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Oct 2012 12:51:21 +0000 Subject: Writable welated fields should return a model instance from .from_native(), not a pk --- rest_framework/fields.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0e527e5..ffc0c9d4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -244,7 +244,7 @@ class RelatedField(WritableField): return value = data.get(field_name) - into[(self.source or field_name) + '_id'] = self.from_native(value) + into[(self.source or field_name)] = self.from_native(value) class ManyRelatedMixin(object): @@ -288,6 +288,12 @@ class PrimaryKeyRelatedField(RelatedField): def to_native(self, pk): return pk + def from_native(self, data): + try: + return self.queryset.get(pk=data) + except ObjectDoesNotExist: + raise ValidationError('Invalid hyperlink - object does not exist.') + def field_to_native(self, obj, field_name): try: # Prefer obj.serializable_value for performance reasons @@ -377,7 +383,7 @@ class HyperlinkedRelatedField(RelatedField): # Try explicit primary key. if pk is not None: - return pk + queryset = self.queryset.filter(pk=pk) # Next, try looking up by slug. elif slug is not None: slug_field = self.get_slug_field() @@ -390,7 +396,7 @@ class HyperlinkedRelatedField(RelatedField): obj = queryset.get() except ObjectDoesNotExist: raise ValidationError('Invalid hyperlink - object does not exist.') - return obj.pk + return obj class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField): -- cgit v1.2.3 From 49f87cfbabe036215cb97d2633d969e27c3915ce Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Oct 2012 14:10:38 +0000 Subject: Deal with None and missing values --- rest_framework/fields.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0e527e5..03c01a88 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -509,7 +509,10 @@ class EmailField(CharField): default_validators = [validators.validate_email] def from_native(self, value): - return super(EmailField, self).from_native(value).strip() + ret = super(EmailField, self).from_native(value) + if ret is None: + return None + return ret.strip() def __deepcopy__(self, memo): result = copy.copy(self) @@ -531,8 +534,9 @@ class DateField(WritableField): empty = None def from_native(self, value): - if value is None: - return value + if value in validators.EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): if timezone and settings.USE_TZ and timezone.is_aware(value): # Convert aware datetimes to the default time zone @@ -570,8 +574,9 @@ class DateTimeField(WritableField): empty = None def from_native(self, value): - if value is None: - return value + if value in validators.EMPTY_VALUES: + return None + if isinstance(value, datetime.datetime): return value if isinstance(value, datetime.date): @@ -629,6 +634,7 @@ class IntegerField(WritableField): def from_native(self, value): if value in validators.EMPTY_VALUES: return None + try: value = int(str(value)) except (ValueError, TypeError): @@ -644,8 +650,9 @@ class FloatField(WritableField): } def from_native(self, value): - if value is None: - return value + if value in validators.EMPTY_VALUES: + return None + try: return float(value) except (TypeError, ValueError): -- cgit v1.2.3 From d206c686a643b7cc720743d8dab207e8ad0280f8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Oct 2012 17:08:38 +0000 Subject: Fixes for urls with suffixes --- rest_framework/fields.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 03c01a88..f9de7cf9 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -331,14 +331,16 @@ class HyperlinkedRelatedField(RelatedField): self.view_name = kwargs.pop('view_name') except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") + self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) def to_native(self, obj): view_name = self.view_name request = self.context.get('request', None) + format = self.format or self.context.get('format', None) kwargs = {self.pk_url_kwarg: obj.pk} try: - return reverse(view_name, kwargs=kwargs, request=request) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass @@ -349,13 +351,13 @@ class HyperlinkedRelatedField(RelatedField): kwargs = {self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request) + return reverse(self.view_name, kwargs=kwargs, request=request, format=format) except: pass kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request) + return reverse(self.view_name, kwargs=kwargs, request=request, format=format) except: pass @@ -405,13 +407,15 @@ class HyperlinkedIdentityField(Field): # TODO: Make this mandatory, and have the HyperlinkedModelSerializer # set it on-the-fly self.view_name = kwargs.pop('view_name', None) + self.format = kwargs.pop('format', None) super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) def field_to_native(self, obj, field_name): request = self.context.get('request', None) + format = self.format or self.context.get('format', None) view_name = self.view_name or self.parent.opts.view_name view_kwargs = {'pk': obj.pk} - return reverse(view_name, kwargs=view_kwargs, request=request) + return reverse(view_name, kwargs=view_kwargs, request=request, format=format) ##### Typed Fields ##### -- cgit v1.2.3 From 4d90bb4af4e565f156ca5d84eb205cbb63810b46 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 30 Oct 2012 00:30:52 +0100 Subject: Fix some typos. --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f9de7cf9..067b1087 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -43,7 +43,7 @@ class Field(object): Called to set up a field prior to field_to_native or field_from_native. parent - The parent serializer. - model_field - The model field this field corrosponds to, if one exists. + model_field - The model field this field corresponds to, if one exists. """ self.parent = parent self.root = parent.root or parent @@ -197,7 +197,7 @@ class WritableField(Field): class ModelField(WritableField): """ - A generic field that can be used against an arbirtrary model field. + A generic field that can be used against an arbitrary model field. """ def __init__(self, *args, **kwargs): try: -- cgit v1.2.3 From 5670cb03bfa0ab62f90aed5fe3c62c5349d64264 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 30 Oct 2012 10:49:01 +0000 Subject: Allow use of absolute URLs when deserializing hyperlinked fields --- rest_framework/fields.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bb7d0918..7dab15fa 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -5,7 +5,7 @@ import warnings from django.core import validators from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.core.urlresolvers import resolve +from django.core.urlresolvers import resolve, get_script_prefix from django.conf import settings from django.forms import widgets from django.utils.encoding import is_protected_type, smart_unicode @@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.reverse import reverse from rest_framework.compat import parse_date, parse_datetime from rest_framework.compat import timezone +from urlparse import urlparse def is_simple_callable(obj): @@ -372,6 +373,14 @@ class HyperlinkedRelatedField(RelatedField): def from_native(self, value): # Convert URL -> model instance pk # TODO: Use values_list + + if value.startswith('http:') or value.startswith('https:'): + # If needed convert absolute URLs to relative path + value = urlparse(value).path + prefix = get_script_prefix() + if value.startswith(prefix): + value = '/' + value[len(prefix):] + try: match = resolve(value) except: -- cgit v1.2.3 From 0047a4602045ed0ef9efae9aa88143aa6a70d93d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 30 Oct 2012 11:03:03 +0000 Subject: Merge fixes for 'blank=True' fields. Fixes #324, Fixes #325 Thanks to @Roarster. --- rest_framework/fields.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7dab15fa..090a1e37 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -114,7 +114,7 @@ class WritableField(Field): def __init__(self, source=None, read_only=False, required=None, validators=[], error_messages=None, widget=None, - default=None): + default=None, blank=None): super(WritableField, self).__init__(source=source) @@ -133,6 +133,7 @@ class WritableField(Field): self.validators = self.default_validators + validators self.default = default or self.default + self.blank = blank # Widgets are ony used for HTML forms. widget = widget or self.widget @@ -467,6 +468,16 @@ class CharField(WritableField): if max_length is not None: self.validators.append(validators.MaxLengthValidator(max_length)) + def validate(self, value): + """ + Validates that the value is supplied (if required). + """ + # if empty string and allow blank + if self.blank and not value: + return + else: + super(CharField, self).validate(value) + def from_native(self, value): if isinstance(value, basestring) or value is None: return value -- cgit v1.2.3 From cea907f172c4f640e8bb44e14fab43150fbbc6ad Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 30 Oct 2012 11:10:23 +0000 Subject: Raise nicer exception if queryset not Set on writable related field. Refs: #338 --- rest_framework/fields.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 090a1e37..1d6d760e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -291,6 +291,9 @@ class PrimaryKeyRelatedField(RelatedField): return pk def from_native(self, data): + if self.queryset is None: + raise Exception('Writable related fields must include a `queryset` argument') + try: return self.queryset.get(pk=data) except ObjectDoesNotExist: @@ -374,6 +377,8 @@ class HyperlinkedRelatedField(RelatedField): def from_native(self, value): # Convert URL -> model instance pk # TODO: Use values_list + if self.queryset is None: + raise Exception('Writable related fields must include a `queryset` argument') if value.startswith('http:') or value.startswith('https:'): # If needed convert absolute URLs to relative path -- cgit v1.2.3 From ff7725f05e8ca624e54d707f7c655e3d5c8b8888 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 31 Oct 2012 15:30:01 +0100 Subject: added support for custom slug field and kwargs without subclassing HyperlinkedRelatedField and overwriting slug_url_kwarg and slug_field there is no possibility to use other fields / arguments. now you can do something like this: url(r'^users/(?P\w[\w-]*)$', UserInstance.as_view(), name='user-detail') class ProjectSerializer(serializers.HyperlinkedModelSerializer): created_by = serializers.HyperlinkedRelatedField(view_name='user-detail', slug_url_kwargs='username', slug_field='username') --- rest_framework/fields.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1d6d760e..63e15923 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -342,6 +342,8 @@ class HyperlinkedRelatedField(RelatedField): self.view_name = kwargs.pop('view_name') except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") + self.slug_url_kwarg = kwargs.pop('slug_url_kwargs', self.slug_url_kwarg) + self.slug_field = kwargs.pop('slug_field', self.slug_field) self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 756297ad1d07f56459471bff041828850ace0496 Mon Sep 17 00:00:00 2001 From: Otto Yiu Date: Wed, 31 Oct 2012 21:40:20 -0700 Subject: fix 'from_native' method when rel is None 'NoneType' object has no attribute 'to' --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1d6d760e..73c8f72b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -211,9 +211,9 @@ class ModelField(WritableField): def from_native(self, value): try: rel = self.model_field.rel + return rel.to._meta.get_field(rel.field_name).to_python(value) except: return self.model_field.to_python(value) - return rel.to._meta.get_field(rel.field_name).to_python(value) def field_to_native(self, obj, field_name): value = self.model_field._get_val_from_obj(obj) -- cgit v1.2.3 From d327c5f531b341ad980d20454211b02b87f34d0e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 1 Nov 2012 23:04:13 +0000 Subject: Relational field support in browseable API. Add slug relational fields. Add quickstart. --- rest_framework/fields.py | 107 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 5 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1d6d760e..6d858087 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -8,6 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.urlresolvers import resolve, get_script_prefix from django.conf import settings from django.forms import widgets +from django.forms.models import ModelChoiceIterator from django.utils.encoding import is_protected_type, smart_unicode from django.utils.translation import ugettext_lazy as _ from rest_framework.reverse import reverse @@ -232,11 +233,72 @@ class ModelField(WritableField): class RelatedField(WritableField): """ Base class for related model fields. + + If not overridden, this represents a to-one relatinship, using the unicode + representation of the target. """ + widget = widgets.Select + cache_choices = False + empty_label = None + def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset', None) super(RelatedField, self).__init__(*args, **kwargs) + ### We need this stuff to make form choices work... + + # def __deepcopy__(self, memo): + # result = super(RelatedField, self).__deepcopy__(memo) + # result.queryset = result.queryset + # return result + + def prepare_value(self, obj): + return self.to_native(obj) + + def label_from_instance(self, obj): + """ + Return a readable representation for use with eg. select widgets. + """ + desc = smart_unicode(obj) + ident = self.to_native(obj) + if desc == ident: + return desc + return "%s - %s" % (desc, ident) + + def _get_queryset(self): + return self._queryset + + def _set_queryset(self, queryset): + self._queryset = queryset + self.widget.choices = self.choices + + queryset = property(_get_queryset, _set_queryset) + + def _get_choices(self): + # If self._choices is set, then somebody must have manually set + # the property self.choices. In this case, just return self._choices. + if hasattr(self, '_choices'): + return self._choices + + # Otherwise, execute the QuerySet in self.queryset to determine the + # choices dynamically. Return a fresh ModelChoiceIterator that has not been + # consumed. Note that we're instantiating a new ModelChoiceIterator *each* + # time _get_choices() is called (and, thus, each time self.choices is + # accessed) so that we can ensure the QuerySet has not been consumed. This + # construct might look complicated but it allows for lazy evaluation of + # the queryset. + return ModelChoiceIterator(self) + + def _set_choices(self, value): + # Setting choices also sets the choices on the widget. + # choices can be any iterable, but we call list() on it because + # it will be consumed more than once. + self._choices = self.widget.choices = list(value) + + choices = property(_get_choices, _set_choices) + + ### Regular serializier stuff... + def field_to_native(self, obj, field_name): value = getattr(obj, self.source or field_name) return self.to_native(value) @@ -253,6 +315,8 @@ class ManyRelatedMixin(object): """ Mixin to convert a related field to a many related field. """ + widget = widgets.SelectMultiple + def field_to_native(self, obj, field_name): value = getattr(obj, self.source or field_name) return [self.to_native(item) for item in value.all()] @@ -276,6 +340,9 @@ class ManyRelatedMixin(object): class ManyRelatedField(ManyRelatedMixin, RelatedField): """ Base class for related model managers. + + If not overridden, this represents a to-many relatinship, using the unicode + representations of the target, and is read-only. """ pass @@ -284,7 +351,7 @@ class ManyRelatedField(ManyRelatedMixin, RelatedField): class PrimaryKeyRelatedField(RelatedField): """ - Serializes a related field or related object to a pk value. + Represents a to-one relationship as a pk value. """ def to_native(self, pk): @@ -313,7 +380,7 @@ class PrimaryKeyRelatedField(RelatedField): class ManyPrimaryKeyRelatedField(ManyRelatedField): """ - Serializes a to-many related field or related manager to a pk value. + Represents a to-many relationship as a pk value. """ def to_native(self, pk): return pk @@ -329,10 +396,36 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): # Forward relationship return [self.to_native(item.pk) for item in queryset.all()] +### Slug relationships + + +class SlugRelatedField(RelatedField): + def __init__(self, *args, **kwargs): + self.slug_field = kwargs.pop('slug_field', None) + assert self.slug_field, 'slug_field is required' + super(SlugRelatedField, self).__init__(*args, **kwargs) + + def to_native(self, obj): + return getattr(obj, self.slug_field) + + def from_native(self, data): + try: + return self.queryset.get(**{self.slug_field: data}) + except ObjectDoesNotExist: + raise ValidationError('Object with %s=%s does not exist.' % + (self.slug_field, unicode(data))) + + +class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField): + pass + ### Hyperlinked relationships class HyperlinkedRelatedField(RelatedField): + """ + Represents a to-one relationship, using hyperlinking. + """ pk_url_kwarg = 'pk' slug_url_kwarg = 'slug' slug_field = 'slug' @@ -417,16 +510,20 @@ class HyperlinkedRelatedField(RelatedField): class ManyHyperlinkedRelatedField(ManyRelatedMixin, HyperlinkedRelatedField): + """ + Represents a to-many relationship, using hyperlinking. + """ pass class HyperlinkedIdentityField(Field): """ - A field that represents the model's identity using a hyperlink. + Represents the instance, or a property on the instance, using hyperlinking. """ + def __init__(self, *args, **kwargs): - # TODO: Make this mandatory, and have the HyperlinkedModelSerializer - # set it on-the-fly + # TODO: Make view_name mandatory, and have the + # HyperlinkedModelSerializer set it on-the-fly self.view_name = kwargs.pop('view_name', None) self.format = kwargs.pop('format', None) super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From b9bff2a984767c4258cad4860addc53a9623bb11 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 14:05:14 +0000 Subject: Fix issues with pk related fields in the browsable API. --- rest_framework/fields.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b7d02234..375d7a46 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -230,6 +230,7 @@ class ModelField(WritableField): ##### Relational fields ##### +# Not actually Writable, but subclasses may need to be. class RelatedField(WritableField): """ Base class for related model fields. @@ -240,10 +241,12 @@ class RelatedField(WritableField): widget = widgets.Select cache_choices = False empty_label = None + default_read_only = True # TODO: Remove this def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset', None) super(RelatedField, self).__init__(*args, **kwargs) + self.read_only = self.default_read_only ### We need this stuff to make form choices work... @@ -260,7 +263,7 @@ class RelatedField(WritableField): Return a readable representation for use with eg. select widgets. """ desc = smart_unicode(obj) - ident = self.to_native(obj) + ident = smart_unicode(self.to_native(obj)) if desc == ident: return desc return "%s - %s" % (desc, ident) @@ -353,7 +356,23 @@ class PrimaryKeyRelatedField(RelatedField): """ Represents a to-one relationship as a pk value. """ + default_read_only = False + # TODO: Remove these field hacks... + def prepare_value(self, obj): + return self.to_native(obj.pk) + + def label_from_instance(self, obj): + """ + Return a readable representation for use with eg. select widgets. + """ + desc = smart_unicode(obj) + ident = smart_unicode(self.to_native(obj.pk)) + if desc == ident: + return desc + return "%s - %s" % (desc, ident) + + # TODO: Possibly change this to just take `obj`, through prob less performant def to_native(self, pk): return pk @@ -382,6 +401,21 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): """ Represents a to-many relationship as a pk value. """ + default_read_only = False + + def prepare_value(self, obj): + return self.to_native(obj.pk) + + def label_from_instance(self, obj): + """ + Return a readable representation for use with eg. select widgets. + """ + desc = smart_unicode(obj) + ident = smart_unicode(self.to_native(obj.pk)) + if desc == ident: + return desc + return "%s - %s" % (desc, ident) + def to_native(self, pk): return pk @@ -400,6 +434,8 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): class SlugRelatedField(RelatedField): + default_read_only = False + def __init__(self, *args, **kwargs): self.slug_field = kwargs.pop('slug_field', None) assert self.slug_field, 'slug_field is required' @@ -429,6 +465,7 @@ class HyperlinkedRelatedField(RelatedField): pk_url_kwarg = 'pk' slug_url_kwarg = 'slug' slug_field = 'slug' + default_read_only = False def __init__(self, *args, **kwargs): try: -- cgit v1.2.3 From 3de61d200ccfe51a8e7e2fd900838c9c1c91b0f2 Mon Sep 17 00:00:00 2001 From: Jacob Magnusson Date: Fri, 2 Nov 2012 20:55:58 +0100 Subject: Don't convert dictionary into a string representation --- rest_framework/fields.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 375d7a46..6dfe7458 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -90,6 +90,8 @@ class Field(object): return value elif hasattr(value, '__iter__') and not isinstance(value, (dict, basestring)): return [self.to_native(item) for item in value] + elif isinstance(value, dict): + return dict((k, self.to_native(v)) for k, v in value.items()) return smart_unicode(value) def attributes(self): -- cgit v1.2.3 From a6806f03078fbdda598e10260f9d9bcdf07c1dce Mon Sep 17 00:00:00 2001 From: Jacob Magnusson Date: Fri, 2 Nov 2012 21:48:16 +0100 Subject: Apply to_native on dictionary keys as well --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6dfe7458..515ccbcf 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -91,7 +91,7 @@ class Field(object): elif hasattr(value, '__iter__') and not isinstance(value, (dict, basestring)): return [self.to_native(item) for item in value] elif isinstance(value, dict): - return dict((k, self.to_native(v)) for k, v in value.items()) + return dict(map(self.to_native, (k, v)) for k, v in value.items()) return smart_unicode(value) def attributes(self): -- cgit v1.2.3 From 6eaec7a0eccabb3e1b010d07633632e8a3ecd86f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 20:53:33 +0000 Subject: foreign key tests --- rest_framework/fields.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 375d7a46..965e22c4 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -383,7 +383,8 @@ class PrimaryKeyRelatedField(RelatedField): try: return self.queryset.get(pk=data) except ObjectDoesNotExist: - raise ValidationError('Invalid hyperlink - object does not exist.') + msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + raise ValidationError(msg) def field_to_native(self, obj, field_name): try: @@ -430,6 +431,16 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): # Forward relationship return [self.to_native(item.pk) for item in queryset.all()] + def from_native(self, data): + if self.queryset is None: + raise Exception('Writable related fields must include a `queryset` argument') + + try: + return self.queryset.get(pk=data) + except ObjectDoesNotExist: + msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + raise ValidationError(msg) + ### Slug relationships -- cgit v1.2.3 From 8ec54e6a9fe8e46955098ca8e7d728d8bb4f7053 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 20:56:51 +0000 Subject: Tweaks --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 965e22c4..661d02de 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -344,7 +344,7 @@ class ManyRelatedField(ManyRelatedMixin, RelatedField): """ Base class for related model managers. - If not overridden, this represents a to-many relatinship, using the unicode + If not overridden, this represents a to-many relationship, using the unicode representations of the target, and is read-only. """ pass -- cgit v1.2.3 From 5b397e50ddb999f85949a7359d0a26c3531c78a9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 5 Nov 2012 10:16:23 +0000 Subject: Include queryset missing exception on writable SlugField --- rest_framework/fields.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5b28463c..eb4c5e1f 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -458,6 +458,9 @@ class SlugRelatedField(RelatedField): return getattr(obj, self.slug_field) def from_native(self, data): + if self.queryset is None: + raise Exception('Writable related fields must include a `queryset` argument') + try: return self.queryset.get(**{self.slug_field: data}) except ObjectDoesNotExist: -- cgit v1.2.3 From 33be4b43b95273da117f6896adc7814aaedf43fa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 5 Nov 2012 12:51:04 +0000 Subject: `queryset` argument is now optional on writable model fields. --- rest_framework/fields.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index eb4c5e1f..dc726a32 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -40,7 +40,7 @@ class Field(object): self.source = source - def initialize(self, parent): + def initialize(self, parent, field_name): """ Called to set up a field prior to field_to_native or field_from_native. @@ -248,7 +248,22 @@ class RelatedField(WritableField): def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset', None) super(RelatedField, self).__init__(*args, **kwargs) - self.read_only = self.default_read_only + self.read_only = kwargs.pop('read_only', self.default_read_only) + + def initialize(self, parent, field_name): + super(RelatedField, self).initialize(parent, field_name) + if self.queryset is None and not self.read_only: + try: + manager = getattr(self.parent.opts.model, self.source or field_name) + if hasattr(manager, 'related'): # Forward + self.queryset = manager.related.model._default_manager.all() + else: # Reverse + self.queryset = manager.field.rel.to._default_manager.all() + except: + raise + msg = ('Serializer related fields must include a `queryset`' + + ' argument or set `read_only=True') + raise Exception(msg) ### We need this stuff to make form choices work... -- cgit v1.2.3 From 03095f607a4f110c1dc831a394f6b480a691f1fd Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 5 Nov 2012 16:37:37 +0100 Subject: added testcase for custom slug field in hyperlinkedrelatedfield --- rest_framework/fields.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2185cf34..305b7562 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -493,6 +493,12 @@ class HyperlinkedRelatedField(RelatedField): self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) + def get_slug_field(self): + """ + Get the name of a slug field to be used to look up by slug. + """ + return self.slug_field + def to_native(self, obj): view_name = self.view_name request = self.context.get('request', None) -- cgit v1.2.3 From 0a660a531af67dba0b42fbe2de09e50e83572e09 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Mon, 5 Nov 2012 16:43:03 +0100 Subject: fixed typo --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 305b7562..734a7551 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -488,7 +488,7 @@ class HyperlinkedRelatedField(RelatedField): self.view_name = kwargs.pop('view_name') except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") - self.slug_url_kwarg = kwargs.pop('slug_url_kwargs', self.slug_url_kwarg) + self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', self.slug_url_kwarg) self.slug_field = kwargs.pop('slug_field', self.slug_field) self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 867033e676548e69c21615d84c0132e8241b2667 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 5 Nov 2012 16:43:44 +0000 Subject: slug_url_kwarg should default to same as slug_field --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f5d3dea8..45c0cc8e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -494,8 +494,8 @@ class HyperlinkedRelatedField(RelatedField): Represents a to-one relationship, using hyperlinking. """ pk_url_kwarg = 'pk' - slug_url_kwarg = 'slug' slug_field = 'slug' + slug_url_kwarg = None # Defaults to same as `slug_field` default_read_only = False def __init__(self, *args, **kwargs): @@ -503,8 +503,8 @@ class HyperlinkedRelatedField(RelatedField): self.view_name = kwargs.pop('view_name') except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") - self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', self.slug_url_kwarg) self.slug_field = kwargs.pop('slug_field', self.slug_field) + self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', self.slug_field) self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 470878a591e4e120dc5b38c834ab0c0811d01b62 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 6 Nov 2012 10:47:26 +0000 Subject: Allow slug_url_kwarg to be overidden by subclass. Fixes #373 --- rest_framework/fields.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 45c0cc8e..d834d9c5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -495,7 +495,7 @@ class HyperlinkedRelatedField(RelatedField): """ pk_url_kwarg = 'pk' slug_field = 'slug' - slug_url_kwarg = None # Defaults to same as `slug_field` + slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden default_read_only = False def __init__(self, *args, **kwargs): @@ -503,8 +503,11 @@ class HyperlinkedRelatedField(RelatedField): self.view_name = kwargs.pop('view_name') except: raise ValueError("Hyperlinked field requires 'view_name' kwarg") + self.slug_field = kwargs.pop('slug_field', self.slug_field) - self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', self.slug_field) + default_slug_kwarg = self.slug_url_kwarg or self.slug_field + self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) + self.format = kwargs.pop('format', None) super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From 0d2377c1480f3270a538de166be950ab4a0b09c8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 6 Nov 2012 10:55:58 +0000 Subject: Respect false value defaults on WritableField. Fixes #377. --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d834d9c5..65d7def5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -135,7 +135,7 @@ class WritableField(Field): self.error_messages = messages self.validators = self.default_validators + validators - self.default = default or self.default + self.default = default if default is not None else self.default self.blank = blank # Widgets are ony used for HTML forms. -- cgit v1.2.3 From d9be6140dcb375f4b6a0e24947494e0537e4b443 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 6 Nov 2012 11:01:53 +0000 Subject: More defensive coding. Refs: #348 --- rest_framework/fields.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 65d7def5..e194690d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -212,10 +212,10 @@ class ModelField(WritableField): super(ModelField, self).__init__(*args, **kwargs) def from_native(self, value): - try: - rel = self.model_field.rel + rel = getattr(self.model_field, "rel", None) + if rel is not None: return rel.to._meta.get_field(rel.field_name).to_python(value) - except: + else: return self.model_field.to_python(value) def field_to_native(self, obj, field_name): -- cgit v1.2.3 From 2c52a2581f690eca62a203d9b5344ac39b43ba74 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 6 Nov 2012 17:02:34 +0100 Subject: added slug support for HyperlinkedIdentityField --- rest_framework/fields.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index e194690d..1b1f57db 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -599,20 +599,50 @@ class HyperlinkedIdentityField(Field): """ Represents the instance, or a property on the instance, using hyperlinking. """ + pk_url_kwarg = 'pk' + slug_field = 'slug' + slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden def __init__(self, *args, **kwargs): # TODO: Make view_name mandatory, and have the # HyperlinkedModelSerializer set it on-the-fly self.view_name = kwargs.pop('view_name', None) self.format = kwargs.pop('format', None) + + self.slug_field = kwargs.pop('slug_field', self.slug_field) + default_slug_kwarg = self.slug_url_kwarg or self.slug_field + self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) + super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) def field_to_native(self, obj, field_name): request = self.context.get('request', None) format = self.format or self.context.get('format', None) view_name = self.view_name or self.parent.opts.view_name - view_kwargs = {'pk': obj.pk} - return reverse(view_name, kwargs=view_kwargs, request=request, format=format) + kwargs = {self.pk_url_kwarg: obj.pk} + try: + return reverse(view_name, kwargs=kwargs, request=request, format=format) + 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, format=format) + except: + pass + + kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} + try: + return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + except: + pass + + raise ValidationError('Could not resolve URL for field using view name "%s"', view_name) ##### Typed Fields ##### -- cgit v1.2.3 From 6d3bb67aa654d5f4c555746655a312000422d474 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 6 Nov 2012 17:11:52 +0000 Subject: Add pk_url_kwarg to hyperlinked fields --- rest_framework/fields.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1b1f57db..651793e2 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -506,6 +506,7 @@ class HyperlinkedRelatedField(RelatedField): self.slug_field = kwargs.pop('slug_field', self.slug_field) default_slug_kwarg = self.slug_url_kwarg or self.slug_field + self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) self.format = kwargs.pop('format', None) @@ -611,6 +612,7 @@ class HyperlinkedIdentityField(Field): self.slug_field = kwargs.pop('slug_field', self.slug_field) default_slug_kwarg = self.slug_url_kwarg or self.slug_field + self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) -- cgit v1.2.3 From f3c94acc4a1917c7c1899ef6bdbc67e25e8ede6e Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Wed, 7 Nov 2012 17:19:13 +0100 Subject: fixed typo --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 651793e2..a4e29a30 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -237,7 +237,7 @@ class RelatedField(WritableField): """ Base class for related model fields. - If not overridden, this represents a to-one relatinship, using the unicode + If not overridden, this represents a to-one relationship, using the unicode representation of the target. """ widget = widgets.Select -- cgit v1.2.3