Web uygulaması geliştirmenin önemli bir parçası da kullanıcıdan alınan verileri işlemden geçirmek ve veritabanında depolamak esasına dayanır. Kullanıcıdan veri almak için de veri girişi formlarından yararlanılır. Bu formlara kullanıcının girdiği veriler formun action parametresi tarafından bir web adresine gönderilirler. Web adreslerinin Flask yapısında bir Python fonksiyonunu işaret ettiğini önceki konumuzdan hatırlıyorsunuzdur. İşte bu fonksiyon içinde kullanıcıdan alınan veriler işlenerek veritabanına kaydedilir.

Bunun en basit şekli, HTML form etiketlerini kullanarak br form oluşturmaktır. Eğer Flask ile web sitesi tasarlıyorsanız ve sadece iletişim formu, üye girişi ve kayıt formu gibi bir kaç tane formunuz olacak ise yeterli olabilir.

Örneğin önceki konuda oluşturduğumuz veritabanı tablosu için bir HTML formu oluşturalım. Örnek proje klasörümüz içinde templates altında yeni bir html dosyası oluşturup adına yeniuye.html diyelim;

{% extends "temel.html" %}

{% block icerik %}

    <form class="form-detail" id="uyelikformu" action="/uyekayit" method="POST">
        <div class="form-group">
            <div class="form-row form-row-1">
                <label for="adSoyad">Adınız - Soyadınız</label>
                <input type="text" name="adSoyad" class="form-control" required>
            </div>
            <div class="form-row form-row-1">
                <label for="kullaniciAdi">Kullanıcı Adınız</label>
                <input type="text" name="kullaniciAdi" class="form-control" required>            </div>
            <div class="form-row form-row-1">
                <label for="sifre">Şifreniz</label>
                <input type="password" name="sifre" class="form-control" required>
            </div>
            <div class="form-row form-row-1">
                <label for="sifreonayi">Şifrenizi tekrar giriniz</label>
                <input type="password" name="sifreonayi" class="form-control" required>
            </div>
            <div class="form-row form-row-1">
                <label for="email">Email Adresiniz</label>
                <input type="email" name="email" pattern="[^@]+@[^@]+.[a-zA-Z]{2,6}" class="form-control" required>
            </div>
        </div><br/>
        <div class="form-row-last">
            <input type="submit" name="kaydet" id="kaydet" class="btn btn-primary" value="KAYDET">
            <input type="reset" name="sil" id="sil" class="btn btn-primary" value="TEMİZLE">
        </div>
    </form>

{% endblock %}

Oluşturduğumuz bu yeni HTML şablonunu web tarayıcıdan ziyaret edebilmek için Python dosyamız içine yolunu oluşturmalıyız;

@ilkproje.route("/yeniuye")
def yeniUye():
    return render_template("yeniuye.html", sehir = konum)

Web tarayıcımızda http://127.0.01:5000/yeniuye.html sayfasını ziyaret ettiğimizde üyelik formu karşımıza gelecektir;

Ancak Kaydet butonuna tıkladığımızda  gidilecek fonksiyonu henüz tanımlamadığımız için herhangi bir işlem gerçekleştirmeyecektir.

Python dosyamız içine öncelikle formdan gelen istekleri alabilmek için flask’ın ilgili request metodunu da eklememiz gerekli.

from flask import Flask, render_template, render_template, request

Şimdi artık dosyamızda gelen verileri işleyecek bir fonksiyon oluşturalım.

@ilkproje.route('/uyekayit', methods=['POST'])
def uyekayit():
    if request.method == 'POST':
        adSoyad = request.form.get("adSoyad")
        kullaniciAdi = request.form.get("kullaniciAdi")
        email = request.form.get("email")
        sifre = request.form.get("sifre")

        try:
            kayit = veritabaniBaglantisi.connection.cursor()
            sorgu = "INSERT INTO kullanicilar VALUES(%s,%s,%s,%s,%s)"
            veriler=(None,adSoyad,kullaniciAdi,sifre,email)

            kayit.execute(sorgu,veriler)
            veritabaniBaglantisi.connection.commit()
            kayit.close()
        except Exception as istisna:
            return istisna

        return render_template("formsonuc.html", adSoyad= adSoyad)
    else:
        return render_template("formsonuc.html", hata="Kayıt yapılamadı. Lütfen tekrar deneyiniz!")

Öncelikle web sayfamızda bulunan formun action parametresinde yer alan /uyekayit yolu için bir fonksiyon oluşturduk. Bu fonksiyonun içinde kullanıcıdan gelen isteğin türü POST, yani veri ekleme talebi şeklinde ise çalıştırılacak kodları bir if koşullu ifadesi içinde tanımladık.

Formdan gelen verileri request metodunun form.get() metodu ile asSoyad = request.form.get(“adSoyad”) şeklinde alıyoruz.

Veritabanı bağlantısını daha önce tanımladığımız nesne üzerinde connection.cursor() metodu ile tanımlayıp, sorgu değişkenimize veritabanına göndereceğimiz SQL sorgusunu depoluyoruz.

execute() metoduna parametre olarak sorgu değişkenimizi ve bir demet şeklinde verilerimizi tablodaki sütun sırasına uygun şekilde gönderiyoruz. Burada dikkat ettiyseniz kullanıcı numarası otomatik oluşturulacağı için ilk parametre None olarak belirlendi.

Daha sonra yaptığımız değişiklikleri uygulamak ve verileri veritabanına kaydetmek  için connection.commit() metodunu kullandık ve veritabanı bağlantısını sonlandırmak için close() metodundan yararlandık. Veritabanı ile işlem gerçekleştirdikten sonra kurulan bağlantıyı kapatmayı unutmamanızı önemle hatırlatıyorum. Tıpkı dosyaları kapattığımız gibi veritabanı bağlantılarını da işimiz bittiğinde sonlandırmayı unutmuyoruz.

Veritabanı üzerinde gerekli işlerimiz sona erdiğinde son olarak kullanıcıya formsonuc.html isimli bir web sayfası döndürüyor ve adSoyad değişkenini bu sayfaya göndererek verilen isimdeki kullanıcının kaydının başarıyla gerçekleştiği ile ilgili bir mesaj görüntülüyoruz. else ifadesi içinde de aynı sayfaya bi hata mesajı göndererek veritabanı bağlantısı kurulamaması ya da işlemler esnasında bir sorun yaşanması durumunda kullanıcıyı bilgilendiriyoruz.

Şimdi templates klasörü altında bu sayfayı da oluşturalım.

formsonuc.html kodu;

{% extends "temel.html" %}

{% block icerik %}
    {{ adSoyad }} isimli kullanıcı kaydı başarılı bir şekilde oluşturulmuştur.
    {{ hata }}
{% endblock %}

Bu şablon işlem başarılı olursa ilk satırı, başarısız olursa hata mesajını görüntülüyor.

Şimdi web tarayıcımızda http://127.0.0.1:5000/yeniuye.html adresini ziyaret edip formu doldurarak “Kaydet” butonuna tıkladığımızda verit tabanımızda ilk kaydımızın başarılı olarak oluşturulduğu ile ilgili mesajımızı görebiliyoruz. Veritabanına kaydolan verileri dilerseniz phpMyAdmin sayfasına giriş yaparak sol taraftan veritabanı adına tıklayıp tabloyu seçerek de görebilirsiniz;

Böylece bir HTML formunu Flask projesine dahil edip bu formdan kullanıcının girdiği verileri MySQL veritabanına yazdırmayı öğrenmiş olduk.

Eğer bir web sitesi üzerinde çalışıyorsanız ve 2-3 adet gibi az sayıda form içeren bir projeniz var ise statik HTML etiketleriyle oluşturduğunuz formlar da işinizi görebilir.

Ancak bir web uygulaması geliştiriyorsanız ve projeniz kullanıcıdan pek çok farklı sayfada veri girişi yapmasını gerektiren bir uygulama ise, daha profesyonel bir yaklaşıma ihtiyacınız var demektir. Bunun için geliştirilmiş WTForms kütüphanesini kullanabilirsiniz.

WTForms ile Form Oluşturma

