فصل ۵: فرم‌ها و اعتبارسنجی — ورودی امن و فارسی

آپلود فایل: ImageField، upload_to، محدودیت حجم و نوع

فایل کاربر را هرگز باور نکنید

آپلود فایل یکی از پرخطرترین بخش‌های هر سامانه است: فایل حجیمی که دیسک را پر می‌کند، «عکسی» که در واقع اسکریپت است، نام فایلی با ../ یا حروف فارسی که روی سرور مشکل می‌سازد. جنگو ابزار کافی دارد، به شرط این‌که درست کنار هم گذاشته شوند.

تنظیمات پایه

# settings.py
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
DATA_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024      # سقف بدنه‌ی درخواست بدون احتساب فایل‌ها
FILE_UPLOAD_MAX_MEMORY_SIZE = 2 * 1024 * 1024      # بزرگ‌تر از این روی دیسک موقت می‌رود
FILE_UPLOAD_PERMISSIONS = 0o644

مدل با اعتبارسنجی حجم و نوع

# apps/catalog/models.py
from pathlib import Path
from uuid import uuid4

from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator
from django.db import models

MAX_IMAGE_MB = 3


def carpet_image_path(instance, filename):
    ext = Path(filename).suffix.lower()
    return f"carpets/{instance.carpet.code}/{uuid4().hex}{ext}"   # نام تصادفی لاتین


def validate_image_size(f):
    if f.size > MAX_IMAGE_MB * 1024 * 1024:
        raise ValidationError(f"حجم تصویر حداکثر {MAX_IMAGE_MB} مگابایت است.")


class CarpetImage(models.Model):
    carpet = models.ForeignKey("catalog.Carpet", on_delete=models.CASCADE, related_name="images")
    image = models.ImageField(
        "تصویر",
        upload_to=carpet_image_path,
        validators=[FileExtensionValidator(["jpg", "jpeg", "png", "webp"]), validate_image_size],
    )
    alt = models.CharField(max_length=150, blank=True)

ImageField به کتابخانه‌ی Pillow نیاز دارد و فایل را واقعاً به‌عنوان تصویر باز می‌کند؛ فایل متنی که پسوند jpg گرفته رد می‌شود. پسوند را هم جدا بررسی می‌کنیم چون Pillow فرمت‌هایی را هم می‌شناسد که نمی‌خواهید (مثل TIFF و فرمت‌های کمتر امن).

فرم و View

<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  {{ form.as_div }}
  <button>بارگذاری</button>
</form>
form = CarpetImageForm(request.POST, request.FILES)   # FILES را فراموش نکنید

در توسعه، سرو فایل‌های media با static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) در urls انجام می‌شود (درس ۱۶)؛ در سرور nginx مستقیم آن‌ها را سرو می‌کند و در nginx هم باید client_max_body_size را متناسب تنظیم کنید.

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

  • FILE_UPLOAD_MAX_MEMORY_SIZE حجم فایل را محدود نمی‌کند؛ فقط آستانه‌ی رفتن از حافظه به فایل موقت است. سقف واقعی را validator و client_max_body_size در nginx تعیین می‌کنند.
  • بدون enctype="multipart/form-data" مرورگر فقط نام فایل را می‌فرستد و فرم با خطای «این فیلد لازم است» برمی‌گردد؛ گیج‌کننده‌ترین باگ آپلود.
  • حذف رکورد مدل فایل را از دیسک پاک نمی‌کند (عمداً، به‌خاطر تراکنش‌ها)؛ برای پاک‌سازی از transaction.on_commit در سیگنال post_delete یا یک دستور مدیریتی دوره‌ای استفاده کنید.
  • content_type فایل آپلودی را مرورگر اعلام می‌کند و قابل جعل است؛ هرگز به آن برای امنیت تکیه نکنید.
  • فایل‌های خصوصی (فاکتور، قرارداد) را در MEDIA عمومی نگذارید؛ آن‌ها را بیرون از ریشه‌ی وب ذخیره و از طریق یک View با بررسی دسترسی و هدر X-Accel-Redirect nginx تحویل دهید.

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