---
# System prepended metadata

title: 透過 Django 架設網頁

---

# 透過 Django 架設網頁

## 安裝 Django 
```
pip3 install django
```

## 創建專案
```
django-admin startproject web_demo_tutorial
cd web_demo_tutorial
```
當前文件夾結構如下
```
.
├── manage.py
└── web_demo_tutorial
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

2 directories, 6 files
```
## 創建 Inference 應用
```
python3 manage.py startapp inference
```
當前文件夾結構如下
```
.
├── inference
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── web_demo_tutorial
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-310.pyc
    │   └── settings.cpython-310.pyc
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

5 directories, 15 files
```

## 註冊 Inference 應用
開啟 ./web_demo_tutorial/settings.py，並將應用名加入（第34行）。
```python=33
INSTALLED_APPS = [
    'inference',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```
同時修改 TEMPLATES（第58行）。
```python=55
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'], ＃修改此行
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
```

## 使用模型
設定 ./inference/models.py
```python=
from django.db import models
 
class Sentence(models.Model) :

    EMOTION_LIST = (
        ('sadness', 'sadness'),
        ('joy', 'joy'),
        ('love', 'love'),
        ('anger', 'anger'), 
        ('fear', 'fear'),
        ('surprise', 'surprise'),
    )

    input_text = models.CharField(max_length=200)
    old_model_answer = models.CharField(max_length=200)
    new_model_answer = models.CharField(max_length=200)
    fix_answer = models.CharField(max_length=200, choices=EMOTION_LIST)
    old_probability_list = []
    new_probability_list = []
    
    def __str__(self) :
        return f'{self.input_text} {self.old_model_answer} {self.new_model_answer}  {self.fix_answer} {self.old_probability_list} {self.new_probability_list}'
```
接著同步資料庫
```
python3 manage.py makemigrations
```

