---
# System prepended metadata

title: '[5]Django Rest API + React.js + Redux + webpack 前後端整合'
tags: [redux, Django, webpack, python, React.js, Django Rest framework]

---

# [5]Django Rest API + React.js + Redux + webpack 前後端整合

###### tags: `python` `Django` `Django Rest framework`  `React.js` `redux` `webpack`
> [time= 2019 12 01 ] 

> 原文 & 參考：
> https://www.youtube.com/watch?v=0d7cIfiydAc&list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60&index=5

<br>


## Django Token Authentication
開啟 `./leadmanager/leads/models.py` 在裡面加入：
```python=
from django.db import models
from django.contrib.auth.models import User # new add


class Lead(models.Model):
    ...
    created_at = models.DateTimeField(auto_now_add=True)
    # new add
    owner = models.ForeignKey(
        User, related_name="leads", on_delete=models.CASCADE, null=True
    )
```

<br><br><br>

*終端機目前位置 `~/django_rest_api_react/leadmanager`*
執行下列指令：
```=
$ python manage.py makemigrations
```
```
Migrations for 'leads':
  leads/migrations/0002_lead_owner.py
    - Add field owner to lead
```

<br><br><br>

```=
$ python manage.py migrate
```
```
 Operations to perform:
   Apply all migrations: admin, auth, contenttypes, leads, sessions
 Running migrations:
   Applying leads.0002_lead_owner... OK
```
*重新 run server*

<br><br><br>

開啟 `./leadmanager/leads/api.py` 全部改成下方程式：
```python=
from leads.models import Lead
from rest_framework import viewsets, permissions
from .serializers import LeadSerializer

# Lead Viewsets
class LeadViewSet(viewsets.ModelViewSet):
    permission_classes = [
        permissions.IsAuthenticated
    ]

    serializer_class = LeadSerializer

    def get_queryset(self):
        return self.request.user.leads.all()
    
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
```

