כיצד להשתמש ביחסי מסד נתונים רבים לרבים עם Flask-SQLAlchemy

המחבר בחר ב־קרן הקוד הפתוח והחופשי לקבלת תרומה כחלק מתוכנית כתוב בשביל תרומות.

הקדמה

Flask הוא פריימוורק קל משקל עבור פייתון המספק כלים ותכונות שימושיים ליצירת אפליקציות אינטרנט בשפת פייתון. SQLAlchemy הוא ערכת כלים ל־SQL המספקת גישה יעילה וביצועית למסדי נתונים רציונליים. היא מספקת דרכים לפעול עם מנועי מסדי נתונים שונים כמו SQLite, MySQL, ו־PostgreSQL. היא מעניקה לך גישה לתכונות ה־SQL של מסד הנתונים. היא מעניקה גם לך Mapper עצם יחסי (ORM), אשר מאפשר לך לבצע שאילתות ולטפל בנתונים באמצעות אובייקטים ושיטות בפייתון. Flask-SQLAlchemy היא תוספת ל־Flask המקלה על שימוש ב־SQLAlchemy עם Flask, ומספקת לך כלים ושיטות לפעול עם מסד הנתונים שלך ביישומי Flask שלך דרך SQLAlchemy.

A many-to-many database relationship is a relationship between two database tables where a record in each table can reference several records in the other table. For example, in a blog, a table for posts can have a many-to-many relationship with a table for storing authors. Each post can have many authors, and each author can write many posts. Therefore, there is a many-to-many relationship between posts and authors. For another example, in a social media application, each post may have many hashtags, and each hashtag may have many posts.

במדריך זה, תשנה אפליקציה שנבנתה בעזרת Flask ו־Flask-SQLAlchemy על ידי הוספת יחס רבים לרבים אליה. תהייה לך יחס בין פוסטים ותגיות, כאשר כל פוסט בבלוג יכול להכיל מספר תגיות, וכל תגית יכולה להכיל מספר פוסטים שתוייגו בה.

איך להשתמש ביחסי מסד נתונים One-to-Many עם Flask-SQLAlchemy הוא סדרת המשך של המדריך שבו אתה בונה מסד נתונים מרובה טבלאות עם יחס one-to-many בין פוסטים ותגובות ביישום לבלוגים.

עד סופו של המדריך, היישום שלך יכלול תכונה חדשה להוספת תגיות לפוסטים. פוסטים יכולים לקבל תגים מרובים, וכל עמוד תג יציג את כל הפוסטים שקיבלו את התג הזה.

דרישות מוקדמות

שלב 1 — הגדרת אפליקציית האינטרנט

בשלב זה, תכינו את אפליקציית הבלוגים כך שתהיה מוכנה לשינויים. תבדקו גם את מודלי מסד הנתונים של Flask-SQLAlchemy ואת הנתיבים של Flask כדי להבין את מבנה האפליקציה. אם עקבתם אחר המדריך במקטע הדרישות המוקדמות ויש לכם את הקוד ואת הסביבה הווירטואלית על המחשב המקומי שלכם, תוכלו לדלג על שלב זה.

כדי להדגים הוספת יחס רבים-אל-רבים ליישום אינטרנט ב־Flask עם Flask-SQLAlchemy, תשתמש בקוד היישום מהמדריך הקודם, שהוא מערכת בלוגים עם יכולת להוסיף ולהציג פוסטים, להגיב על פוסטים, ולקרוא ולמחוק הערות קיימות.

שכפל את המאגר ושנה את שמו מ־flask-slqa-bloggy ל־flask_app באמצעות הפקודה הבאה:

  1. git clone https://github.com/do-community/flask-slqa-bloggy flask_app

נווט לתיקיית flask_app:

  1. cd flask_app

לאחר מכן צור סביבת עבודה וירטואלית חדשה:

  1. python -m venv env

הפעל את הסביבת העבודה:

  1. source env/bin/activate

התקן את Flask וְ-Flask-SQLAlchemy:

  1. pip install Flask Flask-SQLAlchemy

לאחר מכן, הגדר את משתני הסביבה הבאים:

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

FLASK_APP מציין את היישום שאתה פותח כעת, שהוא app.py במקרה זה. FLASK_ENV מציין את מצב הפעולה. תגדיר אותו ל־development למצב פיתוח; זה יאפשר לך לאתר באגים ביישום. זכור לא להשתמש במצב זה בסביבת הפרודקשן.

לאחר מכן, פתח את שאל Flask כדי ליצור את טבלאות המסד נתונים:

  1. flask shell

