اعتبارسنجی ورودی ها در Flask: بهترین روش‌ها

فهرست مطالب

اعتبارسنجی ورودی‌ها در Flask: بهترین روش‌ها

توسعه‌دهندگان وب مدرن همواره با چالش تضمین امنیت، یکپارچگی داده‌ها و تجربه کاربری در برنامه‌های خود روبرو هستند. در اکوسیستم پایتون، Flask به عنوان یک میکرو فریم‌ورک سبک و قدرتمند، گزینه‌ای محبوب برای ساخت برنامه‌های وب و APIهای RESTful است. با این حال، آزادی عمل Flask به این معنی است که مسئولیت بسیاری از جنبه‌های حیاتی، از جمله اعتبارسنجی ورودی‌ها، بر عهده توسعه‌دهنده است. اعتبارسنجی ورودی نه تنها یک گام اساسی در جلوگیری از حملات امنیتی مانند تزریق SQL یا اسکریپت‌نویسی بین سایتی (XSS) است، بلکه برای حفظ کیفیت داده‌ها و ارائه بازخورد سازنده به کاربران نیز ضروری است. در این مقاله جامع، به بررسی عمیق بهترین روش‌ها، ابزارها و تکنیک‌ها برای پیاده‌سازی اعتبارسنجی ورودی قوی و مؤثر در برنامه‌های Flask می‌پردازیم. هدف ما توانمندسازی شما برای ساخت برنامه‌های Flask ایمن، پایدار و قابل اعتماد است.

مقدمه‌ای بر اعتبارسنجی ورودی در Flask: چرا حیاتی است؟

در هر برنامه‌ای که داده‌ها را از طریق ورودی کاربر (مانند فرم‌های وب، پارامترهای URL یا بدنه درخواست‌های API) دریافت می‌کند، اعتبارسنجی ورودی (Input Validation) یک خط دفاعی غیرقابل چشم‌پوشی است. در فریم‌ورک Flask، که رویکرد “Do It Yourself” را در پیش می‌گیرد، این مسئولیت به طور کامل بر دوش توسعه‌دهنده است. نادیده گرفتن یا اجرای ناقص اعتبارسنجی ورودی می‌تواند منجر به طیف وسیعی از مشکلات جدی شود که فراتر از صرفاً “خطاهای کاربر” است.

یکی از دلایل اصلی اهمیت اعتبارسنجی، جنبه امنیتی آن است. ورودی‌های غیرمعتبر یا مخرب می‌توانند به عنوان وکتورهایی برای حملات سایبری مورد استفاده قرار گیرند. به عنوان مثال، اگر یک مهاجم بتواند کدهای SQL را در فیلدی که برای نام کاربری در نظر گرفته شده وارد کند، ممکن است منجر به حمله تزریق SQL شود و دسترسی غیرمجاز به پایگاه داده یا افشای اطلاعات حساس را فراهم آورد. به همین ترتیب، وارد کردن اسکریپت‌های جاوااسکریپت مخرب در فیلدهای متنی می‌تواند زمینه‌ساز حملات XSS شود که می‌تواند اطلاعات نشست کاربر را به سرقت ببرد یا کنترل مرورگر او را در دست بگیرد. حملات Path Traversal، Remote Code Execution و Directory Listing نیز از جمله تهدیداتی هستند که از طریق عدم اعتبارسنجی صحیح ورودی‌ها ممکن است برنامه‌ها را هدف قرار دهند.

فراتر از امنیت، اعتبارسنجی ورودی برای حفظ یکپارچگی داده‌ها (Data Integrity) نیز ضروری است. فرض کنید برنامه شما انتظار دارد که سن کاربر یک عدد صحیح مثبت باشد. اگر کاربر یک رشته متنی یا یک عدد منفی وارد کند، بدون اعتبارسنجی، این داده‌های نامعتبر ممکن است در پایگاه داده شما ذخیره شوند. این نه تنها منجر به داده‌های “کثیف” می‌شود که پردازش آن‌ها دشوار است، بلکه می‌تواند باعث بروز خطاهای منطقی در بخش‌های دیگر برنامه شود که بر اساس فرض صحت داده‌ها عمل می‌کنند. داده‌های نامعتبر می‌توانند گزارش‌ها را بی‌اعتبار کرده، الگوریتم‌ها را با مشکل مواجه سازند و حتی به خرابی‌های فاجعه‌بار منجر شوند.

در نهایت، اعتبارسنجی ورودی تأثیر مستقیمی بر تجربه کاربری (User Experience) دارد. وقتی کاربر اطلاعات نادرستی را وارد می‌کند، مهم است که بلافاصله و به وضوح به او بازخورد داده شود که چه چیزی اشتباه بوده و چگونه باید آن را اصلاح کند. پیام‌های خطای مبهم مانند “خطا در پردازش درخواست شما” برای کاربر خسته‌کننده هستند. در مقابل، پیام‌های خطای دقیق و راهنما مانند “لطفاً یک آدرس ایمیل معتبر وارد کنید” یا “رمز عبور باید حداقل 8 کاراکتر باشد و شامل حروف و اعداد باشد” به کاربر کمک می‌کنند تا به سرعت مشکل را برطرف کرده و فرایند را ادامه دهد. این رویکرد به کاربران احساس کنترل و اطمینان بیشتری می‌دهد و از سردرگمی و ناامیدی جلوگیری می‌کند. به طور خلاصه، اعتبارسنجی ورودی در Flask نه تنها یک ویژگی “nice-to-have” نیست، بلکه یک رکن اساسی برای ساخت برنامه‌های وب قوی، ایمن و کاربرپسند است.

خطرات عدم اعتبارسنجی مناسب ورودی‌ها

نادیده گرفتن اعتبارسنجی ورودی یا اجرای ضعیف آن، راه را برای طیف وسیعی از آسیب‌پذیری‌ها و مشکلات جدی در برنامه‌های Flask باز می‌کند. این مشکلات می‌توانند از نقض امنیتی فاجعه‌بار تا مسائل مربوط به پایداری و عملکرد برنامه متغیر باشند. درک این خطرات به توسعه‌دهندگان کمک می‌کند تا ضرورت اعتبارسنجی قوی را جدی بگیرند.

