07 - Kullanıcı doğrulama

May 18, 2018 4 minute read

Facebook, Twitter, Linkedin, Yemeksepeti, Markafoni, Hepsiburada, Sahibinden… Hepsi bizim kim olduğumuzu bilmek isterler. Çünkü bize göre içerik üretirler, hizmet verirler, istediklerimizi sunarlar. Biz de ekranlarımızı kullanıcıya özel yapmak, herkes kendi ekranlarını görsün ve yönetsin istiyorsak, benzer bir kimlik doğrulama sistemi kullanmalıyız.

Özet

  • Django’nun en önemli bileşenlerinden biri olan kimlik doğrulama sistemini kullanacağız.
  • Kullanıcı giriş yaptı mı yapmadı mı bilgisini tüm Template katmanında layout dosyamızda tutacağız.
  • Bu yazıyla ilgili kodlara buradan erişebilirsiniz.

Kullanıcı modelimiz

Peki bu kadar çok ihtiyaç duyulan bir bileşen için her projede tekrar tekrar yazmak ne kadar doğru olurdu? Django, bazı çok ihtiyaç duyulan şeyleri projenin içinde tutup geliştiriyor, bazen de django-contrib-comments gibi projeden ayırıyor. Django auth1, en temel gereksinimleri içeren ve geliştirilmeye uygun bir kullanıcı modelini içinde barındırıyor; giriş - çıkış sayfaları, hesap doğrulama, parola sıfırlama gibi temel View katmanlarını da içeriyor ve sadece Template katmanını oluşturarak hızlıca bu sayfaları hazırlayabiliyorsunuz.

Önce ne yapmak istediğimizi netleştirelim:

  1. Kullanıcı uptime uygulamasına gittiğinde -giriş yapmamışsa- giriş sayfasına yönlendireceğiz.
  2. Her kullanıcı sadece kendi ekranlarını görecek ve yönetecek.
  3. Çıkış yaptığında anasayfaya yönlendirilecek.

Uygulamayı kurmaya her zaman olduğu gibi settings.py dosyasından başlıyoruz:

# settings.py
...
from django.urls import reverse_lazy  # öncekilerden farklı olarak reverse yerine reverse_lazy
...
INSTALLED_APPS = [
    ...
    'django.contrib.auth',  # uygulamamız zaten kuruluydu.
    ...
]
...
# Auth settings
LOGIN_REDIRECT_URL = reverse_lazy('uptime:dashboard')  # kullanıcıyı giriş yapması için buraya yönlendireceğiz.
LOGOUT_REDIRECT_URL = reverse_lazy('home')  # kullanıcı çıkış yaptığında ise buraya yönlendireceğiz.

İlk migrationları çalıştırdığımızda hatırlarsanız, epey bir çıktı görmüştük. Django auth projeyle birlikte kurulu geldiği için aslında veritabanımzda bir User modelimiz var. Zaten olmasa, Admin panelinde giriş yapabilmek için gerekli super user‘i oluşturamazdık.

Django auth ile birlikte gelen View katmanımızı kullanabilmek için URL’leri bağlamayı unutmayalım:

# hello_django/urls.py
...
urlpatterns = [
    ...
    path('accounts/', include(('django.contrib.auth.urls', 'auth'), namespace='auth')),
    ...
]
...

Model katmanındaki değişiklik

Şimdi uptime uygulamasının Monitor modeline user adında bir field ekleyelim, gerekli migration dosyamızı oluşturup çalıştıralım:

# hello_uptime/models.py
...
from django.conf import settings
...
class Monitor(models.Model):
    user = models.ForeignKey(
        verbose_name=_("User"), to=settings.AUTH_USER_MODEL, related_name='monitors', on_delete=models.CASCADE,
        null=True)
    url = models.URLField(verbose_name=_("URL"))

    class Meta:
        ...
        unique_together = ('user', 'url')
    ...