```
Migrations for 'inference':
  inference/migrations/0001_initial.py
    - Create model Sentence
```
再把 models.py 中的欄位寫入資料庫
```
python3 manage.py migrate
```
```
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, inference, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying inference.0001_initial... OK
  Applying sessions.0001_initial... OK
(tutorial-env) ➜  web_demo_tutorial git:(master) ✗ 
```
當前文件夾結構如下
```
.
├── db.sqlite3
├── inference
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-310.pyc
│   │   ├── admin.cpython-310.pyc
│   │   ├── apps.cpython-310.pyc
│   │   └── models.cpython-310.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 0001_initial.cpython-310.pyc
│   │       └── __init__.cpython-310.pyc
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── web_demo_tutorial
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-310.pyc
    │   ├── settings.cpython-310.pyc
    │   └── urls.cpython-310.pyc
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

7 directories, 24 files
```
## 加入 Inference 程式
./inference/Mylstm.py
```=python
import torch
import torch.nn as nn
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence

from torchtext.data import *

from .path import FIELDFILE_ROOT

text_field = torch.load(FIELDFILE_ROOT + 'field/text_field.pth')

class MyLSTM(nn.Module):
    def __init__(self, dimension=128):
        super(MyLSTM, self).__init__()
        self.embedding = nn.Embedding(len(text_field.vocab), 300)
        self.dimension = dimension
        self.lstm = nn.LSTM(input_size=300,
                            hidden_size=dimension,
                            num_layers=4,
                            dropout=0.3,
                            batch_first=True,
                            bidirectional=False)
        self.drop = nn.Dropout(p=0.3)
        self.fc = nn.Linear(dimension, 6)
    
    def forward(self, text, text_len):
        text_emb = self.embedding(text)
        packed_input = pack_padded_sequence(text_emb, text_len, batch_first=True, enforce_sorted=False)
        packed_output, _ = self.lstm(packed_input)
        output, _ = pad_packed_sequence(packed_output, batch_first=True)
        out_forward = output[range(len(output)), text_len - 1, :self.dimension]
        out_reverse = output[:, 0, self.dimension:]
        out_reduced = torch.cat((out_forward, out_reverse), 1)
        text_fea = self.drop(out_reduced)
        text_fea = self.fc(text_fea)
        text_fea = torch.squeeze(text_fea, 1)
        text_out = torch.nn.functional.relu(text_fea)
        return text_out
```
./inference/save_load_util.py
```=python
import torch
import os

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

# Save and Load Functions
def save_model(save_path, model, valid_loss, print_prompts=True):
    if save_path == None:
        return
    state_dict = {'model_state_dict': model.state_dict(),
                  'valid_loss': valid_loss}
    torch.save(state_dict, save_path)
    if print_prompts:
        print(f'Model saved to ==> {save_path}')

def load_model(load_path, model, print_prompts=True):
    if load_path==None:
        return
    state_dict = torch.load(load_path, map_location=device)
    model.load_state_dict(state_dict['model_state_dict'])
    if print_prompts:
        print(f'Model loaded from <== {load_path}')


def save_metrics(save_path, train_loss_list, valid_loss_list, global_steps_list,
                 print_prompts=True):
    if save_path == None:
        return
    state_dict = {'train_loss_list': train_loss_list,
                  'valid_loss_list': valid_loss_list,
                  'global_steps_list': global_steps_list}
    torch.save(state_dict, save_path)
    if print_prompts:
        print(f'Metrics saved to ==> {save_path}')

def load_metrics(load_path, print_prompts=True):
    if load_path==None:
        return
    state_dict = torch.load(load_path, map_location=device)
    if print_prompts:
        print(f'Metrics loaded from <== {load_path}')
    return state_dict['train_loss_list'], state_dict['valid_loss_list'], state_dict['global_steps_list']

```
./inference/inference.py
```=python
import csv
import torch
import pandas as pd

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

class inf :

    def get_classification(label) :
        classification = ['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']
        return classification[label]

    def inference(self, input, model, vocab) :
        model.eval()
        sentence = input

        words = sentence.split()
        words_index = [vocab[word] for word in words]
        text = torch.tensor([words_index]).to(device)
        text_len = torch.tensor([len(text[0])]).long()
        output = model(text, text_len)
    
        _, predicted_label = torch.max(output.data, 1)        
        label = int(predicted_label)
        score_list = output.data[0].tolist()

        # i = self.get_classification(label) # 印出情緒結果

        probability_list = [score / sum(score_list) for score in score_list]

        for i in range(6) : probability_list[i] = format( probability_list[i], '.2f' )        
        # for i in range(6) : print('{:9}- {:.2%}'.format(self.get_classification(i), probability_list[i]))

        classification = ['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']

        return label, probability_list 
```
當前文件夾結構如下
```
.
├── db.sqlite3
├── inference
│   .
│   .
│   ├── Mylstm.py ✔️
│   ├── inference.py ✔️
│   └── save_load_util.py ✔️
└── ..

7 directories, 27 files
```
## 加入網頁首頁
新增 templates 資料夾
```
mkdir templates
```
在 ./templates/ 路徑下新增 home.html
```html=
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>首頁</title>
    <style>
        @font-face {
            font-family: 'NotoSerif' ;
            src: url(https://fonts.google.com/noto/specimen/Noto+Serif+TC?subset=chinese-traditional);
        }

        .flex{
            display:flex;
            align-items:center;
            justify-content:center;
            position: absolute;
            width: 100%;
            height: 100%;
            background-color:white;
        }

        #homepage {
            border: 1px solid white;
            width: 30%;
            height: 300px;
            text-align: center;
            background: #F5EBB7;
            padding: 20px 50px;
            position: relative;
            border-radius: 30px; 
        }

        #homepage button {
            width: 150px; 
            height: 60px;
            text-transform: uppercase;
            border: 3px solid black;
            text-align: center;
            font-size: 24px;
            font-family : 'NotoSerif', serif ;
            color: black;
            line-height: 50px;
            border-radius: 30px;
            cursor: pointer;
            background:rgba(0,0,0,0);   
        }

        .title {
            font-family : 'NotoSerif', serif ;
            line-height: 1.5 ;
            font-size: 48px; 
        }

    </style>
</head>

<body>
<div class="flex">
    <div id="homepage">
        <p class="title">Text Classification</p>
        <button type="bottom" onclick=" location.href='/inference/'" >Enter</button>              
    </div>
</div>
</body>
```
於 ./web_demo_tutorial/ 新增 view.py
```python=
from django.shortcuts import render

def home(request) :
  return render(request, 'home.html', {})
```
修改./web_demo_tutorial/urls.py
```python=
from django.contrib import admin
from django.urls import path, include

from . import views
urlpatterns = [
    path('',views.home ),
    path('admin/', admin.site.urls),
    path('inference/', include('inference.urls') )
]
```
當前文件夾結構如下
```
.
├── db.sqlite3 
├── templates ✔️
│   └── home.html ✔️
├── web_demo_tutorial
│   .
│   .
│   ├── views.py ✔️
│   └── urls.py ✔️
└── ..

8 directories, 29 files
```
## 加入輸入頁面、結果展示頁面
```
cd inference & mkdir templates & cd templates
mkdir inference & cd inference 
```
在當前路徑下新增輸入頁面與結果展示頁面

