⭐ فصل ۱۲: نکات حرفهای
حالا که جنگو رو یاد گرفتی، بیا چند تکنیک حرفهای رو هم بدونی!
حالا که جنگو رو یاد گرفتی، بیا چند تکنیک حرفهای رو هم بدونی!
📑 فهرست این فصل
⚡ بهینهسازی عملکرد
۱. مشکل N+1 Query
یکی از پرتکرارترین مشکلات!
products = Product.objects.all()
for product in products:
print(product.category.name) # هر بار یک کوئری به دیتابیس!
# تعداد کوئریها: ۱ + N (یک به ازای هر محصول)
products = Product.objects.select_related('category').all()
for product in products:
print(product.category.name) # بدون کوئری اضافه!
# تعداد کوئریها: ۱ کوئری با JOIN
۲. select_related vs prefetch_related
| متد | کاربرد | استفاده برای |
|---|---|---|
select_related |
JOIN در یک کوئری | ForeignKey، OneToOneField |
prefetch_related |
کوئری جدا و ترکیب | ManyToMany، Reverse ForeignKey |
# ForeignKey - select_related
products = Product.objects.select_related('category', 'brand').all()
# ManyToMany - prefetch_related
products = Product.objects.prefetch_related('tags', 'images').all()
# ترکیبی
products = Product.objects.select_related('category').prefetch_related('tags')
۳. only و defer
اگه فقط چند فیلد میخوای، بقیه رو نگیر:
# فقط این فیلدها رو بگیر
products = Product.objects.only('id', 'name', 'price')
# همه فیلدها بهجز اینا
products = Product.objects.defer('description', 'long_text')
۴. values و values_list
# برگرداندن دیکشنری بهجای آبجکت (سریعتر)
data = Product.objects.values('id', 'name', 'price')
# لیست تاپلها
data = Product.objects.values_list('id', 'name')
# فقط یک ستون
ids = Product.objects.values_list('id', flat=True)
۵. bulk_create و bulk_update
# ❌ کند - 1000 کوئری
for i in range(1000):
Product.objects.create(name=f'محصول {i}')
# ✅ سریع - 1 کوئری
products = [Product(name=f'محصول {i}') for i in range(1000)]
Product.objects.bulk_create(products)
# آپدیت گروهی
products = Product.objects.filter(stock=0)
for p in products:
p.is_active = False
Product.objects.bulk_update(products, ['is_active'])
۶. ایندکسگذاری (Indexing)
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True) # ایندکس
slug = models.SlugField(unique=True) # خودکار ایندکس داره
class Meta:
# ایندکس ترکیبی
indexes = [
models.Index(fields=['name', 'category']),
models.Index(fields=['-created_at']),
]
🔒 نکات امنیتی
چکلیست امنیتی
- DEBUG=False در production
- SECRET_KEY در متغیر محیطی
- ALLOWED_HOSTS دقیق
- HTTPS فعال
- CSRF Token در همه فرمها
- اعتبارسنجی همه ورودیها
- عدم استفاده از
raw SQL - محدودیت
ALLOWED_FILE_TYPESدر آپلود - هش کردن رمزها (جنگو خودکار)
- استفاده از
get_object_or_404 - بکآپ منظم دیتابیس
محافظت در برابر SQL Injection
# ❌ خطرناک
name = request.GET.get('name')
Product.objects.raw(f"SELECT * FROM products WHERE name='{name}'")
# ✅ امن
Product.objects.filter(name=name) # ORM خودکار escape میکنه
# اگه مجبوری raw SQL بزنی
Product.objects.raw(
"SELECT * FROM products WHERE name=%s",
[name] # پارامتر رو جدا بده
)
محدودیت در آپلود فایل
def validate_file(value):
# حداکثر حجم
if value.size > 5 * 1024 * 1024: # 5MB
raise ValidationError('حجم فایل بیشتر از ۵ مگابایت است')
# نوع فایل
allowed_types = ['image/jpeg', 'image/png', 'image/webp']
if value.content_type not in allowed_types:
raise ValidationError('فقط jpg، png و webp مجاز است')
class ProductForm(forms.ModelForm):
image = forms.ImageField(validators=[validate_file])
🐛 دیباگ کردن
۱. Django Debug Toolbar
pip install django-debug-toolbar
if DEBUG:
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1']
if settings.DEBUG:
import debug_toolbar
urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]
۲. لاگگیری
import logging
logger = logging.getLogger(__name__)
def my_view(request):
logger.debug('شروع پردازش')
logger.info(f'کاربر {request.user} وارد شد')
try:
# کد
pass
except Exception as e:
logger.error(f'خطا: {e}', exc_info=True)
return render(request, 'page.html')
۳. Django Shell برای تست سریع
# Shell معمولی
python manage.py shell
# Shell+ بهتر (با ایمپورت خودکار) - نیاز به django-extensions
pip install django-extensions
python manage.py shell_plus
۴. بررسی کوئریها
from django.db import connection
def my_view(request):
products = Product.objects.all()
# دیدن کوئریهای اجرا شده
print(connection.queries)
print(f'تعداد کوئری: {len(connection.queries)}')
🧪 تستنویسی
from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth.models import User
from .models import Product, Category
class ProductModelTest(TestCase):
def setUp(self):
self.category = Category.objects.create(name='فرش')
self.product = Product.objects.create(
name='محصول تست',
price=100000,
stock=10,
category=self.category
)
def test_product_creation(self):
self.assertEqual(self.product.name, 'محصول تست')
self.assertEqual(self.product.price, 100000)
def test_str_method(self):
self.assertEqual(str(self.product), 'محصول تست')
class ProductViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='testuser',
password='testpass'
)
def test_product_list_view(self):
response = self.client.get(reverse('products:list'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'products/list.html')
def test_login_required(self):
response = self.client.get(reverse('products:create'))
self.assertRedirects(response, '/login/?next=/products/create/')
def test_create_product(self):
self.client.login(username='testuser', password='testpass')
response = self.client.post(reverse('products:create'), {
'name': 'جدید',
'price': 50000,
})
self.assertEqual(Product.objects.count(), 1)
# اجرای همه تستها
python manage.py test
# تست یک اپ خاص
python manage.py test products
# با verbosity بیشتر
python manage.py test --verbosity=2
🏛️ الگوهای حرفهای
۱. Fat Models, Thin Views
منطق رو توی مدل بذار، نه ویو:
def order_detail(request, pk):
order = get_object_or_404(Order, pk=pk)
# محاسبه در ویو
total = 0
for item in order.items.all():
total += item.quantity * item.price
discount = total * 0.1 if total > 1000000 else 0
final = total - discount
return render(request, 'order.html', {
'order': order,
'total': total,
'discount': discount,
'final': final
})
# models.py
class Order(models.Model):
# فیلدها...
@property
def total(self):
return sum(item.subtotal for item in self.items.all())
@property
def discount(self):
return self.total * 0.1 if self.total > 1000000 else 0
@property
def final_price(self):
return self.total - self.discount
# views.py
def order_detail(request, pk):
order = get_object_or_404(Order, pk=pk)
return render(request, 'order.html', {'order': order})
# قالب
<p>مجموع: {{ order.total }}</p>
<p>تخفیف: {{ order.discount }}</p>
<p>نهایی: {{ order.final_price }}</p>
۲. Custom Manager
class ActiveProductManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
class Product(models.Model):
name = models.CharField(max_length=200)
is_active = models.BooleanField(default=True)
objects = models.Manager() # پیشفرض
active = ActiveProductManager() # سفارشی
# استفاده
Product.objects.all() # همه
Product.active.all() # فقط فعالها
۳. Service Layer
from django.db import transaction
from .models import Order, OrderItem
from products.models import Product
class OrderService:
@staticmethod
@transaction.atomic
def create_order(user, items, address):
order = Order.objects.create(user=user, address=address)
total = 0
for item_data in items:
product = Product.objects.get(id=item_data['product_id'])
if product.stock < item_data['quantity']:
raise ValueError(f'موجودی {product.name} کافی نیست')
item = OrderItem.objects.create(
order=order,
product=product,
quantity=item_data['quantity'],
price=product.price
)
# کسر از موجودی
product.stock -= item.quantity
product.save()
total += item.quantity * item.price
order.total_price = total
order.save()
return order
# views.py
def checkout(request):
try:
order = OrderService.create_order(
user=request.user,
items=request.session['cart'],
address=request.POST['address']
)
return redirect('order_success', pk=order.id)
except ValueError as e:
messages.error(request, str(e))
return redirect('cart')
🛠️ ابزارهای ضروری
| ابزار | کاربرد |
|---|---|
django-debug-toolbar |
دیباگ و آنالیز کوئری |
django-extensions |
shell_plus، graph_models |
django-allauth |
احراز هویت کامل (گوگل، فیسبوک) |
django-filter |
فیلترهای پیشرفته |
django-cors-headers |
تنظیم CORS |
django-import-export |
ایمپورت/اکسپورت اکسل و CSV |
django-environ |
متغیرهای محیطی |
celery |
کارهای پسزمینه |
redis |
کش و broker |
pillow |
پردازش تصویر |
djangorestframework |
ساخت API |
simplejwt |
JWT برای DRF |
jdatetime |
تاریخ شمسی |
django-jalali |
فیلدهای تاریخ شمسی |
whitenoise |
سرو static در production |
sentry-sdk |
ردیابی خطا در production |
⚠️ اشتباهات رایج
این رو هرگز نکن! توی .env بذار.
اطلاعات حساس رو نمایش میده. حتماً False کن.
migrate بعد از تغییر مدل، حتماً makemigrations و migrate رو اجرا کن.
select_relatedباعث مشکل N+1 و کندی میشه.
منطق رو در ویو یا مدل بذار، قالب فقط برای نمایش.
حتماً بکآپ خودکار از دیتابیس بگیر.
🎓 جمعبندی
تبریک محمدعلی جان! 🎉
تو این آموزش با هم یاد گرفتیم:
- اصول و معماری جنگو
- کار با مدلها و ORM قدرتمند
- ساخت ویوها و URLها
- قالبنویسی و فرمها
- پنل ادمین و احراز هویت
- ساخت API با DRF
- سیگنالها، Middleware و Caching
- دیپلوی روی سرور
- نکات حرفهای و بهینهسازی
راه طولانی پیش رو داری ولی پایه قوی شده! حالا وقت تمرین و ساختنه.
📚 منابع برای ادامه یادگیری
- مستندات رسمی جنگو – بهترین منبع
- جامعه جنگو
- Awesome Django – کتابخونههای مفید
- Django Packages
- Real Python – آموزشهای عمیق
بهترین راه یادگیری، ساختن پروژه واقعیه. یه سایت کوچیک شخصی شروع کن و قدم به قدم بزرگش کن. در این مسیر، هر مشکلی برخورد کنی، فرصتی برای یادگیری بیشتره!