mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-22 14:32:55 +03:00
Getting more resourceful - read, create, update and delete methods on ModelMixin
This commit is contained in:
parent
d69581e2af
commit
e8126c3a91
|
@ -9,9 +9,8 @@ from django.db.models.fields.related import ForeignKey
|
|||
from django.http import HttpResponse
|
||||
|
||||
from djangorestframework import status
|
||||
from djangorestframework.renderers import BaseRenderer
|
||||
from djangorestframework.resources import Resource, FormResource, ModelResource
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
|
||||
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
|
||||
|
||||
|
@ -27,11 +26,7 @@ __all__ = (
|
|||
# Reverse URL lookup behavior
|
||||
'InstanceMixin',
|
||||
# Model behavior mixins
|
||||
'ReadModelMixin',
|
||||
'CreateModelMixin',
|
||||
'UpdateModelMixin',
|
||||
'DeleteModelMixin',
|
||||
'ListModelMixin'
|
||||
'ModelMixin',
|
||||
)
|
||||
|
||||
|
||||
|
@ -267,25 +262,32 @@ class ResponseMixin(object):
|
|||
|
||||
def _determine_renderer(self, request):
|
||||
"""
|
||||
Determines the appropriate renderer for the output, given the client's 'Accept' header,
|
||||
and the :attr:`renderers` set on this class.
|
||||
Determines the appropriate renderer for the output, given the client's
|
||||
'Accept' header, and the :attr:`renderers` set on this class.
|
||||
|
||||
Returns a 2-tuple of `(renderer, media_type)`
|
||||
|
||||
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
See: RFC 2616, Section 14
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
"""
|
||||
|
||||
if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
|
||||
if (self._ACCEPT_QUERY_PARAM and
|
||||
request.GET.get(self._ACCEPT_QUERY_PARAM, None)):
|
||||
# Use _accept parameter override
|
||||
accept_list = [request.GET.get(self._ACCEPT_QUERY_PARAM)]
|
||||
|
||||
elif (self._IGNORE_IE_ACCEPT_HEADER and
|
||||
request.META.has_key('HTTP_USER_AGENT') and
|
||||
'HTTP_USER_AGENT' in request.META and
|
||||
MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT'])):
|
||||
# Ignore MSIE's broken accept behavior and do something sensible instead
|
||||
# Ignore MSIE's broken accept behavior and do something sensible
|
||||
# instead.
|
||||
accept_list = ['text/html', '*/*']
|
||||
elif request.META.has_key('HTTP_ACCEPT'):
|
||||
|
||||
elif 'HTTP_USER_AGENT' in request.META:
|
||||
# Use standard HTTP Accept negotiation
|
||||
accept_list = [token.strip() for token in request.META["HTTP_ACCEPT"].split(',')]
|
||||
accept_list = [token.strip() for token in
|
||||
request.META["HTTP_ACCEPT"].split(',')]
|
||||
|
||||
else:
|
||||
# No accept header specified
|
||||
accept_list = ['*/*']
|
||||
|
@ -481,48 +483,86 @@ class InstanceMixin(object):
|
|||
|
||||
########## Model Mixins ##########
|
||||
|
||||
class ReadModelMixin(object):
|
||||
"""
|
||||
Behavior to read a `model` instance on GET requests
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
model = self.resource.model
|
||||
class ModelMixin(object):
|
||||
def get_model(self):
|
||||
"""
|
||||
Return the model class for this view.
|
||||
"""
|
||||
return getattr(self, 'model', self.resource.model)
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return the queryset that should be used when retrieving or listing
|
||||
instances.
|
||||
"""
|
||||
return getattr(self, 'queryset',
|
||||
getattr(self.resource, 'queryset',
|
||||
self._get_model().objects.all()))
|
||||
|
||||
def get_ordering(self):
|
||||
"""
|
||||
Return the ordering that should be used when listing instances.
|
||||
"""
|
||||
return getattr(self, 'ordering',
|
||||
getattr(self.resource, 'ordering',
|
||||
None))
|
||||
|
||||
def get_instance(self, *args, **kwargs):
|
||||
"""
|
||||
Return a model instance or None.
|
||||
"""
|
||||
model = self.get_model()
|
||||
queryset = self.get_queryset()
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
try:
|
||||
# If we have any positional args then assume the last
|
||||
# represents the primary key. Otherwise assume the named kwargs
|
||||
# uniquely identify the instance.
|
||||
if args:
|
||||
# If we have any none kwargs then assume the last represents the primrary key
|
||||
self.model_instance = model.objects.get(pk=args[-1], **kwargs)
|
||||
return queryset.get(pk=args[-1], **kwargs)
|
||||
else:
|
||||
# Otherwise assume the kwargs uniquely identify the model
|
||||
filtered_keywords = kwargs.copy()
|
||||
if BaseRenderer._FORMAT_QUERY_PARAM in filtered_keywords:
|
||||
del filtered_keywords[BaseRenderer._FORMAT_QUERY_PARAM]
|
||||
self.model_instance = model.objects.get(**filtered_keywords)
|
||||
return queryset.get(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||
return None
|
||||
|
||||
return self.model_instance
|
||||
def read(self, request, *args, **kwargs):
|
||||
instance = self.get_instance(*args, **kwargs)
|
||||
return instance
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return a model instance.
|
||||
"""
|
||||
instance = self.get_instance(*args, **kwargs)
|
||||
|
||||
class CreateModelMixin(object):
|
||||
"""
|
||||
Behavior to create a `model` instance on POST requests
|
||||
"""
|
||||
def post(self, request, *args, **kwargs):
|
||||
model = self.resource.model
|
||||
if instance:
|
||||
for (key, val) in self.CONTENT.items():
|
||||
setattr(instance, key, val)
|
||||
else:
|
||||
instance = self.get_model()(**self.CONTENT)
|
||||
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return a model instance.
|
||||
"""
|
||||
model = self._get_model()
|
||||
|
||||
# Copy the dict to keep self.CONTENT intact
|
||||
content = dict(self.CONTENT)
|
||||
m2m_data = {}
|
||||
|
||||
for field in model._meta.fields:
|
||||
if isinstance(field, ForeignKey) and kwargs.has_key(field.name):
|
||||
if isinstance(field, ForeignKey) and field.name in kwargs:
|
||||
# translate 'related_field' kwargs into 'related_field_id'
|
||||
kwargs[field.name + '_id'] = kwargs[field.name]
|
||||
del kwargs[field.name]
|
||||
|
||||
for field in model._meta.many_to_many:
|
||||
if content.has_key(field.name):
|
||||
if field.name in content:
|
||||
m2m_data[field.name] = (
|
||||
field.m2m_reverse_field_name(), content[field.name]
|
||||
)
|
||||
|
@ -549,90 +589,30 @@ class CreateModelMixin(object):
|
|||
data[m2m_data[fieldname][0]] = related_item
|
||||
manager.through(**data).save()
|
||||
|
||||
headers = {}
|
||||
if hasattr(instance, 'get_absolute_url'):
|
||||
headers['Location'] = self.resource(self).url(instance)
|
||||
return Response(status.HTTP_201_CREATED, instance, headers)
|
||||
return instance
|
||||
|
||||
|
||||
class UpdateModelMixin(object):
|
||||
"""
|
||||
Behavior to update a `model` instance on PUT requests
|
||||
"""
|
||||
def put(self, request, *args, **kwargs):
|
||||
model = self.resource.model
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return a model instance or None.
|
||||
"""
|
||||
instance = self.get_instance(*args, **kwargs)
|
||||
|
||||
# TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url
|
||||
try:
|
||||
if args:
|
||||
# If we have any none kwargs then assume the last represents the primary key
|
||||
self.model_instance = model.objects.get(pk=args[-1], **kwargs)
|
||||
else:
|
||||
# Otherwise assume the kwargs uniquely identify the model
|
||||
self.model_instance = model.objects.get(**kwargs)
|
||||
if instance:
|
||||
instance.delete()
|
||||
|
||||
for (key, val) in self.CONTENT.items():
|
||||
setattr(self.model_instance, key, val)
|
||||
except model.DoesNotExist:
|
||||
self.model_instance = model(**self.CONTENT)
|
||||
self.model_instance.save()
|
||||
|
||||
self.model_instance.save()
|
||||
return self.model_instance
|
||||
return instance
|
||||
|
||||
|
||||
class DeleteModelMixin(object):
|
||||
"""
|
||||
Behavior to delete a `model` instance on DELETE requests
|
||||
"""
|
||||
def delete(self, request, *args, **kwargs):
|
||||
model = self.resource.model
|
||||
|
||||
try:
|
||||
if args:
|
||||
# If we have any none kwargs then assume the last represents the primrary key
|
||||
instance = model.objects.get(pk=args[-1], **kwargs)
|
||||
else:
|
||||
# Otherwise assume the kwargs uniquely identify the model
|
||||
instance = model.objects.get(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
||||
|
||||
instance.delete()
|
||||
return
|
||||
|
||||
|
||||
class ListModelMixin(object):
|
||||
"""
|
||||
Behavior to list a set of `model` instances on GET requests
|
||||
"""
|
||||
|
||||
# NB. Not obvious to me if it would be better to set this on the resource?
|
||||
#
|
||||
# Presumably it's more useful to have on the view, because that way you can
|
||||
# have multiple views across different querysets mapping to the same resource.
|
||||
#
|
||||
# Perhaps it ought to be:
|
||||
#
|
||||
# 1) View.queryset
|
||||
# 2) if None fall back to Resource.queryset
|
||||
# 3) if None fall back to Resource.model.objects.all()
|
||||
#
|
||||
# Any feedback welcomed.
|
||||
queryset = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
model = self.resource.model
|
||||
|
||||
queryset = self.queryset if self.queryset is not None else model.objects.all()
|
||||
|
||||
if hasattr(self, 'resource'):
|
||||
ordering = getattr(self.resource, 'ordering', None)
|
||||
else:
|
||||
ordering = None
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return a list of instances.
|
||||
"""
|
||||
queryset = self.get_queryset()
|
||||
ordering = self.get_ordering()
|
||||
|
||||
if ordering:
|
||||
args = as_tuple(ordering)
|
||||
assert(hasattr(ordering, '__iter__'))
|
||||
queryset = queryset.order_by(*args)
|
||||
return queryset.filter(**kwargs)
|
||||
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
from django import forms
|
||||
from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.db.models.fields.related import RelatedField
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.serializer import Serializer, _SkipField
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
||||
import decimal
|
||||
import inspect
|
||||
import re
|
||||
|
||||
|
||||
|
||||
|
||||
class BaseResource(Serializer):
|
||||
"""
|
||||
Base class for all Resource classes, which simply defines the interface they provide.
|
||||
Base class for all Resource classes, which simply defines the interface
|
||||
they provide.
|
||||
"""
|
||||
fields = None
|
||||
include = None
|
||||
|
@ -31,7 +23,8 @@ class BaseResource(Serializer):
|
|||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given the request content return the cleaned, validated content.
|
||||
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Typically raises a :exc:`response.ErrorResponse` with status code 400
|
||||
(Bad Request) on failure.
|
||||
"""
|
||||
return data
|
||||
|
||||
|
@ -45,18 +38,20 @@ class BaseResource(Serializer):
|
|||
class Resource(BaseResource):
|
||||
"""
|
||||
A Resource determines how a python object maps to some serializable data.
|
||||
Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
|
||||
Objects that a resource can act on include plain Python object instances,
|
||||
Django Models, and Django QuerySets.
|
||||
"""
|
||||
|
||||
# The model attribute refers to the Django Model which this Resource maps to.
|
||||
# (The Model's class, rather than an instance of the Model)
|
||||
# The model attribute refers to the Django Model which this Resource maps
|
||||
# to. (The Model's class, rather than an instance of the Model)
|
||||
model = None
|
||||
|
||||
# By default the set of returned fields will be the set of:
|
||||
#
|
||||
# 0. All the fields on the model, excluding 'id'.
|
||||
# 1. All the properties on the model.
|
||||
# 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
|
||||
# 2. The absolute_url of the model, if a get_absolute_url method exists for
|
||||
# the model.
|
||||
#
|
||||
# If you wish to override this behaviour,
|
||||
# you should explicitly set the fields attribute on your class.
|
||||
|
@ -66,42 +61,50 @@ class Resource(BaseResource):
|
|||
class FormResource(Resource):
|
||||
"""
|
||||
Resource class that uses forms for validation.
|
||||
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
|
||||
Also provides a :meth:`get_bound_form` method which may be used by some
|
||||
renderers.
|
||||
|
||||
On calling :meth:`validate_request` this validator may set a :attr:`bound_form_instance` attribute on the
|
||||
view, which may be used by some renderers.
|
||||
On calling :meth:`validate_request` this validator may set a
|
||||
:attr:`bound_form_instance` attribute on the view, which may be used by
|
||||
some renderers.
|
||||
"""
|
||||
|
||||
form = None
|
||||
"""
|
||||
The :class:`Form` class that should be used for request validation.
|
||||
This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
|
||||
This can be overridden by a :attr:`form` attribute on the
|
||||
:class:`views.View`.
|
||||
"""
|
||||
|
||||
|
||||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given some content as input return some cleaned, validated content.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400
|
||||
# (Bad Request) on failure.
|
||||
|
||||
Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied.
|
||||
Validation is standard form validation, with an additional constraint
|
||||
that *no extra unknown fields* may be supplied.
|
||||
|
||||
On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
|
||||
If the :obj:`'field-errors'` key exists it is a dict of ``{'field name as string': ['errors as strings', ...]}``.
|
||||
On failure the :exc:`response.ErrorResponse` content is a dict which
|
||||
may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field
|
||||
errors.
|
||||
If the :obj:`'field-errors'` key exists it is a dict of
|
||||
``{'field name as string': ['errors as strings', ...]}``.
|
||||
"""
|
||||
return self._validate(data, files)
|
||||
|
||||
|
||||
def _validate(self, data, files, allowed_extra_fields=(), fake_data=None):
|
||||
"""
|
||||
Wrapped by validate to hide the extra flags that are used in the implementation.
|
||||
Wrapped by validate to hide the extra flags that are used in the
|
||||
implementation.
|
||||
|
||||
allowed_extra_fields is a list of fields which are not defined by the form, but which we still
|
||||
expect to see on the input.
|
||||
allowed_extra_fields is a list of fields which are not defined by the
|
||||
form, but which we still expect to see on the input.
|
||||
|
||||
fake_data is a string that should be used as an extra key, as a kludge to force .errors
|
||||
to be populated when an empty dict is supplied in `data`
|
||||
fake_data is a string that should be used as an extra key, as a kludge
|
||||
to force `.errors` to be populated when an empty dict is supplied in
|
||||
`data`
|
||||
"""
|
||||
|
||||
# We'd like nice error messages even if no content is supplied.
|
||||
|
@ -127,7 +130,8 @@ class FormResource(Resource):
|
|||
form_fields_set = set(bound_form.fields.keys())
|
||||
allowed_extra_fields_set = set(allowed_extra_fields)
|
||||
|
||||
# In addition to regular validation we also ensure no additional fields are being passed in...
|
||||
# In addition to regular validation we also ensure no additional fields
|
||||
# are being passed in...
|
||||
unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
|
||||
unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
|
||||
|
||||
|
@ -178,7 +182,6 @@ class FormResource(Resource):
|
|||
# Return HTTP 400 response (BAD REQUEST)
|
||||
raise ErrorResponse(400, detail)
|
||||
|
||||
|
||||
def get_form_class(self, method=None):
|
||||
"""
|
||||
Returns the form class used to validate this resource.
|
||||
|
@ -200,7 +203,6 @@ class FormResource(Resource):
|
|||
|
||||
return form
|
||||
|
||||
|
||||
def get_bound_form(self, data=None, files=None, method=None):
|
||||
"""
|
||||
Given some content return a Django form bound to that content.
|
||||
|
@ -217,7 +219,6 @@ class FormResource(Resource):
|
|||
return form()
|
||||
|
||||
|
||||
|
||||
#class _RegisterModelResource(type):
|
||||
# """
|
||||
# Auto register new ModelResource classes into ``_model_to_resource``
|
||||
|
@ -230,11 +231,12 @@ class FormResource(Resource):
|
|||
# return resource_cls
|
||||
|
||||
|
||||
|
||||
class ModelResource(FormResource):
|
||||
"""
|
||||
Resource class that uses forms for validation and otherwise falls back to a model form if no form is set.
|
||||
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
|
||||
Resource class that uses forms for validation and otherwise falls back to a
|
||||
model form if no form is set.
|
||||
Also provides a :meth:`get_bound_form` method which may be used by some
|
||||
renderers.
|
||||
"""
|
||||
|
||||
# Auto-register new ModelResource classes into _model_to_resource
|
||||
|
@ -245,14 +247,16 @@ class ModelResource(FormResource):
|
|||
The form class that should be used for request validation.
|
||||
If set to :const:`None` then the default model form validation will be used.
|
||||
|
||||
This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
|
||||
This can be overridden by a :attr:`form` attribute on the
|
||||
:class:`views.View`.
|
||||
"""
|
||||
|
||||
model = None
|
||||
"""
|
||||
The model class which this resource maps to.
|
||||
|
||||
This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
|
||||
This can be overridden by a :attr:`model` attribute on the
|
||||
:class:`views.View`.
|
||||
"""
|
||||
|
||||
fields = None
|
||||
|
@ -261,22 +265,27 @@ class ModelResource(FormResource):
|
|||
|
||||
May be any of:
|
||||
|
||||
The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model.
|
||||
The name of a model field. To view nested resources, give the field as a
|
||||
tuple of ("fieldName", resource) where `resource` may be any of
|
||||
ModelResource reference, the name of a ModelResourc reference as a string
|
||||
or a tuple of strings representing fields on the nested model.
|
||||
The name of an attribute on the model.
|
||||
The name of an attribute on the resource.
|
||||
The name of a method on the model, with a signature like ``func(self)``.
|
||||
The name of a method on the resource, with a signature like ``func(self, instance)``.
|
||||
The name of a method on the resource, with a signature like
|
||||
``func(self, instance)``.
|
||||
"""
|
||||
|
||||
exclude = ('id', 'pk')
|
||||
"""
|
||||
The list of fields to exclude. This is only used if :attr:`fields` is not set.
|
||||
The list of fields to exclude. This is only used if :attr:`fields` is not
|
||||
set.
|
||||
"""
|
||||
|
||||
|
||||
include = ('url',)
|
||||
"""
|
||||
The list of extra fields to include. This is only used if :attr:`fields` is not set.
|
||||
The list of extra fields to include. This is only used if :attr:`fields`
|
||||
is not set.
|
||||
"""
|
||||
|
||||
def __init__(self, view=None, depth=None, stack=[], **kwargs):
|
||||
|
@ -289,30 +298,35 @@ class ModelResource(FormResource):
|
|||
|
||||
self.model = getattr(view, 'model', None) or self.model
|
||||
|
||||
|
||||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given some content as input return some cleaned, validated content.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400
|
||||
(Bad Request) on failure.
|
||||
|
||||
Validation is standard form or model form validation,
|
||||
with an additional constraint that no extra unknown fields may be supplied,
|
||||
and that all fields specified by the fields class attribute must be supplied,
|
||||
even if they are not validated by the form/model form.
|
||||
with an additional constraint that no extra unknown fields may be
|
||||
supplied, and that all fields specified by the fields class attribute
|
||||
must be supplied, even if they are not validated by the Form/ModelForm.
|
||||
|
||||
On failure the ErrorResponse content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
|
||||
If the ''field-errors'` key exists it is a dict of {field name as string: list of errors as strings}.
|
||||
On failure the ErrorResponse content is a dict which may contain
|
||||
:obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field
|
||||
errors.
|
||||
If the ''field-errors'` key exists it is a dict of
|
||||
`{field name as string: list of errors as strings}`.
|
||||
"""
|
||||
return self._validate(data, files, allowed_extra_fields=self._property_fields_set)
|
||||
|
||||
return self._validate(data, files,
|
||||
allowed_extra_fields=self._property_fields_set)
|
||||
|
||||
def get_bound_form(self, data=None, files=None, method=None):
|
||||
"""
|
||||
Given some content return a ``Form`` instance bound to that content.
|
||||
|
||||
If the :attr:`form` class attribute has been explicitly set then that class will be used
|
||||
to create the Form, otherwise the model will be used to create a ModelForm.
|
||||
If the :attr:`form` class attribute has been explicitly set then that
|
||||
class will be used
|
||||
to create the Form, otherwise the model will be used to create a
|
||||
ModelForm.
|
||||
"""
|
||||
form = self.get_form_class(method)
|
||||
|
||||
|
@ -339,14 +353,16 @@ class ModelResource(FormResource):
|
|||
|
||||
return form()
|
||||
|
||||
|
||||
def url(self, instance):
|
||||
"""
|
||||
Attempts to reverse resolve the url of the given model *instance* for this resource.
|
||||
Attempts to reverse resolve the url of the given model *instance* for
|
||||
this resource.
|
||||
|
||||
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been created for this resource.
|
||||
Requires a ``View`` with :class:`mixins.InstanceMixin` to have been
|
||||
created for this resource.
|
||||
|
||||
This method can be overridden if you need to set the resource url reversing explicitly.
|
||||
This method can be overridden if you need to set the resource url
|
||||
reversing explicitly.
|
||||
"""
|
||||
|
||||
if not hasattr(self, 'view_callable'):
|
||||
|
@ -363,7 +379,9 @@ class ModelResource(FormResource):
|
|||
# Note: defaults = tuple_item[2] for django >= 1.3
|
||||
for result, params in possibility:
|
||||
|
||||
#instance_attrs = dict([ (param, getattr(instance, param)) for param in params if hasattr(instance, param) ])
|
||||
# instance_attrs = dict([ (param, getattr(instance, param))
|
||||
# for param in params
|
||||
# if hasattr(instance, param) ])
|
||||
|
||||
instance_attrs = {}
|
||||
for param in params:
|
||||
|
@ -381,7 +399,6 @@ class ModelResource(FormResource):
|
|||
pass
|
||||
raise _SkipField
|
||||
|
||||
|
||||
@property
|
||||
def _model_fields_set(self):
|
||||
"""
|
||||
|
@ -389,7 +406,7 @@ class ModelResource(FormResource):
|
|||
"""
|
||||
model_fields = set(field.name for field in self.model._meta.fields)
|
||||
|
||||
if fields:
|
||||
if self.fields:
|
||||
return model_fields & set(as_tuple(self.fields))
|
||||
|
||||
return model_fields - set(as_tuple(self.exclude))
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.utils import simplejson as json
|
|||
from djangorestframework import status
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from django.contrib.auth.models import Group, User
|
||||
from djangorestframework.mixins import CreateModelMixin, PaginatorMixin
|
||||
from djangorestframework.mixins import PaginatorMixin
|
||||
from djangorestframework.resources import ModelResource
|
||||
from djangorestframework.response import Response
|
||||
from djangorestframework.tests.models import CustomUser
|
||||
|
@ -25,7 +25,7 @@ class TestModelCreation(TestCase):
|
|||
|
||||
form_data = {'name': 'foo'}
|
||||
request = self.req.post('/groups', data=form_data)
|
||||
mixin = CreateModelMixin()
|
||||
mixin = ModelMixin()
|
||||
mixin.resource = GroupResource
|
||||
mixin.CONTENT = form_data
|
||||
|
||||
|
@ -51,7 +51,7 @@ class TestModelCreation(TestCase):
|
|||
request = self.req.post('/groups', data=form_data)
|
||||
cleaned_data = dict(form_data)
|
||||
cleaned_data['groups'] = [group]
|
||||
mixin = CreateModelMixin()
|
||||
mixin = ModelMixin()
|
||||
mixin.resource = UserResource
|
||||
mixin.CONTENT = cleaned_data
|
||||
|
||||
|
@ -74,7 +74,7 @@ class TestModelCreation(TestCase):
|
|||
request = self.req.post('/groups', data=form_data)
|
||||
cleaned_data = dict(form_data)
|
||||
cleaned_data['groups'] = []
|
||||
mixin = CreateModelMixin()
|
||||
mixin = ModelMixin()
|
||||
mixin.resource = UserResource
|
||||
mixin.CONTENT = cleaned_data
|
||||
|
||||
|
@ -105,7 +105,7 @@ class TestModelCreation(TestCase):
|
|||
request = self.req.post('/groups', data=form_data)
|
||||
cleaned_data = dict(form_data)
|
||||
cleaned_data['groups'] = [group, group2]
|
||||
mixin = CreateModelMixin()
|
||||
mixin = ModelMixin()
|
||||
mixin.resource = UserResource
|
||||
mixin.CONTENT = cleaned_data
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
|
||||
class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||
"""
|
||||
Handles incoming requests and maps them to REST operations.
|
||||
|
@ -59,7 +58,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
permissions = ( permissions.FullAnonAccess, )
|
||||
|
||||
|
||||
@classmethod
|
||||
def as_view(cls, **initkwargs):
|
||||
"""
|
||||
|
@ -71,7 +69,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
view.cls_instance = cls(**initkwargs)
|
||||
return view
|
||||
|
||||
|
||||
@property
|
||||
def allowed_methods(self):
|
||||
"""
|
||||
|
@ -79,7 +76,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
return [method.upper() for method in self.http_method_names if hasattr(self, method)]
|
||||
|
||||
|
||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||
"""
|
||||
Return an HTTP 405 error if an operation is called which does not have a handler method.
|
||||
|
@ -87,7 +83,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
{'detail': 'Method \'%s\' not allowed on this resource.' % self.method})
|
||||
|
||||
|
||||
def initial(self, request, *args, **kargs):
|
||||
"""
|
||||
Hook for any code that needs to run prior to anything else.
|
||||
|
@ -96,14 +91,12 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
def add_header(self, field, value):
|
||||
"""
|
||||
Add *field* and *value* to the :attr:`headers` attribute of the :class:`View` class.
|
||||
"""
|
||||
self.headers[field] = value
|
||||
|
||||
|
||||
# Note: session based authentication is explicitly CSRF validated,
|
||||
# all other authentication is CSRF exempt.
|
||||
@csrf_exempt
|
||||
|
@ -183,26 +176,68 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
return response_obj
|
||||
|
||||
|
||||
class ModelView(View):
|
||||
class ModelView(ModelMixin, View):
|
||||
"""
|
||||
A RESTful view that maps to a model in the database.
|
||||
"""
|
||||
resource = resources.ModelResource
|
||||
|
||||
class InstanceModelView(InstanceMixin, ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
|
||||
def _filter_kwargs(self, kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
if BaseRenderer._FORMAT_QUERY_PARAM in kwargs:
|
||||
del kwargs[BaseRenderer._FORMAT_QUERY_PARAM]
|
||||
return kwargs
|
||||
|
||||
|
||||
class InstanceModelView(ModelView):
|
||||
"""
|
||||
A view which provides default operations for read/update/delete against a model instance.
|
||||
"""
|
||||
_suffix = 'Instance'
|
||||
|
||||
class ListModelView(ListModelMixin, ModelView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
instance = self.read(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
if not instance:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return instance
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return self.update(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
instance = self.destroy(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
if not instance:
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class ListModelView(ModelView):
|
||||
"""
|
||||
A view which provides default operations for list, against a model in the database.
|
||||
"""
|
||||
_suffix = 'List'
|
||||
|
||||
class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
|
||||
class ListOrCreateModelView(ModelView):
|
||||
"""
|
||||
A view which provides default operations for list and create, against a model in the database.
|
||||
"""
|
||||
_suffix = 'List'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
instance = self.create(request, *args, **self._filter_kwargs(kwargs))
|
||||
|
||||
headers = {}
|
||||
if hasattr(instance, 'get_absolute_url'):
|
||||
headers['Location'] = self.resource(self).url(instance)
|
||||
return Response(status.HTTP_201_CREATED, instance, headers)
|
||||
|
|
Loading…
Reference in New Issue
Block a user