Skip to content

36. تمرین عملی: نسخه ۱۶

Image

36.1. مقدمه

عمل URL در برنامه ما در حال حاضر به شکل [/action/param1/param2/…] است. ما می‌خواهیم بتوانیم این URL را پیش‌وندگذاری کنیم. برای مثال، با پیش‌وند [/do]، ما URL را در قالب [/do/action/param1/param2/…] خواهیم داشت.

36.2. پیکربندی جدید مسیر

Image

  • به [2]، محاسبه مسیر اصلاح خواهد شد؛
  • در [1]، فایل [config] برای انعکاس این تغییر اصلاح می‌شود؛

پیکربندی [config] به شرح زیر تغییر می‌یابد:


def configure(config: dict) -> dict:
    #پیکربندی syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    #پیکربندی برنامه
    import parameters
    config['parameters'] = parameters.configure(config)

    #پیکربندی پایگاه داده
    import database
    config["database"] = database.configure(config)

    # مصالحه‌سازی لایه‌های برنامه
    import layers
    config['layers'] = layers.configure(config)

    #پیکربندی لایه MVC
    config['mvc'] = {}

    #پیکربندی کنترل‌کننده‌های لایه [web]
    import controllers
    config['mvc'].update(controllers.configure(config))

    # ASV اقدامات (اقدام نمایش نما)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    # ADS اقدامات (اقدام انجام دادن)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #پیکربندی پاسخ HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #پیکربندی مسیر
    import routes
    routes.configure(config)

    #اعمال پیکربندی
    return config
  • خطوط ۳۷–۳۹: ماژول [routes] (خط ۳۸) مسئول پیکربندی مسیرها (خط ۳۹) است؛

فایل مربوط به مسیرهای بدون توکن، CSRF [configs/routes_without_csrftoken]، به شرح زیر تغییر می‌یابد:


#وابستگی‌ها

from flask import redirect, request, session, url_for
from flask_api import status

# پیکربندی برنامه
config = {}

# کنترل‌کنندهٔ جلویی
def front_controller() -> tuple:
    #درخواست به کنترلر اصلی ارجاع داده می‌شود
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#ریشهٔ برنامه
def index() -> tuple:
    # ارسال مجدد به /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

#init-session
def init_session(type_response: str) -> tuple:
    #کنترل‌کننده‌ی مرتبط با اکشن اجرا می‌شود
    return front_controller()

#احراز هویت کاربر
def authentifier_utilisateur() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#محاسبه-مالیات
def calculer_impot() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()


فایل از مسیرهای خود خالی شده است. تنها توابع مرتبط با آن‌ها باقی مانده‌اند.

فایل‌های مسیر حاوی توکن‌ها، CSRF و [configs/routes_with_csrftoken]، تحت همان فرآیند قرار می‌گیرند:


# وابستگی‌ها

from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf

# پیکربندی
config = {}

#کنترل‌کنندهٔ جلویی
def front_controller() -> tuple:
    #درخواست به کنترل‌کننده اصلی ارجاع داده می‌شود
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#ریشهٔ برنامه
def index() -> tuple:
    # ارسال مجدد به /init-session/html
    return redirect(url_for("init_session", type_response="html"csrf_token=generate_csrf()), status.HTTP_302_FOUND)

#init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#احراز هویت کاربر
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # اجرای کنترلر مرتبط با اکشن
    return front_controller()



#init-session-without-csrftoken برای کلاینت‌های JSON و XML
def init_session_without_csrftoken(type_response: str) -> tuple:
    # ارسال مجدد به /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

مسیرها توسط ماژول زیر محاسبه می‌شوند: [configs/routes]:


from flask import Flask

def configure(config: dict):
    #پیکربندی مسیر

    # برنامه Flask
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    #وارد کردن مسیرهای برنامه وب
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    #تزریق پیکربندی به مسیرها
    routes.config = config

    # پیشوند برنامه URL
    prefix_url = config["parameters"]["prefix_url"]

    # توکن CSRF
    with_csrftoken = config["parameters"]['with_csrftoken']
    if with_csrftoken:
        csrftoken_param = f"/<string:csrf_token>"
    else:
        csrftoken_param = ""

    # مسیرهای برنامه Flask
    #ریشهٔ برنامه
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    # init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    #init-session-without-csrftoken
    if with_csrftoken:
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
                         methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)

    # احراز هویت کاربر
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    # محاسبه-مالیات
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    #محاسبه دسته‌ای مالیات
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    # فهرست-شبیه‌سازی‌ها
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    # حذف-شبیه‌سازی
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    # پایان-جلسه
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    #نمایش محاسبه مالیات
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    # get-admindata
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    #مشاهده-نمای-کلی-محاسبه-مالیات
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    # نمایش-نما-احراز-هویت
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    #نمایش-فهرست-مشاهده-شبیه‌سازی
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    #نمایش-فهرست-نمایش-خطا
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    # مورد خاص
    if with_csrftoken:
        # راه‌اندازی جلسه بدون توکن CSRF برای مشتریان JSON و XML
        app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
                         view_func=routes.init_session_without_csrftoken)
  • خطوط ۶–۸: برنامه Flask ایجاد شده و به پیکربندی اضافه می‌شود؛
  • خطوط ۱۰–۱۴: فایل مسیرها (routes) مناسب با موقعیت وارد می‌شود. توجه داشته باشید که فایل واردشده در واقع هیچ مسیری را در خود ندارد؛ بلکه تنها شامل توابعی است که با آن‌ها مرتبط هستند؛
  • خط ۱۷: توابع مرتبط با مسیرها باید پیکربندی برنامه را بدانند؛
  • خط ۲۰: به پیشوند URL توجه کنید. این پیشوند ممکن است خالی باشد؛
  • خطوط 22–27: مسیرهایی که دارای توکن CSRF هستند، در مقایسه با آنهایی که این توکن را ندارند، یک پارامتر اضافی دارند. برای رسیدگی به این تفاوت، متغیر [csrftoken_param] استفاده می‌شود:
    • اگر توکن CSRF در مسیرها وجود نداشته باشد، حاوی یک رشتهٔ خالی است؛
    • اگر توکن CSRF وجود داشته باشد، حاوی رشته [/<string:csrf_token>] است؛
  • خطوط 29–96: هر مسیر در برنامه با تابعی از فایل مسیرها که در خطوط 10–14 وارد شده است مرتبط است؛

