~/icsd.ir — bash
SYSTEM_ONLINE

⭐ فصل ۱۲: نکات حرفه‌ای

حالا که جنگو رو یاد گرفتی، بیا چند تکنیک حرفه‌ای رو هم بدونی!

حالا که جنگو رو یاد گرفتی، بیا چند تکنیک حرفه‌ای رو هم بدونی!

⚡ بهینه‌سازی عملکرد

۱. مشکل 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 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

اگه فقط چند فیلد می‌خوای، بقیه رو نگیر:

views.py
# فقط این فیلدها رو بگیر
products = Product.objects.only('id', 'name', 'price')

# همه فیلدها به‌جز اینا
products = Product.objects.defer('description', 'long_text')

۴. values و values_list

views.py
# برگرداندن دیکشنری به‌جای آبجکت (سریع‌تر)
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

views.py
# ❌ کند - 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)

models.py
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

views.py
# ❌ خطرناک
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]  # پارامتر رو جدا بده
)

محدودیت در آپلود فایل

forms.py
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
settings.py
if DEBUG:
    INSTALLED_APPS += ['debug_toolbar']
    MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
    INTERNAL_IPS = ['127.0.0.1']
urls.py
if settings.DEBUG:
    import debug_toolbar
    urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]

۲. لاگ‌گیری

views.py
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

۴. بررسی کوئری‌ها

views.py
from django.db import connection

def my_view(request):
    products = Product.objects.all()
    
    # دیدن کوئری‌های اجرا شده
    print(connection.queries)
    print(f'تعداد کوئری: {len(connection.queries)}')

🧪 تست‌نویسی

products/tests.py
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

products/models.py
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

orders/services.py
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

⚠️ اشتباهات رایج

۱. ذخیره SECRET_KEY در کد

این رو هرگز نکن! توی .env بذار.

۲. DEBUG=True در production

اطلاعات حساس رو نمایش می‌ده. حتماً False کن.

۳. فراموشی migrate

بعد از تغییر مدل، حتماً makemigrations و migrate رو اجرا کن.

۴. عدم استفاده از select_related

باعث مشکل N+1 و کندی می‌شه.

۵. نوشتن کوئری در قالب

منطق رو در ویو یا مدل بذار، قالب فقط برای نمایش.

۶. عدم بک‌آپ‌گیری

حتماً بک‌آپ خودکار از دیتابیس بگیر.

🎓 جمع‌بندی

تبریک محمدعلی جان! 🎉

تو این آموزش با هم یاد گرفتیم:

  • اصول و معماری جنگو
  • کار با مدل‌ها و ORM قدرتمند
  • ساخت ویوها و URLها
  • قالب‌نویسی و فرم‌ها
  • پنل ادمین و احراز هویت
  • ساخت API با DRF
  • سیگنال‌ها، Middleware و Caching
  • دیپلوی روی سرور
  • نکات حرفه‌ای و بهینه‌سازی

راه طولانی پیش رو داری ولی پایه قوی شده! حالا وقت تمرین و ساختنه.

📚 منابع برای ادامه یادگیری

💡

توصیه نهایی

بهترین راه یادگیری، ساختن پروژه واقعیه. یه سایت کوچیک شخصی شروع کن و قدم به قدم بزرگش کن. در این مسیر، هر مشکلی برخورد کنی، فرصتی برای یادگیری بیشتره!

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.