فصل ۷: API و امکانات حرفه‌ای — DRF، کش، Celery و امنیت

Middleware سفارشی، لاگ‌گیری و فارسی‌سازی (i18n)

کدی که دور همه‌ی درخواست‌ها می‌پیچد

Middleware یک لایه‌ی سراسری است: هر درخواست قبل از رسیدن به View از آن عبور می‌کند و هر پاسخ در بازگشت. کارهایی که به همه‌ی صفحه‌ها مربوط‌اند جایشان این‌جاست: اندازه‌گیری زمان پاسخ، گرفتن IP واقعی، حالت تعمیرات، افزودن هدرهای امنیتی.

# apps/core/middleware.py
import logging
import time

logger = logging.getLogger("apps.requests")


class RequestTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response          # یک بار، هنگام شروع سرور

    def __call__(self, request):
        start = time.perf_counter()
        response = self.get_response(request)     # بقیه‌ی زنجیره و View
        ms = (time.perf_counter() - start) * 1000
        response["Server-Timing"] = f"app;dur={ms:.0f}"
        if ms > 1000:
            logger.warning("slow request %s %s %.0fms user=%s",
                           request.method, request.path, ms, getattr(request.user, "pk", None))
        return response


class MaintenanceMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        from django.conf import settings
        from django.shortcuts import render
        if settings.MAINTENANCE_MODE and not request.path.startswith("/manage-7f3a/"):
            return render(request, "503.html", status=503)
        return self.get_response(request)

ثبت در MIDDLEWARE: بعد از AuthenticationMiddleware تا request.user در دسترس باشد.

لاگ‌گیری که روی سرور به کار بیاید

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {"std": {"format": "{asctime} {levelname} {name} {message}", "style": "{"}},
    "handlers": {
        "console": {"class": "logging.StreamHandler", "formatter": "std"},
        "file": {"class": "logging.handlers.RotatingFileHandler",
                 "filename": BASE_DIR / "logs" / "app.log",
                 "maxBytes": 10 * 1024 * 1024, "backupCount": 5,
                 "formatter": "std", "encoding": "utf-8"},
    },
    "loggers": {
        "django": {"handlers": ["console", "file"], "level": "WARNING"},
        "apps": {"handlers": ["console", "file"], "level": "INFO"},
    },
}

با systemd، خروجی console خودکار در journal ذخیره می‌شود و با journalctl -u carpet -f دیده می‌شود.

فارسی‌سازی: i18n

حتی اگر سایت فقط فارسی است، متن‌های ثابت کد را با gettext_lazy علامت بزنید تا روزی که نسخه‌ی انگلیسی برای مشتری خارجی لازم شد، فقط فایل ترجمه بسازید:

from django.utils.translation import gettext_lazy as _

class Carpet(models.Model):
    name = models.CharField(_("نام طرح"), max_length=100)

# settings.py
LANGUAGES = [("fa", "فارسی"), ("en", "English")]
LOCALE_PATHS = [BASE_DIR / "locale"]
# MIDDLEWARE: "django.middleware.locale.LocaleMiddleware" بعد از SessionMiddleware
python manage.py makemessages -l en
python manage.py compilemessages

نکته‌هایی که کمتر کسی می‌داند

  • __init__ middleware فقط یک بار اجرا می‌شود؛ اگر در آن MiddlewareNotUsed بالا ببرید، جنگو آن middleware را کاملاً از زنجیره حذف می‌کند (مثلاً وقتی تنظیمی خاموش است).
  • هدر Server-Timing مستقیم در تب Network ابزار توسعه‌ی مرورگر نمایش داده می‌شود؛ بدون هیچ ابزار مانیتورینگی زمان سمت سرور را می‌بینید.
  • در سطح ماژول و فیلدهای مدل حتماً gettext_lazy بنویسید نه gettext؛ نسخه‌ی غیر lazy در زمان import، قبل از فعال شدن زبان، ترجمه می‌شود.
  • makemessages روی ویندوز به ابزار GNU gettext نیاز دارد که همراه پایتون نیست؛ یا نصبش کنید یا این مرحله را روی WSL یا سرور لینوکس انجام دهید.
  • encoding="utf-8" در handler فایل روی ویندوز ضروری است؛ بدون آن اولین پیام لاگ فارسی با UnicodeEncodeError کل لاگ‌گیری را خراب می‌کند.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.