حملات امنیتی

  1. تزریق SQL (SQL Injection): این یکی از شایع‌ترین و خطرناک‌ترین حملات است. اگر ورودی کاربر بدون پاک‌سازی یا پارامتری‌سازی صحیح مستقیماً در یک کوئری SQL استفاده شود، مهاجم می‌تواند کدهای SQL دلخواه را وارد کند. این می‌تواند منجر به افشای کل پایگاه داده، تغییر یا حذف اطلاعات، یا حتی اجرای دستورات سیستمی شود. به عنوان مثال، یک مهاجم ممکن است در فیلد نام کاربری `admin’ OR ‘1’=’1` را وارد کند تا بدون داشتن رمز عبور وارد شود.
  2. اسکریپت‌نویسی بین سایتی (XSS – Cross-Site Scripting): حملات XSS زمانی رخ می‌دهند که برنامه شما ورودی‌های شامل کدهای جاوااسکریپت مخرب را بدون پاک‌سازی مناسب دریافت کرده و سپس آن را در صفحات وب برای سایر کاربران نمایش دهد. این اسکریپت‌ها می‌توانند کوکی‌های کاربران را به سرقت ببرند، اطلاعات حساس را به مهاجم ارسال کنند، یا حتی ظاهر و رفتار صفحه را تغییر دهند. سه نوع اصلی XSS وجود دارد: ذخیره شده (Stored XSS)، منعکس شده (Reflected XSS) و مبتنی بر DOM (DOM-based XSS).
  3. حملات Path Traversal / Directory Traversal: اگر برنامه شما از ورودی کاربر برای ساخت مسیرهای فایل‌ها استفاده کند و این ورودی به درستی اعتبارسنجی نشود، مهاجم می‌تواند از کاراکترهایی مانند `../` برای دسترسی به فایل‌ها و دایرکتوری‌های خارج از دایرکتوری مورد انتظار استفاده کند. این می‌تواند منجر به افشای فایل‌های پیکربندی حساس، کدهای منبع، یا سایر اطلاعات محرمانه شود.
  4. اجرای کد از راه دور (Remote Code Execution – RCE): در موارد شدید، عدم اعتبارسنجی می‌تواند به مهاجم اجازه دهد کدهای دلخواه را روی سرور برنامه اجرا کند. این ممکن است از طریق تزریق در توابع `eval()` یا `exec()` پایتون (که معمولاً توصیه نمی‌شوند) یا سایر آسیب‌پذیری‌های پیچیده‌تر که به دلیل پردازش ورودی‌های نامعتبر رخ می‌دهند، اتفاق بیفتد.
  5. حملات CSRF (Cross-Site Request Forgery): اگرچه CSRF مستقیماً به اعتبارسنجی ورودی مربوط نمی‌شود، اما فریم‌ورک‌های اعتبارسنجی فرم مانند Flask-WTF اغلب شامل حفاظت در برابر CSRF نیز هستند. CSRF به مهاجم اجازه می‌دهد تا یک مرورگر وب کاربر احراز هویت شده را مجبور به ارسال یک درخواست مخرب به برنامه شما کند.

مسائل مربوط به یکپارچگی داده‌ها و منطق برنامه

  1. داده‌های نامعتبر در پایگاه داده: ذخیره داده‌های نامعتبر می‌تواند منجر به “کثیف شدن” پایگاه داده شود. این داده‌ها ممکن است باعث بروز خطاهای غیرمنتظره در کوئری‌ها، گزارش‌گیری‌ها و سایر عملیات‌های داده‌ای شوند. به عنوان مثال، ذخیره یک رشته به جای عدد در فیلد “سن” می‌تواند باعث خرابی برنامه هنگام تلاش برای انجام محاسبات ریاضی روی آن فیلد شود.
  2. خطاهای منطقی برنامه: اگر برنامه شما بر اساس فرض صحت یک نوع داده خاص عمل کند، ورود داده‌های نامعتبر می‌تواند این فرض را نقض کرده و منجر به رفتار غیرمنتظره و باگ‌های سخت‌یابی شود. این می‌تواند شامل محاسبه‌های نادرست، مسیریابی اشتباه، یا حتی حلقه های بی‌نهایت باشد.
  3. کاهش عملکرد: پردازش و تلاش برای تطبیق با داده‌های نامعتبر می‌تواند سربار اضافی بر برنامه وارد کند. در نهایت، با انباشت داده‌های نامعتبر، ممکن است نیاز به فرایندهای پاک‌سازی داده‌ها باشد که زمان‌بر و پرهزینه هستند.

تجربه کاربری ضعیف

  1. پیام‌های خطای مبهم: بدون اعتبارسنجی صریح، کاربران ممکن است با پیام‌های خطای عمومی و غیرمفیدی مواجه شوند که به آنها نمی‌گوید چگونه مشکل را حل کنند. این منجر به ناامیدی و ترک برنامه می‌شود.
  2. از دست دادن داده‌ها: در برخی موارد، کاربر ممکن است اطلاعات زیادی را در یک فرم وارد کند، اما به دلیل یک خطای کوچک در یک فیلد، کل اطلاعات ورودی از بین برود. اعتبارسنجی مناسب می‌تواند این مشکل را با حفظ ورودی‌های معتبر و تنها درخواست تصحیح موارد نامعتبر، حل کند.
  3. افزایش تلاش کاربر: کاربران مجبور می‌شوند چندین بار اطلاعات را وارد کنند تا بالاخره ورودی مورد قبول برنامه واقع شود. این فرایند نه تنها زمان‌بر است، بلکه به تجربه کاربری آسیب می‌رساند.

در نهایت، عدم اعتبارسنجی مناسب ورودی‌ها می‌تواند به اعتبار و شهرت برنامه و شرکت شما آسیب جدی وارد کند. یک رخنه امنیتی می‌تواند منجر به از دست دادن اعتماد مشتریان، جریمه‌های قانونی و هزینه‌های هنگفت برای بازیابی و ترمیم شود. بنابراین، اعتبارسنجی ورودی یک سرمایه‌گذاری حیاتی در امنیت، پایداری و موفقیت هر برنامه Flask است.

روش‌های پایه اعتبارسنجی ورودی در Flask (دستی)

قبل از پرداختن به کتابخانه‌های پیشرفته، درک نحوه اعتبارسنجی دستی ورودی‌ها در Flask اهمیت دارد. این روش برای سناریوهای ساده یا زمانی که نیاز به کنترل بسیار دقیق دارید، مفید است. Flask به شما امکان می‌دهد به راحتی به داده‌های ارسالی توسط کاربر از طریق شیء request دسترسی پیدا کنید.

دسترسی به داده‌های ورودی

بسته به نوع درخواست (GET، POST) و نحوه ارسال داده‌ها (فرم HTML، JSON، پارامترهای URL)، Flask روش‌های مختلفی برای دسترسی به ورودی‌ها ارائه می‌دهد:

  1. داده‌های فرم (POST): برای درخواست‌های POST که از طریق فرم‌های HTML ارسال می‌شوند، داده‌ها در request.form در دسترس هستند. این یک شیء ImmutableMultiDict است که مانند یک دیکشنری عمل می‌کند.
  2. داده‌های JSON (POST، PUT، PATCH): برای APIها یا درخواست‌هایی که داده‌ها را به صورت JSON در بدنه درخواست ارسال می‌کنند، از request.json یا request.get_json() استفاده می‌شود.
  3. پارامترهای کوئری (GET): برای پارامترهای موجود در URL (مانند /search?q=flask)، از request.args استفاده می‌شود. این نیز یک ImmutableMultiDict است.
  4. فایل‌ها: برای آپلود فایل‌ها، از request.files استفاده می‌شود.

مثال اعتبارسنجی دستی برای یک فرم ساده

فرض کنید یک فرم ثبت‌نام کاربر دارید که شامل نام کاربری، ایمیل و رمز عبور است. ما می‌خواهیم مطمئن شویم که هیچ‌کدام از فیلدها خالی نباشند، ایمیل فرمت صحیحی داشته باشد و رمز عبور حداقل طول مشخصی را رعایت کند.


from flask import Flask, request, jsonify, render_template

app = Flask(__name__)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        email = request.form.get('email')
        password = request.form.get('password')

        errors = {}

        if not username:
            errors['username'] = 'نام کاربری نمی‌تواند خالی باشد.'
        elif len(username) < 3:
            errors['username'] = 'نام کاربری باید حداقل 3 کاراکتر باشد.'

        if not email:
            errors['email'] = 'ایمیل نمی‌تواند خالی باشد.'
        elif '@' not in email or '.' not in email: # یک بررسی بسیار پایه
            errors['email'] = 'فرمت ایمیل نامعتبر است.'

        if not password:
            errors['password'] = 'رمز عبور نمی‌تواند خالی باشد.'
        elif len(password) < 8:
            errors['password'] = 'رمز عبور باید حداقل 8 کاراکتر باشد.'

        if errors:
            # اگر خطایی وجود دارد، آنها را به کاربر برگردانید
            return render_template('register.html', errors=errors, 
                                   username=username, email=email)
        
        # اگر همه چیز معتبر است، کاربر را ثبت‌نام کنید
        # ...
        return render_template('success.html', message='ثبت‌نام با موفقیت انجام شد!')

    return render_template('register.html')

