... | ... | @@ -32,9 +32,9 @@ Of course, the best would still be to go trough [Django Rest Framework (DRF)'s t |
|
|
|
|
|
# 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.
|
|
|
The API uses a specific ACL called "can_use_api" to determine if a user is authorized to use the API (both for reading and modifying data). The way to define it is a bit tricky and is different from the usual ACL because django-auth (which manages the ACL and groups) needs to associate a permission to a "content-type" which itself needs to be associated with an app name and a model name. However, this ACL is not model-based but app-based.
|
|
|
|
|
|
So the idea here is to create a fake permission object, associated with a fake content-type object wich represents the api. The app and model name and the label of this fake permission are defined in the [settings of the API](#the-settings-file). To be sure this permission exists as soon as the API is activated, [api/acl.py](https://gitlab.federez.net/federez/re2o/tree/master/api/acl.py) is creating it if not in database at the import step.
|
|
|
So the idea here is to create a fake permission object, associated with a fake content-type object which represents the api. The app and model name and the label of this fake permission are defined in the [settings of the API](#the-settings-file). To be sure this permission exists as soon as the API is activated, [api/acl.py](https://gitlab.federez.net/federez/re2o/tree/master/api/acl.py) is creating it if not in database at the import step.
|
|
|
|
|
|
That way, to check if a user has the right to use the API, one can easily run as for any other standard app:
|
|
|
```python
|
... | ... | @@ -49,7 +49,7 @@ api.acl.can_view(user) |
|
|
|
|
|
The URL system mainly works the same way as for any other app: a path or regular expression associated with a view (and some namespacing options) are provided in a `urlpatterns` variable.
|
|
|
|
|
|
The difference lies in the use of a "router" to build most of those urlpatterns. A router is an object that automatically register [viewsets](#the-viewsets) and generates URLs based on it. It also builds a root page that presents all the registered URLs in a nice way. The `DefaultRouter` which is the most current DRF router used unfortunately is only meant to register viewsets thus only the URLs that corresponds to the viewsets and not the "more simple" views. To fix that a custom router `api.routers.AllViewsRouter` has been added. It acts nearly the same as the `DefaultRouter` except that one can do the following to register views:
|
|
|
The difference lies in the use of a "router" to build most of those urlpatterns. A router is an object that automatically registers [viewsets](#the-viewsets) and generates URLs based on it. It also builds a root page that presents all the registered URLs in a nice way. The `DefaultRouter` which is the most current DRF router is only meant to register viewsets and thus, only the URLs that corresponds to the viewsets and not the "more simple" views. To fix that, a custom router `api.routers.AllViewsRouter` has been added. It acts nearly the same as the `DefaultRouter` except that one can do the following to register views:
|
|
|
* For a view:
|
|
|
```python
|
|
|
router.register_view(r'<pattern>', path.to.view, name='<optional_name_to_refer_to>')
|
... | ... | @@ -78,7 +78,7 @@ users-details |
|
|
|
|
|
[Official DRF documentation about views](https://www.django-rest-framework.org/api-guide/views/)
|
|
|
|
|
|
The views even they could be managed by standard views defined as functions, it is highly advised to use class-based views instead because it means you can use pre-defined DRF's views objects as a base and not care about multiple points that are automated as soon as your view is subclassing `rest_framework.views.APIView` or one of its subclass:
|
|
|
Even if the views could be managed as usual, it is highly advised to use class-based views instead because it means you can use pre-defined DRF's views objects as a base and not to care about multiple points that are automated as soon as your view is subclassing `rest_framework.views.APIView` or one of its subclass:
|
|
|
* The `view.as_view()` method provides a view adapted for the API (CSRF exempted, ...)
|
|
|
* The handling of [authentication](#the-authentication-classes), HTTP methods authorized and [permissions](#the-permission-classes) are automatically done under the hood and the code left is smaller and simpler to understand.
|
|
|
|
... | ... | @@ -177,7 +177,7 @@ view = MyGeneric.as_view() |
|
|
|
|
|
[Official DRF documentation about viewsets](https://www.django-rest-framework.org/api-guide/viewsets/)
|
|
|
|
|
|
To go even further in the automation of exposing Django's model, DRF uses viewsets. The goal of a standard Django's viewset is to group similar view together to make the code more understandable while avoiding duplication of code. In the context of API, a very common use of viewsets is to expose a model by providing one URL to act on the whole queryset (list and create objects) and another URL to act on each of the objects (details, edit, delete). Do automate that DRF provides sone viewsets that handles nearly everything by grouping some pre-defined generics together:
|
|
|
To go even further in the automation of exposing Django's model, DRF uses viewsets. The goal of a standard Django's viewset is to group similar view together to make the code more understandable while avoiding duplication of code. In the context of API, a very common use of viewsets is to expose a model by providing one URL to act on the whole queryset (list and create objects) and another URL to act on each of the objects (details, edit, delete). To automate that, DRF provides sone viewsets that handles nearly everything by grouping some pre-defined generics together:
|
|
|
|
|
|
* **ModelViewSet**: Exposes a Django's model by providing two URL:
|
|
|
* *\<url_base\>/*: use GET and POST method to edit the whole queryset
|
... | ... | @@ -222,7 +222,7 @@ urlpatterns = router.urls |
|
|
|
|
|
[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)
|
|
|
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)
|
|
|
|
|
|
## 4.1 - rest_framework.authentication.SessionAuthentication
|
|
|
|
... | ... | @@ -231,9 +231,9 @@ This method checks the user's cookies to retrieve a valid *Django-auth*'s sessio |
|
|
|
|
|
## 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.
|
|
|
This authentication method uses 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.
|
|
|
|
|
|
This is custom subclass of *rest_framework.authentication.TokenAuthentication* with the slight change that produced tokens expires after a certain delay (defined in [the settings file](#the-settings-file)) and need to be renewed.
|
|
|
This is a custom subclass of *rest_framework.authentication.TokenAuthentication* with the slight change that produced tokens expires after a certain delay (defined in [the settings file](#the-settings-file)) and need to be renewed.
|
|
|
|
|
|
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.
|
|
|
|
... | ... | |