官术网_书友最值得收藏!

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

主站蜘蛛池模板: 特克斯县| 高州市| 宜阳县| 城固县| 赞皇县| 化隆| 浮梁县| 浏阳市| 莱阳市| 辉南县| 延边| 宜兰市| 吉木萨尔县| 恭城| 察哈| 桃源县| 老河口市| 丰镇市| 刚察县| 通山县| 山西省| 海丰县| 绥滨县| 青神县| 额尔古纳市| 昂仁县| 南阳市| 桦甸市| 邓州市| 五河县| 香港 | 绿春县| 比如县| 桐城市| 米易县| 方城县| 高陵县| 蛟河市| 确山县| 湟中县| 阿瓦提县|