input.html
```html=
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>輸入英文句子</title>
    <style>
        @font-face {
            font-family: 'NotoSerif' ;
            src: url(https://fonts.google.com/noto/specimen/Noto+Serif+TC?subset=chinese-traditional);
        }

        .flex{
            display:flex;
            align-items:center;
            justify-content:center;
            position: absolute;
            width: 100%;
            height: 100%;
            background-color:white;
        }

        #inputpage {
            border: 1px solid white;
            width: 30%;
            height: 348px;
            text-align: center;
            background: #F5EBB7;
            padding: 20px 50px;
            position: relative;
            border-radius: 30px; 
        }

        #inputpage button {
            width: 150px; 
            height: 60px;
            text-transform: uppercase;
            border: 3px solid black;
            text-align: center;
            font-size: 24px;
            font-family : 'NotoSerif', serif ;
            color: black ;
            line-height: 50px;
            border-radius: 30px;
            cursor: pointer;
            background:rgba(0,0,0,0); 
            margin-top: 48px;  
        }

        .title {
            font-family : 'NotoSerif', serif ;
            line-height: 1.5 ;
            font-size: 48px;
        }

        .context {
            font-family : 'NotoSerif', serif ;
            line-height: 1.5 ;
            font-size: 24px;  
        }

        #inputpage input {
            width: 350px;
            border: 3px solid white;
            font-size: 20px;
            color: black;
            font-family : 'NotoSerif', serif ;
            padding: 5px 10px;
        }

    </style>
</head>

<body>
<div class="flex">
    <div id="inputpage">
        <p class="title">請輸入英文句子</p>
        <form action="./result/" method="post">
        {% csrf_token %}
        <label class = "context">請輸入英文句子：</label><input type="text" name="input_text"><br />
        <button type="submit">送出</button>    
        </form>
                     
    </div>
</div>
</body>
```
result.html
```=html=
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>結果</title>

    <style>
        @font-face {
            font-family: 'NotoSerif' ;
            src: url(https://fonts.google.com/noto/specimen/Noto+Serif+TC?subset=chinese-traditional);
        }
        body {
            background-color: white;
        }

        .title {
            width : 100% ;
            text-align: center ;
            font-family: 'NotoSerif', serif ;
            font-size: 50px ;
            line-height: 1.1 ;
          }

        .subtitle_box {
            height: 40px;
            margin-top: 25px;
            margin-bottom: -10px;
            padding: 25px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;   
        }

        .subtitle {
            font-family : 'NotoSerif', serif ;
            line-height: 1.5 ;
            font-size: 24px; 
        }

        .wrapper {
            overflow: hidden ;
            display: flex;
        }

        .small_title_box {
            font-family : 'NotoSerif', serif ;
            width: 33%;       
            margin: 10px;
            padding: 25px;
            border-radius: 8px;
            display: flex;
            justify-content: center;
            align-items: center;
            
        }

        .chart_box {
            width: 33%;       
            margin: 10px;
            padding: 25px;
            border-radius: 8px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .old_model_Title {
            height: 30px;
            font-size: 24px;
            background: #d8eee1;
        }

        .new_model_Title {
            height: 30px;
            font-size: 24px;
            background: #f3f7f9;
        }

        .dataset_Title {
            height: 30px;
            font-size: 24px;
            background: #fef2f2;
        }

        .old_model_Info {
            height: 30px;
            font-size: 22px;
            background: #d8eee1;
        }

        .new_model_Info {
            height: 30px;
            font-size: 22px;
            background: #f3f7f9;
        }

        .dataset_Info {
            height: 30px;
            font-size: 22px;
            background: #fef2f2;
        }

    </style>
</head>

<body>
    <div class="wrapper">
        <h1 class="title">
            <strong>情緒辨識結果 : {{ new_model_ans }}</strong>
            <p class="subtitle_box subtitle">你輸入的句子為 : {{ input_text }}</p>
        </h1>
    </div>
    


    <div class="wrapper">
        <div class="small_title_box old_model_Title"><strong>模型訓練前的資訊</strong></div>
        <div class="small_title_box new_model_Title"><strong>模型訓練後的資訊</strong></div>
        <div class="small_title_box dataset_Title"><strong>資料集的相關資訊</strong></div>
    </div>

    <div class="wrapper">
        <div class="small_title_box old_model_Info"><p>模型訓練前的辨識結果：{{ old_model_ans }}</p></div>
        <div class="small_title_box new_model_Info"><p>模型訓練後的辨識結果：{{ new_model_ans }}</p></div>
        <div class="small_title_box dataset_Info"><p>資料集情緒分佈如下</p></div>
    </div>


    <div class="wrapper">
        <div class = "chart_box"><canvas id="oldModel"></canvas></div>
        <div class = "chart_box"><canvas id="newModel"></canvas></div>
        <div class = "chart_box"><canvas id="datasetNumList"></canvas></div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.0.1/chart.min.js" integrity="sha512-tQYZBKe34uzoeOjY9jr3MX7R/mo7n25vnqbnrkskGr4D6YOoPYSpyafUAzQVjV6xAozAqUFIEFsCO4z8mnVBXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
      
    <script>
        const ctx1 = document.getElementById('oldModel');
        
        var df = {{old_probability_list|safe}}
        var cla = {{classification|safe}}
        new Chart(ctx1, {
          	type: 'pie',
          	data: {
            	labels: cla,
            	datasets: [{ label: 'Percentage', data: df, borderWidth: 1 }]
          	},
          	options: {
            	responsive : false,
        	}
        }) ;
    </script>

    <script>
        const ctx2 = document.getElementById('newModel');
      
		var df = {{new_probability_list|safe}}
        var cla = {{classification|safe}}
        new Chart(ctx2, {
          	type: 'pie',
          	data: {
            	labels: cla,
            	datasets: [{ label: 'Percentage', data: df, borderWidth: 1 }]
          	},
          	options: {
            	responsive : false,
        	}
        }) ;
    </script>

<script>
	const ctx3 = document.getElementById('datasetNumList');
	
	var df = {{datasetNumList|safe}}
	var cla = {{classification|safe}}
    var per = {{datasetNumPercentage|safe}}
	new Chart(ctx3, {
		  type: 'pie',
		  data: {
			labels: cla,
			datasets: [{ label: 'Percentage', data: per , borderWidth: 1 }]
		  },
		  options: {
			responsive : false,
		}
	}) ;
</script>
    <div class="right">
        <h2><a href="../">重新輸入</a></h2>
    </div>
</body>
</html>


```
回到 web_demo_tutorial 資料夾，當前文件夾結構如下
```
.
├── db.sqlite3
├── inference
│   .
│   .
│   └── templates ✔️
│       └── inference ✔️
│           ├── input.html ✔️
│           └── result.html ✔️
└── ..

10 directories, 31 files
```
## Linking to URLs
於 ./inference/ 路徑新增 urls.py。
```python=
from django.urls import path

from . import views

app_name = 'inference'

urlpatterns = [
  path('', views.input, name='input'),
  path('result/', views.result, name='result'),
]
```
當前文件夾結構如下
```
.
├── db.sqlite3
├── inference
│   .
│   .
│   └── urls.py ✔️
│
└── ..

10 directories, 32 files
```
## 修改 views
於 ./inference/ 路徑新增 path.py，用於相對路徑。
```python=
import os
settings_dir = os.path.dirname(__file__)
PROJECT_ROOT = os.path.abspath(os.path.dirname(settings_dir))+ '/' 
FIELDFILE_ROOT = os.path.abspath(os.path.dirname(settings_dir)) + '/inference/'
```
修改 ./inference/views.py
```python=
from django.shortcuts import render
import torch
import csv
from .path import PROJECT_ROOT, FIELDFILE_ROOT
from .Mylstm import MyLSTM
from .save_load_util import load_model
from .inference import inf
from .models import Sentence

# new code
import pandas as pd
# end new code

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

# 讀入 field
text_field = torch.load(FIELDFILE_ROOT + 'field/text_field.pth')

# 情緒種類
classification = ['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']

# 讀入新、舊 model


def datasetNum() :
  datasetNumList = [0, 0, 0, 0, 0, 0]
  with open(PROJECT_ROOT + 'train.csv', newline='') as csvfile :
    reader = csv.reader( csvfile )
    dataList = list( reader )
    for r in dataList :
      if r[1] == '0' : datasetNumList[0] = datasetNumList[0] + 1 
      elif r[1] == '1' : datasetNumList[1] = datasetNumList[1] + 1
      elif r[1] == '2' : datasetNumList[2] = datasetNumList[2] + 1
      elif r[1] == '3' : datasetNumList[3] = datasetNumList[3] + 1
      elif r[1] == '4' : datasetNumList[4] = datasetNumList[4] + 1
      elif r[1] == '5' : datasetNumList[5] = datasetNumList[5] + 1

    csvfile.close()

  return datasetNumList

def datasetPercentage( datasetNumList ) :
  datasetNumPercentage = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  all = 0
  for i in range(6) :
    all = datasetNumList[i] + all

  for i in range(6) :
    datasetNumPercentage[i] = format( datasetNumList[i] / all, '.2f' ) 

  return datasetNumPercentage

def input(request) :
  return render(request, 'inference/input.html', {})

def result(request) :

  old_model = MyLSTM().to(device)
  load_model(PROJECT_ROOT + 'original_model.pt', old_model)
  new_model = MyLSTM().to(device)
  load_model(PROJECT_ROOT + 'model.pt', new_model)

  i = inf()
  s = Sentence()

  vocab = text_field.vocab
  s.input_text = request.POST.get('input_text', False)
  old_model_ans_int, s.old_probability_list = i.inference(s.input_text, old_model, vocab)
  new_model_ans_int, s.new_probability_list = i.inference(s.input_text, new_model, vocab)

  s.old_model_answer = classification[old_model_ans_int]
  s.new_model_answer = classification[new_model_ans_int]

  s.fix_answer = s.new_model_answer
  s.save()

  datasetNumList = datasetNum()
  datasetNumPercentage = datasetPercentage( datasetNumList )
  
  content = {
    'input_text' : s.input_text,
    'new_model_ans' : s.new_model_answer,
    'new_model_ans_int' : new_model_ans_int, 
    'new_probability_list' : s.new_probability_list,

    'old_model_ans' : s.old_model_answer,
    'old_model_ans_int' : old_model_ans_int, 
    'old_probability_list' : s.old_probability_list,

    'classification' : classification,

    'datasetNumList' : datasetNumList,
    'datasetNumPercentage' : datasetNumPercentage,
  }

  return render(request, 'inference/result.html', context=content)
```
當前文件夾結構如下
```
.
├── db.sqlite3
├── inference
│   .
│   .
│   ├── path.py  ✔️
│   └── views.py  ✔️
│
└── ..

10 directories, 33 files
```

