فصل ۶: ادمین، احراز هویت و دسترسی

Permission و Group: has_perm، مجوز سفارشی و دسترسی سطح شیء

چه کسی اجازه‌ی چه کاری را دارد؟

احراز هویت می‌گوید «تو کیستی»؛ مجوز می‌گوید «اجازه‌ی چه کاری داری». جنگو برای هر مدل چهار مجوز خودکار می‌سازد: add، change، delete و view. نام کامل هر مجوز به شکل app_label.codename است؛ مثلاً orders.change_order. مجوزها به کاربر یا به Group داده می‌شوند و کاربر مجوزهای همه‌ی گروه‌هایش را به ارث می‌برد.

مجوز سفارشی

class Order(models.Model):
    ...
    class Meta:
        permissions = [
            ("approve_order", "می‌تواند سفارش را تأیید کند"),
            ("export_orders", "می‌تواند خروجی اکسل سفارش‌ها را بگیرد"),
        ]

بعد از makemigrations و migrate، این مجوزها در ادمین قابل تخصیص‌اند.

ساخت گروه‌ها با کد، نه با کلیک

# apps/accounts/management/commands/setup_roles.py
from django.contrib.auth.models import Group, Permission
from django.core.management.base import BaseCommand

ROLES = {
    "فروش": ["orders.view_order", "orders.change_order", "orders.approve_order"],
    "انبار": ["catalog.view_carpet", "catalog.change_carpet"],
    "حسابداری": ["orders.view_order", "orders.export_orders"],
}


class Command(BaseCommand):
    def handle(self, *args, **opts):
        for name, perms in ROLES.items():
            group, _ = Group.objects.get_or_create(name=name)
            objs = []
            for p in perms:
                app_label, codename = p.split(".")
                objs.append(Permission.objects.get(content_type__app_label=app_label, codename=codename))
            group.permissions.set(objs)
            self.stdout.write(f"گروه {name}: {len(objs)} مجوز")

بررسی مجوز در View و قالب

from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin

@permission_required("orders.approve_order", raise_exception=True)
def approve(request, pk): ...

class ExportView(PermissionRequiredMixin, View):
    permission_required = "orders.export_orders"

if request.user.has_perm("orders.approve_order"): ...
{% if perms.orders.approve_order %}
  <button>تأیید سفارش</button>
{% endif %}

دسترسی سطح شیء

مجوزهای جنگو «سطح مدل» هستند: یا می‌توانید همه‌ی سفارش‌ها را تأیید کنید یا هیچ‌کدام را. برای «نماینده‌ی فروش فقط سفارش‌های شهر خودش را» دو راه دارید: ساده و رایج، محدود کردن queryset در get_queryset؛ یا نوشتن یک authentication backend که has_perm(user_obj, perm, obj=None) را با منطق شیء پیاده کند (یا کتابخانه‌هایی مثل django-rules و django-guardian).

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

  • مجوزها روی شیء user کش می‌شوند؛ اگر در همان درخواست مجوزی اضافه کنید، has_perm هنوز False می‌دهد تا کاربر را دوباره از پایگاه داده بخوانید.
  • is_superuser همه‌ی بررسی‌های has_perm را دور می‌زند، اما is_active=False حتی برای سوپریوزر همه را False می‌کند.
  • مجوزها در سیگنال post_migrate ساخته می‌شوند؛ در data migration ممکن است هنوز وجود نداشته باشند. به همین دلیل ساخت گروه‌ها با دستور مدیریتی بعد از migrate امن‌تر است.
  • raise_exception=True برای کاربر لاگین‌شده‌ی بدون مجوز 403 برمی‌گرداند؛ بدون آن به صفحه‌ی ورود ریدایرکت می‌شود و کاربر در حلقه‌ی «وارد شدم ولی باز صفحه‌ی ورود» گیر می‌کند.
  • is_staff فقط اجازه‌ی ورود به ادمین است، نه هیچ مجوز دیگری؛ کاربر staff بدون مجوز در ادمین صفحه‌ای خالی می‌بیند.

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