if __name__ == '__main__':
    app.run(debug=True)

و فایل templates/register.html می‌تواند به این شکل باشد:


<!DOCTYPE html>
<html lang="fa">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ثبت‌نام</title>
</head>
<body>
    <h1>فرم ثبت‌نام</h1>
    <form method="POST">
        <label for="username">نام کاربری:</label><br>
        <input type="text" id="username" name="username" value="{{ username or '' }}"><br>
        {% if errors and errors.username %}<span style="color: red;">{{ errors.username }}</span><br>{% endif %}
        <br>

        <label for="email">ایمیل:</label><br>
        <input type="email" id="email" name="email" value="{{ email or '' }}"><br>
        {% if errors and errors.email %}<span style="color: red;">{{ errors.email }}</span><br>{% endif %}
        <br>

        <label for="password">رمز عبور:</label><br>
        <input type="password" id="password" name="password"><br>
        {% if errors and errors.password %}<span style="color: red;">{{ errors.password }}</span><br>{% endif %}
        <br>
        
        <input type="submit" value="ثبت‌نام">
    </form>
</body>
</html>
```

اعتبارسنجی دستی برای API با داده‌های JSON

برای یک API، پاسخ معمولاً JSON است و کدهای وضعیت HTTP برای نشان دادن موفقیت یا خطا استفاده می‌شوند.


@app.route('/api/users', methods=['POST'])
def create_user_api():
    data = request.get_json()
    errors = {}

    if not data:
        return jsonify({'message': 'درخواست باید شامل داده‌های JSON باشد.'}), 400

    username = data.get('username')
    email = data.get('email')
    password = data.get('password')

    if not username:
        errors['username'] = 'نام کاربری الزامی است.'
    elif len(username) < 3:
        errors['username'] = 'نام کاربری باید حداقل 3 کاراکتر باشد.'

    if not email:
        errors['email'] = 'ایمیل الزامی است.'
    elif '@' not in email or '.' not in email:
        errors['email'] = 'فرمت ایمیل نامعتبر است.'

    if not password:
        errors['password'] = 'رمز عبور الزامی است.'
    elif len(password) < 8:
        errors['password'] = 'رمز عبور باید حداقل 8 کاراکتر باشد.'

    if errors:
        return jsonify({'errors': errors}), 400 # Bad Request

    # پردازش کاربر
    # ...
    return jsonify({'message': 'کاربر با موفقیت ایجاد شد.'}), 201 # Created

مزایا و معایب اعتبارسنجی دستی

مزایا:

  • کنترل کامل: شما کنترل کاملی بر روی نحوه انجام اعتبارسنجی و نحوه مدیریت خطاها دارید.
  • بدون وابستگی: نیازی به نصب کتابخانه اضافی نیست، که برای پروژه‌های کوچک یا زمانی که وابستگی‌ها را به حداقل می‌رسانید، مناسب است.
  • سادگی برای موارد خاص: برای اعتبارسنجی‌های بسیار ساده و یک‌بار مصرف، می‌تواند سریع‌تر باشد.

معایب:

  • تکرار کد (Boilerplate): با افزایش تعداد فرم‌ها و فیلدها، مقدار زیادی کد تکراری برای هر فیلد و هر اعتبارسنجی خواهید داشت.
  • کاهش خوانایی: منطق اعتبارسنجی با منطق اصلی ویو مخلوط می‌شود و خوانایی کد را کاهش می‌دهد.
  • نگهداری دشوار: به‌روزرسانی یا اضافه کردن قوانین اعتبارسنجی جدید می‌تواند پرزحمت و مستعد خطا باشد.
  • فرصت برای خطا: احتمال فراموش کردن یک اعتبارسنجی یا انجام اشتباه آن بیشتر است.
  • عدم حمایت از جنبه‌های پیشرفته: مسائلی مانند اعتبارسنجی CSRF، فیلتر کردن داده‌ها، و اعتبارسنجی از سمت کلاینت (client-side validation) در این روش پوشش داده نمی‌شوند.

در حالی که اعتبارسنجی دستی یک نقطه شروع خوب است، برای اکثر برنامه‌های واقعی Flask با پیچیدگی متوسط به بالا، استفاده از کتابخانه‌های تخصصی اعتبارسنجی به شدت توصیه می‌شود. این کتابخانه‌ها نه تنها به کاهش کد تکراری کمک می‌کنند، بلکه قابلیت‌های پیشرفته و راه‌حل‌های اثبات‌شده‌ای را ارائه می‌دهند که توسعه را سریع‌تر، ایمن‌تر و پایدارتر می‌سازند.

اعتبارسنجی با استفاده از کتابخانه‌های قدرتمند

برای غلبه بر مشکلات و محدودیت‌های اعتبارسنجی دستی، جامعه پایتون و Flask مجموعه‌ای از کتابخانه‌های قدرتمند را برای مدیریت اعتبارسنجی ورودی توسعه داده است. این کتابخانه‌ها نه تنها کد شما را تمیزتر و قابل نگهداری‌تر می‌کنند، بلکه قابلیت‌های پیشرفته‌ای مانند حفاظت CSRF، تبدیل نوع داده، و ساختارهای اعتبارسنجی قابل استفاده مجدد را نیز ارائه می‌دهند. در ادامه به معرفی و بررسی سه کتابخانه محبوب می‌پردازیم: Flask-WTF، Marshmallow و Pydantic.

Flask-WTF و WTForms

Flask-WTF یک افزونه Flask است که از کتابخانه WTForms برای ساخت فرم‌های وب و اعتبارسنجی آن‌ها استفاده می‌کند. این یکی از پرکاربردترین راه حل‌ها برای اعتبارسنجی فرم‌ها در برنامه‌های Flask مبتنی بر تمپلیت است و همچنین قابلیت‌های مفیدی برای اعتبارسنجی APIها نیز ارائه می‌دهد. Flask-WTF به طور خودکار توکن‌های CSRF را مدیریت می‌کند و از فرم‌ها در برابر حملات جعل درخواست بین سایتی محافظت می‌نماید.

ویژگی‌های کلیدی:

  • اعتبارسنجی فرم: مجموعه‌ای گسترده از اعتبارسنجی‌های داخلی (مانند اجباری بودن، طول، فرمت ایمیل، اعداد) و امکان تعریف اعتبارسنجی‌های سفارشی.
  • حفاظت CSRF: به طور خودکار توکن‌های CSRF را در فرم‌ها اضافه و در زمان ارسال درخواست آن‌ها را اعتبارسنجی می‌کند.
  • فیلدها و ویجت‌ها: انواع مختلف فیلدهای HTML (متن، ایمیل، رمز عبور، انتخابگر) و ویجت‌های رندرینگ.
  • یکپارچگی آسان با Flask: به راحتی با سیستم تمپلیتینگ Jinja2 و شیء request در Flask کار می‌کند.

نصب:


pip install Flask-WTF

مثال استفاده:

ابتدا باید یک کلاس فرم تعریف کنید:


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError

class RegistrationForm(FlaskForm):
    username = StringField('نام کاربری', validators=[DataRequired(), Length(min=3, max=20)])
    email = StringField('ایمیل', validators=[DataRequired(), Email()])
    password = PasswordField('رمز عبور', validators=[DataRequired(), Length(min=8)])
    confirm_password = PasswordField('تکرار رمز عبور', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('ثبت‌نام')

    # می‌توانید اعتبارسنجی‌های سفارشی را نیز در اینجا اضافه کنید
    def validate_username(self, username):
        # فرض کنید یک مدل User دارید و می‌خواهید منحصر به فرد بودن نام کاربری را چک کنید
        # از دیتابیس بپرسید: if User.query.filter_by(username=username.data).first():
        #    raise ValidationError('این نام کاربری از قبل وجود دارد.')
        pass

سپس در ویوی Flask خود از آن استفاده کنید:


from flask import Flask, render_template, flash, redirect, url_for
from your_forms_file import RegistrationForm # فرض کنید کلاس فرم در فایل دیگری است

app = Flask(__name__)
app.config['SECRET_KEY'] = 'a_very_secret_key_for_csrf_protection' # برای CSRF لازم است

@app.route('/register_wtf', methods=['GET', 'POST'])
def register_wtf():
    form = RegistrationForm()
    if form.validate_on_submit(): # اگر درخواست POST بود و اعتبارسنجی موفق بود
        username = form.username.data
        email = form.email.data
        password = form.password.data
        
        # ثبت‌نام کاربر در دیتابیس
        flash(f'کاربر {username} با موفقیت ثبت‌نام شد!', 'success')
        return redirect(url_for('success_page')) # یک صفحه موفقیت
    
    # اگر GET بود یا اعتبارسنجی ناموفق بود
    return render_template('register_wtf.html', form=form)

@app.route('/success')
def success_page():
    return "

ثبت‌نام موفقیت‌آمیز!

"

و فایل templates/register_wtf.html:


<!DOCTYPE html>
<html lang="fa">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ثبت‌نام با Flask-WTF</title>
</head>
<body>
    <h1>فرم ثبت‌نام با Flask-WTF</h1>
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            <ul>
                {% for category, message in messages %}
                    <li class="{{ category }}">{{ message }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
    <form method="POST">
        {{ form.csrf_token }} <!-- این توکن CSRF را به صورت خودکار اضافه می‌کند -->
        
        <p>
            {{ form.username.label }}<br>
            {{ form.username() }}<br>
            {% if form.username.errors %}<ul>{% for error in form.username.errors %}<li style="color: red;">{{ error }}</li>{% endfor %}</ul>{% endif %}
        </p>
        <p>
            {{ form.email.label }}<br>
            {{ form.email() }}<br>
            {% if form.email.errors %}<ul>{% for error in form.email.errors %}<li style="color: red;">{{ error }}</li>{% endfor %}</ul>{% endif %}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password() }}<br>
            {% if form.password.errors %}<ul>{% for error in form.password.errors %}<li style="color: red;">{{ error }}</li>{% endfor %}</ul>{% endif %}
        </p>
        <p>
            {{ form.confirm_password.label }}<br>
            {{ form.confirm_password() }}<br>
            {% if form.confirm_password.errors %}<ul>{% for error in form.confirm_password.errors %}<li style="color: red;">{{ error }}</li>{% endfor %}</ul>{% endif %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
</body>
</html>
```

