32. Sürüm 12'deki HTML modu
Sürüm 12'nin başında, uygulamayı birkaç aşamada geliştireceğimizi belirtmiştik. Şöyle yazmıştık:
- HTML uygulamasının görünümlerinden yola çıkarak, web uygulamasının gerçekleştirmesi gereken eylemleri tanımlayacağız. Burada gerçek görünümleri kullanacağız, ancak bunlar sadece kağıt üzerinde çizilmiş görünümler de olabilir;
- bu eylemlerden yola çıkarak, HTML uygulamasının URL hizmetlerini tanımlayacağız;
- bu hizmet URL'lerini, jSON sunan bir sunucu ile uygulayacağız. Bu, sunulacak HTML sayfalarıyla ilgilenmeden web sunucusunun iskeletini tanımlamamızı sağlar. Bu URL hizmetlerini Postman ile test edeceğiz;
- ardından jSON sunucumuzu bir konsol istemcisiyle test edeceğiz;
- jSON sunucusu onaylandıktan sonra, HTML uygulamasını yazmaya geçeceğiz;
jSON ve XML sunucularımız çalışır durumda. Artık HTML sunucusuna geçebiliriz. Göreceğiz ki bu sunucu, jSON / XML sunucuları için geliştirilen tüm mimariyi devralıyor ve bunlara HTML görünüm yönetimini ekliyor.
32.1. MVC Mimarisi
MVC olarak adlandırılan mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygulayacağız:
Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşecektir:
- 1 - istek
Talep edilen URL'ler, http://machine:port/action/param1/param2/… biçiminde olacaktır. [Contrôleur principal], talebi doğru denetleyiciye "yönlendirmek" için bir yapılandırma dosyası kullanacaktır. Bunun için, URL'in [action] alanını kullanacaktır. URL ile [param1/param2/…] arasındaki kısım, eyleme iletilecek isteğe bağlı parametrelerden oluşur. Burada MVC'in C'si, [Contrôleur principal, Contrôleur / Action] dizesidir. İstenen eylemi işleyebilecek hiçbir denetleyici yoksa, web sunucusu istenen URL'in bulunamadığını bildirir.
- 2 - İşleme
- Seçilen [2a] eylemi, [Contrôleur principal] tarafından kendisine iletilen parami parametrelerini kullanabilir. Bu parametreler iki kaynaktan gelebilir:
- [/param1/param2/…]'in URL'teki yolu,
- istemcinin isteğinin gövdesine gönderilen parametrelerden;
- kullanıcının isteğinin işlenmesi sırasında, eylem [métier] ve [2b] katmanlarına ihtiyaç duyabilir. İstemcinin isteği işlendikten sonra, bu eylem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata yanıtı;
- aksi takdirde bir onay yanıtı;
- [Contrôleur / Action], yanıtını [2c] olarak ana denetleyiciye iletecek ve bir durum kodu gönderecektir. Bu durum kodları, uygulamanın bulunduğu durumu benzersiz bir şekilde temsil edecektir. Bu, ya bir başarı kodu ya da bir hata kodu olacaktır;
- 3 - yanıt
- müşterinin jSON yanıtını talep etmesine bağlı olarak, XML veya HTML yanıtını talep etmiş olmasına bağlı olarak, [Contrôleur principal], uygun yanıt türünü [3a]'te oluşturacak ve ondan yanıtı müşteriye göndermesini isteyecektir. [Contrôleur principal], çalıştırılan [Contrôleur / Action] tarafından sağlanan yanıtı ve durum kodunu ona iletecektir;
- istenen yanıt jSON veya XML türündeyse, seçilen yanıt kendisine verilen [Contrôleur / Action] yanıtını biçimlendirecek ve [3c] aracılığıyla gönderecektir. Bu yanıtı işleyebilen istemci, bir Python konsol komut dosyası veya bir HTML sayfasında barındırılan bir JavaScript komut dosyası olabilir;
- istenen yanıt HTML türündeyse, seçilen yanıt kendisine verilen durum kodunu kullanarak [3b], HTML veya [Vuei] görünümlerinden birini seçecektir. Bu, MVC'in V'sidir. Her durum koduna tek bir görünüm karşılık gelir. Bu V görünümü, yürütülen [Contrôleur / Action]'in yanıtını görüntüleyecektir. Bu görünüm, HTML, CSS ve JavaScript kullanarak bu yanıtın verilerini işler. Bu verilere görünüm modeli denir. Bu, MVC'teki M harfidir. İstemci ise çoğu zaman bir tarayıcıdır;
32.2. HTML sunucusunun komut dosyası dizin yapısı

- [1]'te, HTML sunucusunun statik öğeleri;
- [2-3]'te, HTML sunucusunun V görünümleri. [2] parçaları, [3] görünümlerinde yeniden kullanılabilir öğelerdir;
- [4]'te, görünümlerin statik olarak test edilmesinde kullanılacak bir klasör;
- [5] içinde, V görünümlerinin M şablonlarının bulunduğu klasör; MVC'in M şablonu;
32.3. Görünümlerin Tanıtımı
HTML web uygulaması dört görünüm kullanır. İlk görünüm, kimlik doğrulama görünümüdür:
- bu ilk görünüme yönlendiren eylem, [/init-session] [1] eylemidir;
- [Valider] düğmesine tıklandığında, iki parametre gönderilerek [/authentifier-utilisateur] eylemi tetiklenir: [2-3];
Vergi hesaplama görünümü:

- [1]'te, bu görünümü getiren [/authentifier-utilisateur] eylemi;
- [2]'te, [Valider] düğmesine tıklanması, üç parametreyle ([2-5]) [/calculer-impot] eyleminin yürütülmesini tetikler;
- [6] bağlantısına tıklandığında, [/lister-simulations] eylemi parametre olmadan tetiklenir;
- [7] bağlantısına tıklandığında, [/fin-session] eylemi parametre olmadan tetiklenir;
- görünüm, kimliği doğrulanmış kullanıcı tarafından yapılan simülasyonlara aittir:

