İçeriğe geç

Django Email Doğrulaması ile Kullanıcı Kaydı

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 🙂

 

 

 

 

 

 

 

 

Tarih:BlogDjango DersleriPython

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle