class User(Model, UserMixin): username = Column(String(50), unique=True, index=True) email = Column(String(50), unique=True, index=True) first_name = Column(String(32)) last_name = Column(String(64)) password = Column(String, nullable=True) active = Column(Boolean(name='active'), default=False) confirmed_at = Column(DateTime(), nullable=True) # Only used when SECURITY_TRACKABLE is set to True in config.py # last_login_at = Column(DateTime(), nullable=True) # current_login_at = Column(DateTime(), nullable=True) # last_login_ip = Column(String(100)) # current_login_ip = Column(String(100)) # login_count = Column(Integer) user_roles = relationship('UserRole', back_populates='user', cascade='all, delete-orphan') roles = association_proxy('user_roles', 'role', creator=lambda role: UserRole(role=role)) __repr_props__ = ('id', 'username', 'email') def __init__(self, hash_password=True, **kwargs): super().__init__(**kwargs) if 'password' in kwargs and hash_password: self.password = security_hash_password(kwargs['password'])
class Series(Model): title = Column(String(100)) slug = Column(String(100)) file_path = Column(String(255), nullable=True) header_image = Column(String(255), nullable=True) summary = Column(Text) series_articles = relationship('SeriesArticle', back_populates='series', lazy='joined', innerjoin=True, order_by='SeriesArticle.part', cascade='all, delete-orphan') articles = association_proxy( 'series_articles', 'article', creator=lambda article: SeriesArticle(article=article)) category_id = foreign_key('Category', nullable=True) category = relationship('Category', back_populates='series') series_tags = relationship('SeriesTag', back_populates='series', cascade='all, delete-orphan') tags = association_proxy('series_tags', 'tag', creator=lambda tag: SeriesTag(tag=tag)) __repr_props__ = ('id', 'title', 'articles') @on('series_articles', 'append') def on_append_series_article(self, series_article, *_): # auto increment series article part number if necessary if series_article.part is None: series_article.part = len(self.series_articles) + 1 # set the article's category to be the same as the series' category article = series_article.article article.category = self.category # set the article's tags to include the series' tags for tag in self.tags: if tag not in article.tags: article.tags.append(tag)
class Tag(Model): name = Column(String(32)) slug = Column(String(32)) tag_articles = relationship('ArticleTag', back_populates='tag') articles = association_proxy( 'tag_articles', 'article', creator=lambda article: ArticleTag(article=article)) tag_series = relationship('SeriesTag', back_populates='tag') series = association_proxy('tag_series', 'series', creator=lambda series: SeriesTag(series=series)) __repr_props__ = ('id', 'name') def __init__(self, name, **kwargs): super().__init__(**kwargs) self.name = name
class Role(Model, RoleMixin): name = Column(String(50), unique=True, index=True) description = Column(String(255), nullable=True) role_users = relationship('UserRole', back_populates='role', cascade='all, delete-orphan') users = association_proxy('role_users', 'user', creator=lambda user: UserRole(user=user)) __repr_props__ = ('id', 'name')
class User(Model, UserMixin): username = Column(String(50), unique=True, index=True) email = Column(String(50), unique=True, index=True) first_name = Column(String(32)) last_name = Column(String(64)) password = Column(String, nullable=True) active = Column(Boolean(name='active'), default=False) confirmed_at = Column(DateTime(), nullable=True) user_roles = relationship('UserRole', back_populates='user', cascade='all, delete-orphan') roles = association_proxy('user_roles', 'role', creator=lambda role: UserRole(role=role)) articles = relationship('Article', back_populates='author') __repr_props__ = ('id', 'username', 'email') def __init__(self, hash_password=True, **kwargs): super().__init__(**kwargs) if 'password' in kwargs and hash_password: self.password = security_hash_password(kwargs['password'])
class Article(Model): title = Column(String(100)) slug = Column(String(100)) publish_date = Column(DateTime) last_updated = Column(DateTime, nullable=True) file_path = Column(String(255), nullable=True) header_image = Column(String(255), nullable=True) preview = Column(Text) html = Column(Text) author_id = foreign_key('User') author = relationship('User', back_populates='articles') article_series_id = foreign_key('SeriesArticle', nullable=True) article_series = relationship('SeriesArticle', back_populates='article', cascade='all, delete-orphan', single_parent=True) series = association_proxy( 'article_series', 'series', creator=lambda series: SeriesArticle(series=series)) part = association_proxy('article_series', 'part') category_id = foreign_key('Category', nullable=True) category = relationship('Category', back_populates='articles') article_tags = relationship('ArticleTag', back_populates='article', cascade='all, delete-orphan') tags = association_proxy('article_tags', 'tag', creator=lambda tag: ArticleTag(tag=tag)) __repr_props__ = ('id', 'title') @classmethod def get_published(cls): return cls.query\ .filter(cls.publish_date <= utcnow())\ .order_by(cls.publish_date.desc(), cls.last_updated.desc())\ .all() def get_series_prev_next(self): if self.series: series_articles = self.series.series_articles prev = None prev_i = self.part - 2 if prev_i >= 0: prev = series_articles[prev_i] prev = {'slug': prev.article.slug, 'title': prev.article.title} next = None next_i = self.part if next_i < len(series_articles): next = series_articles[next_i] next = {'slug': next.article.slug, 'title': next.article.title} if prev and next: return prev, next elif prev: return prev, None elif next: return None, next return None, None def get_prev_next(self): if self.series: return self.get_series_prev_next() result = db.session.execute( f''' WITH articles AS ( SELECT slug, title, ROW_NUMBER() OVER (ORDER BY publish_date ASC, last_updated ASC) AS row_number FROM {Article.__tablename__} WHERE publish_date <= :now AND article_series_id IS NULL ) SELECT slug, title FROM articles WHERE row_number IN ( SELECT row_number + i FROM articles CROSS JOIN (SELECT -1 AS i UNION ALL SELECT 0 UNION ALL SELECT 1) n WHERE slug = :slug ) ''', { 'now': utcnow(), 'slug': self.slug }) rows = [{'slug': row[0], 'title': row[1]} for row in result.fetchall()] if len(rows) == 1: return None, None elif len(rows) == 3: return rows[0], rows[2] elif rows[0]['slug'] == self.slug: return None, rows[1] elif rows[1]['slug'] == self.slug: return rows[0], None