# Django Rest Framework + React.js + webpack 前後端整合 [simple]
###### tags: `python` `Django Rest framework` `React.js` `webpack`
> [time= 2019 11 06 ]
原本文章都講解得非常清楚,有興趣的可以看看
兩種教學的建置方式不同,此文主要參考文章
> 原文 & 參考:
> https://www.valentinog.com/blog/drf/
<br>
使用 Mac 建置全端環境:
找一個你想建立此專案的位置
此範例位置 `~/Documents/Code/Python/django_example/`
開啟命令終端機查看 python 版本,輸入
```=
$ python --version
```
>*Python 3.7.3*
<br><br><br>
## setting up a Python virtual environment, and the project
建置 python 虛擬環境
```=
$ cd ~/Documents/Code/Python/django_example/
$ python3 -m venv VenvDjango
```
```
django_example
└──VenvDjango
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
進入 VenvDjango 資料夾,並啟動 VenvDjango 虛擬環境
```=
$ cd VenvDjango/ && source bin/activate
```
<br><br><br>
建立 django-drf-react-quickstart 資料夾,並前往此資料夾
```=
$ mkdir django-drf-react-quickstart && cd $_
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
安裝 Django and Django REST framework
```=
$ pip install django djangorestframework
```
<br><br><br>
查看 django-admin 版本
```=
$ django-admin --version
```
>*2.2.6*
<br><br><br>
建立一個 Django project
```=
$ django-admin startproject project
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──project
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
## bulding a Django application
*終端機目前位置`~/django-drf-react-quickstart/project/`*
新增一個你要執行的 Django 應用程式
```=
$ cd project
$ django-admin startapp leads
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──project
│ │ ├──leads
│ │ ├──project
│ │ └──manage.py
│ │
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
開起`./project/settings.py` 加入應用程式 leads 在 INSTALLED_APPS:
```python=
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'leads', # add the leads app
]
```
<br><br><br>
## creating a Django model
*終端機目前位置`~/django-drf-react-quickstart/project/`*
開啟 `./leads/models.py` 並新增 Lead model:
```python=
from django.db import models
class Lead(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
message = models.CharField(max_length=300)
created_at = models.DateTimeField(auto_now_add=True)
```
<br><br><br>
創建 migration
```=
$ python manage.py makemigrations leads
```
<br><br><br>
遷移資料庫
```=
$ python manage.py migrate
```
執行完後會創建一個 db.sqlite3 檔
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──leads
│ ├──project
│ ├──db.sqlite3
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
## Django REST serializers
*終端機目前位置`~/django-drf-react-quickstart/project/`*
新增一個檔案命名為 `./leads/serializers.py` ,在裡面寫入:
```python=
from rest_framework import serializers
from leads.models import Lead
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = ('id', 'name', 'email', 'message')
# fields = '__all__' # 所有欄位可以這樣寫
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──leads
│ │ ├──migrations
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──serializers.py
│ │ ├──tests.py
│ │ └──views.py
│ │
│ ├──project
│ ├──db.sqlite3
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
## setting up the controll… ehm the views
*終端機目前位置`~/django-drf-react-quickstart/project/`*
開啟 `./leads/views.py` 新增 view:
```python=
from leads.models import Lead
from leads.serializers import LeadSerializer
from rest_framework import generics
class LeadListCreate(generics.ListCreateAPIView):
queryset = Lead.objects.all()
serializer_class = LeadSerializer
```
<br><br><br>
## setting up the rout… ehm the urls
*終端機目前位置`~/django-drf-react-quickstart/project/`*
配置 URL,開啟 `./project/urls.py:` 寫入:
```python=
from django.urls import path, include
urlpatterns = [
path('', include('leads.urls')),
]
```
<br><br><br>
新增一個檔案命名為 ./leads/urls.py, 在裡面寫入:
```python=
from django.urls import path
from . import views
urlpatterns = [
path('api/lead/', views.LeadListCreate.as_view() ),
]
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──leads
│ │ ├──migrations
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──serializers.py
│ │ ├──tests.py
│ │ ├──urls.py
│ │ └──views.py
│ │
│ ├──project
│ ├──db.sqlite3
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
開起`./project/settings.py` 加入 rest_framework 在 INSTALLED_APPS:
```python=
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'leads',
'rest_framework' # enable rest framework
]
```
<br><br><br>
現在你可以啟動 development server 檢查看看:
```=
$ python manage.py runserver
```
前往:http://127.0.0.1:8000/api/lead/
(關閉直接在終端機按 `control + c`)