Peki WTForms nedir? En basit haliyle ifade edersek, Python ile web programlama için geliştirilmiş bir form oluşturma ve doğrulama kütüphanesidir. Hangi web uygulama iskeletini(framework) ya da hangi HTML şablon dilini kullandığınızdan bağımsız olarak WTForms ile çalışabilirsiniz. Tabii web uygulama iskeleti(framework) için geliştirilmiş aracı modüller sayesinde. İşte Flask-WTF’de bizim Flask ile kullanıcı veri girişi formları oluşturmak için WTForms kütüphanesini kullanmamızı sağlayan Flask modülüdür.

WTForms, özellikle bir çok veri girişi formu olan web uygulamalarında çok kullanışlıdır. Çünkü her veri girişi formu için ayrı ayrı HTML formu oluşturmaktan sizi kurtarır. Bildiğiniz nesne tabanlı programlama mantığıyla yazılımsal olarak form oluşturmayı ve diğer formların miras yoluyla oluşturulmasını sağlayarak daha az kodla daha çok iş yapmanızı sağlar.

Şimdi örnek olması için oluşturduğumuz veritabanını kullanacak ve web sitesini ziyaret eden kullanıcıların bilgilerini alıp veritabanına kaydedecek bir form oluşturalım.

Öncelikle Flask ile WTForms ilişkisini sağlayan modülün kurulumunu gerçekleştirmeliyiz;

$ pip install Flask-WTF

Bu komutun terminal çıktısı bize Flask-WTF ile birlikte WTForms’un son sürümünün de başarılı bir şekilde kurulduğunu bildiriyor.

Successfully installed Flask-WTF-0.14.3 WTForms-2.3.3

Daha sonra bu modül ile çalışabilmek için Python kodumuza ekliyoruz;

from wtforms import Form, StringField, TextAreaField, PasswordField, validators

Form sınıfı ile birlikte örneğimizde kullanacağımız veritabanında oluşturduğumuz sütunlara uygun şekilde seçtiğimiz form alanını oluşturacak olan sınıfları da ekliyoruz. validators ise bu form alanlarına yapılan veri girişlerinin doğruluğunu kontrol edecek olan sınıfları içeriyor.

Şimdi kullanıcı bilgilerini alacağımız bir form oluşturalım. İlk yapmamız gereken,  wtforms modülünün Form sınıfından miras alan bir sınıf oluşturmaktır.

