Python حرفه‌ای: پارامترهای URL در Flask

Professional Python: URL parameters in Flask

27 اسفند 1399
درسنامه درس 34 از سری پایتون حرفه‌ای
Python حرفه ای: پارامتر های URL در Flask (قسمت 34)

template engine چیست؟

template engine ها به ما اجازه می دهند که مقادیر را به صورت پویا بسازیم. یعنی چه؟ اگر با فریم ورک هایی مانند لاراول یا با template engine هایی مانند PUG یا EJS کار کرده باشید حتما می دانید که template engine چیست. اگر یادتان باشد در جلسه قبل از روش زیر برای آدرس دهی به favicon استفاده کرده بودیم:

<link rel="shortcut icon" href="static/images/favicon.ico" />

این روش بدون نقص کار می کند اما روش دیگری را نیز برای انجام این کار داشتیم:

<link

  rel="shortcut icon"

  href="{{ url_for('static', filename='images/favicon.ico') }}"

/>

اما این کد چطور کار می کند؟ تابع url_for کاربردهای بسیار زیادی دارد که در طول این فصل با آن ها آشنا می شویم اما یکی از قابلیت های آن ساخت url برای فایل های استاتیک است. احتمالا سوال می کنید که مگر می شود در فایل HTML از کدهای پایتون استفاده کرد!؟ پاسخ مثبت است! به فایل index.html رفته و کد زیر را در آن پیدا کنید:

<span class="w3-center w3-padding-large w3-black w3-xlarge w3-wide w3-animate-opacity">MY <span class="w3-hide-small">WEBSITE</span> LOGO</span>

این یک کد HTML ساده است. در ابتدا برای واضح تر شدن کد، تگ <span> اضافی را حذف کنید تا به شکل زیر در بیاید:

<span class="w3-center w3-padding-large w3-black w3-xlarge w3-wide w3-animate-opacity"> 4 + 5 </span>

حالا اگر به آدرس http://127.0.0.1:5000/portfolio بروید، عینا رشته 5 + 4 را مشاهده می کنید اما اگر همین رشته ساده را در {{ }} بگذاریم چطور؟

<span class="w3-center w3-padding-large w3-black w3-xlarge w3-wide w3-animate-opacity"> {{ 4 + 5 }} </span>

حالا با رفتن به مرورگر می بینید که عدد ۹ برایمان نمایش داده شده است! به این قابلیت (استفاده از {{‌}}) template engine می گوییم. زمانی که flask علامت های {{ }} را در فایل HTML ما می بیند متوجه خواهد شد که کدهای درون آن، کدهای عادی نیستند بلکه باید به صورت پویا و با پایتون محاسبه شوند. در اصل flask از template engine ای به نام jinja استفاده می کند بنابراین در صورت تمایل می توانید در مورد آن نیز مطالعه کنید. حالا کد زیر معنی می دهد:

<link

  rel="shortcut icon"

  href="{{ url_for('static', filename='images/favicon.ico') }}"

/>

آرگومان اول url_for یک endpoint خاص را مشخص می کند که در اینجا پوشه static است و آرگومان filename نیز نام فایل مورد نظرتان را مشخص می کند. از آنجایی که favicon خود را درون پوشه images گذاشته بودیم باید filename را برابر images/favicon.ico قرار بدهیم. در نهایت این تابع همان رشته static/images/favicon.ico را ساخته و برمی گرداند. حالا باید به سوالی پاسخ بدهیم: چرا از url_for برای ساخت URL ها استفاده کنیم؟ در اینجا یک URL برای یک فایل ساخته ایم اما URL می تواند به یک صفحه نیز اشاره کند (مثل portfolio/). مزیت استفاده از url_for نسبت به نوشتن دستی URL ها چیست؟ چند دلیل ساده عبارت اند از:

  • ساخت URL بدین روش برای برنامه نویسان حرفه ای، خوانا تر است و ویرایش آن نیز ساده تر خواهد بود.
  • با استفاده از این روش می توانید تمام url ها را درجا ویرایش کنید در حالی که اگر آن ها را به صورت دستی بنویسید باید تک تک آن ها را به صورت دستی ویرایش کنید.
  • url_for به صورت خودکار کاراکترهای ویژه را escape می دهد و کاراکترهای unicode را نیز مدیریت می کند.
  • مسیرهای تولید شده توسط url_for همیشه absolute (آدرس مطلق یا کامل) هستند بنابراین احتمال بروز رفتار های عجیب از مرورگرها کاهش پیدا می کند.
  • در صورتی که برنامه شما در مسیر اصلی (/) قرار نداشته باشد (مثلا در مسیر my-application/ باشد) تابع url_for این مسئله را برایتان مدیریت می کند.

