class Tag(db.Model): __tablename__ = "tags" query_class = TagQuery id = db.Column(db.Integer, primary_key=True) slug = db.Column(db.Unicode(80), unique=True) posts = db.dynamic_loader(Post, secondary=post_tags, query_class=PostQuery) _name = db.Column("name", db.Unicode(80), unique=True) def __str__(self): return self.name def _get_name(self): return self._name def _set_name(self, name): self._name = name.lower().strip() self.slug = slugify(name) name = db.synonym("_name", descriptor=property(_get_name, _set_name)) @cached_property def url(self): return url_for("frontend.tag", slug=self.slug) num_posts = db.column_property( db.select([db.func.count(post_tags.c.post_id)]).where( db.and_(post_tags.c.tag_id == id, Post.id == post_tags.c.post_id, Post.access == Post.PUBLIC)).as_scalar())
class Post(db.Model): __tablename__ = "posts" PUBLIC = 100 FRIENDS = 200 PRIVATE = 300 PER_PAGE = 40 query_class = PostQuery id = db.Column(db.Integer, primary_key=True) author_id = db.Column(db.Integer, db.ForeignKey(User.id, ondelete='CASCADE'), nullable=False) title = db.Column(db.Unicode(200)) description = db.Column(db.UnicodeText) link = db.Column(db.String(250)) date_created = db.Column(db.DateTime, default=datetime.utcnow) score = db.Column(db.Integer, default=1) num_comments = db.Column(db.Integer, default=0) votes = db.Column(DenormalizedText) access = db.Column(db.Integer, default=PUBLIC) _tags = db.Column("tags", db.UnicodeText) author = db.relation(User, innerjoin=True, lazy="joined") __mapper_args__ = {'order_by': id.desc()} class Permissions(object): def __init__(self, obj): self.obj = obj @cached_property def default(self): return Permission(UserNeed(self.obj.author_id)) & moderator @cached_property def view(self): if self.obj.access == Post.PUBLIC: return Permission() if self.obj.access == Post.FRIENDS: needs = [ UserNeed(user_id) for user_id in self.obj.author.friends ] return self.default & Permission(*needs) return self.default @cached_property def edit(self): return self.default @cached_property def delete(self): return self.default @cached_property def vote(self): needs = [UserNeed(user_id) for user_id in self.obj.votes] needs.append(UserNeed(self.obj.author_id)) return auth & Denial(*needs) @cached_property def comment(self): return auth def __init__(self, *args, **kwargs): super(Post, self).__init__(*args, **kwargs) self.votes = self.votes or set() self.access = self.access or self.PUBLIC def __str__(self): return self.title def __repr__(self): return "<%s>" % self @cached_property def permissions(self): return self.Permissions(self) def vote(self, user): self.votes.add(user.id) def _get_tags(self): return self._tags def _set_tags(self, tags): self._tags = tags if self.id: # ensure existing tag references are removed d = db.delete(post_tags, post_tags.c.post_id == self.id) db.engine.execute(d) for tag in set(self.taglist): slug = slugify(tag) tag_obj = Tag.query.filter(Tag.slug == slug).first() if tag_obj is None: tag_obj = Tag(name=tag, slug=slug) db.session.add(tag_obj) if self not in tag_obj.posts: tag_obj.posts.append(self) tags = db.synonym("_tags", descriptor=property(_get_tags, _set_tags)) @property def taglist(self): if self.tags is None: return [] tags = [t.strip() for t in self.tags.split(",")] return [t for t in tags if t] @cached_property def linked_taglist(self): """ Returns the tags in the original order and format, with link to tag page """ return [(tag, url_for('frontend.tag', slug=slugify(tag))) for tag in self.taglist] @cached_property def domain(self): if not self.link: return '' return domain(self.link) @cached_property def json(self): """ Returns dict of safe attributes for passing into a JSON request. """ return dict(post_id=self.id, score=self.score, title=self.title, link=self.link, description=self.description, num_comments=self.num_comments, author=self.author.username) @cached_property def access_name(self): return { Post.PUBLIC: "public", Post.FRIENDS: "friends", Post.PRIVATE: "private" }.get(self.access, "public") def can_access(self, user=None): if self.access == self.PUBLIC: return True if user is None: return False if user.is_moderator or user.id == self.author_id: return True return self.access == self.FRIENDS and self.author_id in user.friends @cached_property def comments(self): """ Returns comments in tree. Each parent comment has a "comments" attribute appended and a "depth" attribute. """ from newsmeme.models.comments import Comment comments = Comment.query.filter(Comment.post_id == self.id).all() def _get_comments(parent, depth): parent.comments = [] parent.depth = depth for comment in comments: if comment.parent_id == parent.id: parent.comments.append(comment) _get_comments(comment, depth + 1) parents = [c for c in comments if c.parent_id is None] for parent in parents: _get_comments(parent, 0) return parents def _url(self, _external=False): return url_for('post.view', post_id=self.id, slug=self.slug, _external=_external) @cached_property def url(self): return self._url() @cached_property def permalink(self): return self._url(True) @cached_property def markdown(self): return Markup(markdown(self.description or '')) @cached_property def slug(self): return slugify(self.title or '')[:80]
class User(db.Model): __tablename__ = "users" query_class = UserQuery # user roles MEMBER = 100 MODERATOR = 200 ADMIN = 300 id = db.Column(db.Integer, primary_key=True) username = db.Column(db.Unicode(60), unique=True, nullable=False) email = db.Column(db.String(150), unique=True, nullable=False) karma = db.Column(db.Integer, default=0) date_joined = db.Column(db.DateTime, default=datetime.utcnow) activation_key = db.Column(db.String(80), unique=True) role = db.Column(db.Integer, default=MEMBER) receive_email = db.Column(db.Boolean, default=False) email_alerts = db.Column(db.Boolean, default=False) followers = db.Column(DenormalizedText) following = db.Column(DenormalizedText) _password = db.Column("password", db.String(80)) _openid = db.Column("openid", db.String(80), unique=True) class Permissions(Permissions): @cached_property def send_message(self): if not self.receive_email: return null needs = [UserNeed(user_id) for user_id in self.friends] if not needs: return null return Permission(*needs) def __init__(self, *args, **kwargs): super(User, self).__init__(*args, **kwargs) self.followers = self.followers or set() self.following = self.following or set() def __str__(self): return self.username def __repr__(self): return "<%s>" % self @cached_property def permissions(self): return self.Permissions(self) def _get_password(self): return self._password def _set_password(self, password): self._password = generate_password_hash(password) password = db.synonym("_password", descriptor=property(_get_password, _set_password)) def check_password(self, password): if self.password is None: return False return check_password_hash(self.password, password) def _get_openid(self): return self._openid def _set_openid(self, openid): self._openid = generate_password_hash(openid) openid = db.synonym("_openid", descriptor=property(_get_openid, _set_openid)) def check_openid(self, openid): if self.openid is None: return False return check_password_hash(self.openid, openid) @cached_property def provides(self): needs = [RoleNeed('authenticated'), UserNeed(self.id)] if self.is_moderator: needs.append(RoleNeed('moderator')) if self.is_admin: needs.append(RoleNeed('admin')) return needs @cached_property def num_followers(self): if self.followers: return len(self.followers) return 0 @cached_property def num_following(self): return len(self.following) def is_following(self, user): return user.id in self.following @property def friends(self): return self.following.intersection(self.followers) def is_friend(self, user): return user.id in self.friends def get_friends(self): return User.query.filter(User.id.in_(self.friends)) def follow(self, user): user.followers.add(self.id) self.following.add(user.id) def unfollow(self, user): if self.id in user.followers: user.followers.remove(self.id) if user.id in self.following: self.following.remove(user.id) def get_following(self): """ Return following users as query """ return User.query.filter(User.id.in_(self.following or set())) def get_followers(self): """ Return followers as query """ return User.query.filter(User.id.in_(self.followers or set())) @property def is_moderator(self): return self.role >= self.MODERATOR @property def is_admin(self): return self.role >= self.ADMIN @cached_property def gravatar(self): if not self.email: return '' md5 = hashlib.md5() md5.update(self.email.strip().lower()) return md5.hexdigest() def gravatar_url(self, size=80): if not self.gravatar: return '' return "http://www.gravatar.com/avatar/%s.jpg?s=%d" % (self.gravatar, size)