dersblog

Flask ile Dinamik Web Sayfalari Gelistirmek

Kuruluş

Kurmak için sanal ortam kurup oradan,

pip install flask

Basit bir Flask uygulaması tek bir Python dosyası içinden servis başlatma, Web aksiyonları, tıklama idaresi, diğer sayfalara yönlendirme gibi işlemleri halledebilir. Dinamik sayfalar için şablonlar (templates) özelliği var, değişken ile belirtilen yerler o değişkenlerin değeri dışarıdan verilerek doldurulur.

Ilk main.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('/index.html')

@app.route('/location/')
def location(coordinates):
    return render_template('/location.html', location=coordinates)

if __name__ == '__main__':
    app.debug = True
    app.run(host="localhost", port=5000)

Şablonların olağan ayarlara göre templates adında bir alt dizinde olduğu kabul edilir.

app.debug=True ile servis başlatınca kodda yapılan değişim servisin tekrar yüklenmesine sebep olur, bu geliştirme açısından faydalı.

Chrome ile sayfa içeriği önbellekte tutuluyor ve kod değiştiği bazen farkedilemiyor. Bu sebeple geliştirme için Firefox daha iyi olabilir. Ya da Chrome'un güncellemesini CTRL-R ile zorlamak lazım.

Sayfalar

index.html

<html>

  <h3>Benim Uygulamam</h3>

  <div id="menu1">
    <a href="/location/32324">
      My Location
    </a>
    <br/>
    ...
  </div>

</html>

location.html

<html>
  <h1>Location at {{ location }}!</h1>
</html>

Flask sevisini baslatmak icin python main.py yeterli. Tarayıcı localhost:5000'e yönlendirilince sonuç görülecektir. Ana sayfada bağlantıya tıklanınca bu bağlantıdaki url bilgisi Python kodu üzerinden sonraki sayfaya aktarılacak, orada işlem yapılıp ikinci sayfa dinamik olarak ekrana basılacak.

CSS

Sayfamiza CSS uygulamak istersek, mesela main.css dosyasinda, bu dosyayi static alt dizinine koymak lazim (python uzerinden servis edilmeyen her sayfa, icerik buraya gider), ve html icinde

<head>
  <link rel="stylesheet" type="text/css" href="/static/main.css" media="screen" />
</head>

gerekli.

HTML

Mobil için UI geliştirenler bilir, HTML görüntüsü küçük çıkıyor. Mobilde ekranı dolduran türden görüntü için

<html>
  <head>
    ...
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  </head>
  <body>
    ....

</html>

Form Verisi, Metin Kutusu

Örnek olarak bir girdi kutusu düşünelim, o kutuya girilen parametre ile bir işlem yapılıyor, sonuçlar aynı sayfada gösteriliyor, bir arama fonksiyonu mesela;

Sayfa

Sonuçlar bir results adındaki bir liste içinde olacak bu liste olduğu gibi ekrana basılacak. Servis tarafı,

from flask import Flask, render_template, request

@app.route('/search')
def search():
    return render_template('/search.html')

@app.route('/submit_search', methods=['POST'])
def submit_search():
    print ("Arananan kelime:", request.form['search'])
    results = []
    for i in range(2):
        results.append([i, 'sonuc '+str(i)])
    return render_template("/search.html",results=results)

Tarayıcıda /search adresine gideriz oradaki forma bilgi yazıp tıklayınca girilen veriye request.form['search'] ile erisebiliyoruz. İki tane sonuç uydurduk üstte, listeye iki öğeli bir satır ekledik, ve tüm listeyi sayfaya gönderdik. Sayfa tüm listeyi gösterdi ama sayfa seviyesinde listeyi gezip satır ve öğelerine teker teker erisebilirdi mesela,

Sayfa

gibi. HTML ve kodun nasıl içiçe geçebildiğini görüyoruz.

Birden Fazla Aksiyon

Eğer form içerisinde iki tane düğme koymak istiyorsak ne yaparız? Bu durumda her iki input için aynı ismi ama farklı value değerleri verebiliriz, mesela

<input type="submit" name="action" value="Aksiyon 1"/>
<input type="submit" name="action" value="Aksiyon 2"/>

ve sonraki Python kodu içinde hangi aksiyona tıklanmış olduğunu request.form['action'] ile okuyabiliriz.

Form Verisi Secenek Listesi (Dropdown List)

<form> içinde gerekli olan komutlar altta gösteriliyor,

Sayfa

Secenegi form post edildikten sonra gonderilen metot icinde request.form.get("secim") komutu ile okuyabiliriz.

Hafızada Sonuç, Referans Veri Tutmak

Flask kodumuzun her tarafindan erisilebilen ve orada olacagindan emin olabilecegimiz bir obje istiyorsak, ve bir suru referans belgesine bakip (oturum, vs. nedir, ne kadar hafizada tutulur, gibi) duruma ozel bir sey ogrenmek istemiyorsak kendimiz bir tekil obje (singleton) yaratabiliriz. Bu objenin ayni Python sureci icinde tek olacagindan emin oluruz, kodumuzun farkli taraflari arasinda bilgi paylasmak icin bu nesneyi kullanabiliriz.

class OnlyOne(object):
    class __OnlyOne:
        def __init__(self):
            self.db = None
            self.some_results = []
        def __str__(self):
            return self.val
    instance = None
    def __new__(cls): # __new__ always a classmethod
        if not OnlyOne.instance:
            OnlyOne.instance = OnlyOne.__OnlyOne()
            OnlyOne.instance.edible = # pandas yuklemesi yap
        return OnlyOne.instance
    def __getattr__(self, name):
        return getattr(self.instance, name)
    def __setattr__(self, name):
        return setattr(self.instance, name)

Simdi OnlyOne().db ile erişince ilk başta yükleme yapılıp ardından yüklenen taban kullanılacak. Taban dediğimiz basit bir Pandas Dataframe objesi. Sonuçları, mesela herhangi bir listelemeden gelecek sonuçlar için someresults kullanmışız, ona OnlyOne().someresults ile erisiriz.

JSON Üretmek

Herhangi Python bazlı bir yazılımı dış dünyaya (servis olarak) açmak için en basit seçeneklerden biri HTTP servisi üzerinden JSON iledir, ayrıca bizi mantıklı URL'ler kullanmaya teşvik eden REST yaklaşımı iyi olur. En alt seviyede web servisi olarak mikro servis Flask olabilir. Flask'in güzel bir özelliği mantıklı URL ile Python fonksiyonu ilişkisini etiket (annotation) ile halledebilmesi.

from flask import Flask, url_for, jsonify
from flask import request, Response
app = Flask(__name__)
@app.route('/')
def api_root():
    return 'Merhaba'
@app.route('/test/url', methods=["PUT", "POST"])
def rq():
    data = request.get_json(force=True)   
    articles = sorted(data['liste'])
    return jsonify({'liste': liste})
if __name__ == '__main__':
    app.debug = True
    app.run(host="localhost", port=8080)

Başlatırız,

python main.py

Ve test için localhost:8080 ziyaret edilir. Merhaba mesajı görülür. Sonra curl ile

curl -H "Content-Type: application/json" -d '{"liste":[3,2,1]}'
  http://localhost:8080/test/url`

ozel URL'e JSON gonderilir. Kodun tek yaptigi listeyi alip siralamak, yani 3,2,1 siralanip 1,2,3 olarak geri gonderilecek.

Ayni cagriyi Python ile de yapabilirdik, mesela

import requests
response = requests.post('http://localhost:8080/test/url', json={"liste":[3,2,1]})
print("Status code: ", response.status_code)
print(response.json())

Dinamik İmaj, HTML Üretmek

Bazen kontrolü aktardığımız bir sonraki ekranın tamamen bir imaj, ya da tamamen pür bizim ürettiğimiz (şablon kullanmadan) bir HTML olmasını isteyebiliriz. Her iki durumda da send_file komutu faydalı. Mesela

@app.route('/show_img`)
def travel_maps(coords,resolution):
  ...  
  fout = ... # imaj yarat, /tmp altinda olabilir, isim fout
  return send_file(fout)  

HTML dosyaları için benzer mantık yeterli.

Dosya Yüklemek (Upload)

Bir siteye yerel dosyalarımıza göndermek / yüklemek için HTML

 <form action="/book_uploader" method="POST" enctype = "multipart/form-data">
  <p>
    <input type = "file" name = "file" />
  </p>
  <input type="submit" value="Gonder"/>
</form>

Python içinde

@app.route('/book_uploader', methods = ['GET', 'POST'])
def upload_file():

   if request.method == 'POST':
      f = request.files['file']
      fbook =  "/dizin/" + f.filename
      f.save(fbook)     
      return 'file uploaded successfully'

Dosya İndirmek

Ekrana basilan degil, eğer eklenti (attachement) olarak indirim (download) dizinine gidecek bir dosyayı tarayıcıya vermek istiyorsak, send_file kullanabiliriz. Alttaki örnekte /tmp/out.csv dosyasının bir şekilde üretildiğini farzediyoruz,

@app.route('/testcsv') def testcsv(): return sendfile('/tmp/out.csv',mimetype='text/csv',asattachment=True)

Artık tarayıcıyla /test_csv adresi ziyaret edildiğinde dosya indirilmeye başlayacaktır.

Başlatma Numaraları

En basit kullanımla bir app.py içinde olan uygulamayı python app.py ile başlatırız. Fakat diyelim ki programları /dizin/dizin altında olan env1 adında bir virtualenv ortamı içindeyiz, ayrıca mesela otomatik başlatım yapmak istiyoruz ve tüm dosya erişimini vermek lazım o zaman /dizin/dizin/env1/bin/python /vs1/vs2/app.py ile başlatım yapılabilir. env1 altındaki python'u kullanınca o virtualenv ortamı otomatik olarak aktif hale gelir ve oradaki tüm paketler kullanıma girer.

Ya peki Flask başlayınca nereden başlatırsak başlatalım üstünde olduğumüz dizinin (current directory) app.py ile aynı lmasını istersek? Bu durumda app.py kodunun en başına

import os; os.chdir(os.path.dirname(os.path.abspath(__file__)))

eklerse başlangıçta ilk yapılan iş kodun olduğu dizine gitmek olacaktır.

Disaridan istemci, tarayici erisimi

Eğer servise dışarıdan erişmek istiyorsak host için o erişilecek IP adresini vermemiz gerekir, makinanın adresi ifconfig -a ile bakılabilir, ve mesela host="192.168.22.33" gibi bir seçenek geçilir.


Yukarı