--- title: \[Django] - 伍、建立資料庫 tags: webdevelopment --- 第五篇主要內容是建立Django資料庫。 資料庫是什麼呢?顧名思義就是一個用來存放資料的地方,一般分為兩的種類,關聯式資料庫以及非關聯式資料庫。 關聯式資料庫(Related database management system): - 由一個或多個資料表(table)組成 - 每欄的資料型態都是固定的 - 不同的資料表間具有關聯性,可以透過某個欄位的資料將其連結 - 具備ACID特性 - 使用SQL(Structured querying language)操作資料庫 - MySQL, PostgreSQL等都是關聯式資料庫 非關聯式資料庫(NoSQL): - 採用文件、鍵值、圖形等資料模型儲存資料 - 資料型態不需固定 - 不具備ACID特性 - 水平擴展性非常好,可以透過增加節點達成 - Redis, MongoDB, Neo4j等都是非關聯式資料庫 *ACID特性: - 原子性(Atomicity):多個指令依照順序進行時,若有一個失敗,則先前已執行過的指令也會失效,讓資料回歸修改之前的模樣。 - 一致性(Consistency):資料的改動需符合欄位的規則,否則無效。 - 隔離性(Isolation):避免同一筆資料被兩個不同的人同時更動,所以在一個人對該筆資料有動作時,會鎖定資料避免出現競爭的情形。 - 持續性(Durability):除非硬體受損,否則都有辦法復原,資料永遠不會流失。 看完簡單的介紹,有對兩種資料庫了解一點嗎? 回過頭來看這次專案需要的兩個資料庫 1. 使用者資訊資料庫 2. 搜尋紀錄資料庫 搜尋紀錄資料庫需要依據使用者來分開,我沒有查過的關鍵字或域名,不應該出現在我的搜尋紀錄裡面。換句話說,搜尋紀錄需要跟使用者有關聯性。除此之外,搜尋紀錄的資料型態只有域名跟關鍵字的字以及排名的整數,不會出現其他的型態。 根據這幾點原因,這次的專案我決定使用關聯式資料庫。~~(絕對不是因為Django已經內建有關聯式資料庫的關係)~~ Django使用的是python中的SQLite,如果你想要用其他的資料庫,Django的文件中也有說明如何操作。這部分就請有需求的讀者自己去找囉~ 接下來開始設定資料庫吧! 首先,先把在settings.py中把時區改成你的所在時區 ![Screenshot 2025-07-31 at 12.51.55](https://hackmd.io/_uploads/ryavO_uvxe.png) 像我的話就是"Asia/Taipei" 接著打開Terminal輸入 ```code=python python manage.py migrate ``` 這個指令會檢查settings.py中的INSTALLED_APPS,並且根據他建立需要的資料庫。輸入後會看到這一串東西。 ![Screenshot 2025-07-31 at 13.00.04](https://hackmd.io/_uploads/S1CuOOODgg.png) 可以看到Django幫我們建立了一些東西。大概是一些管理後台的資料庫和功能。 完成之後,接著就是開始建立我們需要的資料庫了! 不過,不過,不過,在建立資料庫前還需要了解一個重要的原則: DRY(Don't repeat yourself) 這個原則簡單來說,是為了確保程式太複雜或是冗長而需要遵守。 以一言蔽之則是,一個程式核心只能存在一個,且只能存在一個地方。 用在Django資料庫中就是,建立過的物件,我們要重複拿來利用,避免出現不必要的錯誤或是讓程式碼過於冗長。 了解這個核心原則之後,在IDE中打開models.py ```code=python from django.db import models # 引用Django Auth中的「使用者」 # 可以獲取內建或是自定義的「User」model from django.contrib.auth import get_user_model User = get_user_model() # 使用者資訊資料庫 class UserProfileInfo(models.Model): # user欄位一對一對應「User」model # User包含username, password, email, firstname, lastname # on_delete=models.CASCADE: 當參照的物件被刪除時,一併刪除參照該物件的所有物件 user = models.OneToOneField(User, on_delete=models.CASCADE) # 新增一個暱稱欄位 nickname = models.CharField(max_length = 10) # 會印出username def __str__(self): return self.user.username ``` 使用者資訊資料庫完成啦,接下來要建立的是搜尋紀錄資料庫 在原先的預想中搜尋紀錄資料庫大概是長得像下面的表格這樣 ![Screenshot 2025-07-31 at 18.17.25](https://hackmd.io/_uploads/rk0pf6_Pxl.png) 使用一個資料表紀錄所有的搜尋紀錄,再透過User跟Domain去找對應的紀錄 但是,大家還記得前面提到的DRY原則嗎? 沒錯,User跟域名都有可能重複出現。所以為了防止這個狀況發生 我們把資料表設計成這個樣式,來讓後續處理資料時比較輕鬆一點 ![Screenshot 2025-07-31 at 18.34.41](https://hackmd.io/_uploads/HJmxva_Pgx.png) 當然一定還有其他的方式,大家可以想想有什麼更好的方式可以建立我們這次需要資料庫喔! 接下來一樣打開models.py 輸入下面的程式碼 ```code=python # 域名類別,會依據這個類別建立資料庫 class Domain(models.Model): # 用user當作Foreign key,關聯至User model # 反向關聯domains,可以透過User.domains去找該User底下所搜尋過的domain # verbose_name在後台可以看到對應的名稱 user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="domains", verbose_name="所屬使用者") # domain,字元格式,限制長度255位元 domain = models.CharField(max_length=255, verbose_name="域名") # Meta是Django內建的一些方法(method),可以用來設定一些屬性(attributes) class Meta: # 確保一個user不會有重複的domain名稱 unique_together = ("user", "domain",) # 單個的名稱 verbose_name = "域名" # 複數個的名稱 verbose_name_plural = "域名" # 索引,有很多屬性可以自行調整 indexes = [ models.Index(fields=["user", "domain"]), ] # 印出使用者名稱 - 域名 def __str__(self): return f"{self.user.username} - {self.domain}" class Keyword(models.Model): domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name="keywords", verbose_name="所屬域名") keyword = models.CharField(max_length=255, verbose_name="關鍵字") class Meta: unique_together = ("domain", "keyword",) verbose_name = "關鍵字" verbose_name_plural = "關鍵字" indexes = [ models.Index(fields=["domain", "keyword"]), ] def __str__(self): return f"{self.domain.user.username} - {self.domain.domain} - {self.keyword}" class KeywordRankHistory(models.Model): keyword = models.ForeignKey(Keyword, on_delete=models.CASCADE, related_name='rank', # 通過 keyword.rank_history.all() 訪問該關鍵字的歷史排名 verbose_name="所屬關鍵字") rank = models.IntegerField(null=True, blank=True, verbose_name="排名") # 可為空,表示尚未抓取或無排名 # 建立日期:auto_now_add=True 會在物件首次創建時自動設置為當前時間 created_at = models.DateTimeField(auto_now_add=True, verbose_name="建立日期") class Meta: verbose_name = "排名歷史" verbose_name_plural = "排名歷史" # 按建立日期降序排列 ordering = ['-created_at'] indexes = [ models.Index(fields=['keyword_name', 'created_at']), # models.Index(fields=['created_at']), ] def __str__(self): return f"{self.keyword.domain.user.username} - {self.keyword.domain.domain} - {self.keyword.keyword} - {self.rank if self.rank is not None else 'N/A'} @ {self.created_at.strftime('%Y-%m-%d %H:%M')}" ``` 輸入完之後,來到settings.py 在INSTALLED_APPS中加入我們的app 我的是ranking.apps.RankingConfig 如果不知道自己的該輸入什麼怎麼辦呢? 打開apps.py就可以看到你的是什麼名稱了 在前面加上你的app的名稱(會跟你的apps.py所在的資料夾一樣) 大概會是這個樣子 資料夾名稱.apps.XXXConfig 完成後記得儲存! 最後在Terminal輸入 ```code=python python manage.py makemigrations ranking # "ranking"記得換成你自己的app名稱 ``` 成功的話會看到這些訊息 ![Screenshot 2025-07-31 at 19.23.22](https://hackmd.io/_uploads/ry1vfC_Plx.png) 如果想玩一下可以參考Django的文件 我們這邊直接繼續在Terminal輸入 ```code=python python manage.py migrate ``` ![Screenshot 2025-07-31 at 19.26.06](https://hackmd.io/_uploads/BkrkmAdvge.png) 看到這些訊息就代表沒有問題啦!!! 下一篇會進行如何設定一個superuser 並用瀏覽器參觀一下我們建立好的幾個資料庫 請大家拭目以待! 如果有任何想法或是問題歡迎寄信到 chantinghsien@gmail.com 可以一起討論和分享新知!