你可以透過下方表格新增資料
<br><br><br>
## setting up React and webpack
*終端機目前位置`~/django-drf-react-quickstart/project/`*
新增 Django 應用程式 frontend
```=
$ django-admin startapp frontend
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──frontend
│ ├──leads
│ ├──project
│ ├──db.sqlite3
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
建立目錄結構來保存 React components:
```=
$ mkdir -p ./frontend/src/componentsmkdir -p ./frontend/src/components
$ mkdir -p ./frontend/{static,templates}/frontend
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ └──project
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ ├──static
│ │ ├──templates
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──tests.py
│ │ └──views.py
│ ├──leads
│ ├──project
│ ├──db.sqlite3
│ └──manage.py
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
現在要從目前位置`~/django-drf-react-quickstart/project/`移置到上一層資料夾
```=
$ cd ..
```
<br><br><br>
npm版本:
```=
$ npm -- version
```
>*6.9.0*
<br><br><br>
初始化環境:
```=
$ npm init -y
```
<br><br><br>
安裝 webpack 和 webpack cli:
```=
$ npm i webpack webpack-cli --save-dev
```
>*webpack@4.41.2*
>*webpack-cli@3.3.10*
<br><br><br>
現在開啟 `package.json` 配置 scripts:
將
```=
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
```
改成
```javascript=
"scripts": {
"dev": "webpack --mode development ./project/frontend/src/index.js --output ./project/frontend/static/frontend/main.js",
"build": "webpack --mode production ./project/frontend/src/index.js --output ./project/frontend/static/frontend/main.js"
}
```
<br><br><br>
安裝babel來編譯我們的代碼:
```=
$ npm i @babel/core babel-loader @babel/preset-env @babel/preset-react babel-plugin-transform-class-properties --save-dev
```
>*babel-loader@8.0.6*
>*@babel/preset-react@7.6.3*
>*@babel/core@7.6.4*
>*@babel/preset-env@7.6.3*
>*babel-plugin-transform-class-properties@6.24.1*
<br><br><br>
引入 React 和 prop-types:
```=
$ npm i react react-dom prop-types --save-dev
```
>*prop-types@15.7.2*
>*react@16.11.0*
>*react-dom@16.11.0*
<br><br><br>
配置Babel,新增一個檔案命名為`.babelrc` 在裡面寫入:
```bable=
{
"presets": [
"@babel/preset-env", "@babel/preset-react"
],
"plugins": [
"transform-class-properties"
]
}
```
<br><br><br>
配置babel-loader,新增一個檔案命名為`webpack.config.js` 在裡面寫入:
```javascript=
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──node_modules
│ ├──project
│ ├──.babelrc
│ ├──package-lock.json
│ ├──package.json
│ └──webpack.config.js
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
## the React frontend
*終端機目前位置`~/django-drf-react-quickstart/`*
移至 project
```=
$ cd project
```
<br><br><br>
新增一個 view,開啟`./frontend/views.py` 寫入:
```python=
from django.shortcuts import render
def index(request):
return render(request, 'frontend/index.html')
```
<br><br><br>
新增 template `./frontend/templates/frontend/index.html`:
```html=
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css">
<title>Django DRF - React : Quickstart - Valentino G. - www.valentinog.com</title>
</head>
<body>
<section class="section">
<div class="container">
<div id="app" class="columns">
<!-- React -->
</div>
</div>
</section>
</body>
{% load static %}
<script src="{% static "frontend/main.js" %}"></script>
</html>
```
<br><br><br>
配置新的URL `./project/urls.py`:
```python=
urlpatterns = [
path('', include('leads.urls')),
path('', include('frontend.urls')),
]
```
<br><br><br>
新增一個檔案命名為 `./frontend/urls.py` 在裡面寫入:
```python=
from django.urls import path
from . import views
urlpatterns = [
path('', views.index ),
]
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──node_modules
│ ├──project
│ │ ├──frontend
│ │ │ ├──migrations
│ │ │ ├──src
│ │ │ ├──static
│ │ │ ├──templates
│ │ │ ├──__init__.py
│ │ │ ├──admin.py
│ │ │ ├──apps.py
│ │ │ ├──models.py
│ │ │ ├──tests.py
│ │ │ ├──urls.py
│ │ │ └──views.py
│ │ │
│ │ ├──db.sqlite3
│ │ ├──leads
│ │ ├──project
│ │ └──manage.py
│ │
│ ├──.babelrc
│ ├──package-lock.json
│ ├──package.json
│ └──webpack.config.js
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
開起./project/settings.py 加入應用程式 frontend 在 INSTALLED_APPS:
```python=
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'leads',
'rest_framework',
'frontend' # enable the frontend app
]
```
<br><br><br>
新增一個檔案命名為 `./frontend/src/components/App.js` 在裡面寫入:
```javascript=
import React from "react";
import ReactDOM from "react-dom";
import DataProvider from "./DataProvider";
import Table from "./Table";
const App = () => (
<DataProvider endpoint="api/lead/"
render={data => <Table data={data} />} />
);
const wrapper = document.getElementById("app");
wrapper ? ReactDOM.render(<App />, wrapper) : null;
```
<br><br><br>
新增一個檔案命名為 `./frontend/src/components/DataProvider.js` 在裡面寫入:
```javascript=
import React, { Component } from "react";
import PropTypes from "prop-types";
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
```
<br><br><br>
新增一個檔案命名為 `./frontend/src/components/Table.js` 在裡面寫入:
```javascript=
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
const Table = ({ data }) =>
!data.length ? (
<p>Nothing to show</p>
) : (
<div className="column">
<h2 className="subtitle">
Showing <strong>{data.length} items</strong>
</h2>
<table className="table is-striped">
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
);
Table.propTypes = {
data: PropTypes.array.isRequired
};
export default Table;
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──node_modules
│ ├──project
│ │ ├──frontend
│ │ │ ├──migrations
│ │ │ ├──src
│ │ │ │ └──components
│ │ │ │ ├──App.js
│ │ │ │ ├──DataProvider.js
│ │ │ │ └──Table.js
│ │ │ │
│ │ │ ├──static
│ │ │ ├──templates
│ │ │ ├──__init__.py
│ │ │ ├──admin.py
│ │ │ ├──apps.py
│ │ │ ├──models.py
│ │ │ ├──tests.py
│ │ │ ├──urls.py
│ │ │ └──views.py
│ │ │
│ │ ├──db.sqlite3
│ │ ├──leads
│ │ ├──project
│ │ └──manage.py
│ │
│ ├──.babelrc
│ ├──package-lock.json
│ ├──package.json
│ └──webpack.config.js
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
(英文不好,抱歉)
The component generates rows dinamically so we need to rely on an external package for React keys id.
As pointed out by Bartosz using shortid might not be optimal.
A better alternative to shortid for React is weak-key:
```=
$ npm i weak-key --save-dev
```
> *weak-key@1.0.1*
<br><br><br>
最後為webpack創建入口點,
新增一個檔案命名為 `./frontend/src/index.js` 在裡面寫入:
```javascript=
import App from "./components/App";
```
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──node_modules
│ ├──project
│ │ ├──frontend
│ │ │ ├──migrations
│ │ │ ├──src
│ │ │ │ ├──components
│ │ │ │ └──index.js
│ │ │ │
│ │ │ ├──static
│ │ │ ├──templates
│ │ │ ├──__init__.py
│ │ │ ├──admin.py
│ │ │ ├──apps.py
│ │ │ ├──models.py
│ │ │ ├──tests.py
│ │ │ ├──urls.py
│ │ │ └──views.py
│ │ │
│ │ ├──db.sqlite3
│ │ ├──leads
│ │ ├──project
│ │ └──manage.py
│ │
│ ├──.babelrc
│ ├──package-lock.json
│ ├──package.json
│ └──webpack.config.js
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
Run webpack:
```=
$ npm run dev
```
執行完會在 `./frontend/static/frontend/` 新增一個 main.js
```
django_example
└──VenvDjango
├──django-drf-react-quickstart
│ ├──node_modules
│ ├──project
│ │ ├──frontend
│ │ │ ├──migrations
│ │ │ ├──src
│ │ │ ├──static
│ │ │ │ └──components
│ │ │ │ └──index.js
│ │ │ │
│ │ │ ├──templates
│ │ │ ├──__init__.py
│ │ │ ├──admin.py
│ │ │ ├──apps.py
│ │ │ ├──models.py
│ │ │ ├──tests.py
│ │ │ ├──urls.py
│ │ │ └──views.py
│ │ │
│ │ ├──db.sqlite3
│ │ ├──leads
│ │ ├──project
│ │ └──manage.py
│ │
│ ├──.babelrc
│ ├──package-lock.json
│ ├──package.json
│ └──webpack.config.js
│
├──bin
├──include
├──lib
└──pyvenv.cfg
```
<br><br><br>
啟動 development server:
```=
$ python manage.py runserver
```
前往 http://127.0.0.1:8000/ 就會看到 之前在 http://127.0.0.1:8000/api/lead/ 新增的資料了
