طرح بلاگ¶
شما از همان تکنیک هایی که هنگام نوشتن طرح احراز هویت یاد گرفتید برای نوشتن طرح اولیه وبلاگ استفاده خواهید کرد. وبلاگ باید همه پست ها را فهرست کند، به کاربرانی که وارد سیستم شده اند اجازه ایجاد پست ها را بدهد و به نویسنده یک پست اجازه دهد آن را ویرایش یا حذف کند.
همانطور که هر نما را پیاده سازی می کنید، سرور توسعه را در حال اجرا نگه دارید. همانطور که تغییرات خود را ذخیره می کنید، سعی کنید به URL موجود در مرورگر خود بروید و آنها را آزمایش کنید.
طرح¶
طرح اولیه را تعریف کرده و در کارخانه اپلیکیشن ثبت کنید.
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from flaskr.auth import login_required
from flaskr.db import get_db
bp = Blueprint('blog', __name__)
با استفاده از app.register_blueprint()
طرح اولیه را از کارخانه وارد و ثبت کنید. قبل از بازگرداندن برنامه، کد جدید را در انتهای عملکرد کارخانه قرار دهید.
def create_app():
app = ...
# existing code omitted
from . import blog
app.register_blueprint(blog.bp)
app.add_url_rule('/', endpoint='index')
return app
بر خلاف طرح auth، طرح اولیه وبلاگ url_prefix
ندارد. بنابراین نمای index
در /
، نمای create
در /create
و سایر موارد با نام خودشان خواهد بود. وبلاگ ویژگی اصلی Flaskr است، بنابراین منطقی است که نمایه وبلاگ شاخص اصلی باشد.
با این حال، نقطه پایانی برای نمای index
که در زیر تعریف شده است، blog.index
خواهد بود. برخی از نماهای احراز هویت به یک نقطه پایانی ساده index
اشاره میکنند. app.add_url_rule()
نام نقطه پایانی 'index'
را با نشانی اینترنتی /
مرتبط می کند به طوری که url_for('index')
یا url_for ('blog.index')
هر دو کار خواهند کرد و در هر صورت یک URL /
ایجاد می شود.
در برنامه دیگری، میتوانید به وبلاگ یک url_prefix
بدهید و یک نمای index
جداگانه در کارخانه برنامه، شبیه به نمای hello
تعریف کنید. سپس نقاط پایانی و URL های index
و blog.index
متفاوت خواهند بود.
فهرست(Index)¶
فهرست همه پستها به ترتیب تاریخ را نشان میدهد. از یک``JOIN`` استفاده می شود تا اطلاعات نویسنده از جدول user
در نتیجه موجود باشد.
@bp.route('/')
def index():
db = get_db()
posts = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('blog/index.html', posts=posts)
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('blog.create') }}">New</a>
{% endif %}
{% endblock %}
{% block content %}
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == post['author_id'] %}
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post['body'] }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}
هنگامی که یک کاربر وارد سیستم می شود، بلوک header
پیوندی را به نمای create
اضافه می کند. وقتی کاربر نویسنده یک پست باشد، پیوند «ویرایش» به نمای «به روز رسانی» برای آن پست را می بیند. loop.last
یک متغیر ویژه است که در Jinja for loops موجود است. برای نمایش یک خط بعد از هر پست به جز آخرین پست، برای جداسازی بصری آنها استفاده می شود.
ایجاد(Create)¶
نمای create
مانند نمای تأیید اعتبار register
عمل می کند. یا فرم نمایش داده می شود یا داده های ارسال شده اعتبارسنجی می شود و پست به پایگاه داده اضافه می شود یا خطا نشان داده می شود.
دکوراتور login_required
که قبلا نوشتید در نماهای وبلاگ استفاده می شود. یک کاربر برای بازدید از این نماها باید وارد سیستم شود، در غیر این صورت به صفحه ورود هدایت می شوند.
@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO post (title, body, author_id)'
' VALUES (?, ?, ?)',
(title, body, g.user['id'])
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/create.html')
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}
بروزرسانی¶
هر دو نمای update
و delete
باید post
را توسط id
واکشی و بررسی کنند که آیا نویسنده با کاربر وارد شده مطابقت دارد یا خیر. برای جلوگیری از تکرار کد، میتوانید تابعی بنویسید تا post
را دریافت کند و آن را از هر نما فراخوانی کند.
def get_post(id, check_author=True):
post = get_db().execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()
if post is None:
abort(404, f"Post id {id} doesn't exist.")
if check_author and post['author_id'] != g.user['id']:
abort(403)
return post
تابع abort()
یک استثنای خاص ایجاد می کند که یک کد وضعیت HTTP را برمی گرداند. یک پیام اختیاری طول می کشد تا با خطا نشان داده شود، در غیر این صورت یک پیام پیش فرض استفاده می شود. ۴۰۴
به معنای «یافت نشد» و ۴۰۳
به معنای «ممنوع» است. (۴۰۱
به معنای «غیرمجاز» است، اما شما به جای بازگرداندن آن وضعیت، به صفحه ورود هدایت می شوید.)
آرگومان check_author
به گونه ای تعریف شده است که می توان از این تابع برای دریافت post
بدون بررسی نویسنده استفاده کرد. اگر یک نما بنویسید تا یک پست را در یک صفحه نشان دهید، جایی که کاربر اهمیتی ندارد زیرا پست را تغییر نمی دهد.
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
@login_required
def update(id):
post = get_post(id)
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None
if not title:
error = 'Title is required.'
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'UPDATE post SET title = ?, body = ?'
' WHERE id = ?',
(title, body, id)
)
db.commit()
return redirect(url_for('blog.index'))
return render_template('blog/update.html', post=post)
برخلاف نماهایی که تاکنون نوشتهاید، تابع update
آرگومان , id
را میگیرد. که با <int:id>
در مسیر مطابقت دارد. یک نشانی وب واقعی شبیه /1/update
خواهد بود. فلاسک 1
را می گیرد، مطمئن می شود که یک int
است و آن را به عنوان آرگومان id
ارسال می کند. اگر int:
را مشخص نکنید و به جای آن <id>
را انجام دهید، یک رشته خواهد بود. برای ایجاد یک URL به صفحه بهروزرسانی، url_for()
باید از id
عبور داده شود تا بداند چه چیزی را باید پر کند url_for('blog.update', id=post['id'])
. این نیز در فایل index.html
در بالا موجود است.
نمای create
و update
بسیار شبیه به هم هستند. تفاوت اصلی این است که نمای update
از یک شی post
و یک کوئری UPDATE
به جای INSERT
استفاده می کند. با مقداری refactoring هوشمندانه، می توانید از یک نما و الگو برای هر دو عملکرد استفاده کنید، اما برای آموزش، جدا نگه داشتن آنها واضح تر است.
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title"
value="{{ request.form['title'] or post['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}
این قالب دو فرم دارد. اولین مورد داده های ویرایش شده را به صفحه فعلی پست می کند (/<id>/update
). فرم دیگر فقط حاوی یک دکمه است و یک ویژگی عملی
را مشخص می کند که به جای آن در نمای حذف پست می شود. این دکمه از مقداری جاوا اسکریپت برای نشان دادن گفتگوی تأیید قبل از ارسال استفاده می کند.
الگوی {{ request.form['title'] or post['title'] }}
برای انتخاب دادههایی که در فرم نشان داده میشوند استفاده میشود. هنگامی که فرم ارسال نشده است، دادههای post
اصلی ظاهر میشود، اما اگر دادههای فرم نامعتبر پست شده باشد، میخواهید آن را نمایش دهید تا کاربر بتواند خطا را برطرف کند، بنابراین request.form
به جای آن استفاده میشود. request
متغیر دیگری است که به صورت خودکار در قالب ها موجود است.
حذف¶
نمای حذف الگوی خود را ندارد، دکمه حذف بخشی از update.html
است و به نشانی اینترنتی /<id>/delete
پست میشود. از آنجایی که هیچ الگوی وجود ندارد، فقط روش POST
را مدیریت میکند و سپس به نمای «نمایه» هدایت میشود.
@bp.route('/<int:id>/delete', methods=('POST',))
@login_required
def delete(id):
get_post(id)
db = get_db()
db.execute('DELETE FROM post WHERE id = ?', (id,))
db.commit()
return redirect(url_for('blog.index'))
تبریک می گویم، شما اکنون نوشتن درخواست خود را به پایان رسانده اید! کمی وقت بگذارید و همه چیز را در مرورگر امتحان کنید. با این حال، هنوز کارهای بیشتری قبل از تکمیل پروژه وجود دارد.
با پروژه را قابل نصب کنید ادامه دهید.