Herkese merhaba. Bir web sitesine kayıt olduktan sonra e posta adresimizi doğrulamamız gerektiği isteğiyle bir çok defa karşılaşmışızdır. Doğrulanmış hesaplar ile parola sıfırlama, parola değiştirme işlemleri yapılabilmekte veya spam oluşumunun bir nebze de önüne geçilebilmekte.
Peki bunu Django ile geliştirilmiş bir sistemde nasıl yapabiliriz? Django’nun hali hazırdaki auth uygulaması bir çok yerleşik işlemi beraberinde getirsede e posta doğrulama bunlardan biri değil. Fakat bu özelliği ona kazandırmak çok çok basit. İsterseniz 3.parti bir uygulama ile bu işlemi halledebilirsiniz ya da kendiniz böyle bir sistem yazabilirsiniz.
Hazırsanız yeni bir proje oluşturarak başlayalım. (Django 2.2 sürümü kullanılmıştır)
Bulunulan dizine bir proje oluşturup accounts uygulaması oluşturuldu.
django-admin startproject email_login . && ./manage.py startapp accounts
├── accounts │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── models.py │ ├── templates │ │ └── accounts │ │ ├── activate_account.html │ │ ├── login.html │ │ └── register.html │ ├── tests.py │ ├── tokens.py │ └── views.py ├── db.sqlite3 ├── email_login │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── README.md ├── requirements.txt └── templates └── index.html
Yapılması gerekenler.
- Email ayarları.
- Kullanıcı kaydı.
- Token oluşturucu.
- Mail gönderimi.
- Aktivasyon işlemi.
- Giriş çıkış işlemleri ve url yönlendirmeleri.
settings.py dosyasında şu değişiklikleri yapın.
EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = 'your account example@gmail.com' EMAIL_HOST_PASSWORD = 'account password' EMAIL_PORT = 587
Hizmet aldığınız servisini bilgilerini giriyoruz. (Bu yazıda Gmail kullanılmaktadır. Hata alırsanız 3.parti uygulamalara izin verin ve capctha doğrulamasını kapatın.)
Kullanıcı kaydı.
Django’nun yerleşik kullanıcı kayıt formu UserCreationForm‘u kullanacağız fakat biraz özelleştirmemiz ve validasyon eklememiz gerekmekte.
accounts/forms.py
from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm UserModel = get_user_model() class CustomUserCreationForm(UserCreationForm): class Meta: model = UserModel fields = [ 'username', 'email', 'password1', 'password2' ] def clean_email(self): if UserModel.objects.filter(email=self.cleaned_data.get('email')).exists(): raise forms.ValidationError('This email exists on system') return self.cleaned_data['email'] def save(self, commit=True): user = super().save(commit=False) if commit: user.email = self.cleaned_data.get('email') user.is_active = False user.save() return user
UserCreationForm user modelinden türetilen bir modelform. Dolayısıyla email alanı içermekte fakat herhangi bir validasyona tabi tutulmamakta. Bu yüzden Meta classı ile bu alanı ekliyoruz ve o adresin sistemde kayıtlı olup olmadığı kontrolünü ekliyoruz. Daha sonra save metodunu ezerek her kullanıcı kaydında formdan dönen email adresinin kayıt edilen kullanıcıya iliştirilmesini sağlıyoruz. is_active default kullanıcı modelinde gelen bir kullanıcı özelliği. Bool bir ifade tutmakta ve False değer taşıyan kullanıcının sisteme giriş yapmasını engellemekte. Bunu da ekliyoruz. Tamam görünüyor.
Token oluşturucu.
accounts/tokens.py
from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.utils import six class TokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return ( six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active) ) account_activate_token = TokenGenerator()
Django kullanıcı parola sıfırlama işlemleri fonksiyonlara ve token oluşturucuya sahiptir. Ama bunu farklı bir yolla yapar.
İlk olarak bir sistemde tek kullanımlık linkler veya kodlar üretilecekken unique olmasını gerektirecek işlemler yapılmalıdır. Mesela giriş için giriş koduna ihtiyacınız olan bir durumda kullanıcı tablosuna bir alan ekleyip her giriş isteği geldiğinde kod oluşturup oraya yazmak daha sonra doğrulayıp dump etmek çalışan bir yaklaşımdır ama her seferinde bu komutları koşturmak bir süre sonra kullanıcı sayısı arttığında yanlış bir yaklaşım olur. Django ise veritabanına müdahele etmeden token leri yönetebilir. Oluşturulan tokene ihtiyaç duyulduğunda tokeni kontrol edebilir.
Bunun için default token üretecini miras alıp gerekli fonksiyonu ezip bir token üretici oluşturuyoruz ve dışarı aktarıyoruz. Bu üreteç kullanıcı id’si, isteğin yapıldığı zaman ve kullanıcı aktiflik durumuna göre(False) bir anahtar oluşturuyor.
Kullanıcı kaydı ve mail gönderimi.
accounts/views.py
Öncelikle diğer view larda da kullanılacak uzun bir import listesi var onları ekleyerek başlayalım.
from django.conf import settings from django.contrib import messages from django.contrib.auth import login from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User from django.contrib.auth.views import LoginView from django.contrib.messages.views import SuccessMessageMixin from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail from django.http import HttpResponse from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy, reverse from django.utils.encoding import force_bytes from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.views import View from django.views.generic import FormView from accounts.forms import CustomUserCreationForm from .tokens import account_activate_token
Kayıt işlemi.
class RegisterUser(SuccessMessageMixin, FormView): form_class = CustomUserCreationForm template_name = 'accounts/register.html' success_url = reverse_lazy('index') success_message = 'Successfully registered! But activate your account before login!' def form_valid(self, form): user = form.save(commit=True) mail_subject = 'Activate your account. Before login.' current_site = get_current_site(self.request) uid = urlsafe_base64_encode(force_bytes(user.pk)) token = account_activate_token.make_token(user) link_dict = {'url': reverse('activate', kwargs={'uidb64': uid, 'token': token})} link = f"{get_current_site(self.request).domain}{link_dict.get('url')}" message = render_to_string('accounts/activate_account.html', { 'user': user, 'domain': current_site.domain, 'uid': uid, 'token': token }) email = send_mail(mail_subject, message, settings.EMAIL_HOST_USER, (user.email,), fail_silently=True) print('success\n', link) if email else print('something wrong!') return super().form_valid(form)
Gerekli sınıfları miras alıyoruz. Daha sonra hangi formla hangi template de çalışacağımızı işlemin başarılı olması durumunda mesajı ve döneceğimiz adresi belirtiyoruz. Form valid dönmüş ise mail konusunu belirliyoruz. urlsafe encode fonksiyonu ile url de kullanmak üzere bytestring base64 stringine kodluyoruz. Oluşturduğumuz üreteç ile bir token oluşturuyoruz. Sonraki iki satır mail istemcinize girmeye üşenirseniz diye 🙂 terminale çıktı göndermek için.
render_to_string ile bir template fonksiyonu. Amacı ilk argüman template’i yükleyip içine context göndermek ve template’de kullanılmasını sağlamak. render metodu gibi düşünebilirsiniz. send_mail fonksiyonuna ilk geçtiğimiz argüman mail konusu ve sonrasında sırasıyla mesaj, mail’i göndereceğimiz user, mail alıcısı fakat burası önemli gönderi listesi birden fazla olabileceği için ya liste ya da demet vermelisiniz. Tek elemanlı demet oluşturmak için virgül koymayı unutmayın 🙂 fail_silently default değeri False dur. Yani işlem başarısız ise debug ekranında hata gösterir. True ya çekerek bool bir ifade dönmesini sağlıyoruz ki işlemi kontrol edelim.
accounts/templates/accounts/activate_account.html
{% autoescape off %} Hi {{ user.username }}, Please click on the link below to confirm your registration. http://{{ domain }}{% url 'activate' uidb64=uid token=token %} {% endautoescape %}
Gönderilecek mail.
accounts/templates/accounts/register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Register</title> </head> <body> <h1>Register.</h1> <form action="" method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Register!</button> </form> </body> </html>
Aktivasyon.
class ActivateView(View): def get(self, request, uidb64, token): try: uid = force_text(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) except (User.DoesNotExist, TypeError, ValueError, OverflowError): user = None if user is not None and account_activate_token.check_token(user=user, token=token): user.is_active = True user.save() login(request=request, user=user) messages.add_message(request, messages.INFO, 'You are successfully activated your account') return redirect('index') else: return HttpResponse('The link is invalid sorry!')
Linke get isteği yapıldığındaki gerekli parametreleri belirliyoruz. Url’den (uidb64, token ve request[zorunlu]) gelecek. Kodladığımız uid parametresini çözüyoruz. ve kullanıcıyı veritabanında o uid ile arıyoruz. Eğer yok ise basit bir cevap dönüyoruz. Fakat var ise oluşturduğumuz token üretecininin doğrulamasını kullanıyoruz. Token hala geçerli mi vs vs. Bizim oluşturduğumuz üreteçte bir geçerlilik tarihi yok. Kullanıcı is_active True işaretlenir ve mesaj eklenip anasayfaya gönderilir.
Kullanıcı giriş çıkış ve url yönlendirmeleri.
settings.py
LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'
Giriş veya çıkış yapıldığında redirect edilecek adresler.
email_login/templates/index.html
Anasayfa içeriği.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HomePage </title> </head> <body> <h1>Register user with email verification example.</h1> {% if messages %} {% for message in messages %} {{ message }} {% endfor %} {% endif %} <h3 style="color:#6fff6b">---Hello {{ request.user }}---</h3> <ul> {% if request.user.is_anonymous %} <li><a href="{% url 'login' %}">Login</a></li> <li><a href="{% url 'register' %}">Register</a></li> {% endif %} {% if request.user.is_authenticated %} <li><a href="{% url 'logout' %}">Logout</a></li> {% endif %} </ul> </body> </html>
from django.contrib import admin from django.contrib.auth.views import LogoutView, LoginView from django.urls import path from django.views.generic import TemplateView from accounts.views import RegisterUser, ActivateView urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='index.html'), name='index'), path('register/', RegisterUser.as_view(), name='register'), path('login/', LoginView.as_view(template_name='accounts/login.html'), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('activate/<uidb64>/<token>/', ActivateView.as_view(), name='activate'), ]
Url’de CBV ile view yazmadan template render edebilir veya giriş çıkış işlemlerini halledebilirsiniz.
Aktivasyon url’ine gerekli parametreleri geçip projeye son halini veriyoruz. LoginView ve LogoutView yerleşik auth view larını gereksiz uzayacağından burada açıklamıyorum. Komple uygulamayı ele aldığım bir seri yazacağım. Detaylı bir şekilde Django ile nası token oluşturur ve kullanırız anlatmaya çalıştım umarım yararlı olmuştur projenin kaynak kodları için buraya tıklayabilirsiniz. İyi günler 🙂
İlk Yorumu Siz Yapın