按SQLAlchemy中的类包含的方法排序

时间:2014-08-05 15:21:41

标签: python python-3.x flask sqlalchemy flask-sqlalchemy

我目前正在开发一个模型,我将判断文章的相关性。这遵循Hacker News的算法。这是我在app/articles/models.py

中的文章模型
from app.extensions import db

class Article(db.Model):
    """ database representation of an article """
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))
    subtitle = db.Column(db.String(512))
    body = db.Column(db.Text())
    votes = db.Column(db.Integer, default=1)
    views = db.Column(db.Integer, default=1)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)

    def popularity(self, gravity=1.8):
        """ uses hacker news popularity rating """
        submit_delta = (self.timestamp - datetime.utcnow()).total_seconds()
        time_decay = submit_delta / 60 / 60
        popularity = (self.views - 1) / (time_decay + 2) ** gravity
        return popularity

目前,我正在尝试按popularity的结果排序。

>>> from app.articles.models import Article
>>> Article.query.order_by(Article.popularity()).all()

这不起作用。我如何按他们的受欢迎程度对文章进行排序?

2 个答案:

答案 0 :(得分:3)

您可以使用hybrid methods创建一个方法,在类调用时生成SQL表达式(用于查询),但在实例调用时表现得像常规方法。

这是一个有效的例子。它打印由python和数据库计算的流行度。由于时间和四舍五入,这些会略有不同。

from datetime import datetime
from sqlalchemy import create_engine, Integer, Column, DateTime, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.orm import Session

engine = create_engine('postgresql:///example', echo=True)
Base = declarative_base(bind=engine)
session = Session(bind=engine)


class Article(Base):
    __tablename__ = 'article'

    id = Column(Integer, primary_key=True)
    views = Column(Integer, nullable=False, default=1)
    ts = Column(DateTime, nullable=False, default=datetime.utcnow)

    @hybrid_method
    def popularity(self, gravity=1.8):
        seconds = (self.ts - datetime.utcnow()).total_seconds()
        hours = seconds / 3600

        return (self.views - 1) / (hours + 2) ** gravity

    @popularity.expression
    def popularity(self, gravity=1.8):
        seconds = func.extract('epoch', self.ts - func.now())
        hours = seconds / 3600

        return (self.views - 1) / func.power((hours + 2), gravity)


Base.metadata.create_all()

a1 = Article(views=100)
a2 = Article(views=200)

session.add_all((a1, a2))
session.commit()

comparison = session.query(Article, Article.popularity()).all()

for a, pop in comparison:
    print 'py: {} db: {}'.format(a.popularity(), pop)

这适用于PostgreSQL,但func.powerfunc.extract在其他数据库中的工作方式可能不同。 SQLite特别没有powerextract的实现方式不同。

答案 1 :(得分:1)

如果您希望将其用作"命令,那么您需要将流行度计算重写为sql表达式。在数据库中。您的其他选择是获取所有文章并在python中排序(不适用于较大的数据集)或预先计算所有流行度值并将其缓存在数据库的数字字段中,并对其进行排序。

例如(这是Postgres特有的,我没有使用Flask成语,但你应该明白这一点):

order_exp = "(article.views - 1) / power(2 + extract(epoch from (now() at time zone 'UTC' - timestamp))/3600, :gravity)"
order = sqlalchemy.text(order_exp).bindparams(gravity=1.8)
print(session.query(Article).order_by(order).all())