شاید در نگاه اول متوجه برخی از این نکات نشوید اما تا انتهای فصل به صورت عملی با آن ها آشنا خواهیم شد. یک مثال ساده و قابل درک برای شما، زمانی است که می خواهیم url را به صورت پویا بسازیم و مقادیر آن را از قبل نمی دانیم. به مثال زیر توجه کنید:

{{ url_for('events', user_id=user.id, year=2021) }}

این تابع url ای به شکل website.com/events/1388224/2021 ایجاد خواهد کرد در حالی که نوشتن آن به صورت دستی ممکن نیست. بیایید این کار را به صورت عملی انجام بدهیم.

پارامترهای URL

یکی دیگر از قابلیت های flask، قابلیت variable rules می باشد. این قابلیت به ما اجازه می دهد که پارامترهای موجود در URL هایمان را به صورت پویا بنویسیم! اگر به documentation رسمی flask مراجعه کنید چنین مثالی را می بینید:

from markupsafe import escape




@app.route('/user/<username>')

def show_user_profile(username):

    # show the user profile for that user

    return 'User %s' % escape(username)




@app.route('/post/<int:post_id>')

def show_post(post_id):

    # show the post with the given id, the id is an integer

    return 'Post %d' % post_id




@app.route('/path/<path:subpath>')

def show_subpath(subpath):

    # show the subpath after /path/

    return 'Subpath %s' % escape(subpath)

برخی از شما با خواندن کد بالا نحوه کار پارامترهای پویا را درک می کنید اما بسیاری از شما احتمالا متوجه آن نمی شوید بنابراین بهتر است به صورت ساده و با یک مثال از server.py شروع کنیم:

from flask import Flask, render_template

app = Flask(__name__)







@app.route('/')

def hello_world():

    return 'Hello, World!'







@app.route('/portfolio/<username>')

def portfolio(username):

    return render_template('index.html', name=username)

همانطور که می بینید من مسیر portfolio را کمی تغییر داده ام و <username> را نیز به آن اضافه کرده ام. زمانی که در url خود مقداری را درون علامت های <> قرار بدهید یعنی آن مقدار پویا است و واقعا برابر با username نیست. برای درک بهتر می توانید <username> را به عنوان یک متغیر در نظر بگیرید. سپس این مقدار را به تابع portfolio پاس داده ایم و از آنجا به عنوان آرگومان دوم به تابع render_template پاس داده ایم. هر مقداری را که به عنوان آرگومان دوم به این تابع پاس بدهید در آن فایل HTML در دسترس خواهد بود. البته باید برای این مقدار یک نام را انتخاب کنید که من نام name را انتخاب کرده ام.

حالا به فایل index.html رفته و قسمت زیر از همان کدهای قبلی را بدین شکل ویرایش می کنیم:

<!-- First Parallax Image with Logo Text -->

<div class="bgimg-1 w3-display-container w3-opacity-min" id="home">

  <div class="w3-display-middle" style="white-space: nowrap">

    <span

      class="w3-center w3-padding-large w3-black w3-xlarge w3-wide w3-animate-opacity"

      >{{ name }}</span>

  </div>

</div>

همانطور که می بینید من در این فایل متغیری به نام name را چاپ کرده ام! این name از فایل server.py و از طریق تابع render_template به فایل HTML ما تزریق می شود. به همین دلیل است که به چنین متغیری دسترسی داشته و می توانیم آن را چاپ کنیم. به نظر شما اگر حالا به آدرس http://127.0.0.1:5000/portfolio برویم چه اتفاقی می افتد؟ خطای Not Found می گیریم! چرا؟  به دلیل اینکه دیگر مسیر portfolio/ وجود ندارد بلکه مسیر زیر را داریم:

@app.route('/portfolio/<username>')

def portfolio(username):

    return render_template('index.html', name=username)

یعنی حتما باید مقداری پس از portfolio قرار داشته باشد. به طور مثال من به آدرس http://127.0.0.1:5000/portfolio/roxo.ir در مرورگر می روم و اگر شما نیز این کار را انجام بدهید خواهید دید که عبارت roxo.ir نمایش داده شده است:

نمایش آدرس roxo.ir در صفحه ی اصلی
نمایش آدرس roxo.ir در صفحه اصلی

یعنی هر مقداری که پس از portfolio در آدرس مرورگر وارد شود به عنوان name در این فایل نمایش داده می شود. شما می توانید این کار را با انواع مقادیر دیگر تست کنید. سوال دیگری که پیش می آید این است که اگر از کاراکترهای ویژه استفاده کنیم چه اتفاقی می افتد؟ مثلا من به آدرس زیر می روم:

http://127.0.0.1:5000/portfolio/alert('hello')$!{data* special}