Marshmallow: اعتبارسنجی و سریالی‌سازی قدرتمند

Marshmallow یک کتابخانه قدرتمند برای سریالی‌سازی/دسریالی‌سازی آبجکت‌ها و اعتبارسنجی داده‌ها در پایتون است. این کتابخانه به ویژه برای ساخت APIهای RESTful که نیاز به تبدیل آبجکت‌های پایتون به فرمت‌هایی مانند JSON و بالعکس دارند، بسیار مفید است. Marshmallow به شما اجازه می‌دهد تا اسکیماهای (schemas) داده‌ای تعریف کنید که هم ساختار داده‌ها را مشخص می‌کنند و هم قوانین اعتبارسنجی را اعمال می‌نمایند.

ویژگی‌های کلیدی:

  • سریالی‌سازی/دسریالی‌سازی: تبدیل آبجکت‌های پیچیده پایتون به فرمت‌های ساده‌تر (مانند دیکشنری‌های پایتون که قابل تبدیل به JSON هستند) و بالعکس.
  • اعتبارسنجی مبتنی بر اسکیما: تعریف قوانین اعتبارسنجی به صورت متمرکز در یک اسکیما.
  • فیلدها و ولیدیتورهای متنوع: مجموعه‌ای غنی از فیلدها (String, Integer, Nested, List) و ولیدیتورها (Email, Length, Range, Regexp).
  • یکپارچگی با ORMها: به خوبی با ORMهایی مانند SQLAlchemy کار می‌کند (از طریق Flask-Marshmallow).

نصب:


pip install marshmallow Flask-Marshmallow # اگر می‌خواهید با Flask یکپارچه کنید

مثال استفاده:


from flask import Flask, request, jsonify
from marshmallow import Schema, fields, validate, ValidationError

app = Flask(__name__)

class UserSchema(Schema):
    id = fields.Int(dump_only=True) # فقط برای سریال‌سازی، نه برای ورودی
    username = fields.Str(required=True, validate=validate.Length(min=3, max=20))
    email = fields.Email(required=True)
    password = fields.Str(required=True, validate=validate.Length(min=8))
    is_admin = fields.Bool(dump_default=False) # مقدار پیش‌فرض در زمان سریالی‌سازی

@app.route('/api/users_marshmallow', methods=['POST'])
def create_user_marshmallow():
    data = request.get_json()
    if not data:
        return jsonify({'message': 'درخواست باید شامل داده‌های JSON باشد.'}), 400

    user_schema = UserSchema()
    try:
        # load() داده‌ها را دسریالایز و اعتبارسنجی می‌کند
        validated_data = user_schema.load(data)
    except ValidationError as err:
        return jsonify({'errors': err.messages}), 400

    # اگر اعتبارسنجی موفق بود، می‌توانید از validated_data استفاده کنید
    # ... مثلاً یک کاربر جدید در دیتابیس ایجاد کنید
    # user = User(**validated_data)
    # db.session.add(user)
    # db.session.commit()

    # سریالی‌سازی آبجکت کاربر برای بازگشت
    # result = user_schema.dump(user) # اگر user یک آبجکت واقعی بود
    return jsonify({'message': 'کاربر با موفقیت ایجاد شد.', 'data': validated_data}), 201

در این مثال، UserSchema تمام قوانین اعتبارسنجی را در یک مکان متمرکز می‌کند. متد load() داده‌های ورودی را اعتبارسنجی و دسریالایز می‌کند و در صورت وجود خطا، یک ValidationError پرتاب می‌کند. dump() برعکس عمل می‌کند و یک آبجکت پایتون را به یک دیکشنری قابل سریالی‌سازی تبدیل می‌کند.

Pydantic: اعتبارسنجی با Type Hints پایتون

Pydantic یک کتابخانه مدرن است که از Type Hints پایتون برای اعتبارسنجی داده‌ها و تنظیمات استفاده می‌کند. این کتابخانه به سرعت محبوبیت پیدا کرده است، به خصوص در پروژه‌هایی که از FastAPI استفاده می‌کنند (که Pydantic را به عنوان هسته خود به کار می‌گیرد)، اما به راحتی می‌توان آن را در برنامه‌های Flask نیز به کار برد. Pydantic به شما امکان می‌دهد مدل‌های داده‌ای را با استفاده از کلاس‌های پایتون و type hints تعریف کنید، و سپس Pydantic به طور خودکار اعتبارسنجی را بر اساس این تعاریف انجام می‌دهد و داده‌ها را به انواع صحیح تبدیل می‌کند.

