# campervan_project
- 複習[基礎Django](https://hackmd.io/BqHnSfRtQcaCzGGZzywE9w?view)
- `python3 manage.py --help` 命令會顯示 Django 的 manage.py 腳本的幫助信息,列出所有可用的管理命令
## 專案環境建設
> `pip3 freeze`
- 列出當前 Python 環境中已安裝的所有套件及其版本
- 輸出格式適合用於創建`requirements.txt`文件,這個文件通常用於記錄項目的依賴關係
- 輸出的每一行都是 "套件名==版本號" 的格式
- 這個命令常用於以下場景:
- **記錄項目依賴**: 開發者可以使用 `pip3 freeze > requirements.txt` 來創建一個包含所有當前環境依賴的文件
- **環境複製**: 其他開發者或在其他機器上可以使用 `pip3 install -r requirements.txt` 來重現相同的環境
- **版本控制**: 幫助追踪和管理項目使用的套件版本
### 專案初始化
> 專案根目錄 Root Directory `campervan_project/`
```bash=
# terminal目前在這個專案目錄 => campervan_project/
python3 -m venv env # 虛擬環境的目錄名,我取名為env
source env/bin/activate #運行腳本 or
deactivate #退出虛擬環境
pip3 install django # 安裝django
django-admin startproject campervan . # 安裝campervan_project/ 配置檔
```
> 讓web server 跑起來!
```bash
python3 manage.py runserver
```
## 創建第一個app
> 創建第一個app > `apps/`
```bash
python3 manage.py startapp pages
```
> 到專案層的設置檔 `campervan_project/campervan/settings.py`的 `INSTALLED_APPS`這個串列增加:
```python
'pages.apps.PagesConfig',
```
- 
### 為什麼是註冊這行呢?`pages.apps.PagesConfig`
- 到pages這個app下的app.py來看:
- 
> 創建`pages app`的`urls.py `=> 在`pages/`這個app目錄夾下創建`urls.py`
> 並且把`campervan_project/campervan/urls.py`檔案中複製以下內容到`campervan_project/pages/urls.py`
```python=
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
], #到網址根目錄就要讓試圖渲染home方法
]
```
> 到`campervan_project/campervan/urls.py`
```python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pages.urls')),
]
```
> 創建視圖views中的第一個方法:
```python
from django.shortcuts import render
# Create your views here.
def home(request):
return render(request, 'pages/home.html')
```
## 在專案層創建templates資料夾`campervan_project/templates`
- 繼續在`campervan_project/templates/` 創建`pages/home.html`
- 並在`home.html`先增加一行內文待會做識別`<h2>你好啊</h2>`
> 到`campervan_project/campervan/settings.py`中的TEMPLATES串列中的字典裡{'DIRS':['templates']}增加
```
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'], ## 增加這行
```
> 讓web server 跑起來!=> 確認`home.html`頁面內容有被渲染出來
```bash
python3 manage.py runserver
```
### 加入版本控制
- 在專案層中創建空的儲存庫
```python=
git init .
git add .
git commit -m "project init"
```
- 創建`campervan_project/.gitignore`
- 可參考[gitignore網站](https://www.toptal.com/developers/gitignore/)
- 在搜尋筐裡打上`Django`
## 設置靜態檔案
> `campervan_project/campervan/`新增`static`目錄夾
- 把`css/, fonts/, img/, js/` 目錄夾放在 `static`目錄夾下(靜態檔案目錄夾)
> 設置`campervan_project/campervan/settings.py`中的靜態文件
```python=
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'campervan/static')
]
```
### 靜態文件變數
- 這些是 Django 中用於管理靜態文件的設置
> `STATIC_URL = '/static/'`
- 定義了靜態文件在網頁中的 URL(在網頁中引用靜態文件時,使用這個前綴)
- 有一個名為 `style.css` 樣式表文件在你的應用的靜態文件目錄中,它的 URL 將會是 /static/style.css。
> `STATIC_ROOT = os.path.join(BASE_DIR, 'static')`
- 定義了收集(或者說彙總)所有靜態文件的根目錄
- 在部署應用程序時,Django 的 collectstatic 命令會將所有應用中的靜態文件收集到這個目錄中
- 這個設置指定了一個絕對的文件系統路徑,用於存放生產環境中的靜態文件
> `STATICFILES_DIRS = [
> os.path.join(BASE_DIR, 'campervan/static'),
> ]`
- 是一個列表,包含了額外的靜態文件目錄
- 這些目錄用於開發階段,在這些目錄中的靜態文件會被 Django 伺服器直接提供
- 在這個例子中`campervan/static`目錄被添加到了靜態文件目錄列表中
- 這意味著在開發模式下,Django 會從這個目錄提供靜態文件,而不需要運行 collectstatic 命令
- 這些設置結合起來,讓 Django 能夠有效管理靜態文件:
- `STATIC_URL` 定義了靜態文件的 URL 路徑
- `STATIC_ROOT` 指定了靜態文件在生產環境中的收集目錄
- `STATICFILES_DIRS` 則提供了開發時使用的其他靜態文件目錄
### 當前目錄夾結構
```python
campervan_project/ <-- 最上層的專案目錄(專案目錄層/專案級)
│
├── env/
│ ├── bin/
│ │ ├── activate
│ ├── include/
│ ├── lib/
│ └── ...
│
├── campervan/ <-- 整個專案的主要配置目錄
│ ├── static/
│ ├── __pycache__
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py <-- 專案級別的URL配置,定義了URL模式和對應的視圖(views)
│ ├── wsgi.py
│ └── asgi.py
│
├── pages/ <-- job_board 應用程式目錄(應用程式級)
│ ├── migrations/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── views.py
│ └── urls.py <-------- 創建urls.py檔案(應用程式級)
│
├── templates/
│
├── .gitignore
│
└── manage.py
```
> 然後運行以下指令:
```bash=
python3 manage.py collectstatic
```
- Django 會查找所有 INSTALLED_APPS 中定義的應用,以及 `STATICFILES_DIRS` 中指定的目錄
也就是收集在'campervan/static'下所有的靜態文件與目錄夾
> 就會在專案層級中也出現了`static`的目錄夾
- 
### 模板包含(template inclusion)
- 我把css link從主結構中抽離出來
```html
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" />
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/animate.min.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/bootstrap-submenu.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/bootstrap-select.min.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/magnific-popup.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'fonts/font-awesome/css/font-awesome.min.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'fonts/flaticon/font/flaticon.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'fonts/linearicons/style.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/jquery.mCustomScrollbar.css' %}"
/>
<link rel="stylesheet" type="text/css" href="{% static 'css/dropzone.css' %}" />
<link rel="stylesheet" type="text/css" href="{% static 'css/slick.css' %}" />
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/lightbox.min.css' %}"
/>
<link rel="stylesheet" type="text/css" href="{% static 'css/jnoty.css' %}" />
<!-- Custom stylesheet -->
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
<link
rel="stylesheet"
type="text/css"
id="style_sheet"
href="{% static 'css/skins/red.css' %}"
/>
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/ie10-viewport-bug-workaround.css' %}"
/>
<!-- Favicon icon -->
<link rel="shortcut icon" href="img/favicon.ico" type="image/x-icon" />
<!-- Google fonts -->
<link
rel="stylesheet"
type="text/css"
href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800%7CPlayfair+Display:400,700%7CRoboto:100,300,400,400i,500,700"
/>
<script src="{% static 'js/ie-emulation-modes-warning.js' %}"></script>
```
- 我把js script從主結構中抽離出來
```html
{% load static %}
<script src="{% static 'js/jquery-2.2.0.min.js' %}"></script>
<script src="{% static 'js/popper.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/bootstrap-submenu.js' %}"></script>
<script src="{% static 'js/rangeslider.js' %}"></script>
<script src="{% static 'js/jquery.mb.YTPlayer.js' %}"></script>
<script src="{% static 'js/bootstrap-select.min.js' %}"></script>
<script src="{% static 'js/jquery.easing.1.3.js' %}"></script>
<script src="{% static 'js/jquery.scrollUp.js' %}"></script>
<script src="{% static 'js/jquery.mCustomScrollbar.concat.min.js' %}"></script>
<script src="{% static 'js/dropzone.js' %}"></script>
<script src="{% static 'js/slick.min.js' %}"></script>
<script src="{% static 'js/jquery.filterizr.js' %}"></script>
<script src="{% static 'js/jquery.magnific-popup.min.js' %}"></script>
<script src="{% static 'js/jquery.countdown.js' %}"></script>
<script src="{% static 'js/jquery.mousewheel.min.js' %}"></script>
<script src="{% static 'js/lightgallery-all.js' %}"></script>
<script src="{% static 'js/jnoty.js' %}"></script>
<script src="{% static 'js/app.js' %}"></script>
```
- 在主架構的home.html要include這些靜態文件,最上層仍然要使用`{% load static %}`
> include 或 extends tags時檔案要加上目錄夾
```html
{% load static %}
{% include 'pages/head_links.html' %}
{% include 'pages/body_scripts.html' %}
```
### 模板包含 topbar navbar footer(template inclusion)
- 一次的把主頁上的menu連結 => 設定好urls => views針對各個urls的應對處理方法
```pyhton
CAMERVAN_PROJECT/
├── campervan/
├── env/
├── pages/
│ ├── __pycache__/
│ ├── migrations/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
├── static/
├── templates/
│ ├── includes/
│ │ ├── footer.html
│ │ ├── navbar.html
│ │ ├── topbar.html
│ ├── pages/
│ │ ├── about.html
│ │ ├── body_scripts.html
│ │ ├── campervans.html
│ │ ├── contact.html
│ │ ├── head_links.html
│ │ ├── home.html
│ │ ├── services.html
│ ├── base.html
├── .gitignore
├── db.sqlite3
```
## 註冊資料庫 連線資料庫(postgreSQL)
### postgre登入
- 嘗試無密碼登錄:打開終端,輸入
```bash
psql -U postgres
```
- 在 psql 提示符下設置密碼:如果成功登錄到 psql,您可以設置密碼( ALTER USER 命令,這個更改是立即生效)
```bash
ALTER USER postgres WITH PASSWORD 'your_new_password';
#輸入 \q 退出 psql
```
- 如果無密碼登錄失敗,嘗試使用系統用戶身份:
```bash
sudo -u postgres psql
```
#### 將密碼存進環境變數
```bash
vim ~/.bash_profile or vim ~/.bashrc
#在文件尾端
export POSTGRES_PASSWORD='your_new_password'
source ~/.bash_profile
#驗證
echo $POSTGRES_PASSWORD
```
#### 在 Django 中使用環境變數:
- 在您的 Django 項目的 settings.py 文件中
```python
'PASSWORD': os.environ.get('POSTGRES_PASSWORD')
```
- 確保包含密碼的配置文件權限設置正確
- chmod 600 ~/.zshrc or chmod 600 ~/.bash_profile
- 也可以考慮使用 .env 文件:
對於開發環境,考慮使用 .env 文件和 python-dotenv 庫來管理環境變數,這樣可以避免修改 shell 配置文件
### 安裝 psycopg2
> 會有提示說要先下載
```python=
pip3 install psycopg2
```
- 確保你的 Django 項目已經安裝了 psycopg2 庫,它是 Django 和 PostgreSQL 之間的介面庫。你可以使用以下命令來安裝
### settings.py
- 基本設定
- 下載postgre和圖形介面
> 註冊RDBMS => 創建資料庫 => Django全域設置中註冊資料庫
- 這是預設:sqlite3

- 改成postgreSQL
- 先建立postgresql的資料庫
- 
- 注意owner是postgre
- 改寫成:
```python=
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'your_database_name', # PostgreSQL 數據庫名稱
'USER': 'postgres', # PostgreSQL 用戶名
'PASSWORD': 'your_database_password', # PostgreSQL 密碼
'HOST': 'localhost', # PostgreSQL 主機,可以是 IP 地址或主機名
'PORT': '5432', # PostgreSQL 端口,默認是 5432
}
}
```
### 讓django知道,我要在該資料庫``創建這個 model 資料表
> makemigration => 創建指令,告訴django database內容/狀態改變 => 可以說是條列出資料庫的更改內容
```bash
python3 manage.py makemigrations
```
> migrate => 下這個指令就是說 將剛剛資料庫所有變化的狀態 『正式:寫入到資料庫/正式生效』
> 這有點像 python 連mysql時 針對conn.commit()
```bash=
python3 manage.py migrate
```
### 先創建django 的super user
```bash=
winpty python3 manage.py createsuperuser # =>這是windows
```
```bash
python3 manage.py createsuperuser
```
- 設定帳號
- 設定密碼
```bash
python3 manage.py runserver
```
- 進到網址admin
## 建立model
### 常用欄位
#### Django Model 與 MySQL 資料型態的對應
| CharField(max_length=100)| TextField() | IntegerField() | BooleanField(default=False) |
| -------- | -------- | -------- |-------- |
| 字符字段 | 文本字段 | 整數字段 | true/false |
| 該字段默認表單是`<input type="text">TextInput` | 該字段默認表單是 `<textarea></textarea>Textarea`| 該字段的默認表單部件是 `<input type="number">NumberInput` |default=False |
| 我想成MySQL的 varchar(100) | 想成MySQL的 text | 想成MySQL的 int |- |
| ImageField(upload_to='photos/%Y/%m/%d') | UrlField(max_length=100) | Column 3 |
| -------- | -------- | -------- |
| Text | Text | Text |
- 特別為整理一下django和mysql的時間欄位對應
| DateField | TimeField | DateTimeField |TimeStampField|
| -------- | -------- | -------- |-------- |
| 對應到 MySQL的DATE|對應到MySQL的TIME| 對應到MySQL的DATETIME|存儲時間戳,在 MySQL可將DATETIME/TIMESTAMP 來存儲 |
| 用於存儲日期(年、月、日)|存儲時間(時、分、秒) |存儲日期和時間的字段,包括年、月、日、時、分、秒` auto_now=True`当保存对象时,每次都会自动更新为当前时间。适用于需要跟踪最后修改时间的场景`auto_now_add=True`当对象第一次创建时自动设置为当前时间,之后保存对象时不会更新。适用于创建时间的场景 |- |
### Django Model 欄位schema => 先從sql的schema思考 =>由gpt改善
- pages/models.py
```python=
from django.db import models
# Create your models here.
class Team(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
designation = models.CharField(max_length=255)
photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
facebook_link = models.URLField(max_length=100)
instagram_link = models.URLField(max_length=100)
threads_link = models.URLField(max_length=100)
google_plus_link = models.URLField(max_length=100)
create_date = models.DateTimeField(auto_now_add=True)
```
> Pillow 是 Python 中用於處理圖片的流行庫。它提供了一組函數,可以打開、操作和保存許多不同的圖片文件格式
```python
pip3 install pillow
```
```python
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py runserver
```
> 進入django admin > 看不的Teams的model 因為該資料表還沒有註冊在admin中
> 到pages/admin.py 中註冊:
```python
from django.contrib import admin
from .models import Team
# Register your models here.
admin.site.register(Team)
```
#### 把玩一下介面上的資料增刪修查
- 我新增了幾筆資料
#### shell指令針對model的增刪修查
- 也練習使用shell
- 進入django 提供的shell
```bash
python3 manage.py shell
# 在shell指令中引入入 Team model `from app_name.models import Team`
from pages.models import Team
In [5]: Team.objects.all()
Out[5]: <QuerySet [<Team: jj>, <Team: 雅婷>, <Team: 俊杰>, <Team: 佳儀>, <Team: 子偉>,
<Team: 美玲>]>
#用指令新增一筆資料
In [8]: Team.objects.create(first_name="美奇",last_name="黃",designation="客服部
...: 主任",photo="photos/2024/07/24/avatar-4.jpg",facebook_link="https://www.
...: facebook.com/?locale=zh_TW",instagram_link="https://www.instagram.com/",
...: threads_link="https://www.threads.net/?hl=zh-tw",google_plus_link="https
...: ://www.linkedin.com/")
Out[8]: <Team: 美奇>
```
- models.Model 模型管理器
- 給予基本針對model/資料表 增刪修查的方法
```python=
# model manager -> objects
Team.objects.all()
Team.objects.get(id=1)
Team.objects.filter(title="job title")
Team.objects.create(first_name="陳葦綾",last_name="")
```
- 無法上傳圖片?
> 在 Django 中配置 `MEDIA_ROOT` 和 `MEDIA_URL` 的目的是為了處理使用者上傳的媒體檔案(例如圖片、影片等) 就跟處理靜態文件是一樣的道理
- `MEDIA_ROOT`是一個檔案系統路徑,用於指定存放使用者上傳媒體檔案的實際資料夾
- 設置方法: 在 Django 的設定檔(通常是 settings.py)中,透過 os.path.join(BASE_DIR, 'media') 來設定 MEDIA_ROOT。
- `BASE_DIR` 是你的 Django 專案根目錄,通常是使用 `Path(__file__).resolve().parent.parent` 取得的
- `media`是你希望存放上傳檔案的資料夾名稱
### 需要設定media file => 到`campervan/settings.py`
> 定義: `MEDIA_URL`是用來訪問使用者上傳媒體檔案的 URL 前綴,用於瀏覽器或應用程式中的檔案訪問。
- 設置方法: 在`Django`的設定檔中設置`MEDIA_URL`,通常是以斜線結尾的 URL 路徑,用於訪問`MEDIA_ROOT`中的檔案
```python=
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
```
- 區分使用者上傳檔案: 將使用者上傳的媒體檔案(如圖片、影片等)存儲在獨立的資料夾中(即 MEDIA_ROOT),而不是混合在靜態檔案中
- 提供訪問路徑: 透過配置 MEDIA_URL,可以方便地透過瀏覽器或應用程式訪問這些上傳檔案
- URL 配置: 確保 MEDIA_URL 是以斜線結尾的 URL 路徑,這樣可以確保 Django 正確處理使用者上傳檔案的訪問請求
### 再到`campervan/urls.py`
```python=
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pages.urls')),
] + static(setting.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
- 用於設定 Django 專案中的媒體檔案路徑,以確保在開發環境中能正確存取使用者上傳的媒體檔案(如圖片、影片等)
- 在 Django 中,為了能夠存取和顯示使用者上傳的媒體檔案,需要進行以下配置:
- **static 函式**: Django 提供了 `static` 函式,用於在開發環境中提供靜態檔案(包括媒體檔案)的訪問路徑
- **settings.MEDIA_URL**: 在 Django 的設定檔中設定的使用者上傳媒體檔案的 URL 前綴,例如 '/media/'
- **settings.MEDIA_ROOT**: 在 Django 的設定檔中設定的使用者上傳媒體檔案的實際檔案系統路徑,通常是專案根目錄下的一個子資料夾
- **urlpatterns**: Django 中用來設定路由的主要清單,決定了不同 URL 路徑對應的視圖或應用
> runserver到admin畫面 試著增加一個team的indivisual資料 看是否成功!
## 如何將Team資料表/model 的資料 呈現在畫面template上 => views
- 要呈現團隊區塊的模板有home,about介面
> 到pages/views.py 撰寫
- 處理home()方法,利用home() 裡去跟models要到Team資料
```python
from django.shortcuts import render
from .models import Team #很常忘記寫
# Create your views here.
def home(request):
# 從資料庫/模型裡拿資料
teams = Teams.objects.all() #Queryset
data = {
'teams' = teams,
}
return render(request,'pages/home.html',data)#return render(request, 'pages/home.html')
# 這個data就會被帶入到模板中'pages/home.html'
```
- 處理about()方法,利用about() 裡去跟models要到Team資料
```python
def about(request):
teams = Teams.objects.all()
data = {
'teams': teams,
}
return render(request, 'pages/about.html', data)
# 這個data就會被帶入到模板中'pages/about.html'
```
> 在 Django view 中,當你從資料庫中獲取資料並使用 Teams.objects.all() 時,返回的是一個 QuerySet 對象,這個對象包含了符合查詢條件的所有模型實例。在將這個 QuerySet 傳遞給模板中的字典時,它實際上是可以作為值的,這是因為 Django 模板系統能夠處理 QuerySet 對象。
### 把html單一 員工的資訊卡 標籤結構拉出來
- 以class="slick-slide-item"為一個資訊卡片單位
```html
{% for team in teams %}
<div class="slick-slide-item">
<div class="team-1">
<div class="photo">
<a href="#">
<img
src="{{ team.photo.url }}"
alt="team-1"
class="img-fluid"
/>
</a>
<div class="social-list clearfix">
<a href="{{team.facebook_link}}" class="facebook-bg"
><i class="fa fa-facebook"></i
></a>
<a href="{{team.instagram_link}}" class="twitter-bg"
><i class="fa fa-twitter"></i
></a>
<a href="{{team.threads_link}}" class="google-bg"
><i class="fa fa-google"></i
></a>
<a href="{{team.google_plus_link}}" class="google-bg"
><i class="fa fa-google"></i
></a>
</div>
</div>
<div class="details">
<h4><a href="team-detail.html">{{ team.last_name}}{{ team.first_name}}</a></h4>
<h5>{{ team.designation }}</h5>
</div>
</div>
</div>
{% endfor %}
```
## 客製化 Team Admin Customization
## 向 Django-admin 註冊 pages app 的 Team models(資料表)
- 到 pages/ 下的`admin.py`
```python
from django.contrib import admin # 預設就有的
```
- 首先先導入 `pages/models.py` 中的模型/資料表 `Team`
- 接著跟 admin 註冊 `Team` 該模型/資料表
```python
from django.contrib import admin
from .models import Team
admin.site.register(Team)
```
- `admin.site.register()` 是 Django 中用來將模型model註冊到admin後台管理界面的函數
- 通過這個函數,可以告訴 Django 管理員界面需要管理哪些模型(即資料表)以及如何展示它們
- 重新`$python3 manage.py runserver`=> 到 django 中的管理員 admin/
- 會看到 admin 中的 dashboard 會出現 Team 這個 model/資料表
- 會將 Team 的資料變成=>`Team object (1)`呈現出來
## Django-admin 中的Team Model做客製化的標題呈現
- 想要再登入後台時清楚看到該Team模型/資料表的語意內容
- 就可以在該 models 檔案來做一些**客製化**
- 我想在 admin 中的 Team dashboard 呈現該 model 時想要呈現 first_name| thumbnails | 職位 | ...
> 到pages/ 下的`admin.py`創建Team 在Admin管理中的類(就叫TeamAdmin)
> 從Admin類中繼承ModelAdmin
```python
from django.contrib import admin
from .models import Team
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
list_display = ('id','first_name', 'designation', 'created_date')
```
- 想再model admin的管理介面上再加上每個工作人員的大頭貼
- 就是在管理介面表中嵌入html的img標籤
- 引用format_html模組
```python
from django.contrib import admin
from .models import Team
from django.utils.html import format_html
# Register your models here.
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
#用來顯示圖片縮略圖
def thumbnail(self, object):
return format_html('<img src="{}" width="40" style="border-radius: 50%;" />'.format(object.photo.url))
#.short_description 改在admin dashboard欄位顯示的名稱
thumbnail.short_description = 'Avatar'
list_display = ('id','thumbnail','first_name','designation', 'create_date')
list_display_links = ('id','thumbnail', 'first_name')
```
### 補充:`admin.ModelAdmin`
- `admin.ModelAdmin` 是一個用來定制管理後台(admin)界面的基類(base class)
- 通常情況下,當你想要對某個model模型進行後台管理時,會創建一個對應的管理類(admin class),並讓它繼承自 admin.ModelAdmin
- 以下是 admin.ModelAdmin 的主要作用和用法:
> 自定義後台顯示字段: 你可以通過在管理類中定義 list_display 屬性,指定在管理後台中顯示的字段
```python=
from django.contrib import admin
from .models import Team
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'designation')
```
> 篩選器和搜索字段: 你可以使用 list_filter 屬性來添加篩選器,使用 search_fields 屬性來指定搜索字段
```python
class TeamAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'designation')
list_filter = ('designation',)
search_fields = ('first_name', 'last_name')
```
> 在 Django 的管理後台中,使用 .short_description 屬性可以修改管理界面中欄位的顯示名稱,使其更具描述性和易於理解
> 字段排序: 通過 ordering 屬性來指定默認的排序方式
```python
class TeamAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'designation')
ordering = ('last_name', 'first_name')
```
> 詳細視圖和編輯表單: 可以進一步自定義詳細視圖和編輯表單的內容和布局
```python=
class TeamAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'designation')
fieldsets = (
('Personal Information', {
'fields': ('first_name', 'last_name', 'designation')
}),
('Social Media Links', {
'fields': ('facebook_link', 'twitter_link', 'google_plus_link')
}),
)
```
> 1.傳統方式-註冊到後台: 最後,通過 admin.site.register() 函數將管理類註冊到 Django 的後台中
```python
admin.site.register(Team, TeamAdmin)
```
> 2.常用方式-註冊到後台:在 Django 中,@admin.register() 装饰器提供了一种注册模型到 Admin 后台的简便方式,可提供多個模型註冊
- 装饰器方式比传统的注册方式更为推荐,因为它更加简洁和易于阅读,尤其是在管理多个模型时,能够更清晰地看到每个模型对应的管理类
```python
from django.contrib import admin
from .models import Team, Player
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'city', 'established_year')
@admin.register(Player)
class PlayerAdmin(admin.ModelAdmin):
list_display = ('name', 'team', 'position')
```
## 模板標籤顯示當前的日期
- 在 Django 的模板中,使用 `{% now 'Y-m-d' %}` 可以獲取當前日期的格式化字符串
- 加入時分秒`{% now 'Y-m-d H:i:s' %}`
## `request.path` =>如何取的用戶發送的 HTTP 請求中的 URL 路徑部分
- 在 Django 中,request.path 是指請求的路徑部分,即用戶發送的 HTTP 請求中的 URL 路徑部分,不包括主機名和埠號等信息。
- 例如,如果用戶訪問的 URL 是 http://example.com/blog/post/,則 request.path 的值將是 /blog/post/。
- 這在開發 Web 應用程序時非常有用,特別是當你需要基於用戶訪問的不同 URL 路徑來執行不同的邏輯或者顯示不同的內容時。通常情況下,request.path 可以幫助你根據 URL 的不同部分來進行路由和處理。
- 在 Django 中,這是 HttpRequest 對象的一部分,你可以在視圖函數或者模板中訪問 request 對象來獲取相應的信息,如 request.path、request.method
## 創建第二個app => cars的車種眾多 => 再建一個app
- 要把下面一系列動作內化才行:
> 1.創建app(執行下列指令,會產生cars的app應用程式目錄夾與其相館配套檔案)
```bash
python3 manage.py startapp cars
```
- 產生了cars的app模組/包之後=> 把app.py打開來看
- 
> 2.註冊該cars app到專案中=> 到專案層(global config)的`settings.py`
```python
INSTALLED_APPS = [
'cars.apps.CarsConfig',
'pages.apps.PagesConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
```
> 3.創建cars/urls.py 檔案
- 記得引入`from . import views
- 
> 4.引入cars/urls.py 到專案層(global config)的`urls.py`
- 
> 5.到cars/views.py 建立cars的方法:

> 6.到templates目錄夾新增cars的目錄夾 以及 其目錄下的cars.html
## 創建第二個app cars的模型(資料表) & Car模型(資料表) 註冊到Car的admin.py & 數據庫遷徙說明書 => 數據庫遷徙
> ==創建第二個app cars的模型(資料表)==
> 針對車款-定義model schema => cars app/models.py
```python
from django.db import models
from datetime import datetime
# Create your models here.
class Car(models.Model):
state_choice = (
('AL', 'Alabama'),
('AK', 'Alaska'),
('AZ', 'Arizona'),
('AR', 'Arkansas'),
('CA', 'California'),
('CO', 'Colorado'),
('CT', 'Connecticut'),
('DE', 'Delaware'),
('DC', 'District Of Columbia'),
('FL', 'Florida'),
('GA', 'Georgia'),
('HI', 'Hawaii'),
('ID', 'Idaho'),
('IL', 'Illinois'),
('IN', 'Indiana'),
('IA', 'Iowa'),
('KS', 'Kansas'),
('KY', 'Kentucky'),
('LA', 'Louisiana'),
('ME', 'Maine'),
('MD', 'Maryland'),
('MA', 'Massachusetts'),
('MI', 'Michigan'),
('MN', 'Minnesota'),
('MS', 'Mississippi'),
('MO', 'Missouri'),
('MT', 'Montana'),
('NE', 'Nebraska'),
('NV', 'Nevada'),
('NH', 'New Hampshire'),
('NJ', 'New Jersey'),
('NM', 'New Mexico'),
('NY', 'New York'),
('NC', 'North Carolina'),
('ND', 'North Dakota'),
('OH', 'Ohio'),
('OK', 'Oklahoma'),
('OR', 'Oregon'),
('PA', 'Pennsylvania'),
('RI', 'Rhode Island'),
('SC', 'South Carolina'),
('SD', 'South Dakota'),
('TN', 'Tennessee'),
('TX', 'Texas'),
('UT', 'Utah'),
('VT', 'Vermont'),
('VA', 'Virginia'),
('WA', 'Washington'),
('WV', 'West Virginia'),
('WI', 'Wisconsin'),
('WY', 'Wyoming'),
)
# 生成從2000年到當前年份的選擇集
your_choices = []
for r in range(2000, (datetime.now().year+1)):
#第一個r是存儲在數據庫中的值,第二個r是顯示給用戶的值
year_choices.append((r,r))
features_choices = (
('Cruise Control', 'Cruise Control'),
('Audio Interface', 'Audio Interface'),
('Airbags', 'Airbags'),
('Air Conditioning', 'Air Conditioning'),
('Seat Heating', 'Seat Heating'),
('Alarm System', 'Alarm System'),
('ParkAssist', 'ParkAssist'),
('Power Steering', 'Power Steering'),
('Reversing Camera', 'Reversing Camera'),
('Direct Fuel Injection', 'Direct Fuel Injection'),
('Auto Start/Stop', 'Auto Start/Stop'),
('Wind Deflector', 'Wind Deflector'),
('Bluetooth Handset', 'Bluetooth Handset'),
)
door_choices = (
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5'),
('6', '6'),
)
car_title = models.CharField(max_length=255)
# 可以把資料作為choices的參數帶入
state = models.CharField(choices=state_choice, max_length=100)
city = models.CharField(max_length=100)
color = models.CharField(max_length=100)
model = models.CharField(max_length=100)
year = models.IntegerField(('year'),choices=year_choices)
condition = models.CharField(max_length=100)
price = models.IntegerField()
description = models.TextField(max_length=500)
# image檔案
car_photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_1 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_2 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_3 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_4 = models.ImageField(upload_to='photos/%Y/%m/%d/')
features = models.CharField(choices=features_choices, max_length=100)
body_style = models.CharField(max_length=100)
engine = models.CharField(max_length=100)
transmission = models.CharField(max_length=100)
interior = models.CharField(max_length=100)
miles = models.IntegerField()
doors = models.CharField(choices=door_choices,max_length=100)
passengers = models.IntegerField()
vin_no = models.CharField(max_length=100)
milage = models.IntegerField()
fuel_type = models.CharField(max_length=100)
no_of_owners = models.CharField(max_length=100)
is_featured = models.BooleanField(max_length=100)
#是一個函數引用,它將在創建新記錄時被調用,從而獲取當前時間。如果你不想立即調用函數,可以使用這種方式
created_date = models.DateTimeField(default=datetime.now, blank=True)
#這個參數允許該字段在表單中可以留空。這意味著即使表單中沒有提供created_date的值,該字段也不會觸發表單驗證錯誤
```
- 這段比較複雜,所以先以想要在Admin怎麼呈現這個欄位為目標,再來解釋怎麼做:
- 以上面`state_choice=(('AL', 'Alabama'),
('AK', 'Alaska'),...)`這一長串的Tuple資料來說,在Admin中這個欄位`state = models.CharField(choices=state_choice, max_length=100)`想要呈現的結果如下:
- 
- 所以state_choice裡面的每一組tuple,第一個元素是存在資料庫的'AL',呈現在介面上的'Alabama'選項
- `choices`參數用於定義一個字段的『選擇集/select options』,它限制該字段的值只能是選擇集中定義的值。這樣可以幫助你在數據庫層面上保持數據的有效性和一致性
- 每個選擇項都是一個二元組`state_choice=(('AL', 'Alabama'),
('AK', 'Alaska'),...)`
- 第一個元素是存儲在數據庫中的實際值,第二個元素是顯示給用戶的友好名稱
- 在管理界面或表單中,state字段將顯示為一個『下拉選單』,選項是我們在STATE_CHOICES中定義的友好名稱。這樣可以幫助用戶選擇有效的值並防止輸入錯誤
- 再來是 `year = models.IntegerField(('year'),choices=year_choices)`
- 
```python
# 生成從2000年到當前年份的選擇集
year_choices = []
for r in range(2000, (datetime.now().year+1)):
#第一個r是存儲在數據庫中的值,第二個r是顯示給用戶的值
year_choices.append((r,r))
```
- 
```python
# image檔案
car_photo = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_1 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_2 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_3 = models.ImageField(upload_to='photos/%Y/%m/%d/')
car_photo_4 = models.ImageField(upload_to='photos/%Y/%m/%d/')
```
- 
```python=
features_choices = (
('Cruise Control', 'Cruise Control'),
('Audio Interface', 'Audio Interface'),
('Airbags', 'Airbags'),
('Air Conditioning', 'Air Conditioning'),
('Seat Heating', 'Seat Heating'),
('Alarm System', 'Alarm System'),
('ParkAssist', 'ParkAssist'),
('Power Steering', 'Power Steering'),
('Reversing Camera', 'Reversing Camera'),
('Direct Fuel Injection', 'Direct Fuel Injection'),
('Auto Start/Stop', 'Auto Start/Stop'),
('Wind Deflector', 'Wind Deflector'),
('Bluetooth Handset', 'Bluetooth Handset'),
)
```
- 
```python
door_choices = (
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5'),
('6', '6'),
)
```
- 
- `is_featured = models.BooleanField(default=False)`
- 
```python
created_date = models.DateTimeField(default=datetime.now, blank=True)
```
> ==把第二個app cars的Car模型(資料表) 註冊到Car的admin.py==
- Cars/admin.py
- 直接使用裝飾器註冊Cars模型並綁定class
```python
from django.contrib import admin
from .models import Car #記得要先引入這個Car 模型
@admin.register(Car)
class CarAdmin(admin.ModelAdmin):
pass
```
> ==數據庫遷徙說明書 => 數據庫遷徙==
## 安裝`django-ckeditor` 文本編輯器([用版本5](https://pypi.org/project/django-ckeditor-5/))
> 想呈現的結果是:
> 1.安裝ckeditor編輯器
```bash
pip3 install django-ckeditor
```
> 2.配置 Django 設置:專案層的配置目錄夾下的`settings.py`
```python
INSTALLED_APPS = [
'ckeditor',
]
```
> 3.在模型中使用 RichTextField:到cars的應用程式層的models.py
```python
from ckeditor.fields import RichTextField
#把description這個欄位的schema更改一下
description = RichTextField()
```
- `from ckeditor.fields import RichTextField` 是用於在Django模型中引入 CKEditor 的富文本編輯器字段
- CKEditor 是一個流行的基於瀏覽器的富文本編輯器,它允許用戶在表單中編輯和格式化文本
> 4. ==數據庫遷徙說明書 => 數據庫遷徙==
## 安裝[mutiselectField](https://pypi.org/project/django-multiselectfield/)
- 想要在Admin中的欄位呈現多選項:
- 
```bash
pip3 install django-multiselectfield
```
- Model.py的欄位也要改成:
```python
from multiselectfield import MultiSelectField
MY_CHOICES = (('item_key1', 'Item title 1.1'),
('item_key2', 'Item title 1.2'),
('item_key3', 'Item title 1.3'),
('item_key4', 'Item title 1.4'),
('item_key5', 'Item title 1.5'))
class MyModel(models.Model):
# .....
my_field = MultiSelectField(choices=MY_CHOICES)
```
- 在專案層的settings.py
```python
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
#.....................#
'multiselectfield',
)
```
> ==數據庫遷徙說明書 => 數據庫遷徙==
## 把車款資料渲染到template
- 取得資料 從views中的home方法裡跟Cars取到車款資料
```python=
from django.shortcuts import render
from .models import Team
# Create your views here.
def home(request):
teams = Team.objects.all() #整個Teams的資料 queryset
# 先排序再过滤
featured_car = Car.objects.order_by('-created_date').filter(is_featured=True)
# 資料要是字典{ }
data = {
'teams': teams,
'featured_car': featured_car,
}
return render(request, 'pages/home.html', data)
```
- 組資料:在home.html的車款資訊卡中把featured_car資料透過迴圈迭代進去
## [Django-contrib.humanize](https://docs.djangoproject.com/en/5.0/ref/contrib/humanize/)
```python=
{% load humanize %}
```
```python=
# Application definition
INSTALLED_APPS = [
'cars.apps.CarsConfig',
'pages.apps.PagesConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'multiselectfield',
'django_ckeditor_5',
"django.contrib.humanize", #寫上這行
]
```
## 為每個車生成單一頁面

## Django 提供的分頁paginator功能
- 可以讓我輕鬆的查詢結果分頁顯示,網頁上顯示數據時,分頁是一個常見的需求,有助於提高頁面加載速度,並增強用戶體驗。
- `Paginator`
- 使用`EmptyPage` 和 `PageNotAnInteger` 處理異常分析
- ==步驟1 導入模組==
- ==步驟2 獲取數據並創建 Paginator物件==
- ==步驟3 獲取當前頁面號 ==
- ==步驟4 處理分頁異常==
- ==步驟5 將頁面對象傳遞給模板==
- ==步驟6 在模板中顯示分頁數據 和 分頁元件==