با مراجعه به این آدرس، در اصل به آدرس زیر می رویم:

http://127.0.0.1:5000/portfolio/alert('hello')$!%7Bdata*%20special%7D

این کاراکترهای ویژه به کاراکترهای فرمت URL تبدیل می شوند و مشکلی نخواهیم داشت.

تعیین نوع داده برای پارامترها

ما می توانیم برای این پارامترهای دریافتی در URL یک نوع داده خاص را مشخص کنیم. جدول مربوط به این قوانین در documentation رسمی flask آمده است که بدین شرح است:

  • string یا رشته: هر رشته ای بدون علامت اسلش (/) را شامل می شود و مقدار پیش فرض است.
  • int یا عدد صحیح: تمام اعداد صحیح مثبت را شامل می شود.
  • float یا عدد اعشاری: تمام اعداد اعشاری مثبت را شامل می شود.
  • path یا مسیر: دقیقا مانند رشته است با این تفاوت که کاراکتر اسلش را نیز شامل می شود.
  • uuid یا «شناسه منحصر به فرد جهانی»: فقط رشته های UUID را قبول می کند.

حالا می توانیم با یکی از مقادیر بالا فقط مقادیر خاصی را دریافت کنیم. مثال:

from flask import Flask, render_template

app = Flask(__name__)







@app.route('/')

def hello_world():

    return 'Hello, World!'







@app.route('/portfolio/<username>')

def portfolio(username):

    return render_template('index.html', name=username)







@app.route('/posts/<int:post_id>')

def post_ID(post_id):

    return render_template('index.html', name=post_id)

من یک مسیر جدید به نام posts/post_id را ایجاد کرده ام اما این بار :int را نیز در ابتدایش گذاشته ایم که نوع داده اش را مشخص می کند. من از همان index.html برای چاپ این مقدار استفاده کرده ام و نام متغیر را باز هم name گذاشته ام. شما می توانید هر مقدار دیگری را به عنوان نام متغیر انتخاب کنید اما یادتان باشد که بعدا باید به index.html رفته و آن را تغییر بدهید. حالا برای تست این کدها ابتدا به آدرس http://127.0.0.1:5000/portfolio/alert('hello') می رویم. در این آدرس نباید هیچ مشکلی وجود داشته باشد و نباید خطایی دریافت کنید. در مرحله بعدی به آدرس http://127.0.0.1:5000/posts/13 می رویم که مسیر جدید ما است. حالا باید عدد ۱۳ برایمان چاپ شود. با این حساب نوع داده دریافتی از پارامتر را مشخص کرده ایم (int یا عدد صحیح) اما اگر مقداری غیر از آن را دریافت کنیم چه می شود؟ مثلا اگر به آدرس http://127.0.0.1:5000/posts/amir بروید، خطای Not Found را دریافت می کنید چرا که چنین صفحه ای وجود ندارد (amir رشته است و عدد نیست). همچنین اگر به آدرس http://127.0.0.1:5000/posts/13.3 بروید نیز همین اتفاق می افتد چرا که ۱۳.۳ یک عدد اعشاری (float) است نه عدد صحیح (int).

نکته: در برخی از مواقع سرور به صورت خودکار ریستارت می شود اما دچار مشکلاتی خواهد بود بنابراین ممکن است خطا دریافت کنید. در این حالت یک بار سرور را از ترمینال خود قطع کرده (Ctrl + C) و دوباره flask run را اجرا کنید.

البته ما می توانیم از چندین پارامتر پویا در URL خود استفاده کنیم. به طور مثال:

from flask import Flask, render_template

app = Flask(__name__)







@app.route('/')

def hello_world():

    return 'Hello, World!'







@app.route('/portfolio/<string:username>/<int:post_id>')

def portfolio(username, post_id):

    return render_template('index.html', name=username, postId=post_id)

با اینکه حالت پیش فرض string است اما من باز هم string را برای username آورده ام تا این مسئله را به شما یادآوری کنم. همانطور که می بینید برای پاس دادن دیگر مقادیر پویا کافی است که آن ها را به عنوان آرگومان های بعدی به render_template پاس بدهیم. در نهایت به فایل index.html رفته و همان بخش قبلی را به شکل زیر ویرایش می کنیم:

<!-- First Parallax Image with Logo Text -->

<div class="bgimg-1 w3-display-container w3-opacity-min" id="home">

  <div class="w3-display-middle" style="white-space: nowrap">

    <span

      class="w3-center w3-padding-large w3-black w3-xlarge w3-wide w3-animate-opacity"

      >User {{ name }} and post {{ postId }}</span

    >

  </div>

</div>