- [1]'te, bu görünümü getiren [/lister-simulations] eylemi;
- [2]'te, [Supprimer] bağlantısına tıklanması, listeden silinecek simülasyon numarası parametresiyle [/supprimer-simulation] eylemini tetikler;
- [3] bağlantısına tıklandığında, parametre içermeyen [/afficher-calcul-impot] eylemi tetiklenir ve vergi hesaplama ekranı yeniden görüntülenir;
- [4] bağlantısına tıklandığında, parametre olmadan [/fin-session] eylemi tetiklenir;
- ekran, beklenmedik hatalar ekranı olarak adlandırılacaktır:
- [1]'te: kullanıcı URL'i kendisi girmiştir. Oysa bu örnekte herhangi bir simülasyon yoktu. Dolayısıyla [2] hata mesajı alınır. Bu hatayı biliyoruz. jSON / XML'te de karşılaşmıştık. Bu tür hatalara “beklenmedik hata” diyeceğiz, çünkü bunlar uygulamanın normal kullanımı sırasında ortaya çıkamaz. Bu hatalar, kullanıcı URL kodlarını kendisi girdiğinde ortaya çıkabilir;
- beklenmedik bir hata durumunda, [3-5] bağlantıları diğer üç görünümden birine geri dönmenizi sağlar;
jSON / XML sunucusunun çeşitli URL hizmetlerini hatırlayalım:
Eylem | Rol | Çalıştırma bağlamı |
/init-session | İstenen yanıtların türünü (json, xml, html) belirlemek için kullanılır | GET isteği Herhangi bir zamanda gönderilebilir |
/kullanıcı-doğrulama | Bir kullanıcının oturum açmasına izin verir veya vermez | POST isteği. İstek, [user, password] adlı iki POST parametresine sahip olmalıdır Yalnızca oturum türü (json, xml, html) biliniyorsa gönderilebilir |
/calculer-impot | Vergi hesaplaması simülasyonu yapar | POST isteği. İstek, üç adet POST parametresi içermelidir: [marié, enfants, salaire] Yalnızca oturum türü (json, xml, html) biliniyorsa ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/simülasyonları-listele | Oturumun başlangıcından itibaren gerçekleştirilen simülasyonların listesini görüntüleme isteği | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/simülasyonu-sil/numara | Simülasyon listesinden bir simülasyonu siler | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/vergi-hesaplamasını-göster | Vergi hesaplama sayfası HTML'i görüntüler | GET isteği. Yalnızca oturum türü (json, xml, html) biliniyorsa ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
/oturumu-sonlandır | Simülasyon oturumunu sonlandırır. | Teknik olarak eski web oturumu silinir ve yeni bir oturum oluşturulur Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
Bu farklı URL hizmet kodları, HTML sunucusu için de kullanılacaktır.
32.4. Görünümlerin yapılandırılması
Bir eylem, bir denetleyici tarafından işlenir. Bu denetleyici, bir tuple (sonuç, status_code) döndürür; burada:
- [résultat], [action, état, réponse] anahtarlarından oluşan bir sözlüktür;
- [status_code], istemciye gönderilecek HTTP yanıtının durum kodudur;
Bir HTML oturumunda, bir eylemin ardından görüntülenen sayfa, denetleyici tarafından döndürülen durum koduna bağlıdır. Bu bağımlılık, [config] yapılandırmasında şu şekilde somutlaşır:
# HTML görünümleri ve bunların şablonları, denetleyici tarafından döndürülen duruma bağlıdır
"views": [
{
# kimlik doğrulama sayfası
"états": [
# /oturum-açma başarılı
700,
# /kullanıcı-kimlik-doğrulama başarısız
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vergi hesaplama sayfası
"états": [
# /kullanıcı-kimlik-doğrulama başarılı
200,
# /vergi-hesaplama başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# simülasyon listesini görüntüle
"états": [
# /simülasyonları-listele
500,
# /simülasyonu-sil
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# beklenmedik hataların görünümü
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# yönlendirmeler
"redirections": [
{
"états": [
400, # /oturumu-başarıyla-sonlandır
],
# yönlendirme
"to": "/init-session/html",
}
],
}
- 2-40. satırlar: [views] bir görünüm listesidir. 3-13. satırlardaki görünümü ele alalım:
- 11. satır: görüntülenen V görünümü;
- satır 12: bu görünümün M modelini oluşturmakla görevli sınıf örneği;
- satır 5-10: bu görünüme yönlendiren durumlar;
- 3-13. satırlar: kimlik doğrulama görünümü;
- 14-28. satırlar: vergi hesaplama görünümü;
- satır 29-39: simülasyon listesi görünümü;
- satır 42-46: beklenmedik hatalar görünümü;
- satır 49-57: Bazı raporlar, bir yönlendirme yoluyla bir görünüme yönlendirir. [/fin-session] eyleminin başarılı olduğu 400 numaralı rapor da bu durumdadır. Bu durumda, istemciyi [http://machine:port/chemin/init-session/html] eylemine yönlendirmek gerekir;
Şimdi çeşitli görünümleri tanıtacağız.
32.5. Kimlik doğrulama görünümü

32.5.1. Görünümün tanıtımı
Kimlik doğrulama görünümü şu şekildedir:

Görünüm, "parça" olarak adlandırılan iki öğeden oluşur:
- [1] parçası, [v-bandeau.html] parçası tarafından oluşturulur;
- [2] parçası, [v-authentification.html] parçası tarafından oluşturulur;
Kimlik doğrulama sayfası, aşağıdaki [vue-authentification.html] sayfası tarafından oluşturulur:
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- başlık çubuğu -->
{% include "fragments/v-bandeau.html" %}
<!-- iki sütunlu satır -->
<div class="row">
<div class="col-md-9">
{% include "fragments/v-authentification.html" %}
</div>
</div>
<!-- hata durumunda - bir hata uyarısı görüntülenir -->
{% if modèle.error %}
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Açıklamalar
- 2. satır: HTML belgesi bu satırla başlar;
- 3-36. satırlar: HTML sayfası, <html> </html> etiketleri arasında yer alır;
- 4-11. satırlar: HTML belgesinin başlığı (head);
- 6. satır: <meta charset> etiketi, belgenin UTF-8 karakter kümesinde kodlandığını belirtir;
- 7. satır: <meta name=’viewport’> etiketi, görünümün ilk görüntülenişini belirler: görüntüyü gösteren ekranın tüm genişliği (width) üzerinde, daha küçük bir ekran boyutuna uyum sağlamak için yeniden boyutlandırılmadan (shrink-to-fit) orijinal boyutunda (initial-scale);
- 9. satır: <link rel=’stylesheet’> etiketi, görünümün görünümünü belirleyen CSS dosyasını tanımlar. Burada CSS Bootstrap 4.4.1 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] çerçevesini kullanıyoruz ;
- 10. satır: <title> etiketi, sayfanın başlığını belirler:

- 13-35. satırlar: Web sayfasının gövdesi <body></body> etiketleri arasında yer alır;
- 14-34. satırlar: <div> etiketi, görüntülenen sayfanın bir bölümünü sınırlar. Görünümde kullanılan [class] özniteliklerinin tümü, CSS Bootstrap çerçevesine atıfta bulunur. <div class=’container’> etiketi (14. satır), bir Bootstrap konteynerini sınırlar;
- 26. satır: [v-bandeau.html] parçası eklenir. Bu parça, sayfanın [1] başlık çubuğunu oluşturur. Bunu birazdan açıklayacağız;
- 18-22. satırlar: <div class=’row’> etiketi bir Bootstrap satırını sınırlar. Bu satırlar 12 sütundan oluşur;
- 19. satır: <div class=’col-md-9’> etiketi, 9 sütunluk bir bölümü sınırlar;
- 20. satır: Sayfadaki [2] kimlik doğrulama formunu görüntüleyen [v-authentification.html] parçası eklenmiştir. Bunu yakında açıklayacağız;
- 24-33. satırlar: Bu satırlardaki HTML kodu, yalnızca [modèle.error] değeri True ise kullanılır. Her zaman şu şekilde ilerleyeceğiz: HTML görünümünün şablonu, [modèle] sözlüğü içinde kapsüllenecektir;
- 24-33. satırlar: Kullanıcı yanlış kimlik bilgilerini girerse kimlik doğrulama başarısız olur. Bu durumda, kimlik doğrulama görünümü bir hata mesajıyla birlikte yeniden görüntülenir. [modèle.error] özniteliği, bu hata mesajının görüntülenip görüntülenmeyeceğini belirtir;
- satır 27-30: pembe arka planlı bir alanı sınırlar (class="alert alert-danger") (satır 27);

- 28. satır: bir metin;
- satır 29: HTML <ul> (sırasız liste) etiketi, madde işaretli bir liste görüntüler. Listenin her bir öğesi <li>öğe</li> sözdizimine sahip olmalıdır. Burada [modèle.erreurs] değerinin değeri görüntülenir. Bu değer, [safe] filtresi tarafından (| karakterinin varlığı nedeniyle) filtrelenir. Varsayılan olarak, bir karakter dizisi tarayıcıya gönderilmesi gerektiğinde, Flask, tarayıcının bunları yorumlamaması için içinde bulunabilecek tüm HTML etiketlerini "etkisiz hale getirir". Ancak bazen bu etiketlerin yorumlanmasını isteyebiliriz. Bu durum, [modèle.erreurs] dizesinin, liste öğesini sınırlandırmak için kullanılan HTML <li> ve </li> etiketlerini içerdiği durumlarda geçerlidir. Bu durumda, Flask’a görüntülenecek dizgenin güvenli (safe) olduğunu ve dolayısıyla içinde bulacağı HTML etiketlerini etkisiz hale getirmemesi gerektiğini belirten [safe] filtresini kullanırız;
Bu koddan tanımlanması gereken dinamik öğeleri şöyle özetleyebiliriz:
- [modèle.error]: bir hata mesajı görüntülemek için;
- [modèle.erreurs]: bir hata mesajları listesi (terimin HTML anlamında);
32.5.2. [v-bandeau.html] parçası
[v-bandeau.html] parçası, web uygulamasının tüm görünümlerinde üst başlığı görüntüler:

[v-bandeau.html] parçasının kodu şöyledir:
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="{{ url_for('static', filename='images/logo.jpg') }}" alt="Cerisier en fleurs"/>
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Açıklamalar
- 2-13. satırlar: Başlık çubuğu, Jumbotron tipi bir Bootstrap bölümü ([<div class="jumbotron">]) içinde yer almaktadır. Bu Bootstrap sınıfı, görüntülenen içeriği öne çıkarmak için özel bir şekilde biçimlendirir;
- 3-12. satırlar: bir Bootstrap satırı;
- 4-6. satırlar: [img] adlı bir resim, satırın ilk dört sütununa yerleştirilmiştir;
- 5. satır: sözdizimi:
Flask'ın [url_for] işlevini kullanır. Burada, bu işlevin değeri [static] klasöründeki [images/logo.pg] dosyasının URL değeri olacaktır;
- 7-11. satırlar: satırdaki diğer 8 sütun (toplamda 12 sütun olduğunu hatırlatırız), bir metni (9. satır) büyük harflerle (<h1>, 8-10. satırlar) yerleştirmek için kullanılacaktır;
32.5.3. [v-authentification.html] parçası
[v-authentification.html] parçası, web uygulamasının kimlik doğrulama formunu görüntüler:

[v-authentification.html] parçasının kodu şöyledir:
<!-- form HTML - değerler [authentifier-utilisateur] eylemiyle gönderilir -->
<form method="post" action="/authentifier-utilisateur">
<!-- başlık -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- Bootstrap formu -->
<fieldset class="form-group">
<!-- 1. satır -->
<div class="form-group row">
<!-- açıklama -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- metin giriş alanı -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
</div>
</div>
<!-- 2. satır -->
<div class="form-group row">
<!-- etiket -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- metin giriş alanı -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe" required>
</div>
</div>
<!-- 3. satırda [submit] tipi düğme -->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Açıklamalar
- 2-39. satırlar: <form> etiketi, bir HTML formunu sınırlar. Bu form genellikle aşağıdaki özelliklere sahiptir:
- giriş alanları tanımlar (17. ve 27. satırlardaki <input> etiketleri);
- [submit] türünde bir düğme içerir (satır 34); bu düğme, girilen değerleri [form] etiketinin [action] özniteliğinde belirtilen URL adresine gönderir (2. satır) içinde belirtilen URL etiketine gönderir. Bu URL etiketine istek göndermek için kullanılan HTTP yöntemi, [form] etiketinin (2. satır) [method] özniteliğinde belirtilmiştir;
- burada, kullanıcı [Valider] düğmesine (satır 34) tıkladığında, tarayıcı formda girilen değerleri URL [/authentifier-utilisateur]'e (satır 2) gönderecektir (satır 2);
- Gönderilen değerler, kullanıcının 17. ve 27. satırlardaki giriş alanlarına girdiği değerlerdir. Bu değerler, tarayıcının HTTP biçiminde oluşturacağı isteğin gövdesinde [x-www-forl-urlencoded] şeklinde gönderilecektir. [user, password] parametrelerinin adları, 17. ve 27. satırlardaki giriş alanlarının [name] özniteliklerinin adlarıdır;
- 5-7. satırlar: mavi arka plan üzerinde bir başlık görüntülemek için bir Bootstrap bölümü:
- satır 10-37: bir Bootstrap formu. Formun tüm öğeleri belirli bir şekilde biçimlendirilecektir;
- satır 12-20: formun ilk Bootstrap satırını tanımlar:
![]()
- 14. satır, [1] etiketini üç sütuna yayacak şekilde tanımlar. [label] etiketinin [for] özniteliği, bu etiketi 17. satırdaki giriş alanının [id] özniteliğine bağlar;
- 15-19. satırlar: giriş alanını dört sütunlu bir gruba yerleştirir;
- 17-18. satırlar: HTML ve [input] etiketleri bir giriş alanını tanımlar. Bu etiketlerin birkaç parametresi vardır:
- [type=’text’]: Bu bir metin giriş alanıdır. Buraya herhangi bir şey yazılabilir;
- [class=’form-control’]: giriş alanı için Bootstrap stili;
- [id=’user’]: Giriş alanının kimliği. Bu kimlik genellikle CSS ve JavaScript kodu tarafından kullanılır;
- [name=’user’]: giriş alanının adı. Kullanıcı tarafından girilen değer, tarayıcı tarafından bu adla gönderilir;
- [placeholder=’invite’]: Kullanıcı henüz hiçbir şey yazmadığında giriş alanında görüntülenen metin;
![]()
- (devam)
- [value=’valeur’]: Giriş alanı görüntülendiği anda, yani kullanıcı başka bir şey girmeden önce, bu alana "değer" metni görüntülenecektir. Bu mekanizma, bir hata oluştuğunda hataya neden olan girişi görüntülemek için kullanılır. Burada bu değer, [modèle.login] değişkeninin değeri olacaktır;
- [required]: Formun sunucuya gönderilebilmesi için kullanıcının bir değer girmesini zorunlu kılar:
- 21-30. satırlar: şifre girişi için benzer bir kod;
- 27. satır: [type=’password’], bir metin giriş alanı oluşturur (istediğiniz herhangi bir şeyi yazabilirsiniz) ancak yazılan karakterler gizlenir:
![]()
- 32-36. satırlar: [Valider] düğmesi için üçüncü bir Bootstrap satırı;
- 34. satır: [type=submit] özniteliğine sahip olduğu için, bu düğmeye tıklandığında, daha önce açıklandığı gibi girilen değerler tarayıcı tarafından sunucuya gönderilir. CSS [class="btn btn-primary"] özniteliği mavi bir düğme görüntüler:
Son bir şeyi daha açıklamamız gerekiyor. 2. satırda, [action="/authentifier-utilisateur"] özniteliği, eksik bir URL tanımlar (http://machine:port/chemin ile başlamaz). Örneğimizde, uygulamadaki tüm URL'ler [http://machine:port/chemin/action/param1/param2/..] biçimindedir; burada [http://machine:port/chemin], hizmet URL'lerinin köküdür. [action="/authentifier-utilisateur"]'te, URL'e göre mutlak bir konum vardır; yani bu konum, URL kökünden ölçülmüştür. Dolayısıyla, POST'in tamamlayıcı parçası olan URL, [http://machine:port/chemin/authentifier-utilisateur]'tir ve tarayıcı bunu kullanacaktır.
Bu parçanın [modèle.login] şablonunu kullandığını unutmayalım.
32.5.4. Görsel testler
Görünümler uygulamaya entegre edilmeden çok önce test edilebilir. Burada amaç, görünümlerin görsel yönünü test etmektir. Tüm test görünümlerini projenin [tests_views] klasöründe toplayacağız:

V [vue-authentification.html] görünümünü test etmek için, bu görünümün görüntüleyeceği M veri modelini oluşturmamız gerekiyor. Bunu [test_vue_authentification.py] komut dosyasıyla yapıyoruz:
from flask import Flask, render_template, make_response
# Flask uygulaması
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Ana Sayfa URL
@app.route('/')
def index():
# sayfa verilerini şablona yerleştiriyoruz
modèle = {}
# kullanıcı kimliği
modèle["login"] = "albert"
# hata listesi
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# hataların bir listesi oluşturulur HTML
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# sayfanın görüntülenmesi
return make_response(render_template("views/vue-authentification.html", modèle=modèle))
# ana sayfa
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Açıklamalar
- 1-3. satırlar: Tek amacı [vue-authentification.html] görünümünü (22. satır) görüntülemek olan bir Flask uygulaması oluşturulur;
- 7. satır: Uygulamanın tek bir URL hizmet nesnesi vardır;
- 9-20. satırlar: Kimlik doğrulama görünümünde, [modèle] nesnesi tarafından kontrol edilen dinamik bölümler bulunmaktadır. Bu nesneye görünüm modeli denir. MVC kısaltması için verilen iki tanımdan birine göre, burada MVC'in M'si bulunmaktadır. [vue-authentification.html] görünümünü tanımlarken üç dinamik değer belirlemiştik:
- [modèle.error]: bir hata mesajının görüntülenip görüntülenmeyeceğini belirten boole değeri;
- [modèle.erreurs]: HTML hata mesajları listesi;
- [modèle.login]: bir kullanıcının oturum açma bilgisi;
Dolayısıyla bu üç dinamik değeri tanımlamamız gerekiyor.
- 9-20. satırlar: kimlik doğrulama görünümünün üç dinamik öğesini tanımlıyoruz;
Testi yapmak için [tests_views/test_vue_authentification.py] komut dosyasını çalıştırıyoruz ve URL ile [/localhost:5000/]'i istiyoruz:
Sonuçtan memnun kalana kadar bu görsel testlere devam edilir.

32.5.5. Görünüm modelinin hesaplanması
Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Görünüm modelleri, [models_for_views] klasöründe toplanan sınıflar tarafından oluşturulacaktır:

Bir görünüm modeli oluşturan her sınıf, aşağıdaki [InterfaceModelForView] arayüzüne uyacaktır:
from abc import ABC, abstractmethod
from flask import Request
from werkzeug.local import LocalProxy
class InterfaceModelForView(ABC):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
- 8-10. satırlar: [get_model_for_view] yöntemi, bir sözlük içinde kapsüllenmiş bir görünüm şablonu üretmekle görevlidir. Bunun için aşağıdaki bilgileri alır:
- [request, session, config], eylem denetleyicisi tarafından kullanılan parametrelerin aynısıdır. Dolayısıyla bu parametreler modele de aktarılır;
- denetleyici, modele de aktarılan bir [résultat] sonucu üretmiştir. Bu sonuç, mevcut eylemin yürütülmesinin nasıl gerçekleştiğini gösteren önemli bir öğe olan [état]'i içerir. Model bu bilgiyi kullanacaktır;
Uygulamanın [config] yapılandırmasında, denetleyiciler tarafından döndürülen durum kodlarının, görüntülenecek HTML görünümünü belirlemek için kullanıldığını gördük:
# HTML görünümleri ve şablonları, denetleyici tarafından döndürülen duruma bağlıdır
"views": [
{
# kimlik doğrulama görünümü
"états": [
# /init-session başarılı
700,
# /kullanıcı-kimlik-doğrulama başarısız
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vergi hesaplama sayfası
"états": [
# /kullanıcı-kimlik-doğrulama başarılı
200,
# /vergi-hesaplama başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# simülasyon listesini görüntüle
"états": [
# /simülasyonları-listele
500,
# /simülasyonu-sil
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# beklenmedik hataların görünümü
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# yönlendirmeler
"redirections": [
{
"états": [
400, # /oturumu-başarıyla-sonlandır
],
# yönlendirme
"to": "/init-session/html",
}
],
}
Dolayısıyla, kimlik doğrulama ekranının görüntülenmesini sağlayan kodlar [700, 201] (7. ve 9. satırlar) kodlarıdır. Bu kodların anlamını öğrenmek için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanılabilir:
- [init-session-json-700]: 700, [init-session] eyleminin başarıyla tamamlanmasının ardından ortaya çıkan durum kodudur; bu durumda boş kimlik doğrulama formu görüntülenir;
- [authentifier-utilisateur-201]: 201, [authentifier-utilisateur] eyleminin başarısızlıkla sonuçlanmasının ardından (kimlik bilgileri tanınmadı) verilen durum kodudur: bu durumda, düzeltilmesi için kimlik doğrulama formu görüntülenir;
Artık kimlik doğrulama formunun hangi durumlarda görüntülenmesi gerektiğini bildiğimize göre, [ModelForAuthentificationView] (12. satır) içinde form şablonunu hesaplayabiliriz:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForAuthentificationView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# sayfa verilerini şablona yerleştiriyoruz
modèle = {}
# uygulama durumu
état = résultat["état"]
# şablon duruma bağlı
if état == 700:
# formun boş olarak görüntülenmesi durumu
modèle["login"] = ""
# görüntülenecek hata yok
modèle["error"] = False
elif état == 201:
# kimlik doğrulama hatası
# Başlangıçta girilen kullanıcı yeniden görüntüleniyor
modèle["login"] = request.form.get("user")
# görüntülenecek bir hata var
modèle["error"] = True
# HTML hata mesajları listesi
erreurs = ""
for erreur in résultat["réponse"]:
erreurs += f"<li>{erreur}</li>"
modèle["erreurs"] = erreurs
# şablon oluşturuluyor
return modèle
Açıklamalar
- 8. satır: Kimlik doğrulama görünümünün [get_model_for_view] yöntemi, üç anahtarlı bir sözlük sağlamalıdır: [error, erreurs, login]. Bu hesaplama, eylemin denetleyicisi tarafından döndürülen durum koduna göre yapılır;
- 12. satır: mevcut eylemi işleyen denetleyici tarafından döndürülen durum kodunu alırız;
- 14-29. satırlar: model, bu durum koduna bağlıdır;
- satır 15-18: boş bir kimlik doğrulama formunun görüntülenmesi gereken durum;
- 20-29. satırlar: kimlik doğrulama hatası durumunda, kullanıcı tarafından girilen kimlik bilgisi görüntülenir ve bir hata mesajı gösterilir. Kullanıcı, klavyeyi kullanarak kimlik doğrulamayı yeniden deneyebilir;
- 22. satır: Kullanıcı tarafından başlangıçta girilen kimlik bilgisi, istemci isteğinde bulunabilir;
- 24. satır: Görüntülenecek hatalar olduğu bildirilir;
- 26-29. satırlar: Hata durumunda, result[‘réponse’] bir hata listesi içerir;
32.5.6. HTML yanıtlarının oluşturulması
HTML uygulamasının MVC modeline geri dönelim:
- 2'de (2a, 2b): denetleyici bir eylem gerçekleştirir;
- 3'te (3a, 3b, 3c): bir görünüm seçilir ve istemciye gönderilir;
[3a]'te bir yanıt türü (jSON, XML, HTML) seçilir. jSON ve XML yanıtlarının nasıl oluşturulduğunu gördük, ancak HTML yanıtlarını henüz ele almadık. Bunlar, [HtmlResponse] sınıfı tarafından oluşturulur:

Ana komut dosyası [main]'te kullanıcıya verilecek yanıt türünün nasıl belirlendiğini hatırlayalım:
….
# gönderilecek yanıt oluşturuluyor
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# yanıt gönderiliyor
return response, status_code
Burada, 3. satırda config[‘responses’] şu sözlükten oluşur:
# farklı yanıt türleri (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
Dolayısıyla, [HtmlResponse] sınıfı, HTML yanıtını oluşturur. Bu sınıfın kodu şöyledir:
# sonuçta yer alan duruma göre yanıt sözlüğü HTML
from flask import make_response, render_template
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# HTML yanıtı, denetleyici tarafından döndürülen durum koduna bağlıdır
état = résultat["état"]
# Yönlendirme yapılmalı mı?
for redirection in config["redirections"]:
# Yönlendirme gerektiren durumlar
états = redirection["états"]
if état in états:
# yönlendirme yapılmalıdır
return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND
# her duruma bir görünüm karşılık gelir
# bu görünüm, görünümler listesinde aranır
views_configs = config["views"]
trouvé = False
i = 0
# görünüm listesi taranıyor
nb_views = len(views_configs)
while not trouvé and i < nb_views:
# i numaralı görünüm
view_config = views_configs[i]
# i numaralı görünüme ilişkin raporlar
états = view_config["états"]
# aranan durum, i numaralı görünüme ilişkin durumlar arasında yer alıyor mu
if état in états:
trouvé = True
else:
# sonraki görünüm
i += 1
# bulundu mu?
if not trouvé:
# uygulamanın mevcut durumu için herhangi bir görünüm yoksa
# hata görünümünü oluşturur
view_config = config["view-erreurs"]
# görüntülenecek görünüm şablonu hesaplanıyor
model_for_view = view_config["model_for_view"]
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
# yanıtın kodunu oluşturur HTML
html = render_template(view_config["view_name"], modèle=modèle)
# HTTP yanıtı oluşturulur
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
# sonuç döndürülür
return response, status_code
- 11. satır: HTML yanıtını oluşturmakla görevli [build_http_response] yöntemi, aşağıdaki parametreleri alır:
- [request, session, dict]: Bunlar, denetleyici tarafından devam eden eylemi işlemek için kullanılan parametrelerdir;
- [status_code, résultat], aynı denetleyici tarafından üretilen iki sonuçtur;
- 14. satır: Daha önce de belirttiğimiz gibi, sunucunun HTML yanıtı, [résultat] sözlüğünde yer alan durum koduna bağlıdır;
- 16-22. satırlar: Öncelikle yönlendirmeler işlenir. Şu an için, bir yönlendirme örneğiyle karşılaşana kadar bu durumu göz ardı edeceğiz. Yönlendirmelerin tipik olarak HTML sunucusunun bir kullanım durumu olduğunu belirtelim. jSON ve ouXML sunucularında bu durumla karşılaşılmamaktadır;
- 24-41. satırlar: Görünümler arasında, [états] listesinde aranan durumu içeren görünüm aranır;
- satır 42-46: Eğer hiçbir görünüm bulunamadıysa, bu beklenmedik bir hatadır. Bir örnek verelim. Uygulamanın normal çalışması sırasında, [/supprimer-simulation] eylemi asla hata vermemelidir. Nitekim, bu simülasyonların silinmesinin kod tarafından oluşturulan bağlantılar aracılığıyla gerçekleştiğini göreceğiz. Bu bağlantılar doğrudur ve bir hataya yol açamaz. Ancak daha önce gördüğümüz gibi, kullanıcı doğrudan URL veya [/supprimer-simulation/id] yazarak bir hataya neden olabilir. Bu durumda, [SupprimerSimulationController] denetleyicisi 601 durum kodunu döndürür. Oysa bu durum kodu, HTML sayfasının görüntülenmesine yol açan durum kodları listesinde yer almamaktadır. Bu nedenle hata sayfası görüntülenecektir. Bu sayfa, yapılandırmada şu şekilde tanımlanmıştır:
# beklenmedik hataların görünümü
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
- 49. satır: Görüntülenecek görünüm belirlendikten sonra, bu görünümün modelini oluşturan sınıf alınır. Bu sınıf da [config] yapılandırmasında bulunur;
- satır 50: bu sınıf bulunduktan sonra, görünümün modeli oluşturulur;
- satır 52: V görünümünün M modeli hesaplandıktan sonra, görünümün HTML kodunu oluşturabiliriz;
- satır 54-55: HTML gövdesi ile HTTP yanıtı oluşturulur;
- satır 56-57: HTTP yanıtı, durum koduyla birlikte döndürülür;
32.5.7. [Postman] Testleri
Oturum açma ekranını görüntüleyen [700, 201] kodlarını üreten sorguları çalıştıracağız:
- [init-session-html-700]: 700, [init-session] eyleminin başarıyla tamamlanmasının ardından elde edilen durum kodudur; bu durumda boş kimlik doğrulama formu görüntülenir;
- [authentifier-utilisateur-201]: 201, [authentifier-utilisateur] eyleminin başarısızlıkla sonuçlanmasının ardından (kimlik bilgileri tanınmadı) verilen durum kodudur: bu durumda, düzeltilmesi için kimlik doğrulama formu görüntülenir;
Bunları tekrar kullanıp kimlik doğrulama ekranının düzgün bir şekilde görüntülenip görüntülenmediğini kontrol etmek yeterlidir. Burada iki durum gösterilmektedir:
Durum 1: [init-session-html-700], HTML oturumunun başlangıcı;

Cevap şöyledir:

- [5]'te, [Preview] modu, alınan HTML sayfasını görüntülemeye olanak tanır;
- [6]'te beklendiği gibi boş form görünüyor;
- [7]'te, Postman sayfanın resim bağlantısını takip etmemiştir;
- [8]'te, [Raw] modu, alınan HTML'e erişim sağlar;

- [3]'te, Postman'ın yüklemediği bağlantı. Resim yüklenemediğinde görüntülenen [alt=alternative] özniteliğinin değerini gösterdi. Burada ise Postman'ın resmi yüklemek istemediği anlaşılıyor. Bunu, Postman ile URL ve [http://localhost :5000/static/images.logo.jpg]'i sorgulayarak doğrulayabiliriz:
Durum 2: [authentifier-utilisateur-201], hatalı kimlik doğrulama

Şimdi, HTML oturumunun başarılı bir şekilde başlatılmasının ardından hatalı bir kimlik doğrulama yapalım:

Yukarıda:
- [4,7]'te: istek, [user=bernard&password=thibault] dizesini gönderir;
Yanıt şu şekildedir:

- [4]'te bir hata mesajı görüntülenir;
- [3]'te, hatalı kullanıcı yeniden görüntülenir;
32.5.8. Sonuç
Diğer görünümleri yazmadan [vue-authentification.html] görünümünü test edebildik. Bunun mümkün olmasının nedeni şudur:
- tüm denetleyiciler yazılmış;
- [Postman], tüm görünümler olmadan da sunucuya istek göndermemize olanak tanır. Denetleyicileri yazarken, hiçbir görünümün izin vermeyeceği istekleri işleme hazırlıklı olmak gerekir. Asla önceden “bu istek imkansız” diye düşünmemeliyiz. Kontrol etmek gerekir;
32.6. Vergi hesaplama görünümü

32.6.1. Görünümün tanıtımı
Vergi hesaplama görünümü şöyledir:

Görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.html] parçası tarafından oluşturulur;
- 2: [v-calcul-impot.html] parçası tarafından oluşturulan vergi hesaplama formu;
- 3: [v-menu.html] parçası tarafından oluşturulan, iki bağlantı içeren bir menü;
Vergi hesaplama görünümü, aşağıdaki [vue-calcul-impot.html] kodu tarafından oluşturulur:
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- başlık çubuğu -->
{% include "fragments/v-bandeau.html" %}
<!-- iki sütunlu satır -->
<div class="row">
<!-- menü -->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- hesaplama formu -->
<div class="col-md-9">
{% include "fragments/v-calcul-impot.html" %}
</div>
</div>
<!-- başarı durumu -->
{% if modèle.success %}
<!-- başarı uyarısı görüntülenir -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
{{modèle.impôt}}</br>
{{modèle.décôte}}</br>
{{modèle.réduction}}</br>
{{modèle.surcôte}}</br>
{{modèle.taux}}</br>
</div>
</div>
</div>
{% endif %}
{% if modèle.error %}
<!-- 9 sütunlu hata listesi -->
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>{{modèle.erreurs | safe}}</ul>
</div>
</div>
</div>
{% endif %}
</div>
</body>
</html>
Yorumlar
- Yalnızca henüz karşılaşılmamış yenilikler hakkında yorum yapıyoruz;
- 16. satır: Görünümün üst başlığının, görünümün ilk Bootstrap satırına eklenmesi;
- 21. satır: Görünümün ikinci Bootstrap satırında (18., 20. satırlar) üç sütun kaplayacak menünün eklenmesi;
- 25. satır: görünümün ikinci Bootstrap satırında (18. satır) dokuz sütun (24. satır) kaplayacak vergi hesaplama formunun eklenmesi;
- satır 30-46: [modèle.success=True] vergi hesaplaması başarılı olursa, vergi hesaplamasının sonucu yeşil bir çerçeve içinde görüntülenir (satır 37-43). Bu çerçeve, görünümün üçüncü Bootstrap satırında (satır 32) yer alır ve üç boş sütunun (satır 33-35) sağında dokuz sütun (satır 36) kaplar. Dolayısıyla bu çerçeve, vergi hesaplama formunun altında yer alacaktır;
- satır 48-61: [modèle.error=True] vergi hesaplaması başarısız olursa, pembe bir çerçeve içinde bir hata mesajı görüntülenir (satır 55-58). Bu çerçeve, görünümün üçüncü Bootstrap satırında (satır 50) yer alır ve üç boş sütunun (satır 51-53) sağında dokuz sütun (satır 54) kaplar. Dolayısıyla bu çerçeve de vergi hesaplama formunun altında yer alacaktır;
32.6.2. [v-calcul-impot.html] parçası
[v-calcul-impot.html] parçası, web uygulamasının vergi hesaplama formunu görüntüler:
[v-calcul-impot.html] parçasının kodu şöyledir:

<!-- HTML formu gönderildi -->
<form method="post" action="/calculer-impot">
<!-- mavi arka planlı 12 sütunlu mesaj -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- form öğeleri -->
<fieldset class="form-group">
<!-- 9 sütunlu ilk satır -->
<div class="row">
<!-- 4 sütunlu başlık -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- 5 sütunlu radyo düğmeleri-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" {{modèle.checkedOui}}>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" {{modèle.checkedNon}}>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- 9 sütunlu ikinci satır -->
<div class="form-group row">
<!-- 4 sütunlu etiket -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- 5 sütunlu çocuk sayısı sayısal giriş alanı -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="{{modèle.enfants}}" required>
</div>
</div>
<!-- 9 sütunlu üçüncü satır -->
<div class="form-group row">
<!-- 4 sütunlu açıklama -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
<!-- 5 sütunlu maaş için sayısal giriş alanı -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel net imposable" aria-describedby="salaireHelp" value="{{modèle.salaire}}" required>
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- dördüncü satır, 5 sütunlu [submit] düğmesi -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Açıklamalar
- 2. satır: HTML formu, [method] özniteliği aracılığıyla URL'e gönderilecektir ([/calculer-impot] ([action] özniteliği). Gönderilen değerler, giriş alanlarının değerleri olacaktır:
- işaretlenen radyo düğmesinin değeri şu şekilde olacaktır:
- [marié=oui] (eğer [Oui] radyo düğmesi işaretlenmişse (satır 17-22). [marié], 18. satırdaki [name] özniteliğinin değeridir; [oui] ise 18. satırdaki [value] özniteliğinin değeridir;
- [marié=non], [Non] radyo düğmesi işaretlendiğinde (satır 23-28). [marié], 24. satırdaki [name] özniteliğinin değeridir; [non] ise 24. satırdaki [value] özniteliğinin değeridir;
- 37. satırdaki sayısal giriş alanının değeri [enfants=xx] biçimindedir; burada [enfants], 37. satırdaki [name] özniteliğinin değeridir, ve [xx], kullanıcı tarafından klavyeyle girilen değerdir;
- 46. satırdaki sayısal giriş alanının değeri [salaire=xx] biçimindedir; burada [salaire], 46. satırdaki [name] özniteliğinin değeridir, ve [xx], kullanıcı tarafından klavyeden girilen değerdir;
Sonuç olarak, kaydedilen değer [marié=xx&enfants=yy&salaire=zz] şeklinde olacaktır.
- (devam)
- kullanıcı 53. satırdaki [submit] türündeki düğmeye tıkladığında girilen değerler gönderilecektir;
- 16-30. satırlar: iki radyo düğmesi:
![]()
Bu iki radyo düğmesi, aynı [name] özniteliğine sahip oldukları için (satır 18, 24) aynı radyo düğmesi grubuna aittir. Tarayıcı, bir radyo düğmesi grubunda herhangi bir anda yalnızca bir tanesinin işaretli olmasını sağlar. Dolayısıyla, birine tıklandığında, önceden işaretli olan diğerinin seçimi kaldırılır;
- [type="radio"] özniteliği nedeniyle (18. ve 24. satırlar) bunlar radyo düğmeleridir;
- Formun görüntülenmesinde (veri girişi öncesinde), radyo düğmelerinden biri işaretlenmiş olmalıdır: bunun için ilgili <input type="radio"> etiketine [checked=’checked’] özniteliğini eklemek yeterlidir. Bu, dinamik değişkenler kullanılarak gerçekleştirilir:
- 18. satırda [modèle.checkedOui];
- 24. satırda [modèle->checkedNon];
Bu değişkenler görünüm şablonunun bir parçası olacaktır.
- 37. satır: minimum değeri 0 olan bir sayısal giriş alanı [type="number"]. Güncel tarayıcılarda bu, kullanıcının yalnızca >=0 olan bir sayı girebileceği anlamına gelir. Aynı güncel tarayıcılarda, giriş, yukarı veya aşağı yönde tıklanabilen bir kaydırma çubuğu ile yapılabilir. 37. satırdaki [step="1"] özniteliği, kaydırma çubuğunun 1 birimlik adımlarla çalışacağını belirtir. Bunun sonucu olarak, kaydırma çubuğu yalnızca 0'dan n'ye kadar 1'er birimlik adımlarla artan tamsayıları değer olarak kabul edecektir. Manuel giriş için bu, virgül içeren sayıların kabul edilmeyeceği anlamına gelir;
- 37. satır: Bazı ekranlarda, alt öğelerin giriş alanı bu alana en son girilen değerle önceden doldurulmalıdır. Bunun için, giriş alanında görüntülenecek değeri belirleyen [value] özniteliği kullanılır. Bu değer dinamik olacak ve [modèle.enfants] değişkeni tarafından oluşturulacaktır;
- 37. satır: [required] özniteliği, formun onaylanabilmesi için kullanıcının bir veri girmesini zorunlu kılar;
- 46. satır: Maaş girişi için de çocuk girişi ile aynı açıklamalar geçerlidir;
- satır 53: [submit] türündeki düğme, girilen değerleri POST'e aktararak URL ve [/calculer-impot]'e (satır 2) gönderir;
![]()
32.6.3. [v-menu.html] parçası
Bu parça, vergi hesaplama formunun sol tarafında bir menü görüntüler:

Bu parçanın kodu şöyledir:
<!-- Bootstrap menüsü -->
<nav class="nav flex-column">
<!-- bağlantı listesinin görüntülenmesi HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
Açıklamalar
- 2-7. satırlar: HTML [nav] etiketleri, diğer belgelere yönlendirme bağlantıları içeren HTML belgesinin bir bölümünü çerçeveler;
- 5. satır: HTML [a] etiketi bir gezinme bağlantısı ekler:
- [optionMenu.url]: URL bağlantısına tıklandığında yönlendirilen hedeftir. Bu durumda tarayıcı tarafından bir [GET optionMenu.url] işlemi gerçekleştirilir. [optionMenu.url], uygulamanın [http://machine :port/chemin] kök dizininden ölçülen mutlak bir URL olacaktır. Dolayısıyla [1]'te şu bağlantı oluşturulacaktır:
- 5. satır: Parçanın [modèle.optionsMenu] şablonu şu biçimde bir liste olacaktır:
- 2. ve 7. satırlar: CSS ve [nav, flex-column, nav-link] sınıfları, menüye görünümünü veren Bootstrap sınıflarıdır;
32.6.4. Görsel test
Bu farklı öğeleri [Tests] klasöründe bir araya getiriyoruz ve [vue-calcul-impot.html] görünümü için bir test şablonu oluşturuyoruz:

[test_vue_calcul_impot] test komut dosyası şu şekilde olacaktır:
from flask import Flask, render_template, make_response
# Flask uygulaması
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Ana Sayfa URL
@app.route('/')
def index():
# sayfa verilerini şablona yerleştirme
modèle = {}
# form
modèle["checkedOui"] = ""
modèle["checkedNon"] = 'checked="checked"'
modèle["enfants"] = 2
modèle["salaire"] = 300000
# başarı mesajı
modèle["success"] = True
modèle["impôt"] = "Montant de l'impôt : 1000 euros"
modèle["décôte"] = "Décôte : 15 euros"
modèle["réduction"] = "Réduction : 20 euros"
modèle["surcôte"] = "Surcôte : 0 euros"
modèle["taux"] = "Taux d'imposition : 14 %"
# hata mesajı
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# hataların bir listesi oluşturulur HTML
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# menü
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# sayfa görüntüleniyor
return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))
# ana sayfa
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Açıklamalar
- 9-34. satırlar: [vue-calcul-impot.html] görünümünün ve [v-calcul-impot.html] ile [v-menu.html] parçalarının tüm dinamik kısımları başlatılır;
- 36. satır: [vue-calcul-impot.html] görünümü görüntülenir;
[test_vue_calcul_impot] test komut dosyasını çalıştırdığımızda şu sonuç elde edilir:
Görsel olarak elde edilen sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışılır. Ardından, bu görünümü yazım aşamasında olan web uygulamasına entegre etmeye geçilebilir.

32.6.5. Görünüm modelinin hesaplanması
Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
{
# vergi hesaplama görünümü
"états": [
# /kullanıcı-kimlik-doğrulama başarılı
200,
# /vergi-hesapla başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
Dolayısıyla, vergi hesaplama görünümünü görüntüleyen durum kodları [200, 300, 301, 800]'tir. Bu kodların anlamını bulmak için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanabiliriz:
- [authentifier-utilisateur-200]: 200, [authentifier-utilisateur] eyleminin başarıyla tamamlanmasının ardından ortaya çıkan durum kodudur; bu durumda boş vergi hesaplama formu görüntülenir;
- [calculer-impot-300]: 300, [calculer-impot] işleminin başarıyla tamamlanmasının ardından verilen durum kodudur. Bu durumda, girilen veriler ve vergi tutarı ile birlikte hesaplama formu görüntülenir. Kullanıcı daha sonra başka bir hesaplama yapabilir;
- [301] durum kodu, hatalı bir vergi hesaplaması sonucunda elde edilen koddur;
- [800] durum kodu daha sonra ele alınacaktır. Henüz bu kodla karşılaşmadık;
Artık vergi hesaplama formunun hangi durumlarda görüntülenmesi gerektiğini bildiğimize göre, [ModelForCalculImpotView] sınıfında form şablonunu hesaplayabiliriz:

from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForCalculImpotView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# görünüm verilerini şablona aktarıyoruz
modèle = {}
# uygulamanın durumu
état = résultat["état"]
# şablon duruma bağlıdır
if état in [200, 800]:
# boş bir formun ilk görüntülenmesi
modèle["success"] = False
modèle["error"] = False
modèle["checkedNon"] = 'checked="checked"'
modèle["checkedOui"] = ""
modèle["enfants"] = ""
modèle["salaire"] = ""
elif état == 300:
# hesaplamanın başarılı olması - sonucun görüntülenmesi
modèle["success"] = True
modèle["error"] = False
modèle["impôt"] = f"Montant de l'impôt : {résultat['réponse']['impôt']} euros"
modèle["décôte"] = f'Décôte : {résultat["réponse"]["décôte"]} euros'
modèle["réduction"] = f"Réduction : {résultat['réponse']['réduction']} euros"
modèle["surcôte"] = f'Surcôte : {résultat["réponse"]["surcôte"]} euros'
modèle["taux"] = f"Taux d'imposition : {résultat['réponse']['taux'] * 100} %"
# form, girilen değerlerle yeniden oluşturuldu
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
elif état == 301:
# hata oluştu - form, girilen değerlerle yeniden yüklendi
modèle["checkedOui"] = 'checked="checked"' if request.form.get("marié") == "oui" else ""
modèle["checkedNon"] = 'checked="checked"' if request.form.get("marié") == "non" else ""
modèle["enfants"] = request.form.get("enfants")
modèle["salaire"] = request.form.get("salaire")
# hata
modèle["success"] = False
modèle["error"] = True
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# menü seçenekleri
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# şablon geri yükleniyor
return modèle
Açıklamalar
- 12. satır: Görüntülenecek görünüm, denetleyici tarafından döndürülen durum koduna bağlıdır;
- 14-21. satırlar: Boş bir formun görüntülenmesi;
- 22-35. satırlar: vergi hesaplamasının başarılı olması durumu. Girilen değerler ve vergi tutarı yeniden görüntülenir;
- 36-47. satırlar: vergi hesaplamasının başarısız olması durumu;
- satır 49-52: menüdeki iki seçeneğin hesaplanması;
32.6.6. [Postman] Testleri
[init-session-html-700] isteği ile bir HTML oturumu başlatılır, ardından [authentifier-utilisateur-200] isteği ile kimlik doğrulaması yapılır. Ardından şu [calculer-impot-300] isteği kullanılır:
Sunucudan gelen yanıt şöyledir:


Şimdi şu [calculer-impot-301] isteğini deneyelim:

Sunucunun yanıtı şöyledir:
Şimdi beklenmedik bir durumu deneyelim; POST'te parametrelerin eksik olduğu durumu. Bu durum, uygulamanın normal işleyişinde mümkün değildir. Ancak herkes, şu anda yaptığımız gibi bir HTTP isteğini "uyarlayabilir":


- [6]'te, [marié]'te gönderilen parametrenin işaretini kaldırdık;
Sunucunun yanıtı şöyledir:

- [3]'e dönüştü; bu, sunucunun hata mesajıdır;
Bu uygulamada bir seçeneğimiz vardı. Bu hata durumuna, beklenmedik hatalar sayfasını yönlendiren bir durum kodu atayabilirdik. Bu uygulamada her denetleyici için iki durum kodu seçtik:
- [xx0]: başarılı işlem için;
- [xx1]: başarısızlık için;
Başarısızlık durumlarında, hataları daha ayrıntılı bir şekilde yönetmek için durum kodlarını çeşitlendirebiliriz. Örneğin şunları kullanabilirdik:
- [xx1]: hatayı oluşturan sayfada görüntülenecek hatalar için;
- [xx2]: uygulamanın normal kullanımı sırasında ortaya çıkan beklenmedik hatalar için;
32.7. Simülasyon listesi görünümü

32.7.1. Görünümün tanıtımı
Simülasyonların listesini gösteren görünüm şöyledir:

[vue-liste-simulations.html] kodu tarafından oluşturulan görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.html] parçası tarafından oluşturulur;
- 3: [v-liste-simulations.html] parçası tarafından oluşturulan simülasyon tablosu;
- 2: Daha önce tanıtılan [v-menu.html] parçası tarafından oluşturulan, iki bağlantı içeren bir menü;
Simülasyonlar sayfası, aşağıdaki [vue-liste-simulations.html] kodu ile oluşturulur:
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- başlık çubuğu -->
{% include "fragments/v-bandeau.html" %}
<!-- iki sütunlu düzen -->
<div class="row">
<!-- üç sütunlu menü-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- 9 sütunlu simülasyon listesi-->
<div class="col-md-9">
{% include "fragments/v-liste-simulations.html" %}
</div>
</div>
</div>
</body>
</html>
Açıklamalar
- 16. satır: [1] uygulama başlığının eklenmesi;
- 21. satır: [2] menüsünün eklenmesi. Bu menü, başlığın altında üç sütun halinde görüntülenecektir;
- 26. satır: [3] simülasyon tablosunun eklenmesi. Bu tablo, başlığın altında ve menünün sağında dokuz sütun halinde görüntülenecektir;
Bu görünümdeki üç parçadan ikisini daha önce yorumlamıştık:
[v-liste-simulations.html] parçası şöyledir:
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- mavi arka planlı mesaj -->
<div class="alert alert-primary" role="alert">
<h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- mavi arka planlı mesaj -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- simülasyon tablosu -->
<table class="table table-sm table-hover table-striped">
<!-- tablonun altı sütununun başlıkları -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- tablonun gövdesi (görüntülenen veriler) -->
<tbody>
<!-- simülasyon tablosunu tarayarak her bir simülasyon görüntülenir -->
{% for simulation in modèle.simulations %}
<!-- 6 sütunlu bir tablo satırının görüntülenmesi - <tr> etiketi -->
<!-- 1. sütun: satır başlığı (simülasyon numarası) - <th scope='row'> etiketi -->
<!-- 2. sütun: parametre değeri [marié] - <td> etiketi -->
<!-- 3. sütun: [enfants] parametre değeri - <td> etiketi -->
<!-- 4. sütun: [salaire] parametre değeri - <td> etiketi -->
<!-- 5. sütun: [impôt] parametre değeri (vergi) - <td> etiketi -->
<!-- 6. sütun: [surcôte] parametre değeri - <td> etiketi -->
<!-- 7. sütun: [décôte] parametre değeri - <td> etiketi -->
<!-- 8. sütun: [réduction] parametre değeri - <td> etiketi -->
<!-- 9. sütun: [taux] parametre değeri (vergi) - <td> etiketi -->
<!-- 10. sütun: simülasyonu silme bağlantısı - <td> etiketi -->
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="/supprimer-simulation/{{simulation.id}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Yorumlar
- HTML tablosu, <table> etiketiyle oluşturulmuştur (15. ve 62. satırlar);
- tablonun sütun başlıkları <thead> etiketi içinde yer almaktadır (satır 17, 30). <tr> etiketi (table row, satır 18 ve 29) bir satırı sınırlar. Satır 19-28'de, <th> etiketi (table header) bir sütun başlığını tanımlar. Dolayısıyla on tane vardır. [scope="col"], başlığın sütuna uygulandığını gösterir. [scope="row"], başlığın satıra uygulandığını gösterir;
- 32-61. satırlar: <tbody> etiketi, tablo tarafından görüntülenen verileri çerçeveler;
- 47-58. satırlar: <tr> etiketi, tablonun bir satırını çerçeveler;
- 48. satır: <th scope=’row’> etiketi, satırın başlığını tanımlar. Tarayıcı bu başlığı vurgular;
- 49-57. satırlar: Her bir <td> etiketi (tablo verisi), satırın bir sütununu tanımlar;
- satır 34: Simülasyon listesi, bir sözlük listesi olan [modèle.simulations] şablonunda bulunur;
- satır 57: simülasyonu silmek için bir bağlantı. URL, satırda görüntülenen simülasyon numarasını kullanır;
32.7.2. Görsel test
[vue-liste-simulations.html] görünümü için bir test komut dosyası oluşturuyoruz:
[test_vue_liste_simulations] komut dosyası şu şekildedir:

from flask import Flask, make_response, render_template
# Flask uygulaması
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Ana Sayfa URL
@app.route('/')
def index():
# sayfanın verilerini şablona yerleştiriyoruz
modèle = {}
# simülasyonları sayfanın beklediği biçime getiriyoruz
modèle["simulations"] = [
{
"id": 7,
"marié": "oui",
"enfants": 2,
"salaire": 60000,
"impôt": 448,
"décôte": 100,
"réduction": 20,
"surcôte": 0,
"taux": 0.14
},
{
"id": 19,
"marié": "non",
"enfants": 2,
"salaire": 200000,
"impôt": 25600,
"décôte": 0,
"réduction": 0,
"surcôte": 8400,
"taux": 0.45
}
]
# menü
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# sayfanın görüntülenmesi
return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))
# ana sayfa
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Açıklamalar
- 12-35. satırlar: modele iki simülasyon ekliyoruz
- 37-39. satırlar: menü seçenekleri tablosu;
Bu komut dosyasını çalıştırarak bu görünümü görüntüleyelim. Aşağıdaki sonuç elde edilir:

Görsel olarak elde ettiğimiz sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışırız. Ardından, yazmakta olduğumuz web uygulamasına bu görünümü entegre etmeye geçebiliriz.
32.7.3. Görünüm modelinin hesaplanması
Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:

{
# simülasyon listesinin görünümü
"états": [
# /simülasyonları-listele
500,
# /simülasyonu-sil
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
Dolayısıyla, simülasyon görünümünü görüntüleyen kodlar [500, 600]’tir. Bu kodların anlamını öğrenmek için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanılabilir:
- [lister-simulations-500]: 500, [lister-simulations] eyleminin başarıyla tamamlanmasının ardından ortaya çıkan durum kodudur; bu durumda, kullanıcı tarafından gerçekleştirilen simülasyonların listesi görüntülenir;
- [supprimer-simulation-600]: 600, [supprimer-simulation] eyleminin başarıyla tamamlanmasının ardından verilen durum kodudur. Bu silme işleminin ardından elde edilen yeni simülasyon listesi görüntülenir;
Artık simülasyon listesinin hangi durumlarda görüntülenmesi gerektiğini bildiğimize göre, [ModelForListeSimulationsView] sınıfında bu listenin modelini hesaplayabiliriz:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForListeSimulationsView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# sayfa verilerini şablona yerleştiriyoruz
modèle = {}
# simülasyonlar, eylemi gerçekleştiren denetleyicinin yanıtında bulunur
# sözlüklerden oluşan bir tablo biçiminde TaxPayer
modèle["simulations"] = résultat["réponse"]
# menü
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# şablon oluşturulur
return modèle
Açıklamalar
- 13. satır: Görüntülenecek simülasyonlar [["réponse"] sonucu] içinde bulunur;
- 15-17. satırlar: görüntülenecek menü seçenekleri;
32.7.4. [Postman] Testleri
Burada
- HTML oturumu başlatılır;
- kimlik doğrulaması yapılır;
- üç vergi hesaplaması yapılıyor;
[lister-simulations-500] testi, 500 durum kodunu almamızı sağlar. Bu, simülasyonları görüntüleme talebine karşılık gelir:

Sunucudan gelen yanıt şöyledir:

[supprimer-simulation-600] testi, durum kodu 600'ü almamızı sağlar. Burada 2 numaralı simülasyonu sileceğiz.
Geri gönderilen sonuç, bir simülasyonun çıkarıldığı simülasyon listesidir:


32.8. Beklenmedik hataların görüntülenmesi
Burada, web uygulamasının normal kullanımı kapsamında meydana gelmemesi gereken bir hataya "beklenmedik hata" diyoruz. Örneğin, kimlik doğrulaması yapılmadan vergi hesaplaması talep edilmesi gibi. Bir kullanıcının tarayıcısına doğrudan URL [/calcul-impot] yazmasını engelleyen hiçbir şey yoktur. Ayrıca, daha önce gördüğümüz gibi, beklenen parametreleri göndermeyerek URL [/calcul-impot] adresine POST şeklinde bir istek gönderebilir. Web uygulamamızın bu isteğe doğru şekilde yanıt verebildiğini gördük. HTML uygulaması kapsamında meydana gelmemesi gereken bir hataya “beklenmedik hata” adını vereceğiz. Böyle bir hata meydana gelirse, muhtemelen birisi uygulamayı “hacklemeye” çalışıyordur. Eğitim amaçlı olarak, bu tür durumlar için bir hata sayfası görüntülemeye karar verdik. Gerçekte, müşteriye gönderilen son sayfayı yeniden görüntüleyebiliriz. Bunun için, gönderilen son HTML yanıtını oturumda kaydetmek yeterlidir. Beklenmedik bir hata durumunda, bu yanıtı geri göndeririz. Böylece, görüntülenen sayfa değişmediği için kullanıcı, sunucunun hatalarına yanıt vermediğini düşünür.
32.8.1. Görünümün Sunumu

Beklenmedik hataları gösteren görünüm şöyledir:

[vue-erreurs.html] kodu tarafından oluşturulan görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.html] parçası tarafından oluşturulur;
- 2: Beklenmedik hata veya hatalar;
- 3: Daha önce tanıtılan [v-menu.html] parçası tarafından oluşturulan, üç bağlantı içeren bir menü;
Beklenmedik hataların görüntüsü, aşağıdaki [vue-erreurs.html] komut dosyası tarafından oluşturulur:
<!-- belge HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Application impôts</title>
</head>
<body>
<div class="container">
<!-- 12 sütunlu başlık -->
{% include "fragments/v-bandeau.html" %}
<!-- iki bölümlü satır -->
<div class="row">
<!-- 3 sütunlu menü-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- 9 sütunlu hata listesi -->
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>{{modèle.erreurs|safe}}</ul>
</div>
</div>
</div>
</div>
</body>
</html>
Açıklamalar
- 16. satır: [1] uygulamasının başlık çubuğunun eklenmesi;
- 21. satır: [3] menüsünün eklenmesi. Bu menü, başlığın altında üç sütun halinde görüntülenecektir;
- 24-29. satırlar: hata alanının dokuz sütun halinde görüntülenmesi;
- 25. satır: Bu görüntüleme, pembe arka planlı bir Bootstrap çerçeve içinde yapılacaktır;
- 26. satır: bir tanıtım metni;
- 27. satır: <ul> etiketi, bir madde işaretli listeyi çerçeveler. Bu madde işaretli liste, [modèle.erreurs] şablonu tarafından sağlanır;
Bu görüntünün iki parçasını daha önce yorumlamıştık:
32.8.2. Görsel test
[vue-erreurs.html] görünümü için bir test komut dosyası oluşturuyoruz:

from flask import Flask, render_template, make_response
# Flask uygulaması
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# Ana Sayfa URL
@app.route('/')
def index():
# sayfa verilerini şablona yerleştiriyoruz
modèle = {}
# hataların bir listesi oluşturulur HTML
content = ""
for erreur in ["erreur1", "erreur2"]:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# menü seçenekleri
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/calculer-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# sayfanın görüntülenmesi
return make_response(render_template("views/vue-erreurs.html", modèle=modèle))
# ana sayfa
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Yorumlar
- 11-15. satırlar: HTML hata listesinin oluşturulması;
- 17-20. satırlar: menü seçenekleri tablosu;
Bu komut dosyasını çalıştıralım. Aşağıdaki sonucu elde ederiz:
Görsel olarak elde ettiğimiz sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışırız. Ardından, yazmakta olduğumuz web uygulamasına bu görünümü entegre etmeye geçebiliriz.

32.8.3. Görünüm modelinin hesaplanması

Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
# HTML görünümleri ve şablonları, denetleyici tarafından döndürülen duruma bağlıdır
"views": [
{
# kimlik doğrulama görünümü
"états": [
# /init-session başarılı
700,
# /oturumu-sonlandır
400,
# /kullanıcı-kimlik-doğrulama başarısız
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# vergi hesaplama sayfası
"états": [
# /kullanıcı-doğrulama başarılı
200,
# /vergi-hesaplama başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# simülasyon listesini görüntüle
"états": [
# /simülasyonları-listele
500,
# /simülasyonu-sil
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# beklenmedik hataların görünümü
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
3-41. satırlardaki HTML görünümüne yönlendirmeyen durum kodları, beklenmedik hataların görünümünü görüntülemektedir.
[vue-erreurs.html] görünümünün model hesaplaması, aşağıdaki [ModelForErreursView] sınıfı tarafından yapılır:
from flask import Request
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class ModelForErreursView(InterfaceModelForView):
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# model
modèle = {}
# hatalar
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# menü
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# şablonu oluşturuyoruz
return modèle
Açıklamalar
- 11-14. satırlar: [vue-erreurs.html] görünümü tarafından kullanılan [modèle.erreurs] modelinin hesaplanması;
- satır 16-197: [v-menu.html] parçası tarafından kullanılan [modèle.optionsMenu] şablonunun hesaplanması;
32.8.4. [Postman] Testleri
Şu işlem yapılır:
- [/init-session/html] eylemi;
- ardından [/init-session/x] eylemi;
HTML yanıtı şu şekildedir:

32.9. Uygulama menüsündeki eylemlerin uygulanması
Burada menü eylemlerinin uygulanmasını ele alacağız. Karşılaştığımız bağlantıların anlamlarını hatırlayalım
Görünüm | Bağlantı | Hedef | Rol |
Vergi Hesaplama | [Liste des simulations] | [/lister-simulations] | Simülasyon listesini talep et |
[Fin de session] | [/fin-session] | ||
Simülasyon listesi | [Calcul de l’impôt] | [/afficher-calcul-impot] | Vergi hesaplama görünümünü görüntüle |
[Fin de session] | [/fin-session] | ||
Beklenmedik hatalar | [Calcul de l’impôt] | [/afficher-calcul-impot] | Vergi hesaplama görünümünü görüntüle |
[Liste des simulations] | [/lister-simulations] | ||
[Fin de session] | [/fin-session] |
Bir bağlantıya tıklandığında, bağlantının hedefine yönlendiren bir GET eylemi tetiklendiğini hatırlatmak gerekir. [/lister-simulations, /fin-session] eylemleri, bir GET işlemiyle uygulanmıştır; bu sayede bunları bağlantı hedefleri olarak belirleyebiliyoruz. Eylem bir POST ile gerçekleştirildiğinde, JavaScript ile entegre edilmedikçe bağlantı kullanımı artık mümkün değildir.
32.9.1. [/afficher-calcul-impot] hisse senedi
Yukarıdaki işlemlerden, [/afficher-calcul-impot] işleminin henüz uygulanmadığı anlaşılmaktadır. Bu, iki görünüm arasında gezinme işlemidir: jSON veya XML sunucularının bunu uygulaması için hiçbir neden yoktur, çünkü bu sunucularda görünüm kavramı yoktur. Bu kavramı getiren HTML sunucusudur.
Bu nedenle [/afficher-calcul-impot] eylemini uygulamamız gerekiyor. Bu, sunucu içinde bir eylemin uygulanma sürecini gözden geçirmemizi sağlayacaktır.
Öncelikle, yeni bir ikincil denetleyici eklememiz gerekiyor. Buna [AfficherCalculImpotController] adını vereceğiz:

Bu denetleyici, [config] yapılandırma dosyasına eklenmelidir:
# denetleyiciler
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
…
# izin verilen eylemler ve bunların denetleyicileri
"controllers": {
# hesaplama oturumunun başlatılması
"init-session": InitSessionController(),
# bir kullanıcının kimlik doğrulaması
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# bireysel modda vergi hesaplaması
"calculer-impot": CalculerImpotController(),
# toplu modda vergi hesaplaması
"calculer-impots": CalculerImpotsController(),
# simülasyon listesi
"lister-simulations": ListerSimulationsController(),
# simülasyon silme
"supprimer-simulation": SupprimerSimulationController(),
# hesaplama oturumunun sonlandırılması
"fin-session": FinSessionController(),
# vergi hesaplama görünümünün görüntülenmesi
"afficher-calcul-impot": AfficherCalculImpotController(),
# vergi dairesinden verilerin alınması
"get-admindata": GetAdminDataController(),
# ana denetleyici
"main-controller": MainController()
},
…
# HTML görünümleri ve bunların şablonları, denetleyicinin döndürdüğü duruma bağlıdır
"views": [
{
# kimlik doğrulama görünümü
…
},
{
# vergi hesaplama sayfası
"états": [
# /kullanıcı-kimlik-doğrulama başarılı
200,
# /vergi-hesapla başarılı
300,
# /vergi-hesapla başarısız
301,
# /vergi-hesaplamasını-göster
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{…
}
],
- 2. satır: yeni denetleyici;
- 28. satır: yeni eylem ve kontrolörü;
- 51. satır: yeni denetleyici 800 durum kodunu döndürecektir. Görünüm değiştiğinde hata oluşmamalıdır. Görüntülenen görünüm, incelediğimiz, açıkladığımız ve test ettiğimiz [vue-calcul-impot.html] görünümüdür;
[AfficherCalculImpotController] denetleyicisi şu şekilde olacaktır:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class AfficherCalculImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# yolun öğeleri alınır
dummy, action = request.path.split('/')
# görünüm değişikliği - sadece bir durum kodu ayarlanması gerekiyor
return {"action": action, "état": 800, "réponse": ""}, status.HTTP_200_OK
Açıklamalar
- 6. satır: Diğer ikincil denetleyiciler gibi, yeni denetleyici de [InterfaceController] arayüzünü uygular;
- 13. satır: Görünüm değişiklikleri uygulaması kolaydır: Hedef görünüme ilişkin bir durum kodu döndürmek yeterlidir; burada, yukarıda görüldüğü gibi 800 kodu kullanılır;
32.9.2. [/fin-session] eylemi
[/fin-session] eylemi özel bir durumdur. Bu eylem doğrudan bir görünüme değil, bir yönlendirmeye götürür. Yönlendirmelerin, [config] yapılandırmasında şu şekilde ayarlandığını hatırlayalım:
# yönlendirmeler
"redirections": [
{
"états": [
400, # /oturum sonlandırma başarılı
],
# şuraya yönlendirme
"to": "/init-session/html",
}
],
Uygulamada tek bir yönlendirme vardır:
- denetleyici [400] durum kodunu döndürdüğünde (5. satır), istemciyi URL [http://machine:port/chemin/init-session/html] adresine yönlendirmek gerekir (8. satır);
[400] durum kodu, [/fin-session] eyleminin başarıyla tamamlanmasının ardından döndürülen koddur. Öyleyse neden istemciyi URL ve [/init-session/html] durum kodlarına yönlendirmek gerekiyor? Çünkü [/fin-session] eyleminin kodu, web oturumunda bulunan oturum türünü siler. Bu durumda, bir HTML oturumunda olduğumuz artık bilinmez. Bu nedenle yeniden yönlendirme yapılması gerekir. Bu, [/init-session/html] eylemi kullanılarak gerçekleştirilir.
HTML yönlendirmeleri, [HtmlResponse] sınıfı tarafından yönetilir:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# HTML yanıtı, denetleyici tarafından döndürülen durum koduna bağlıdır
état = résultat["état"]
# Yönlendirme yapılmalı mı?
for redirection in config["redirections"]:
# yönlendirme gerektiren durumlar
états = redirection["états"]
if état in états:
# yönlendirme yapılmalıdır
return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND
# her duruma bir görünüm karşılık gelir
# bu görünüm, görünümler listesinde aranır
..
- 6-12. satırlar yönlendirmeleri işler;
- 7. satır: config[‘redirections’], yönlendirmelerin bir listesidir. Her yönlendirme, aşağıdaki anahtarları içeren bir sözlükten oluşur:
- [états]: denetleyici tarafından döndürülen ve bir yönlendirmeye yol açan durumlar;
- [to]: yönlendirme adresi;
- 7-12. satırlar: yönlendirme listesi taranır;
- 9. satır: her yönlendirme için, ona yol açan durumlar alınır;
- 10. satır: Test edilen durum bu listede yer alıyorsa, yönlendirme yapılır (12. satır);
- 12. satır: [build_http_response] yönteminin iki elemanlı bir tuple döndürmesi gerektiği hatırlatılır:
- [response]: Verilecek HTTP yanıtı. Bu yanıt, parametresi yönlendirme adresi olan [redirect] işleviyle oluşturulur;
- [status_code]: HTTP yanıtının durum kodu; burada, istemciye yeniden yönlendirilmesi gerektiğini belirten [status.HTTP_302_FOUND] kodu;
[Postman] ile bir test yapalım. Şu adımları uygulayalım:
- bir oturum başlatıyoruz: HTML [init-session/html];
- kimlik doğrulaması yapıyoruz: [/authentifier-utilisateur];
- [/fin-session] oturumunu sonlandırıyoruz;

Sunucudan gelen yanıt şöyledir:

Kimlik doğrulama ekranı görüntülendi. Tam da beklediğimiz ekran bu. Şimdi, bu ekranın nasıl elde edildiğine bir bakalım. [Postman] konsoluna geçelim (Ctrl-Alt-C):

- [1]'te, [/fin-session] eylemi;
- [2-3]'e, sunucu tarafından döndürülen HTTP durum kodu 302, istemciye yeniden yönlendirileceğini bildirir;
- [4]'te, [Postman] istemcisi yönlendirmeye uyar;
32.10. HTML uygulamasının gerçek koşullarda test edilmesi
Kod yazıldı ve her eylem [Postman] ile test edildi. Geriye, gerçek ortamda sayfa akışını test etmek kalıyor. HTML oturumunu başlatmanın bir yoluna ihtiyacımız var. Sunucuya [/init-session/html] isteğini göndermemiz gerektiğini biliyoruz. Bu pek kullanışlı bir URL değil. URL ve [/] ile başlamayı tercih ederiz.
Ana komut dosyası [main]’e şu yolu yazdık:
from flask import request, Flask, session, url_for, redirect
…
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# /init-session/html adresine yönlendirme
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
…
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# eylemle ilişkili denetleyici çalıştırılır
return front_controller()
- 4-7. satırlar: [/] yolunun yönetimi. Web uygulamasının giriş noktası URL[/init-session/html] olacaktır (10. satır). Ayrıca 7. satırda, istemciyi bu URL adresine yönlendiriyoruz:
- [url_for] işlevi 1. satırda içe aktarılmıştır. Bu işlevin burada iki parametresi vardır (7. satır):
- 1. parametre, yönlendirme fonksiyonlarından birinin adıdır; burada 11. satırdaki fonksiyon. Bu fonksiyonun, müşterinin istediği yanıt türünü (json, xml, html) belirten [type_response] parametresini beklediği görülmektedir;
- ikinci parametre, 11. satırdaki parametrenin adını ([type_response]) alır ve ona bir değer atar. Başka parametreler olsaydı, bu işlem her biri için tekrarlanırdı;
- bu işlem, kendisine verilen iki parametre ile belirtilen fonksiyona atanan URL değerini döndürür. Burada, parametrenin [/init-session/html] değeriyle değiştirildiği 10. satırdaki URL değeri elde edilir;
- [redirect] işlevi 1. satırda içe aktarılmıştır. Bu işlevin görevi, istemciye bir HTTP yönlendirme başlığı göndermektir:
- 1. parametre, müşterinin yönlendirilmesi gereken URL adresidir;
- 2. parametre, müşteriye verilen HTTP yanıtının durum kodudur. [status.HTTP_302_FOUND] kodu, bir HTTP yönlendirmesine karşılık gelir;
Hazırız. Şimdi birkaç sayfa akışını göstereceğiz.
Tarayıcımızda istek izlemeyi etkinleştiriyoruz (Chrome, Firefox, Edge'de F12) ve [http://localhost:5000/] başlatma isteği olan URL'i gönderiyoruz. Sunucunun yanıtı şöyledir:

İstemci ile sunucu arasında gerçekleşen ağ iletişimine bakıldığında:

- [4, 5] adresinde, tarayıcının URL adresine yönlendirme isteği aldığını görüyoruz;
Aldığımız formu dolduralım;

Ardından birkaç simülasyon yapalım:


Simülasyonların listesini isteyelim:

İlk simülasyonu silelim:

Oturumu sonlandıralım:

Okuyucu, başka testler de yapmaya davet edilir.