# [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)
```
重新刷新頁面後,之前新增的資料都會不見

<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,再重新刷新)

<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 註冊一個帳號

<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 登入看看

<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 取得登入者的資訊

<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`

<br><br>
登出成功後,再一次取得 User 就會得到下列訊息

<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)