- Learning Flask Framework
- Matt Copperwaite Charles Leifer
- 1051字
- 2021-07-30 10:18:35
Building a tagging system
Tags are a lightweight taxonomy system that is perfect for blogs. Tags allow you to apply multiple categories to a blog post and allow multiple posts to be related to one another outside their category. On my own blog I use tags to organize the posts, so that people interested in reading my posts about Flask need only look under the "Flask" tag and find all the relevant posts. As per the spec that we discussed in Chapter 1, Creating Your First Flask Application, each blog entry can have as few or as many tags as you want, so a post about Flask might be tagged with both Flask and Python. Similarly, each tag (for example, Python) can have multiple entries associated with it. In database parlance, this is called a many-to-many relationship.
In order to model this, we must first create a model to store tags. This model will store the names of tags we use, so after we've added a few tags the table might look something like the following one:

Let's open models.py
and add a definition for the Tag
model. Add the following class at the end of the file, below the Entry
class:
class Tag(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) slug = db.Column(db.String(64), unique=True) def __init__(self, *args, **kwargs): super(Tag, self).__init__(*args, **kwargs) self.slug = slugify(self.name) def __repr__(self): return '<Tag %s>' % self.name
You've seen all of this before. We've added a primary key, which will be managed by the database, and a single column to store the name of the tag. The name
column is marked as unique, so each tag will only be represented by a single row in this table, regardless of how many blog entries it appears on.
Now that we have models for both blog entries and tags, we need a third model to store the relationships between the two. When we wish to signify that a blog entry is tagged with a particular tag, we will store a reference in this table. The following is a diagram of what is happening at the database table level:

Since we will never be accessing this intermediary table directly (SQLAlchemy will handle it for us transparently), we will not create a model for it but will simply specify a table to store the mapping. Open models.py
and add the following highlighted code:
import datetime, re from app import db def slugify(s): return re.sub('[^\w]+', '-', s).lower() entry_tags = db.Table('entry_tags', db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')), db.Column('entry_id', db.Integer, db.ForeignKey('entry.id')) ) class Entry(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100)) slug = db.Column(db.String(100), unique=True) body = db.Column(db.Text) created_timestamp = db.Column(db.DateTime, default=datetime.datetime.now) modified_timestamp = db.Column( db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now) tags = db.relationship('Tag', secondary=entry_tags, backref=db.backref('entries', lazy='dynamic')) def __init__(self, *args, **kwargs): super(Entry, self).__init__(*args, **kwargs) self.generate_slug() def generate_slug(self): self.slug = '' if self.title: self.slug = slugify(self.title) def __repr__(self): return '<Entry %s>' % self.title class Tag(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) slug = db.Column(db.String(64), unique=True) def __init__(self, *args, **kwargs): super(Tag, self).__init__(*args, **kwargs) self.slug = slugify(self.name) def __repr__(self): return '<Tag %s>' % self.name
By creating the entry_tags
table, we have established a link between the Entry
and Tag
models. SQLAlchemy provides a high-level API for working with this relationship, the aptly-named db.relationship
function. This function creates a new property on the Entry
model that allows us to easily read and write the tags for a given blog entry. There is a lot going on in these two lines of code so let's take a closer look:
tags = db.relationship('Tag', secondary=entry_tags, backref=db.backref('entries', lazy='dynamic'))
We are setting the tags attribute of the Entry
class equal to the return value of the db.relationship
function. The first two arguments, 'Tag'
and secondary=entry_tags
, instruct SQLAlchemy that we are going to be querying the Tag
model via the entry_tags
table. The third argument creates a back-reference, allowing us to go from the Tag
model back to the associated list of blog entries. By specifying lazy='dynamic',
we instruct SQLAlchemy that, instead of it loading all the associated entries for us, we want a Query object instead.
Adding and removing tags from entries
Let's use the IPython shell to see how this works. Close your current shell and re-run the scripts/create_db.py
script. This step is necessary since we added two new tables. Now re-open IPython:
(blog) $ python scripts/create_db.py (blog) $ ipython In []: from models import * In []: Tag.query.all() Out[]: []
There are currently no tags in the database, so let's create a couple of them:
In []: python = Tag(name='python') In []: flask = Tag(name='flask') In []: db.session.add_all([python, flask]) In []: db.session.commit()
Now let's load up some example entries. In my database there are four:
In []: Entry.query.all() Out[]: [<Entry Py thon entry>, <Entry Flask entry>, <Entry More flask>, <Entry Django entry>] In []: python_entry, flask_entry, more_flask, django_entry = _
Note
In IPython, you can use an underscore (_
) to reference the return-value of the previous line.
To add tags to an entry, simply assign them to the entry's tags
attribute. It's that easy!
In []: python_entry.tags = [python] In []: flask_entry.tags = [python, flask] In []: db.session.commit()
We can work with an entry's list of tags just like a normal Python list, so the usual .append()
and .remove()
methods will also work:
In []: kittens = Tag(name='kittens') In []: python_entry.tags.append(kittens) In []: db.session.commit() In []: python_entry.tags Out[]: [<Tag python>, <Tag kittens>] In []: python_entry.tags.remove(kittens) In []: db.session.commit() In []: python_entry.tags Out[]: [<Tag python>]
Using backrefs
When we created the tags
attribute on the Entry
model, you will recall we passed in a backref
argument. Let's use IPython to see how the back-reference is used.
In []: python # The python variable is just a tag. Out[]: <Tag python> In []: python.entries Out[]: <sqlalchemy.orm.dynamic.AppenderBaseQuery at 0x332ff90> In []: python.entries.all() Out[]: [<Entry Flask entry>, <Entry Python entry>]
Unlike the Entry.tags
reference, the back-reference is specified as lazy='dynamic'
. This means that, unlike entry.tags,
which gives us a list of tags, we will not receive a list of entries every time we access tag.entries
. Why is this? Typically, when the result-set is larger than a few items, it is more useful to treat the backref
argument as a query, which can be filtered, ordered, and so on. For example, what if we wanted to show the latest entry tagged with python
?
In []: python.entries.order_by(Entry.created_timestamp.desc()).first() Out[]: <Entry Flask entry>
Note
The SQLAlchemy documentation contains an excellent overview of the various values that you can use for the lazy argument. You can find them online at http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#sqlalchemy.orm.relationship.params.lazy
- The Supervised Learning Workshop
- 大學計算機應用基礎實踐教程
- Python數據分析入門與實戰
- Python數據可視化:基于Bokeh的可視化繪圖
- AWS Serverless架構:使用AWS從傳統部署方式向Serverless架構遷移
- 算法訓練營:入門篇(全彩版)
- PLC編程及應用實戰
- H5頁面設計:Mugeda版(微課版)
- Visual Basic程序設計實驗指導(第二版)
- UVM實戰
- 單片機C語言程序設計實訓100例
- PLC應用技術(三菱FX2N系列)
- 新一代SDN:VMware NSX 網絡原理與實踐
- CoffeeScript Application Development Cookbook
- Java Web應用開發項目教程