重新刷新頁面後，之前新增的資料都會不見
![](https://i.imgur.com/QaBQjxN.png)

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/messages.js` 在裡面加入：
```javascript=
import { CREATE_MESSAGE, GET_ERRORS } from './types'; // modify

export const creatMessage = msg => {
    ...
};

// new add
export const returnErrors = (msg, status) => {
    return {
        type: GET_ERRORS,
        payload: { msg, status }
    };
};
```

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/leads.js` 修改這些地方：
```javascript=
import axios from 'axios';
import { GET_LEADS, DELETE_LEAD, ADD_LEAD } from './types'; // delete GET_ERRORS
import { creatMessage, returnErrors } from './messages'; // modify

export const getLeads = () => dispatch => {
    axios.get('/api/leads')
        .then(res => {
            ...
        })
        // modify
        .catch(err => dispatch(returnErrors(err.response.data, err.response.status)));
};

export const deleteLead = (id) => dispatch => {
    ...
};

export const addLead = lead => dispatch => {
    axios.post('/api/leads/', lead)
        .then(res => {
            ...
        })
        // modify
        .catch(err => dispatch(returnErrors(err.response.data, err.response.status)));
};
```

現在重新刷新頁面和按 submit 按鈕，就會取得錯誤訊息
(如果沒有取得錯誤訊息，先清除 chrome 的 cookies，再重新刷新)
![](https://i.imgur.com/WcavQsW.png)

<br><br><br>

開啟 `./leadmanager/leadmanager/settings.py` 在裡面加入：
```python=
...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'leads',
    'frontend',
    'knox', # new add
]

# new add
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',)
}

...
```

<br><br><br>

執行下列命令：
```=
$ python manage.py migrate
```

<br><br><br>

### accounts app
*終端機目前位置 `~/django_rest_api_react/leadmanager`*
執行下列指令：
```=
$ python manage.py startapp accounts
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──accounts
│  │  ├──migrations
│  │  ├──__init__.py
│  │  ├──admin.py
│  │  ├──apps.py
│  │  ├──models.py
│  │  ├──tests.py
│  │  └──views.py
│  │  
│  ├──frontend
│  ├──leadmanager
│  ├──leads
│  ├──db.sqlite3
│  └──manage.py
│
├──node_modules
├──.babelrc
├──package-lock.json
├──package.json
├──Pipfile
├──Pipfile.lock
└──webpack.config.js
```

<br><br><br>

開起 `./leadmanager/leadmanager/settings.py` 加入應用程式 `accounts` 在 `INSTALLED_APPS`:
```python=
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'leads',
    'frontend',
    'knox',
    'accounts', # new add
]
```

<br><br><br>

### registerAPI
新增一個檔案命名為` ./leadmanager/accounts/serializers.py` 在裡面寫入：
```python=
from rest_framework import serializers
from django.contrib.auth.models import User
from django.contrib.auth import authenticate


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "email")


class RegisterSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "email", "password")
        extra_kwrgs = {"password": {"write_only": True}}

    def create(self, validated_data):
        user = User.objects.create_user(
            validated_data["username"],
            validated_data["email"],
            validated_data["password"],
        )

        return user
```

<br><br><br>

新增一個檔案命名為` ./leadmanager/accounts/api.py` 在裡面寫入：
```python=
from rest_framework import generics, permissions
from rest_framework.response import Response
from knox.models import AuthToken
from .serializers import UserSerializer, RegisterSerializer

class RegisterAPI(generics.GenericAPIView):
    serializer_class = RegisterSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()

        return Response({
            "user": UserSerializer(user, context = self.get_serializer_context).data,
            "token": AuthToken.objects.create(user)[1]
        })
```

<br><br><br>

新增一個檔案命名為` ./leadmanager/accounts/urls.py` 在裡面寫入：
```python=
from django.urls import path, include
from .api import RegisterAPI
from knox import views as knox_views

urlpatterns = [
    path('api/auth/', include('knox.urls')),
    path('api/auth/register', RegisterAPI.as_view())
]
```

<br><br><br>

開啟 `./leadmanager/leadmanager/urls.py` 加入：
```python=
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('frontend.urls')),
    path('', include('leads.urls')),
    path('', include('accounts.urls')), # new add
]
```

<br><br><br>

前往 http://127.0.0.1:8000/api/auth/register 註冊一個帳號
![](https://i.imgur.com/LWt7qnN.png)

<br><br><br>

### loging API
開啟 `./leadmanager/accounts/serializers.py` 加入：
```python=
...

class UserSerializer(serializers.ModelSerializer):
    ...

class RegisterSerializer(serializers.ModelSerializer):
    ...

# new add
class LogingSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def validate(self, data):
        user = authenticate(**data)
        if user and user.is_active:
            return user

        raise serializers.ValidationError("Incorrect Credentials")
```

<br><br><br>

開啟 `./leadmanager/accounts/api.py` 加入：
```python=
...
from knox.models import AuthToken
# modify
from .serializers import UserSerializer, RegisterSerializer, LogingSerializer

class RegisterAPI(generics.GenericAPIView):
    ...

# new add
class LoginAPI(generics.GenericAPIView):
    serializer_class = LogingSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data

        return Response({
            "user": UserSerializer(user, context = self.get_serializer_context).data,
            "token": AuthToken.objects.create(user)[1]
        })
```

<br><br><br>

開啟 `./leadmanager/accounts/urls.py` 加入：
```python=
from django.urls import path, include
from .api import RegisterAPI, LoginAPI # modify
from knox import views as knox_views

urlpatterns = [
    path('api/auth/', include('knox.urls')),
    path('api/auth/register', RegisterAPI.as_view()),
    path('api/auth/login', LoginAPI.as_view()) # new add
]
```

<br><br><br>

現在前往 http://127.0.0.1:8000/api/auth/login 登入看看
![](https://i.imgur.com/k9tVeeN.png)

<br><br><br>

### get API
開啟 `./leadmanager/accounts/api.py` 加入：
```python=
...

class RegisterAPI(generics.GenericAPIView):
    ...

class LoginAPI(generics.GenericAPIView):
    ...

# new add
class UserAPI(generics.RetrieveAPIView):
    permission_classes = [
        permissions.IsAuthenticated,
    ]        

    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user
```

<br><br><br>

開啟 `./leadmanager/accounts/urls.py` 加入：
```python=
from django.urls import path, include
from .api import RegisterAPI, LoginAPI, UserAPI # modify
from knox import views as knox_views

urlpatterns = [
    path('api/auth/', include('knox.urls')),
    path('api/auth/register', RegisterAPI.as_view()),
    path('api/auth/login', LoginAPI.as_view()),
    path('api/auth/user', UserAPI.as_view()) # new add
]
```

<br><br><br>

使用 [Postman](https://www.getpostman.com/) 
選擇 `GET` 網址填入 `http://localhost:8000/api/auth/user`
`Headers` 設定：
```
key: Authorization
Valur: Token 你登入成功所傳回的token
(Token 8c44588ed646be68851a7d4a25e7c7c88415e61de6b7f722306e1ff768a5dc80)
```
按 `Send`，會利用這個 Token 取得登入者的資訊
![](https://i.imgur.com/A8cH8ER.png)

<br><br><br>

### logout 
開啟 `./leadmanager/accounts/urls.py` 加入：
```python=
...

urlpatterns = [
    ...
    path('api/auth/user', UserAPI.as_view()),
    # new add
    path('api/auth/logout', knox_views.LogoutView.as_view(), name='knox_logout')
]
```

<br><br><br>

選擇 `POST` 網址填入 `http://localhost:8000/api/auth/logout`
`Headers` 設定：
```
key: Authorization
Valur: Token 8c44588ed646be68851a7d4a25e7c7c88415e61de6b7f722306e1ff768a5dc80
```
按 `Send`
![](https://i.imgur.com/wety7cf.png)

<br><br>

登出成功後，再一次取得 User 就會得到下列訊息
![](https://i.imgur.com/v8DvgNO.png)

<br><br><br>

[[6]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/Hk1LUJK6r)
[[4]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/B1Wg2Egpr)