לאחר מכן ייבא את עצם מסד הנתונים Flask-SQLAlchemy, את מודל ה־Post, ואת מודל ה־Comment, וצור את טבלאות מסד הנתונים באמצעות פונקציית ה־db.create_all():

  1. from app import db, Post, Comment
  2. db.create_all()
  3. exit()

לאחר מכן הוסף נתונים למסד הנתונים באמצעות התוכנית init_db.py:

  1. python init_db.py

פעולה זו מוסיפה שלושה פוסטים וארבעה הערות למסד הנתונים.

הפעל את שרת הפיתוח:

  1. flask run

אם תעבור לדפדפן שלך, תראה את היישום פועל בכתובת ה-URL הבאה:

http://127.0.0.1:5000/

תראו עמוד דומה לזה:

אם תקבלו שגיאה, ודאו שעקבתם אחר השלבים לעיל בצורה תקינה.

כדי לעצור את שרת הפיתוח, השתמשו ב־CTRL + C.

לאחר מכן, תעברו על דפי המודלים של מסד הנתונים של Flask-SQLAlchemy כדי להבין את היחסים הנוכחיים בין הטבלאות. אם אתם מכירים את תוכן קובץ app.py, תוכלו לדלג לשלב הבא.

פתחו את קובץ app.py:

  1. nano app.py

תוכן הקובץ הוא כדלקמן:

flask_app/app.py
import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'


@app.route('/')
def index():
    posts = Post.query.all()
    return render_template('index.html', posts=posts)


