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