36.3. کنترل‌کننده‌های جدید

Image

در نسخه قبلی، همه کنترل‌کننده‌ها اقدام در حال پردازش را به شرح زیر دریافت می‌کردند:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #بازیابی عناصر مسیر
        params = request.path.split('/')
        action = params[1]

این کد دیگر در صورتی که پیشوندی وجود داشته باشد، برای مثال [/do/lister-simulations]، کار نخواهد کرد. در این حالت، اکشن در خط ۳، [do] خواهد بود و بنابراین نادرست خواهد بود.

ما این کد را به شکل زیر تبدیل می‌کنیم:


    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #بازیابی عناصر مسیر
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

ما این کار را در تمام کنترلرها انجام می‌دهیم.

36.4. قالب‌های جدید

Image

در نسخه قبلی، هر کلاس قالب یک قالب را به صورت زیر تولید می‌کرد:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # دربرگیری داده‌های صفحه در یک قالب
        modèle = {}
        #وضعیت برنامه
        état = résultat["état"]
        # قالب به وضعیت وابسته است
        

        # توکن CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # اقدامات ممکن از دیدگاه
        modèle['actions_possibles'] = []

        # قالب رندر می‌شود
        return modèle

از این پس، قالب یک کلید اضافی با پیشوند URL خواهد داشت:


def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # دربرگرفتن داده‌های صفحه در قالب
        modèle = {}
        #وضعیت برنامه
        état = résultat["état"]
        # مدل به وضعیت وابسته است
        

        #توکن CSRF
        modèle['csrf_token'] = super().get_csrftoken(config)

        # اقدامات ممکن از دیدگاه
        modèle['actions_possibles'] = []
    
        #پیشوند برای URL
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        # قالب رندر می‌شود
        return modèle

36.5. قطعات جدید

Image

تمام قطعات حاوی URL باید اصلاح شوند.

قطعه [v-authentification]


<!-- فرم HTML – مقادیر آن را با استفاده از اکشن [authentifier-utilisateur] ارسال می‌کنیم -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- عنوان -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>

قطعه [v-calcul-impot]


<!-- HTML فرم ارسال شد -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- پیامی در ۱۲ ستون روی پس‌زمینه آبی -->
    

</form>

قطعه [v-liste-simulations]




{% if modèle.simulations is defined and modèle.simulations|length!=0 %}


<!-- جدول شبیه‌سازی -->
<table class="table table-sm table-hover table-striped">
    
    <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="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

قطعه [v-menu]


<!-- منوی بوت‌استرپ -->
<nav class="nav flex-column">
    <!--نمایش فهرستی از پیوندها HTML -->
    {% for optionMenu in modèle.optionsMenu %}
    <a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

36.6. پاسخ جدید HTML

Image

در نسخه قبلی، کد پاسخ HTML با یک هدایت پایان یافت:


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"]

        

        #اکنون باید هدایت URL تولید شود، بدون فراموش کردن توکن CSRF در صورت نیاز
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #پاسخ هدایت
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

این کد اکنون به شرح زیر است:


def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        

        # اکنون باید کد هدایت URL را تولید کنیم، بدون فراموش کردن توکن CSRF در صورت درخواست
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # پاسخ هدایت
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. آزمایش‌ها

Image

بیایید پیشوند زیر را به فایل پیکربندی [parameters] اضافه کنیم:



        # توکن CSRF
        "with_csrftoken"True,
        # پایگاه‌های داده مدیریت‌شده: MySQL (MySQL), PostgreSQL (PostgreSQL)
        "databases"["mysql""pgres"],
        #پیشوند برنامه برای URL
        # اگر نیازی به پیش‌وند نباشد، رشته را خالی تنظیم کنید، در غیر این صورت از /prefix استفاده کنید
        "prefix_url""/do",

بیایید برنامه را اجرا کرده و سپس URL [http://localhost:5000/do] را درخواست کنیم؛ پاسخ به شرح زیر است:

Image

  • در [1]، پیشوند URL؛
  • به [2]، توکن برای CSRF؛

برنامه را می‌توان با استفاده از تست‌های کنسول برای [http-clients/09] نیز آزمایش کرد:

Image

در فایل‌های پیکربندی [1] و [2]، پیشوند از URL باید در URL سرور گنجانده شود:


        "server": {
            # "urlServer": "http://127.0.0.1:5000",
            "urlServer""http://127.0.0.1:5000/do",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                
            }
  • خط ۳: URL سرور اکنون شامل پیشوند URL است؛

پس از اعمال این تغییر، تمام تست‌های کنسول باید کار کنند.