@app.route('/<int:post_id>/', methods=('GET', 'POST'))
def post(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        comment = Comment(content=request.form['content'], post=post)
        db.session.add(comment)
        db.session.commit()
        return redirect(url_for('post', post_id=post.id))

    return render_template('post.html', post=post)


@app.route('/comments/')
def comments():
    comments = Comment.query.order_by(Comment.id.desc()).all()
    return render_template('comments.html', comments=comments)


@app.post('/comments/<int:comment_id>/delete')
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    post_id = comment.post.id
    db.session.delete(comment)
    db.session.commit()
    return redirect(url_for('post', post_id=post_id))

כאן יש לכם שני מודלי מסד נתונים המייצגים שני טבלאות:

  • Post: שיש לו עמודת ID, כותרת, תוכן, ויחס One-to-Many עם טבלת התגובות.

  • Comment: שיש לו עמודת ID, עמודה עבור התוכן, ועמודה post_id להתייחסות לפוסט שבו נמצאת התגובה.

מתחת למודלים יש לכם את הנתיבים הבאים:

  • /: עמוד האינדקס, שמציג את כל הפוסטים במסד הנתונים.
  • /<int:post_id>/: עמוד הפוסט היחידי. לדוגמה, הקישור http://127.0.0.1:5000/2/ מציג את פרטי הפוסט השני במסד הנתונים ואת התגובות שלו.
  • /comments/: עמוד שמציג את כל התגובות במסד הנתונים ומקשר לפוסט שבו פורסמה כל תגובה.
  • /comments/<int:comment_id>/delete: נתיב שמוחק תגובה דרך לחצן מחק תגובה.

סגור את קובץ app.py.

בשלב הבא, תשתמש ביחס רבים-לרבים כדי ליצור קישור בין שני טבלאות.

שלב 2 — הגדרת מודלי מסד נתונים עבור יחס רבים-לרבים

בשלב זה, תוסיף מודל מסד נתונים שיצג את טבלת התגיות. תקשור אותו עם טבלת הפוסטים הקיימת באמצעות טבלת קשר, שהיא טבלה שמחברת את שתי הטבלאות שלך ביחס רבים-לרבים. יחס רבים-לרבים מקשר שתי טבלאות שבהן כל פריט בטבלה יש פריטים רבים הקשורים בטבלה השנייה. בטבלת הקשר, כל פוסט יפנה לתגיות שלו וכל תג יפנה לפוסטים המתוייגים עם התג. תכניס גם מספר פוסטים ותגיות למסד הנתונים שלך, תדפיס פוסטים עם התגיות שלהם, ותדפיס תגיות והפוסטים הקשורים אליהם.

נניח שיש לך טבלה פשוטה עבור פוסטי בלוג כך:

Posts
+----+-----------------------------------+
| id | content                           |
+----+-----------------------------------+
| 1  | A post on life and death          |
| 2  | A post on joy                     |
+----+-----------------------------------+

וטבלה עבור תגים כך:

Tags
+----+-------+
| id | name  |
+----+-------+
| 1  | life  |
| 2  | death |
| 3  | joy   |
+----+-------+

תנסה לסמן פוסט על חיים ומוות עם התגיות חיים ו־מוות. ניתן לעשות זאת על ידי הוספת שורה חדשה בטבלת הפוסטים כך:

Posts
+----+-----------------------------------+------+
| id | content                           | tags |
+----+-----------------------------------+------+
| 1  | A post on life and death          | 1, 2 |
| 2  | A post on joy                     |      |
+----+------------------------------------------+

שיטה זו אינה עובדת, מכיוון שכל עמודה צריכה לכלול ערך יחיד בלבד. כאשר ישנם ערכים מרובים, פעולות בסיסיות כגון הוספה ועדכון נתונים מסתבכות ואיטיות. במקום זאת, יש ליצור טבלה שלישית שמפנה את המפתחות הראשיים של הטבלאות הקשורות – לטבלה זו נקראת לעיתים טבלת עמודות חיבור או טבלת הצמיחה, והיא מאחסנת מזהים של כל פריט מכל טבלה.

להלן דוגמה לטבלת הצמיחה שמקשרת בין פוסטים ותגיות:

post_tag
+----+---------+-------------+
| id | post_id | tag_id      |
+----+---------+-------------+
| 1  | 1       | 1           |
| 2  | 1       | 2           |
+----+---------+-------------+

בשורה הראשונה, הפוסט עם המזהה 1 (שזהו, פוסט על חיים ומוות) מתייחס לתג עם המזהה 1 (חיים). בשורה השנייה, אותו פוסט גם מתייחס לתג עם המזהה 2 (מוות). זה אומר שהפוסט מסומן גם בתגיות חיים ו־מוות. באופן דומה, ניתן לסמן כל פוסט עם מספר תגיות.

עכשיו, תשנה את קובץ app.py כך שתוסיף מודל מסד נתונים חדש שמייצג את הטבלה שבה תשמור תגיות. תוסיף גם טבלת post_tag המקשרת בין פוסטים לתגיות.

לפני כל דבר, פתח את app.py כדי להקים קשר בין פוסטים ותגיות:

  1. nano app.py

הוסף טבלת post_tag ומודל Tag מתחת לאובייקט db ומעל המודל Post, ואז הוסף יחס tags pseudo-column למודל Post כך שתוכל לגשת לתגיות של הפוסט דרך post.tags ולגשת לפוסטים של תג דרך tag.posts:

flask_app/app.py

# ...

db = SQLAlchemy(app)


post_tag = db.Table('post_tag',
                    db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
                    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
                    )


class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

    def __repr__(self):
        return f'<Tag "{self.name}">' 



class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')
    tags = db.relationship('Tag', secondary=post_tag, backref='posts')

    def __repr__(self):
        return f'<Post "{self.title}">'

שמור וסגור את הקובץ.

כאן אתה משתמש בפונקציית db.Table() כדי ליצור טבלה עם שני עמודות. עבור טבלאות של התחברות, פרקטיקה הטובה היא להשתמש בטבלה במקום במודל במסד נתונים.

טבלת post_tag מכילה שתי עמודות המייצגות שני מפתחות זרים, שהם מפתחות המשמשים להתייחסות לעמודות מפתח ראשית בטבלה אחרת:

  • post_id: מפתח זר של מספר שלם המייצג את זיהוי הפוסט ומתייחס לעמודת הזיהוי בטבלת post.
  • tag_id: מפתח זר של מספר שלם המייצג את זיהוי התג ומתייחס לעמודת הזיהוי בטבלת tag.

המפתחות הללו מקימים את היחסים בין הטבלאות.

מתחת לטבלת post_tag, אתה יוצר מודל Tag, המייצג את הטבלה שבה תאחסן את התגיות שלך. טבלת התגיות מכילה שתי עמודות:

  • id: זיהוי התג.
  • name: שם התג.

אתה משתמש בשם התג במתודת __repr__() המיוחדת כדי להעניק לכל אובייקט תג מיוצג מחרוזת ברורה למטרות איתור שגיאות.

אתה מוסיף משתנה קבוע tags למודל Post. אתה משתמש בשיטת db.relationship(), ומעביר לה את שם מודל התגים (Tag במקרה זה).

אתה מעביר את טבלת הקישורים post_tag לפרמטר secondary כדי להגדיר יחס רבים-לרבים בין פוסטים ותגי פוסטים.

אתה משתמש בפרמטר backref כדי להוסיף הפניה אחורית שתתנהג כמו עמודה למודל Tag. בכך, תוכל לגשת לפוסטים של התג דרך tag.posts ולתגי הפוסט דרך post.tags. תראה דוגמה מדגימה לכך מאוחר יותר.

לאחר מכן, ערוך את קובץ התכנית פייתון init_db.py כדי לשנות את מסד הנתונים ולהוסיף את טבלת הקישורים post_tag ואת טבלת התגים המבוססת על מודל Tag:

  1. nano init_db.py

ערוך את הקובץ כדלקמן:

flask_app/init_db.py
from app import db, Post, Comment, Tag

db.drop_all()
db.create_all()

post1 = Post(title='Post The First', content='Content for the first post')
post2 = Post(title='Post The Second', content='Content for the Second post')
post3 = Post(title='Post The Third', content='Content for the third post')

comment1 = Comment(content='Comment for the first post', post=post1)
comment2 = Comment(content='Comment for the second post', post=post2)
comment3 = Comment(content='Another comment for the second post', post_id=2)
comment4 = Comment(content='Another comment for the first post', post_id=1)

tag1 = Tag(name='animals')
tag2 = Tag(name='tech')
tag3 = Tag(name='cooking')
tag4 = Tag(name='writing')

post1.tags.append(tag1)  # הקש את הפוסט הראשון בתג 'חיות'
post1.tags.append(tag4)  # הקש את הפוסט הראשון בתג 'כתיבה'
post3.tags.append(tag3)  # הקש את הפוסט השלישי בתג 'בישול'
post3.tags.append(tag2)  # הקש את הפוסט השלישי בתג 'טכנולוגיה'
post3.tags.append(tag4)  # הקש את הפוסט השלישי בתג 'כתיבה'


db.session.add_all([post1, post2, post3])
db.session.add_all([comment1, comment2, comment3, comment4])
db.session.add_all([tag1, tag2, tag3, tag4])

db.session.commit()

שמור וסגור את הקובץ.

כאן, אתה מייבא את המודל Tag. אתה מוחק הכל מהמסד נתונים באמצעות פונקציית db.drop_all() כדי להוסיף את הטאגים ואת הטבלה post_tag בבטחה וכדי למנוע כל בעיות נפוצות הקשורות להוספת טבלאות חדשות למסד נתונים. לאחר מכן אתה יוצר את כל הטבלאות מחדש באמצעות הפונקציה db.create_all().

לאחר הקוד מהמדריך הקודם המכריז על הפוסטים והתגובות, אתה משתמש במודל Tag כדי ליצור ארבעה תגיות.

אז אתה מוסיף תגיות לפוסטים באמצעות המאפיין tags שנוסף דרך השורה tags = db.relationship('Tag', secondary=post_tag, backref='posts') בקובץ app.py. אתה משייך תגיות לפוסטים באמצעות שיטת append() דומה לרשימות בפייתון.

בשלב הבא, אתה מוסיף את התגיות שיצרת למסד הנתונים באמצעות הפונקציה db.session.add_all().

הערה:

הפונקציה db.create_all() לא תיצור מחדש או תעדכן טבלה אם היא כבר קיימת. לדוגמה, אם אתה משנה את המודל שלך על ידי הוספת עמודה חדשה ומריץ את הפונקציה db.create_all(), השינוי שאתה עושה במודל לא ייושם על הטבלה אם הטבלה כבר קיימת במסד הנתונים. הפתרון הוא למחוק את כל טבלאות המסד הקיימות באמצעות הפונקציה db.drop_all() ולאחר מכן ליצור אותן מחדש עם הפונקציה db.create_all(), כפי שמודגם בקובץ init_db.py.

התהליך הזה יחוליף את השינויים שתעשה לדפוסים שלך, אך ימחק גם את כל הנתונים הקיימים במסד הנתונים. כדי לעדכן את מסד הנתונים ולשמור על הנתונים הקיימים, יהיה עליך להשתמש בהגדרת המסכם, שמאפשרת לך לשנות את הטבלאות שלך ולשמור על הנתונים. באפשרותך להשתמש בהרחבה Flask-Migrate כדי לבצע הגדרות מסכמת SQLAlchemy דרך ממשק השורת פקודה של Flask.

הרץ את התוכנית init_db.py כדי ליישם שינויים במסד הנתונים:

  1. python init_db.py

התוכנית צריכה להריץ בהצלחה בלי פלט. אם אתה רואה שגיאה, ודא כי ביצעת את השינויים בקובץ init_db.py בצורה תקינה.

כדי לצפות בפוסטים ובתגיות הקיימים כעת במסד הנתונים, פתח את מסך ה Flask:

  1. flask shell

הרץ את קוד הפייתון הבא שמעבור על פוסטים ותגיות:

from app import Post

posts = Post.query.all()

for post in posts:
    print(post.title)
    print(post.tags)
    print('---')

כאן, אתה מייבא את הדגם Post מתוך app.py. אתה שואל את טבלת הפוסטים ומשיג את כל הפוסטים במסד הנתונים. אתה עובר על כל הפוסטים ומדפיס את כותרת הפוסט ואת רשימת התגיות המשויות עם כל פוסט.

תקבל פלט דומה לפלט הבא:

Output
Post The First [<Tag "animals">, <Tag "writing">] --- Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] --- Post The Second [] ---

