--- name: django-patterns description: Django 架構模式、使用 DRF 的 REST API 設計、ORM 最佳實踐、快取、信號 (Signals)、中間件 (Middleware) 以及生產等級的 Django 應用程序。 --- # Django 開發模式 (Django Development Patterns) 適用於可擴充、易維護應用程序的生產等級 Django 架構模式。 ## 何時啟用 - 建構 Django 網頁應用程序。 - 設計 Django REST Framework (DRF) API。 - 使用 Django ORM 和模型 (Models)。 - 設定 Django 專案結構。 - 實作快取、信號 (Signals)、中間件 (Middleware)。 ## 專案結構 ### 建議版面配置 (Recommended Layout) ``` myproject/ ├── config/ │ ├── __init__.py │ ├── settings/ │ │ ├── __init__.py │ │ ├── base.py # 基礎設定 │ │ ├── development.py # 開發環境設定 │ │ ├── production.py # 生產環境設定 │ │ └── test.py # 測試環境設定 │ ├── urls.py │ ├── wsgi.py │ └── asgi.py ├── manage.py └── apps/ ├── __init__.py ├── users/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── serializers.py │ ├── urls.py │ ├── permissions.py │ ├── filters.py │ ├── services.py │ └── tests/ └── products/ └── ... ``` ### 設定檔拆分模式 (Split Settings Pattern) ```python # config/settings/base.py from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent SECRET_KEY = env('DJANGO_SECRET_KEY') DEBUG = False ALLOWED_HOSTS = [] INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', 'corsheaders', # 在地 App 'apps.users', 'apps.products', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'config.urls' WSGI_APPLICATION = 'config.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST'), 'PORT': env('DB_PORT', default='5432'), } } # config/settings/development.py (開發環境) from .base import * DEBUG = True ALLOWED_HOSTS = ['localhost', '127.0.0.1'] DATABASES['default']['NAME'] = 'myproject_dev' INSTALLED_APPS += ['debug_toolbar'] MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # config/settings/production.py (生產環境) from .base import * DEBUG = False ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True # 記錄日誌 (Logging) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': '/var/log/django/django.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'WARNING', 'propagate': True, }, }, } ``` ## 模型設計模式 (Model Design Patterns) ### 模型最佳實踐 (Model Best Practices) ```python from django.db import models from django.contrib.auth.models import AbstractUser from django.core.validators import MinValueValidator, MaxValueValidator class User(AbstractUser): """擴展 AbstractUser 的自定義使用者模型。""" email = models.EmailField(unique=True) phone = models.CharField(max_length=20, blank=True) birth_date = models.DateField(null=True, blank=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] class Meta: db_table = 'users' verbose_name = 'user' verbose_name_plural = 'users' ordering = ['-date_joined'] def __str__(self): return self.email def get_full_name(self): return f"{self.first_name} {self.last_name}".strip() class Product(models.Model): """具備正確欄位配置的產品模型。""" name = models.CharField(max_length=200) slug = models.SlugField(unique=True, max_length=250) description = models.TextField(blank=True) price = models.DecimalField( max_digits=10, decimal_places=2, validators=[MinValueValidator(0)] ) stock = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=True) category = models.ForeignKey( 'Category', on_delete=models.CASCADE, related_name='products' ) tags = models.ManyToManyField('Tag', blank=True, related_name='products') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'products' ordering = ['-created_at'] indexes = [ models.Index(fields=['slug']), models.Index(fields=['-created_at']), models.Index(fields=['category', 'is_active']), ] constraints = [ models.CheckConstraint( check=models.Q(price__gte=0), name='price_non_negative' ) ] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) ``` ### QuerySet 最佳實踐 ```python from django.db import models class ProductQuerySet(models.QuerySet): """產品模型的自定義 QuerySet。""" def active(self): """僅回傳啟用的產品。""" return self.filter(is_active=True) def with_category(self): """預先選取相關類別以避免 N+1 查詢問題。""" return self.select_related('category') def with_tags(self): """針對多對多關係預先抓取 (Prefetch) 標籤。""" return self.prefetch_related('tags') def in_stock(self): """回傳庫存量大於 0 的產品。""" return self.filter(stock__gt=0) def search(self, query): """依據名稱或描述搜尋產品。""" return self.filter( models.Q(name__icontains=query) | models.Q(description__icontains=query) ) class Product(models.Model): # ... 欄位定義 ... objects = ProductQuerySet.as_manager() # 使用自定義 QuerySet # 使用範例 Product.objects.active().with_category().in_stock() ``` ### 管理員 (Manager) 方法 ```python class ProductManager(models.Manager): """用於複雜查詢的自定義管理員。""" def get_or_none(self, **kwargs): """回傳物件或 None,而非抛出 DoesNotExist 異常。""" try: return self.get(**kwargs) except self.model.DoesNotExist: return None def create_with_tags(self, name, price, tag_names): """建立產品及其關聯標籤。""" product = self.create(name=name, price=price) tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names] product.tags.set(tags) return product def bulk_update_stock(self, product_ids, quantity): """批次更新多個產品的庫存。""" return self.filter(id__in=product_ids).update(stock=quantity) # 在模型中使用 class Product(models.Model): # ... 欄位 ... custom = ProductManager() ``` ## Django REST Framework 模式 ### 序列化程式 (Serializer) 模式 ```python from rest_framework import serializers from django.contrib.auth.password_validation import validate_password from .models import Product, User class ProductSerializer(serializers.ModelSerializer): """產品模型的序列化程式。""" category_name = serializers.CharField(source='category.name', read_only=True) average_rating = serializers.FloatField(read_only=True) discount_price = serializers.SerializerMethodField() class Meta: model = Product fields = [ 'id', 'name', 'slug', 'description', 'price', 'discount_price', 'stock', 'category_name', 'average_rating', 'created_at' ] read_only_fields = ['id', 'slug', 'created_at'] def get_discount_price(self, obj): """計算適用折扣後的價格。""" if hasattr(obj, 'discount') and obj.discount: return obj.price * (1 - obj.discount.percent / 100) return obj.price def validate_price(self, value): """確保價格為非負數。""" if value < 0: raise serializers.ValidationError("價格不能為負數。") return value class ProductCreateSerializer(serializers.ModelSerializer): """用於建立產品的序列化程式。""" class Meta: model = Product fields = ['name', 'description', 'price', 'stock', 'category'] def validate(self, data): """針對多個欄位的自定義驗證。""" if data['price'] > 10000 and data['stock'] > 100: raise serializers.ValidationError( "高價產品不應有過多庫存。" ) return data class UserRegistrationSerializer(serializers.ModelSerializer): """使用者註冊序列化程式。""" password = serializers.CharField( write_only=True, required=True, validators=[validate_password], style={'input_type': 'password'} ) password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'}) class Meta: model = User fields = ['email', 'username', 'password', 'password_confirm'] def validate(self, data): """驗證兩次輸入的密碼是否一致。""" if data['password'] != data['password_confirm']: raise serializers.ValidationError({ "password_confirm": "密碼欄位不一致。" }) return data def create(self, validated_data): """建立帶有雜湊密碼的使用者。""" validated_data.pop('password_confirm') password = validated_data.pop('password') user = User.objects.create(**validated_data) user.set_password(password) user.save() return user ``` ### ViewSet 模式 ```python from rest_framework import viewsets, status, filters from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, IsAdminUser from django_filters.rest_framework import DjangoFilterBackend from .models import Product from .serializers import ProductSerializer, ProductCreateSerializer from .permissions import IsOwnerOrReadOnly from .filters import ProductFilter from .services import ProductService class ProductViewSet(viewsets.ModelViewSet): """產品模型的 ViewSet。""" queryset = Product.objects.select_related('category').prefetch_related('tags') permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_class = ProductFilter search_fields = ['name', 'description'] ordering_fields = ['price', 'created_at', 'name'] ordering = ['-created_at'] def get_serializer_class(self): """根據 Action 回傳適當的序列化程式。""" if self.action == 'create': return ProductCreateSerializer return ProductSerializer def perform_create(self, serializer): """保存時帶入使用者上下文。""" serializer.save(created_by=self.request.user) @action(detail=False, methods=['get']) def featured(self, request): """回傳精選產品。""" featured = self.queryset.filter(is_featured=True)[:10] serializer = self.get_serializer(featured, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def purchase(self, request, pk=None): """購買產品。""" product = self.get_object() service = ProductService() result = service.purchase(product, request.user) return Response(result, status=status.HTTP_201_CREATED) @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated]) def my_products(self, request): """回傳目前使用者建立的產品。""" products = self.queryset.filter(created_by=request.user) page = self.paginate_queryset(products) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) ``` ### 自定義 Action (Custom Actions) ```python from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @api_view(['POST']) @permission_classes([IsAuthenticated]) def add_to_cart(request): """將產品加入使用者購物車。""" product_id = request.data.get('product_id') quantity = request.data.get('quantity', 1) try: product = Product.objects.get(id=product_id) except Product.DoesNotExist: return Response( {'error': '找不到產品'}, status=status.HTTP_404_NOT_FOUND ) cart, _ = Cart.objects.get_or_create(user=request.user) CartItem.objects.create( cart=cart, product=product, quantity=quantity ) return Response({'message': '已加入購物車'}, status=status.HTTP_201_CREATED) ``` ## 服務層模式 (Service Layer Pattern) ```python # apps/orders/services.py from typing import Optional from django.db import transaction from .models import Order, OrderItem class OrderService: """針對訂單相關業務邏輯的服務層。""" @staticmethod @transaction.atomic def create_order(user, cart: Cart) -> Order: """從購物車建立訂單。""" order = Order.objects.create( user=user, total_price=cart.total_price ) for item in cart.items.all(): OrderItem.objects.create( order=order, product=item.product, quantity=item.quantity, price=item.product.price ) # 清空購物車 cart.items.all().delete() return order @staticmethod def process_payment(order: Order, payment_data: dict) -> bool: """處理訂單付款。""" # 串接支付閘道器 (Payment Gateway) payment = PaymentGateway.charge( amount=order.total_price, token=payment_data['token'] ) if payment.success: order.status = Order.Status.PAID order.save() # 發送確認郵件 OrderService.send_confirmation_email(order) return True return False @staticmethod def send_confirmation_email(order: Order): """發送訂單確認郵件。""" # 郵件發送邏輯 pass ``` ## 快取策略 (Caching Strategies) ### 視圖層級快取 (View-Level Caching) ```python from django.views.decorators.cache import cache_page from django.utils.decorators import method_decorator @method_decorator(cache_page(60 * 15), name='dispatch') # 快取 15 分鐘 class ProductListView(generic.ListView): model = Product template_name = 'products/list.html' context_object_name = 'products' ``` ### 範本片段快取 (Template Fragment Caching) ```django {% load cache %} {% cache 500 sidebar %} ... 高成本的側邊欄內容 ... {% endcache %} ``` ### 低階快取 (Low-Level Caching) ```python from django.core.cache import cache def get_featured_products(): """獲取精選產品並執行快取。""" cache_key = 'featured_products' products = cache.get(cache_key) if products is None: products = list(Product.objects.filter(is_featured=True)) cache.set(cache_key, products, timeout=60 * 15) # 15 分鐘 return products ``` ### QuerySet 快取 ```python from django.core.cache import cache def get_popular_categories(): cache_key = 'popular_categories' categories = cache.get(cache_key) if categories is None: categories = list(Category.objects.annotate( product_count=Count('products') ).filter(product_count__gt=10).order_by('-product_count')[:20]) cache.set(cache_key, categories, timeout=60 * 60) # 1 小時 return categories ``` ## 信號 (Signals) ### 信號模式 (Signal Patterns) ```python # apps/users/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth import get_user_model from .models import Profile User = get_user_model() @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): """建立使用者時同時建立 Profile。""" if created: Profile.objects.create(user=instance) @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): """保存使用者時同時保存 Profile。""" instance.profile.save() # apps/users/apps.py from django.apps import AppConfig class UsersConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'apps.users' def ready(self): """當 App 就緒時導入信號。""" import apps.users.signals ``` ## 中間件 (Middleware) ### 自定義中間件 ```python # middleware/active_user_middleware.py import time from django.utils.deprecation import MiddlewareMixin class ActiveUserMiddleware(MiddlewareMixin): """用於追蹤活躍使用者的中間件。""" def process_request(self, request): """處理傳入的請求。""" if request.user.is_authenticated: # 更新最後活動時間 request.user.last_active = timezone.now() request.user.save(update_fields=['last_active']) class RequestLoggingMiddleware(MiddlewareMixin): """用於記錄請求日誌的中間件。""" def process_request(self, request): """記錄請求開始時間。""" request.start_time = time.time() def process_response(self, request, response): """記錄請求耗時。""" if hasattr(request, 'start_time'): duration = time.time() - request.start_time logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s') return response ``` ## 效能優化 ### 預防 N+1 查詢問題 ```python # 不良的做法 - 會產生 N+1 個查詢 products = Product.objects.all() for product in products: print(product.category.name) # 會針對每個產品發送獨立查詢 # 推薦的做法 - 使用 select_related 進行單一查詢 products = Product.objects.select_related('category').all() for product in products: print(product.category.name) # 多對多關係推薦使用 prefetch_related products = Product.objects.prefetch_related('tags').all() for product in products: for tag in product.tags.all(): print(tag.name) ``` ### 資料庫索引 ```python class Product(models.Model): name = models.CharField(max_length=200, db_index=True) slug = models.SlugField(unique=True) category = models.ForeignKey('Category', on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(fields=['name']), models.Index(fields=['-created_at']), models.Index(fields=['category', 'created_at']), ] ``` ### 批次操作 (Bulk Operations) ```python # 批次建立 (Bulk create) Product.objects.bulk_create([ Product(name=f'Product {i}', price=10.00) for i in range(1000) ]) # 批次更新 (Bulk update) products = Product.objects.all()[:100] for product in products: product.is_active = True Product.objects.bulk_update(products, ['is_active']) # 批次刪除 Product.objects.filter(stock=0).delete() ``` ## 快速參考 | 模式 | 描述 | |---------|-------------| | 設定檔拆分 (Split settings) | 分離開發/生產/測試環境設定 | | 自定義 QuerySet | 可重用的查詢方法 | | 服務層 (Service Layer) | 業務邏輯分離 | | ViewSet | REST API 端點 | | 序列化與驗證 | 請求/回應轉換 | | select_related | 外鍵 (Foreign key) 優化 | | prefetch_related | 多對多關係優化 | | 快取優先 (Cache first) | 先行快取高成本操作 | | 信號 (Signals) | 事件驅動行為 | | 中間件 (Middleware) | 請求/回應處理 | 請記住:Django 提供了許多捷徑,但對於生產環境的應用程序而言,良好的結構與組織比精簡的程式碼更為重要。請為「可維護性」而建構。