DjangoCon Europe 2019: Making your life (h)APIer with Django

Writeup of the DjangoCon Europe 2019 talk »Making your life (h)APIer with Django« by Emma Delescolle

Emma Delescolle: Long-time pythonista, Django fan, co-maintainer and co-author of DRF-Schema-Adapter and other OSS libraries. She is from Belgium and has been involved in OSS at different levels for about 15 years now.

APIs

Let's talk about Django APIs. The most prevalent API type Django developers implement are REST APIs with the usual GET (list/show), POST (create), PATCH & PUT (update), DELETE HTTP verbs. People also may mention that it's important to make these APIs discoverable – going to get back to that later.

APIs are needed for a variety of things – searchable dropdowns are the most simple use case, but there are larger use cases like completely separate frontends (in the browser via js frameworks, or via standalone desktop/mobile clients), or just providing the API directly or via API clients to users.

Also, the concept is just proven to work, so people fall back to writing an API, and more often than not, they choose to go with REST.

DRF improvements

Within the Django ecosystem, Django Rest Framework has become the de-facto standard. Earlier on there was also Tastypie, which worked similar to frameworks like Rails, making prototyping very easy. In Django Rest Framework, you have viewsets which are provided with serializers and querysets, and produce proper JSON output. Tastypie just had you register a resource from a model, which is great for speed, but a bit awkward for modifications to the standard output.

Especially with many models, starting with DRF can be a lot of work, and it would be so nice to just do something like importing the Django admin urls. So here is an improvement to DRF:

from drf_auto_endpoint.router import router, register
from drf_auto_endpoint.endpoints import Endpoint

@register
class ProductEndpoint(Endpoint):
   model = Product

router.register(ProductEndpoint)

urlpatterns = [url(r'^api/', include(router.urls))]

INSTALLED_APPS.append('drf_auto_endpoint')

And then the URLs will just be auto generated. This is brilliant to do for rapid prototyping. For customisations, you can do many things similar to admin modifications, by providing order/filter fields.

Similarly, serializers are abstracted and simplified via a serializer factory, which wraps DRF in a series of factories. This keeps you from repeating standard default information. The combination of auto generated viewsets, urls, and serializers makes for very fast prototyping and modifications.

Discoverability

People say "discoverability", they expect to see URLs instead of IDs in foreign keys. This is nice for humans, but gets repetitive fast. DRF already allows you to provide metadata classes, which modify the response to OPTIONS requests. OPTIONS usually gives you additional information regarding expected formats, encodings, required fields, etc. We want to expand this data by providing metadata regarding related models, for example.

Actions

REST APIs usually need interactive elements, so-called action endpoints, which trigger certain actions that can't or shouldn't be simple model updates. DRF already tells you about actions you can perform on the URL you are on, but you would want to see related (child) action URLs and their required attributes, too.

Custom adapter implications

So custom adapters can be very helpful. Many custom adapters claim to use JSONSchema, but since its functionality doesn't include foreign keys etc, that's never quite true. Emma's tooling provides adapters for Angular, React, and Ember (most complete), which heavily assists in form and page generation off of the DRF metadata output. Adding your own adapter is not hard either, though!

For ember, you usually define models and APIs, since it's a complete MVC framework. These models mirror the Django models very directly. So the custom adapter includes a js file that provides the model as rendered by DRF. This results in you being able to render complete pages and forms without knowing anything about the models themselves.

See: DRF-Schema-Adapter