# [7]Django Rest API + React.js + Redux + webpack 前後端整合 ###### tags: `python` `Django` `Django Rest framework` `React.js` `redux` `webpack` > [time= 2019 12 08 ] > 原文 & 參考: > https://www.youtube.com/watch?v=kfpY5BsIoFg&list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60&index=7 <br> ## Frontend Authentication ### login 開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入: ```javascript= ... export const AUTH_ERROR = 'AUTH_ERROR'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; // new add export const LOGIN_FAIL = 'LOGIN_FAIL'; // new add ``` <br><br><br> 開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入: ```javascript= import axios from 'axios'; import { returnErrors } from './messages'; // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL } from './types'; // CHECK TOKEN & LOAD USER export const loadUser = () => (dispatch, getState) => { ... } // new add // LOGIN USER export const login = (username, password) => dispatch => { const config = { headers: { 'Content-Type': 'application/json' } }; // Request Body const body = JSON.stringify({ username, password }); axios.post("/api/auth/login", body, config).then(res => { dispatch({ type: LOGIN_SUCCESS, payload: res.data }); }).catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: LOGIN_FAIL }); }) } ``` <br><br><br> 開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入: ```javascript= // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS } from '../actions/types'; const initialState = { ... }; export default function (state = initialState, action) { switch (action.type) { case USER_LOADING: ... case USER_LOADED: ... // new add case LOGIN_SUCCESS: localStorage.setItem('token', action.payload.token); return { ...state, ...action.payload, isAuthenticated: true, isLoading: false } case AUTH_ERROR: case LOGIN_FAIL: // new add localStorage.removeItem('token'); ... default: return state } } ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/accounts/Login.js` 在裡面寫入: ```javascript= import React, { Component } from 'react'; import { Link, Redirect } from 'react-router-dom'; // modify import { connect } from 'react-redux'; // new add import PropTypes from 'prop-types'; // new add import { login } from '../../actions/auth'; // new add export class Login extends Component { state = { ... }; // new add static propTypes = { login: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool } onSubmit = e => { e.preventDefault(); // modify this.props.login(this.state.username, this.state.password); } onChange = e => this.setState({ [e.target.name]: e.target.value }); render() { // new add if(this.props.isAuthenticated){ return <Redirect to="/" />; } const { username, password } = this.state; return ( ... ) } } // new add const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated }); export default connect(mapStateToProps, { login })(Login); // modify ``` 現在可以用登入頁面登入,畫面會自動轉跳到 Add Lead <br><br><br> ### logout 開啟 `./leadmanager/frontend/src/components/layout/Header.js` 在裡面加入: ```javascript= import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; // new add import PropTypes from 'prop-types'; // new add import { logout } from '../../actions/auth'; // new add export class Header extends Component { // new add static propTypes = { auth: PropTypes.object.isRequired, logout: PropTypes.func.isRequired }; render() { // new add const { isAuthenticated, user } = this.props.auth; // new add const authLinks = ( <ul className="navbar-nav ml-auto mt-2 mt-lg-0"> <span className="navbar-text mr-3"> <strong> {user ? `Welcom ${user.username}` : ''} </strong> </span> <button className="nav-link btn btn-info btn-sm text-light" onClick={this.props.logout}>Logout</button> </ul> ); // new add const guestLinks = ( <ul className="navbar-nav ml-auto mt-2 mt-lg-0"> <li className="nav-item"> <Link to="/register" className="nav-link">Register</Link> </li> <li className="nav-item"> <Link to="/login" className="nav-link">Login</Link> </li> </ul> ); return ( ... <div className="collapse navbar-collapse" id="navbarTogglerDemo01"> <a className="navbar-brand" href="#">Lead Manager</a> // modify {isAuthenticated ? authLinks : guestLinks} </div> ... ) } } // new add const mapStateToProps = state => ({ auth: state.auth }); export default connect(mapStateToProps, { logout })(Header); // modify ``` <br><br><br> 開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入: ```javascript= ... export const LOGIN_FAIL = 'LOGIN_FAIL'; export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; // new add ``` <br><br><br> 開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入: ```javascript= import axios from 'axios'; import { returnErrors } from './messages'; // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT_SUCCESS } from './types'; // CHECK TOKEN & LOAD USER export const loadUser = () => (dispatch, getState) => { ... } // LOGIN USER export const login = (username, password) => dispatch => { ... } // new add // LOGOUT USER export const logout = () => (dispatch, getState) => { // Get token from state const token = getState().auth.token; const config = { headers: { 'Content-Type': 'application/json' } } // If token, add to headers config if (token) { config.headers['Authorization'] = `Token ${token}`; } axios .post("/api/auth/logout", null, config) .then(res => { dispatch({ type: LOGOUT_SUCCESS }); }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); }) } ``` <br><br><br> 開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入: ```javascript= // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS, LOGOUT_SUCCESS } from '../actions/types'; const initialState = { ... }; export default function (state = initialState, action) { switch (action.type) { ... case LOGIN_SUCCESS: ... case AUTH_ERROR: case LOGIN_FAIL: case LOGOUT_SUCCESS: // new add localStorage.removeItem('token'); return { ...state, token: null, user: null, isAuthenticated: false, isLoading: false } default: return state } } ``` 現在可以按登出鈕來登出 <br><br><br> 開啟 `./leadmanager/frontend/src/components/layout/Alerts.js` 在裡面加入: ```javascript= ... export class Alerts extends Component { static propTypes = { ... }; componentDidUpdate(prevProps) { if(error !== prevProps.error ){ ... if (error.msg.message) alert.error(`Message: ${error.msg.message.join()}`); // new add if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join()); } if (message !== prevProps.message) { ... ``` 當你輸入錯誤時會顯示錯誤訊息 ![](https://i.imgur.com/aQHoOQ2.png) <br><br><br> 開啟 `./leadmanager/frontend/src/actions/auth.js` 改寫成: ```javascript= ... // CHECK TOKEN & LOAD USER export const loadUser = () => (dispatch, getState) => { // User Loading dispatch({ type: USER_LOADING }); // delete token, config, if (token) axios .get("/api/auth/user", tokenConfig(getState)) // modify .then(...) } // LOGIN USER export const login = (username, password) => dispatch => { ... } // LOGOUT USER export const logout = () => (dispatch, getState) => { // delete token, config, if (token) axios .post("/api/auth/logout", null, tokenConfig(getState)) // modify .then(...) } // new add // Setup config with token - helper funcrion export const tokenConfig = getState => { // Get token from state const token = getState().auth.token; const config = { headers: { 'Content-Type': 'application/json' } } // If token, add to headers config if (token) { config.headers['Authorization'] = `Token ${token}`; } return config; } ``` <br><br><br> ### register 開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入: ```javascript= ... export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; // new add export const REGISTER_FAIL = 'REGISTER_FAIL'; // new add ``` <br><br><br> 開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入: ```javascript= import axios from 'axios'; import { returnErrors } from './messages'; // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT_SUCCESS, REGISTER_SUCCESS, REGISTER_FAIL } from './types'; // CHECK TOKEN & LOAD USER export const loadUser = () => (dispatch, getState) => { ... } // LOGIN USER export const login = (username, password) => dispatch => { ... } // new add // REGISTER USER export const register = ({username, password, email}) => dispatch => { const config = { headers: { 'Content-Type': 'application/json' } }; // Request Body const body = JSON.stringify({ username, email, password }); axios .post("/api/auth/register", body, config) .then(res => { dispatch({ type: REGISTER_SUCCESS, payload: res.data }); }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: REGISTER_FAIL }); }) } // LOGOUT USER export const logout = () => (dispatch, getState) => { ... } // Setup config with token - helper funcrion export const tokenConfig = getState => { ... } ``` <br><br><br> 開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入: ```javascript= // modify import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS, LOGOUT_SUCCESS, REGISTER_SUCCESS, REGISTER_FAIL } from '../actions/types'; const initialState = { ... }; export default function (state = initialState, action) { switch (action.type) { case USER_LOADING: ... case USER_LOADED: ... case LOGIN_SUCCESS: case REGISTER_SUCCESS: // new add localStorage.setItem('token', action.payload.token); ... case AUTH_ERROR: case LOGIN_FAIL: case LOGOUT_SUCCESS: case REGISTER_FAIL: // new add localStorage.removeItem('token'); ... default: return state } } ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/accounts/Register.js` 在裡面寫入: ```javascript= import React, { Component } from 'react'; import { Link, Redirect } from 'react-router-dom'; // modify import { connect } from 'react-redux'; // new add import PropTypes from 'prop-types'; // new add import { register } from '../../actions/auth'; // new add import { creatMessage } from '../../actions/messages'; // new add export class Register extends Component { state = { ... }; // new add static propTypes = { register: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool } onSubmit = e => { e.preventDefault(); // new add const { username, email, password, password2 } = this.state; if (password !== password2) { this.props.creatMessage({ passwordNotMatch: "Password do not match" }); }else{ const newUser = { username, password, email }; this.props.register(newUser); } } onChange = e => this.setState({ [e.target.name]: e.target.value }); render() { // new add if(this.props.isAuthenticated){ return <Redirect to="/" />; } const { username, email, password, password2 } = this.state; return ( ... ) } } // new add const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated }); // modify export default connect(mapStateToProps, { register, creatMessage })(Register); ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/layout/Alerts.js` 在裡面加入: ```javascript= ... export class Alerts extends Component { static propTypes = { ... }; componentDidUpdate(prevProps) { const { error, alert, message } = this.props; if (error !== prevProps.error) { ... if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join()); // new add if (error.msg.username) alert.error(error.msg.username.join()); } if (message !== prevProps.message) { if (message.deleteLead) alert.success(message.deleteLead); if (message.addLead) alert.success(message.addLead); // new add if (message.passwordNotMatch) alert.error(message.passwordNotMatch); } }; ``` 現在註冊新帳號,成功後頁面自動轉跳到 Add Lead <br><br><br> 在註冊時密碼輸入不一樣 ![](https://i.imgur.com/oEoiKl9.png) <br><br><br> 註冊已存在的User ![](https://i.imgur.com/fQ8tige.png) <br><br><br> ### handel leads 開啟 `./leadmanager/frontend/src/actions/leads.js` 在裡面加入: ```javascript= ... import { creatMessage, returnErrors } from './messages'; import { tokenConfig } from './auth'; // new add export const getLeads = () => (dispatch, getState) => { // modify axios.get('/api/leads', tokenConfig(getState)) // modify .then(...) export const deleteLead = (id) => (dispatch, getState) => { // modify axios.delete(`/api/leads/${id}`, tokenConfig(getState)) // modify .then(...) }; export const addLead = lead => (dispatch, getState) => { // modify axios.post('/api/leads/', lead, tokenConfig(getState)) // modify .then(...) }; ``` 現在你在各自的帳號做新增刪除都不會互相影響了。 ### END <br><br><br> [[6]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/Hk1LUJK6r)