# 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`) ![](https://i.imgur.com/uWXkqeN.png) 你可以透過下方表格新增資料 <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/ 新增的資料了 ![](https://i.imgur.com/Cae5WdU.png)