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

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

主站蜘蛛池模板: 濮阳市| 石泉县| 利辛县| 曲周县| 武汉市| 西安市| 略阳县| 施甸县| 七台河市| 亚东县| 玛沁县| 墨江| 大连市| 讷河市| 大连市| 安西县| 渝中区| 若羌县| 朔州市| 南开区| 昂仁县| 兰西县| 策勒县| 延长县| 随州市| 扶余县| 筠连县| 台安县| 黑水县| 滕州市| 灵丘县| 萨迦县| 大同市| 福建省| 东丽区| 泉州市| 东城区| 榆中县| 枣强县| 邵阳市| 塔城市|