class KullaniciKayıtFormu(Form)
Bu sınıf içinde de kullanacağımız özellikler(değişkenler) olarak her veri girişi türüne göre ilgili sınıfın bir nesnesini oluşturacağız.
Kullanıcının adını ve soyadını alacağımız form alanı metin türünde veri girişi barındıracağı için StringField() sınıfından bir nesne oluşturuyoruz. Alacağı parametreleri ise Visual Studio Code editöründe sınıf adının üzerine geldiğimizde görebiliyoruz;
Açıklamaların en altında bu sınıfın <input type=”text”> şeklinde bir HTML kodu ürettiğini görüyorsunuz.
En basit şekliyle veri girişi alanının ne ile ilgili olduğunu belirtecek isim etiketini eklemek yeterli olacaktır;
adSoyad = StringField(“Adınız – Soyadınız: “)
Ancak kullanıcının gireceği veri ile ilgili belirli şartlar oluşturmak ya da hatalı veri girişi yaptığında uyarmak isterseniz girilen veriye doğrulama işlemi uygulayabilirsiniz. Yapabileceğiniz doğrulama işlemlerini klavyenizden Ctrl tuluna basılı tutarken validators anahtar kelimesinin üzerine gelip tıkladığınızda açılan wtforms modülünün validators.py isimli dosyası içinde görebilirsiniz;
Biz veri girişi alanının boyutunu belirlemek ve kullanıcı tarafından boş bırakılmaması, zorunlu olarak giriş yapılmasını sağlamak için length() metodunu ve InputRequired() metodunu kullanmak istiyoruz;
adSoyad = StringField(“Adınız – Soyadınız: “, validators=[validators.length(min=3, max=40, message=”Adınızı ve Soyadınızı minimum 2, maksimum 40 karakter olacak şekilde giriniz”), validators.InputRequired(“Lütfen bu alanı boş bırakmayınız!”)])
validators.length() metodu ile veri girişinin en az 3 karakter en fazla 40 karakterden oluşabileceğini belirledik ve bunun dışında kullanıcının yapacağı bir giriş için uyarı mesajı görüntülenmesini sağladık. Ayrıca bu veri girişi alanının kullanıcı tarafından boş bırakılmamasını validators.InputRequired() metodu ile garanti altına aldık.
Örneğimiz için oluşturmamız gereken diğer alanları veritabanı tablosunu tasarlarken şöyle belirlemiştik;
Bunlardan kullaniciNo alanı için bir veri girişi oluşturmamıza zaten gerek yok, çünkü bu alanı her kayıt oluşturulduğunda otomatik olarak artan bir sayı olarak belirlemiştik. Diğer alanları da benzer şekilde ihtiyacımıza uygun olarak oluşturabiliriz.
class KullaniciKayitFormu(Form):
    adSoyad = StringField("Adınız - Soyadınız: ", validators=[validators.length(min=3, max=40, message="Adınızı ve Soyadınızı minimum 3, maksimum 40 karakter olacak şekilde giriniz"), validators.InputRequired("Lütfen bu alanı boş bırakmayınız!")])

    kullaniciAdi = StringField("Kullanıcı Adınız: ", validators=[validators.length(min=3, max=15, message="Kullanıcı adınızı minimum 3, maksimum 15 karakter olacak ve arasında boşluk olmayan, Türkçe karakterler bulunmayan şekilde giriniz"), validators.InputRequired("Lütfen bu alanı boş bırakmayınız!")])

    sifre = PasswordField("Şifreniz: ", validators=[validators=[validators.length(min=8, message="Şifrenizi minimum 8 karakter olacak şekilde giriniz"), validators.EqualTo(fieldname = "sifreOnayi", message="Şifreleriniz eşleşmiyor!"), validators.InputRequired("Lütfen şifrenizi belirleyiniz."))

    sifreOnayi = PasswordField("Şifrenizi tekrar giriniz: ")

    email = StringField("Email Adresiniz: ", validators=[validators.Email(message="Lütfen geçerli bir email adresi giriniz.") , validators.InputRequired("Lütfen bu alanı boş bırakmayınız!")])

Form sınıfımızı oluşturduk. Şimdi kullanıcının veri girişi formunu içine yerleştireceğimiz HTML şablonunu oluşturalım. İlk adım olarak Python dosyamızda web adresini oluşturuyoruz. Yalnız daha önce oluşturduğumuz web adreslerinden farklı olarak bu adresin kullanabileceği HTTP istek türlerini belirtiyoruz. Böylece web adresi hem GET istekleri hem de POST istekleri alabilir duruma geliyor;

@ilkproje.route("/uyelik", methods=["GET","POST"])
def uyelik():
    return render_template("uyelik.html", sehir = konum)

Şimdi de templates klasörü altında formumuzu kullanıcıya görüntüleyecek olan HTML şablonunu oluşturalım. Bu yaklaşımda direkt HTML etiketleriyle web sayfası oluşturmayacağımız için WTForms dokümanında yer alan “Şablonlarda Formlar” konusunda yer alan bilgilerden faydalanıyoruz. Formları şablona eklemek oldukça basit bir işlem. Ancak daha estetik ve işlevsel form görünümü istiyorsanız WTForms makro adı verilen ve formda yer alan alanları özelleştiren yardımcı kodlar yazmanıza imkan veren bir özelliğe sahip. Makrolar, form alanlarına etiket(label) ve hata kodları eklemenizi sağlıyorlar. Siz bir form alanının nasıl görüntüleneceğini makro ile belirliyorsunuz ve makronuzu HTML şablonunuza tıpkı bir modül gibi ekliyorsunuz. Böylece formda yer alan her bir veri girişi alanı makroda tanımladığınız fonksiyonu kullanarak belirlediğiniz görünüme sahip oluyor.

Nasıl kullanıldığını görmek için örnek makromuzu yazıp templates klasörü içine kaydedelim;

form_makrosu.html kodu;

{% macro formetiketi_tasarimi(etiket) %}
  <dt>{{ etiket.label }}
  <dd>{{ etiket(**kwargs)|safe }}
  {% if etiket.errors %}
    <ul class=hata>
    {% for hata in etiket.errors %}
      <li>{{ hata }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

Bu makro, WTForms’un form alanları fonksiyonuna iletilebilecek bir kaç anahtar kelime içermektedir. Bu anahtar kelimeler HTML parametreleri oluşturacaktır. Örneğin kullanıcı adını aldığımız veri giriş alanı için render_field(form.kullaniciAdi, class=”kullaniciadi”) şeklinde çağırılan metod HTML input etiketinin içine kullaniciadi CSS sınıfının eklenmesini sağlar. Bunu aşağıda form alanlarına form-control Bootstrap sınıfını eklerken kullandık.

Bu yardımcı makroyu kullanacak olan ve formumuzu web sayfamızın içinde görüntüleyecek olan HTML şablonu ise şu şekilde olacaktır;

{% extends "temel.html" %}

{% block icerik %}

{% from "form_makrosu.html" import formetiketi_tasarimi %}
<form method=post>
  <dl>
    {{ formetiketi_tasarimi(form.adSoyad, class="form-control") }}
    {{ formetiketi_tasarimi(form.email, class="form-control") }}
    {{ formetiketi_tasarimi(form.kullaniciAdi, class="form-control") }}
    {{ formetiketi_tasarimi(form.sifre, class="form-control") }}
    {{ formetiketi_tasarimi(form.sifreOnayi, class="form-control") }}
  </dl>
  <div class="form-row-last">
    <input type="submit" name="kaydet" id="kaydet" class="btn btn-primary" value="ÜYE OL">
    <input type="reset" name="sil" id="sil" class="btn btn-primary" value="TEMİZLE">
  </div>
</form>

{% endblock %}
Bu tasarımı görmek için http://127.0.0.1:5000/uyelik sayfasını ziyaret ettiğimizde;
Tabii bu form kullanıcı tarafından doldurulup “Üye Ol” butonuna tıklayarak POST isteği gönderildiğinde bu isteği işleyecek olan bir de Python fonksiyonumuz olmalı ve kullanıcı verilerini veritabanımıza kaydetmelidir. Burada kullanacağımız veritabanı bağlantısı ve tabloya kayıt için gerekli olan kodlar yukarıdaki basit HTML formda olana benzer şekildedir;
@ilkproje.route("/uyelik", methods=["GET","POST"])
def uyelik():
    form = KullaniciKayitFormu(request.form)

    if request.method == "POST" and form.validate():
        adSoyad = request.form.get("adSoyad")
        kullaniciAdi = request.form.get("kullaniciAdi")
        email = form.email.data
        sifre = request.form.get("sifre")

        try:
            kayit = veritabaniBaglantisi.connection.cursor()
            sorgu = "INSERT INTO kullanicilar VALUES(%s,%s,%s,%s,%s)"
            veriler=(None,adSoyad,kullaniciAdi,sifre,email)

            kayit.execute(sorgu,veriler)
            veritabaniBaglantisi.connection.commit()
            kayit.close()
        except Exception as istisna:
            return istisna
        return redirect(url_for("formsonuc", adSoyad = adSoyad))

    return render_template("uyelik.html", form=form, sehir = konum)

@ilkproje.route("/formsonuc/<adSoyad>")
def formsonuc(adSoyad):
    return render_template("formsonuc.html", adSoyad = adSoyad, sehir = konum)

Burada özellikle veritbanına kayıt gerçekleştirdikten sonra redirect(url_for(“formsonuc”, adSoyad = adSoyad)) ifadesiyle gönderilen POST isteği sonrasında kullanıcıyı formsonuc isimli bir fonksiyona yönlendirdiğimize ve aynı zamanda bu fonksiyona bir de parametre gönderdiğimize dikkat ediniz. Tabii bu fonksiyonda parametrenin nasıl alınıp kullanıldığına ve bu parametrenin foksiyona ait olan HTML şablonu ile nasıl kullanıldığına da dikkat etmeyi unutmayınız.

Ayrıca bu kod içinde form verilerini 2 farklı şekilde nasıl alabileceğinizi de görebilirsiniz; Birinci yöntem; request.form.get(“email”) kullanmak, ikincisi ise; email = form.email.data

Bu aşamadan sonra artık kullanıcımız formu doldurup gönderebilir;

Kullanıcı “ÜYE OL” butonuna tıkladığında işlemin sonucu formsonuc.html sayfasında görüntülenebilir;

Ve dahası phpMyAdmin sayfasını ziyaret ettiğinizde kullanıcı verilerinin veritabanına kayıt edildiğini teyit edebilirsiniz;

Veritabanı kayıtlarımızı dikkatli incelediyseniz eğer, web sayfasında kullanıcının girdiği şifreler gizli olmasına rağmen burada açıkça görülebiliyorlar. Çünkü formdan verileri metin(string) formatında aldık ve diğer veriler gibi şifreleri de direkt veritabanına kaydettik. Oysa bu istenilen bir durum değildir. Kullanıcnın şifresinin kendisine özel olması ve veritabanında şifreli olarak depolanması gerekir. Bunun için de passlib isimli bir modül vardır. Bu modül verilen metni hash ile şifrelemeyi sağlar. Bu sayede kullanıcnın formdan girdiği şifresini hash ile kodlayarak veritabanına kaydedebilirsiniz.

Öncelikle bir terminal penceresi açalım ve kurulumunu gerçekleştirelim;

$ pip install passlib

Daha sonra bu modülün ilgili metodlarını Python dosyamıza eklemeliyiz;

from passlib.hash import sha256_crypt

Artık formda yer alan şifre alanını hash ile kodlayarak veritabanında saklayabiliriz;

sifre = sha256_crypt.encrypt(form.sifre.data)

Şimdi http://127.0.0.1/uyelik sayfasını ziyaret ederek yeni bir kullanıcı oluşturduğumuzda bu kullanıcının şifresi veritabanında şifrelenmiş olarak depolanacaktır;

Şifreler gibi özel bilgileri veritabanında güvenli olarak depolamanın alternatif bir diğer yolu da werkzeug kütüphanesinin güvenlik modülüdür. Werkzeug Almanca’da Araç/Alet(Tool) anlamına gelen bir deyimdir.

Werkzeug, kapsamlı bir WSGI(Web Server Gateway Interface-Web Sunucu Ağ Geçidi Arayüzü) uygulama kütüphanesidir.

WSGI(Web Sunucusu Ağ Geçidi Arayüzü) bir web sunucusunun web uygulamalarıyla nasıl iletişim kurduğunu ve web uygulamalarının bir isteği işlemek için nasıl bağlanabileceğini açıklayan bir özelliktir. Bu kompleks kavramsal açıklama terimsel ifadeleri nedeniyle soyut geldiği için detaylı incelenmeden anlaşılabilecek bir konu değildir. Biz burada sadece güvenlik için wekzeug araçlarından birini nasıl kullanabileceğimizi öğreneceğiz. Ancak werkzeug kütüphanesinin yetenekleri çok geniştir ve detaylı bir incelemeyi ve projelerinizde kullanılmayı hak etmektedir. Bu sistemi yalnızca web uygulamalarınız için değil; aynı zamanda masaüstü uygulamaları, script uygulamaları ve hatta mobil uygulamalar gibi bağımsız python uygulamaları için de kullanabilirsiniz. Bunu nedenle Werkzeug dokümanlarına ayrıntılı şekilde çalışmanızda fayda vardır.

Öncelikle bir terminal penceresi açalım ve kurulumunu gerçekleştirelim;

$ pip install werkzeug

Daha sonra bu modülün ilgili metodlarını Python dosyamıza eklemeliyiz;

from werkzeug.security import generate_password_hash,check_password_hash

Artık formda yer alan şifre alanını hash ile kodlayarak veritabanında saklayabiliriz;

sifre = generate_password_hash(form.sifre.data, "sha256")
Eğer kullanıcı verilerini veritabanında depoladıktan sonra bir kullanıcı girişi uygulaması yapmak isterseniz, veritabanındaki hash ile depolanan şifreleri kullanıcı girişi formundan gelen şifre ile karşılaştırma yapmak için de check_password_hash metodunu uygulamamıza eklemeli ve kodumuz içinde kullanıcnın girişi ile veritabanındaki hash edilmiş şifreyi karşılaştırmalıyız;
if veritabanındaki_sifre == check_password_hash(girisformu.sifre.data):
        return render_template("kullanici_sayfasi.html")
Tabii bu son yazdığım basit kod işin mantığını ifade etmek için yazılmıştır. İlerleyen konularda kullanıcı giriş işlemleri ile ilgili detayları ayrıca işleyeceğiz.