# Django API Server

This sample explains how to extend an existing Django API Starter and add another model managed via a new API node.

* [Django API Server](/docs/boilerplate-code/api-server/django.md) - original project
* [Django API Server Sample](https://github.com/app-generator/api-server-django-sample) - the source code with new enhancements

## Codebase structure

```
PROJECT ROOT
├── api                           # App containing all project-specific apps
│   ├── apps.py
│   ├── authentication            # Implements authentication app logic (register, login, session) logic
│   │   ├── apps.py
│   │   ├── backends.py           # Handles the active session authentication
│   │   ├── migrations
│   │   ├── serializers
│   │   │   ├── login.py          # Handles the proccess of login for an user
│   │   │   └── register.py       # Handle the creation of a new user
│   │   ├── tests.py              # Test for login, registration and session
│   │   └── viewsets       
│   │       ├── active_session.py # Handles session check
│   │       ├── login.py          # Handles login
│   │       ├── logout.py         # Handles logout
│   │       └── register.py       # Handles registration
|   |
│   ├── fixtures                  # Package containg the project fixtures
│   ├── __init__.py
│   ├── routers.py                # Define api routes
│   └── user                      # Implements user app logic
│       ├── apps.py
│       ├── __init__.py
│       ├── migrations
│       ├── serializers.py       # Handle the serialization of user object
│       └── viewsets.py          # Handles  the modification of an user
|
├── core                         # Implements app logic      
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py              # Django app bootstrapper
│   ├── test_runner.py           # Custom  test runner
│   ├── urls.py    
│   └── wsgi.py
|
├── docker-compose.yml
├── Dockerfile
├── .env                         # Inject Configuration via Environment
├── manage.py                    # Starts the app
└── requirements.txt             # Contains development packages
```

## Used Patterns

Working with Django Rest Framework, the most common design pattern is the **Template Method Pattern**.

It mostly consists of providing base/skeleton for some features with the possibility to override/extends these skeletons.

For example, you can check the code in `api/user/viewsets.py`. The `UserViewSet` inherits of `viewsets.GenericsViewSet` and `CreateModelMixin` and `UpdateModelMixin`.

The `UpdateModelMixin` provides the logic to update an object using `PUT`.

We only need to rewrite the method which handles the `updating` and provides the `serializer_class` and the `permission_classes`.

## How to use the API

> POSTMAN usage

The API is actually built around these endpoints :

* `api/users/signup`
* `api/users/login`
* `api/users/edit`
* `api/users/checkSession`
* `api/users/logout`

> **Register** - `api/users/register`

```javascript
POST api/users/register
Content-Type: application/json

{
    "username":"test",
    "password":"pass", 
    "email":"test@appseed.us"
}
```

**Response :**

```javascript
{
    "success": true,
    "userID": 1,
    "msg": "The user was successfully registered"
}
```

> **Login** - `api/users/login`

```
cd api && django-admin startapp transactions
```

Once it's done, rewrite the `apps.py` file with the following content.

```
```

```javascript
POST /api/users/login
Content-Type: application/json

{
    "password":"pass", 
    "email":"test@appseed.us"
}
```

**Response :**

```javascript
{
    "success": true,
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjI4NzgxNTY4fQ.mB_YcZxZJd37r_4NYnLYeCByEHKGBC2ob0xe9KgcOII",
    "user": {
        "_id": 1,
        "username": "test",
        "email": "test@appseed.us"
    }
}
```

> **Logout** - `api/users/logout`

```javascript
POST api/users/logout
Content-Type: application/json
authorization: JWT_TOKEN (returned by Login request)

{
    "token":"JWT_TOKEN"
}
```

**Response :**

```javascript
{
    "success": true,
    "msg": "Token revoked"
}
```

> cURL usage

Let's edit information about the user and check a session using cURL.

> **Check Session**- `api/users/checkSession`

```
curl --request POST \
  --url http://127.0.0.1:8000/api/users/checkSession \
  --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjI4NzgxNTY4fQ.mB_YcZxZJd37r_4NYnLYeCByEHKGBC2ob0xe9KgcOII' \
  --header 'Content-Type: application/json'
```

**Response :**

```javascript
{
  "success": true
}
```

> **Edit User** - `api/users/edit`

```
curl --request POST \
  --url http://127.0.0.1:8000/api/users/edit \
  --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjI4NzgxNTY4fQ.mB_YcZxZJd37r_4NYnLYeCByEHKGBC2ob0xe9KgcOII' \
  --header 'Content-Type: application/json' \
  --data '{
	"username": "tester",
	"email": "test@admin.us",
	"userID": 1
}'
```

**Response :**

```javascript
{
  "success": true
}
```

## How to extend API

> Add a new model - transactions

To add a model for `transaction` in the project, let's create a new application in the `api` directory.

* Creating the app using `django-admin` command in the `api` directory.
* Then modify the name and the label of the app in `apps.py`
* And add the `app` in the `INSTALLED_APPS` in the `settings.py` of the project.

```
cd api && django-admin startapp transaction
```

Then modify the `apps.py` file.

```python
# api/transaction/apps.py

from django.apps import AppConfig


class TransactionConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'api.transaction'
    label = 'api_transaction'
```

And don't forget to add the `default_app_config` in the `__init__.py` file the `transaction` directory.

```python
# api/transaction/__init__.py

default_app_config = "api.transaction.apps.TransactionConfig"
```

We can now register the application in `settings.py` file.

```python
# core/settings.py

INSTALLED_APPS = [
    ...
    "api.authentication",
    "api.transaction"
]
```

> Add API interface to manage transactions

Creating an API interface to manage transactions usually go this way :

* Creating the model
* Creating the serializer
* Write the views or the viewsets
* Register the viewsets by creating routes

We've already created the model for `transaction`.

Let's create the [serializer](https://www.django-rest-framework.org/api-guide/serializers/).

A [serializer](https://www.django-rest-framework.org/api-guide/serializers/) allows us to convert complex Django complex data structures such as `querysets` or model instances in Python native objects that can be easily converted JSON/XML format, but a serializer also serializes JSON/XML to naive Python.

```python
# api/transaction/serializers.py

from api.transaction.models import Transaction
from rest_framework import serializers


class TransactionSerializer(serializers.ModelSerializer):
    info = serializers.CharField(allow_null=True, allow_blank=True)
    price = serializers.IntegerField(required=True)

    class Meta:
        model = Transaction
        fields = ["id", "product", "price", "qty", "discount", "info"]
        read_only_field = ["id", "created"]
```

And now the [viewsets](https://www.django-rest-framework.org/api-guide/viewsets/).

The routes for the transaction interface API should look like this :

* `api/transactions/create` -> create transaction
* `api/transactions/edit/id`-> edit transaction
* `api/transactions/delete/id` -> delete transaction
* `api/transactions/get/id` -> get specific transaction
* `api/transactions/get` -> get all transactions

The [ViewSet](https://www.django-rest-framework.org/api-guide/viewsets/#viewset) class comes with built-in [actions](https://www.django-rest-framework.org/api-guide/viewsets/#viewset-actions) :

* list
* retrieve
* create
* update
* partial\_update
* destroy

And to make sure the names of the URLs match what we need, we'll be using [actions](https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing).

First of all, create a file name `viewsets` in the `transactions` directory.

And add the following code.

```python
# api/transactions/viewsets.py

from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, NotFound, MethodNotAllowed
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated

from django.core.exceptions import ObjectDoesNotExist

from api.transaction.models import Transaction
from api.transaction.serializers import TransactionSerializer


class TransactionViewSet(viewsets.ModelViewSet):
    serializer_class = TransactionSerializer
    permission_classes = (IsAuthenticated,)
    http_method_names = ['get', 'post']
    
    not_found_message = {"success": False, "msg": "This object doesn't exist."}
    missing_id_message = {"success": False, "msg": "Provide a transaction id."}
```

Then let's rewrite the `get_queryset` method. This method is used by the viewset to return a list of objects, here a list of transactions.

```python
class TransactionViewSet(viewsets.ModelViewSet):
    ...

    def get_queryset(self):
        return Transaction.objects.all()
```

Great. Now, let's make sure DRF will exactly match the URLs we want. First of all, we have to block the default routes.

```python
class TransactionViewSet(viewsets.ModelViewSet):
    ...

    def list(self, request, *args, **kwargs):
        raise MethodNotAllowed('GET')

    def create(self, request, *args, **kwargs):
        raise MethodNotAllowed('POST')

    def destroy(self, request, *args, **kwargs):
        raise MethodNotAllowed('DELETE')

    def update(self, request, *args, **kwargs):
        raise MethodNotAllowed('PUT')

    def retrieve(self, request, *args, **kwargs):
        raise MethodNotAllowed('GET')
```

And we can write our own actions now.

Let's start with `api/transactions/create`.

```python
class TransactionViewSet(viewsets.ModelViewSet):
    ...

    @action(methods=['post'], detail=False, url_path='create')
    def create_transaction(self, request, *args, **kwargs):
        data = request.data

        serializer = self.serializer_class(data=data)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response({
            "success": True
        }, status.HTTP_201_CREATED)
```

To avoid name collision with the default built-in method `create` , we are naming the method `create_transaction`. Hopefully, DRF provides the option to specify the `url_path` of the method.

Let's write the actions for `api/transactions/get` and `api/transactions/get/id`

```python
class TransactionViewSet(viewsets.ModelViewSet):
    ...
    
    @action(methods=['get'], detail=False, url_path='get')
    def get_transactions(self, request, *args, **kwargs):

        transactions = self.get_queryset()

        page = self.paginate_queryset(transactions)

        if page is not None:
            serializer = self.get_serializer(page, many=True, context=self.get_serializer_context())
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(transactions, many=True, context=self.get_serializer_context())
        return Response({
            "success": True,
            "transactions": serializer.data
        }, status=status.HTTP_200_OK)

    @action(methods=['get'], detail=False, url_path=r'get/(?P<transaction_id>\w+)')
    
    def get_transaction(self, request, transaction_id=None, *args, **kwargs):

        if transaction_id is None:
            raise ValidationError(self.missing_id_message)

        try:
            obj = Transaction.objects.get(pk=transaction_id)
        except ObjectDoesNotExist:
            raise NotFound(self.not_found_message)

        serializer = self.get_serializer(obj)

        return Response({
            "success": True,
            "transaction": serializer.data
        }, status=status.HTTP_200_OK)
```

Notice that for the `get/id` (`get_transaction`), we are writing the `url_path` using **regex expression**.

And finally, the actions for `api/transactions/delete/id` and `api/transactions/edit`.

```python
class TransactionViewSet(viewsets.ModelViewSet):
    ...
    @action(methods=['post'], detail=False, url_path=r'edit/(?P<transaction_id>\w+)')
    def edit_transaction(self, request, transaction_id=None, *args, **kwargs):

        if transaction_id is None:
            raise ValidationError(self.missing_id_message)

        try:
            obj = Transaction.objects.get(pk=transaction_id)
        except ObjectDoesNotExist:
            raise NotFound(self.not_found_message)

        serializer = self.get_serializer(obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(obj, "_prefetched_objects_cache", None):
            obj._prefetched_objects_cache = {}

        return Response({
            "success": True
        }, status.HTTP_200_OK)

    @action(methods=['post'], detail=False, url_path=r'delete/(?P<transaction_id>\w+)')
    def delete_transaction(self, request, transaction_id=None, *args, **kwargs):

        if transaction_id is None:
            raise ValidationError(self.missing_id_message)

        try:
            obj = Transaction.objects.get(pk=transaction_id)
        except ObjectDoesNotExist:
            raise NotFound(self.not_found_message)

        obj.delete()

        return Response({
            "success": True
        }, status.HTTP_200_OK)
```

Now, we can register the viewset.

There is already a `routers.py` file which contains the routes for `api/users`.

Let's create a different [router](https://www.django-rest-framework.org/api-guide/routers/) for transactions.

In the `api` directory, create a new package called `routers`. Move the file `routers.py` in it and rename it to `users.py`.

Then create a new file named `transactions.py` and add the following code to register `TransactionViewSet` actions.

```python
# api/routers/transactions.py

from rest_framework import routers

from api.transaction.viewsets import TransactionViewSet

router = routers.SimpleRouter(trailing_slash=False)

router.register('', TransactionViewSet, basename='transactions')

urlpatterns = [
    *router.urls,
]
```

And the last step, open the urls.py file in the core directory and add the transaction router.

```python
# core/urls.py

from django.urls import path, include

urlpatterns = [
    path("api/users/", include(("api.routers.users", "api-users"), namespace="api-users")),
    path("api/transactions/", include(("api.routers.transactions", "api-transactions"), namespace="api-transactions"))
]
```

And that's it. You can start testing the API with Postman.

Congratulations. You just learned :

* How to add a new model;
* How to add a serializer for this model;
* How to rewrite `viewset` behavior and `actions` to match your needs.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://appseed.gitbook.io/docs/free-samples/django/api-server-sample.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
