# 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/ ```