Login, Register ve Logout Sayfalarının Yapımı
Login, Register ve Logout Sayfalarının Yapımı¶
Bu yazımda sayfaların tasarımı için css yazmak yerine Uikit adında front-end framework’u kullanacağım ve bu yazımda kullandığım kodlar Eatingword adında Django bilgimi taze tutmak ve yeni şeyler öğrenmek amacı ile geliştirmekte olduğum projemden alıyorum, şuan için proje gizli durumda, hazır olduğu zaman MIT lisansı ile paylaşacağım.
Form¶
form_mixin.py
class UikitFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name in self.fields:
widget = self.fields[field_name].widget
while hasattr(widget, "widget"):
widget = widget.widget
if "nouikit" in widget.attrs:
continue
class_names = [widget.attrs.get("class", "")]
if isinstance(widget, forms.Select):
widget.attrs["title"] = (
"Select " + field_name.replace("_", " ").title()
)
class_names.extend(["uk-select"])
elif isinstance(widget, forms.Textarea):
class_names.extend(["uk-textarea"])
class_names.extend(["uk-input", "uk-form-width-large"])
widget.attrs["class"] = " ".join(class_names)
widget.attrs.setdefault("autocomplete", "off")
widget.attrs.setdefault(
"placeholder", field_name.replace("_", " ").title()
)
uk_icons = {
"username": "user",
"password": "lock",
"password1": "lock",
"password2": "lock",
"source": "arrow-right",
"target": "pencil",
}
widget.uk_icon = uk_icons.get(field_name, None)
widget.label_classes = ("uk-form-label",)
UikitFormMixin
mixinine şöyle bir baktığınızda aslında ne amaç ile yazıldığını anlayabilirsiniz, ben yinede kısaca bahsedeyim.
Bir form sınıfımıza bu sınıfı miras aldığınız zaman, önce form’u çalıştırıyor, sonra bu çalışıyor ve bir önceki formda oluşan alanları for döngüsü ile alıp her birinin widget
inin tipine bakıyor daha sonra gelen tiplere uygun uikit
sınıf atamalarını yapıyor ve alan isimlerine göre de icon isimlerini atıyor daha sonra bu atamaları __form.html şablonumuzu kullanarak gelen her formu uikit tasarımına sahip bir forma dönüştürmüş olacağız.
forms.py
from django.contrib.auth import forms as auth_forms
from django.contrib.auth import get_user_model
from .form_mixin import UikitFormMixin
UserModel = get_user_model()
class RegisterForm(UikitFormMixin, auth_forms.UserCreationForm):
class Meta(auth_forms.UserCreationForm.Meta):
model = UserModel
class AuthenticationForm(UikitFormMixin, auth_forms.AuthenticationForm):
pass
Bakın bu form classlarımda tanımlarken UikitFormMixin
class’ımı miras aldım, o classs oto bir şekilde form alanlarımı uikit için hazır hale getiriyor.
Burada neden direkt from django.contrib.auth import User
böyle yapıp User
modelini almak yerine from django.contrib.auth import get_user_model
get_user_model
‘ını alıp, onu kullandım ?
sebebi şu arkadaşlar User Modelini Genişletmek adında bir yazı yazmıştım size bu yazımda bahsettiğim yöntemlerden biri olan AbstractBaseUser
sınıfını miras alıp kullanıcı modelinizi bu şekilde oluşturduysanız o modeli dönderir , oluşturmadıysanız zaten varsayılan olan User
modelini dönderiyor. Bu sayede user modeliniz nasıl olursa olsun uyumlu bir form çıkartmış oluyorsunuz.
Daha sonra zaten Django’da UserCreationForm
hazır bir halde var, bu formu Django admin sayfasında siz bir kullanıcı oluşturduğunuzda kullanıyor, bizde kendi kayıt ol sayfamızı yazarken kullanacağız. Bu formun kodlarını inceleyin mutlaka ben sizlere link bırakayım UserCreationForm
Sonra aynı şekilde kullanıcı giriş formu yine Django’da var biz aynı formu kullanacağız ama uikit tasarımına uygun olması gerekiyor bu yüzden sadece UikitFormMixin
‘ i miras aldık ve bu kadar, formlarımız hazır. Yine aynı şekilde AuthenticationForm’un kodlarını incelemeniz için link bırakıyorum AuthenticationForm
View¶
view_mixin.py
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
class MessageMixin(SuccessMessageMixin):
success_message = "Successfully Created/Updated"
def form_invalid(self, form):
messages.error(self.request, "Form Invalid")
return super().form_invalid(form)
Bu sınıfımız ile de form işlemleri sonrası form başarılı şekilde geçerli olursa başarılı mesajını, geçersiz olursa form geçersiz mesajını, vermek için kullanacağız.
Bu view’i kullanmak için ilgili sınıf tabanlı view’imize miras almamız yeterli olacaktır, eğer isterseniz success_message
niteliği ile başarılı mesajını kendiniz belirleyebilirsiniz.
Djangoda başarılı mesajı için zaten SuccessMessageMixin
adlı bir sınıf var ben yukarıda bunu miras alarak hem onu hemde form geçersiz oldugunda mesaj iletsin diye böyle bir sınıf yazdım.
views.py
from django.contrib import messages
from django.contrib.auth import authenticate, login
from django.contrib.auth import views as auth_views
from django.urls import reverse_lazy
from django.views import generic
from .forms import AuthenticationForm, RegisterForm
from .view_mixin import MessageMixin
class RegisterView(MessageMixin, generic.CreateView):
template_name = "registration/register.html" # get isteği sonrası render edilecek olan şablonumuz
success_url = reverse_lazy("wordapp:index") # form geçerli olur ve herhangi bir sıkıntı çıkmaz ise bu adrese yönlenecek
form_class = RegisterForm # form sınıfımız
success_message = "You have successfully registered %(username)s" # form geçerli olursa tanımlı olan success_url'e yönlenmeden önce messages ile gönderilecek olan mesaj
def form_valid(self, form):
# tanımlı form_class'ımız post isteği sonrası geçerli olduğu zaman çalışacak olan fonksiyon
response = super().form_valid(form) # formu kayıt edip yukarıda tanımlı olan success_url'e yönlenecek olan responsu alıyoruz.
if user := authenticate( # self.object si super().form_valid(form) sırasında kayıt olan formun dönderdiği nesnedir yanı kayıt olan kullanıcımızdır.
self.request,
username=self.object.username,
password=self.object.password,
): # burada başarılı bir kullanıcı girişi var ise bize o kullanıcıyı döndürecek, python 3.8 ile gelen := walrus operatürü ile bunu alıyoruz.
login(self.request, user) # burada dönen kullanıcının giriş yapmasını sağlıyoruz.
else:
messages.error(self.request, "Could not login") # user false dönerse, mesaj gönderiyoruz
return response # yukarıda bize dönen cevabı döndüyoruz, bu sayede kullanıcı success_url tanımlı adrese yönleniyor.
class LoginView(MessageMixin, auth_views.LoginView):
form_class = AuthenticationForm
success_message = "You have successfully logged in %(username)s"
Geldik viewlerimize
Kayıt olmak ve giriş yapmamızı sağlayan viewlerimiz yukarıda da gördüğünüz gibi oldukça kısa, bir şeyler gereksizce uzun ise bir çok şeyi yanlış yapıyorsunuzdur.
Register view’imizın amacı yeni bir kullanıcı kayıt etmek olduğu için bunun generic.CreateView
kullanarak hızlı bir şekilde yapabiliriz.
Burada generic.FormView
, generic.View
veya fonksiyonel bazlı yazarakta yapabiliriz ama bu yöntemlerden biri ile yazmayı tercih edersek gereksizce kod uzun, anlaşılması zor olacaktır bu yüzden en doğru şekilde yapmaya çalışmak her zaman iyidir.
RegisterView’ı yorum satırları ile anlattım, kodlardan kontrol edebilirsiniz.
LoginView de ise çok bir şey yok, Djangoda zaten LoginView
var bende onu kullanarak kendi view’imi yazdım form_class’ımı verdim, birde mesajı verdim bu kadar, kodları incelemek isterseniz LoginView
Urls¶
urls.py
from django.contrib.auth.views import LogoutView
from django.urls import path
from apps.account.views import LoginView, RegisterView
urlpatterns = [
path("register/", RegisterView.as_view(), name="register"),
path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"),
]
Daha sonra urllerimiz tanımladık, çıkış işlemi için tekrardan bir view yazmadık cunku Django’da var zaten, LogoutView
Templates¶
Şimdi sıra geldi son adımlarımıza, templateler.
Form işlemleri sonrası Django messages framework’u ile mesaj yolluyorduk yukarıda, ben o mesajları daha güzel göstermek adına toastr adında bir JS lib’i kullanıyorum, Django ile dönen mesajları base.html template’imin head etiketi içine basıp daha sonra js ile çekip mesaj var ise gösteriyorum, ilgili kodları aşağıda göreceksiniz.
custom.js
document.addEventListener(
"DOMContentLoaded",
function () {
/* toasatr */
toastr.options = {
closeButton: true,
debug: false,
newestOnTop: true,
progressBar: true,
positionClass: "toast-bottom-right",
preventDuplicates: false,
onclick: null,
showDuration: "300",
hideDuration: "1000",
timeOut: "5000",
extendedTimeOut: "1000",
showEasing: "swing",
hideEasing: "linear",
showMethod: "fadeIn",
hideMethod: "fadeOut",
};
let messages = document.head.querySelectorAll("meta[name=message]");
for (let message of messages) {
let tagName = message.dataset["tag"];
let content = message.content;
eval(`toastr.${tagName}`)(content);
}
},
false
);
Yukarıdaki kod ile DOM yüklendiği zaman toastr ayarlarını yapıp meta etiketine bakıyor eğer Django mesaj basmış ise dönen uyarı tipine ( error, warning, vs ) göre bildirim üretiyor.
templates/base.html
{% load static %}
<!DOCTYPE html>
<html class="uk-background-muted">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"
/>
<title>Learn Words</title>
<meta name="description" content="Learn Word" />
<meta name="keywords" content="english, turkish, word, learn" />
<link rel="icon" href="{% static 'media/eating-word.svg' %}" />
<!-- messages meta -->
{% for message in messages %}
<meta name="message" data-tag="{{message.tags}}" content="{{ message }}" />
{% endfor %}
<!-- jquery -->
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"
></script>
<!-- UIkit CSS -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/uikit@3.5.6/dist/css/uikit.min.css"
/>
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.5.6/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.5.6/dist/js/uikit-icons.min.js"></script>
<!-- toastr -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<link
rel="stylesheet"
type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"
/>
<!-- custome js -->
<script src="{% static 'custom.js' %}"></script>
</head>
<body class="uk-animation-scale-up uk-background-default">
<!-- start:: header -->
<nav class="uk-navbar-container uk-margin" uk-navbar>
<div class="uk-navbar-left">
<a class="uk-navbar-item uk-logo" href="#">
<img width="60" src="{% static 'media/eating-word.svg' %}" />
</a>
</div>
<div class="uk-navbar-right">
<ul class="uk-navbar-nav">
{% if user.is_authenticated %}
<li class="uk-active"><a href="{% url 'logout' %}">Logout</a></li>
{% endif %}
</ul>
</div>
</nav>
<!-- end:: header -->
<!-- start:: body -->
<div class="uk-card">
<div class="uk-card-header">
<h3 class="uk-card-title">{% block title %}{% endblock title %}</h3>
</div>
<div class="uk-card-body">{% block content %}{% endblock content %}</div>
<div class="uk-card-footer">{% block footer %}{% endblock footer %}</div>
</div>
<!-- end:: body -->
<!-- start:: footer -->
<!-- end:: footer -->
</body>
</html>
Yukarıda base.html dosyasını görüyorsunuz, anlatacak çok bir şey yok aslında, title, content, footer adında 3 blogum var, base.html dosyamı genişleteceğim zaman onları kullanarak bir tasarım çıkartıyorum, JS, CSS linkleri vs var o kadar.
templates/include/__form.html
<!-- {% comment %}
{% include 'analyst/include/__form.html' with form_url_name='' form_id="formId" method="post" form=form' %}
{% endcomment %}
-->
{% with button_id=form_id|add:'Button'|default:'formSubmitButton' %}
<form {% if url_name %}action="{% url form_url_name %}" {% endif %} method="{{ method|default:'POST' }}" id="{{ form_id|default:'formSubmitButton' }}" class="uk-form-horizontal">
{{ form.media }}
{% if method|lower != "get" %} {% csrf_token %} {% endif %}
{{ form.non_field_errors }} {% for hidden_field in form.hidden_fields %} {{ hidden_field.errors }}
{{ hidden_field }} {% endfor %} {% for field in form.visible_fields %}
<div class="field" id="group_{{ field.html_name }}">
{% if field.errors %}
<ol>
{% for error in field.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
<label class="{% for class in field.field.widget.label_classes %}{{ class }}{% endfor %}" for="{{ field.auto_id }}">
{{ field.label }}
</label>
<div class="uk-inline">
{% if field.field.widget.uk_icon %}
<span class="uk-form-icon" uk-icon="{{ field.field.widget.uk_icon }}"></span>
{% endif %} {{ field }}
</div>
<small class="uk-text-meta uk-text-background" id="{{ field.id_for_label }}Help">
{{ field.help_text|safe }}
</small>
</div>
{% endfor %}
<button id="{{button_id}}" type="submit" class="uk-button uk-button-primary">
{{ buttonText|default:"Submit" }}
</button>
</form>
{% endwith %}
Geldik sevdiğim bir kısma __form.html şablona bu şablon bütün formlarda kullanacağım ortak bir şablondur, uikit’e göre biraz düzenledim.
Kullanımınıda en yukarıya yazmışım
{% include 'analyst/include/__form.html' with form_url_name='' form_id="formId" method="post" form=form' %}
Kullanımda da gördüğünüz gibi formun method’unu ( post, get ) varsayılan post, değiştirebiliyorsunuz, varsayılan form olan form nesnenizi değiştirebiliyorsunuz, form id’nizide değiştirebiliyorsunuz ve son olarak varsayılan olarak bulunduğunuz sayfa olan form action’ı form_url_name
ile belirleyebiliyorsunuz.
templates/registration/register.html
{% extends "base.html" %} {% block title %} Register {% endblock title %}
{% block content %}
{% include 'include/__form.html' with buttonText="Register" %}
{% endblock content %}
{% block footer %}
<div class="ui bottom attached warning message">
<i class="icon help"></i>
Already signed up? <a href="{% url 'login' %}">Login here</a> instead.
</div>
{% endblock footer %}
Kayıt ol şablonumuz burada base’i genişletip content kısmına form’u verip footer kısmında eğer hesabı varsa login yapmasını söylemişim.
templates/registration/login.html
{% extends "base.html" %} {% block title %} Login {% endblock title %}
{% block content %}
{% include 'include/__form.html' with buttonText="Login" %}
{% endblock content %}
{% block footer %}
<div class="ui bottom attached warning message">
<i class="icon help"></i>
Do not have an account?
<a href="{% url 'register' %}">Register Now</a>
</div>
{% endblock footer %}
Login sayfasıda aynı şekilde.
Bu konuda anlatacaklarım bu kadar, anlaşılmayan bir bölüm var ise bana telegramdan yazabilirsiniz bende bu yazıyı güncelleyerek o başlığı daha detaycı anlatmaya çalışırım, okuduğunuz için teşekkürler.
Created: June 1, 2023