## Admin 客製化
修改 ./inference/admin.py
```python=
import os
from django.contrib import admin
from django.http import HttpResponse
from .models import *
from .path import PROJECT_ROOT
import csv
import pandas as pd

def input_text_name( Sentence ) :
    return ("%s" %(Sentence.input_text))

input_text_name.short_description = 'Input text'

def new_model_ans_name( Sentence ) :
    return ("%s" %(Sentence.new_model_answer))

new_model_ans_name.short_description = 'Emotion'

def fix_ans_name( Sentence ) :
    return ("%s" %(Sentence.fix_answer))

fix_ans_name.short_description = 'Fix answer '

class ExportCsv :

    def export_as_csv(self, request, queryset) :

        meta = self.model._meta
        data_field_names = ['input_text', 'fix_answer']
        
        newDataList = []

        for obj in queryset :
               newDataList.append([getattr(obj, field) for field in data_field_names] )

        classification = ['sadness', 'joy', 'love', 'anger', 'fear', 'surprise']

        for i in range( len(newDataList) ) :
            if ( newDataList[i][1] == classification[0] ) : newDataList[i][1] = int(0)
            elif ( newDataList[i][1] == classification[1] ) : newDataList[i][1] = int(1)
            elif ( newDataList[i][1] == classification[2] ) : newDataList[i][1] = int(2)
            elif ( newDataList[i][1] == classification[3] ) : newDataList[i][1] = int(3)
            elif ( newDataList[i][1] == classification[4] ) : newDataList[i][1] = int(4)
            elif ( newDataList[i][1] == classification[5] ) : newDataList[i][1] = int(5)
        
        with open(PROJECT_ROOT + 'train.csv', mode = 'a', newline='') as csvfile :
            writer = csv.writer( csvfile )
    
            for i in newDataList :
                row = writer.writerow( i )
            csvfile.close()
        
    export_as_csv.short_description = "將勾選資料與本地端資料集合併"
    
class SentenceAdmin(admin.ModelAdmin, ExportCsv) :    
    list_display = (input_text_name, new_model_ans_name, fix_ans_name)
    actions = ["export_as_csv"]
    
admin.site.register(Sentence, SentenceAdmin)
```
## 啟動 Web Server
```
python3 manage.py runserver
```
```
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 01, 2023 - 15:57:09
Django version 4.1.3, using settings 'web_demo_tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```
開啟瀏覽器，輸入 http://127.0.0.1:8000/
畫面如下
![](https://i.imgur.com/bdjDpKP.png)

## 註冊 Admin SuperUser
```
python3 manage.py createsuperuser
```
```
Username (leave blank to use 'admin'): admin
Email address: [INPUTYOUREMAIL]
Password: [INPUTYOURPASSWORD]
Password (again): [INPUTYOURPASSWORD]
Superuser created successfully.
```
啟動 Web Server
```
python3 manage.py runserver
```
開啟瀏覽器，輸入 http://127.0.0.1:8000/admin/
畫面如下
![](https://i.imgur.com/y8xiCpf.png)
輸入設定的帳號密碼後即可進入管理介面。
![](https://i.imgur.com/tVYlIrW.png)
點選 Sentences 欄位，可查看過去使用者的輸入紀錄。
![](https://i.imgur.com/Zc5Kxco.png)