אתה יכול לגשת לשמות התגיות באמצעות tag.name כפי שמודגש בדוגמה הבאה, שאתה יכול להריץ בעזרת מסך ה Flask:

from app import Post

posts = Post.query.all()

for post in posts:
    print('TITLE: ', post.title)
    print('-')
    print('TAGS:')
    for tag in post.tags:
        print('> ', tag.name)
    print('-'*30)

כאן, לצד הדפסת כותרת הפוסט, אתה עובר על כל התגיות של כל פוסט ומדפיס את שם התגית.

תקבל פלט דומה לפלט הבא:

Output
TITLE: Post The First - TAGS: > animals > writing ------------------------------ TITLE: Post The Third - TAGS: > cooking > tech > writing ------------------------------ TITLE: Post The Second - TAGS: ------------------------------

כפי שאתה יכול לראות, התגיות שהוספת לפוסטים בתוכנית init_db.py מקושרות כראוי לפוסטים שנתייגו בהן.

כדי לראות הדגמה של איך לגשת לפוסטים שתווייגו עם תג מסוים דרך tag.posts, הרץ את הקוד הבא בתוך מסך הפלאסק:

from app import Tag

writing_tag = Tag.query.filter_by(name='writing').first()

for post in writing_tag.posts:
    print(post.title)
    print('-'*6)
    print(post.content)
    print('-')
    print([tag.name for tag in post.tags])
    print('-'*20)

