Take a REST with django-nap: APIs for Django¶



Whilst there are many existing RESTful API tools about, many of them are very complex, and [as I found] some are quite slow!
I wanted to take the solid serialising pattern from TastyPie, with the separation of roles of django-rest-framework, and include a Publisher API I developed some time ago.
In the spirit of the Unix philosophy, Nap provides a few tools which each do one thing, and do it well. They are:
Serialiser
Declarative style Serialiser definitions for reducing complex Python objects to simple types expressible in JSON.
Publisher
A Class-based view system which merges many related views into a single class, including url routing.
API
Manage many Publishers and API versions with simple auto-discover resource registration.
Nap does not provide the wide range of features you see in tools like Django REST Framework and TastyPie, such as rate limiting, token authentication, automatic UI, etc. Instead, it provides a flexible framework that makes it easy to combine with other specialised apps.
Contents:
Serialisers¶
Quick Overview¶
Serialisers define how to turn a Python object into a collection of JSON compatible types.
They are defined using the familiar declarative syntax, like Models and Forms.
Serialiser objects¶
A Serialiser class is defined much like a Form or Model:
class MySerialiser(Serialiser):
foo = fields.Field()
bar = fields.Field('foo.bar')
baz = fields.IntegerField()
Without an attribute specified, the field will use its name as the attribute name.
The Deflate Cycle¶
For each declared field:
- Call the fields deflate method. The field is expected to to store its result in the data dict.
- If there is a deflate_FOO method on the Serialiser, set the value in the data dict to its return value.
The Inflate Cycle¶
For reach declared field:
- If the Serialiser has an inflate_FOO method, its result is stored in the obj_data dict.
- Otherwise, call the fields inflate method. The field is expected to store its result in the obj_data dict.
- If there were no ValidationError exceptions raised, pass the obj_data, instance, and kwargs to restore_object.
If any ValidationError exceptions are raised during inflation, a ValidationErrors exception is raised, containing all the validation errors.
Custom Deflaters¶
-
deflate_FOO
(obj=obj, data=data, **kwargs)¶ After the
Field
’s deflate method is called, if a matching deflate_FOO method exists on the class it will be passed the source objectobj
, the updated data dictdata
, and any additional keyword arguments as passed toobject_deflate
.Note
The custom deflate methods are called after all Field deflate methods have been called.
Custom Inflaters¶
-
inflate_FOO
(data, obj, instance, **kwargs)¶ If an inflate_FOO method exists on the class, it will be called instead of the
Field
’s inflate method. It is passed the source data, the inflated object data, the instance (which defaults to None), and any keyword arguments as passed to object_inflate.Note
The custom inflate methods are called after all Field inflate methods have been called.
The custom inflater may raise a
nap.exceptions.ValidationException
to indicate the data are invalid.
Serialiser API¶
-
class
Serialiser
¶ -
object_deflate
(obj, **kwargs)¶ Returns obj reduced to its serialisable format.
The kwargs are passed on to field and custom deflate methods.
-
list_deflate
(obj_list, **kwargs)¶ Return a list made by calling object_deflate on each item in the supplied iterable.
Passes kwargs to each call to object_deflate.
-
object_inflate
(data, instance=None, **kwargs)¶ Restore data to an object. If the instance is passed, it is expected to be updated by
restore_object
.
-
restore_object
(obj_data, **kwargs)¶ Construct an object from the inflated data.
By default, if Serialiser.obj_class has been provided, it will construct a new instance, passing objdata as keyword arguments. Otherwise, it will raise a NotImplementedError.
-
Fields¶
Fields are declared on Serialisers to pluck values from the object for deflation, as well as to cast them back when inflating.
The basic Field class can be used for any value that has a matching JSON counterpart; i.e. bools, strings, floats, dicts, lists.
There are also some for common types:
- BooleanField
- IntegerField
- DecimalField
- DateTimeField
- DateField
- TimeField
- StringField
Finally, there are the two Serialiser Fields, which will generate their value using a serialiser class. They are the SerialiserField, and ManySerialiserField.
Field¶
-
class
Field
(attribute=None, default=None, readonly=False, null=True, *args, **kwargs)¶ Parameters: - attribute – Define the attribute this field sources it value from on the object. If omitted, the name of this field in its Serialiser class will be used. This may be in Django template dotted-lookup syntax.
- default – The value to use if the source value is absent.
- readonly – Is this field only for deflating?
- null – Can this value be None when inflating?
- virtual –
The value for this field will be generated by a custom deflate method, so don’t raise an AttributeError.
Can also be used to omit a field if a value is not found.
-
type_class
¶ For simple fields, type_class is used to restore values.
-
reduce
(value, **kwargs)¶ Reduce the sourced value to a serialisable type.
-
restore
(value, **kwargs)¶ Restore a serialisable form of the value to its Python type. By default this will use
type_class
unless the value is None.
-
deflate
(name, obj, data, **kwargs)¶ Deflate our value from the obj, and store it into data. The
name
will be used only ifself.attribute
is None.This uses
digattr
to extract the value from the obj, then callsreduce
if the value is not None.
-
inflate
(name, data, obj, **kwargs)¶ Inflate a value from data into obj. The
name
will be used only ifself.attribute
is None.
Deflate Cycle¶
- Determine if we use name or attribute.
- Use nap.utils.digattr to get the value
- If the value is not None, call self.reduce
- Add our value to the data dict under the key in name
The reduce method is the last stage of casting. By default, it does nothing.
Inflate Cycle¶
- If this field is read-only, return immediately.
- Determine if we use name or attribute
- Try to get our value from the data dict. If it’s not there, return.
- Pass the value through self.restore
- Save the value in the obj dict
By default, restore tries to construct a new self.type_class from the value, unless type_class is None.
StringField¶
StringField is for cases where you want to ensure the value is forced to a string. Its reduce method uses django.utils.encoding.force_text.
Serialiser Fields¶
SerialiserField follows the same pattern as above, but replaces the normal reduce/restore methods with calls to its serialisers object_deflate/object_inflate.
ManySerialiserField does the same, but uses list_deflate/list_inflate.
Publishers¶
The publisher is a general purpose class for dispatching requests. It’s similar to the generic Class-Based Views in Django, but handles many views in a single class.
This pattern works well for APIs, where typically a group of views require the same functions.
r'^object/(?P<object_id>[-\w]+)/(?P<action>\w+)/(?P<argument>.+?)/?$'
r'^object/(?P<object_id>[-\w]+)/(?P<action>\w+)/?$'
r'^object/(?P<object_id>[-\w]+)/?$'
r'^(?P<action>\w+)/(?P<argument>.+?)/$'
r'^(?P<action>\w+)/?$'
r'^$'
Clearly this list does not permit ‘object’ to be an action.
The publisher recognises a small, fixed set of URL patterns, and dispatches them to methods on the class according to a simple pattern: target, method, action. The target is either “list” or “object”, depending on if an object_id was supplied. The method is the HTTP method, lower cased (i.e. get, put, post, delete, etc.). And finally, the action, which defaults to ‘default’.
So, for example, a GET request to /foo would call the list_get_foo
handler.
Whereas a POST to /object/123/nudge/ would call object_post_nudge
, passing
“123” as the object_id.
All handlers should follow the same definition:
def handler(self, request, action, object_id, **kwargs):
Both action and object_id are passed as kwargs, so where they’re not needed they can be omitted.
Like a view, every handler is expected to return a proper HttpResponse object.
Publishing¶
In order to add a Publisher
to your URL patterns, you need to include all of
its patterns. Fortunately, it provides a handy method to make this simple:
url(r’^myresource/’, include(MyPublisher.patterns())),
Base Publisher¶
-
class
BasePublisher
(request [,*args] [,**kwargs])¶ -
CSRF = True
Determines if CSRF protection is applied to the view function used in
patterns
-
ACTION_PATTERN
¶
-
OBJECT_PATTERN
¶
-
ARGUMENT_PATTERN
¶ Used by the
patterns
method to control the URL patterns generated.
-
classmethod
patterns
(api_name=None)¶ Builds a list of URL patterns for this Publisher.
The
api_name
argument will be used for naming the url patterns.
-
classmethod
index
()¶ Returns details about handlers available on this publisher.
The result will be a dict with two keys: list, and detail.
Each item will contain a list of handlers and the HTTP verbs they accept.
-
dispatch(request, action='default', object_id=None, **kwargs):
Entry point used by the view function.
-
execute(handler):
Call hook for intercepting handlers.
dispatch
passes the handler method here to invoke. It will call the handler, and catch anyBaseHttpResponse
exceptions, returning them.This was originally added to make New Relic support simpler.
-
Custom Patterns¶
By overridding the patterns method, you can provide your own url patterns.
One sample is included: nap.publisher.SimplePatternsMixin
It omits the object/ portion of the object urls above, but limits object_ids to just digits.
Alternatively, if you just want to change the regex used for each part of the URL, you can overrid them using OBJECT_PATTERN, ACTION_PATTERN, and ARGUMENT_PATTERN, which default to ‘[-w]+’, ‘w+’ and ‘.*?’ respectively.
Publisher¶
The Publisher extends the BasePublisher class with some useful methods for typical REST-ful uses.
-
class
Publisher
¶ -
page_size
¶ Enable pagination and specify the default page size. Default: unset
-
max_page_size
¶ Limit the maximum page size. Default: page_size
If a request passes an override LIMIT value, it can not exceed this.
-
LIMIT_PARAM
¶ Specifies the query parameter name used to specify the pagination size limit. Default: ‘limit’
-
OFFSET_PARAM
¶ Specifies the query parameter name used to specify the pagination offset. Default: ‘offset’
-
PAGE_PARAM
¶ Specifies the query parameter name used to specify the pagination page. Default: ‘page’
-
response_class
¶ Default class to use in
create_response
-
CONTENT_TYPES
¶ A list of content types supported by the de/serialiser. Default: [‘application/json’, ‘text/json’]
The first value in the list will be used as the content type of responses.
-
dumps
(data)¶ Used to serialise data. By default calls json.dumps.
-
loads
(data)¶ Deserialise data. By default calls json.loads.
-
get_serialiser
()¶ Called to get the
Serialiser
instance to use for this request. Default: returns self.serialiser
-
get_serialiser_kwargs
()¶ Used to generate extra kwargs to pass to serialiser calls (i.e. object_deflate, list_deflate, etc)
-
get_object_list
()¶ Return the raw object list for this request. This is Not Implemented. You must provide this method in your Serialiser class.
-
get_object
(object_id)¶ Return the object for the given ID. You must provide this method in your Serialiser class.
-
filter_object_list
(object_list)¶ Apply filtering to an object list, returning the filtered list. Default: Returns the passed object_list.
-
sort_object_list
(object_list)¶ Apply sorting to an object list, returning the sorted list. Default: Returns the passed object_list.
-
get_page(object_list):
Paginate the object_list.
If the page_size is not defined on the Serialiser, no pagination is performed, and the following dict is returned:
{ 'meta': {}, 'objects': object_list }
Otherwise, the object_list is paginated. If self.PAGE_PARAM was passed, it will be used for the page number. It not, and self.OFFSET_PARAM is supplied, the page will be determined by dividing the offset by page_size.
The
meta
dict will contain:'offset': page.start_index() - 1, 'page': page_num, 'total_pages': paginator.num_pages, 'limit': page_size, 'count': paginator.count, 'has_next': page.has_next(), 'has_prev': page.has_previous(),
-
get_request_data
()¶ Returns the data sent in this request. If the request type is specified in
CONTENT_TYPES
it will be used to de-serialise the data. Otherwise, request.GET or request.POST will be returned as apporpriate for the HTTP method used.
-
render_single_object(obj, serialiser=None, **kwargs):
A helper function to serialise the object and create a response, using self.response_class. If
serialiser
is None, it will callget_serialiser
. The kwargs will be passed on tocreate_response
-
create_response(content, **kwargs):
A helper function for building
self.response_class
. Passing response_class as an argument overrides the class used.It sets ‘content_type’ in kwargs to self.CONTENT_TYPES[0] if it’s not set. Then, it passes
content
toself.dumps
, and passes that, along with kwargs, to build a new response_class instance, returning it.
-
list_get_default(request, **kwargs):
Default list handler.
Calls get_object_list, filter_object_list and sort_object_list, then passes the list to get_page. It then uses the object from get_serialiser to deflate the object list.
Returns the resulting data using
create_response
.
-
Filtering and Sorting¶
The Publisher class has two methods for sorting and filtering:
-
filter_object_list
(object_list)¶
-
sort_object_list
(object_list)¶
By default, these simply return the list they are passed.
Filtering and sorting are not applied by get_object_list. This lets you apply required filtering [site, security, user, etc] in get_object_list, and optional filtering [query, etc] where it’s wanted. Also, ordering can be an unwanted expense when it’s not important to the use.
The default Publisher.list_get_default will pass the result of get_object_list to filter_object_list and sort_object_list in turn before serialising.
ModelPublisher¶
The ModelPublisher implements some default handlers that are more sensible for a Model.
It includes a default model
property that will return the model from the
meta class of self.serialiser. This way, by default, it will publish the model
of its default Serialiser.
Authorisation¶
Authorisation is handled using the permit
decorator.
Use it to decorate any handler method, passing it a function that will yield True/False.
The function will be passed all the arguments the handler will receive.
APIs¶
When you have multiple Publishers collected together, it can be handy to publish them under the same path. This is where the Api class comes in.
api = Api('name')
api.register(MyPublisher)
api.register(OtherPublisher, 'friendlyname')
If not overridden by being passed in the call to register, the Publisher will
be registered with the name in its api_name
property. This is used as the
root of its URL within the Api.
Once registered, you can get a list of URL patterns to include:
(r'^api/', include(api.patterns())),
When you add the patterns, you can optionally pass a ‘flat’ argument, which will omit the Api’s name from the url patterns.
Also, the Api class provides an introspection view that will list all its Publishers, as well as their handlers and methods supported.
Auto-discover¶
Just like Django’s Admin, Api supports auto-discover.
In your publishers.py use:
from nap import api
...
api.register('apiname', Publisher,....)
If you’re only registering a single Publisher, you can use api.register as a decorator.
@api.register('apiname')
class JoyPublisher(Publisher):
An Api instance with that name will be created, if it hasn’t already, and put into api.APIS. Then the publisher(s) you pass will be registered with it.
Note
The publisher will be registered as the lower-cased version of its class
name. You can control this by setting an api_name
property.
Then, in your urls.py, just add:
from nap import api
api.autodiscover()
Which will trigger it to import $APP.serialiser from each of your INSTALLED_APPS. Then you can just include the urls:
(r'^apis/', include(api.patterns()))
Model Classes¶
Of course, there are classes to support dealing with Models.
ModelSerialiser¶
This class will, like a ModelForm, inspect a Model and generate appropriate fields for it.
class MyModelSerialiser(ModelSerialiser):
class Meta:
model = MyModel
fields = [...fields to include...]
exclude = [...fields to exclude...]
read_only = [...fields to mark read-only...]
Like any other serialiser, you can define additional fields, as well as custom inflate/deflate methods.
By default, the restore_object
method will pass the inflated dict to the
model to create a new instance, or update all the properties on an existing
instance. It will save the updated instance, however you can pass commit=False
to prevent this.
Meta¶
The ModelSerialiser
supports additional Meta
properties:
-
class
ModelMeta
¶ -
model
¶ Default: None The model this
Serialiser
is for
-
fields
¶ Default: () The list of fields to use from the Model
-
exclude
¶ Default: () The list of fields to ignore from the Model
-
read_only_fields
¶ Default: () The list of fields from the Model to mark as read-only
-
field_types
¶ Default {} A map of field names to Field class overrides.
Default: ()
-
ignore_fields
¶ Default: () When restoring an object, the fields which should not be passed to the instance.
-
key_fields
¶ Default: (‘id’,) When trying to
get_or_create
a model instance, which fields from the inflated data should be used to match the model.
-
defaults
¶ Default: {} When trying to
get_or_create
a model instance, additional default values to pass.
-
core_fields
¶ Default: () When trying to
get_or_create
a model instance, additional fields to include in the defaults dict.
-
ModelPublisher sub-classes¶
There are two extra sub-classes to help building complex cases when restoring instances.
ModelReadSerialiser will only retrieve existing instances, passing all data to
the managers get
method.
The ModelCreateUpdateSerialier will try to construct a new instance, or update an existing one if it can be found.
The values found from Meta.key_fields
will be passed to get_or_create
.
The defaults
argument will be constructed from Meta.defaults
, and the
infalted values listed in Meta.core_fields
.
Then, the instance will be updated for all fields not listed in
Meta.related_fields
or Meta.ignored_fields
.
Finally, all Meta.related_fields
will be set by calling their add
method.
ModelPublisher¶
A ModelPublisher adds a model property to a publisher, which by default yields the model of the serialiser class.
It also adds get_object_list
and get_object
, where get_object
assumes object_id is the pk of the model.
This gives basic read-only access to your model through the API.
modelserialiser_factory¶
This utility class allows you to programmatically generate a ModelSerialiser.
myser = modelserialiser_factory(name, model, [fields=], [exclude=], [read_only=], **kwargs)
The optional arguments will be treated the same as if passed in the Meta of a ModelSerialiser. Additional deflate/inflate methods may be passed in kwargs.
ModelSerialiserField & ModelManySerialiserField¶
Model counterparts to SerialiserField and ManySerialiserField. If not passed a serialiser, they will generate one from the model provided.
HTTP Utilities¶
In nap.http is a set of tools to go one step further than Django’s existing HttpResponse.
Status¶
Firstly, there is STATUS_CODES, which is a list of two-tuples of HTTP Status codes and their descriptions.
Also, and more usefully, there is the STATUS object. Accessing it as a dict, you can look up HTTP status code descriptions by code:
>>> STATUS[401]
'Unauthorized'
However, you can also look up attributes to find out the status code:
>>> STATUS.UNAUTHORIZED
401
This lets it act as a two-way constant.
BaseHttpResponse¶
This class blends Django’s HttpResponse with Python’s Exception. Why? Because then, when you’re nested who-knows how deep in your code, it can raise a response, instead of having to return one and hope everyone bubbles it all the way up.
BaseHttpResponse
HttpResponseSuccess
- OK
- Created
- Accepted
- NoContent
- ResetContent
- PartialContent
HttpResponseRedirect
- MultipleChoices
- MovedPermanently*
- Found*
- SeeOther*
- NotModified
- UseProxy*
- TemporaryRedirect
- PermanentRedirect
Items marked with a * require a location passed as their first argument. It will be set as the
Location
header in the response.HttpResponseError
- BadRequest
- Unauthorized
- PaymentRequired
- Forbidden
- NotFound
- MethodNotAllowed
- NotAcceptable
- ProxyAuthenticationRequired
- RequestTimeout
- Conflict
- Gone
- LengthRequired
- PreconditionFailed
- RequestEntityTooLarge
- RequestURITooLong
- UnsupportedMediaType
- RequestedRangeNotSatisfiable
- ExpectationFailed
HttpResponseServerError
- InternalServerError
- NotImplemented
- BadGateway
- ServiceUnavailable
- GatewayTimeout
- HttpVersiontNotSupported
It will be clear that, unlike Django, these mostly do not start with HttpResponse. This is a personal preference, in that typically you’d use:
from nap import http
...
return http.Accept(...)
Http404 versus http.NotFound¶
Generally in your API, you’ll want to prefer http.NotFound for returning a 404 response. This avoids being caught by the normal 404 handling, so it won’t invoke your handler404.
New Relic¶
If you are using NewRelic, you will want to use the hooks provided, otherwise all Nap API calls will be logged as “nap.publisher.view”.
Simply add the following lines to your newrelic.ini
[import-hook:nap.publisher]
enabled = true
execute = nap.newrelic:instrument_django_nap_publisher
Examples¶
Sometimes, an example is much easier to understand than abstract API docs, so here’s some sample use cases.
Case 1: Simple Blog API¶
models.py¶
from django.db import models
from taggit.managers import TaggableManager
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
published = models.BooleanField(default=False)
content = models.TextField(blank=True)
tags = TaggableManager(blank=True)
serialiser.py¶
from nap.models import ModelSerialiser
from nap import fields
class PostSerialiser(ModelSerialiser):
class Meta:
model = models.Post
tags = fields.Field()
def deflate_tags(self, obj, \**kwargs):
'''Turn the tags into a flat list of names'''
return [tag.name for tag in obj.tags.all()]
publishers.py¶
from nap.models import ModelPublisher
from .serialiser import PostSerialiser
class PostPublisher(ModelPublisher):
serialiser = PostSerialiser()
urls.py¶
from .publishers import PostPublisher
urlpatters = patterns('',
(r'^api/', include(PostPublisher.patterns())),
)
Changelog¶
v0.13.9¶
Enhancements:
- Added Django 1.7 AppConfig, which will auto-discover on ready
- Added a default implementation of ModelPublsiher.list_post_default
- Tidied code with flake8
Bug Fixes:
- Fixed use of wrong argument in auth.permit_groups
v0.13.8¶
Enhancements:
- Added prefetch_related and select_related support to ExportCsv action
- Added Field.virtual to smooth changes to Field now raising AttributeError, and support optional fields
v0.13.7¶
Enhancements:
- Added ReadTheDocs, and prettied up the docs
- Use Pythons content-type parsing
- Added RPC publisher [WIP]
- Allow api.register to be used as a decorator
- Make Meta classes more proscriptive
- Allow ModelSerialiser to override Field type used for fields.
- Added ModelReadSerialiser and ModelCreateUpdateSerialiser to support more complex inflate scenarios [WIP]
Bug Fixes:
- Fixed ExportCsv and simplecsv extras
- Raise AttributeError if a deflating a field with no default set would result in using its default. [Fixes #28]
- Fixed auto-generated api_names.
- Purged under-developed ModelFormMixin class
v0.13.6¶
Enhancements:
- Overhauled testing
- Added ‘total_pages’ to page meta.
- Added Serialiser.obj_class
v0.13.5¶
Bug Fixes:
- Fix use of b’’ for Py3.3 [thanks zzing]
Enhancements:
- Add options to control patterns
v0.13.4¶
Bug Fixes:
- Return http.NotFound instead of raising it
Enhancements:
- Added views publisher
- Updated docs
- Re-added support for ujson, if installed
- Tidied up with pyflakes/pylint
- Added Publisher.response_class property
v0.13.2¶
Enhancements:
- Separate Publisher.build_view from Publisher.patterns to ease providing custom patterns
- Added SimplePatternsMixin for Publisher
- Added Publisher.sort_object_list and Publisher.filter_object_list hooks
v0.13.0¶
WARNING: API breakage
Changed auto-discover to look for ‘publishers’ instead of ‘seraliser’.
Enhancements:
- Added Field.null support
- Now use the Field.default value
- ValidationError handled in all field and custom inflator methods
v0.12.5¶
Bugs Fixed:
- Restored Django 1.4 compatibility
Enhancements:
- Allow disabling of API introspection index
v0.12.4¶
Bugs Fixed:
- Fixed filename generation in csv export action
- Fixed unicode/str issues with type() calls
Enhancements:
- Split simplecsv and csv export into extras module
- Merged engine class directly into Publisher
- Added fields.StringField
v0.12.3¶
Bugs Fixed:
- Fix argument handling in Model*SerialiserFields
- Tidied up with pyflakes
Enhancements:
- Added support for Py3.3 [thanks ioneyed]
- Overhauled the MetaSerialiser class
- Overhauled “sandbox” app
- Added csv export action
v0.12.1¶
Bugs Fixed:
- Flatten url patterns so object_default can match without trailing /
- Fix class returned in permit decorator [Thanks emilkjer]
Enhancements:
- Allow passing an alternative default instead of None for Publisher.get_request_data
- Added “read_only_fields” to ModelSerialiser [thanks jayant]
v0.12¶
Enhancements:
- Tune Serialisers to pre-build their deflater/inflater method lists, removing work from the inner loop
- Remove *args where it’s no helpful
v0.11.6.1¶
Bugs Fixed:
- Renamed HttpResponseRedirect to HttpResponseRedirection to avoid clashing with Django http class
v0.11.5¶
Enhancements:
- Add Publisher.execute to make wrapping handler calls easier [also, makes NewRelic simpler to hook in]
- Allow empty first pages in pagination
- Added support module for NewRelic
v0.11.3¶
Enhancements:
- Make get_page honor limit parameter, but bound it to max_page_size, which defaults to page_size
- Allow changing the GET param names for page, offset and limit
- Allow passing page+limit or offset+limit
v0.11.1¶
Enhancements:
- Changed SerialiserField/ManySerialiserField to replace reduce/restore instead of overriding inflate/deflate methods
- Fixed broken url pattern for object action
- Updated fields documentation
v0.11¶
API breakage
Serialiser.deflate_object and Serialiser.deflate_list have been renamed.
Enhancements:
- Changed deflate_object and deflate_list to object_deflate and list_deflate to avoid potential field deflater name conflict
- Moved all model related code to models.py
- Added modelserialiser_factory
- Updated ModelSerialiserField/ModelManySerialiserField to optionally auto-create a serialiser for the supplied model
v0.10.3¶
Enhancements:
- Added python2.6 support back [thanks nkuttler]
- Added more documentation
- Added Publisher.get_serialiser_kwargs hook
- Publisher.get_data was renamed to Publisher.get_request_data for clarity
v0.10¶
Bugs Fixed:
- Removed useless cruft form utils
Enhancements:
- Replaced http subclasses with Exceptional ones
- Wrap call to handlers to catch Exceptional http responses
v0.9.1¶
Enhancements:
- Started documentation
- Added permit_groups decorator
- Minor speedup in MetaSerialiser
v0.9¶
Bugs Fixed:
- Fixed var name bug in ModelSerialiser.restore_object
- Removed old ‘may’ auth API
Enhancements:
- Added permit decorators
- use string formatting not join - it’s slightly faster
v0.8¶
Enhancements:
- Added create/delete methods to ModelPublisher
- Renamed HttpResponse subclasses
- Split out BasePublisher class
- Added http.STATUS dict/list utility class
Note
Because this uses OrderedDict nap is no longer python2.6 compatible
v0.7¶
Bugs Fixed:
- Removed custom JSON class
Enhancements:
- Added Engine mixin classes
- Added MsgPack support
- Added type-casting fields
v0.6¶
Bugs Fixed:
- Fixed JSON serialising of date/datetime objects
Enhancements:
- Added index view to API
- Make render_single_object use create_response
- Allow create_response to use a supplied response class
v0.2¶
Bugs Fixed:
- Fixed bug in serialiser meta-class that broke inheritance
- Fixed variable names
Enhancements:
- Pass the Publisher down into the Serialiser for more flexibility
- Allow object IDs to be slugs
- Handle case of empty request body with JSON content type
- Added SerialiserField and ManySerialiserField
- Added Api machinery
- Changed Serialiser to use internal Meta class
- Added ModelSerialiser class
Quick Start¶
- Create a Serialiser for your Model in serialisers.py
from nap import models
from myapp.models import MyModel
class MyModelSerialiser(models.ModelSerialiser):
class Meta:
model = MyModel
exclude = ['user',]
- Create a Publisher in publishers.py, and register it.
from nap import api, models
from myapp.serialisers import MyModelSerialiser
class MyModelPublisher(models.ModelPublisher):
serialiser = MyModelSerialiser()
api.register('api', MyModelPublisher)
- Auto-discover publishers, and add your APIs to your URLs:
from nap import api
api.autodiscover()
urlpatterns('',
(r'', include(api.patterns())
...
)