# Django 心得記錄
###### tags: `Django`
## MTV開發流程
1. 資料庫設計,ex: Table怎麼開,怎麼join...
2. 設計每個網頁的html檔案
3. 建立django開發的虛擬環境
4. `django-admin startproject` 建立專案
5. `python manage.py startapp` 建立app
6. 在app內建立 `templates`資料夾,放置所有專屬該app的html檔
7. 在app內建立 `static` 資料夾,放置所有屬於該app的靜態檔案(img, css, js...)
8. 修改 `settings.py`,加入相關設定,並把產生的app名稱加入 `INSTALLED_APPS` list中
9. 編輯 `models.py`,建立資料庫表格
10. 編輯 `views.py`,先import在models.py中建立的資料模型
11. 編輯 `admin.py`,把 `models.py` 中定義的資料模型加入,並使用 `admin.site.register` 註冊新增的模型,讓後台可以直接管理資料庫
12. 編輯 `views.py`,設計每個def,來管控每個request進來之後分別要做的事情,並決定render到哪一個網頁內容
13. 編輯 `urls.py`,先 import 在 `views.py` 中定義的def(或是直接載入`views`整包),最後在 `urlpatterns` 裡分配每個route對應到的 `views.def`
14. 執行 `python manage.py makemigrations`,將資料庫models相關的設定初始化
15. 執行 `python manage.py migrate`,正式將上一步的設定建立起來
16. 執行 `python manage.py runserver`,測試網站
## Model
>**每一個 Model 對應到一個資料庫的資料表**,包含每個欄位的**型態、格式,以及與其他表格的互動關係(關聯)**
```python=
# Models.py
from django.db import models
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='種類') # 外來鍵連到Category的主鍵
sku = models.CharField(max_length=20)
name = models.CharField(max_length=20)
description = models.TextField()
image = FilerImageField(related_name='product_image', on_delete=models.CASCADE) # 外部連結顯示圖片
website = models.URLField(null=True)
stock = models.PositiveIntegerField(default=0)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def __str__(self): # 在admin後臺顯示的名稱
return self.name
```
* `ForeignKey`: 外來鍵,負責指向另外一張表格的主鍵(PK),表示這個表格是依附於另外一張表格的
* `on_delete=models.CASCADE`: 當被參照的物件被刪除時,此參照物件也要一併被刪除
* `verbose_name`: 想在管理後台顯示的欄位名稱
建立後,記得要在cmd裏頭執行以下程式碼,將資料庫同步:
```
python manage.py makemigrations
python manage.py migrate
```
在 `admin.py` 中建立資料表管理介面,將相關的app註冊到裏頭
```python=
# admin.py
from django.contrib import admin
from myshop import models
class ProductAdmin(admin.ModelAdmin):
list_display = ('category', 'sku', 'name', 'stock', 'price')
ordering = ('category',)
admin.site.register(models.Product, ProductAdmin) # 自訂Product在後台的顯示方式
```
## Templates
接著,建立 template,先在 `settings.py` 的 `DIRS` 裡面加上 `[os.path.join(BASE_DIR, 'templates')]` ,記得也要先 `import os`。
```python=
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, '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',
],
},
},
]
```
在該 app 裡面建立 templates 資料夾,並建立 `list.html`
```htmlembedded=
<!-- list.html -->
....
....
{% for p in products %}
<tr>
<td>{{ p.name }}</td>
<td>{{ p.stock }}</td>
</tr>
{% endfor %}
....
```
### 在tamplate裡面使用static檔案
```python=
# settings.py
...
STATIC_URL = '/static/' # 以'/static'開頭的網址就視為是要對靜態檔案進行讀取
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static') # 設定靜態檔案要放置的檔案位置
]
```
則html檔使用方式如下
```htmlembedded=
<!-- index -->
...
{% load static %}
<img src='{% static "images/logo.png" %}'></img>
...
```
* `{% load static %}` 只要在整個檔案中使用過一次即可
### template繼承
會設計一個 `base.html`,內容是放置一些固定不變的html。
```htmlembedded=
<!-- base.html -->
...
<title>
{% block title %}{% endblock %}
</title>
...
<body>
{% block content %}{% endblock %}
{% include "footer.html" %}
</body>
```
```htmlembedded=
<!-- footer.html -->
<em>Copyright 2021. All rights reserved.</em>
```
* `title`、`content` 是每一個block的名稱,可以自己命名
* `{% include "footer.html" %}` 是引入其他html檔案,ex: 頁尾的版權聲明文字
其餘檔案要引用,就用以下方式即可
```htmlembedded=
<!-- index.html -->
{% extends "base.html" %}
{% block title %} 測試網站 {% endblock %}
{% block content %}
...測試
{% endblock %}
...
```
* `{% extends %}` 引入基礎檔案
* 依照各個block的坑來填進去想加的內容
* 若有一個變數叫 product 被渲染進來,想要顯示該變數名稱又想有`default`的值,則可以用 `{{ product.nickname | default: 找不到此商品 }}` 表示
## View
>決定程式邏輯的地方
```python=
# Views.py
from django.shortcuts import render, redirect
from myshop import models
def index(request):
return render(request, 'index.html')
def listing(request):
products = Product.objects.all()
return render(request, 'list.html', locals())
```
* 每個函式的 `request` 都要傳進來,render的時候也要傳回去
* `Product.objects.all()`: 把Product這個model的欄位資料全部取出來
* `Product.objects.get(欄位名稱=變數名稱)`: 取得指定欄位的資料,若找不到就會觸發`DoesNotExist`的例外而中斷程式,為了避免程式被中斷,需用`try-except`來協助處理
* `locals()`: 把所有在def內的區域變數名稱與值mapping成dict
* render 到 `list.html`
## URL
開啟 `urls.py`,設定各 route 要分配到的 views
```python=
from django.contrib import admin
from django.urls import path, include
from myshop import views
urlpatterns = [
path('admin/', admin.site.urls), # 管理員後台
path('', views.index), # index首頁
path('list/', views.listing), # listing
]
```
* 需要注意的是,`''` 就代表首頁了,不能再加上`'/'`
* 每個 pattern 必須以 `'/'`做結尾
此外,如果遇到以下形式的url
```
localhost:8000/about/0
localhost:8000/about/1
localhost:8000/about/2
```
Django 2.0 可以用 `Path Converters` 解決
```python=
# urls.py
...
path('about/<int:author_no>', views.about, name='about-url')
```
```python=
# views.py
...
def about(request, author_no=0):
...
```
* `<int:author_no>`: <參數型態 : 變數名稱>
* `views.py` 裡面的 `about` 函式要傳入 `author_no`
* `name` 是將設計好的 pattern 取一個名字,方便程式中呼叫
* `author_no=0` 可以加上預設的值
```htmlembedded=
<!-- index.html -->
...
<a href="{% url 'about-url' 2 %}">Show the about</a>
...
```
* `'about-url'` 是剛剛在 `name`裡面自己取的名字
* `2` 是傳進 `about-url` 的參數,也就是 `author_no`
當網址輸入 `/about/2` 就會以此網址作為連結。
此外,當有多個 app 共用同一組 views,且具有同樣的性質,可以使用 `include` 語法來把urlpatterns放到另外一個地方去設定
```python=
# urls.py
...
my_patterns = [
path('company/', views.company),
path('sales/', views.sales),
path('contact/', views.contact),
]
urlpatterns = [
path('info/', include(my_patterns)),
]
```
這樣子的話,所有網址中只要是 `/info` 開頭的字串,都會對應到 `my_patterns` 這組 url 去處理。如:
```
localhost:8000/info/company/
localhost:8000/info/sales/
localhost:8000/info/contact/
```