אתה מייבא את מודל התג (Tag). אז אתה משתמש בשיטת filter_by() על התכונה query ומעביר לה פרמטר name כדי לקבל את התג writing לפי שמו, ואתה מקבל את התוצאה הראשונה באמצעות שיטת first(). אתה שומר את אובייקט התג במשתנה בשם writing_tag. למידע נוסף על השיטה filter_by, ראה שלב 4 של איך להשתמש ב-Flask-SQLAlchemy לקריאה עם מסדי נתונים ביישום Flask.

אתה עובר דרך הפוסטים שתווייגו עם התג writing, שאליה אתה גושר דרך writing_tag.posts. אתה מדפיס את כותרת הפוסט, התוכן, ורשימת שמות התגים שאתה בונה באמצעות רשימת קימפרהנשיין המבוססת על התגיות של הפוסט, אליהם אתה גושר דרך post.tags.

תקבל פלט דומה לזה:

Output
Post The Third ------ Content for the third post - ['cooking', 'tech', 'writing'] -------------------- Post The First ------ Content for the first post - ['animals', 'writing'] --------------------

כאן אתה רואה את שני הפוסטים שתווייגו עם התג writing, ושמות התגים מוצגים ברשימת פייתון.

כעת אתה יכול לגשת לפוסטים ולתגים שלהם ולגשת לפוסטים של תג מסוים.

אתה הוספת מודל בסיס נתונים שמייצג את טבלת התגיות. קשרת בין פוסטים ובין תגיות באמצעות טבלת איגוד, והכנסת כמה תגיות לבסיס הנתונים והקשת פוסטים אליהם. גישת פוסטים ותגיותיהם ופוסטי התג האישי. בשלב הבא, תשתמש ב-Flask shell כדי להוסיף פוסטים חדשים ותגיות חדשות ולקשר ביניהם, ותלמד איך להסיר תגיות מפוסט.

שלב 3 — ניהול נתונים ביחס רב לרב

בשלב זה, תשתמש ב-Flask shell כדי להוסיף פוסטים חדשים לבסיס הנתונים, להוסיף תגיות ולקשר בין פוסטים ותגיות. תגיש פוסטים עם תגיותיהם, ותראה כיצד לנתח פריט מפריט אחר ביחסי רב לרב.

ראשית, בעזרת סביבת התכנות שלך מופעלת, פתח את ה-Flask shell אם עדיין לא עשית זאת:

  1. flask shell

בשלב הבא, הוסף מספר פוסטים ותגיות:

from app import db, Post, Tag

life_death_post = Post(title='A post on life and death', content='life and death')
joy_post = Post(title='A post on joy', content='joy')

life_tag = Tag(name='life')
death_tag = Tag(name='death')
joy_tag = Tag(name='joy')

life_death_post.tags.append(life_tag)
life_death_post.tags.append(death_tag)
joy_post.tags.append(joy_tag)

db.session.add_all([life_death_post, joy_post, life_tag, death_tag, joy_tag])

db.session.commit()

