... | ... | @@ -2,38 +2,35 @@ The API uses some concepts that are really different from the web interface beca |
|
|
|
|
|
Of course, the best would still be to go trough [Django Rest Framework (DRF)'s tutorial and the API reference](https://www.django-rest-framework.org/) but the learning curve is quite steep, so for simple actions on the API, this page might be enough. Also it might be a good idea to read the [API raw usage wiki page](API/Raw usage) to get an idea of what's currently possible.
|
|
|
|
|
|
1. [The ACL "can_use_api"](#the-acl-can_use_api)
|
|
|
2. [The URLs and routers](#the-urls-and-routers)
|
|
|
3. [The class-based views, generics and viewsets](#the-class-based-views-generics-and-viewsets)
|
|
|
1. [The class-based views](#the-class-based-views)
|
|
|
2. [The generics](#the-generics)
|
|
|
3. [The viewsets](#the-viewsets)
|
|
|
4. [The authentication classes](#the-authentication-classes)
|
|
|
1. [rest_framework.authentication.SessionAuthentication](#rest_frameworkauthenticationsessionauthentication)
|
|
|
2. [api.authentication.ExpiringTokenAuthentication](#apiauthenticationexpiringtokenauthentication)
|
|
|
5. [The permission classes](#the-permission-classes)
|
|
|
1. [Selecting the permission classes](#selecting-the-permission-classes)
|
|
|
2. [api.permissions.AutodetectACLPermission](#apipermissionsautodetectaclpermission)
|
|
|
3. [api.permissions.ACLPermission](#apipermissionsaclpermission)
|
|
|
6. [The serializers](#the-serializers)
|
|
|
1. [Default behavior](#default-behavior)
|
|
|
2. [Django models relation serialization](#django-models-relation-serialization)
|
|
|
1. [rest_framework.serializers.ModelSerializer](#rest_frameworkserializersmodelserializer)
|
|
|
2. [rest_framework.serializers.HyperlinkedModelSerializer](#rest_frameworkserializershyperlinkedmodelserializer)
|
|
|
3. [api.serializers.NamespacedHMSerializer](#apiserializersnamespacedhmserializer)
|
|
|
3. [Control the outputed field](#control-the-outputed-field)
|
|
|
4. [Specifying a non-default serializer field](#specifying-a-non-default-serializer-field)
|
|
|
5. [Add custom serializer fields](#add-custom-serializer-fields)
|
|
|
6. [Using serializers as serializer fields](#using-serializers-as-serializer-fields)
|
|
|
7. [Usage in Re2o](#usage-in-re2o)
|
|
|
7. [The pagination](#the-pagination)
|
|
|
8. [The format](#the-format)
|
|
|
9. [The settings file](#the-settings-file)
|
|
|
10. [The tests](#the-tests)
|
|
|
|
|
|
|
|
|
|
|
|
# The ACL "can_use_api"
|
|
|
1. [The ACL "can_use_api"](#1-the-acl-can_use_api)
|
|
|
2. [The URLs and routers](#2-the-urls-and-routers)
|
|
|
3. [The class-based views, generics and viewsets](#3-the-class-based-views-generics-and-viewsets)
|
|
|
3.1. [The class-based views](#31-the-class-based-views)
|
|
|
3.2. [The generics](#32-the-generics)
|
|
|
3.3. [The viewsets](#33-the-viewsets)
|
|
|
4. [The authentication classes](#4-the-authentication-classes)
|
|
|
4.1. [rest_framework.authentication.SessionAuthentication](#41-rest_frameworkauthenticationsessionauthentication)
|
|
|
4.2. [api.authentication.ExpiringTokenAuthentication](#42-apiauthenticationexpiringtokenauthentication)
|
|
|
5. [The permission classes](#5-the-permission-classes)
|
|
|
5.1. [Selecting the permission classes](#51-selecting-the-permission-classes)
|
|
|
5.2. [api.permissions.AutodetectACLPermission](#52-apipermissionsautodetectaclpermission)
|
|
|
5.3. [api.permissions.ACLPermission](#53-apipermissionsaclpermission)
|
|
|
6. [The serializers](#6-the-serializers)
|
|
|
6.1. [Default behavior](#61-default-behavior)
|
|
|
6.2. [Django models relation serialization](#62-django-models-relation-serialization)
|
|
|
6.3. [Control the outputed field](#63-control-the-outputed-field)
|
|
|
6.4. [Specifying a non-default serializer field](#64-specifying-a-non-default-serializer-field)
|
|
|
6.5. [Add custom serializer fields](#65-add-custom-serializer-fields)
|
|
|
6.6. [Using serializers as serializer fields](#66-using-serializers-as-serializer-fields)
|
|
|
6.7. [Usage in Re2o](#67-usage-in-re2o)
|
|
|
7. [The pagination](#7-the-pagination)
|
|
|
8. [The format](#8-the-format)
|
|
|
9. [The settings file](#9-the-settings-file)
|
|
|
10. [The tests](#10-the-tests)
|
|
|
|
|
|
|
|
|
|
|
|
# 1 - The ACL "can_use_api"
|
|
|
|
|
|
The API uses a specific ACL called "can_use_api" used to determine if a user is authorized to use the API (both for seeing and modifying data). The way to define it is a bit tricky and different from the usual ACL because django-auth (which manages the ACL and groups) need to associate a permission to a "content-type" which itself need to be associated with an app name and a model name. However, this ACL is not model-based but app-based.
|
|
|
|
... | ... | @@ -46,7 +43,7 @@ api.acl.can_view(user) |
|
|
```
|
|
|
|
|
|
|
|
|
# The URLs and routers
|
|
|
# 2 - The URLs and routers
|
|
|
|
|
|
[Official DRF documentation about routers](https://www.django-rest-framework.org/api-guide/routers/)
|
|
|
|
... | ... | @@ -70,9 +67,9 @@ users-details |
|
|
```
|
|
|
|
|
|
|
|
|
# The class-based views, generics and viewsets
|
|
|
# 3 - The class-based views, generics and viewsets
|
|
|
|
|
|
## The class-based views
|
|
|
## 3.1 - The class-based views
|
|
|
|
|
|
[Official DRF documentation about views](https://www.django-rest-framework.org/api-guide/views/)
|
|
|
|
... | ... | @@ -107,7 +104,7 @@ view = MyView.as_view() |
|
|
```
|
|
|
|
|
|
|
|
|
## The generics
|
|
|
## 3.2 - The generics
|
|
|
|
|
|
[Official DRF documentation about Generics](https://www.django-rest-framework.org/api-guide/generic-views/)
|
|
|
|
... | ... | @@ -171,7 +168,7 @@ view = MyGeneric.as_view() |
|
|
```
|
|
|
|
|
|
|
|
|
## The viewsets
|
|
|
## 3.3 - The viewsets
|
|
|
|
|
|
[Official DRF documentation about viewsets](https://www.django-rest-framework.org/api-guide/viewsets/)
|
|
|
|
... | ... | @@ -216,18 +213,18 @@ urlpatterns = router.urls |
|
|
```
|
|
|
|
|
|
|
|
|
# The authentication classes
|
|
|
# 4 - The authentication classes
|
|
|
|
|
|
[Official DRF documentation about authentication](https://www.django-rest-framework.org/api-guide/authentication/)
|
|
|
|
|
|
To use the API, it is usually better for the user to be authenticated (except for specific pages like the root page) because it means that the actions can be tracked and the ACL checked. To do that DRF provides multiple methods. Re2o's API is only using two authentication method, they are defined in [the settings file](#the-settings-file)
|
|
|
|
|
|
## rest_framework.authentication.SessionAuthentication
|
|
|
## 4.1 - rest_framework.authentication.SessionAuthentication
|
|
|
|
|
|
This method checks the user's cookies to retrieve a valid *Django-auth*'s session. This method is useful to explore the API in a browser, because logging in via the standard login page, will set this cookie and thus also log in the API
|
|
|
|
|
|
|
|
|
## api.authentication.ExpiringTokenAuthentication
|
|
|
## 4.2 - api.authentication.ExpiringTokenAuthentication
|
|
|
|
|
|
This authentication method is using tokens passed in the `Authorization` field of the header of the request. One can get a token by making a POST request with valid credentials to a dedicated URL. See [the usage of the API](API/Raw usage) for more.
|
|
|
|
... | ... | @@ -236,13 +233,13 @@ This is custom subclass of *rest_framework.authentication.TokenAuthentication* w |
|
|
This methods uses the *rest_framework.authtoken.models.Token* model to store per user token in the database. Those tokens are only created if the user successfully log in once.
|
|
|
|
|
|
|
|
|
# The permission classes
|
|
|
# 5 - The permission classes
|
|
|
|
|
|
[Official DRF documentation about permission](https://www.django-rest-framework.org/api-guide/permissions/)
|
|
|
|
|
|
In addition to the authentication, DRF introduces a permission mechanism to restrict the user's possible actions. The provided DRF permissions classes are simple basic checks and did not fit into our custom ACL system, so custom permission classes were created in `api.permissions`.
|
|
|
|
|
|
## Selecting the permission classes
|
|
|
## 5.1 - Selecting the permission classes
|
|
|
|
|
|
By default, *api.permissions.AutodetectACLPermission* is used on all view as specified in [the settings file](#the-settings-file). But this behavior can be changed on specific views like that:
|
|
|
|
... | ... | @@ -268,7 +265,7 @@ class MyView1(APIView): |
|
|
```
|
|
|
|
|
|
|
|
|
## api.permissions.AutodetectACLPermission
|
|
|
## 5.2 - api.permissions.AutodetectACLPermission
|
|
|
|
|
|
This permission requires a `.queryset` or a `.get_queryset` method to be set on the view. It is used to determine the model against which to check the users's ACL. The ACL used are the one that makes the most sense according the HTTP method used (e.g. GET will lead to checking `model.can_view_all`, POST will lead to checking `model.can_create`).
|
|
|
|
... | ... | @@ -277,7 +274,7 @@ If the view is targeting an object precisely, DRF's generic views will additiona |
|
|
Moreover, for every request made, it is also checking that the user has the right to use the API by calling `api.can_use`.
|
|
|
|
|
|
|
|
|
## api.permissions.ACLPermission
|
|
|
## 5.3 - api.permissions.ACLPermission
|
|
|
|
|
|
For views that do not define a queryset or requires a different behavior, this permission can be used. Its behavior is intended to be more flexible and generic but the configuration is more complex. To use it, the view should have a `.perms_map` attribute defined. This attribute has a quite strict syntax but is still easy to read:
|
|
|
```python
|
... | ... | @@ -299,7 +296,7 @@ perms_map = { |
|
|
It is also important to note that this permission will also checks that the user can use the api by systematically calling `api.can_use` in addition to all other ACL functions. So there is no need to specify this ACL in the `.perms_map` attribute.
|
|
|
|
|
|
|
|
|
# The serializers
|
|
|
# 6 - The serializers
|
|
|
|
|
|
[Official DRF documentation about the serializers](https://www.django-rest-framework.org/api-guide/serializers/)
|
|
|
|
... | ... | @@ -312,7 +309,7 @@ The data that is extracted from the Django objects need to be formatted so the r |
|
|
:warning: Even though it might seems simple, be careful because it can quickly gets complicated because of the many options to specify what and how to render an object.
|
|
|
|
|
|
|
|
|
## Default behavior
|
|
|
## 6.1 - Default behavior
|
|
|
|
|
|
DRF provides some base serializers that have an automatic behavior on certain kind of fields. For example, for a *CharField*, the string is outputed ; for a *DateTimeField*, the date is casted to a string using [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) format ; for a *BooleanField*, the value is casted to `true` or `false` ; for an *IntegerField*, the value is casted to a string, ... Here is an example:
|
|
|
|
... | ... | @@ -349,12 +346,12 @@ print(json.dumps(seria.data), indent=2) |
|
|
For more complex Django fields such as *ForeignKey*, *OneToOneField* or *ManyToManyField*, there is also a default behavior which is detailed just below. For non-supported third-party fields, you will need either to find an extension that handle the serialization field or code yourself the *SerializerField*. Please refer to the [official DRF documentation on serializer fields](https://www.django-rest-framework.org/api-guide/fields/) for details.
|
|
|
|
|
|
|
|
|
## Django models relation serialization
|
|
|
## 6.2 - Django models relation serialization
|
|
|
|
|
|
Serializing the relation between Django models can be done in muliple ways and it the main difference between the different base serialization provided by DRF:
|
|
|
|
|
|
|
|
|
### rest_framework.serializers.ModelSerializer
|
|
|
### 6.2.1 - rest_framework.serializers.ModelSerializer
|
|
|
|
|
|
Serializing such relation results in outputing **the primary key** of the related model. Consider the following example:
|
|
|
|
... | ... | @@ -388,7 +385,7 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
### rest_framework.serializers.HyperlinkedModelSerializer
|
|
|
### 6.2.2 - rest_framework.serializers.HyperlinkedModelSerializer
|
|
|
|
|
|
Instead of simply outputting the `pk` field of the object and letting the user find the right url to use to get information on this related object, this serializer will use Django's URL system to resolve the url to use with this `pk` field and it will output the full URL. This means that the URL to see the details of the model need to exist. By default, the url used will be `resolve('<model_name>-details', kwargs={'pk': <pk>})` but this can be changed via some parameter to look for another URL name and use a different fileld fo the lookup. See the DRF documentation for those details. Here is an example of how it works:
|
|
|
|
... | ... | @@ -422,12 +419,12 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
### api.serializers.NamespacedHMSerializer
|
|
|
### 6.2.3 - api.serializers.NamespacedHMSerializer
|
|
|
|
|
|
This serializer is a simple subclass of `rest_framework.serializers.HyperlinkedModelSerializer` to support the use of namespaced URL (which is not supported because considered not useful for an API and also I think to complex to implement for any namespacing). This serializer will behave exactly as the other one but all URL resolving for related models is done in the namespace 'api', where are all Re2o's API URLs. Without this modification, either the namespacing need to be remove for the whole API or the serializer will fail resolving the URL, so consider using this serializer instead of `rest_framework.serializers.HyperlinkedModelSerializer`.
|
|
|
|
|
|
|
|
|
## Control the outputed field
|
|
|
## 6.3 - Control the outputed field
|
|
|
|
|
|
One can specify which fields to output by adding a `fields` attribute or an `exclude` attribute in the `Meta` class. The default is to output all fields (`fields = '__all__'`).
|
|
|
|
... | ... | @@ -464,7 +461,7 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
## Specifying a non-default serializer field
|
|
|
## 6.4 - Specifying a non-default serializer field
|
|
|
|
|
|
[Official DRF documentation of serializer fields](https://www.django-rest-framework.org/api-guide/fields/)
|
|
|
|
... | ... | @@ -505,7 +502,7 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
## Add custom serializer fields
|
|
|
## 6.5 - Add custom serializer fields
|
|
|
|
|
|
This custom addition of serializer field can also be used to add fields that do not correspond to a field in the model or to rename fields. Note that new serializer fields need to be explicitely include in the `fields` attribute. Example:
|
|
|
|
... | ... | @@ -559,7 +556,7 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
## Using serializers as serializer fields
|
|
|
## 6.6 - Using serializers as serializer fields
|
|
|
|
|
|
[Official DRF documentaiton on serializer relations](https://www.django-rest-framework.org/api-guide/relations/)
|
|
|
|
... | ... | @@ -607,7 +604,7 @@ print(json.dumps(seria.data), indent=2) |
|
|
```
|
|
|
|
|
|
|
|
|
## Usage in Re2o
|
|
|
## 6.7 - Usage in Re2o
|
|
|
|
|
|
All models currently have an associated serializer in `api.serializers` that output all the relevant data. Some subclass can be used to only output a subset of the fields or output differently a field if needed. Example:
|
|
|
|
... | ... | @@ -618,7 +615,7 @@ class OriginV4RecordSerializer(IpListSerializer): |
|
|
```
|
|
|
|
|
|
|
|
|
# The pagination
|
|
|
# 7 - The pagination
|
|
|
|
|
|
[Official DRF documentation on pagination](https://www.django-rest-framework.org/api-guide/pagination/)
|
|
|
|
... | ... | @@ -629,7 +626,7 @@ For the above reasons, the default behavior of the DRF generics views in Re2o is |
|
|
As the standard DRF pagination, the page is requested through the `page` argument in the query. The difference is that a custom page_size can be requested by the client. It can be any integer from 0 to 10000 (still limited for safety but should not be a problem in practice) or it can be the string `all` to specify the page should contains all the results. This option is too use carefully and only for optimizing specific request that are time-constrained.
|
|
|
|
|
|
|
|
|
# The format
|
|
|
# 8 - The format
|
|
|
|
|
|
[Official DRF documentation on formats](https://www.django-rest-framework.org/api-guide/format-suffixes/)
|
|
|
|
... | ... | @@ -638,7 +635,7 @@ DRF offers the possibility to handle multiple format in which output the data. T |
|
|
* **api**: A nicer HTML representation of the JSON API response to be displayed in browser.
|
|
|
|
|
|
|
|
|
# The settings file
|
|
|
# 9 - The settings file
|
|
|
|
|
|
The API app has its own setting file where all the settings about the API are set. This file is imported in the general setttings file only if `'api'` is in the `INSTALLED_APPS` variable. Thus the settings defined in the api can be easily retrieved via the the usual import `from django.conf import setttings`.
|
|
|
|
... | ... | @@ -652,7 +649,7 @@ This file contains the following variables: |
|
|
* **API_TOKEN_DURATION**: The expiration time of the token in seconds
|
|
|
|
|
|
|
|
|
# The tests
|
|
|
# 10 - The tests
|
|
|
|
|
|
The tests of the API do not cover the content but mostly that the endpoints are working. They mainly consist of performing a request on all endpoints in specific conditions (auth, perms, format) and check the HTTP status code returned corresponds to the one expected.
|
|
|
|
... | ... | |