# [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) {
...
```
當你輸入錯誤時會顯示錯誤訊息

<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>
在註冊時密碼輸入不一樣

<br><br><br>
註冊已存在的User

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