זה יוצר שני פוסטים ושלוש תגיות. אתה מקשר פוסטים עם התגיות הקשורות אליהם, ואתה משתמש בשיטת add_all() כדי להוסיף את הפריטים שנוצרו לאסימון בסיס הנתונים. אז אתה מכין את השינויים ומחייב אותם לבסיס הנתונים באמצעות השיטה commit().

בשלב הבא, השתמש ב-Flask shell כדי לקבל את כל הפוסטים ותגיותיהם כמו שעשית בשלב הקודם:

posts = Post.query.all()

for post in posts:
    print(post.title)
    print(post.tags)
    print('---')

תקבל פלט דומה לזה:

Output
Post The First [<Tag "animals">, <Tag "writing">] --- Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] --- Post The Second [] --- A post on life and death [<Tag "life">, <Tag "death">] --- A post on joy [<Tag "joy">] ---

אתה יכול לראות שנוספו פוסטים יחד עם התגיות שלהם.

כדי להדגים איך לשבור קשר בין שני פריטים ביחסי נתונים רבים לרבים, נניח שהפוסט הפוסט השלישי כבר אינו עוסק בבישול, אז עליכם להסיר את התג בישול ממנו.

ראשית, עליכם לקבל את הפוסט והתג שברצונכם להסיר:

  1. from app import db, Post, Tag
  2. post = Post.query.filter_by(title='Post The Third').first()
  3. tag = Tag.query.filter_by(name='cooking').first()
  4. print(post.title)
  5. print(post.tags)
  6. print(tag.posts)

כאן אתם מביאים את הפוסט בכותרת הפוסט השלישי באמצעות שיטת filter_by(). אתם משיגים את התג בישול. אתם מדפיסים את כותרת הפוסט, את התגים שלו, ואת הפוסטים שתוגנו עם התג בישול.

שיטת filter_by() מחזירה אובייקט שאילתה, וניתן להשתמש בשיטת all() כדי לקבל רשימה של כל התוצאות. אך מכיוון שאנו מצפים לרק תוצאה אחת במקרה זה, אנו משתמשים בשיטת first() כדי לקבל את התוצאה הראשונה (והיחידה). למידע נוסף על השימוש בשיטות first() ו- all(), ראו שלב 4 של כיצד להשתמש ב- Flask-SQLAlchemy כדי לפעול עם מסדי נתונים באפליקציה של Flask.

תקבלו את הפלט הבא:

Output
Post The Third [<Tag "cooking">, <Tag "tech">, <Tag "writing">] [<Post "Post The Third">]

כאן אתם רואים את כותרת הפוסט, את התגים של הפוסט, ורשימת הפוסטים שתוגנו עם התג בישול.

כדי להסיר את התג בישול מהפוסט, השתמשו בשיטת remove() כך:

  1. post.tags.remove(tag)
  2. db.session.commit()
  3. print(post.tags)
  4. print(tag.posts)

כאן אתם משתמשים בשיטת remove() כדי לנתק את התג בישול מהפוסט. לאחר מכן אתם משתמשים בשיטת db.session.commit() כדי להחיל את השינויים במסד הנתונים.

תקבלו פלט שמאשר שהתג הוסר מהפוסט:

Output
[<Tag "tech">, <Tag "writing">] []

כפי שאתה רואה, התג cooking כבר אינו ברשימת התגים של post.tags, והפוסט הוסר מרשימת הפוסטים של tag.posts.

צא מהקופסה של Flask:

  1. exit()

הוספת פוסטים חדשים ותגי תגיות. תייגת פוסטים והסרת תגיות מפוסטים. בשלב הבא, תציג את התגיות של כל פוסט בדף הבית של הבלוג שלך ב-Flask.

שלב 4 — הצגת תגיות מתחת לכל פוסט

בשלב זה, תערוך את תבנית האינדקס כדי להציג תגיות מתחת לכל פוסט.

ראשית, תסתכל על דף הבית הנוכחי של בלוג ה-Flask.

עם סביבת התכנות שלך מופעלת, תגיד ל-Flask על היישום (app.py במקרה זה) באמצעות משתנה הסביבה FLASK_APP. לאחר מכן, הגדר את משתנה הסביבה FLASK_ENV ל-development כדי להפעיל את היישום במצב פיתוח:

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

לבסוף, הרץ את היישום:

  1. flask run

עם השרת לפיתוח פועל, בקר בכתובת ה-URL הבאה בדפדפן שלך:

http://127.0.0.1:5000/

תראה עמוד דומה לתמונה הבאה:

השאר את השרת לפיתוח רץ ופתח חלון טרמינל חדש.

