from django.forms.models import modelform_factory
from .views import Endpoint
from .http import HttpError, Http200, Http201
from .models import serialize
__all__ = ['ListEndpoint', 'DetailEndpoint', 'ActionEndpoint']
def _get_form(form, model):
from django import VERSION
if VERSION[:2] >= (1,8):
mf = lambda m: modelform_factory(m, fields='__all__')
else:
mf = modelform_factory
if form:
return form
elif model:
return mf(model)
else:
raise NotImplementedError('Form or Model class not specified')
[docs]class ListEndpoint(Endpoint):
"""
List :py:class:`restless.views.Endpoint` supporting getting a list of
objects and creating a new one. The endpoint exports two view methods by
default: get (for getting the list of objects) and post (for creating a
new object).
The only required configuration for the endpoint is the `model`
class attribute, which should be set to the model you want to have a list
(and/or create) endpoints for.
You can also provide a `form` class attribute, which should be the
model form that's used for creating the model. If not provided, the
default model class for the model will be created automatically.
You can restrict the HTTP methods available by specifying the `methods`
class variable.
"""
model = None
form = None
methods = ['GET', 'POST']
[docs] def get_query_set(self, request, *args, **kwargs):
"""Return a QuerySet that this endpoint represents.
If `model` class attribute is set, this method returns the `all()`
queryset for the model. You can override the method to provide custom
behaviour. The `args` and `kwargs` parameters are passed in directly
from the URL pattern match.
If the method raises a :py:class:`restless.http.HttpError` exception,
the rest of the request processing is terminated and the error is
immediately returned to the client.
"""
if self.model:
return self.model.objects.all()
else:
raise HttpError(404, 'Resource Not Found')
[docs] def serialize(self, objs):
"""Serialize the objects in the response.
By default, the method uses the :py:func:`restless.models.serialize`
function to serialize the objects with default behaviour. Override the
method to customize the serialization.
"""
return serialize(objs)
[docs] def get(self, request, *args, **kwargs):
"""Return a serialized list of objects in this endpoint."""
if 'GET' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
qs = self.get_query_set(request, *args, **kwargs)
return self.serialize(qs)
[docs] def post(self, request, *args, **kwargs):
"""Create a new object."""
if 'POST' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
Form = _get_form(self.form, self.model)
form = Form(request.data or None, request.FILES)
if form.is_valid():
obj = form.save()
return Http201(self.serialize(obj))
raise HttpError(400, 'Invalid Data', errors=form.errors)
[docs]class DetailEndpoint(Endpoint):
"""
Detail :py:class:`restless.views.Endpoint` supports getting a single
object from the database (HTTP GET), updating it (HTTP PUT) and deleting
it (HTTP DELETE).
The only required configuration for the endpoint is the `model`
class attribute, which should be set to the model you want to have the
detail endpoints for.
You can also provide a `form` class attribute, which should be the
model form that's used for updating the model. If not provided, the
default model class for the model will be created automatically.
You can restrict the HTTP methods available by specifying the `methods`
class variable.
"""
model = None
form = None
lookup_field = 'pk'
methods = ['GET', 'PUT', 'DELETE']
[docs] def get_instance(self, request, *args, **kwargs):
"""Return a model instance represented by this endpoint.
If `model` is set and the primary key keyword argument is present,
the method attempts to get the model with the primary key equal
to the url argument.
By default, the primary key keyword argument name is `pk`. This can
be overridden by setting the `lookup_field` class attribute.
You can override the method to provide custom behaviour. The `args`
and `kwargs` parameters are passed in directly from the URL pattern
match.
If the method raises a :py:class:`restless.http.HttpError` exception,
the rest of the request processing is terminated and the error is
immediately returned to the client.
"""
if self.model and self.lookup_field in kwargs:
try:
return self.model.objects.get(**{
self.lookup_field: kwargs.get(self.lookup_field)
})
except self.model.DoesNotExist:
raise HttpError(404, 'Resource Not Found')
else:
raise HttpError(404, 'Resource Not Found')
[docs] def serialize(self, obj):
"""Serialize the object in the response.
By default, the method uses the :py:func:`restless.models.serialize`
function to serialize the object with default behaviour. Override the
method to customize the serialization.
"""
return serialize(obj)
[docs] def get(self, request, *args, **kwargs):
"""Return the serialized object represented by this endpoint."""
if 'GET' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
return self.serialize(self.get_instance(request, *args, **kwargs))
[docs] def put(self, request, *args, **kwargs):
"""Update the object represented by this endpoint."""
if 'PUT' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
Form = _get_form(self.form, self.model)
instance = self.get_instance(request, *args, **kwargs)
form = Form(request.data or None, request.FILES,
instance=instance)
if form.is_valid():
obj = form.save()
return Http200(self.serialize(obj))
raise HttpError(400, 'Invalid data', errors=form.errors)
[docs] def delete(self, request, *args, **kwargs):
"""Delete the object represented by this endpoint."""
if 'DELETE' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
instance = self.get_instance(request, *args, **kwargs)
instance.delete()
return {}
[docs]class ActionEndpoint(DetailEndpoint):
"""
A variant of :py:class:`DetailEndpoint` for supporting a RPC-style action
on a resource. All the documentation for DetailEndpoint applies, but
only the `POST` HTTP method is allowed by default, and it invokes the
:py:meth:`ActionEndpoint.action` method to do the actual work.
If you want to support any of the other HTTP methods with their default
behaviour as in DetailEndpoint, just modify the `methods` list to
include the methods you need.
"""
methods = ['POST']
def post(self, request, *args, **kwargs):
if 'POST' not in self.methods:
raise HttpError(405, 'Method Not Allowed')
instance = self.get_instance(request, *args, **kwargs)
return self.action(request, instance, *args, **kwargs)
def action(self, request, obj, *args, **kwargs):
raise HttpError(405, 'Method Not Allowed')