2025年6月25日 星期三

Django 入門:用 Python 輕鬆打造 AI 驅動的多租戶預約平台後端!

Django 入門:用 Python 輕鬆打造 AI 驅動的多租戶預約平台後端!

前言:開發網站從零開始,其實可以更省力!

您好!寫程式的朋友們應該都清楚,從頭開發一個網站或應用程式,從使用者管理、資料庫操作到安全考量,每環節都需要不少工夫。不過,如果有個工具能幫您把這些基礎的重複性工作先打理好,讓您可以把更多心力放在核心功能上,那倒也挺不錯的。

這就是今天要聊的 Django!一個用 Python 寫的免費開源網頁框架。它有個特色叫「自帶電池 (Batteries included)」,意思就是它已經預備好大部分網站開發會用到的功能,讓您不用到處找工具,算是一站式的解決方案。如果您對 Python 熟悉,那 Django 確實值得花點時間了解。

這篇文章會帶您簡單認識 Django 的基本概念,並結合您手邊這個「AI 驅動的多租戶線上預約平台」的專案。這個專案目前提供的是核心的代碼骨架與基礎配置,也就是說,它為您搭建了一個穩固的平台基礎,方便您在此之上逐步完善各種功能。我們來看看這個 Django 後端是怎麼運作,以及您該如何著手進行後續開發。

一、認識 Django 的基本結構:專案與應用程式 (Project & App)

在 Django 裡面,有兩個基本的概念,了解它們就能掌握大致的骨架:

  • 專案 (Project):您可以把它想像成整個網站的主體框架,裡頭包含所有的設定、資料庫連線資訊,以及整個網站的網址(URL)規劃。您這個 booking-platform-django-fastapi 專案的 Django 後端部分,就是一個 Django Project。通常,在專案的最外層會有一個同名的資料夾(例如 backend/core_project/),放著主要的設定檔。

  • 應用程式 (App):App 就像是網站裡頭一個個獨立的功能模組。舉例來說,您的網站可能會有「使用者管理」、「商家資料」、「預約排程」這些獨立的功能。在 Django 的設計裡,每個功能區塊都可以設計成一個 App。這樣做的好處是:

    • 分工明確:程式碼分門別類,維護起來比較方便。

    • 彈性運用:寫好的 App 搞不好將來在其他 Django 專案也能直接拿來用。

在您這個 booking-platform-django-fastapi 專案裡,Django 後端就分成了好幾個 App:users (處理使用者)、merchants (處理商家)、appointments (處理預約) 還有 common (一些通用功能)。這些 App 都放在 backend/apps/ 資料夾底下。

booking-platform-django-fastapi/
└── backend/
    ├── core_project/           # Django Project 的核心設定資料夾
    │   ├── settings.py         # 專案總設定檔
    │   └── urls.py             # 專案的總路由配置
    ├── apps/
    │   ├── users/              # 使用者管理 App
    │   │   ├── models.py       # 資料庫模型定義
    │   │   ├── views.py        # 處理邏輯與回傳回應
    │   │   └── urls.py         # App 內部的 URL 路由
    │   ├── merchants/          # 商家資訊管理 App
    │   │   ├── models.py
    │   │   └── ...
    │   ├── appointments/       # 預約排程管理 App
    │   │   ├── models.py
    │   │   └── ...
    │   └── common/             # 通用功能 App
    │       ├── models.py
    │       └── ...
    └── manage.py               # Django 專案的管理工具

二、核心 Django 概念簡單說