Yaptığımız değişiklikleri anlayalım:

  1. Öncelikle url field’inden unique attribute’unu kaldırdık. Eskiden uygulamamızı ziyaret eden herkes tek bir ekran ekleyebiliyorken, şimdi her kullanıcı kendi ekran panelinden aynı URL’i takip edebilecek. Ama yine de her kullanıcının kendi panelinde aynı domainin takip edilmesini engellemek için unique_together kullandık.
  2. user field’i için ForeignKey kullandık, User modelini tanımlarken AUTH_USER_MODEL tanımlamamızı kullandık, eğer Django’nun içindeki User modeli bize uymasaydı ve kendimiz bir User model hazırlamış olsaydık, settings.py dosyamızdan AUTH_USER_MODEL tanımlamamız gerekecekti. Şimdilik varsayılan auth.User.
  3. related_name olarak monitors yazdık, kodun içinde elimizde user olursa, o kullanıcının ekranlarına user.monitors.all() diyerek erişebileceğiz.
  4. on_delete CASCADE, kullanıcımız veritabanından silinirse, ekranları da beraberinde silinecek.
  5. null neden True dedik? Çünkü bu zamana kadar uygulamayı hep user field olmadan kullandık, ya migration için öntanımlı bir kullanıcı tanımlamamız gerekiyordu, ya da Model katmanında user field’inin boş olmasına izin verecektik. En kolayı şuan için buna izin vermek.
# terminal
$ python manage.py makemigrations hello_uptime
Migrations for 'hello_uptime':
  hello_uptime/migrations/0002_auto_20180519_1203.py
    - Add field user to monitor
    - Alter field url on monitor
    - Alter unique_together for monitor (1 constraint(s))
# terminal
$ python manage.py migrate hello_uptime
Operations to perform:
  Apply all migrations: hello_uptime
Running migrations:
  Applying hello_uptime.0002_auto_20180519_1203... OK

Template katmanındaki değişiklik

Tüm sayfalarda kullanıcının giriş yapıp yapmadığı bilgisini gösterebilmek için base.html layout dosyamızda değişiklik yapıyoruz:

<!-- templates/base.html -->
{% load i18n %}  <!-- trans ve bloctrans template tag'ı buradan geliyor. Çeviri yapabilmemiz için gerekli. -->

{% block header %} <!-- Neden header block'una aldık? Çünkü bazı sayfalarda bu block'u boş göstereceğiz. -->
    <header>
        <!-- context_processors konusunu hatırlayın, user tüm sayfalarda tanımlı. -->
        {% if user.is_authenticated %}
            <!-- işte size tek satırda örnek blocktrans, with, get_full_name ve default kullanımı. -->
            {% blocktrans with name=user.get_full_name|default:user.username %}Hello,  {{ name }}!{% endblocktrans %}
            <a href="{% url 'auth:logout' %}">{% trans "Log out" %}</a>
        {% else %}
            <a href="{% url 'auth:login' %}">{% trans "Log in" %}</a>
        {% endif %}
    </header>
{% endblock %}

Django auth içindeki Login View katmanı, registration/login.html adında bir Template dosyası arıyor. Kendimiz basit bir form oluşturalım:

<!-- templates/registration/login.html -->
{% extends 'base.html' %}

{% load i18n %}

{% block title %}{{ block.super }} - Log in{% endblock %}

{% block header %}{% endblock %} <!-- Bunun içinde zaten bu sayfaya yönlendiren bir link var sadece, gereksiz. -->

{% block content %}
    <form action="" method="post">
        {% csrf_token %}

        <fieldset>
            <legend>{% trans "Log in" %}</legend>

            <table>
                <tbody>
                {{ form.as_table }}
                <tr>
                    <th></th>
                    <td>
                        <button type="submit">{% trans "Log in" %}</button>
                    </td>
                </tr>
                </tbody>
            </table>
        </fieldset>
    </form>
{% endblock %}

Login page

View katmanında değişiklik

Model’imiz hazır, Template’imiz hazır. Şimdi View katmanımızı kullanıcının giriş yapması için kısıtlamalıyız. Giriş yapmayan kullanıcı uptime uygulamasını göremeyecek, onun yerine login ekranımıza yönlendirilecek, giriş yaptıktan sonra tekrar uptime uygulamasına yönlendirilecek. Bir diğer kural, her kullanıcı kendi ekranlarını görecek, düzenleyecek:

# hello_uptime/views.py
...
from django.contrib.auth.mixins import LoginRequiredMixin
...
class UptimeDashboardView(LoginRequiredMixin, ModelFormSetView):  # Yetki kontrolü bundan ibaret :-)
    ...
    def get_queryset(self):
        return Monitor.objects.filter(user=self.request.user).order_by('id')
    ...
    def formset_valid(self, formset):
        monitors = formset.save()
        for monitor in monitors:
            monitor.user = self.request.user
            monitor.save()
        ...
    ...

Uptime with auth

Bundan sonraki yazımızda Django ile komut satırı uygulaması yazıp URL’lerin durumunu kontrol edeceğiz. Kalın sağlıcakla.