為什麼要分離圖片處理邏輯?Django 最佳實踐指南
更新日期: 2024 年 12 月 26 日
本文為圖片轉 WebP 功能模組化系列文,第 5 篇:
- 如何設計 Django 的通用工具,並選擇適合的存放位置
- 從零開始:如何實現圖片轉 WebP 並上傳到 S3 的功能
- 使用基類模型(class model)優化代碼:從零到掌握 DRY 原則
- Django 抽象模型:理解 class Meta: abstract = True
- 為什麼要分離圖片處理邏輯?Django 最佳實踐指南 👈 所在位置
- Django 信號處理:如何在保存前自動處理圖片
- Django 信號處理的最佳實踐:如何選擇合適的位置
- 將圖片處理移至信號:模型設計的最佳實踐
- 理解 Python 的方法解析順序 (MRO):Django 多重繼承的最佳實踐
建議閱讀本文前,先閱讀完 圖片轉 WebP 系列文
在 Django 項目中,將代碼合理分層是保持代碼可讀性、可維護性和可測試性的關鍵之一。
對於圖片處理這類業務邏輯,是否應該單獨抽離成工具函數?
本文將帶你理解分離邏輯的原因和優勢,以及在不同情況下該如何做出選擇。
分離關注點(Separation of Concerns)
關注點分離的原則強調,每個模組或類應該只專注於完成單一的責任。
- 原因: 模型的核心職責是定義數據結構,而圖片轉換和上傳則屬於業務邏輯,與模型的主要功能不同。
將兩者混合在一起會使代碼臃腫,降低可讀性。 - 優勢:
- 代碼更清晰:模型專注於數據結構,圖片處理則由工具模組負責。
- 便於修改:當圖片處理邏輯需要變更時,直接修改工具函數即可,無需進一步修改模型代碼。
提升代碼的可重用性
在多個地方可能需要執行類似的圖片處理邏輯(如 Profile
模型和 ServiceImage
模型)時,將這些邏輯抽取成工具函數尤為重要。
- 原因: 抽取通用邏輯可以避免代碼重複,方便多處調用。
- 優勢:
- 減少重複代碼:一旦工具函數完成,任何需要圖片處理的地方都可以直接使用。
- 方便擴展:如果未來需要支持其他圖片格式(如 PNG 或 GIF),只需修改工具函數即可,不必在每個模型中逐一更新。
提高代碼的可測試性
工具函數的分離不僅讓邏輯更清晰,還能讓測試更加簡單和高效。
- 原因: 單獨的工具函數可以被獨立測試,避免因與模型的耦合而導致測試覆蓋率下降。
- 優勢:
- 易於測試:工具函數可以模擬圖片轉換和上傳邏輯,並獨立驗證其結果。
- 錯誤定位更清晰:圖片處理出現問題時,可以單獨檢查工具函數,而無需調試整個模型的保存流程。
什麼情況下可以不分離?
如果圖片處理邏輯只在單一模型中使用,並且該邏輯相對簡單,那麼可以直接將邏輯寫在模型的 save
方法中。
示例:直接在模型中處理圖片
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
photo = models.ImageField(upload_to="profile_photos/", blank=True, null=True)
def save(self, *args, **kwargs):
# 圖片轉 WebP 並上傳邏輯
if self.photo:
from PIL import Image
from io import BytesIO
import boto3
import uuid
from django.conf import settings
image = Image.open(self.photo)
webp_image_io = BytesIO()
image.save(webp_image_io, format="WEBP", quality=85)
webp_image_io.seek(0)
unique_filename = f"profile_photos/{uuid.uuid4()}.webp"
s3 = boto3.client("s3", region_name=settings.AWS_S3_REGION_NAME)
s3.upload_fileobj(
webp_image_io,
settings.AWS_STORAGE_BUCKET_NAME,
unique_filename,
ExtraArgs={"ContentType": "image/webp"},
)
self.photo.name = unique_filename
super().save(*args, **kwargs)
這種方式在邏輯簡單、只需在單一模型中實現時是可行的,但隨著功能擴展,這種方式容易導致代碼重複,增加維護成本。
如何實現邏輯分離?
將圖片處理邏輯提取到工具模組是一種更符合 Django 最佳實踐的做法。
步驟:創建工具函數
在項目中新增一個工具模組,例如 utils/image_processing.py
:
from PIL import Image
from io import BytesIO
import boto3
import uuid
from django.conf import settings
def process_and_upload_image(photo, folder_name):
# 圖片轉換為 WebP
image = Image.open(photo)
webp_image_io = BytesIO()
image.save(webp_image_io, format="WEBP", quality=85)
webp_image_io.seek(0)
# 生成唯一文件名
unique_filename = f"{folder_name}/{uuid.uuid4()}.webp"
# 上傳至 S3
s3 = boto3.client("s3", region_name=settings.AWS_S3_REGION_NAME)
s3.upload_fileobj(
webp_image_io,
settings.AWS_STORAGE_BUCKET_NAME,
unique_filename,
ExtraArgs={"ContentType": "image/webp"},
)
return unique_filename
步驟:在模型中調用工具函數
將工具函數集成到模型中,讓模型邏輯更加簡潔:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
photo = models.ImageField(upload_to="profile_photos/", blank=True, null=True)
def save(self, *args, **kwargs):
if self.photo:
from utils.image_processing import process_and_upload_image
unique_filename = process_and_upload_image(self.photo, "profile_photos")
self.photo.name = unique_filename
super().save(*args, **kwargs)
綜合建議
何時直接在模型中處理邏輯?
- 當圖片處理邏輯僅限於單個模型使用。
- 預計未來不會在多個地方復用。
何時將邏輯抽離為工具函數?
- 當圖片處理邏輯需要在多個模型中使用。
- 需要提高代碼的可重用性和可測試性。
- 預計未來會進行功能擴展或修改。
總結
將圖片處理邏輯單獨抽離成工具函數是一種符合 Django 最佳實踐的設計方式,可以提升代碼的可讀性、重用性和測試效率。
然而,根據項目的複雜程度和需求,可以靈活選擇是否分離。
合理的結構設計能讓你的代碼更加清晰高效,未來維護起來也更加輕鬆! 😊