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

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

主站蜘蛛池模板: 翼城县| 乌恰县| 礼泉县| 江西省| 台北市| 怀集县| 泰顺县| 澄城县| 枣强县| 绥芬河市| 济南市| 曲阳县| 葵青区| 分宜县| 互助| 铜山县| 泰州市| 临高县| 四平市| 醴陵市| 伊金霍洛旗| 淮南市| 安龙县| 红原县| 东光县| 云梦县| 永川市| 沙坪坝区| 皋兰县| 大厂| 攀枝花市| 松桃| 墨竹工卡县| 股票| 富民县| 福建省| 毕节市| 昌吉市| 绥棱县| 马龙县| 同仁县|