حالا بیایید به آدرس http://127.0.0.1:5000/portfolio/amir/13 برویم. با این کار نتیجه را بدون خطا مشاهده خواهید کرد:

ترکیب پارامتر های URL و نتیجه ی آن
ترکیب پارامترهای URL و نتیجه آن

حالا باید به سراغ یک template دیگر برویم!

MIME Type چیست؟

قبل از اینکه بخواهیم به سراغ ادامه جلسه برویم باید با MIME Type آشنا شویم. اگر مرورگر خود را باز کرده و با کلید f12 قسمت dev tools آن را باز کنید به قابلیت های مختلفی دسترسی خواهید داشت. یکی از این قابلیت ها در سربرگ network است. روی سربرگ network کلیک کنید تا بتوانید تمام درخواست های موجود بین سرور سایت و مرورگر خود را مشاهده نمایید. در نظر داشته باشید که پس از باز کردن network باید یک بار صفحه را refresh کنید تا این درخواست ها برایتان ظاهر شود. به محض refresh شدن صفحه تمام فایل هایی که بین سرور و مرورگر شما رد و بدل شده اند را مشاهده خواهید کرد. در ستون type از همین صفحه، نوع آن فایل را مشاهده خواهید کرد. حالا از شما می خواهم که روی یکی از این فایل ها کلیک کنید. با این کار صفحه دیگری برایتان باز می شود که خودش چندین سربرگ دارد اما برای ما سربرگ header مهم است:

مقدار content-type در header درخواست های HTTP
مقدار content-type در header درخواست های HTTP

من از وب سایت گوگل استفاده کرده ام. در تصویر بالا برخی از این داده ها را می بینید اما قسمت مهم گزارش زیر است:

content-type: text/javascript; charset=UTF-8

یعنی نوع داده فایلی که من روی آن کلیک کرده ام (یکی از فایل های ستون سمت چپ در تصویر بالا) متنی و از نوع جاوا اسکریپت است. ما به این مقدار MIME Type یا Multipurpose Internet Mail Extensions می گوییم. وب سایت توسعه دهندگان موزیلا در این باره می گوید:

Important: Browsers use the MIME type, not the file extension, to determine how to process a URL, so it's important that web servers send the correct MIME type in the response's Content-Type header. If this is not correctly configured, browsers are likely to misinterpret the contents of files and sites will not work correctly, and downloaded files may be mishandled.

اگر بخواهم پاراگراف بالا را به صورت خلاصه برایتان ترجمه کرده و توضیحی نیز اضافه کنم، اینطور خواهد شد: مرورگرها برای پردازش یک فایل اول باید بدانند که این فایل چه نوعی دارد تا رفتار مناسب را از خود نشان داده و آن را به شکل مناسب پردازش کنند. دوما مرورگرها از پسوند فایل برای تشخیص نوع آن استفاده نمی کنند بلکه از MIME Type استفاده می کنند بنابراین در صورتی که سرور MIME Type اشتباه را ارسال کند وب سایت خراب شده و مرورگرها نمی دانند با فایل های مورد نظر شما (کدهای CSS و JavaScript و تصاویر و ویدیو ها و غیره) چه کار کنند.

من پیشنهاد می کنم اگر تازه کار هستید، توضیحات صفحه توسعه دهندگان موزیلا را کاملا مطالعه کنید. قسمتی در این صفحه به نام Important MIME types for Web developers وجود دارد که MIME Type های اصلی و مهم برای توسعه دهندگان وب را توضیح می دهد.

انتخاب قالبی جدید برای portfolio

ما تا این قسمت از قالب آماده وب سایت W3Schools استفاده می کردیم اما برای تمرین بیشتر بهتر است از یک قالب دیگر و حرفه ای تر استفاده کنیم. ما قالب جدیدمان را از وب سایت www.mashup-template.com دانلود می کنیم. من از قالب Univers در این وب سایت استفاده می کنم اما شما می توانید هر قالب دیگری را دانلود کنید. برای این کار روی دکمه download کلیک کرده و از پنجره باز شده گزینه HTML را انتخاب نمایید. با این کار فرآیند دانلود برای شما شروع می شود.

من پوشه جدیدی به نام portfolio ایجاد کرده و محتوای فایل دانلود شده را درون این پوشه extract می کنم. با این کار فایل ها و پوشه های زیادی برایمان ظاهر می شوند. ابتدا باید موارد زیر را حذف کنید:

  • پوشه sample
  • دو فایل با فرمت gz
  • یک فایل با پسوند map

پس از انجام این کار تنها باید فایل های HTML و CSS و JavaScript و یک پوشه به نام asset برایتان باقی بماند که حاوی تصاویر پروژه است. در جلسه بعدی شروع به کدنویسی این پروژه خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.