diff options
Diffstat (limited to 'rest_framework/fields.py')
| -rw-r--r-- | rest_framework/fields.py | 78 |
1 files changed, 60 insertions, 18 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c0e527e5..1d6d760e 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): @@ -43,7 +44,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 @@ -113,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) @@ -132,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 @@ -197,7 +199,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: @@ -244,7 +246,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 +290,15 @@ class PrimaryKeyRelatedField(RelatedField): def to_native(self, pk): 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: + raise ValidationError('Invalid hyperlink - object does not exist.') + def field_to_native(self, obj, field_name): try: # Prefer obj.serializable_value for performance reasons @@ -331,14 +342,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 +362,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 @@ -364,6 +377,16 @@ 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 + value = urlparse(value).path + prefix = get_script_prefix() + if value.startswith(prefix): + value = '/' + value[len(prefix):] + try: match = resolve(value) except: @@ -377,7 +400,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 +413,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): @@ -405,13 +428,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 ##### @@ -448,6 +473,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 @@ -509,7 +544,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 +569,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 +609,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 +669,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 +685,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): |