ویژگی‌های کلیدی:

  • اعتبارسنجی مبتنی بر Type Hints: استفاده از type hints استاندارد پایتون (PEP 484) برای تعریف ساختار داده و قوانین اعتبارسنجی.
  • تبدیل خودکار نوع: Pydantic به طور خودکار انواع داده‌ها را در صورت امکان تبدیل می‌کند (مثلاً "123" به 123).
  • پیام‌های خطای دقیق: تولید پیام‌های خطای خوانا و ساختاریافته.
  • یکپارچگی با IDE: به دلیل استفاده از type hints، IDEها می‌توانند تکمیل کد و بررسی نوع را به خوبی انجام دهند.
  • پشتیبانی از انواع پیچیده: پشتیبانی از لیست‌ها، دیکشنری‌ها، Union، Optional و مدل‌های تو در تو.

نصب:


pip install pydantic

مثال استفاده:


from flask import Flask, request, jsonify
from pydantic import BaseModel, Field, EmailStr, ValidationError
from typing import Optional

app = Flask(__name__)

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=20)
    email: EmailStr
    password: str = Field(min_length=8)
    is_admin: Optional[bool] = False # Optional field with a default value

@app.route('/api/users_pydantic', methods=['POST'])
def create_user_pydantic():
    data = request.get_json()
    if not data:
        return jsonify({'message': 'درخواست باید شامل داده‌های JSON باشد.'}), 400

    try:
        # ایجاد یک نمونه از مدل Pydantic، که به طور خودکار اعتبارسنجی می‌کند
        user_data = UserCreate(**data)
    except ValidationError as e:
        # e.errors() یک لیست از دیکشنری‌های خطا را برمی‌گرداند
        return jsonify({'errors': e.errors()}), 400
    
    # اگر اعتبارسنجی موفق بود، user_data یک آبجکت Pydantic معتبر است
    # می‌توانید به فیلدها به صورت user_data.username دسترسی پیدا کنید
    # print(user_data.username, user_data.email, user_data.password)

    # تبدیل مدل به دیکشنری برای ذخیره سازی یا بازگشت (اگر لازم باشد)
    validated_data = user_data.model_dump() # در Pydantic v2
    # validated_data = user_data.dict() # در Pydantic v1

    return jsonify({'message': 'کاربر با موفقیت ایجاد شد.', 'data': validated_data}), 201

Pydantic یک راه حل بسیار تمیز و مدرن برای اعتبارسنجی داده‌ها ارائه می‌دهد که به خوبی با اکوسیستم پایتون و ابزارهای توسعه همکاری می‌کند. انتخاب بین این کتابخانه‌ها بستگی به نیازهای پروژه، ترجیحات تیم و نوع برنامه‌ای که می‌سازید (فرم‌های سنتی وب در مقابل APIهای RESTful) دارد. برای فرم‌های وب سنتی، Flask-WTF یک انتخاب عالی است، در حالی که برای APIهای قدرتمند و انعطاف‌پذیر، Marshmallow یا Pydantic گزینه‌های برتری هستند.

پیاده‌سازی اعتبارسنجی سفارشی و پیچیده

در حالی که کتابخانه‌های اعتبارسنجی مانند WTForms، Marshmallow و Pydantic مجموعه‌ای غنی از ولیدیتورهای داخلی را ارائه می‌دهند، اما اغلب نیاز به پیاده‌سازی منطق اعتبارسنجی سفارشی دارید که مختص منطق کسب‌وکار یا الزامات خاص برنامه شما باشد. این شامل بررسی‌های پایگاه داده برای منحصر به فرد بودن، اعتبارسنجی فرمت‌های خاص یا منطق‌های پیچیده‌تر بین فیلدها می‌شود.

اعتبارسنجی سفارشی در Flask-WTF / WTForms

در WTForms، شما می‌توانید متدهای validate_<field_name> را در کلاس فرم خود تعریف کنید. این متدها به طور خودکار توسط form.validate_on_submit() فراخوانی می‌شوند.


from wtforms import StringField, ValidationError
from wtforms.validators import DataRequired, Email, Length
from flask_wtf import FlaskForm

# فرض کنید یک مدل کاربر دارید و با SQLAlchemy کار می‌کنید
# class User(db.Model):
#     id = db.Column(db.Integer, primary_key=True)
#     username = db.Column(db.String(80), unique=True, nullable=False)
#     email = db.Column(db.String(120), unique=True, nullable=False)

class CustomRegistrationForm(FlaskForm):
    username = StringField('نام کاربری', validators=[DataRequired(), Length(min=3, max=20)])
    email = StringField('ایمیل', validators=[DataRequired(), Email()])
    password = StringField('رمز عبور', validators=[DataRequired(), Length(min=8)])

    def validate_username(self, field):
        # این ولیدیتور سفارشی بررسی می‌کند که نام کاربری از قبل موجود نباشد
        # if User.query.filter_by(username=field.data).first():
        #     raise ValidationError('این نام کاربری از قبل گرفته شده است. لطفاً نام دیگری را انتخاب کنید.')
        # برای مثال:
        if field.data == 'admin':
            raise ValidationError('نام کاربری "admin" مجاز نیست.')

    def validate_email(self, field):
        # این ولیدیتور سفارشی بررسی می‌کند که ایمیل از قبل موجود نباشد
        # if User.query.filter_by(email=field.data).first():
        #     raise ValidationError('این ایمیل از قبل ثبت شده است. لطفاً وارد شوید یا ایمیل دیگری را انتخاب کنید.')
        # برای مثال:
        if field.data == 'test@example.com':
            raise ValidationError('این ایمیل برای تست رزرو شده است.')

    def validate(self, extra_validators=None):
        # این متد می تواند برای اعتبارسنجی های پیچیده تر بین چند فیلد استفاده شود
        initial_validation = super().validate(extra_validators)
        if not initial_validation:
            return False

        # مثال: بررسی اینکه رمز عبور شامل نام کاربری نباشد
        if self.username.data and self.password.data and self.username.data in self.password.data:
            self.password.errors.append('رمز عبور نمی‌تواند شامل نام کاربری باشد.')
            return False
        
        return True # اگر همه اعتبارسنجی‌ها موفق بودند

متدهای validate_<field_name> برای اعتبارسنجی‌های مربوط به یک فیلد خاص ایده‌آل هستند، در حالی که متد validate() برای اعتبارسنجی‌های پیچیده‌تر که به چندین فیلد نیاز دارند، مناسب است.

اعتبارسنجی سفارشی در Marshmallow

Marshmallow چندین راه برای تعریف ولیدیتورهای سفارشی ارائه می‌دهد:

  1. ولیدیتورهای تابع (Function Validators): می‌توانید توابع پایتون را مستقیماً به عنوان ولیدیتور به فیلدها اضافه کنید.
  2. متدهای validate_<field_name>: مشابه WTForms، می‌توانید این متدها را در کلاس اسکیما تعریف کنید.
  3. متد @validates_schema: برای اعتبارسنجی‌های در سطح اسکیما که شامل چندین فیلد هستند.
  4. کلاس‌های ولیدیتور سفارشی: ایجاد کلاس‌هایی که از marshmallow.validate.Validator ارث‌بری می‌کنند.

from marshmallow import Schema, fields, validate, ValidationError, validates_schema
from marshmallow.validate import Length, Email
from flask import jsonify, request, Flask

