diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/exceptions.py | 74 | ||||
| -rw-r--r-- | rest_framework/fields.py | 2 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 83 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/horizontal/list_fieldset.html | 3 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/inline/list_fieldset.html | 1 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/vertical/list_fieldset.html | 1 | ||||
| -rw-r--r-- | rest_framework/validators.py | 14 | 
7 files changed, 125 insertions, 53 deletions
| diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 0b06d6e6..dbab6684 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -5,7 +5,11 @@ In addition Django's built in 403 and 404 exceptions are handled.  (`django.http.Http404` and `django.core.exceptions.PermissionDenied`)  """  from __future__ import unicode_literals + +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy  from rest_framework import status +from rest_framework.compat import force_text  import math @@ -15,10 +19,13 @@ class APIException(Exception):      Subclasses should provide `.status_code` and `.default_detail` properties.      """      status_code = status.HTTP_500_INTERNAL_SERVER_ERROR -    default_detail = 'A server error occured' +    default_detail = _('A server error occured')      def __init__(self, detail=None): -        self.detail = detail or self.default_detail +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail)      def __str__(self):          return self.detail @@ -31,6 +38,19 @@ class APIException(Exception):  # from rest_framework import serializers  # raise serializers.ValidationError('Value was invalid') +def force_text_recursive(data): +    if isinstance(data, list): +        return [ +            force_text_recursive(item) for item in data +        ] +    elif isinstance(data, dict): +        return dict([ +            (key, force_text_recursive(value)) +            for key, value in data.items() +        ]) +    return force_text(data) + +  class ValidationError(APIException):      status_code = status.HTTP_400_BAD_REQUEST @@ -39,7 +59,7 @@ class ValidationError(APIException):          # The details should always be coerced to a list if not already.          if not isinstance(detail, dict) and not isinstance(detail, list):              detail = [detail] -        self.detail = detail +        self.detail = force_text_recursive(detail)      def __str__(self):          return str(self.detail) @@ -47,59 +67,77 @@ class ValidationError(APIException):  class ParseError(APIException):      status_code = status.HTTP_400_BAD_REQUEST -    default_detail = 'Malformed request.' +    default_detail = _('Malformed request.')  class AuthenticationFailed(APIException):      status_code = status.HTTP_401_UNAUTHORIZED -    default_detail = 'Incorrect authentication credentials.' +    default_detail = _('Incorrect authentication credentials.')  class NotAuthenticated(APIException):      status_code = status.HTTP_401_UNAUTHORIZED -    default_detail = 'Authentication credentials were not provided.' +    default_detail = _('Authentication credentials were not provided.')  class PermissionDenied(APIException):      status_code = status.HTTP_403_FORBIDDEN -    default_detail = 'You do not have permission to perform this action.' +    default_detail = _('You do not have permission to perform this action.')  class MethodNotAllowed(APIException):      status_code = status.HTTP_405_METHOD_NOT_ALLOWED -    default_detail = "Method '%s' not allowed." +    default_detail = _("Method '%s' not allowed.")      def __init__(self, method, detail=None): -        self.detail = detail or (self.default_detail % method) +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail) % method  class NotAcceptable(APIException):      status_code = status.HTTP_406_NOT_ACCEPTABLE -    default_detail = "Could not satisfy the request Accept header" +    default_detail = _('Could not satisfy the request Accept header')      def __init__(self, detail=None, available_renderers=None): -        self.detail = detail or self.default_detail +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail)          self.available_renderers = available_renderers  class UnsupportedMediaType(APIException):      status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE -    default_detail = "Unsupported media type '%s' in request." +    default_detail = _("Unsupported media type '%s' in request.")      def __init__(self, media_type, detail=None): -        self.detail = detail or (self.default_detail % media_type) +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail) % media_type  class Throttled(APIException):      status_code = status.HTTP_429_TOO_MANY_REQUESTS -    default_detail = 'Request was throttled.' -    extra_detail = " Expected available in %d second%s." +    default_detail = _('Request was throttled.') +    extra_detail = ungettext_lazy( +        'Expected available in %(wait)d second.', +        'Expected available in %(wait)d seconds.', +        'wait' +    )      def __init__(self, wait=None, detail=None): +        if detail is not None: +            self.detail = force_text(detail) +        else: +            self.detail = force_text(self.default_detail) +          if wait is None: -            self.detail = detail or self.default_detail              self.wait = None          else: -            format = (detail or self.default_detail) + self.extra_detail -            self.detail = format % (wait, wait != 1 and 's' or '')              self.wait = math.ceil(wait) +            self.detail += ' ' + force_text( +                self.extra_detail % {'wait': self.wait} +            ) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 36afe7a9..bb43708d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -947,6 +947,8 @@ class ChoiceField(Field):              self.fail('invalid_choice', input=data)      def to_representation(self, value): +        if value in ('', None): +            return value          return self.choice_strings_to_values[six.text_type(value)] diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 84282cdb..2e34dbe7 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -720,49 +720,60 @@ class ModelSerializer(Serializer):          # Determine if we need any additional `HiddenField` or extra keyword          # arguments to deal with `unique_for` dates that are required to          # be in the input data in order to validate it. -        unique_fields = {} +        hidden_fields = {} +          for model_field_name, field_name in model_field_mapping.items():              try:                  model_field = model._meta.get_field(model_field_name)              except FieldDoesNotExist:                  continue -            # Deal with each of the `unique_for_*` cases. -            for date_field_name in ( +            # Include each of the `unique_for_*` field names. +            unique_constraint_names = set([                  model_field.unique_for_date,                  model_field.unique_for_month,                  model_field.unique_for_year -            ): -                if date_field_name is None: -                    continue - -                # Get the model field that is refered too. -                date_field = model._meta.get_field(date_field_name) - -                if date_field.auto_now_add: -                    default = CreateOnlyDefault(timezone.now) -                elif date_field.auto_now: -                    default = timezone.now -                elif date_field.has_default(): -                    default = model_field.default -                else: -                    default = empty - -                if date_field_name in model_field_mapping: -                    # The corresponding date field is present in the serializer -                    if date_field_name not in extra_kwargs: -                        extra_kwargs[date_field_name] = {} -                    if default is empty: -                        if 'required' not in extra_kwargs[date_field_name]: -                            extra_kwargs[date_field_name]['required'] = True -                    else: -                        if 'default' not in extra_kwargs[date_field_name]: -                            extra_kwargs[date_field_name]['default'] = default +            ]) +            unique_constraint_names -= set([None]) + +            # Include each of the `unique_together` field names, +            # so long as all the field names are included on the serializer. +            for parent_class in [model] + list(model._meta.parents.keys()): +                for unique_together_list in parent_class._meta.unique_together: +                    if set(fields).issuperset(set(unique_together_list)): +                        unique_constraint_names |= set(unique_together_list) + +        # Now we have all the field names that have uniqueness constraints +        # applied, we can add the extra 'required=...' or 'default=...' +        # arguments that are appropriate to these fields, or add a `HiddenField` for it. +        for unique_constraint_name in unique_constraint_names: +            # Get the model field that is refered too. +            unique_constraint_field = model._meta.get_field(unique_constraint_name) + +            if getattr(unique_constraint_field, 'auto_now_add', None): +                default = CreateOnlyDefault(timezone.now) +            elif getattr(unique_constraint_field, 'auto_now', None): +                default = timezone.now +            elif unique_constraint_field.has_default(): +                default = model_field.default +            else: +                default = empty + +            if unique_constraint_name in model_field_mapping: +                # The corresponding field is present in the serializer +                if unique_constraint_name not in extra_kwargs: +                    extra_kwargs[unique_constraint_name] = {} +                if default is empty: +                    if 'required' not in extra_kwargs[unique_constraint_name]: +                        extra_kwargs[unique_constraint_name]['required'] = True                  else: -                    # The corresponding date field is not present in the, -                    # serializer. We have a default to use for the date, so -                    # add in a hidden field that populates it. -                    unique_fields[date_field_name] = HiddenField(default=default) +                    if 'default' not in extra_kwargs[unique_constraint_name]: +                        extra_kwargs[unique_constraint_name]['default'] = default +            elif default is not empty: +                # The corresponding field is not present in the, +                # serializer. We have a default to use for it, so +                # add in a hidden field that populates it. +                hidden_fields[unique_constraint_name] = HiddenField(default=default)          # Now determine the fields that should be included on the serializer.          for field_name in fields: @@ -838,12 +849,16 @@ class ModelSerializer(Serializer):                      'validators', 'queryset'                  ]:                      kwargs.pop(attr, None) + +            if extras.get('default') and kwargs.get('required') is False: +                kwargs.pop('required') +              kwargs.update(extras)              # Create the serializer field.              ret[field_name] = field_cls(**kwargs) -        for field_name, field in unique_fields.items(): +        for field_name, field in hidden_fields.items():              ret[field_name] = field          return ret diff --git a/rest_framework/templates/rest_framework/horizontal/list_fieldset.html b/rest_framework/templates/rest_framework/horizontal/list_fieldset.html index a30514c6..a9ff04a6 100644 --- a/rest_framework/templates/rest_framework/horizontal/list_fieldset.html +++ b/rest_framework/templates/rest_framework/horizontal/list_fieldset.html @@ -5,9 +5,12 @@              <legend class="control-label col-sm-2 {% if style.hide_label %}sr-only{% endif %}" style="border-bottom: 0">{{ field.label }}</legend>          </div>      {% endif %} +    <!--      <ul>      {% for child in field.value %}          <li>TODO</li>      {% endfor %}      </ul> +    --> +    <p>Lists are not currently supported in HTML input.</p>  </fieldset> diff --git a/rest_framework/templates/rest_framework/inline/list_fieldset.html b/rest_framework/templates/rest_framework/inline/list_fieldset.html new file mode 100644 index 00000000..2ae56d7c --- /dev/null +++ b/rest_framework/templates/rest_framework/inline/list_fieldset.html @@ -0,0 +1 @@ +<span>Lists are not currently supported in HTML input.</span> diff --git a/rest_framework/templates/rest_framework/vertical/list_fieldset.html b/rest_framework/templates/rest_framework/vertical/list_fieldset.html index 74bbf448..1d86c7f2 100644 --- a/rest_framework/templates/rest_framework/vertical/list_fieldset.html +++ b/rest_framework/templates/rest_framework/vertical/list_fieldset.html @@ -4,4 +4,5 @@      {% for field_item in field.value.field_items.values() %}          {{ renderer.render_field(field_item, layout=layout) }}      {% endfor %} --> +    <p>Lists are not currently supported in HTML input.</p>  </fieldset> diff --git a/rest_framework/validators.py b/rest_framework/validators.py index fa4f1847..7ca4e6a9 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -93,6 +93,9 @@ class UniqueTogetherValidator:          The `UniqueTogetherValidator` always forces an implied 'required'          state on the fields it applies to.          """ +        if self.instance is not None: +            return +          missing = dict([              (field_name, self.missing_message)              for field_name in self.fields @@ -105,8 +108,17 @@ class UniqueTogetherValidator:          """          Filter the queryset to all instances matching the given attributes.          """ +        # If this is an update, then any unprovided field should +        # have it's value set based on the existing instance attribute. +        if self.instance is not None: +            for field_name in self.fields: +                if field_name not in attrs: +                    attrs[field_name] = getattr(self.instance, field_name) + +        # Determine the filter keyword arguments and filter the queryset.          filter_kwargs = dict([ -            (field_name, attrs[field_name]) for field_name in self.fields +            (field_name, attrs[field_name]) +            for field_name in self.fields          ])          return queryset.filter(**filter_kwargs) | 