תצטרך להציג את התגיות של כל פוסט בשתי עמודים: מתחת לכל פוסט בעמוד האינדקס ומתחת לתוכן הפוסט בעמוד הפוסט. תשתמש באותו קוד כדי להציג את התגיות. כדי למנוע חזרת קוד, תשתמש ב Jinja macro, שמתנהג כמו פונקציה בפייתון. המקרו מחזיק קוד HTML דינמי שניתן להציג בכל מקום שבו תקרא למקרו, ועריכה שלו מיישמת שינויים בכל מקום שבו נקרא. זה מאפשר ניצוליות בקוד.

ראשית, פתח קובץ חדש בשם macros.html בתיקיית templates שלך:

  1. nano templates/macros.html

הוסף את הקוד הבא לקובץ:

flask_app/templates/macros.html
{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
                <a href="#" style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

שמור וסגור את הקובץ.

כאן, אתה משתמש במילת המפתח macro כדי להכריז על מקרו בשם display_tags() עם פרמטר בשם post. אתה משתמש בתגית <div>, שבה אתה מציג כותרת <h4>. אתה משתמש בלולאת for כדי לעבור דרך התגיות של אובייקט הפוסט שיעבר כארגומנט למקרו כאשר תקרא לו, דומה לאופן בו ארגומנט מועבר בקריאת פונקציה בפייתון. אתה מקבל תגיות דרך post.tags. אתה מציג את שם התג בתוך תגית <a>. תערוך את ערך המאפיין href כדי ליצור קישור לעמוד תג שתיצור, שבו יוצגו כל הפוסטים המתוייגים בתג מסוים. אתה מציין את סיום המקרו באמצעות מילת המפתח endmacro.

בשלב הבא, כדי להציג תגיות מתחת לכל פוסט בעמוד האינדקס, פתח את קובץ התבנית index.html:

  1. nano templates/index.html

ראשית, תצטרך לייבא את המאקרו display_tags() מהקובץ macros.html. הוסף את הייבוא הבא בראש הקובץ מעל שורת {% extends 'base.html' %}:

flask_app/templates/index.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

באמצעות הוספת השורה הבאה בתוך הלולאה for post in posts, תקרא למאקרו display_tags() כך:

flask_app/templates/index.html
{% for post in posts %}
    <div class="post">
        <p><b>#{{ post.id }}</b></p>
        <b>
            <p class="title">
                <a href="{{ url_for('post', post_id=post.id)}}">
                    {{ post.title }}
                </a>
            </p>
        </b>
        <div class="content">
            <p>{{ post.content }}</p>
        </div>

        {{ display_tags(post) }}

        <hr>
    </div>
{% endfor %}

שמור וסגור את הקובץ.

אתה קורא למאקרו display_tags(), ומעביר לו את האובייקט post. זה יציג את שמות התגיות מתחת לכל פוסט.

רענן את עמוד האינדקס בדפדפן שלך ותראה תגיות מתחת לכל פוסט, כפי שמוצג בתמונה הבאה:

בשלב הבא, תוסיף תגיות מתחת לתוכן הפוסט בעמוד הפוסט. פתח את קובץ התבנית post.html:

  1. nano templates/post.html

ראשית, הוסף את המאקרו display_tags בראש הקובץ:

flask_app/templates/post.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

לאחר מכן, קרא למאקרו display_tags(), ומעביר לו את האובייקט post מתחת לתוכן הפוסט ומעל התגית <hr>:

flask_app/templates/post.html
<div class="post">
    <p><b>#{{ post.id }}</b></p>
    <b>
        <p class="title">{{ post.title }}</p>
    </b>
    <div class="content">
        <p>{{ post.content }}</p>
    </div>

    {{ display_tags(post) }}

    <hr>
    <h3>Comments</h3>

שמור וסגור את הקובץ.

עכשיו, נווט לעמוד פוסט:

http://127.0.0.1:5000/2

תראה שתגיות מוצגות באותו אופן כמו התגיות המוצגות בעמוד האינדקס.

הצגת את התגיות שהוספת לפוסטים מתחת לכל פוסט. בשלב הבא, תוסיף נתיב חדש ליישום ה-Flask שלך שמציג את כל הפוסטים המסומנים בתג מסוים. לאחר מכן, תפעיל את הקישורים של התגים שהצגת בשלב זה.

שלב 5 — הצגת תגיות והפוסטים שלהן

בשלב זה, תוסיף נתיב ותבנית ליישום האינטרנט שלך כדי להציג את התגיות שיש לך במסד הנתונים והפוסטים שלהן.

ראשית, תוסיף נתיב להצגת הפוסטים של כל תג. לדוגמה, הנתיב /tags/שם_התג/ יציג עמוד שמציג את כל הפוסטים שתוגו בתג בשם שם_התג.

פתח את app.py לעריכה:

  1. nano app.py

הוסף את הנתיב הבא לסוף הקובץ:

flask_app/app.py

# ...

@app.route('/tags/<tag_name>/')
def tag(tag_name):
    tag = Tag.query.filter_by(name=tag_name).first_or_404()
    return render_template('tag.html', tag=tag)

שמור וסגור את הקובץ.

בפרק זה אתה משתמש במשתנה URL בשם שם_התג שמקבע את התג והפוסטים שתוגו בו שיוצגו בעמוד התג. שם התג מועבר לפונקציית התצוגה tag() דרך הפרמטר שם_התג, שאתה משתמש בו על השיטה filter_by() כדי לשאול את התג. אתה משתמש ב־first_or_404() כדי לקבל את אובייקט התג ולאחסן אותו במשתנה בשם תג, או להגיב עם הודעת שגיאת 404 Not Found במקרה שלא קיים תג עם השם הנתון במסד הנתונים.

לאחר מכן, אתה מרנדר קובץ תבנית בשם tag.html, ומעביר לו את אובייקט התג tag.

פתח את הקובץ החדש templates/tag.html לעריכה:

  1. nano templates/tag.html

הוסף את הקוד הבא לתוכו:

flask_app/templates/tag.html
{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

{% block content %}
    <span class="title">
        <h1>{% block title %} Posts Tagged with "{{ tag.name }}" {% endblock %}</h1>
    </span>
    <div class="content">
        {% for post in tag.posts %}
        <div class="post">
            <p><b>#{{ post.id }}</b></p>
            <b>
                <p class="title">
                    <a href="{{ url_for('post', post_id=post.id)}}">
                        {{ post.title }}
                    </a>
                </p>
            </b>
            <div class="content">
                <p>{{ post.content }}</p>
            </div>

            {{ display_tags(post) }}

            <hr>
        </div>
        {% endfor %}
    </div>
{% endblock %}

שמור וסגור את הקובץ.

אתה מייבא את המקרו display_tags() מהקובץ macros.html, ומרחיב את תבנית הבסיס.

בתוך בלוק התוכן, אתה מגדיר כותרת ככותרת עם שם התג כלול. לאחר מכן אתה מפעיל לולאה על הפוסטים המתוייגים בתג נתון, שאליה אתה נגיש דרך tag.posts. אתה מציג את מזהה הפוסט, את כותרת הפוסט ואת תוכן הפוסט. לאחר מכן אתה קורא למקרו display_tags() כדי להציג את כל התגים של הפוסט.

עם השרת שלך פועל, נווט אל ה-URL הבא:

http://127.0.0.1:5000/tags/writing/

זהו הדף עבור התג writing. כפי שאתה רואה, כל הפוסטים שתוייגו בתג writing מוצגים:

עכשיו ערוך את המקרו display_tags() כדי להפוך את הקישורים של התגים לפועליים. פתח את macros.html:

  1. nano templates/macros.html

ערוך את ערך המאפיין href כך:

flask_app/templates/macros.html

{% macro display_tags(post) %}
    <div class="tags">
        <p>
            <h4>Tags:</h4>
            {% for tag in post.tags %}
            <a href="{{ url_for('tag', tag_name=tag.name) }}"
               style="text-decoration: none; color: #dd5b5b">
                    {{ tag.name }}
                </a>
                |
            {% endfor %}
        </p>
    </div>
{% endmacro %}

שמור וסגור את הקובץ.

רענן את הדפים בהם המקרו display_tags() נעשה שימוש, ותראה שהקישורים של התגים פועלים כעת:

http://127.0.0.1:5000/
http://127.0.0.1:5000/2/
http://127.0.0.1:5000/tags/writing/

כפי שאתה רואה, באמצעות מקרו Jinja אפשר לשכפל קוד, ועריכה של מקרו מיישם שינויים במספר תבניות.

הוספת עמוד לתגים יחידים שבו משתמשים יכולים לצפות בכל הפוסטים שתוייגו בתג מסוים, והתגים מתחת לפוסטים מקשרים כעת לעמוד חדש זה.

מסקנה

התכונה שהוספת למערכת הבלוגים שלך מדגימה איך לנהל יחסי רבים לרבים באמצעות הרחבה Flask-SQLAlchemy. למדת כיצד לקשר בין שני טבלאות קשורות באמצעות טבלת שיוך (נקראת גם טבלת שיוך), לקשר ערך עם ערך נוסף, להוסיף את הערך למסד הנתונים, ולגשת ולהפריד מידע מערך.

אם ברצונך לקרוא עוד על Flask, ניתן לבדוק את המדריכים האחרים בסדרת איך לבנות אפליקציות אינטרנט עם Flask.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-many-to-many-database-relationships-with-flask-sqlalchemy