mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-10 19:56:59 +03:00
First pass
This commit is contained in:
parent
371d30aa87
commit
4ac4676a40
File diff suppressed because it is too large
Load Diff
|
@ -79,18 +79,16 @@ class GenericAPIView(views.APIView):
|
|||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, instance=None, data=None, files=None, many=False,
|
||||
partial=False, allow_add_remove=False):
|
||||
def get_serializer(self, instance=None, data=None, many=False, partial=False):
|
||||
"""
|
||||
Return the serializer instance that should be used for validating and
|
||||
deserializing input, and for serializing output.
|
||||
"""
|
||||
serializer_class = self.get_serializer_class()
|
||||
context = self.get_serializer_context()
|
||||
return serializer_class(instance, data=data, files=files,
|
||||
many=many, partial=partial,
|
||||
allow_add_remove=allow_add_remove,
|
||||
context=context)
|
||||
return serializer_class(
|
||||
instance, data=data, many=many, partial=partial, context=context
|
||||
)
|
||||
|
||||
def get_pagination_serializer(self, page):
|
||||
"""
|
||||
|
|
|
@ -36,12 +36,10 @@ class CreateModelMixin(object):
|
|||
Create a model instance.
|
||||
"""
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
|
||||
serializer = self.get_serializer(data=request.DATA)
|
||||
|
||||
if serializer.is_valid():
|
||||
self.pre_save(serializer.object)
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
self.object = serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
|
@ -90,26 +88,20 @@ class UpdateModelMixin(object):
|
|||
partial = kwargs.pop('partial', False)
|
||||
self.object = self.get_object_or_none()
|
||||
|
||||
serializer = self.get_serializer(self.object, data=request.DATA,
|
||||
files=request.FILES, partial=partial)
|
||||
serializer = self.get_serializer(self.object, data=request.DATA, partial=partial)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
self.pre_save(serializer.object)
|
||||
except ValidationError as err:
|
||||
# full_clean on model instance may be called in pre_save,
|
||||
# so we have to handle eventual errors.
|
||||
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
|
||||
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
|
||||
lookup_value = self.kwargs[lookup_url_kwarg]
|
||||
extras = {self.lookup_field: lookup_value}
|
||||
|
||||
if self.object is None:
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
self.object = serializer.save(extras=extras)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
self.object = serializer.save(force_update=True)
|
||||
self.post_save(self.object, created=False)
|
||||
self.object = serializer.save(extras=extras)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
|
|
|
@ -48,17 +48,17 @@ class DefaultObjectSerializer(serializers.Field):
|
|||
super(DefaultObjectSerializer, self).__init__(source=source)
|
||||
|
||||
|
||||
class PaginationSerializerOptions(serializers.SerializerOptions):
|
||||
"""
|
||||
An object that stores the options that may be provided to a
|
||||
pagination serializer by using the inner `Meta` class.
|
||||
# class PaginationSerializerOptions(serializers.SerializerOptions):
|
||||
# """
|
||||
# An object that stores the options that may be provided to a
|
||||
# pagination serializer by using the inner `Meta` class.
|
||||
|
||||
Accessible on the instance as `serializer.opts`.
|
||||
"""
|
||||
def __init__(self, meta):
|
||||
super(PaginationSerializerOptions, self).__init__(meta)
|
||||
self.object_serializer_class = getattr(meta, 'object_serializer_class',
|
||||
DefaultObjectSerializer)
|
||||
# Accessible on the instance as `serializer.opts`.
|
||||
# """
|
||||
# def __init__(self, meta):
|
||||
# super(PaginationSerializerOptions, self).__init__(meta)
|
||||
# self.object_serializer_class = getattr(meta, 'object_serializer_class',
|
||||
# DefaultObjectSerializer)
|
||||
|
||||
|
||||
class BasePaginationSerializer(serializers.Serializer):
|
||||
|
@ -66,7 +66,7 @@ class BasePaginationSerializer(serializers.Serializer):
|
|||
A base class for pagination serializers to inherit from,
|
||||
to make implementing custom serializers more easy.
|
||||
"""
|
||||
_options_class = PaginationSerializerOptions
|
||||
# _options_class = PaginationSerializerOptions
|
||||
results_field = 'results'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -1,486 +0,0 @@
|
|||
"""
|
||||
Serializer fields that deal with relationships.
|
||||
|
||||
These fields allow you to specify the style that should be used to represent
|
||||
model relationships, including hyperlinks, primary keys, or slugs.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch
|
||||
from django import forms
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||
from django.forms import widgets
|
||||
from django.forms.models import ModelChoiceIterator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.fields import Field, WritableField, get_component, is_simple_callable
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.compat import urlparse
|
||||
from rest_framework.compat import smart_text
|
||||
|
||||
|
||||
# Relational fields
|
||||
|
||||
# Not actually Writable, but subclasses may need to be.
|
||||
class RelatedField(WritableField):
|
||||
"""
|
||||
Base class for related model fields.
|
||||
|
||||
This represents a relationship using the unicode representation of the target.
|
||||
"""
|
||||
widget = widgets.Select
|
||||
many_widget = widgets.SelectMultiple
|
||||
form_field_class = forms.ChoiceField
|
||||
many_form_field_class = forms.MultipleChoiceField
|
||||
null_values = (None, '', 'None')
|
||||
|
||||
cache_choices = False
|
||||
empty_label = None
|
||||
read_only = True
|
||||
many = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
queryset = kwargs.pop('queryset', None)
|
||||
self.many = kwargs.pop('many', self.many)
|
||||
if self.many:
|
||||
self.widget = self.many_widget
|
||||
self.form_field_class = self.many_form_field_class
|
||||
|
||||
kwargs['read_only'] = kwargs.pop('read_only', self.read_only)
|
||||
super(RelatedField, self).__init__(*args, **kwargs)
|
||||
|
||||
if not self.required:
|
||||
# Accessed in ModelChoiceIterator django/forms/models.py:1034
|
||||
# If set adds empty choice.
|
||||
self.empty_label = BLANK_CHOICE_DASH[0][1]
|
||||
|
||||
self.queryset = queryset
|
||||
|
||||
def initialize(self, parent, field_name):
|
||||
super(RelatedField, self).initialize(parent, field_name)
|
||||
if self.queryset is None and not self.read_only:
|
||||
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()
|
||||
|
||||
# We need this stuff to make form choices work...
|
||||
|
||||
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_text(obj)
|
||||
ident = smart_text(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)
|
||||
|
||||
# Default value handling
|
||||
|
||||
def get_default_value(self):
|
||||
default = super(RelatedField, self).get_default_value()
|
||||
if self.many and default is None:
|
||||
return []
|
||||
return default
|
||||
|
||||
# Regular serializer stuff...
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
try:
|
||||
if self.source == '*':
|
||||
return self.to_native(obj)
|
||||
|
||||
source = self.source or field_name
|
||||
value = obj
|
||||
|
||||
for component in source.split('.'):
|
||||
if value is None:
|
||||
break
|
||||
value = get_component(value, component)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if self.many:
|
||||
if is_simple_callable(getattr(value, 'all', None)):
|
||||
return [self.to_native(item) for item in value.all()]
|
||||
else:
|
||||
# Also support non-queryset iterables.
|
||||
# This allows us to also support plain lists of related items.
|
||||
return [self.to_native(item) for item in value]
|
||||
return self.to_native(value)
|
||||
|
||||
def field_from_native(self, data, files, field_name, into):
|
||||
if self.read_only:
|
||||
return
|
||||
|
||||
try:
|
||||
if self.many:
|
||||
try:
|
||||
# Form data
|
||||
value = data.getlist(field_name)
|
||||
if value == [''] or value == []:
|
||||
raise KeyError
|
||||
except AttributeError:
|
||||
# Non-form data
|
||||
value = data[field_name]
|
||||
else:
|
||||
value = data[field_name]
|
||||
except KeyError:
|
||||
if self.partial:
|
||||
return
|
||||
value = self.get_default_value()
|
||||
|
||||
if value in self.null_values:
|
||||
if self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
into[(self.source or field_name)] = None
|
||||
elif self.many:
|
||||
into[(self.source or field_name)] = [self.from_native(item) for item in value]
|
||||
else:
|
||||
into[(self.source or field_name)] = self.from_native(value)
|
||||
|
||||
|
||||
# PrimaryKey relationships
|
||||
|
||||
class PrimaryKeyRelatedField(RelatedField):
|
||||
"""
|
||||
Represents a relationship as a pk value.
|
||||
"""
|
||||
read_only = False
|
||||
|
||||
default_error_messages = {
|
||||
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
|
||||
'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
|
||||
}
|
||||
|
||||
# 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_text(obj)
|
||||
ident = smart_text(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
|
||||
|
||||
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 = self.error_messages['does_not_exist'] % smart_text(data)
|
||||
raise ValidationError(msg)
|
||||
except (TypeError, ValueError):
|
||||
received = type(data).__name__
|
||||
msg = self.error_messages['incorrect_type'] % received
|
||||
raise ValidationError(msg)
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
if self.many:
|
||||
# To-many relationship
|
||||
|
||||
queryset = None
|
||||
if not self.source:
|
||||
# Prefer obj.serializable_value for performance reasons
|
||||
try:
|
||||
queryset = obj.serializable_value(field_name)
|
||||
except AttributeError:
|
||||
pass
|
||||
if queryset is None:
|
||||
# RelatedManager (reverse relationship)
|
||||
source = self.source or field_name
|
||||
queryset = obj
|
||||
for component in source.split('.'):
|
||||
if queryset is None:
|
||||
return []
|
||||
queryset = get_component(queryset, component)
|
||||
|
||||
# Forward relationship
|
||||
if is_simple_callable(getattr(queryset, 'all', None)):
|
||||
return [self.to_native(item.pk) for item in queryset.all()]
|
||||
else:
|
||||
# Also support non-queryset iterables.
|
||||
# This allows us to also support plain lists of related items.
|
||||
return [self.to_native(item.pk) for item in queryset]
|
||||
|
||||
# To-one relationship
|
||||
try:
|
||||
# Prefer obj.serializable_value for performance reasons
|
||||
pk = obj.serializable_value(self.source or field_name)
|
||||
except AttributeError:
|
||||
# RelatedObject (reverse relationship)
|
||||
try:
|
||||
pk = getattr(obj, self.source or field_name).pk
|
||||
except (ObjectDoesNotExist, AttributeError):
|
||||
return None
|
||||
|
||||
# Forward relationship
|
||||
return self.to_native(pk)
|
||||
|
||||
|
||||
# Slug relationships
|
||||
|
||||
class SlugRelatedField(RelatedField):
|
||||
"""
|
||||
Represents a relationship using a unique field on the target.
|
||||
"""
|
||||
read_only = False
|
||||
|
||||
default_error_messages = {
|
||||
'does_not_exist': _("Object with %s=%s does not exist."),
|
||||
'invalid': _('Invalid value.'),
|
||||
}
|
||||
|
||||
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):
|
||||
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:
|
||||
raise ValidationError(self.error_messages['does_not_exist'] %
|
||||
(self.slug_field, smart_text(data)))
|
||||
except (TypeError, ValueError):
|
||||
msg = self.error_messages['invalid']
|
||||
raise ValidationError(msg)
|
||||
|
||||
|
||||
# Hyperlinked relationships
|
||||
|
||||
class HyperlinkedRelatedField(RelatedField):
|
||||
"""
|
||||
Represents a relationship using hyperlinking.
|
||||
"""
|
||||
read_only = False
|
||||
lookup_field = 'pk'
|
||||
|
||||
default_error_messages = {
|
||||
'no_match': _('Invalid hyperlink - No URL match'),
|
||||
'incorrect_match': _('Invalid hyperlink - Incorrect URL match'),
|
||||
'configuration_error': _('Invalid hyperlink due to configuration error'),
|
||||
'does_not_exist': _("Invalid hyperlink - object does not exist."),
|
||||
'incorrect_type': _('Incorrect type. Expected url string, received %s.'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.view_name = kwargs.pop('view_name')
|
||||
except KeyError:
|
||||
raise ValueError("Hyperlinked field requires 'view_name' kwarg")
|
||||
|
||||
self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
|
||||
self.format = kwargs.pop('format', None)
|
||||
|
||||
super(HyperlinkedRelatedField, self).__init__(*args, **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.
|
||||
"""
|
||||
lookup_field = getattr(obj, self.lookup_field)
|
||||
kwargs = {self.lookup_field: lookup_field}
|
||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
||||
|
||||
def get_object(self, queryset, view_name, view_args, view_kwargs):
|
||||
"""
|
||||
Return the object corresponding to a matched URL.
|
||||
|
||||
Takes the matched URL conf arguments, and the queryset, and should
|
||||
return an object instance, or raise an `ObjectDoesNotExist` exception.
|
||||
"""
|
||||
lookup_value = view_kwargs[self.lookup_field]
|
||||
filter_kwargs = {self.lookup_field: lookup_value}
|
||||
return queryset.get(**filter_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)
|
||||
|
||||
assert request is not None, (
|
||||
"`HyperlinkedRelatedField` requires the request in the serializer "
|
||||
"context. Add `context={'request': request}` when instantiating "
|
||||
"the serializer."
|
||||
)
|
||||
|
||||
# If the object has not yet been saved then we cannot hyperlink to it.
|
||||
if getattr(obj, 'pk', None) is None:
|
||||
return
|
||||
|
||||
# Return the hyperlink, or error if incorrectly configured.
|
||||
try:
|
||||
return self.get_url(obj, view_name, request, format)
|
||||
except NoReverseMatch:
|
||||
msg = (
|
||||
'Could not resolve URL for hyperlinked relationship using '
|
||||
'view name "%s". You may have failed to include the related '
|
||||
'model in your API, or incorrectly configured the '
|
||||
'`lookup_field` attribute on this field.'
|
||||
)
|
||||
raise Exception(msg % view_name)
|
||||
|
||||
def from_native(self, value):
|
||||
# Convert URL -> model instance pk
|
||||
# TODO: Use values_list
|
||||
queryset = self.queryset
|
||||
if queryset is None:
|
||||
raise Exception('Writable related fields must include a `queryset` argument')
|
||||
|
||||
try:
|
||||
http_prefix = value.startswith(('http:', 'https:'))
|
||||
except AttributeError:
|
||||
msg = self.error_messages['incorrect_type']
|
||||
raise ValidationError(msg % type(value).__name__)
|
||||
|
||||
if http_prefix:
|
||||
# If needed convert absolute URLs to relative path
|
||||
value = urlparse.urlparse(value).path
|
||||
prefix = get_script_prefix()
|
||||
if value.startswith(prefix):
|
||||
value = '/' + value[len(prefix):]
|
||||
|
||||
try:
|
||||
match = resolve(value)
|
||||
except Exception:
|
||||
raise ValidationError(self.error_messages['no_match'])
|
||||
|
||||
if match.view_name != self.view_name:
|
||||
raise ValidationError(self.error_messages['incorrect_match'])
|
||||
|
||||
try:
|
||||
return self.get_object(queryset, match.view_name,
|
||||
match.args, match.kwargs)
|
||||
except (ObjectDoesNotExist, TypeError, ValueError):
|
||||
raise ValidationError(self.error_messages['does_not_exist'])
|
||||
|
||||
|
||||
class HyperlinkedIdentityField(Field):
|
||||
"""
|
||||
Represents the instance, or a property on the instance, using hyperlinking.
|
||||
"""
|
||||
lookup_field = 'pk'
|
||||
read_only = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.view_name = kwargs.pop('view_name')
|
||||
except KeyError:
|
||||
msg = "HyperlinkedIdentityField requires 'view_name' argument"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.format = kwargs.pop('format', None)
|
||||
lookup_field = kwargs.pop('lookup_field', None)
|
||||
self.lookup_field = lookup_field or self.lookup_field
|
||||
|
||||
super(HyperlinkedIdentityField, self).__init__(*args, **kwargs)
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
request = self.context.get('request', None)
|
||||
format = self.context.get('format', None)
|
||||
view_name = self.view_name
|
||||
|
||||
assert request is not None, (
|
||||
"`HyperlinkedIdentityField` requires the request in the serializer"
|
||||
" context. Add `context={'request': request}` when instantiating "
|
||||
"the serializer."
|
||||
)
|
||||
|
||||
# By default use whatever format is given for the current context
|
||||
# unless the target is a different type to the source.
|
||||
#
|
||||
# Eg. Consider a HyperlinkedIdentityField pointing from a json
|
||||
# representation to an html property of that representation...
|
||||
#
|
||||
# '/snippets/1/' should link to '/snippets/1/highlight/'
|
||||
# ...but...
|
||||
# '/snippets/1/.json' should link to '/snippets/1/highlight/.html'
|
||||
if format and self.format and self.format != format:
|
||||
format = self.format
|
||||
|
||||
# Return the hyperlink, or error if incorrectly configured.
|
||||
try:
|
||||
return self.get_url(obj, view_name, request, format)
|
||||
except NoReverseMatch:
|
||||
msg = (
|
||||
'Could not resolve URL for hyperlinked relationship using '
|
||||
'view name "%s". You may have failed to include the related '
|
||||
'model in your API, or incorrectly configured the '
|
||||
'`lookup_field` attribute on this field.'
|
||||
)
|
||||
raise Exception(msg % view_name)
|
||||
|
||||
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.
|
||||
"""
|
||||
lookup_field = getattr(obj, self.lookup_field, None)
|
||||
kwargs = {self.lookup_field: lookup_field}
|
||||
|
||||
# Handle unsaved object case
|
||||
if lookup_field is None:
|
||||
return None
|
||||
|
||||
return reverse(view_name, kwargs=kwargs, request=request, format=format)
|
|
@ -458,7 +458,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
):
|
||||
return
|
||||
|
||||
serializer = view.get_serializer(instance=obj, data=data, files=files)
|
||||
serializer = view.get_serializer(instance=obj, data=data)
|
||||
serializer.is_valid()
|
||||
data = serializer.data
|
||||
|
||||
|
@ -579,10 +579,10 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
|
||||
'response_headers': response_headers,
|
||||
|
||||
'put_form': self.get_rendered_html_form(view, 'PUT', request),
|
||||
'post_form': self.get_rendered_html_form(view, 'POST', request),
|
||||
'delete_form': self.get_rendered_html_form(view, 'DELETE', request),
|
||||
'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),
|
||||
#'put_form': self.get_rendered_html_form(view, 'PUT', request),
|
||||
#'post_form': self.get_rendered_html_form(view, 'POST', request),
|
||||
#'delete_form': self.get_rendered_html_form(view, 'DELETE', request),
|
||||
#'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),
|
||||
|
||||
'raw_data_put_form': raw_data_put_form,
|
||||
'raw_data_post_form': raw_data_post_form,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@ from django.db.models.query import QuerySet
|
|||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.functional import Promise
|
||||
from rest_framework.compat import force_text
|
||||
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||
# from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
|
||||
import datetime
|
||||
import decimal
|
||||
import types
|
||||
|
@ -106,14 +106,14 @@ else:
|
|||
SortedDict,
|
||||
yaml.representer.SafeRepresenter.represent_dict
|
||||
)
|
||||
SafeDumper.add_representer(
|
||||
DictWithMetadata,
|
||||
yaml.representer.SafeRepresenter.represent_dict
|
||||
)
|
||||
SafeDumper.add_representer(
|
||||
SortedDictWithMetadata,
|
||||
yaml.representer.SafeRepresenter.represent_dict
|
||||
)
|
||||
# SafeDumper.add_representer(
|
||||
# DictWithMetadata,
|
||||
# yaml.representer.SafeRepresenter.represent_dict
|
||||
# )
|
||||
# SafeDumper.add_representer(
|
||||
# SortedDictWithMetadata,
|
||||
# yaml.representer.SafeRepresenter.represent_dict
|
||||
# )
|
||||
SafeDumper.add_representer(
|
||||
types.GeneratorType,
|
||||
yaml.representer.SafeRepresenter.represent_list
|
||||
|
|
86
rest_framework/utils/html.py
Normal file
86
rest_framework/utils/html.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Helpers for dealing with HTML input.
|
||||
"""
|
||||
|
||||
def is_html_input(dictionary):
|
||||
# MultiDict type datastructures are used to represent HTML form input,
|
||||
# which may have more than one value for each key.
|
||||
return hasattr(dictionary, 'getlist')
|
||||
|
||||
|
||||
def parse_html_list(dictionary, prefix=''):
|
||||
"""
|
||||
Used to suport list values in HTML forms.
|
||||
Supports lists of primitives and/or dictionaries.
|
||||
|
||||
* List of primitives.
|
||||
|
||||
{
|
||||
'[0]': 'abc',
|
||||
'[1]': 'def',
|
||||
'[2]': 'hij'
|
||||
}
|
||||
-->
|
||||
[
|
||||
'abc',
|
||||
'def',
|
||||
'hij'
|
||||
]
|
||||
|
||||
* List of dictionaries.
|
||||
|
||||
{
|
||||
'[0]foo': 'abc',
|
||||
'[0]bar': 'def',
|
||||
'[1]foo': 'hij',
|
||||
'[2]bar': 'klm',
|
||||
}
|
||||
-->
|
||||
[
|
||||
{'foo': 'abc', 'bar': 'def'},
|
||||
{'foo': 'hij', 'bar': 'klm'}
|
||||
]
|
||||
"""
|
||||
Dict = type(dictionary)
|
||||
ret = {}
|
||||
regex = re.compile(r'^%s\[([0-9]+)\](.*)$' % re.escape(prefix))
|
||||
for field, value in dictionary.items():
|
||||
match = regex.match(field)
|
||||
if not match:
|
||||
continue
|
||||
index, key = match.groups()
|
||||
index = int(index)
|
||||
if not key:
|
||||
ret[index] = value
|
||||
elif isinstance(ret.get(index), dict):
|
||||
ret[index][key] = value
|
||||
else:
|
||||
ret[index] = Dict({key: value})
|
||||
return [ret[item] for item in sorted(ret.keys())]
|
||||
|
||||
|
||||
def parse_html_dict(dictionary, prefix):
|
||||
"""
|
||||
Used to support dictionary values in HTML forms.
|
||||
|
||||
{
|
||||
'profile.username': 'example',
|
||||
'profile.email': 'example@example.com',
|
||||
}
|
||||
-->
|
||||
{
|
||||
'profile': {
|
||||
'username': 'example,
|
||||
'email': 'example@example.com'
|
||||
}
|
||||
}
|
||||
"""
|
||||
ret = {}
|
||||
regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
|
||||
for field, value in dictionary.items():
|
||||
match = regex.match(field)
|
||||
if not match:
|
||||
continue
|
||||
key = match.groups()[0]
|
||||
ret[key] = value
|
||||
return ret
|
Loading…
Reference in New Issue
Block a user