一個 Django 網站的運作,大致上可以套用 MVT (Model-View-Template) 這個模式來理解。雖然您的前端是 Vue.js,沒有直接用到 Django 的 Template 部分,但後端的運作邏輯還是差不多:

  1. Model (模型)

    • 放在 apps/*/models.py:模型就是把您資料庫的結構,用 Python 的類別 (Class) 來表示。通常一個類別會對應到資料庫裡的一個資料表 (Table),類別裡面的每個屬性 (Attribute) 則對應到資料表的一個欄位 (Column)。

    • Django 有個好用的 ORM (Object-Relational Mapping) 機制,讓您可以直接用 Python 程式碼來操作資料庫,不用花力氣寫原生的 SQL 語句,省事很多。

    • 在您的專案中backend/apps/users/models.py 會定義 User 模型,它就對應到 MySQL 裡的 users 表。依此類推,backend/apps/merchants/models.py 定義 Merchant 模型,對應 merchants 表。

  2. View (視圖)

    • 放在 apps/*/views.py:View 的工作就是接收網頁來的請求(Request),然後處理完後回傳一個回應(Response)。當您在瀏覽器上輸入一個網址時,Django 就會去找到對應的 View 函數或類別來處理。

    • View 會負責從 Model 那邊拿資料、跑一些業務邏輯,然後再把結果整理好送出去。

    • 在您的專案中backend/apps/users/views.py 裡頭,就會有處理使用者註冊、登入、個人資料查詢或更新這些邏輯的程式碼。

  3. URL (路由)

    • 放在 mysite/urls.py (總路由) 和 apps/*/urls.py (App 路由):URL 就是定義網站的網址規則,把特定的網址對應到特定的 View。

    • 通常專案的總 urls.py 會把各個 App 的路由都串起來,這樣每個 App 就能各自管理自己的網址,比較不會亂。

    • 在您的專案中:您在瀏覽器上輸入 http://localhost/api/users/profile/ 時,這個請求會先經過 backend/core_project/urls.py,然後再導向到 backend/apps/users/urls.py,最後才由 users App 裡頭相對應的 View 來處理。

  4. Admin Site (管理介面)

    • Django 自帶一個蠻方便的後台管理介面。只要做一些簡單的設定,您就可以透過網頁輕鬆管理資料庫裡的數據,不用自己寫一堆後台管理程式。這對專案開發初期或是日常的數據維護來說,都挺實用的。

    • 在您的專案中:您可以打開瀏覽器,輸入 http://localhost/admin。一旦您建立好超級用戶並登入,就能在網頁上管理 usersmerchants 等 App 的資料了。

三、資料庫怎麼初始化與管理:SQL 腳本跟 Django 遷移一起用

您這個 booking-platform-django-fastapi 專案在處理資料庫初始化這部分,用了一個比較彈性的方式:

  1. 初始 SQL 腳本 (mysql_init_scripts/init_database_schema.sql)

    • 這個檔案裡頭,就是所有資料庫表格的 CREATE TABLE 語句。

    • 當您第一次用 docker-compose up -d 指令啟動 MySQL 容器時,MySQL 自己會去跑這個腳本,一次就把所有資料庫表格都建好。對於要把一個既有、完整的資料庫結構先建立起來,這個方法還挺方便的。

  2. Django 遷移 (Migrations)

    • Django 有一套自己的資料庫遷移機制。當您在 Python 的 Model (models.py) 裡有改動時,可以透過 Django 的指令來生成檔案,這些檔案會記錄資料庫需要怎麼變更。

    • 生成遷移文件python manage.py makemigrations

      • 這個指令會檢查您的 models.py 文件,把所有變更轉成一些 Python 檔案,這些檔案就像是資料庫的「變更紀錄」。

    • 應用遷移python manage.py migrate

      • 這個指令會讀這些變更紀錄檔,然後去資料庫執行對應的變更操作。

      • 因為您的專案已經有 init_database_schema.sql 先建好表格了,所以第一次跑 migrate 時,可能會建議您加上 --fake-initial 這個參數。它會跟 Django 說:「這些 App 的初始資料表都已經有了,你記一下就好,不用再重複建立了。」這樣 Django 自己的狀態就能跟資料庫實際狀況同步。

這種方式的好處是,您既能利用 Docker 快速建立資料庫,也能透過 Django 的遷移系統來持續追蹤和管理資料庫的後續變動,彈性不錯。

四、啟動您的 Django 後端專案

接下來,我們就來實際操作,啟動這個專案的 Django 後端部分,讓它能跟其他服務一起協同運作。

請在您的 booking-platform-django-fastapi 專案根目錄下,按照以下步驟操作:

  1. 啟動所有 Docker 服務

    docker-compose up --build -d
    

    這個指令會把 MySQL、Django 後端、FastAPI 推薦引擎、Redis、Nginx 等所有必要的服務都啟動起來。

  2. 稍微等一下,確認 Django 後端服務啟動完成:

    您可以打 docker-compose logs django_backend 這個指令,看看 Django 容器的啟動日誌。如果看到類似 Starting development server at http://0.0.0.0:8000/ 或者沒有持續的錯誤訊息,通常就表示 Django 服務已經準備好了。

  3. 執行資料庫初始化與遷移 (這個步驟通常只在第一次或需要重設資料庫時才做):

    前面提過,這是為了讓 Django 內部對資料庫狀態的認知,跟您用 init_database_schema.sql 建立的實際資料庫結構保持一致。

    # 檢查並生成新的遷移文件(如果模型的定義有變動,或這是第一次)
    docker exec -it booking-platform-django-fastapi_django_backend python manage.py makemigrations
    
    # 應用所有遷移,針對那些已經用 SQL 腳本建好的 App,做一個「假遷移」
    docker exec -it booking-platform-django-fastapi_django_backend python manage.py migrate --fake-initial
    
    • 為什麼要 makemigrations 就算您用 SQL 腳本建好表了,Django 還是需要對應的 Python 遷移檔案來追蹤。如果您有更動 models.py,就得再跑一次。

    • 為什麼要 migrate --fake-initial 如果您是全新啟動,而且已經透過 SQL 腳本把表建好了,Django 會發現這些表已經在那裡了。--fake-initial 就是告訴 Django,把這些初始的遷移記下來就好,不用真的去跑建立表的 SQL,以免重複操作。

  4. 建立 Django 超級使用者:

    為了能進到 Django 的管理後台(http://localhost/admin),您需要先建立一個超級管理員帳號:

    docker exec -it booking-platform-django-fastapi_django_backend python manage.py createsuperuser
    

    照著提示輸入使用者名稱、電子郵件和密碼就可以了。

五、簡單逛逛您的 Django 網站

現在,您的 Django 後端應該就能開始運作了!您可以試著:

  • 進入 Django 管理後台:打開瀏覽器,輸入 http://localhost/admin。用您剛才建立的超級使用者帳號登入後,您應該能看到 Users、Merchants 等 App 的管理介面,可以直接在這邊看看和修改資料庫裡的數據。

  • 試試 API 介面:這個專案透過 Nginx 把 Django 的 API 導向到 /api/。您可以透過前端介面操作,或是用 Postman、Insomnia 這類工具來測試 API。

  • 看看 FastAPI 推薦引擎的文件:打開 http://localhost:8001/docs,這是 FastAPI 自動生成的 API 文件,會列出所有可用的推薦 API。

六、專案代碼庫與關鍵代碼片段

您的專案原始碼在這裡,歡迎大家參考:https://github.com/BpsEason/booking-platform-django-fastapi.git

以下我們會挑一些專案裡重要的 Django 程式碼片段,並加上詳細的註解,希望能幫助您更快理解它們的功能。

1. Django 模型範例 (backend/apps/users/models.py)

這是 User 模型,它定義了使用者在資料庫裡的長相,以及一些相關的處理邏輯。特別要注意的是,我們用了 AbstractBaseUserPermissionsMixin 來建立自訂的使用者模型,這樣會更有彈性。

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager

# 自訂 UserManager 類別,用來處理使用者創建的流程
class UserManager(BaseUserManager):
    # 建立一般使用者的方法
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Email 這個欄位一定要填。')
        email = self.normalize_email(email) # 把 email 格式統一
        user = self.model(email=email, **extra_fields)
        user.set_password(password) # 設定密碼,Django 會自動加密
        user.save(using=self._db)
        return user

    # 建立超級管理員的方法
    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault('user_type', 'super_admin') # 預設是超級管理員
        extra_fields.setdefault('status', 'active') # 預設狀態是啟用中
        # 這裡也可以根據需求加其他預設屬性
        return self.create_user(email, password, **extra_fields)

# 自訂 User 模型,繼承了 Django 內建的基礎用戶功能
class User(AbstractBaseUser, PermissionsMixin):
    # 定義使用者類型,例如顧客、商家管理員等
    USER_TYPE_CHOICES = (
        ('customer', '顧客'),
        ('merchant_admin', '商家管理員'),
        ('staff', '員工'),
        ('super_admin', '超級管理員'),
    )
    # 定義使用者狀態,例如啟用、停用、凍結
    STATUS_CHOICES = (
        ('active', '啟用'),
        ('inactive', '停用'),
        ('suspended', '凍結'),
    )

    email = models.EmailField(unique=True) # 電子郵件,而且不能重複
    # password_hash 這個欄位不是存明碼,是存密碼加密後的雜湊值
    password_hash = models.CharField(max_length=255)
    phone_number = models.CharField(max_length=20, unique=True, null=True, blank=True)
    first_name = models.CharField(max_length=100, blank=True, null=True)
    last_name = models.CharField(max_length=100, blank=True, null=True)
    user_type = models.CharField(max_length=20, choices=USER_TYPE_CHOICES)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
    last_login_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True) # 建立時自動填入時間
    updated_at = models.DateTimeField(auto_now=True) # 每次更新時自動更新時間

    objects = UserManager() # 指定用我們自訂的 UserManager 來管理這個模型

    USERNAME_FIELD = 'email' # 設定用哪個欄位來當登入帳號
    REQUIRED_FIELDS = ['user_type'] # 建立使用者時,除了帳號密碼外,還要填這些欄位

    class Meta:
        db_table = 'users' # 指定這個模型對應到資料庫裡的表格名稱就是 'users'

    def __str__(self):
        return self.email # 在管理介面顯示這個使用者時,會顯示他的 email

2. Django Serializer 範例 (backend/apps/users/serializers.py)

Serializer(序列化器)是 Django REST Framework 的一個核心工具,它主要負責把複雜的資料(比如資料庫裡的模型物件)轉換成 Python 比較好處理的資料類型,然後再變成 JSON 或 XML 格式,方便網路傳輸。反過來,它也能處理從前端傳過來的資料,做驗證和轉換。

from rest_framework import serializers
from .models import User # 從目前的 App 裡引入 User 模型

# UserProfileSerializer 用來顯示使用者的個人資料
class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = User # 指定要處理的是 User 模型
        # 要在 API 回傳中顯示哪些欄位
        fields = ['id', 'username', 'email', 'full_name', 'user_type', 'status', 'created_at']
        # 這些欄位是唯讀的,前端傳回來也不能改
        read_only_fields = ['username', 'id', 'user_type', 'status', 'created_at']

# UserRegistrationSerializer 用來處理使用者註冊的資料
class UserRegistrationSerializer(serializers.ModelSerializer):
    password2 = serializers.CharField(write_only=True, required=True, style={'input_type': 'password'})
    
    class Meta:
        model = User
        # 註冊時需要哪些欄位
        fields = ['email', 'username', 'password', 'password2', 'full_name', 'user_type']
        # 密碼欄位只允許寫入,不允許讀取(安全性考量)
        extra_kwargs = {'password': {'write_only': True}}

    # 自訂驗證方法,確保兩次輸入的密碼是一樣的
    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({"password": "兩次輸入的密碼不一致。"})
        return attrs

    # 建立新使用者的方法
    def create(self, validated_data):
        # 註冊時,把 password2 這個多餘的欄位從資料中移除
        validated_data.pop('password2') 
        user = User.objects.create_user(
            email=validated_data['email'],
            username=validated_data['username'],
            password=validated_data['password'],
            full_name=validated_data.get('full_name', ''), # 取得全名,如果沒有就給空字串
            user_type=validated_data.get('user_type', 'customer') # 預設是顧客
        )
        return user

# PasswordChangeSerializer 用來處理使用者修改密碼的資料
class PasswordChangeSerializer(serializers.Serializer):
    old_password = serializers.CharField(required=True, style={'input_type': 'password'})
    new_password = serializers.CharField(required=True, style={'input_type': 'password'})
    new_password2 = serializers.CharField(write_only=True, required=True, style={'input_type': 'password'})

    def validate(self, attrs):
        if attrs['new_password'] != attrs['new_password2']:
            raise serializers.ValidationError({"new_password": "新密碼和確認密碼不一致。"})
        return attrs

3. Django View 範例 (backend/apps/users/views.py)

View 負責接收從前端來的請求,它會利用 Serializer 來處理資料和驗證,然後跟 Model 互動來讀寫資料庫。這裡我們使用了 Django REST Framework 的 generics 視圖,這些 View 已經幫我們預設好很多常見的 API 操作了。

from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.views import APIView
from .models import User
from .serializers import UserProfileSerializer, UserRegistrationSerializer, PasswordChangeSerializer
from django.contrib.auth.hashers import make_password, check_password

# 使用者註冊 View
# CreateAPIView 適合處理 POST 請求,用於建立新的資源
class UserRegistrationView(generics.CreateAPIView):
    queryset = User.objects.all() # 定義這個 View 會操作哪些資料(雖然這裡主要是建立)
    serializer_class = UserRegistrationSerializer # 指定要用哪個序列化器來處理資料
    permission_classes = [AllowAny] # 允許任何人都可以訪問(因為這是註冊功能)

# 使用者個人資料 View
# RetrieveUpdateAPIView 適合處理 GET(取得單一)和 PUT/PATCH(更新)請求
class UserProfileView(generics.RetrieveUpdateAPIView):
    queryset = User.objects.all()
    serializer_class = UserProfileSerializer
    permission_classes = [IsAuthenticated] # 只有登入的使用者才能訪問

    # 覆寫 get_object 方法,確保每個使用者都只能看到自己的個人資料
    def get_object(self):
        return self.request.user # 直接返回當前發出請求的使用者物件

# 密碼變更 View
# APIView 提供最基本的 View 類別,適合需要高度自訂邏輯的 API
class ChangePasswordView(APIView):
    permission_classes = [IsAuthenticated] # 只有登入的使用者才能訪問

    # 處理 POST 請求,用於變更密碼
    def post(self, request, *args, **kwargs):
        serializer = PasswordChangeSerializer(data=request.data)
        serializer.is_valid(raise_exception=True) # 驗證傳入的資料,如果驗證失敗會直接拋出錯誤

        user = request.user # 取得當前登入的使用者物件
        old_password = serializer.validated_data['old_password'] # 取得前端傳來的舊密碼
        new_password = serializer.validated_data['new_password'] # 取得前端傳來的新密碼

        # 檢查舊密碼是否正確,Django 內建的 check_password 會處理雜湊比對
        if not check_password(old_password, user.password):
            return Response(
                {"old_password": ["舊密碼不正確。"]}, # 回傳錯誤訊息
                status=status.HTTP_400_BAD_REQUEST # 回傳 HTTP 400 Bad Request 狀態碼
            )
        
        # 如果舊密碼正確,就設定新密碼並儲存使用者物件
        user.set_password(new_password) # 設定新密碼,Django 會自動雜湊
        user.save() # 儲存到資料庫

        return Response(
            {"detail": "密碼已成功變更。請重新登入。"}, # 回傳成功訊息
            status=status.HTTP_200_OK # 回傳 HTTP 200 OK 狀態碼
        )

4. Django App 的 URL 路由 (backend/apps/users/urls.py)

每個 App 都有自己專屬的 urls.py 檔案,用來定義這個 App 內部所有功能的網址路徑。

from django.urls import path
from . import views # 從當前目錄引入 views.py 裡的所有視圖

# 定義 URL 模式列表
urlpatterns = [
    # 使用者註冊的 API 端點
    # 當請求來到 'register/' 時,會由 views.UserRegistrationView 處理
    # name='user-register' 是給這個 URL 模式一個名稱,方便在程式中引用
    path('register/', views.UserRegistrationView.as_view(), name='user-register'),
    # 使用者個人資料的 API 端點 (可用於取得和更新資料)
    # 當請求來到 'profile/' 時,會由 views.UserProfileView 處理
    path('profile/', views.UserProfileView.as_view(), name='user-profile'),
    # 使用者密碼變更的 API 端點
    # 當請求來到 'change-password/' 時,會由 views.ChangePasswordView 處理
    path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
]

5. Django 專案的總 URL 路由 (backend/core_project/urls.py)

專案的總 urls.py 扮演著交通樞紐的角色,它負責把來自 Nginx 或是直接送過來的網址請求,導向到不同的 App 去處理。

from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
    TokenObtainPairView, # 這個 View 是用來讓使用者登入後,獲取 JWT 的訪問令牌 (Access Token) 和刷新令牌 (Refresh Token)
    TokenRefreshView,    # 這個 View 是用來讓使用者用刷新令牌,獲取新的訪問令牌(避免訪問令牌過期需要重新登入)
)

# 定義專案的總 URL 模式
urlpatterns = [
    # Django 內建的管理後台,通常用於開發者管理資料庫數據
    path('admin/', admin.site.urls),

    # JWT 認證相關的 API 端點
    # 登入介面,使用者會 POST 帳號密碼到這裡,成功後取得 JWT
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    # 刷新令牌介面,用於獲取新的訪問令牌
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    
    # Django REST Framework 提供的 browsable API 登入/登出介面 (通常只在開發模式下會用到)
    path('api-auth/', include('rest_framework.urls')),
    
    # 專案中各個自訂 App 的 URL 包含設定
    # 如果請求的網址以 '/api/users/' 開頭,就交給 apps.users.urls 去處理後面的路徑
    path('api/users/', include('apps.users.urls')),
    # 同理,處理商家的相關 API
    path('api/merchants/', include('apps.merchants.urls')),
    # 同理,處理預約的相關 API
    path('api/appointments/', include('apps.appointments.urls')),

    # 健康檢查端點,通常用於 Docker 或監控系統檢查服務是否正常運行
    path('api/health/', include('apps.common.urls')),

    # Prometheus 監控指標 (如果您的專案有安裝並啟用 django-prometheus,這行需要取消註釋)
    # path('metrics/', include('django_prometheus.urls')),
]

結語:Django 的學習之路,剛開始而已

恭喜您,已經大致了解 Django 的基礎以及這個專案的後端架構了!這個 booking-platform-django-fastapi 專案,結合了 Django 的穩定性與 FastAPI 的高性能推薦服務,是個不錯的學習範例。

目前這個專案提供的是一個強健的「骨架」與「核心邏輯片段」,方便您在其上進行更深入的開發。文章中提供的程式碼片段,也都是讓您理解核心概念、並能著手去「填充」這些功能的範例。

接下來,您可以:

  • 更深入了解 Model (backend/apps/*/models.py):看看不同欄位的型別怎麼設定、資料表之間怎麼建立關聯(一對多、多對多),以及 ORM 的增刪改查基本操作。

  • 鑽研 View (backend/apps/*/views.py):學習怎麼接收 HTTP 請求、處理資料、以及回傳 JSON 格式的回應(因為您的前端是 Vue.js,通常是走 RESTful API 路線)。

  • 釐清 URL 路由 (backend/core_project/urls.py & backend/apps/*/urls.py):搞懂網址的請求是怎麼一步步被導向到正確的 View 去處理的。

  • 探索 Django REST Framework (DRF):這是一個很實用的工具,可以幫助您快速打造 RESTful API,這個專案裡也大量用到了它。

Django 的世界其實挺大的,希望這篇文章能為您的學習之旅提供一些幫助,有個不錯的開端。祝您開發順利,做出好東西!

沒有留言:

張貼留言

網誌存檔