🔐 فصل ۷: احراز هویت
جنگو یه سیستم احراز هویت کامل و آماده داره - فقط کافیه استفاده کنی!
جنگو یه سیستم احراز هویت کامل و آماده داره – فقط کافیه استفاده کنی!
📑 فهرست این فصل
👤 مدل User جنگو
جنگو یه مدل User آماده داره با این فیلدها:
username– نام کاربری (یکتا)email– ایمیلpassword– رمز عبور (هششده)first_nameوlast_nameis_active– فعال بودن حسابis_staff– دسترسی به ادمینis_superuser– مدیر کلdate_joined– تاریخ عضویتlast_login– آخرین ورود
from django.contrib.auth.models import User
# ساخت کاربر معمولی
user = User.objects.create_user(
username='ali',
email='ali@example.com',
password='SecurePass123'
)
# ساخت ادمین
admin = User.objects.create_superuser(
username='admin',
email='admin@site.com',
password='AdminPass123'
)
# بررسی رمز عبور
user.check_password('SecurePass123') # True یا False
# تغییر رمز عبور
user.set_password('NewPass456')
user.save()
اگه user.password = '123' بنویسی، رمز هش نمیشه. حتماً از set_password یا create_user استفاده کن.
📝 ثبتنام کاربر
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegisterForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=30, required=True, label='نام')
last_name = forms.CharField(max_length=30, required=True, label='نام خانوادگی')
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'password1', 'password2']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('این ایمیل قبلاً ثبت شده')
return email
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib import messages
from .forms import RegisterForm
def register(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user) # ← ورود خودکار بعد از ثبتنام
messages.success(request, f'حساب کاربری {user.username} ساخته شد!')
return redirect('home')
else:
form = RegisterForm()
return render(request, 'accounts/register.html', {'form': form})
🚪 ورود و خروج
روش ۱: استفاده از ویوهای آماده جنگو
from django.urls import path, include
urlpatterns = [
# ...
path('accounts/', include('django.contrib.auth.urls')),
]
این بهطور خودکار این URLها رو اضافه میکنه:
accounts/login/accounts/logout/accounts/password_change/accounts/password_reset/- و چند تای دیگه…
فقط باید قالبهاش رو بسازی (templates/registration/login.html و…)
روش ۲: ساخت دستی
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
messages.success(request, f'خوش آمدی {user.username}!')
# هدایت به صفحه قبلی یا خانه
next_url = request.GET.get('next', 'home')
return redirect(next_url)
else:
messages.error(request, 'نام کاربری یا رمز عبور اشتباه است')
return render(request, 'accounts/login.html')
def user_logout(request):
logout(request)
messages.info(request, 'با موفقیت خارج شدید')
return redirect('home')
قالب لاگین
<form method="POST" class="login-form">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">نام کاربری</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">رمز عبور</label>
<input type="password" name="password" class="form-control" required>
</div>
<input type="hidden" name="next" value="{{ request.GET.next }}">
<button type="submit" class="btn btn-primary">ورود</button>
</form>
🛡️ محدود کردن دسترسی
۱. login_required – فقط کاربر وارد شده
from django.contrib.auth.decorators import login_required
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
# هدایت به صفحه دلخواه برای ورود
@login_required(login_url='/login/')
def profile(request):
return render(request, 'profile.html')
۲. permission_required – دسترسی خاص
from django.contrib.auth.decorators import permission_required
@permission_required('products.add_product')
def add_product(request):
# فقط کسی که اجازه add_product داره
pass
@permission_required(['products.add_product', 'products.change_product'])
def manage_products(request):
# نیاز به هر دو دسترسی
pass
۳. user_passes_test – شرط دلخواه
from django.contrib.auth.decorators import user_passes_test
def is_admin(user):
return user.is_authenticated and user.is_staff
@user_passes_test(is_admin)
def admin_panel(request):
return render(request, 'admin_panel.html')
۴. در CBV – استفاده از Mixin
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
UserPassesTestMixin
)
from django.views.generic import ListView
class DashboardView(LoginRequiredMixin, ListView):
model = Order
template_name = 'dashboard.html'
login_url = '/login/'
class AdminView(UserPassesTestMixin, ListView):
model = User
def test_func(self):
return self.request.user.is_staff
تنظیمات احراز هویت
# آدرس صفحه ورود (پیشفرض)
LOGIN_URL = '/accounts/login/'
# آدرس بعد از ورود
LOGIN_REDIRECT_URL = '/'
# آدرس بعد از خروج
LOGOUT_REDIRECT_URL = '/'
👥 گروهها و دسترسیها
جنگو سیستم پیچیدهای برای مدیریت دسترسی داره:
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from products.models import Product
# ساخت گروه
editors = Group.objects.create(name='ویراستارها')
# اضافه کردن کاربر به گروه
user = User.objects.get(username='ali')
user.groups.add(editors)
# گرفتن دسترسی محصول
content_type = ContentType.objects.get_for_model(Product)
permission = Permission.objects.get(
codename='change_product',
content_type=content_type
)
# اضافه کردن دسترسی به گروه
editors.permissions.add(permission)
# اضافه کردن دسترسی مستقیم به کاربر
user.user_permissions.add(permission)
# بررسی دسترسی
user.has_perm('products.change_product') # True یا False
دسترسیهای پیشفرض
برای هر مدل، جنگو ۴ دسترسی میسازه:
add_modelname– افزودنchange_modelname– ویرایشdelete_modelname– حذفview_modelname– مشاهده
در قالب چک کنی
{% if user.is_authenticated %}
سلام {{ user.username }}
{% else %}
<a href="{% url 'login' %}">ورود</a>
{% endif %}
{% if perms.products.add_product %}
<a href="{% url 'add_product' %}">افزودن محصول</a>
{% endif %}
{% if user.is_staff %}
<a href="/admin/">پنل ادمین</a>
{% endif %}
🎨 مدل کاربر سفارشی
اگه میخوای فیلدهای بیشتری به User اضافه کنی، دو روش داری:
روش ۱: Profile جدا (ساده)
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=11, blank=True)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
birth_date = models.DateField(null=True, blank=True)
def __str__(self):
return f'پروفایل {self.user.username}'
# سیگنال - وقتی کاربر ساخته شد، پروفایل هم ساخته بشه
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
روش ۲: AbstractUser (حرفهای)
این روش بهتره ولی باید از اول پروژه باشه:
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
phone = models.CharField(max_length=11, blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
birth_date = models.DateField(null=True, blank=True)
def __str__(self):
return self.username
# مدل کاربر سفارشی
AUTH_USER_MODEL = 'accounts.CustomUser'
AbstractUser رو فقط در شروع پروژه قبل از اولین migrate استفاده کن. در غیر این صورت کلی دردسر داره.
🔑 بازیابی رمز عبور
جنگو سیستم آمادهای داره! فقط کافیه تنظیمات ایمیل رو انجام بدی:
# تنظیمات ایمیل
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-password'
# برای تست در حالت توسعه - ایمیل در کنسول چاپ بشه
# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
حالا URLهای آماده رو اضافه کن (اگه قبلاً نکردی) و قالبهاش رو بساز.
🎯 مثالهای کاربردی
مثال ۱: navbar با وضعیت لاگین
<nav>
{% if user.is_authenticated %}
<span>سلام {{ user.first_name|default:user.username }}</span>
<a href="{% url 'profile' %}">پروفایل</a>
<a href="{% url 'logout' %}">خروج</a>
{% else %}
<a href="{% url 'login' %}">ورود</a>
<a href="{% url 'register' %}">ثبتنام</a>
{% endif %}
</nav>
مثال ۲: محدود کردن دسترسی به سفارشات کاربر
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseForbidden
from .models import Order
@login_required
def my_orders(request):
# فقط سفارشات کاربر فعلی
orders = Order.objects.filter(user=request.user).order_by('-created_at')
return render(request, 'orders/list.html', {'orders': orders})
@login_required
def order_detail(request, order_id):
order = get_object_or_404(Order, id=order_id)
# کاربر فقط سفارش خودش رو ببینه
if order.user != request.user and not request.user.is_staff:
return HttpResponseForbidden('شما به این سفارش دسترسی ندارید')
return render(request, 'orders/detail.html', {'order': order})
مثال ۳: ورود با موبایل و OTP
import random
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.core.cache import cache
def request_otp(request):
if request.method == 'POST':
phone = request.POST.get('phone')
# ساخت کد ۶ رقمی
otp_code = random.randint(100000, 999999)
# ذخیره در کش با انقضای ۲ دقیقه
cache.set(f'otp_{phone}', otp_code, 120)
# ارسال SMS (اینجا فقط چاپ میکنیم)
print(f'کد ارسالشده به {phone}: {otp_code}')
# send_sms(phone, f'کد ورود: {otp_code}')
request.session['otp_phone'] = phone
return redirect('verify_otp')
return render(request, 'accounts/request_otp.html')
def verify_otp(request):
phone = request.session.get('otp_phone')
if not phone:
return redirect('request_otp')
if request.method == 'POST':
entered_code = request.POST.get('code')
cached_code = cache.get(f'otp_{phone}')
if cached_code and str(cached_code) == entered_code:
# یافتن یا ساخت کاربر
user, created = User.objects.get_or_create(
username=phone,
defaults={'first_name': 'کاربر'}
)
# ورود
login(request, user)
cache.delete(f'otp_{phone}')
del request.session['otp_phone']
return redirect('home')
else:
return render(request, 'accounts/verify_otp.html', {
'error': 'کد اشتباه است یا منقضی شده'
})
return render(request, 'accounts/verify_otp.html')
📝 تمرینها
تمرین ۱
یه ویو بساز که فقط کاربران لاگینشده بتونن ببیننش. اگه کاربر لاگین نباشه، به صفحه ورود هدایت بشه.
تمرین ۲
یه فرم تغییر رمز عبور بساز که کاربر رمز فعلی رو وارد کنه و رمز جدید رو تأیید کنه.
تمرین ۳
یه سیستم احراز هویت دو مرحلهای (2FA) با ایمیل بساز. بعد از ورود نام کاربری و رمز، یه کد به ایمیل ارسال بشه.