فایل کاربر را هرگز باور نکنید
آپلود فایل یکی از پرخطرترین بخشهای هر سامانه است: فایل حجیمی که دیسک را پر میکند، «عکسی» که در واقع اسکریپت است، نام فایلی با ../ یا حروف فارسی که روی سرور مشکل میسازد. جنگو ابزار کافی دارد، به شرط اینکه درست کنار هم گذاشته شوند.
تنظیمات پایه
# 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-Redirectnginx تحویل دهید.