32. حالت HTML در نسخه ۱۲
ما در ابتدای نسخه ۱۲ اشاره کرده بودیم که برنامه را در چند مرحله توسعه خواهیم داد. ما نوشته بودیم:
- بر اساس دیدگاههای برنامه HTML، اقداماتی را که برنامه وب باید پیادهسازی کند، تعریف خواهیم کرد. در اینجا از دیدگاههای واقعی استفاده خواهیم کرد، اما اینها میتوانند به سادگی دیدگاههایی روی کاغذ باشند؛
- بر اساس این اقدامات، سرویس URL را برای برنامه HTML تعریف خواهیم کرد؛
- ما این نقاط پایانی سرویس را با استفاده از یک سرور که صفحات وب را ارائه میدهد، پیادهسازی خواهیم کرد. این به ما امکان میدهد تا چارچوب سرور وب را بدون نگرانی در مورد صفحات خاصی که باید ارائه شوند، تعریف کنیم. ما این سرویسهای URL را با استفاده از Postman آزمایش خواهیم کرد؛
- سپس سرور jSON خود را با استفاده از یک کلاینت کنسول آزمایش خواهیم کرد؛
- پس از اعتبارسنجی سرور jSON، به نوشتن برنامه HTML میپردازیم؛
سرورهای jSON و XML راهاندازی و در حال اجرا هستند. اکنون میتوانیم به سرور HTML بپردازیم. مشاهده خواهیم کرد که این سرور کل معماری توسعه یافته برای سرورهای jSON / XML را در خود جای داده و مدیریت نما (view) HTML را به آنها اضافه میکند.
32.1. معماری MVC
ما الگوی معماری موسوم به MVC (مدل–نما–کنترلکننده) را به شرح زیر پیادهسازی خواهیم کرد:
پردازش یک درخواست مشتری به شرح زیر انجام خواهد شد:
- ۱ – درخواست
درخواستهای URL به شکل http://machine:port/action/param1/param2/… خواهند بود. [Contrôleur principal] از یک فایل پیکربندی برای مسیریابی درخواست به کنترلر صحیح استفاده خواهد کرد. برای این کار، از فیلد [action] در URL استفاده خواهد شد. باقیمانده URL و [param1/param2/…] شامل پارامترهای اختیاری است که به اکشن ارسال خواهند شد. حرف C در MVC در این مورد، رشته [Contrôleur principal, Contrôleur / Action] است. اگر هیچ کنترولری نتواند اقدام درخواستی را مدیریت کند، وبسرور پاسخ خواهد داد که URL درخواستی یافت نشد.
- ۲ – پردازش
- عمل انتخابشده [2a] میتواند از پارامترهای parami که توسط [Contrôleur principal] به آن ارسال شدهاند، استفاده کند. این پارامترها ممکن است از دو منبع زیر آمده باشند:
- مسیر [/param1/param2/…] از URL،
- از پارامترهای ارسالشده در بدنه درخواست کلاینت؛
- هنگام پردازش درخواست کاربر، ممکن است این اقدام به لایههای [métier] و [2b] نیاز داشته باشد. پس از پردازش درخواست مشتری، ممکن است پاسخهای مختلفی ایجاد شود. یک مثال معمول عبارت است از:
- یک پاسخ خطا اگر درخواست نتوانست به درستی پردازش شود؛
- در غیر این صورت، یک پاسخ تأیید؛
- [Contrôleur / Action] پاسخ خود، [2c]، را به همراه یک کد وضعیت به کنترلکننده اصلی بازمیگرداند. این کدهای وضعیت، وضعیت فعلی برنامه را به طور منحصربهفردی نشان میدهند. این کدها یا یک کد موفقیت یا یک کد خطا خواهند بود؛
- ۳ – پاسخ
- بسته به اینکه آیا کلاینت پاسخ jSON را درخواست کرده باشد، XML یا HTML، [Contrôleur principal]، [3a] را با نوع پاسخ مناسب ایجاد کرده و به آن دستور میدهد که پاسخ را برای کلاینت ارسال کند. [Contrôleur principal] هم پاسخ و هم کد وضعیت ارائهشده توسط [Contrôleur / Action] که اجرا شده بود را به آن منتقل میکند؛
- اگر پاسخ مورد نظر از نوع jSON یا XML باشد، پاسخ انتخابشده پاسخ ارائهشده از [Contrôleur / Action] را قالببندی کرده و از طریق [3c] ارسال میکند. کلاینتی که قادر به پردازش این پاسخ است ممکن است یک اسکریپت کنسول پایتون یا یک اسکریپت جاوااسکریپت میزبانیشده روی یک صفحه HTML باشد؛
- اگر پاسخ مورد نظر از نوع HTML باشد، پاسخ انتخابشده با استفاده از کد وضعیت ارائهشده به آن، یکی از نماهای HTML یا [Vuei] را انتخاب خواهد کرد. این V برای MVC است. هر کد وضعیت با یک نما مطابقت دارد. این نما V پاسخ اجرا شده از [Contrôleur / Action] را نمایش خواهد داد. این از HTML، CSS و جاوااسکریپت برای ارائه دادههای این پاسخ استفاده میکند. این دادهها به عنوان مدل نما (view model) شناخته میشوند. این «M» در MVC است. کلاینت معمولاً یک مرورگر وب است؛
32.2. درخت اسکریپت سرور HTML

- در [1]، عناصر ایستا سرور HTML؛
- به [2-3]، نماهای V از سرور HTML. قطعات [2] عناصر قابل استفادهی مجدد در داخل نماهای [3] هستند؛
- در [4]، پوشهای برای استفاده در تست ایستا نماها؛
- در [5]، پوشهای که حاوی M قالب برای نماهای V است، M برای MVC؛
32.3. مروری بر ویوها
برنامه وب HTML از چهار نما استفاده میکند. نمای اول نمای احراز هویت است:
- عملکردی که به این نمای اول منتهی میشود، عملکرد [/init-session] [1] است؛
- کلیک روی دکمه [Valider]، اقدام [/authentifier-utilisateur] را با دو پارامتر ارسالشده [2-3] فعال میکند؛
نمایان محاسبه مالیات:

- در [1]، اقدام [/authentifier-utilisateur] که این نما را نمایش میدهد؛
- در [2]، کلیک بر دکمه [Valider] باعث اجرای اقدام [/calculer-impot] با سه پارامتر ارسالشده [2-5] میشود؛
- کلیک روی لینک [6]، اقدام [/lister-simulations] را بدون هیچ پارامتری فعال میکند؛
- کلیک روی لینک [7] اقدام [/fin-session] را بدون هیچ پارامتری فعال میکند؛
نمای سوم شبیهسازیهای انجامشده توسط کاربر احرازشده را نشان میدهد:

- در [1]، اقدام [/lister-simulations] که به این نما منتهی میشود؛
- در [2]، کلیک بر روی لینک [Supprimer]، اقدام [/supprimer-simulation] را با یک پارامتر: شماره شبیهسازی که باید از لیست حذف شود، راهاندازی میکند؛
- کلیک بر روی لینک [3]، اقدام [/afficher-calcul-impot] را بدون هیچ پارامتری فعال میکند که نمای محاسبه مالیات را مجدداً نمایش میدهد؛
- کلیک بر روی لینک [4]، اقدام [/fin-session] را بدون هیچ پارامتری فعال میکند؛
دید چهارم «دید خطاهای غیرمنتظره» نامیده میشود:
- در [1]: کاربر خود وارد URL شده است. با این حال، در این مثال هیچ شبیهسازیای وجود نداشت. بنابراین ما پیام خطای [2] را دریافت میکنیم. ما با این پیام آشنا هستیم. ما در jSON / XML با آن مواجه شدیم. ما به این نوع خطا «خطای غیرمنتظره» میگوییم، زیرا در حین استفاده عادی از برنامه نمیتواند رخ دهد. این خطاها تنها زمانی رخ میدهند که کاربر خود کدهای URL را وارد کند؛
- در صورت وقوع یک خطای غیرمنتظره، پیوندهای [3-5] به شما امکان بازگشت به یکی از سه نمای دیگر را میدهند؛
بیایید سرویسهای مختلف URL برای سرور jSON / XML را به یاد آوریم:
اقدام | نقش | زمینهٔ اجرا |
/init-session | برای تعیین نوع (json, xml, html) پاسخهای مورد نظر استفاده میشود | درخواست GET میتوان در هر زمانی صادر شود |
/احراز-هویت-کاربر | ورود کاربر را مجاز یا رد میکند | درخواست POST. درخواست باید دارای دو پارامتر POST به نامهای [user, password] باشد فقط در صورتی قابل ارسال است که نوع جلسه (json, xml, html) مشخص باشد |
/محاسبه-مالیات | شبیهسازی محاسبه مالیات را انجام میدهد | درخواست POST. درخواست باید سه پارامتر POST داشته باشد: [marié, enfants, salaire] فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد |
/فهرست-شبیهسازیها | درخواست فهرستی از شبیهسازیهای انجامشده از ابتدای جلسه | درخواست GET. فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد |
/حذف-شبیهسازی/شماره | حذف یک شبیهسازی از فهرست شبیهسازیها | درخواست GET. فقط در صورتی قابل اجرا است که نوع جلسه (json, xml, html) مشخص باشد و کاربر احراز هویت شده باشد |
/نمایش-محاسبه-مالیات | نمایش صفحه محاسبه مالیات HTML | درخواست GET. فقط در صورتی قابل اجرا است که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد |
/end-session | پایان جلسه شبیهسازی. | از نظر فنی، جلسه وب قدیمی حذف شده و یک جلسه جدید ایجاد میشود فقط در صورتی صادر میشود که نوع جلسه (json، xml، html) مشخص باشد و کاربر احراز هویت شده باشد |
این کدهای سرویس مختلف URL نیز برای سرور HTML استفاده خواهند شد.
32.4. پیکربندی نما
یک اقدام توسط یک کنترلکننده پردازش میشود. این کنترلکننده یک تپل (result, status_code) را بازمیگرداند که در آن:
- [résultat] یک دیکشنری از کلیدهای [action, état, réponse] است؛
- [status_code] کد وضعیت پاسخ HTTP است که به کلاینت ارسال میشود؛
در یک جلسه HTML، صفحهای که پس از انجام یک اقدام نمایش داده میشود، به کد وضعیت بازگرداندهشده توسط کنترلر بستگی دارد. این وابستگی در پیکربندی [config] به شرح زیر منعکس شده است:
# نماهای HTML و قالبهای آنها به حالتی که توسط کنترلکننده بازگردانده میشود بستگی دارند
"views": [
{
#نمای احراز هویت
"états": [
#/init-session موفق بود
700,
# /authentification-کاربر شکست
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
#نمای محاسبه مالیات
"états": [
#/احراز-هویت-کاربر موفق
200,
# /محاسبه-مالیات موفق
300,
# /محاسبه-مالیات ناموفق
301,
# /نمایش-محاسبه-مالیات
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
#مشاهده فهرست شبیهسازیها
"états": [
# /لیست-شبیهسازیها
500,
# /حذف-شبیهسازی
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
#مشاهده خطاهای غیرمنتظره
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# ارسال مجدد
"redirections": [
{
"états": [
400, # /end-session-success
],
# ارسال مجدد به
"to": "/init-session/html",
}
],
}
- خطوط ۲–۴۰: [views] فهرستی از ویوها است. بیایید ویوی خطوط ۳–۱۳ را در نظر بگیریم:
- خط ۱۱: نمای V در حال نمایش؛
- خط ۱۲: نمونه کلاس مسئول تولید مدل M برای این نما؛
- خطوط ۵–۱۰: حالتهایی که به این نما منتهی میشوند؛
- خطوط ۳–۱۳: نمای احراز هویت؛
- خطوط ۱۴–۲۸: نمای محاسبه مالیات؛
- خطوط ۲۹–۳۹: نمای فهرست شبیهسازی؛
- خطوط ۴۲–۴۶: نمای خطاهای غیرمنتظره؛
- خطوط ۴۹–۵۷: برخی گزارشها از طریق یک هدایت به یک نما منتهی میشوند. این مورد برای گزارش ۴۰۰ صادق است که با اقدام موفقیتآمیز [/fin-session] مطابقت دارد. سپس مشتری باید به اقدام [http://machine:port/chemin/init-session/html] هدایت شود؛
اکنون نماهای مختلف را ارائه میکنیم.
32.5. نما نمایش احراز هویت

32.5.1. نمای کلی
نمایه احراز هویت به شرح زیر است:

این نما از دو عنصر تشکیل شده است که ما آنها را قطعات مینامیم:
- قطعه [1] توسط قطعه [v-bandeau.html] تولید میشود؛
- قطعه [2] توسط قطعه [v-authentification.html] تولید میشود؛
نمایه احراز هویت توسط صفحه زیر، [vue-authentification.html]، تولید میشود:
<!-- سند HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- برچسبهای متای مورد نیاز -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- بوتاسترپ 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">
<!-- سربرگ -->
{% include "fragments/v-bandeau.html" %}
<!-- قالب دو ستونی -->
<div class="row">
<div class="col-md-9">
{% include "fragments/v-authentification.html" %}
</div>
</div>
<!-- اگر خطایی رخ دهد – هشدار خطا را نمایش دهید -->
{% 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>
نظرات
- خط ۲: یک سند HTML با این خط آغاز میشود؛
- خطوط ۳–۳۶: صفحه HTML در میان تگهای <html> و </html> قرار دارد؛
- خطوط ۴–۱۱: بخش head سند HTML؛
- خط ۶: تگ <meta charset> در اینجا نشان میدهد که سند در UTF-8 رمزگذاری شده است؛
- خط ۷: تگ <meta name='viewport'> نمایش اولیهٔ نما را تنظیم میکند: در تمام عرض صفحهای که آن را نمایش میدهد (width) در اندازهٔ اصلی خود (initial-scale) بدون تغییر اندازه برای جا گرفتن در صفحهای کوچکتر (shrink-to-fit)؛
- خط ۹: تگ <link rel='stylesheet'> فایل CSS را مشخص میکند که ظاهر نما را کنترل میکند. در اینجا از فریمورک Bootstrap 4.4.1 CSS [https://getbootstrap.com/docs/4.0/getting-started/introduction/] استفاده میکنیم. ;
- خط ۱۰: تگ <title> عنوان صفحه را تعیین میکند:

- خطوط ۱۳–۳۵: بدنه صفحه وب بین تگهای <body> و </body> قرار گرفته است؛
- خطوط 14–34: تگ یک بخش از صفحهٔ نمایشدادهشده را محدود میکند. ویژگیهای [class] که در نما استفاده شدهاند همگی به فریمورک CSS Bootstrap اشاره دارند. تگ <div class='container'> (خط 14) یک کانtejner Bootstrap را تعریف میکند؛
- خط ۲۶: قطعه [v-bandeau.html] گنجانده شده است. این قطعه هدر [1] صفحه را تولید میکند. به زودی این موضوع را توضیح خواهیم داد؛
- خطوط ۱۸–۲۲: تگ <div class='row'> یک ردیف Bootstrap را تعریف میکند. این ردیفها از ۱۲ ستون تشکیل شدهاند؛
- خط ۱۹: تگ <div class='col-md-9'> یک بخش ۹ ستونی را تعریف میکند؛
- خط ۲۰: قطعه [v-authentification.html] گنجانده شده است که فرم احراز هویت صفحه [2] را نمایش میدهد. ما به زودی این موضوع را توضیح خواهیم داد؛
- خطوط ۲۴–۳۳: کد HTML در این خطوط تنها در صورتی استفاده میشود که [modèle.error] برابر True باشد. ما همیشه به شرح زیر عمل خواهیم کرد: مدل برای یک نما HTML در یک دیکشنری [modèle] محصور خواهد شد؛
- خطوط ۲۴–۳۳: احراز هویت در صورتی که کاربر اطلاعات نادرست وارد کند، ناموفق خواهد بود. در این صورت، نمای احراز هویت همراه با یک پیام خطا مجدداً نمایش داده میشود. ویژگی [modèle.error] نشان میدهد که آیا این پیام خطا باید نمایش داده شود یا خیر؛
- خطوط ۲۷–۳۰: تعریف یک ناحیه با پسزمینه صورتی (class="alert alert-danger") (خط ۲۷);

- خط ۲۸: مقداری متن؛
- خط ۲۹: تگ (فهرست غیر مرتب) از HTML یک فهرست نقطهدار نمایش میدهد. هر مورد از فهرست باید دارای سینتکس item باشد. مقدار [modèle.erreurs] در اینجا نمایش داده میشود. این مقدار توسط فیلتر [safe] (برای وجود |) فیلتر میشود. به طور پیشفرض، هنگامی که یک رشته قرار است به مرورگر ارسال شود، Flask هرگونه تگ HTML را که ممکن است وجود داشته باشد «خنثی» میکند تا مرورگر آنها را تفسیر نکند. با این حال، گاهی میخواهید که این تگها تفسیر شوند. در اینجا نیز همینطور است، جایی که رشته [modèle.erreurs] شامل تگهای HTML و است که برای جدا کردن یک آیتم در لیست استفاده میشوند. در این مورد، از فیلتر [safe] استفاده میکنیم که به Flask میگوید رشتهٔ نمایش داده شده ایمن است و بنابراین نباید هیچیک از تگهای HTML را که در آن مییابد، پاکسازی (sanitise) کند؛
بیایید عناصر پویا را که باید در این کد تعریف شوند، مشخص کنیم:
- [modèle.error]: برای نمایش یک پیام خطا؛
- [modèle.erreurs]: یک لیست (در معنای HTML) از پیامهای خطا؛
32.5.2. قطعه [v-bandeau.html]
قطعه [v-bandeau.html] بنر بالایی را در تمام نماهای برنامه وب نمایش میدهد:

کد قطعه [v-bandeau.html] به شرح زیر است:
<!-- بوتاسترپ جامبوترون -->
<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>
نظرات
- ردههای ۲ تا ۱۳: بنر در یک بخش Jumbotron بوتاسترپ ([<div class="jumbotron">]) قرار گرفته است. این کلاس بوتاسترپ محتوای نمایشدادهشده را به شیوهای خاص استایل میدهد تا برجسته شود؛
- خطوط ۳–۱۲: یک ردیف Bootstrap؛
- خطوط ۴–۶: یک تصویر [img] در چهار ستون اول ردیف قرار داده شده است؛
- خط ۵: نحو:
از تابع [url_for] فلاسک استفاده میکند. در اینجا، مقدار آن URL از فایل [images/logo.pg] در پوشه [static] خواهد بود؛
- خطوط ۷–۱۱: ۸ ستون دیگر در این ردیف (به یاد داشته باشید که در مجموع ۱۲ ستون وجود دارد) برای نمایش متن (خط ۹) با قلم درشت (، خطوط ۸–۱۰) استفاده خواهند شد؛
32.5.3. قطعه [v-authentification.html]
قطعه [v-authentification.html] فرم ورود وباپلیکیشن را نمایش میدهد:

کد قطعه [v-authentification.html] به شرح زیر است:
<!-- فرم HTML – مقادیر با استفاده از عمل [authentifier-utilisateur] ارسال میشوند -->
<form method="post" action="/authentifier-utilisateur">
<!-- عنوان -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- فرم بوتاسترپ -->
<fieldset class="form-group">
<!-- خط اول -->
<div class="form-group row">
<!-- برچسب -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- میدان ورودی متن -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="{{ modèle.login }}" required>
</div>
</div>
<!-- خط دوم -->
<div class="form-group row">
<!-- برچسب -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- میدان ورودی متن -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe" required>
</div>
</div>
<!--دکمه از نوع [submit] در خط سوم -->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
توضیحات
- خطوط ۲–۳۹: تگ <form> یک فرم HTML را مشخص میکند. این معمولاً دارای ویژگیهای زیر است:
- این فیلدهای ورودی را تعریف میکند (برچسبهای <input> در خطوط 17 و 27)؛
- این دارای یک دکمه از نوع [submit] (خط ۳۴) است که مقادیر واردشده را به URL مشخصشده در ویژگی [action] تگ [form] ارسال میکند (خط ۲). متد HTTP که برای پرسوجو از این URL استفاده میشود، در ویژگی [method] تگ [form] مشخص شده است (خط ۲)؛
- در اینجا، هنگامی که کاربر روی دکمه [Valider] (خط ۳۴) کلیک میکند، مرورگر مقادیر وارد شده در فرم را ارسال (خط ۲) به URL [/authentifier-utilisateur] (خط ۲) خواهد کرد؛
- مقادیر ارسالشده، همانهایی هستند که کاربر در فیلدهای ورودی در خطوط 17 و 27 وارد کرده است. این مقادیر در بدنه درخواست HTTP که مرورگر در فرم [x-www-forl-urlencoded] ارسال میکند، قرار خواهند گرفت. نامهای پارامترهای [user, password]، نامهای ویژگیهای [name]ِ فیلدهای ورودی در خطوط 17 و 27 هستند؛
- خطوط ۵–۷: یک بخش Bootstrap برای نمایش یک عنوان روی پسزمینه آبی:
- خطوط ۱۰–۳۷: یک فرم Bootstrap. سپس تمام عناصر فرم به شیوهای خاص استایل داده خواهند شد؛
- خطوط ۱۲–۲۰: اولین ردیف بوتاسترپ فرم را تعریف میکنند:
![]()
- خط ۱۴ برچسب [1] را در سه ستون تعریف میکند. ویژگی [for] تگ [label]، برچسب را به ویژگی [id] فیلد ورودی در خط 17 پیوند میدهد؛
- خطوط ۱۵–۱۹: فیلد ورودی را در یک چیدمان چهار ستونی قرار میدهد؛
- خطوط ۱۷–۱۸: تگ HTML [input] یک فیلد ورودی را تعریف میکند. این تگ چندین پارامتر دارد:
- [type=’text’]: این یک فیلد ورودی متنی است. میتوانید هر چیزی را در آن تایپ کنید؛
- [class=’form-control’]: سبک Bootstrap برای فیلد ورودی؛
- [id=’user’]: شناسهی فیلد ورودی. این شناسه معمولاً توسط CSS و کد جاوااسکریپت استفاده میشود؛
- [name=’user’]: نام فیلد ورودی متنی. مقداری که کاربر وارد میکند توسط مرورگر با این نام ارسال خواهد شد؛
- [placeholder=’invite’]: متنی که هنگام وارد نکردن هیچچیز توسط کاربر در فیلد ورودی نمایش داده میشود؛
![]()
- (ادامه)
- [value=’valeur’]: متن 'value' به محض ظاهر شدن در فیلد ورودی نمایش داده میشود، یعنی قبل از اینکه کاربر چیز دیگری وارد کند. این مکانیزم در صورت بروز خطا برای نمایش ورودیای که باعث خطا شده است، استفاده میشود. در اینجا، این مقدار، مقدار متغیر [modèle.login] خواهد بود؛
- [required]: نیاز دارد کاربر مقداری را وارد کند تا فرم بتواند به سرور ارسال شود:
- خطوط ۲۱–۳۰: کدی مشابه برای فیلد ورود رمز عبور؛
- خط ۲۷: [type=’password’] یک فیلد ورودی متنی ایجاد میکند (میتوانید هر چیزی تایپ کنید) اما کاراکترهای تایپشده پنهان هستند:
![]()
- خطوط ۳۲–۳۶: یک خط Bootstrap سوم برای دکمه [Valider]؛
- خط ۳۴: به دلیل داشتن ویژگی [type=submit]، کلیک روی این دکمه باعث میشود مرورگر مقادیر واردشده را به سرور ارسال کند، همانطور که قبلاً توضیح داده شد. ویژگی CSS [class="btn btn-primary"] یک دکمه آبی را نمایش میدهد:
یک نکته پایانی برای توضیح باقی مانده است. در خط ۲، ویژگی [action="/authentifier-utilisateur"] یک URL ناقص را تعریف میکند (این ویژگی با http://machine:port/chemin شروع نمیشود). در مثال ما، تمام URL در برنامه از شکل [http://machine:port/chemin/action/param1/param2/..] هستند، که در آن [http://machine:port/chemin] ریشه سرویس URL است. در [action="/authentifier-utilisateur"]، ما یک URL مطلق داریم، یعنی اندازهگیری شده از ریشه URL. بنابراین URL کامل برای POST، [http://machine:port/chemin/authentifier-utilisateur] است و این همان چیزی است که مرورگر استفاده خواهد کرد.
شایان ذکر است که این قطعه از قالب [modèle.login] استفاده میکند.
32.5.4. آزمایشهای بصری
ویوها را میتوان مدتها پیش از آنکه در برنامه یکپارچه شوند، بهخوبی آزمایش کرد. هدف در اینجا آزمون ظاهر بصری آنهاست. ما تمام ویوهای آزمون را در پوشه [tests_views] پروژه جمعآوری خواهیم کرد:

برای تست نمای V [vue-authentification.html]، باید مدل داده M را که نمایش خواهد داد، ایجاد کنیم. این کار را با استفاده از اسکریپت [test_vue_authentification.py] انجام میدهیم:
from flask import Flask, render_template, make_response
# برنامه Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# خانه URL
@app.route('/')
def index():
# دادههای صفحه در یک قالب جایگذاری شدهاند
modèle = {}
#شناسهٔ کاربر
modèle["login"] = "albert"
# فهرست خطاها
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
# فهرستی از خطاها تولید میشود HTML
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# صفحه نمایش داده شد
return make_response(render_template("views/vue-authentification.html", modèle=modèle))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
توضیحات
- خطوط ۱–۳: ما یک اپلیکیشن Flask ایجاد میکنیم که تنها هدف آن نمایش ویو [vue-authentification.html] (خط ۲۲) است؛
- خط ۷: این برنامه تنها یک سرویس دارد، URL؛
- خطوط ۹–۲۰: نمای احراز هویت دارای بخشهای پویایی است که توسط شیء [modèle] کنترل میشوند. این شیء به عنوان قالب نما (view template) شناخته میشود. بر اساس یکی از دو تعریف ارائهشده برای مخفف MVC، این «M» در MVC است. هنگام تعریف نمای [vue-authentification.html]، ما سه مقدار پویا را شناسایی کردیم:
- [modèle.error]: یک مقدار بولی که نشان میدهد آیا باید یک پیام خطا نمایش داده شود یا خیر؛
- [modèle.erreurs]: یک لیست (HTML) از پیامهای خطا؛
- [modèle.login]: نام کاربری کاربر؛
بنابراین باید این سه مقدار پویا را تعریف کنیم.
- خطوط ۹–۲۰: ما سه عنصر پویا از نمای احراز هویت را تعریف میکنیم؛
برای آزمایش این موضوع، اسکریپت [tests_views/test_vue_authentification.py] را اجرا کرده و URL و [/localhost:5000/] را درخواست میکنیم:
ما این تستهای بصری را تا زمانی که از نتیجه راضی شویم ادامه میدهیم.

32.5.5. محاسبه مدل نما
پس از تعیین ظاهر بصری نما، میتوان مدل نما را تحت شرایط دنیای واقعی محاسبه کرد. مدلهای نما توسط کلاسهای موجود در پوشه [models_for_views] تولید خواهند شد:

هر کلاسی که مدل نما تولید میکند، رابط زیر [InterfaceModelForView] را پیادهسازی خواهد کرد:
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
- خطوط ۸–۱۰: متد [get_model_for_view] مسئول تولید یک قالب نما (view template) است که در یک دیکشنری (dictionary) جای گرفته است. برای این کار، اطلاعات زیر را دریافت میکند:
- [request, session, config] همان پارامترهایی هستند که توسط کنترلکننده اکشن استفاده میشوند. بنابراین، آنها نیز به ویو ارسال میشوند؛
- کنترلکننده نتیجهای [résultat] تولید کرده است که آن نیز به ویو ارسال میشود. این نتیجه حاوی یک عنصر مهم [état] است که نشان میدهد اجرای اقدام فعلی چگونه پیش رفته است. ویو از این اطلاعات استفاده خواهد کرد؛
ما دیدهایم که در پیکربندی برنامه [config]، کدهای وضعیت بازگرداندهشده توسط کنترلکنندهها برای مشخص کردن اینکه کدام نما HTML باید نمایش داده شود، استفاده میشوند:
# ویوهای HTML و قالبهای آنها به حالتی که توسط کنترلر بازگردانده میشود بستگی دارند
"views": [
{
#نمای احراز هویت
"états": [
# /init-session موفقیتآمیز
700,
#/authentification-کاربر شکست
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
#نمای محاسبه مالیات
"états": [
# /احراز-هویت-کاربر موفق
200,
# /محاسبه-مالیات موفق
300,
# /محاسبه-مالیات ناموفق
301,
# /نمایش-محاسبه-مالیات
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
#مشاهده فهرست شبیهسازیها
"états": [
# /لیست-شبیهسازیها
500,
# /حذف-شبیهسازی
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
#مشاهده خطاهای غیرمنتظره
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
# ارسال مجدد
"redirections": [
{
"états": [
400, # /end-session-success
],
# ارسال مجدد به
"to": "/init-session/html",
}
],
}
بنابراین، کدهای وضعیت [700, 201] (خطوط ۷ و ۹) هستند که باعث نمایش نمای احراز هویت میشوند. برای پی بردن به معنای این کدها، میتوانیم به تستهای [Postman] انجامشده روی برنامه jSON مراجعه کنیم:
- [init-session-json-700]: 700 کد وضعیت پس از یک اقدام موفق [init-session] است: در این صورت فرم احراز هویت خالی نمایش داده میشود؛
- [authentifier-utilisateur-201]: 201 کد وضعیت پس از یک اقدام ناموفق [authentifier-utilisateur] (اطلاعات شناسایی معتبر شناخته نشده) است: در این صورت فرم احراز هویت نمایش داده میشود تا جزئیات اصلاح شوند؛
اکنون که میدانیم فرم احراز هویت باید چه زمانی نمایش داده شود، میتوانیم قالب آن را در [ModelForAuthentificationView] (خط ۱۲) محاسبه کنیم:
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:
# دربرگیری دادههای صفحه در یک قالب
modèle = {}
#وضعیت برنامه
état = résultat["état"]
# قالب به وضعیت بستگی دارد
if état == 700:
# در صورتی که فرم خالی نمایش داده شود
modèle["login"] = ""
# هیچ خطایی برای نمایش وجود ندارد
modèle["error"] = False
elif état == 201:
# احراز هویت نادرست
# کاربر واردشدهٔ اولیه دوباره نمایش داده میشود
modèle["login"] = request.form.get("user")
# یک خطا برای نمایش وجود دارد
modèle["error"] = True
# فهرست پیامهای خطا (HTML)
erreurs = ""
for erreur in résultat["réponse"]:
erreurs += f"<li>{erreur}</li>"
modèle["erreurs"] = erreurs
#قالب رندر میشود
return modèle
نظرات
- خط ۸: متد [get_model_for_view] نمای احراز هویت باید یک دیکشنری با سه کلید فراهم کند: [error, erreurs, login]. این محاسبه بر اساس کد وضعیت بازگردانده شده توسط کنترلکننده اقدام است؛
- خط ۱۲: کد وضعیت بازگردانده شده توسط کنترلگری که اقدام فعلی را پردازش کرده است، بازیابی میشود؛
- خطوط ۱۴–۲۹: قالب به این کد وضعیت بستگی دارد؛
- خطوط ۱۵–۱۸: حالتی که باید یک فرم احراز هویت خالی نمایش داده شود؛
- خطوط ۲۰–۲۹: در صورت ناموفق بودن احراز هویت: نام کاربری وارد شده توسط کاربر به همراه یک پیام خطا نمایش داده میشود. کاربر سپس میتواند دوباره برای ورود تلاش کند؛
- خط ۲۲: نام کاربری که در ابتدا توسط کاربر وارد شده است را میتوان از درخواست کلاینت بازیابی کرد؛
- خط ۲۴: مشخص میشود که خطاهایی برای نمایش وجود دارد؛
- خطوط ۲۶–۲۹: اگر خطایی رخ دهد، نتیجه [‘réponse’] شامل فهرستی از خطاها است؛
32.5.6. تولید پاسخها HTML
بیایید به مدل MVC از برنامه HTML بازگردیم:
- در ۲ (۲a، ۲b): کنترلکننده یک اکشن را اجرا میکند؛
- در 3 (3a, 3b, 3c): یک نما انتخاب شده و به کلاینت ارسال میشود؛
در [3a]، یک نوع پاسخ (jSON, XML, HTML) انتخاب میشود. ما دیدهایم که پاسخهای jSON و XML چگونه تولید میشوند، اما هنوز پاسخهای HTML را ندیدهایم. این پاسخها توسط کلاس [HtmlResponse] تولید میشوند:

بیایید به یاد بیاوریم که چگونه، در اسکریپت اصلی [main]، نوع پاسخی که باید به کاربر ارسال شود، تعیین میشود:
….
#در حال ساخت پاسخ برای ارسال
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
#ارسال پاسخ
return response, status_code
که در آن، در خط ۳، **config[‘responses’]** فرهنگ لغت زیر است:
#انواع مختلف پاسخ (json, xml, html)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
بنابراین کلاس [HtmlResponse] است که پاسخ HTML را تولید میکند. کد آن به شرح زیر است:
#فرهنگ پاسخ 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 به کد وضعیت بازگرداندهشده توسط کنترلر بستگی دارد
état = résultat["état"]
#آیا باید هدایت انجام شود؟
for redirection in config["redirections"]:
#وضعیتهایی که نیاز به هدایت مجدد دارند
états = redirection["états"]
if état in états:
#یک هدایت لازم است
return redirect(f"/{redirection['to']}"), status.HTTP_302_FOUND
#هر وضعیت با یک نما مطابقت دارد
# ما این را در فهرست ویوها جستجو میکنیم
views_configs = config["views"]
trouvé = False
i = 0
# ما لیست ویوها را مرور میکنیم
nb_views = len(views_configs)
while not trouvé and i < nb_views:
# نما شمارهٔ i
view_config = views_configs[i]
#گزارشهای مرتبط با نمای شمارهٔ i
états = view_config["états"]
#آیا گزارشی که به دنبال آن هستید در میان گزارشهای مرتبط با نمای شمارهٔ i است؟
if état in états:
trouvé = True
else:
# نما بعدی
i += 1
# یافت شد؟
if not trouvé:
# اگر هیچ نمایی برای وضعیت فعلی برنامه وجود نداشته باشد
#نمای خطا را نمایش میدهد
view_config = config["view-erreurs"]
#قالب نمای قابل نمایش محاسبه میشود
model_for_view = view_config["model_for_view"]
modèle = model_for_view.get_model_for_view(request, session, config, résultat)
#کد پاسخ را تولید میکند HTML
html = render_template(view_config["view_name"], modèle=modèle)
#پاسخ ساخته میشود HTTP
response = make_response(html)
response.headers['Content-Type'] = 'text/html; charset=utf-8'
#نتیجه بازگردانده میشود
return response, status_code
- خط ۱۱: متد [build_http_response]، مسئول تولید پاسخ HTML، پارامترهای زیر را دریافت میکند:
- [request, session, dict]: اینها پارامترهایی هستند که کنترلکننده برای پردازش اقدام فعلی از آنها استفاده میکند؛
- [status_code, résultat]: اینها دو نتیجهای هستند که توسط همین کنترلکننده تولید میشوند؛
- خط 14: همانطور که ذکر شد، پاسخ سرور HTML به کد وضعیت موجود در دیکشنری [résultat] بستگی دارد؛
- خطوط 16–22: هدایتها ابتدا پردازش میشوند. فعلاً این مورد را نادیده میگیریم تا زمانی که با مثالی از هدایت مواجه شویم. شایان ذکر است که هدایتها معمولاً یک مورد استفاده برای سرور HTML هستند. این سناریو با سرورهای jSON و ouXML رخ نمیدهد؛
- خطوط ۲۴–۴۱: ما در میان ویوها به دنبال ویویی میگردیم که لیست [états] آن حاوی وضعیت مورد نیاز باشد؛
- خطوط ۴۲–۴۶: اگر هیچ ویویی پیدا نشود، این یک خطای غیرمنتظره است. بیایید یک مثال بزنیم. در حین کار عادی برنامه، اقدام [/supprimer-simulation] هرگز نباید شکست بخورد. در واقع، خواهیم دید که این حذف شبیهسازیها با استفاده از لینکهایی که توسط کد ایجاد شدهاند، انجام میشود. این لینکها صحیح هستند و نمیتوانند منجر به خطا شوند. با این حال، همانطور که دیدیم، کاربر ممکن است مستقیماً URL یا [/supprimer-simulation/id] را تایپ کند و در نتیجه باعث خطا شود. در این حالت، کنترلکننده [SupprimerSimulationController] کد وضعیت ۶۰۱ را بازمیگرداند. با این حال، این کد وضعیت در فهرست کدهای وضعیتی که نمایش صفحه HTML را فعال میکنند، گنجانده نشده است. بنابراین نمای خطا نمایش داده خواهد شد. این نما در پیکربندی به شرح زیر تعریف شده است:
#نمای خطاهای غیرمنتظره
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
- خط ۴۹: وقتی بدانیم کدام نما را نمایش دهیم، کلاسی را که قالب آن را تولید میکند، بازیابی میکنیم. این مورد همچنین در پیکربندی [config] یافت میشود؛
- خط ۵۰: پس از یافتن این کلاس، مدل نما تولید میشود؛
- خط ۵۲: هنگامی که مدل M برای نما V محاسبه شد، میتوانیم کد HTML را برای نما تولید کنیم؛
- خطوط ۵۴–۵۵: پاسخ HTTP با بدنه HTML ساخته میشود؛
- خطوط ۵۶–۵۷: پاسخ HTTP همراه با کد وضعیت خود بازگردانده میشود؛
32.5.7. [Postman] تستها
ما درخواستهایی را اجرا خواهیم کرد که کدهای [700, 201] را تولید میکنند، که نمای احراز هویت را نمایش میدهند:
- [init-session-html-700]: 700 کد وضعیت پس از یک عملیات موفق [init-session] است: سپس فرم احراز هویت خالی نمایش داده میشود؛
- [authentifier-utilisateur-201]: 201 کد وضعیت پس از یک اقدام ناموفق [authentifier-utilisateur] (اطلاعات شناسایی نامعتبر) است: در این صورت فرم احراز هویت نمایش داده میشود تا جزئیات اصلاح شوند؛
بهسادگی از آنها استفاده مجدد کنید و بررسی کنید که آیا نمای احراز هویت را بهدرستی نمایش میدهند یا خیر. دو حالت در اینجا نشان داده شده است:
مورد ۱: [init-session-html-700]، شروع یک جلسه HTML؛

پاسخ به شرح زیر است:

- در [5]، حالت [Preview] اجازه میدهد تا صفحه دریافتی HTML مشاهده شود؛
- در [6]، ما در واقع فرم خالی مورد انتظار را داریم؛
- در [7]، پستمن لینک تصویر را در صفحه دنبال نکرد؛
- در [8]، حالت [Raw] دسترسی به HTML دریافتشده را فراهم میکند؛

- در [3]، لینکی که پستمن آن را بارگذاری نکرد. این لینک مقدار ویژگی [alt=alternative] را نمایش داد، که هنگام عدم بارگذاری تصویر نشان داده میشود. در این مورد، احتمالاً Postman به سادگی از بارگذاری آن خودداری کرده است. این موضوع را میتوان با درخواست URL و [http://localhost :5000/static/images.logo.jpg] در Postman تأیید کرد:
مورد ۲: [authentifier-utilisateur-201]، احراز هویت نادرست

اکنون، پس از راهاندازی موفقیتآمیز جلسه HTML، یک تلاش نادرست برای احراز هویت انجام دهیم:

بالا:
- در [4,7]: درخواست رشته [user=bernard&password=thibault] را ارسال میکند؛
پاسخ به شرح زیر است:

- در [4]، یک پیام خطا نمایش داده میشود؛
- در [3]، کاربر نادرست دوباره نمایش داده شد؛
32.5.8. نتیجهگیری
ما توانستیم نمای [vue-authentification.html] را بدون نوشتن سایر نماها آزمایش کنیم. این امر ممکن شد زیرا:
- تمام کنترلرها نوشته شدهاند؛
- [Postman] به ما امکان میدهد بدون نیاز به تمام ویوها، درخواستها را به سرور ارسال کنیم. هنگام نوشتن کنترلرها، باید آماده باشید تا درخواستهایی را که هیچ ویویی اجازهٔ آنها را نمیدهد، مدیریت کنید. هرگز نباید پیشفرض کرد که «این درخواست غیرممکن است». باید بررسی کرد؛
32.6. نما نمایش محاسبه مالیات

32.6.1. نمای کلی ویو
نما محاسبه مالیات به شرح زیر است:

این نما از سه بخش تشکیل شده است:
- ۱: بنر بالایی توسط قطعه [v-bandeau.html] تولید میشود که قبلاً توضیح داده شده است؛
- ۲: فرم محاسبه مالیات، تولید شده توسط قطعه [v-calcul-impot.html]؛
- ۳: منویی شامل دو پیوند، تولید شده توسط قطعه [v-menu.html];
نمایه محاسبه مالیات توسط کد زیر تولید میشود: [vue-calcul-impot.html]:
<!--سند HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- برچسبهای متای مورد نیاز -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!--بوتاسترپ 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">
<!-- بنر -->
{% include "fragments/v-bandeau.html" %}
<!-- قالب دو ستونی -->
<div class="row">
<!-- منو -->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- فرم محاسبه -->
<div class="col-md-9">
{% include "fragments/v-calcul-impot.html" %}
</div>
</div>
<!-- مورد موفقیت -->
{% if modèle.success %}
<!-- یک هشدار موفقیت نمایش داده میشود -->
<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 %}
<!-- فهرست ۹ ستونی خطاها -->
<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>
نظرات
- ما فقط در مورد ویژگیهای جدیدی که هنوز با آنها مواجه نشدهایم، نظر میدهیم؛
- خط ۱۶: درج بنر بالایی نما در ردیف اول بوتاسترپ نما؛
- خط ۲۱: درج منو، که سه ستون از ردیف دوم بوتاسترپ نما (خطوط ۱۸ و ۲۰) را اشغال خواهد کرد؛
- خط ۲۵: درج فرم محاسبه مالیات، که نه ستون (خط ۲۴) از ردیف دوم Bootstrap نما (خط ۱۸) را اشغال خواهد کرد؛
- خطوط ۳۰–۴۶: اگر محاسبه مالیات با موفقیت انجام شود ([modèle.success=True])، نتیجه محاسبه مالیات در یک کادر سبز رنگ نمایش داده میشود (خطوط ۳۷–۴۳). این کادر در سومین ردیف بوتاسترپ نما (خط ۳۲) قرار دارد و نه ستون (خط ۳۶) را در سمت راست سه ستون خالی (خطوط ۳۳–۳۵) اشغال میکند. بنابراین، این کادر در زیر فرم محاسبه مالیات قرار خواهد گرفت؛
- ردهها ۴۸–۶۱: اگر محاسبه مالیات ناموفق باشد ([modèle.error=True])، پیام خطا در یک کادر صورتی (ردهها ۵۵–۵۸) نمایش داده میشود. این کادر در سومین سطر Bootstrap نما (خط 50) قرار دارد و نه ستون (خط 54) را در سمت راست سه ستون خالی (خطوط 51–53) اشغال میکند. بنابراین این کادر نیز در زیر فرم محاسبه مالیات قرار خواهد گرفت؛
32.6.2. قطعه [v-calcul-impot.html]
قطعه [v-calcul-impot.html] فرم محاسبه مالیات را از برنامه وب نمایش میدهد:
کد قطعه [v-calcul-impot.html] به شرح زیر است:

<!-- HTML فرم ارسال شد -->
<form method="post" action="/calculer-impot">
<!-- پیامی در ۱۲ ستون روی پسزمینه آبی -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- میدانهای فرم -->
<fieldset class="form-group">
<!-- اولین خط در ۹ ستون -->
<div class="row">
<!--متن در ۴ ستون -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- دکمههای رادیویی در ۵ ستون-->
<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>
<!-- ردیف دوم در عرض ۹ ستون -->
<div class="form-group row">
<!-- برچسبگذاری شده در عرض ۴ ستون -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- میدان ورودی عددی برای تعداد فرزندان، عرض ۵ ستون -->
<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>
<!-- ردیف سوم، ۹ ستون -->
<div class="form-group row">
<!--متن در ۴ ستون -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel net imposable</label>
<!-- میدان ورودی عددی برای حقوق، عرض ۵ ستون -->
<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>
<!-- ردیف چهارم، دکمه [submit] در عرض ۵ ستون -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
نظرات
- خط ۲: فرم HTML ارسال خواهد شد (ویژگی [method]) به URL [/calculer-impot] (ویژگی [action]). مقادیر ارسالشده، مقادیر فیلدهای ورودی خواهند بود:
- مقدار دکمه رادیویی انتخابشده در فرم:
- [marié=oui] اگر دکمه رادیویی [Oui] انتخاب شده باشد (خطوط 17–22). [marié] مقدار ویژگی [name] در خط ۱۸ است، [oui] مقدار ویژگی [value] در خط ۱۸ است؛
- [marié=non] اگر دکمهٔ رادیویی [Non] انتخاب شده باشد (خطوط 23–28). [marié] مقدار ویژگی [name] در سطر 24 است، [non] مقدار ویژگی [value] در سطر 24 است؛
- مقدار در فیلد ورودی عددی در خط ۳۷ در فرم [enfants=xx]، که در آن [enfants] مقدار ویژگی [name] در خط ۳۷ است، و [xx] مقداری است که توسط کاربر از طریق صفحهکلید وارد شده است؛
- مقدار فیلد ورودی عددی در خط ۴۶ در فرم [salaire=xx]، که در آن [salaire] مقدار ویژگی [name] در خط ۴۶ است، و [xx] مقداری است که کاربر از طریق صفحهکلید وارد کرده است؛
در نهایت، مقدار ارسالشده به شکل [marié=xx&enfants=yy&salaire=zz] خواهد بود.
- (ادامه)
- مقادیر واردشده هنگام کلیک کاربر روی دکمه از نوع [submit] در خط ۵۳ ارسال خواهند شد؛
- خطوط ۱۶–۳۰: دو دکمه رادیویی:
![]()
این دو دکمه رادیویی بخشی از یک گروه دکمه رادیویی هستند زیرا ویژگی یکسانی به نام [name] (خطوط ۱۸ و ۲۴) را به اشتراک میگذارند. مرورگر تضمین میکند که در داخل یک گروه دکمه رادیویی، در هر زمان تنها یکی از آنها انتخاب باشد. بنابراین، کلیک کردن روی یکی، دیگری را که قبلاً انتخاب شده بود، غیرفعال میکند؛
- آنها به دلیل ویژگی [type="radio"] (خطوط ۱۸ و ۲۴) دکمه رادیویی هستند؛
- هنگامی که فرم نمایش داده میشود (قبل از هرگونه ورودی)، یکی از دکمههای رادیویی باید انتخاب شده باشد: برای این کار، کافی است ویژگی [checked=’checked’] را به تگ مربوطه <input type="radio"> اضافه کنید. این کار با استفاده از متغیرهای پویا انجام میشود:
- [modèle.checkedOui] در خط ۱۸؛
- [modèle->checkedNon] در خط ۲۴؛
این متغیرها بخشی از قالب نما را تشکیل خواهند داد.
- خط ۳۷: یک فیلد ورودی عددی [type="number"] با حداقل مقدار ۰ [min="0"]. در مرورگرهای مدرن، این بدان معناست که کاربر تنها میتواند عددی بزرگتر یا مساوی صفر وارد کند. در همین مرورگرهای مدرن، میتوان ورودی را با استفاده از یک اسلایدر انجام داد که با کلیک روی آن میتوان مقدار را افزایش یا کاهش داد. ویژگی [step="1"] در خط ۳۷ نشان میدهد که اسلایدر به صورت افزایشی با گام ۱ واحد عمل خواهد کرد. در نتیجه، اسلایدر فقط مقادیر صحیح در بازه ۰ تا n را با گامهای ۱ واحدی میپذیرد. برای ورود دستی، این بدان معناست که اعداد دارای ممیز اعشاری پذیرفته نخواهند شد؛
- خط ۳۷: در برخی نمایشگرها، فیلد ورودی فرعی باید با آخرین مقداری که در آن وارد شده است، از پیش پر شود. برای دستیابی به این هدف، از ویژگی [value] برای تعیین مقداری که باید در فیلد ورودی نمایش داده شود، استفاده میشود. این مقدار پویا بوده و توسط متغیر [modèle.enfants] تولید میشود؛
- خط ۳۷: ویژگی [required] مستلزم آن است که کاربر دادههایی را برای اعتبارسنجی فرم وارد کند؛
- خط ۴۶: توضیحات مشابه برای فیلد حقوق و فیلد فرزندان اعمال میشود؛
- خط ۵۳: دکمه از نوع [submit]، که بر اساس مقادیر وارد شده در URL و [/calculer-impot] (خط ۲) باعث اجرای POST میشود؛
![]()
32.6.3. قطعه [v-menu.html]
این قطعه منویی را در سمت چپ فرم محاسبه مالیات نمایش میدهد:

کد این قطعه به شرح زیر است:
<!--منوی بوتاسترپ -->
<nav class="nav flex-column">
<!--نمایش فهرستی از پیوندها HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
توضیحات
- خطوط ۲–۷: تگ HTML [nav] بخشی از سند HTML را که حاوی پیوندهای ناوبری به اسناد دیگر است، در بر میگیرد؛
- خط ۵: تگ HTML [a] یک لینک ناوبری را معرفی میکند:
- [optionMenu.url]: نشانگر URL است که کاربر هنگام کلیک بر روی پیوند [optionMenu.text] به آن هدایت میشود. این سپس عملی [GET optionMenu.url] است که توسط مرورگر انجام میشود. [optionMenu.url] یک URL مطلق خواهد بود که از ریشهٔ [http://machine :port/chemin] برنامه اندازهگیری میشود. بنابراین، در [1]، لینک را ایجاد خواهیم کرد:
- خط ۵: قالب [modèle.optionsMenu] قطعه، فهرستی به شکل زیر خواهد بود:
- خطوط ۲ و ۷: کلاسهای CSS و [nav, flex-column, nav-link] کلاسهای Bootstrap هستند که ظاهر منو را تعریف میکنند؛
32.6.4. آزمایش بصری
ما این عناصر مختلف را در پوشه [Tests] جمعآوری کرده و یک قالب آزمایشی برای نمای [vue-calcul-impot.html] ایجاد میکنیم:

اسکریپت آزمایشی [test_vue_calcul_impot] به شرح زیر خواهد بود:
from flask import Flask, render_template, make_response
# برنامه Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# خانه URL
@app.route('/')
def index():
# دربرگرفتن دادههای صفحه در یک قالب
modèle = {}
# فرم
modèle["checkedOui"] = ""
modèle["checkedNon"] = 'checked="checked"'
modèle["enfants"] = 2
modèle["salaire"] = 300000
# پیام موفقیت
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 %"
# پیام خطا
modèle["error"] = True
erreurs = ["erreur1", "erreur2"]
#یک فهرست خطا تولید میشود HTML
content = ""
for erreur in erreurs:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# منو
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# نمایش صفحه
return make_response(render_template("views/vue-calcul-impot.html", modèle=modèle))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
توضیحات
- خطوط ۹–۳۴: ما تمام بخشهای پویا از نمای [vue-calcul-impot.html] و قطعات [v-calcul-impot.html] و [v-menu.html] را مقداردهی اولیه میکنیم؛
- خط ۳۶: نما [vue-calcul-impot.html] نمایش داده میشود؛
وقتی اسکریپت تست [test_vue_calcul_impot] اجرا میشود، نتیجه زیر به دست میآید:
ما به کار روی این نما ادامه میدهیم تا زمانی که از نتیجهٔ بصری آن رضایت داشته باشیم. سپس میتوانیم نما را در وباپلیکیشن در حال توسعه ادغام کنیم.

32.6.5. محاسبه مدل نما
پس از نهایی شدن ظاهر بصری نما، میتوانیم به محاسبه مدل نما در شرایط دنیای واقعی بپردازیم. بیایید کدهای وضعیت را که به این نما منتهی میشوند، به یاد آوریم. این کدها را میتوان در فایل پیکربندی یافت:
{
# نمایش محاسبه مالیات
"états": [
# /احراز-هویت-کاربر-موفقیتآمیز
200,
# /محاسبه-مالیات موفق
300,
# /محاسبه-مالیات شکست
301,
# /نمایش-محاسبه-مالیات
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
بنابراین، کدهای وضعیت [200, 300, 301, 800] هستند که باعث نمایش نمای محاسبه مالیات میشوند. برای درک معنای این کدها، میتوانیم به تستهای [Postman] انجامشده بر روی برنامه jSON مراجعه کنیم:
- [authentifier-utilisateur-200]: 200 کد وضعیت پس از یک اقدام موفق [authentifier-utilisateur] است: سپس فرم خالی محاسبه مالیات نمایش داده میشود؛
- [calculer-impot-300]: 300 کد وضعیت پس از یک اقدام موفق [calculer-impot] است. سپس فرم محاسبه نمایش داده میشود و دادههای وارد شده و مبلغ مالیات را نشان میدهد. کاربر سپس میتواند محاسبه دیگری را انجام دهد؛
- کد وضعیت [301] برای محاسبه نادرست مالیات بازگردانده میشود؛
- کد وضعیت [800] در آینده معرفی خواهد شد. ما هنوز با آن مواجه نشدهایم؛
اکنون که میدانیم فرم محاسبه مالیات باید چه زمانی نمایش داده شود، میتوانیم قالب آن را در کلاس [ModelForCalculImpotView] تعریف کنیم:

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:
# درکپسوله کردن دادههای نما در قالب
modèle = {}
#وضعیت برنامه
état = résultat["état"]
# قالب به وضعیت وابسته است
if état in [200, 800]:
# نمایش اولیه یک فرم خالی
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:
#محاسبه موفقیتآمیز بود – نتیجه نمایش داده شد
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} %"
# فرم با مقادیر واردشده بازیابی شد
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:
#خطا رخ داده است – فرم با مقادیر وارد شده بازیابی شد
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")
#خطا
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>"
# گزینههای منو
modèle["optionsMenu"] = [
{"text": 'Liste des simulations', "url": '/lister-simulations'},
{"text": 'Fin de session', "url": '/fin-session'}]
# قالب در حال بازگشت است
return modèle
نظرات
- خط ۱۲: نمای نمایش داده شده به کد وضعیت بازگردانده شده توسط کنترلر بستگی دارد؛
- خطوط ۱۴–۲۱: نمایش یک فرم خالی؛
- خطوط ۲۲–۳۵: محاسبه موفق مالیات. مقادیر وارد شده و مبلغ مالیات دوباره نمایش داده میشوند؛
- خطوط ۳۶–۴۷: محاسبه مالیات ناموفق است؛
- خطوط ۴۹–۵۲: محاسبه دو گزینه منو؛
32.6.6. آزمایش [Postman]
ما یک جلسه HTML را با درخواست [init-session-html-700] آغاز میکنیم، سپس با استفاده از درخواست [authentifier-utilisateur-200] احراز هویت میکنیم. سپس از درخواست زیر [calculer-impot-300] استفاده میکنیم:
پاسخ سرور به شرح زیر است:


اکنون بیایید درخواست زیر را امتحان کنیم: [calculer-impot-301]:

پاسخ سرور به شرح زیر است:
اکنون بیایید یک سناریوی غیرمنتظره را امتحان کنیم، جایی که پارامترها در POST مفقود هستند. این سناریو در حین عملکرد عادی برنامه امکانپذیر نیست. با این حال، هر کسی میتواند با درخواست HTTP «دستکاری» کند، همانطور که اکنون انجام میدهیم:


- در [6]، ما پارامتر ارسالشده [marié] را بررسی نکردهایم؛
پاسخ سرور به شرح زیر است:

- به [3]، پیام خطای سرور؛
در این برنامه، ما یک انتخاب داشتیم. میتوانستیم به این مورد خطا یک کد وضعیت اختصاص دهیم که به صفحه خطاهای غیرمنتظره هدایت کند. در این برنامه، ما برای هر کنترلر دو کد وضعیت انتخاب کردهایم:
- [xx0]: برای موفقیت؛
- [xx1]: برای خطا؛
برای موارد خطا، میتوانیم کدهای وضعیت را متنوع کنیم تا امکان مدیریت دقیقتر خطاها فراهم شود. برای مثال، میتوانستیم از موارد زیر استفاده کنیم:
- [xx1]: برای خطاهایی که باید در صفحهای که باعث خطا شده نمایش داده شوند؛
- [xx2]: برای خطاهای غیرمنتظره که در حین استفادهٔ عادی از برنامه رخ میدهند؛
32.7. نمای لیست شبیهسازی

32.7.1. نمای کلی نما
نمای نمایش فهرست شبیهسازیها به شرح زیر است:

نما تولیدشده توسط کد [vue-liste-simulations.html] از سه بخش تشکیل شده است:
- ۱: بنر بالایی توسط قطعه [v-bandeau.html] تولید میشود که قبلاً توضیح داده شده است؛
- ۳: جدول شبیهسازیها که توسط قطعه [v-liste-simulations.html] تولید میشود؛
- ۲: منویی شامل دو پیوند، تولید شده توسط قطعه [v-menu.html] که قبلاً ارائه شده است؛
نمایه شبیهسازی توسط کد زیر تولید میشود: [vue-liste-simulations.html]:
<!--سند HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- برچسبهای متای الزامی -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!--بوتاسترپ 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">
<!-- سربرگ -->
{% include "fragments/v-bandeau.html" %}
<!-- قالب دو ستونی -->
<div class="row">
<!-- منوی سهستونی-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- فهرست ۹ ستونی شبیهسازیها-->
<div class="col-md-9">
{% include "fragments/v-liste-simulations.html" %}
</div>
</div>
</div>
</body>
</html>
نظرات
- خط ۱۶: درج بنر برنامه [1];
- خط ۲۱: درج منوی [2]. این منو در سه ستون زیر بنر نمایش داده خواهد شد؛
- خط ۲۶: درج جدول شبیهسازی [3]. این جدول در نه ستون زیر بنر و در سمت راست منو نمایش داده خواهد شد؛
ما قبلاً دو بخش از سه بخش این نما را مورد بحث قرار دادهایم:
قطعه [v-liste-simulations.html] به شرح زیر است:
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- پیام روی پسزمینه آبی -->
<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 %}
<!-- پیام روی پسزمینه آبی -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- جدول شبیهسازیها -->
<table class="table table-sm table-hover table-striped">
<!-- سربرگهای شش ستون جدول -->
<thead>
<tr>
<th scope="col">#
<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>
<!-- بدنه جدول (دادههای نمایش داده شده) -->
<tbody>
<!-- هر شبیهسازی با تکرار روی جدول شبیهسازی نمایش داده میشود -->
{% for simulation in modèle.simulations %}
<!--نمایش یک سطر از جدول با ۶ ستون – تگ <tr> -->
<!--ستون ۱: سربرگ ردیف (شماره شبیهسازی) – تگ <th scope='row'> -->
<!--ستون ۲: مقدار پارامتر [marié] – تگ -->
<!-- ستون ۳: مقدار پارامتر [enfants] - تگ -->
<!-- ستون ۴: مقدار پارامتر [salaire] - تگ -->
<!--ستون ۵: مقدار پارامتر [impôt] (مالیات) – تگ -->
<!--ستون ۶: مقدار پارامتر [surcôte] – تگ -->
<!-- ستون ۷: مقدار پارامتر [décôte] – تگ -->
<!--ستون ۸: مقدار پارامتر [réduction] – تگ -->
<!--ستون ۹: مقدار پارامتر [taux] (برای مالیات) – تگ -->
<!--ستون ۱۰: لینک حذف شبیهسازی – تگ -->
<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 %}
نظرات
- یک جدول HTML با استفاده از تگ (خطوط ۱۵ و ۶۲) ایجاد میشود؛
- سربرگهای ستون جدول درون تگ <thead> (سر جدول، خطوط 17 و 30) تعریف شدهاند. تگ <tr> (رد جدول، خطوط 18 و 29) یک سطر را جدا میکند. خطوط 19–28: تگ (سر جدول) یک سرستون را تعریف میکند. بنابراین در مجموع ده تا از آنها وجود دارد. [scope="col"] نشان میدهد که سربرگ به ستون اعمال میشود. [scope="row"] نشان میدهد که سربرگ به سطر اعمال میشود؛
- خطوط ۳۲–۶۱: تگ <tbody> دادههای نمایشدادهشده توسط جدول را در بر میگیرد؛
- خطوط ۴۷–۵۸: تگ <tr> یک سطر از جدول را در بر میگیرد؛
- خط ۴۸: تگ <th scope='row'> سربرگ سطر را تعریف میکند. مرورگر این سربرگ را برجسته میکند؛
- خطوط ۴۹–۵۷: هر تگ (دادههای جدول) یک ستون در ردیف را تعریف میکند؛
- خط ۳۴: فهرست شبیهسازیها را میتوان در قالب [modèle.simulations] یافت که شامل فهرستی از فرهنگلغتها است؛
- خط ۵۷: لینکی برای حذف شبیهسازی. قالب URL از شماره شبیهسازی نمایشدادهشده در سطر استفاده میکند؛
32.7.2. آزمون بصری
ما یک اسکریپت آزمایشی برای نمای [vue-liste-simulations.html] ایجاد میکنیم:
اسکریپت [test_vue_liste_simulations] به شرح زیر است:

from flask import Flask, make_response, render_template
# برنامه Flask
app = Flask(__name__, template_folder="../templates", static_folder="../static")
#خانه URL
@app.route('/')
def index():
#دادههای صفحه در یک قالب جایگذاری شدهاند
modèle = {}
#شبیهسازیها برای مطابقت با نیازهای صفحه قالببندی شدهاند
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
}
]
# منو
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
# صفحه را نمایش دهید
return make_response(render_template("views/vue-liste-simulations.html", modèle=modèle))
# اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
توضیحات
- خطوط ۱۲–۳۵: دو شبیهسازی به مدل اضافه شدهاند
- خطوط ۳۷–۳۹: جدول گزینههای منو؛
بیایید با اجرای این اسکریپت این نما را نمایش دهیم. نتیجهٔ زیر را دریافت میکنیم:

ما روی این نما کار میکنیم تا زمانی که از نتیجهٔ بصری آن رضایت داشته باشیم. سپس میتوانیم به ادغام این نما در وباپلیکیشن در حال توسعهٔ خود بپردازیم.
32.7.3. محاسبه مدل نما
پس از نهایی شدن ظاهر بصری ویو، میتوانیم به محاسبه مدل ویو در شرایط دنیای واقعی بپردازیم. بیایید کدهای وضعیت را که به این ویو منتهی میشوند، به یاد آوریم. این کدها را میتوان در فایل پیکربندی یافت:

{
# نمایش فهرست شبیهسازیها
"états": [
# /list-simulations
500,
# /حذف-شبیهسازی
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
بنابراین، کدهای وضعیت [500, 600] هستند که باعث نمایش نمای شبیهسازی میشوند. برای درک معنای این کدها، میتوانیم به تستهای [Postman] انجامشده روی برنامه jSON مراجعه کنیم:
- [lister-simulations-500]: 500 کد وضعیت پس از یک اقدام موفق [lister-simulations] است: فهرست شبیهسازیهای انجامشده توسط کاربر سپس نمایش داده میشود؛
- [supprimer-simulation-600]: 600 کد وضعیت پس از یک عملیات موفق [supprimer-simulation] است. سپس لیست جدید شبیهسازیها که پس از این حذف به دست آمده، نمایش داده میشود؛
اکنون که میدانیم فهرست شبیهسازیها باید چه زمانی نمایش داده شود، میتوانیم مدل آن را در کلاس [ModelForListeSimulationsView] پیادهسازی کنیم:
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:
# دادههای صفحه در یک قالب جایگذاری شده است
modèle = {}
# شبیهسازیها در پاسخ کنترلکنندهای که اقدام را اجرا کرده است یافت میشوند
#به صورت آرایهای از فرهنگلغتها TaxPayer
modèle["simulations"] = résultat["réponse"]
# منو
modèle["optionsMenu"] = [
{"text": "Calcul de l'impôt", "url": '/afficher-calcul-impot'},
{"text": 'Fin de session', "url": '/fin-session'}]
#قالب رندر میشود
return modèle
نظرات
- خط ۱۳: شبیهسازیهایی که باید نمایش داده شوند در [result["réponse"]] یافت میشوند؛
- خطوط ۱۵–۱۷: گزینههای منوی قابل نمایش؛
32.7.4. آزمایشها [Postman]
ما
- راهاندازی یک جلسه HTML؛
- احراز هویت؛
- سه محاسبه مالیاتی انجام میدهد؛
آزمون [lister-simulations-500] کد وضعیت 500 را بازمیگرداند. این مربوط به درخواستی برای مشاهده شبیهسازیها است:

پاسخ سرور به شرح زیر است:

آزمون [supprimer-simulation-600] کد وضعیت 600 را برمیگرداند. در اینجا، شبیهسازی شمارهٔ 2 را حذف خواهیم کرد.
نتیجه بازگردانده شده فهرستی از شبیهسازیها است که یک شبیهسازی در آن مفقود است:


32.8. نمایش خطاهای غیرمنتظره
در اینجا، «خطای غیرمنتظره» به خطایی اطلاق میشود که نباید در حین استفاده عادی از برنامه وب رخ میداد. به عنوان مثال، درخواست محاسبه مالیات بدون احراز هویت. هیچ چیزی مانع از این نمیشود که کاربر مستقیماً URL [/calcul-impot] را در مرورگر خود تایپ کند. علاوه بر این، همانطور که دیدیم، یک کاربر میتوانست با ارسال نکردن پارامترهای مورد انتظار، یک خطای «POST» را در نقطه پایانی «URL [/calcul-impot]» ایجاد کند. ما دیدهایم که اپلیکیشن وب ما توانست به درستی به این درخواست پاسخ دهد. ما به «خطای غیرمنتظره» به عنوان خطایی اشاره میکنیم که نباید در چارچوب برنامه HTML رخ دهد. اگر این خطا رخ دهد، به احتمال زیاد کسی در تلاش برای «هک» برنامه است. به دلایل آموزشی، ما تصمیم گرفتهایم در چنین مواردی یک صفحه خطا نمایش دهیم. در واقع، میتوانستیم آخرین صفحهای را که به کلاینت ارسال شده بود مجدداً نمایش دهیم. برای این کار، کافی است آخرین پاسخ ارسالشده HTML را در جلسه (session) ذخیره کنیم. در صورت وقوع خطای غیرمنتظره، این پاسخ را بازمیگردانیم. به این ترتیب، کاربر تصور میکند که سرور به خطاهای او پاسخ نمیدهد، زیرا صفحه نمایشدادهشده تغییر نمیکند.
32.8.1. طرحبندی نما

نمایانی که خطاهای غیرمنتظره را نمایش میدهد به شرح زیر است:

نما تولید شده توسط کد [vue-erreurs.html] سه بخش دارد:
- ۱: بنر بالایی توسط قطعه [v-bandeau.html] تولید میشود که قبلاً نمایش داده شده است؛
- ۲: خطای(های) غیرمنتظره؛
- ۳: منویی شامل سه لینک، تولیدشده توسط قطعه [v-menu.html] که قبلاً ارائه شده است؛
نمای خطاهای غیرمنتظره توسط اسکریپت زیر، [vue-erreurs.html]، تولید میشود:
<!--سند HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- تگهای متای مورد نیاز -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!--بوتاسترپ 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">
<!--بنر ۱۲ ستونی -->
{% include "fragments/v-bandeau.html" %}
<!-- ردیف دو بخشی -->
<div class="row">
<!-- منوی سهستونی-->
<div class="col-md-3">
{% include "fragments/v-menu.html" %}
</div>
<!-- فهرست ۹ ستونی خطاها -->
<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>
نظرات
- خط ۱۶: درج بنر برنامه [1]؛
- خط ۲۱: درج منوی [3]. این منو در سه ستون زیر بنر نمایش داده خواهد شد؛
- خطوط ۲۴–۲۹: نمایش ناحیه خطا در نه ستون؛
- خط ۲۵: این در یک کانتینر Bootstrap با پسزمینه صورتی نمایش داده خواهد شد؛
- خط ۲۶: متن مقدماتی؛
- خط ۲۷: تگ یک لیست نقطهدار را در بر میگیرد. این لیست نقطهدار توسط قالب [modèle.erreurs] فراهم شده است؛
ما قبلاً در مورد دو قطعه این نما نظر دادهایم:
32.8.2. آزمون بصری
ما در حال ایجاد یک اسکریپت آزمایشی برای نما [vue-erreurs.html] هستیم:

from flask import Flask, render_template, make_response
# برنامه فلسک
app = Flask(__name__, template_folder="../templates", static_folder="../static")
# خانه URL
@app.route('/')
def index():
# دربرگیری دادههای صفحه در یک قالب
modèle = {}
#ما یک لیست HTML از خطاها میسازیم
content = ""
for erreur in ["erreur1", "erreur2"]:
content += f"<li>{erreur}</li>"
modèle["erreurs"] = content
# گزینههای منو
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'}]
# نمایش صفحه
return make_response(render_template("views/vue-erreurs.html", modèle=modèle))
#اصلی
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
توضیحات
- خطوط ۱۱–۱۵: ساخت لیست خطاهای HTML؛
- خطوط ۱۷–۲۰: آرایه گزینههای منو؛
بیایید این اسکریپت را اجرا کنیم. نتیجهٔ زیر را دریافت میکنیم:
روی این نما کار میکنیم تا زمانی که از ظاهر آن رضایت داشته باشیم. سپس میتوانیم به ادغام این نما در وباپلیکیشن در حال توسعهمان بپردازیم.

32.8.3. محاسبه مدل نما

پس از نهایی شدن ظاهر بصری ویو، میتوانیم به محاسبه مدل ویو در شرایط دنیای واقعی بپردازیم. بیایید کدهای وضعیت را که به این ویو منتهی میشوند، به یاد آوریم. این کدها را میتوان در فایل پیکربندی یافت:
# نماهای HTML و قالبهای آنها به وضعیتی که توسط کنترلر بازگردانده میشود بستگی دارند
"views": [
{
#نمای احراز هویت
"états": [
# /init-session موفق
700,
# /پایان-سشن
400,
#/عدم موفقیت احراز هویت کاربر
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
#نمای محاسبه مالیات
"états": [
# /احراز هویت کاربر موفق
200,
# /محاسبه-مالیات موفق
300,
# /نابسامانی-محاسبه-مالیات
301,
# /نمایش-محاسبه-مالیات
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
#مشاهده فهرست شبیهسازیها
"états": [
# /list-simulations
500,
# /حذف-شبیهسازی
600
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
}
],
# مشاهده خطاهای غیرمنتظره
"view-erreurs": {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
},
این کدهای وضعیت هستند که در خطوط ۳–۴۱ منجر به نمایش یک ویوی HTML نمیشوند و باعث نمایش ویوی خطاهای غیرمنتظره میشوند.
محاسبه مدل برای نمای [vue-erreurs.html] توسط کلاس زیر، [ModelForErreursView]، انجام میشود:
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:
# مدل
modèle = {}
# خطاها
modèle["erreurs"] = ""
for erreur in résultat['réponse']:
modèle['erreurs'] += f"<li>{erreur}</li>"
# منو
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'}]
# رندر مدل
return modèle
توضیحات
- خطوط ۱۱–۱۴: محاسبه قالب [modèle.erreurs] مورد استفاده در نمای [vue-erreurs.html]؛
- خطوط ۱۶–۱۹۷: محاسبه قالب [modèle.optionsMenu] مورد استفاده توسط قطعه [v-menu.html]؛
32.8.4. آزمایشهای [Postman]
ما انجام میدهیم:
- عمل [/init-session/html];
- سپس اقدام [/init-session/x];
پاسخ HTML سپس به شرح زیر است:

32.9. پیادهسازی عملیات منوی برنامه
در اینجا به پیادهسازی عملیات منو میپردازیم. بیایید معنای لینکهایی را که با آنها مواجه شدهایم، به یاد بیاوریم
نما | لینک | هدف | نقش |
محاسبه مالیات | [Liste des simulations] | [/lister-simulations] | درخواست فهرست شبیهسازیها |
[Fin de session] | [/fin-session] | ||
فهرست شبیهسازیها | [Calcul de l’impôt] | [/afficher-calcul-impot] | نمایش نمای محاسبه مالیات |
[Fin de session] | [/fin-session] | ||
خطاهای غیرمنتظره | [Calcul de l’impôt] | [/afficher-calcul-impot] | نمایش نمای محاسبه مالیات |
[Liste des simulations] | [/lister-simulations] | ||
[Fin de session] | [/fin-session] |
توجه داشته باشید که کلیک روی یک لینک، یک GET را به مقصد لینک اجرا میکند. عملیاتهای [/lister-simulations, /fin-session] با استفاده از یک عملیات GET پیادهسازی شدهاند، که به ما امکان میدهد آنها را به عنوان مقصد لینک تنظیم کنیم. وقتی عمل از طریق یک POST انجام میشود، دیگر نمیتوان از یک لینک استفاده کرد مگر اینکه با جاوااسکریپت ترکیب شود.
32.9.1. عمل [/afficher-calcul-impot]
از میان اقدامات فهرستشده در بالا، به نظر میرسد که اقدام [/afficher-calcul-impot] هنوز پیادهسازی نشده است. این یک عملیات ناوبری بین دو نما است: سرورهای jSON یا XML دلیلی برای پیادهسازی آن ندارند، زیرا مفهوم «نما» را تشخیص نمیدهند. این سرور HTML است که این مفهوم را معرفی میکند.
بنابراین باید اکشن [/afficher-calcul-impot] را پیادهسازی کنیم. این به ما امکان میدهد تا رویه پیادهسازی یک اکشن در سرور را بازبینی کنیم.
ابتدا باید یک کنترلر ثانویه جدید اضافه کنیم. آن را [AfficherCalculImpotController] نامگذاری خواهیم کرد:

این کنترلر باید به فایل پیکربندی [config] اضافه شود:
#کنترلکنندهها
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
…
# اقدامات مجاز و کنترلکنندههای آنها
"controllers": {
# آغاز جلسه محاسباتی
"init-session": InitSessionController(),
#احراز هویت کاربر
"authentifier-utilisateur": AuthentifierUtilisateurController(),
#محاسبه مالیات در حالت فردی
"calculer-impot": CalculerImpotController(),
#محاسبه مالیات در حالت دستهای
"calculer-impots": CalculerImpotsController(),
# فهرست شبیهسازیها
"lister-simulations": ListerSimulationsController(),
# حذف یک شبیهسازی
"supprimer-simulation": SupprimerSimulationController(),
#پایان جلسه محاسبه
"fin-session": FinSessionController(),
#نمایش نمای محاسبه مالیات
"afficher-calcul-impot": AfficherCalculImpotController(),
#بازیابی دادهها از مراجع مالیاتی
"get-admindata": GetAdminDataController(),
# کنترلکننده اصلی
"main-controller": MainController()
},
…
# نماهای HTML و قالبهای آنها به وضعیتی که توسط کنترلکننده بازگردانده میشود بستگی دارند
"views": [
{
#نمای احراز هویت
…
},
{
#نمای محاسبه مالیات
"états": [
# موفقیت /احراز-هویت-کاربر
200,
#/محاسبه-مالیات موفق
300,
# /محاسبه-مالیات شکست
301,
# /نمایش-محاسبه-مالیات
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{…
}
],
- خط ۲: کنترلر جدید؛
- خط ۲۸: اکشن جدید و کنترلر آن؛
- خط ۵۱: کنترلکننده جدید کد وضعیت ۸۰۰ را برمیگرداند. هنگام تغییر نماها نباید خطایی رخ دهد. نمای نمایشدادهشده نمای [vue-calcul-impot.html] است که ما آن را مطالعه، توضیح و آزمایش کردهایم؛
کنترلر [AfficherCalculImpotController] به شرح زیر خواهد بود:
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):
# بازیابی عناصر مسیر
dummy, action = request.path.split('/')
#تغییر نما – فقط یک کد وضعیت برای تنظیم
return {"action": action, "état": 800, "réponse": ""}, status.HTTP_200_OK
نظرات
- خط ۶: مانند سایر کنترلکنندههای ثانویه، کنترلکننده جدید رابط [InterfaceController] را پیادهسازی میکند؛
- خط ۱۳: تغییرات نما ساده است: کافی است یک کد وضعیت مرتبط با نمای هدف را بازگردانید، در این مورد کد ۸۰۰ همانطور که در بالا مشاهده میشود؛
32.9.2. عمل [/fin-session]
عمل [/fin-session] ویژه است. این مستقیماً به یک نما نمیرود، بلکه به یک هدایت (redirection) منتهی میشود. به یاد داشته باشید که هدایتها در پیکربندی [config] به شرح زیر تنظیم میشوند:
# بازراستگذاری میکند
"redirections": [
{
"états": [
400, # /end-session موفق
],
# ارسال مجدد به
"to": "/init-session/html",
}
],
در این برنامه تنها یک هدایت مجدد وجود دارد:
- هنگامی که کنترلکننده کد وضعیت [400] (خط ۵) را بازمیگرداند، کلاینت باید به URL [http://machine:port/chemin/init-session/html] (خط ۸) هدایت شود؛
کد وضعیت [400] پس از یک اقدام موفق [/fin-session] بازگردانده میشود. پس چرا باید کلاینت به URL و [/init-session/html] هدایت شود؟ زیرا عملیات [/fin-session] نوع جلسه را از جلسه وب حذف میکند. سیستم دیگر تشخیص نمیدهد که در یک جلسه HTML قرار دارد. بنابراین باید هدایت شود. این کار با استفاده از عملیات [/init-session/html] انجام میشود.
redirectهای HTML توسط کلاس [HtmlResponse] مدیریت میشوند:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
#پاسخ HTML به کد وضعیت بازگرداندهشده توسط کنترلر بستگی دارد
état = résultat["état"]
#آیا باید هدایت انجام شود؟
for redirection in config["redirections"]:
#وضعیتهایی که نیاز به هدایت مجدد دارند
états = redirection["états"]
if état in états:
#یک هدایت لازم است
return redirect(f"{redirection['to']}"), status.HTTP_302_FOUND
#هر وضعیت با یک نما مطابقت دارد
# ما این را در فهرست نماها جستجو میکنیم
..
- خطوط ۶ تا ۱۲ هدایتها را مدیریت میکنند؛
- خط ۷: config[‘redirections’] فهرستی از redirectها است. هر redirect یک دیکشنری با کلیدهای زیر است:
- [états]: وضعیتهای بازگرداندهشده توسط کنترلر که منجر به هدایت میشوند؛
- [to]: آدرس هدایت؛
- خطوط ۷–۱۲: لیست ریدایرکتها بهصورت حلقهای پردازش میشود؛
- خط ۹: برای هر هدایت، وضعیتهای منتهی به آن بازیابی میشوند؛
- خط ۱۰: اگر حالت در حال آزمایش در این لیست باشد، آنگاه هدایت انجام میشود (خط ۱۲);
- خط ۱۲: توجه کنید که متد [build_http_response] باید یک توییپ با دو عنصر را بازگرداند:
- [response]: پاسخ HTTP که باید تولید شود. این با استفاده از تابع [redirect] ساخته میشود که پارامتر آن آدرس هدایت مجدد است؛
- [status_code]: کد وضعیت پاسخ HTTP، در این مورد کد [status.HTTP_302_FOUND]، که به کلاینت دستور میدهد هدایت مجدد انجام دهد؛
بیایید یک تست اجرا کنیم: [Postman]. ما:
- یک جلسه را با HTML [init-session/html] راهاندازی میکند؛
- احراز هویت: [/authentifier-utilisateur];
- پایان جلسه [/fin-session];

پاسخ سرور به شرح زیر است:

ما نمای احراز هویت را به دست آوردیم. این دقیقاً همان چیزی است که انتظار داشتیم. اکنون ببینیم چگونه به دست آمد. به کنسول [Postman] (Ctrl-Alt-C) سوئیچ کنید:

- در [1]، اقدام [/fin-session];
- به [2-3]؛ کد وضعیت 302 HTTP که توسط سرور بازگردانده میشود به کلاینت میگوید که در حال هدایت است؛
- در [4]، کلاینت [Postman] هدایت را دنبال میکند؛
32.10. آزمایش برنامه HTML در شرایط واقعی
کد نوشته شده و هر اقدام با استفاده از [Postman] آزمایش شده است. اکنون باید توالی نماها را در یک سناریوی دنیای واقعی آزمایش کنیم. ما به روشی نیاز داریم تا جلسه HTML را راهاندازی کنیم. ما میدانیم که باید درخواست [/init-session/html] را به سرور ارسال کنیم. این URL چندان عملی نیست. ترجیح میدهیم با URL [/] شروع کنیم.
ما مسیر زیر را در اسکریپت اصلی [main] نوشتهایم:
from flask import request, Flask, session, url_for, redirect
…
…
@app.route('/', methods=['GET'])
def index() -> tuple:
# ارسال مجدد به /init-session/html
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:
#کنترلر مرتبط با اکشن اجرا میشود
return front_controller()
- خطوط ۴–۷: رسیدگی به مسیر [/]. نقطهٔ ورود به برنامهٔ وب URL[/init-session/html] (خط ۱۰) خواهد بود. همچنین، در خط ۷، ما کلاینت را به این URL هدایت میکنیم:
- تابع [url_for] در خط ۱ وارد شده است. این تابع در اینجا دو پارامتر دارد (خط ۷):
- پارامتر اول نام یکی از توابع مسیریابی است، در این مورد، آنی که در خط ۱۱ قرار دارد. میبینیم که این تابع منتظر پارامتر با نام [type_response] است، که نوع (json, xml, html) پاسخی است که توسط کلاینت درخواست شده است؛
- پارامتر دوم نام پارامتر از خط ۱۱، [type_response] را میگیرد و مقداری را به آن اختصاص میدهد. اگر پارامترهای دیگری وجود داشتند، این عملیات برای هر یک از آنها تکرار میشد؛
- این تابع، URL مرتبط با تابع مشخصشده توسط دو پارامتر ارائهشده به آن را برمیگرداند. در اینجا، این تابع، URL از خط ۱۰ را برمیگرداند، که در آن پارامتر با مقدار خود، [/init-session/html]، جایگزین شده است؛
- تابع [redirect] در خط ۱ وارد شده است. نقش آن ارسال یک هدر تغییر مسیر HTTP به کلاینت است:
- پارامتر اول، URL است که باید کلاینت به آن هدایت شود؛
- پارامتر دوم کد وضعیت پاسخ HTTP است که به کلاینت ارسال میشود. کد [status.HTTP_302_FOUND] معادل یک هدایت HTTP است؛
ما آمادهایم. اکنون به چند توالی نما نگاهی میاندازیم.
در مرورگر خود ردیابی درخواست را فعال میکنیم (F12 در کروم، فایرفاکس و اج) و درخواست اولیه URL را از [http://localhost:5000/] میفرستیم. پاسخ سرور به شرح زیر است:

اگر به ترافیک شبکه بین کلاینت و سرور نگاه کنیم:

- میتوانیم ببینیم که در [4, 5]، مرورگر درخواستی برای هدایت به URL [/init-session/html] دریافت کرده است؛
بیایید فرم دریافتی را پر کنیم؛

سپس بیایید چند شبیهسازی را اجرا کنیم:


بیایید فهرست شبیهسازیها را درخواست کنیم:

بیایید اولین شبیهسازی را حذف کنیم:

بیایید جلسه را پایان دهیم:

از خوانندگان دعوت میشود تا آزمایشهای بیشتری انجام دهند.