app = Flask(__name__)

def validate_even_number(n):
    if n % 2 != 0:
        raise ValidationError('عدد باید زوج باشد.')

class ProductSchema(Schema):
    name = fields.Str(required=True, validate=Length(min=2, max=100))
    price = fields.Float(required=True, validate=validate.Range(min=0.01))
    quantity = fields.Int(required=True, validate=[validate.Range(min=1), validate_even_number]) # ولیدیتور تابع

    def validate_name(self, name):
        # اعتبارسنجی سفارشی برای نام محصول
        if name.lower() == "test product":
            raise ValidationError("نام 'Test Product' مجاز نیست.")

    @validates_schema
    def validate_product_logic(self, data, **kwargs):
        # اعتبارسنجی در سطح اسکیما
        if data['price'] < 10 and data['quantity'] > 50:
            raise ValidationError('محصولات ارزان‌قیمت نمی‌توانند در مقادیر بسیار زیاد سفارش داده شوند.', 'quantity')
        
        # فرض کنید یک سرویس برای بررسی موجودی دارید
        # if not check_product_inventory(data['name'], data['quantity']):
        #     raise ValidationError('موجودی کافی برای این محصول نیست.', 'quantity')

@app.route('/api/products', methods=['POST'])
def create_product():
    json_data = request.get_json()
    if not json_data:
        return jsonify({'message': 'درخواست باید شامل داده‌های JSON باشد.'}), 400

    schema = ProductSchema()
    try:
        validated_data = schema.load(json_data)
    except ValidationError as err:
        return jsonify(err.messages), 400
    
    return jsonify(validated_data), 201

اعتبارسنجی سفارشی در Pydantic

Pydantic امکانات قدرتمندی برای اعتبارسنجی سفارشی با استفاده از متدهای validator و root_validator (در Pydantic v1) یا @field_validator و @model_validator (در Pydantic v2) ارائه می‌دهد.


from flask import Flask, request, jsonify
from pydantic import BaseModel, Field, EmailStr, ValidationError, model_validator, field_validator
from typing import Optional

app = Flask(__name__)

class UserPydantic(BaseModel):
    username: str = Field(min_length=3, max_length=20)
    email: EmailStr
    password: str = Field(min_length=8)
    age: Optional[int] = Field(None, ge=18, le=120) # بزرگتر مساوی 18، کوچکتر مساوی 120

    @field_validator('username') # Pydantic v2
    @classmethod
    def validate_username_custom(cls, value):
        if value == 'root':
            raise ValueError('نام کاربری "root" مجاز نیست.')
        # فرض کنید چک منحصر به فرد بودن نام کاربری را اینجا انجام می‌دهید
        # if User.exists(username=value):
        #     raise ValueError('این نام کاربری از قبل وجود دارد.')
        return value

    @model_validator(mode='after') # Pydantic v2
    def validate_password_complexity(self):
        # اعتبارسنجی پیچیدگی رمز عبور در سطح مدل
        if self.password.lower() == self.username.lower():
            raise ValueError('رمز عبور نمی‌تواند با نام کاربری یکسان باشد.')
        
        # یک مثال پیچیده تر: بررسی اینکه رمز عبور شامل حداقل یک عدد و یک حرف بزرگ باشد
        if not any(char.isdigit() for char in self.password):
            raise ValueError('رمز عبور باید حداقل یک عدد داشته باشد.')
        if not any(char.isupper() for char in self.password):
            raise ValueError('رمز عبور باید حداقل یک حرف بزرگ داشته باشد.')
        
        return self

@app.route('/api/users_pydantic_custom', methods=['POST'])
def create_user_pydantic_custom():
    data = request.get_json()
    if not data:
        return jsonify({'message': 'درخواست باید شامل داده‌های JSON باشد.'}), 400

    try:
        user_data = UserPydantic(**data)
    except ValidationError as e:
        return jsonify({'errors': e.errors()}), 400
    
    return jsonify({'message': 'کاربر با موفقیت ایجاد شد.', 'data': user_data.model_dump()}), 201

@field_validator به شما امکان می‌دهد تا قبل از اختصاص داده به یک فیلد خاص، آن را اعتبارسنجی و حتی تغییر دهید. @model_validator به شما اجازه می‌دهد تا اعتبارسنجی‌هایی را انجام دهید که به مقادیر چندین فیلد نیاز دارند، و این کار را بعد از اعتبارسنجی‌های فیلد انجام می‌دهد.

انتخاب روش مناسب برای اعتبارسنجی سفارشی به کتابخانه مورد استفاده و پیچیدگی منطق شما بستگی دارد. در هر سه کتابخانه، اصول کلی یکسان است: شناسایی خطاهای اعتبارسنجی، ایجاد پیام‌های خطا و پرتاب یک استثنا یا اضافه کردن خطا به لیست خطاها. استفاده صحیح از این قابلیت‌ها به شما این امکان را می‌دهد که اعتبارسنجی‌های پیچیده و مختص به نیازهای برنامه خود را به صورت تمیز و قابل نگهداری پیاده‌سازی کنید.

مدیریت و گزارش‌دهی خطاها در اعتبارسنجی

اعتبارسنجی ورودی تنها نیمی از کار است؛ نحوه مدیریت و گزارش‌دهی خطاها به کاربر نیز به همان اندازه اهمیت دارد. یک سیستم گزارش‌دهی خطای خوب باید اطلاعات واضح، دقیق و سازنده‌ای را به کاربر ارائه دهد تا بتواند به راحتی مشکل را برطرف کند. این نه تنها تجربه کاربری را بهبود می‌بخشد، بلکه در عیب‌یابی و اشکال‌زدایی نیز به توسعه‌دهندگان کمک می‌کند.

کدهای وضعیت HTTP (HTTP Status Codes)

برای APIهای RESTful، استفاده صحیح از کدهای وضعیت HTTP برای نشان دادن نتیجه اعتبارسنجی بسیار مهم است. این کدها به کلاینت (خواه یک مرورگر، اپلیکیشن موبایل یا سرویس دیگر) اجازه می‌دهند تا به سرعت وضعیت درخواست را درک کند:

  • 200 OK / 201 Created: نشان‌دهنده موفقیت درخواست (پس از اعتبارسنجی موفق).
  • 400 Bad Request: رایج‌ترین کد برای خطاهای اعتبارسنجی. این کد به این معنی است که سرور نمی‌تواند درخواست را به دلیل خطاهای کلاینت (مانند داده‌های نامعتبر یا از دست رفته) پردازش کند.
  • 422 Unprocessable Entity: این کد وضعیت توسط برخی APIها برای نشان دادن خطاهای معنایی اعتبارسنجی استفاده می‌شود. به این معنی که درخواست به درستی فرمت شده است (نه مانند 400 که ممکن است مربوط به JSON نامعتبر باشد)، اما محتوای آن از نظر منطقی قابل پردازش نیست (مثلاً یک فیلد ایمیل معتبر از نظر ساختار، اما ایمیل در پایگاه داده وجود ندارد و باید منحصر به فرد باشد).
  • 401 Unauthorized: برای خطاهای احراز هویت (Authentication).
  • 403 Forbidden: برای خطاهای مجوز (Authorization).

همیشه سعی کنید از 400 Bad Request برای خطاهای اعتبارسنجی استفاده کنید مگر اینکه دلیلی خاص برای استفاده از 422 داشته باشید.

فرمت پاسخ خطا (Error Response Format)

علاوه بر کد وضعیت HTTP، بدنه پاسخ خطا باید به گونه‌ای ساختاریافته باشد که کلاینت بتواند به راحتی آن را تجزیه و تحلیل کند. فرمت JSON برای این منظور بسیار محبوب است. یک فرمت رایج شامل یک شیء JSON با یک کلید errors است که شامل جزئیات خطاها می‌شود.

مثال عمومی:


{
    "errors": {
        "username": [
            "نام کاربری نمی‌تواند خالی باشد.",
            "نام کاربری باید حداقل 3 کاراکتر باشد."
        ],
        "email": [
            "فرمت ایمیل نامعتبر است."
        ],
        "general": [
            "رمز عبور نمی‌تواند شامل نام کاربری باشد."
        ]
    }
}

برخی APIها ممکن است یک کلید message نیز برای خطای کلی فراهم کنند:


{
    "message": "خطا در اعتبارسنجی ورودی",
    "errors": {
        "username": "نام کاربری از قبل وجود دارد.",
        "password": "رمز عبور بسیار ضعیف است."
    }
}

این ساختار به کلاینت اجازه می‌دهد تا پیام‌های خطا را به فیلدهای مربوطه در فرم یا رابط کاربری نمایش دهد.

مدیریت خطاها در Flask-WTF / WTForms

Flask-WTF به طور خودکار خطاها را در شیء form.errors جمع‌آوری می‌کند، که یک دیکشنری است. در تمپلیت، می‌توانید این خطاها را به راحتی نمایش دهید:


@app.route('/register', methods=['POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        # ... پردازش موفقیت آمیز
        return redirect(url_for('success'))
    # اگر اعتبارسنجی ناموفق بود، form.errors پر می‌شود
    return render_template('register.html', form=form)

در تمپلیت Jinja2:


<p>
    {{ form.username.label }}<br>
    {{ form.username() }}<br>
    {% if form.username.errors %}<ul>{% for error in form.username.errors %}<li style="color: red;">{{ error }}</li>{% endfor %}</ul>{% endif %}
</p>

برای APIها، می‌توانید form.errors را مستقیماً به JSON تبدیل کرده و با کد 400 بازگردانید:


@app.route('/api/register', methods=['POST'])
def api_register():
    form = RegistrationForm()
    if form.validate_on_submit():
        # ... پردازش موفقیت آمیز
        return jsonify({'message': 'User registered successfully'}), 201
    return jsonify({'errors': form.errors}), 400

مدیریت خطاها در Marshmallow

Marshmallow در صورت وجود خطای اعتبارسنجی، یک استثنا ValidationError پرتاب می‌کند. این استثنا شامل یک ویژگی messages است که یک دیکشنری حاوی جزئیات خطاهاست:


from marshmallow import ValidationError

@app.route('/api/data', methods=['POST'])
def process_data_marshmallow():
    json_data = request.get_json()
    schema = MySchema()
    try:
        validated_data = schema.load(json_data)
        # ... پردازش داده‌ها
        return jsonify(validated_data), 200
    except ValidationError as err:
        return jsonify({'errors': err.messages}), 400

مدیریت خطاها در Pydantic

مشابه Marshmallow، Pydantic نیز در صورت عدم موفقیت اعتبارسنجی، یک ValidationError پرتاب می‌کند. ویژگی errors() در شیء استثنا، یک لیست از دیکشنری‌ها را برمی‌گرداند که هر دیکشنری جزئیات یک خطا را شامل می‌شود:


from pydantic import ValidationError

@app.route('/api/item', methods=['POST'])
def create_item_pydantic():
    json_data = request.get_json()
    try:
        item = ItemModel(**json_data)
        # ... پردازش item
        return jsonify(item.model_dump()), 200 # یا item.dict() در Pydantic v1
    except ValidationError as e:
        return jsonify({'errors': e.errors()}), 400

هر عنصر در لیست e.errors() به طور معمول دارای کلیدهایی مانند loc (مکان خطا، یعنی فیلد), msg (پیام خطا) و type (نوع ولیدیتور) است. این اطلاعات می‌تواند برای نمایش خطاها به کاربر مفید باشد.

نکات تکمیلی برای مدیریت خطاها:

  • ثبت خطاها (Logging): همیشه خطاهای اعتبارسنجی را در لاگ‌های برنامه خود ثبت کنید. این می‌تواند به شناسایی الگوهای ورودی مخرب یا مشکلات رایج کاربران کمک کند.
  • بین‌المللی‌سازی (Internationalization - i18n): برای برنامه‌های چندزبانه، پیام‌های خطا باید قابل ترجمه باشند. کتابخانه‌های اعتبارسنجی معمولاً از این قابلیت پشتیبانی می‌کنند یا با ابزارهای i18n Flask (مانند Flask-Babel) یکپارچه می‌شوند.
  • یکپارچگی در فرمت: سعی کنید فرمت پاسخ خطای خود را در سراسر برنامه یکسان نگه دارید تا کلاینت‌ها بتوانند به طور سازگار با آن برخورد کنند.
  • عدم افشای اطلاعات حساس: هرگز اطلاعات حساس یا جزئیات پیاده‌سازی داخلی (مانند tracebacks) را در پیام‌های خطای عمومی نمایش ندهید.

مدیریت خطای مؤثر یک بخش حیاتی از ساخت یک برنامه Flask قوی و کاربرپسند است. با انتخاب کدهای وضعیت HTTP مناسب و ارائه پیام‌های خطای واضح و ساختاریافته، می‌توانید تجربه کاربری را به میزان قابل توجهی بهبود بخشیده و به توسعه‌دهندگان کلاینت کمک کنید تا به طور مؤثرتری با API شما تعامل داشته باشند.

بهترین روش‌ها و نکات پیشرفته برای اعتبارسنجی جامع

پیاده‌سازی اعتبارسنجی ورودی به تنهایی کافی نیست؛ باید اطمینان حاصل شود که این اعتبارسنجی جامع، ایمن و قابل نگهداری است. در ادامه به بهترین روش‌ها و نکات پیشرفته‌ای می‌پردازیم که به شما کمک می‌کنند تا سیستم اعتبارسنجی قوی‌تری در برنامه‌های Flask خود بسازید.

اعتبارسنجی در لایه‌های مختلف (Layered Validation)

اعتبارسنجی نباید فقط در یک نقطه از برنامه انجام شود. یک رویکرد دفاعی عمیق (Defense in Depth) شامل اعتبارسنجی در چندین لایه است:

  1. اعتبارسنجی سمت کلاینت (Client-Side Validation): از طریق HTML5 (required, type="email", minlength, pattern) و جاوااسکریپت. این نوع اعتبارسنجی برای بهبود تجربه کاربری و ارائه بازخورد فوری است و هرگز نباید به عنوان تنها لایه امنیتی مورد اعتماد قرار گیرد، زیرا به راحتی می‌توان آن را دور زد.
  2. اعتبارسنجی سمت سرور (Server-Side Validation): این لایه ضروری و غیرقابل چشم‌پوشی است. داده‌ها باید همیشه در سمت سرور اعتبارسنجی شوند، حتی اگر در سمت کلاینت اعتبارسنجی شده باشند. اینجاست که از کتابخانه‌هایی مانند Flask-WTF، Marshmallow یا Pydantic استفاده می‌شود.
  3. اعتبارسنجی در لایه داده (Database/ORM Level): پایگاه داده نیز می‌تواند محدودیت‌هایی (constraints) مانند NOT NULL، UNIQUE، CHECK و انواع داده (VARCHAR, INTEGER) را اعمال کند. این یک لایه نهایی از حفاظت را فراهم می‌کند و از ذخیره داده‌های نامعتبر یا ناسازگار جلوگیری می‌نماید، حتی اگر لایه‌های بالایی به دلیل باگ یا سوءاستفاده از کار افتاده باشند. ORMها مانند SQLAlchemy می‌توانند این محدودیت‌ها را به مدل‌های شما نگاشت کنند.

جداسازی منطق اعتبارسنجی

کلاس‌های فرم (Flask-WTF) یا اسکیماها/مدل‌ها (Marshmallow/Pydantic) را به فایل‌های جداگانه منتقل کنید. این کار خوانایی، نگهداری و قابلیت استفاده مجدد کد را افزایش می‌دهد. ویوها باید فقط مسئول فراخوانی و مدیریت نتایج اعتبارسنجی باشند، نه تعریف خود قوانین اعتبارسنجی.

پاک‌سازی و نرمال‌سازی داده‌ها (Sanitization and Normalization)

اعتبارسنجی صرفاً به معنی رد کردن داده‌های نامعتبر نیست؛ بلکه شامل پاک‌سازی و نرمال‌سازی داده‌های معتبر نیز می‌شود:

  • Sanitization: حذف یا خنثی کردن کاراکترهای بالقوه خطرناک. به عنوان مثال، هنگام نمایش ورودی کاربر در HTML، همیشه آن را escape کنید تا از XSS جلوگیری شود. Flask و Jinja2 به طور پیش‌فرض این کار را انجام می‌دهند، اما هنگام ذخیره در دیتابیس یا استفاده در جای دیگر باید محتاط باشید.
  • Normalization: تبدیل داده‌ها به یک فرم استاندارد. به عنوان مثال، کوتاه کردن فضای خالی اضافی از ابتدا و انتهای رشته‌ها، تبدیل تمام ایمیل‌ها به حروف کوچک، یا فرمت‌بندی شماره تلفن‌ها به یک شیوه ثابت. بسیاری از فیلدهای WTForms یا Marshmallow شامل فیلترهایی برای این کار هستند.

مدیریت انواع مختلف درخواست (GET, POST, PUT, PATCH)

الزامات اعتبارسنجی ممکن است بر اساس نوع درخواست HTTP متفاوت باشد:

  • GET: معمولاً پارامترهای کوئری را شامل می‌شود که اغلب برای فیلتر کردن، صفحه‌بندی یا جستجو استفاده می‌شوند. اعتبارسنجی در اینجا ممکن است شامل بررسی نوع (عدد صحیح برای page)، محدوده (page > 0) و مقادیر مجاز (sort_by فقط می‌تواند name یا date باشد) باشد.
  • POST: برای ایجاد منابع جدید استفاده می‌شود. همه فیلدهای اجباری برای ایجاد یک منبع جدید باید وجود داشته باشند و اعتبارسنجی شوند.
  • PUT: برای جایگزینی کامل یک منبع موجود استفاده می‌شود. معمولاً اعتبارسنجی مشابه POST است، زیرا انتظار می‌رود تمام فیلدها دوباره ارسال شوند.
  • PATCH: برای به‌روزرسانی جزئی یک منبع استفاده می‌شود. در اینجا، فیلدها اغلب اختیاری هستند و فقط فیلدهایی که ارسال می‌شوند باید اعتبارسنجی شوند. کتابخانه‌هایی مانند Marshmallow با گزینه‌های partial=True در متد load() این امکان را فراهم می‌کنند. Pydantic نیز از این مفهوم پشتیبانی می‌کند.

اعتبارسنجی شرطی (Conditional Validation)

گاهی اوقات یک فیلد تنها در صورت برقراری یک شرط خاص (مثلاً مقدار یک فیلد دیگر) باید اعتبارسنجی شود. این را می‌توان با منطق سفارشی در متدهای validate() (WTForms) یا @validates_schema / @model_validator (Marshmallow/Pydantic) پیاده‌سازی کرد.


# مثال Pydantic برای اعتبارسنجی شرطی
from pydantic import BaseModel, Field, model_validator
from typing import Optional

class Event(BaseModel):
    event_type: str
    location: Optional[str] = None
    online_link: Optional[str] = None

    @model_validator(mode='after')
    def validate_event_type_fields(self):
        if self.event_type == 'in-person' and not self.location:
            raise ValueError('برای رویداد حضوری، مکان الزامی است.')
        if self.event_type == 'online' and not self.online_link:
            raise ValueError('برای رویداد آنلاین، لینک الزامی است.')
        if self.event_type == 'hybrid' and (not self.location or not self.online_link):
            raise ValueError('برای رویداد ترکیبی، مکان و لینک هر دو الزامی هستند.')
        return self

اعتبارسنجی با دسترسی به پایگاه داده (Database-backed Validation)

برای بررسی منحصر به فرد بودن نام کاربری، ایمیل یا سایر فیلدها، نیاز به کوئری زدن از پایگاه داده دارید. این نوع اعتبارسنجی باید با دقت انجام شود تا از Bottleneck شدن برنامه جلوگیری شود. این را معمولاً در ولیدیتورهای سفارشی یا متدهای validate_<field_name> انجام می‌دهند. توجه داشته باشید که این کوئری‌ها باید در یک Context مناسب (مانند درخواست وب) اجرا شوند.

تست کردن اعتبارسنجی (Testing Validation)

تست‌های واحد (Unit Tests) و تست‌های یکپارچه‌سازی (Integration Tests) قوی برای اطمینان از صحت و کامل بودن منطق اعتبارسنجی ضروری هستند. سناریوهای مختلفی را تست کنید: ورودی‌های معتبر، ورودی‌های نامعتبر (از جمله انواع مختلف خطاها)، ورودی‌های خالی، ورودی‌های بیش از حد طولانی و سناریوهای گوشه‌ای.

بین‌المللی‌سازی پیام‌های خطا (Internationalization of Error Messages)

اگر برنامه شما برای کاربران با زبان‌های مختلف در دسترس است، پیام‌های اعتبارسنجی باید قابل ترجمه باشند. Flask-Babel با Flask-WTF و سایر ابزارها می‌توانند در این زمینه کمک کنند تا پیام‌ها بر اساس زبان کاربر نمایش داده شوند.

استفاده از Data Transfer Objects (DTOs)

برای APIها، به جای استفاده مستقیم از مدل‌های ORM برای اعتبارسنجی ورودی، از DTOها (که معمولاً با Marshmallow Schemas یا Pydantic Models تعریف می‌شوند) استفاده کنید. DTOها به شما اجازه می‌دهند تا داده‌های ورودی را از مدل‌های داخلی برنامه جدا کنید و یک لایه امنیتی و انعطاف‌پذیری اضافه کنید. یک DTO می‌تواند شامل زیرمجموعه‌ای از فیلدهای یک مدل یا فیلدهایی با قوانین اعتبارسنجی متفاوت باشد که برای یک عملیات خاص طراحی شده است.

با رعایت این بهترین روش‌ها، می‌توانید یک سیستم اعتبارسنجی ورودی قوی، ایمن و قابل نگهداری در برنامه‌های Flask خود ایجاد کنید که از برنامه شما در برابر حملات محافظت کرده، یکپارچگی داده‌ها را حفظ کند و تجربه کاربری مثبتی را ارائه دهد.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

"با شرکت در این دوره جامع و کاربردی، به راحتی مهارت‌های برنامه‌نویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر می‌سازد تا به سرعت الگوریتم‌های پیچیده را درک کرده و اپلیکیشن‌های هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفه‌ای و امکان دانلود و تماشای آنلاین."

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان