├── FlaskZhihu ├── __init__.py ├── decorators.py ├── forms │ ├── __init__.py │ ├── collection.py │ ├── answer.py │ ├── comment.py │ ├── question.py │ └── user.py ├── views │ ├── __init__.py │ ├── index.py │ ├── comment.py │ ├── answer.py │ ├── question.py │ ├── collection.py │ └── user.py ├── constants.py ├── models │ ├── __init__.py │ ├── user │ │ ├── operation │ │ │ ├── __init__.py │ │ │ ├── comment.py │ │ │ ├── collection.py │ │ │ ├── question.py │ │ │ ├── user.py │ │ │ └── answer.py │ │ └── __init__.py │ ├── topic.py │ ├── collection.py │ ├── base.py │ ├── comment.py │ ├── question.py │ ├── answer.py │ └── signals.py ├── extensions.py ├── templates │ ├── question │ │ ├── index.html │ │ ├── edit.html │ │ ├── add.html │ │ ├── my.html │ │ └── show.html │ ├── answer │ │ ├── edit.html │ │ ├── my.html │ │ └── show.html │ ├── collection │ │ ├── edit.html │ │ ├── add.html │ │ ├── my.html │ │ ├── select_by_answer.html │ │ └── show.html │ ├── user │ │ ├── edit.html │ │ ├── login.html │ │ ├── add.html │ │ └── _user_banner.html │ └── base.html ├── permissions.py ├── helpers.py ├── application.py ├── signals.py ├── api │ └── __init__.py └── settings.py ├── docs └── images │ └── flaskzhihu.png ├── TODO ├── app.py ├── README.md ├── .gitignore ├── requirements.txt ├── scripts ├── import_finish.py ├── import_question.py ├── import_people.py └── import_answer.py └── tests ├── test_orm.py └── orm.py /FlaskZhihu/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /FlaskZhihu/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | -------------------------------------------------------------------------------- /docs/images/flaskzhihu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shn7798/FlaskZhihu/HEAD/docs/images/flaskzhihu.png -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | //Done 优化user_on_x 多对多表的速度 2 | 3 | 增加测试用例 4 | 增加后端api接口 5 | 美化前端 6 | 前端动态内容加载, 数据来自后端api 7 | 增加cache优化 8 | 9 | 实现收藏夹 10 | 实现timeline 11 | 实现评论的"查看对话" 12 | 实现通知 13 | 实现搜索 14 | 实现专栏 15 | 实现问题编辑日志 -------------------------------------------------------------------------------- /FlaskZhihu/forms/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from .answer import * 5 | from .comment import * 6 | from .user import * 7 | from .question import * 8 | from .collection import * -------------------------------------------------------------------------------- /FlaskZhihu/views/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from .answer import * 5 | from .user import * 6 | from .comment import * 7 | from .question import * 8 | from .collection import * -------------------------------------------------------------------------------- /FlaskZhihu/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | VOTE_UP = 1 5 | VOTE_DOWN = -1 6 | VOTE_NONE = 0 7 | 8 | THANK_ON = 1 9 | THANK_OFF = 0 10 | 11 | FOLLOW_ON = 1 12 | FOLLOW_OFF = 0 -------------------------------------------------------------------------------- /FlaskZhihu/views/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import redirect 5 | from flask.blueprints import Blueprint 6 | 7 | index = Blueprint('index', __name__) 8 | 9 | @index.route('/') 10 | def index_page(): 11 | return redirect('/question/') -------------------------------------------------------------------------------- /FlaskZhihu/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from .answer import * 5 | from .collection import * 6 | from .comment import * 7 | from .question import * 8 | from .topic import * 9 | from .user import * 10 | 11 | # 必须引用, 以使signal修饰器生效 12 | from . import signals -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from .answer import AnswerOperationMixin 5 | from .collection import CollectionOperationMixin 6 | from .question import QuestionOperationMixin 7 | from .user import UserOperationMixin 8 | from .comment import CommentOperationMixin -------------------------------------------------------------------------------- /FlaskZhihu/extensions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.sqlalchemy import SQLAlchemy 5 | from flask.ext.cache import Cache 6 | from flask.ext.session import Session 7 | 8 | __all__ = ['db', 'cache', 'session'] 9 | 10 | db = SQLAlchemy() 11 | cache = Cache() 12 | session = Session() 13 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from FlaskZhihu.application import create_app 3 | from FlaskZhihu.settings import TestSettings, IPythonSettings 4 | if __name__ == '__main__': 5 | settings = IPythonSettings() 6 | app = create_app(settings=settings) 7 | #app.run(host='0.0.0.0', processes=10) 8 | app.run(host='0.0.0.0') 9 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/question/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 随机问题浏览 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | random_questions:
6 | {% for q in random_questions %} 7 | {{ q.title }} ({{ q.answers_count }})
8 | {% endfor %} 9 | 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlaskZhihu 2 | 3 | - 这是一个**很水**的web项目 4 | 5 | - 功能完全山寨自知乎 6 | 7 | - 项目结构参考自newsmeme 8 | 9 | - 欢迎PR 10 | 11 | # Quick start 12 | ## 搭建环境 13 | - 安装mysql和redis服务 14 | - 配置virtualenv环境 15 | - 安装依赖包 pip install -r requirements.txt 16 | - 配置settings.py中的IPythonSettings中的参数 17 | - 运行python app.py 18 | - 浏览器打开http://127.0.0.1:5000/ 19 | 20 | ## 首页效果图 21 | ![index_page](https://raw.githubusercontent.com/shn7798/FlaskZhihu/master/docs/images/flaskzhihu.png) -------------------------------------------------------------------------------- /FlaskZhihu/templates/answer/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 修改回答 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 |
6 | 15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /FlaskZhihu/forms/collection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms import TextAreaField, SubmitField, HiddenField, StringField 6 | from wtforms.validators import required, Regexp 7 | 8 | class CollectionAddForm(Form): 9 | title = StringField(validators=[required()]) 10 | description = StringField(validators=[required()]) 11 | 12 | 13 | class CollectionEditForm(Form): 14 | title = StringField() 15 | description = StringField() -------------------------------------------------------------------------------- /FlaskZhihu/templates/question/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 提问 {% endblock %} 3 | {% block body %} 4 |
5 | 18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/collection/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 修改收藏夹 {% endblock %} 3 | {% block body %} 4 |
5 | 18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/question/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 提问 {% endblock %} 3 | {% block body %} 4 |
5 | 18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/user/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 修改 {% endblock %} 3 | {% block body %} 4 |
5 | 18 |
19 | 20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.constants import VOTE_UP, VOTE_DOWN, VOTE_NONE 5 | class CommentOperationMixin(object): 6 | def voteup_comment(self, comment, undo=False): 7 | op = self.op_on_comment(comment, edit=True) 8 | if undo: 9 | if op.vote == VOTE_UP: 10 | op.vote = VOTE_NONE 11 | else: 12 | op.vote = VOTE_UP 13 | 14 | def add_comment_reply(self, comment, reply): 15 | reply.quote_comment = comment 16 | 17 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/collection/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 创建收藏夹 {% endblock %} 3 | {% block body %} 4 |
5 | 18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}FlaskZhihu{% endblock %} 6 | {% block stylesheets %} 7 | {% endblock %} 8 | {% block extra_stylesheets %}{% endblock %} 9 | {% block favicon %}{% endblock %} 10 | {% block javascripts %}{% endblock %} 11 | {% block extra_javascripts %}{% endblock %} 12 | {% block extrahead %}{% endblock %} 13 | 14 | 15 | {% block body %} 16 | {% endblock %} 17 | 18 | 19 | -------------------------------------------------------------------------------- /FlaskZhihu/forms/answer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms import TextAreaField, SubmitField, HiddenField 6 | from wtforms.validators import required, Regexp 7 | 8 | __all__ = ['AnswerAddForm', 'AnswerEditForm'] 9 | 10 | 11 | class AnswerAddForm(Form): 12 | question_id = HiddenField(validators=[required(), 13 | Regexp(r'^\d+$')]) 14 | content = TextAreaField(validators=[required(message=u'请输入内容')]) 15 | 16 | 17 | class AnswerEditForm(Form): 18 | content = TextAreaField(validators=[required(message=u'请输入内容')]) -------------------------------------------------------------------------------- /FlaskZhihu/forms/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms import TextAreaField, SubmitField, HiddenField 6 | from wtforms.validators import required, Regexp 7 | 8 | __all__ = ['CommentAddForm'] 9 | 10 | 11 | class CommentAddForm(Form): 12 | target_type = HiddenField(validators=[required(message=u'请输入评论类型'), 13 | Regexp(r'^[a-z]+$')]) 14 | target_id = HiddenField(validators=[required(message=u'请输入评论所属ID'), 15 | Regexp(r'^\d+$')]) 16 | content = TextAreaField(validators=[required(message=u'请输入内容')]) -------------------------------------------------------------------------------- /FlaskZhihu/permissions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.principal import Permission, RoleNeed 5 | from flask.ext.login import LoginManager 6 | from FlaskZhihu.models import User 7 | 8 | admin = Permission(RoleNeed('admin')) 9 | auth = Permission(RoleNeed('authenticated')) 10 | 11 | login_manager = LoginManager() 12 | login_manager.login_view = 'UserView:login' 13 | 14 | @login_manager.user_loader 15 | def user_loader(user_id): 16 | if not user_id: 17 | return None 18 | try: 19 | return User.find_by_id(user_id, abort404=True) 20 | except Exception as e: 21 | print e 22 | return None -------------------------------------------------------------------------------- /FlaskZhihu/forms/question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms import TextAreaField, SubmitField, HiddenField, StringField 6 | from wtforms.validators import required, Regexp 7 | 8 | __all__ = ['QuestionAddForm', 'QuestionEditForm'] 9 | 10 | 11 | class QuestionAddForm(Form): 12 | title = StringField(validators=[required(message=u'请输入标题')]) 13 | content = TextAreaField(validators=[required(message=u'请输入内容')]) 14 | 15 | class QuestionEditForm(Form): 16 | title = StringField(validators=[required(message=u'请输入标题')]) 17 | content = TextAreaField(validators=[required(message=u'请输入内容')]) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | settings.py 3 | scripts/import* 4 | # form https://github.com/notedit/flask-base 5 | 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Packages 12 | *.egg 13 | *.egg-info 14 | dist 15 | build 16 | eggs 17 | parts 18 | bin 19 | var 20 | sdist 21 | develop-eggs 22 | .installed.cfg 23 | lib 24 | lib64 25 | 26 | # Installer logs 27 | pip-log.txt 28 | 29 | # Unit test / coverage reports 30 | .coverage 31 | .tox 32 | nosetests.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # virtualenv 43 | env/ 44 | 45 | # tmp files 46 | .DS_Store 47 | *.swp 48 | *.sccsc 49 | *~ 50 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/user/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 登录 {% endblock %} 3 | {% block body %} 4 |
5 | {{ form.next_url }} 6 | 20 |
21 | 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/user/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 注册 {% endblock %} 3 | {% block body %} 4 |
5 | 22 |
23 | 24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/user/_user_banner.html: -------------------------------------------------------------------------------- 1 | {% block user_banner %} 2 | 首页 3 | {% if current_user.is_anonymous %} 4 | |登录 5 | |提问 6 | {% else %} 7 | |欢迎你: {{ current_user.name }}({{ current_user.username }}) 8 | |提问 9 | |我的提问 10 | |我的回答 11 | |我的收藏 12 | |修改资料 13 | |退出 14 | {% endif %} 15 | 16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/collection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.constants import * 5 | 6 | 7 | class CollectionOperationMixin(object): 8 | def add_collection_comment(self, collection, comment): 9 | comment.user = self 10 | collection.comments.appen(comment) 11 | 12 | def add_collection_answer(self, collection, answer): 13 | collection.answers.append(answer) 14 | 15 | def follow_collection(self, collection): 16 | op = self.op_on_collection(collection, edit=True) 17 | op.follow = FOLLOW_ON 18 | 19 | def unfollow_collection(self, collection): 20 | op = self.op_on_collection(collection, edit=True) 21 | op.follow = FOLLOW_OFF -------------------------------------------------------------------------------- /FlaskZhihu/forms/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask.ext.wtf import Form 5 | from wtforms import TextAreaField, SubmitField, HiddenField, StringField 6 | from wtforms.validators import required 7 | 8 | __all__ = ['UserLoginForm', 'UserAddForm', 'UserEditForm'] 9 | 10 | class UserLoginForm(Form): 11 | username = StringField(validators=[required()]) 12 | password = StringField(validators=[required()]) 13 | next_url = HiddenField() 14 | 15 | 16 | class UserAddForm(Form): 17 | name = StringField(validators=[required()]) 18 | username = StringField(validators=[required()]) 19 | password = StringField(validators=[required()]) 20 | 21 | 22 | class UserEditForm(Form): 23 | name = StringField(validators=[]) 24 | password = StringField(validators=[]) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | backports.shutil-get-terminal-size==1.0.0 3 | blinker==1.4 4 | decorator==4.0.9 5 | Flask==0.10.1 6 | Flask-Cache==0.13.1 7 | Flask-Classy==0.6.10 8 | Flask-Login==0.3.2 9 | Flask-Principal==0.4.0 10 | Flask-Session==0.2.3 11 | Flask-SQLAlchemy==2.1 12 | Flask-SQLAlchemy-Cache==0.1.5 13 | Flask-Testing==0.4.2 14 | Flask-WTF==0.12 15 | gnureadline==6.3.3 16 | inflect==0.2.5 17 | ipython==4.2.0 18 | ipython-genutils==0.1.0 19 | itsdangerous==0.24 20 | Jinja2==2.8 21 | MarkupSafe==0.23 22 | MySQL-python==1.2.5 23 | pathlib2==2.1.0 24 | pexpect==4.0.1 25 | pickleshare==0.7.2 26 | ptyprocess==0.5.1 27 | pymongo==3.2.2 28 | redis==2.10.5 29 | simplegeneric==0.8.1 30 | six==1.10.0 31 | sqlacodegen==1.1.6 32 | SQLAlchemy==1.0.12 33 | traitlets==4.2.1 34 | Werkzeug==0.11.9 35 | WTForms==2.1 36 | -------------------------------------------------------------------------------- /scripts/import_finish.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from pymongo import MongoClient 5 | 6 | from flask import Flask 7 | 8 | from FlaskZhihu.settings import IPythonSettings 9 | from FlaskZhihu.models import * 10 | from FlaskZhihu.extensions import db 11 | 12 | def enc(s): 13 | if isinstance(s, unicode): 14 | return s.encode('UTF-8') 15 | else: 16 | return s 17 | 18 | app = Flask(__name__) 19 | app.config.from_object(IPythonSettings()) 20 | db.init_app(app) 21 | 22 | ctx = app.app_context() 23 | 24 | ctx.push() 25 | mysql = db.session() 26 | 27 | update_answers_count_sql = """UPDATE question q SET answers_count = (SELECT COUNT(1) FROM answer a WHERE a.question_id = q.id) WHERE answers_count <= 0""" 28 | 29 | 30 | mysql.execute(update_answers_count_sql) 31 | mysql.commit() -------------------------------------------------------------------------------- /FlaskZhihu/templates/question/my.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 我的提问 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for question in questions %} 15 | 16 | 17 | 18 | 23 | 24 | {% endfor %} 25 | 26 |
编辑创建时间标题
编辑{{ question.create_time }} 19 | 20 | {{ question.title }} ({{ question.answers_count }}) 21 | 22 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.constants import * 5 | 6 | 7 | class QuestionOperationMixin(object): 8 | def add_question(self, question): 9 | """ 创建问题 """ 10 | question.user = self 11 | 12 | def add_question_answer(self, question, answer): 13 | """ 回答问题 """ 14 | answer.user = self 15 | question.answers.append(answer) 16 | 17 | def add_question_comment(self, question, comment): 18 | """ 评论问题 """ 19 | comment.user = self 20 | question.comments.append(comment) 21 | 22 | def follow_question(self, question): 23 | """ 关注问题 """ 24 | op = self.op_on_question(question, edit=True) 25 | op.follow = FOLLOW_ON 26 | 27 | def unfollow_question(self, question): 28 | """ 取消关注问题 """ 29 | op = self.op_on_question(question, edit=True) 30 | op.follow = FOLLOW_OFF -------------------------------------------------------------------------------- /FlaskZhihu/templates/answer/my.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 我的回答 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for answer in answers %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 | 28 |
创建时间编辑问题赞成反对答案
{{ answer.create_time }}编辑{{ answer.question.title }}({{ answer.question.answers_count }}){{ answer.voteup_count }}{{ answer.votedown_count }}{{ answer.content[0:50] }}
29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /FlaskZhihu/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import session, request 5 | import blinker 6 | from FlaskZhihu.extensions import cache 7 | import functools 8 | 9 | def cached(*cargs, **ckwargs): 10 | def decorator(func): 11 | @functools.wraps(func) 12 | def wrapper(*args, **kwargs): 13 | return cache.cached(*cargs, unless=lambda: session.get('user_id', None) is not None, **ckwargs)(func)(*args, **kwargs) 14 | return wrapper 15 | return decorator 16 | 17 | 18 | def keep_next_url(func): 19 | @functools.wraps(func) 20 | def wrapper(*args, **kwargs): 21 | next_url = request.args.get('next') 22 | if not next_url: 23 | if request and request.method in ("PUT", "POST"): 24 | next_url = request.form.get('next') 25 | return func(*args, next_url=next_url, **kwargs) 26 | return wrapper 27 | 28 | 29 | def use_signal(signal): 30 | assert isinstance(signal, blinker.NamedSignal) 31 | 32 | def decorator(func): 33 | signal.connect(func) 34 | return func 35 | return decorator 36 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/collection/my.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 我的收藏夹 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | 新建 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for collection in collections %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
创建时间收藏夹名称收藏的答案关注人数编辑删除
{{ collection.create_time }}{{ collection.title }}收藏的答案({{ collection.answers_count }}){{ collection.following_count }}编辑删除
31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | 5 | class UserOperationMixin(object): 6 | def follow_user(self, user): 7 | op = self.op_on_user(user, edit=True) 8 | op.follow = True 9 | 10 | def unfollow_user(self, user): 11 | op = self.op_on_user(user, edit=True) 12 | op.follow = False 13 | 14 | def block_user(self, user): 15 | op = self.op_on_user(user, edit=True) 16 | op.follow = False 17 | op.block = True 18 | 19 | def unblock_user(self, user): 20 | op = self.op_on_user(user, edit=True) 21 | op.block = False 22 | 23 | @property 24 | def followed_users(self): 25 | return [op.dest_user for op in self.user_on_dest_user if op.follow] 26 | 27 | @property 28 | def who_followed_me(self): 29 | return [op.user for op in self.dest_user_on_user if op.follow] 30 | 31 | @property 32 | def blocked_users(self): 33 | return [op.dest_user for op in self.user_on_dest_user if op.block] 34 | 35 | @property 36 | def who_blocked_me(self): 37 | return [op.user for op in self.dest_user_on_user if op.block] 38 | 39 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/collection/select_by_answer.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 选择收藏夹 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | 新建 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for collection in collections %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 | 30 |
创建时间收藏夹名称编辑收藏的答案关注人数操作
{{ collection.create_time }}{{ collection.title }}编辑收藏的答案({{ collection.answers_count }}){{ collection.following_count }}选择
31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /FlaskZhihu/models/user/operation/answer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.constants import VOTE_NONE, VOTE_UP, VOTE_DOWN, THANK_OFF, THANK_ON 5 | 6 | class AnswerOperationMixin(object): 7 | def add_answer(self, answer): 8 | """ 创建回答 """ 9 | answer.user = self 10 | 11 | def add_answer_comment(self, answer, comment): 12 | """ 评论回答 """ 13 | comment.user = self 14 | answer.comments.append(comment) 15 | 16 | def voteup_answer(self, answer, undo=False): 17 | op = self.op_on_answer(answer, edit=True) 18 | if undo: 19 | if op.vote == VOTE_UP: 20 | op.vote = VOTE_NONE 21 | else: 22 | op.vote = VOTE_UP 23 | 24 | 25 | def votedown_answer(self, answer, undo=False): 26 | op = self.op_on_answer(answer, edit=True) 27 | if undo: 28 | if op.vote == VOTE_DOWN: 29 | op.vote = VOTE_NONE 30 | else: 31 | op.vote = VOTE_DOWN 32 | 33 | def thank_answer(self, answer): 34 | op = self.op_on_answer(answer, edit=True) 35 | op.thank = THANK_ON 36 | 37 | def unthank_answer(self, answer): 38 | op = self.op_on_answer(answer, edit=True) 39 | op.thank = THANK_OFF -------------------------------------------------------------------------------- /FlaskZhihu/application.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Flask 3 | 4 | from FlaskZhihu.settings import DefaultSettings 5 | from FlaskZhihu.extensions import db, cache, session 6 | from FlaskZhihu.views.index import index 7 | from FlaskZhihu.views import QuestionView, AnswerView, CommentView, UserView, CollectionView 8 | from FlaskZhihu.permissions import login_manager 9 | from FlaskZhihu.api import * 10 | 11 | 12 | def create_app(settings=None): 13 | if not settings: 14 | settings = DefaultSettings() 15 | app = Flask(__name__) 16 | app.config.from_object(settings) 17 | 18 | # flask login 19 | login_manager.init_app(app) 20 | session.init_app(app) 21 | 22 | init_extensions(app) 23 | init_views(app) 24 | return app 25 | 26 | 27 | def init_extensions(app): 28 | db.init_app(app) 29 | cache.init_app(app) 30 | 31 | 32 | def init_views(app): 33 | app.register_blueprint(index) 34 | QuestionView.register(app) 35 | AnswerView.register(app) 36 | CommentView.register(app) 37 | UserView.register(app) 38 | CollectionView.register(app) 39 | 40 | # api 41 | QuestionApiView.register(app) 42 | AnswerApiView.register(app) 43 | UserApiView.register(app) 44 | CommentApiView.register(app) 45 | CollectionApiView.register(app) -------------------------------------------------------------------------------- /FlaskZhihu/models/topic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin 6 | from flask.ext.sqlalchemy_cache import CachingQuery 7 | 8 | 9 | class Topic(DateTimeMixin, FindByIdMixin, db.Model): 10 | __tablename__ = 'topic' 11 | query_class = CachingQuery 12 | 13 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 14 | name = db.Column('name', db.String(60)) 15 | description = db.Column('description', db.String(1024)) 16 | avatar_url = db.Column('avatar_url', db.String(200)) 17 | 18 | questions = db.relationship(u'Question', secondary='topic_and_question', backref='topics') 19 | user_on_topic = db.relationship(u'UserOnTopic', backref='topic') 20 | 21 | def __repr__(self): 22 | return '<%s %r>' % (self.__class__.__name__, self.name) 23 | 24 | 25 | 26 | class TopicAndQuestion(DateTimeMixin, FindByIdMixin, db.Model): 27 | __tablename__ = 'topic_and_question' 28 | 29 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 30 | topic_id = db.Column('topic_id', db.ForeignKey(u'topic.id'), nullable=False, index=True) 31 | question_id = db.Column('question_id', db.ForeignKey(u'question.id'), nullable=False, index=True) 32 | -------------------------------------------------------------------------------- /scripts/import_question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from pymongo import MongoClient 5 | 6 | from flask import Flask 7 | 8 | from FlaskZhihu.settings import IPythonSettings 9 | from FlaskZhihu.models import * 10 | from FlaskZhihu.extensions import db 11 | 12 | def enc(s): 13 | if isinstance(s, unicode): 14 | return s.encode('UTF-8') 15 | else: 16 | return s 17 | 18 | app = Flask(__name__) 19 | app.config.from_object(IPythonSettings()) 20 | db.init_app(app) 21 | 22 | ctx = app.app_context() 23 | 24 | ctx.push() 25 | mysql = db.session() 26 | 27 | mg = MongoClient('192.168.5.202').zhihu 28 | 29 | print Question.query.delete() 30 | mysql.commit() 31 | #exit(1) 32 | 33 | cur = mg.questions.find() #.limit(100000) 34 | i = 0 35 | for item in cur: 36 | try: 37 | d = item['data'] 38 | o = Question() 39 | o.title = enc(d['title']) 40 | o.id = int(d['id']) 41 | o.excerpt = enc(d.get('excerpt', '')) 42 | o.content = enc(d['detail']) 43 | o.user_hashid = enc(d['author']['id']) 44 | # o.answers_count = Answer.query.filter(Answer.question_id == o.id).count() 45 | 46 | u = User.get_user_by_hashid(o.user_hashid) 47 | if u: 48 | o.user = u 49 | 50 | mysql.add(o) 51 | except: 52 | pass 53 | 54 | i += 1 55 | if i % 100 == 0: 56 | mysql.commit() 57 | 58 | mysql.commit() -------------------------------------------------------------------------------- /scripts/import_people.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from pymongo import MongoClient 5 | 6 | from flask import Flask 7 | 8 | from FlaskZhihu.settings import IPythonSettings 9 | from FlaskZhihu.models import * 10 | from FlaskZhihu.extensions import db 11 | 12 | def enc(s): 13 | return s.encode('UTF-8') 14 | 15 | app = Flask(__name__) 16 | app.config.from_object(IPythonSettings()) 17 | db.init_app(app) 18 | 19 | ctx = app.app_context() 20 | 21 | ctx.push() 22 | mysql = db.session() 23 | 24 | mg = MongoClient('192.168.5.202').zhihu 25 | 26 | print User.query.delete() 27 | mysql.commit() 28 | #exit(1) 29 | cur = mg.people.find() #.limit(100000) 30 | 31 | i = 0 32 | for people in cur: 33 | try: 34 | p = people['data'] 35 | 36 | 37 | u = User() 38 | u.hashid = p['id'] 39 | u.name = enc(p['name']) 40 | u.password = '123456' 41 | if User.query.filter(User.username == enc(p['name'])).count(): 42 | u.username = enc(u'%s_%s' % (p['name'] ,u.hashid)) 43 | else: 44 | u.username = enc(p['name']) 45 | u.avatar_url = enc(p['avatar_url']) 46 | u.headline = enc(p['headline']) 47 | u.description = enc(p['description']) 48 | u.user_hashid = p['id'] 49 | u.gender = p['gender'] 50 | 51 | mysql.add(u) 52 | except: 53 | pass 54 | 55 | i += 1 56 | if i % 100 == 0: 57 | mysql.commit() 58 | 59 | mysql.commit() -------------------------------------------------------------------------------- /FlaskZhihu/templates/collection/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} {{ collection.title }} {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | title: {{ collection.title }}
6 | 编辑 7 | |关注 8 | |取消关注 9 |
10 | description: {{ collection.description }}
11 | answers:
12 | 13 | 14 | {% for answer in collection.answers %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% if answer.user %} 23 | 24 | {% else %} 25 | 26 | {% endif %} 27 | 28 | 29 | {% endfor %} 30 | 31 |
移除赞同({{ answer.voteup_count }})反对({{ answer.votedown_count }})取消意见详情{{ answer.user.name }} {{ answer.user.hashid }}未知{{ answer.content|safe }}
32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /scripts/import_answer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from pymongo import MongoClient 5 | 6 | from flask import Flask 7 | 8 | from FlaskZhihu.settings import IPythonSettings 9 | from FlaskZhihu.models import * 10 | from FlaskZhihu.extensions import db 11 | import datetime 12 | def enc(s): 13 | if isinstance(s, unicode): 14 | return s.encode('UTF-8') 15 | else: 16 | return s 17 | 18 | app = Flask(__name__) 19 | app.config.from_object(IPythonSettings()) 20 | db.init_app(app) 21 | 22 | ctx = app.app_context() 23 | 24 | ctx.push() 25 | mysql = db.session() 26 | 27 | mg = MongoClient('192.168.5.202').zhihu 28 | 29 | print Answer.query.delete() 30 | mysql.commit() 31 | #exit(1) 32 | 33 | cur = mg.answers.find() #.limit(100000) 34 | 35 | i = 0 36 | for item in cur: 37 | try: 38 | d = item['data'] 39 | o = Answer() 40 | o.id = d['id'] 41 | o.content = enc(d['content']) 42 | o.excerpt = enc(d.get('excerpt', '')) 43 | create_time = d.get('created_time', None) 44 | if create_time: 45 | o.create_time = datetime.datetime.fromtimestamp(create_time) 46 | update_time = d.get('updated_time', None) 47 | if update_time: 48 | o.update_time = datetime.datetime.fromtimestamp(update_time) 49 | 50 | o.user_hashid = enc(d['author']['id']) 51 | u = User.get_user_by_hashid(o.user_hashid) 52 | if u: 53 | o.user = u 54 | 55 | o.question_id = int(d['question']['id']) 56 | 57 | mysql.add(o) 58 | except: 59 | pass 60 | 61 | i += 1 62 | if i % 100 == 0: 63 | mysql.commit() 64 | 65 | mysql.commit() -------------------------------------------------------------------------------- /FlaskZhihu/models/collection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin 6 | from flask.ext.sqlalchemy_cache import CachingQuery 7 | 8 | 9 | class Collection(DateTimeMixin, FindByIdMixin, db.Model): 10 | __tablename__ = 'collection' 11 | query_class = CachingQuery 12 | 13 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 14 | title = db.Column('title', db.String(60)) 15 | description = db.Column('description', db.String(4096)) 16 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 17 | user_hashid = db.Column('user_hashid', db.String(32)) 18 | 19 | answers_count = db.Column('answers_count', db.Integer, server_default='0') 20 | following_count = db.Column('following_count', db.Integer, server_default='0') 21 | 22 | answers = db.relationship(u'Answer', secondary='collection_and_answer', backref='collections') 23 | comments = db.relationship(u'Comment', backref='collection') 24 | user_on_collection = db.relationship(u'UserOnCollection', backref='collection') 25 | 26 | def __repr__(self): 27 | return '<%s %r>' % (self.__class__.__name__, self.title) 28 | 29 | 30 | class CollectionAndAnswer(DateTimeMixin, FindByIdMixin, db.Model): 31 | __tablename__ = 'collection_and_answer' 32 | 33 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 34 | collection_id = db.Column('collection_id', db.ForeignKey(u'collection.id'), nullable=False, index=True) 35 | answer_id = db.Column('answer_id', db.ForeignKey(u'answer.id'), nullable=False, index=True) 36 | -------------------------------------------------------------------------------- /FlaskZhihu/templates/question/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} {{ question.title }} {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | title: {{ question.title }} 6 |
7 | content: {{ question.content|safe }}
8 | answers:
9 | 10 | 11 | {% for answer in answers %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% if answer.user %} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | {% endfor %} 26 | 27 |
赞同({{ answer.voteup_count }})反对({{ answer.votedown_count }})取消意见详情{{ answer.user.name }} {{ answer.user.hashid }}未知{{ answer.content|safe }}
28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | random_questions:
36 | {% for q in random_questions %} 37 | {{ q.title }} ({{ q.answers_count }})
38 | {% endfor %} 39 | 40 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /FlaskZhihu/models/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | import datetime 6 | 7 | class FindByIdMixin(): 8 | @classmethod 9 | def find_by_id(cls, id, abort404=False, pk='id'): 10 | pk_col = getattr(cls, pk) 11 | query = cls.query.filter(pk_col==id) 12 | if abort404: 13 | return query.first_or_404() 14 | else: 15 | return query.first() 16 | 17 | class DateTimeMixin(): 18 | create_time = db.Column('create_time', db.DateTime, default=datetime.datetime.now) 19 | update_time = db.Column('update_time', db.DateTime, 20 | default=datetime.datetime.now, 21 | onupdate=datetime.datetime.now) 22 | 23 | 24 | def try_decode(s, enc_list=['UTF-8', 'GBK']): 25 | for enc in enc_list: 26 | try: 27 | v = s.decode(enc) 28 | except: 29 | pass 30 | else: 31 | return v 32 | return s 33 | 34 | def try_encode(s, enc_list=['UTF-8', 'GBK']): 35 | for enc in enc_list: 36 | try: 37 | v = s.encode(enc) 38 | except: 39 | pass 40 | else: 41 | return v 42 | return s 43 | 44 | def blob_unicode(key_name): 45 | def _set_blob(self, val): 46 | if isinstance(val, unicode): 47 | val = try_encode(val) 48 | setattr(self, key_name, val) 49 | 50 | 51 | def _get_blob(self): 52 | val = getattr(self, key_name, None) 53 | if val is None: 54 | return val 55 | 56 | if isinstance(val, unicode): 57 | return val 58 | else: 59 | return try_decode(val) 60 | 61 | return db.synonym(key_name, descriptor=property(_get_blob, _set_blob)) 62 | -------------------------------------------------------------------------------- /FlaskZhihu/signals.py: -------------------------------------------------------------------------------- 1 | from blinker import Namespace 2 | 3 | signals = Namespace() 4 | # 5 | # signals_names = [ 6 | # 'question_answer_add', 7 | # 'question_follow', 8 | # 'question_unfollow', 9 | # 'question_comment_add', 10 | # 'question_comment_delete', 11 | # 'answer_comment_add', 12 | # 'answer_comment_delete', 13 | # 'answer_voteup', 14 | # 'answer_votedown', 15 | # 'answer_cancel_vote', 16 | # ] 17 | # 18 | # signals_defines = dict() 19 | # for name in signals_names: 20 | # print "%s = signals.signal('%s')" % (name, name) 21 | # signals_defines[name] = signals.signal(name) 22 | # 23 | # globals().update(signals_defines) 24 | # 25 | # __all__ = signals_names 26 | 27 | question_answer_add = signals.signal('question_answer_add') 28 | question_follow = signals.signal('question_follow') 29 | question_unfollow = signals.signal('question_unfollow') 30 | question_comment_add = signals.signal('question_comment_add') 31 | question_comment_delete = signals.signal('question_comment_delete') 32 | answer_comment_add = signals.signal('answer_comment_add') 33 | answer_comment_delete = signals.signal('answer_comment_delete') 34 | answer_voteup = signals.signal('answer_voteup') 35 | answer_votedown = signals.signal('answer_votedown') 36 | answer_cancel_vote = signals.signal('answer_cancel_vote') 37 | answer_thank = signals.signal('answer_thank') 38 | answer_unthank = signals.signal('answer_unthank') 39 | comment_voteup = signals.signal('comment_voteup') 40 | comment_cancel_vote = signals.signal('comment_cancel_vote') 41 | collection_answer_add = signals.signal('collection_answer_add') 42 | collection_answer_delete = signals.signal('collection_answer_delete') 43 | collection_follow = signals.signal('collection_follow') 44 | collection_unfollow = signals.signal('collection_unfollow') 45 | -------------------------------------------------------------------------------- /FlaskZhihu/api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import url_for, abort, redirect, render_template, request 5 | from flask.ext.classy import FlaskView, route 6 | from flask.ext.login import current_user, login_required 7 | 8 | from FlaskZhihu.extensions import db 9 | from FlaskZhihu.forms import AnswerAddForm 10 | from FlaskZhihu.models import Answer, Question, User 11 | from FlaskZhihu.signals import * 12 | 13 | __all__ = ['AnswerApiView', 'QuestionApiView', 'UserApiView', 'CollectionApiView', 'CommentApiView'] 14 | 15 | class BaseApiView(FlaskView): 16 | route_prefix = '/api' 17 | 18 | 19 | class QuestionApiView(BaseApiView): 20 | route_base = '/question' 21 | decorators = [login_required] 22 | 23 | @route('/follow//') 24 | def follow(self, id): 25 | pass 26 | 27 | @route('/collect//') 28 | def collect(self, id): 29 | pass 30 | 31 | 32 | class AnswerApiView(BaseApiView): 33 | route_base = '/answer' 34 | decorators = [login_required] 35 | 36 | @route('/vote//') 37 | def vote(self, id): 38 | pass 39 | 40 | @route('/follow//') 41 | def follow(self, id): 42 | pass 43 | 44 | @route('/comment//') 45 | def comment(self, id): 46 | pass 47 | 48 | @route('/thank//') 49 | def thank(self, id): 50 | pass 51 | 52 | 53 | class UserApiView(BaseApiView): 54 | route_base = '/user' 55 | decorators = [login_required] 56 | 57 | def add(self): 58 | pass 59 | 60 | 61 | class CommentApiView(BaseApiView): 62 | route_base = '/comment' 63 | decorators = [login_required] 64 | 65 | 66 | 67 | class CollectionApiView(BaseApiView): 68 | route_base = '/collection' 69 | decorators = [login_required] -------------------------------------------------------------------------------- /FlaskZhihu/templates/answer/show.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} {{ answer.user.name }} 的回答 {% endblock %} 3 | {% block body %} 4 | {% include 'user/_user_banner.html' %} 5 | 作者: {{ answer.user.name }} 6 |
收藏 7 | |赞同({{ answer.voteup_count }}) 8 | |反对({{ answer.votedown_count }}) 9 | |取消意见 10 | |感谢({{ answer.thanks_count }}) 11 | |取消感谢 12 |
13 | content: {{ answer.content|safe }}
14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | {% for comment in answer.comments %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% endfor %} 36 | 37 |
赞同({{ comment.voteup_count }})取消意见{{ comment.create_time }}{{ comment.user.name }}{{ comment.content }}
38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /FlaskZhihu/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | from redis import Redis 4 | 5 | 6 | class DefaultSettings(object): 7 | DEBUG = True 8 | SQLALCHEMY_DATABASE_URI = "mysql://flaskzhihu:123456@192.168.5.202/flaskzhihu?charset=UTF-8" 9 | SQLALCHEMY_ECHO = False 10 | SQLALCHEMY_TRACK_MODIFICATIONS = True 11 | SECRET_KEY = "asasasasas" 12 | 13 | 14 | class TestSettings(object): 15 | DEBUG = True 16 | SQLALCHEMY_DATABASE_URI = "mysql://flaskzhihu:123456@192.168.5.202/flaskzhihu_test?charset=utf8" 17 | SQLALCHEMY_ECHO = False 18 | SQLALCHEMY_TRACK_MODIFICATIONS = True 19 | WTF_CSRF_ENABLED = False 20 | SECRET_KEY = "asasasasas" 21 | CACHE_TYPE = "simple" 22 | CACHE_DEFAULT_TIMEOUT = 60 23 | SQLALCHEMY_RECORD_QUERIES = True 24 | 25 | 26 | class IPythonSettings(object): 27 | DEBUG = True 28 | WTF_CSRF_ENABLED = False 29 | SECRET_KEY = "asasasasas" 30 | 31 | ###### flask-sqlalchemy ######### 32 | SQLALCHEMY_DATABASE_URI = "mysql://flaskzhihu:123456@192.168.5.202/flaskzhihu_test?charset=utf8" 33 | SQLALCHEMY_ECHO = False 34 | SQLALCHEMY_TRACK_MODIFICATIONS = True 35 | SQLALCHEMY_RECORD_QUERIES = True 36 | 37 | ####### flask-cache ########### 38 | CACHE_TYPE = 'redis' 39 | CACHE_REDIS_HOST = '127.0.0.1' 40 | CACHE_KEY_PREFIX = 'flask_cache_' 41 | CACHE_REDIS_DB = 1 42 | #CACHE_TYPE = "simple" 43 | CACHE_DEFAULT_TIMEOUT = 600 44 | 45 | ######## flask-session ######## 46 | SESSION_TYPE = 'redis' 47 | SESSION_KEY_PREFIX = 'flask_session_' 48 | # SESSION_REDIS = Redis('127.0.0.1', db=0) 49 | SESSION_REDIS = property(lambda self: Redis('127.0.0.1', db=1)) 50 | 51 | 52 | class DeploySettings(IPythonSettings): 53 | SQLALCHEMY_DATABASE_URI = "mysql://flaskzhihu:123456@192.168.5.202/flaskzhihu?charset=utf8" 54 | 55 | -------------------------------------------------------------------------------- /FlaskZhihu/models/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.models.user import UserOnComment 5 | from FlaskZhihu.extensions import db 6 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin 7 | from FlaskZhihu.constants import VOTE_UP, VOTE_NONE, VOTE_DOWN 8 | from flask.ext.sqlalchemy_cache import CachingQuery 9 | 10 | 11 | class Comment(DateTimeMixin, FindByIdMixin, db.Model): 12 | __tablename__ = 'comment' 13 | query_class = CachingQuery 14 | 15 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 16 | content = db.Column('content', db.String(4096)) 17 | comment_target = db.Column('comment_target', db.String(10)) 18 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), index=True) 19 | user_hashid = db.Column('user_hashid', db.String(32)) 20 | 21 | quote_comment_id = db.Column('quote_comment_id', db.ForeignKey(u'comment.id'), index=True) 22 | answer_id = db.Column('answer_id', db.ForeignKey(u'answer.id'), index=True) 23 | question_id = db.Column('question_id', db.ForeignKey(u'question.id'), index=True) 24 | collection_id = db.Column('collection_id', db.ForeignKey(u'collection.id'), index=True) 25 | 26 | user_on_comment = db.relationship(u'UserOnComment', backref='comment') 27 | reply_comments = db.relationship("Comment", 28 | backref=db.backref('quote_comment', remote_side=[id])) 29 | 30 | # count cache 31 | voteup_count = db.Column('voteup_count', db.Integer, server_default='0') 32 | 33 | @property 34 | def voteup_users(self): 35 | ops = UserOnComment.query.filter( 36 | db.and_( 37 | UserOnComment.comment_id == self.id, 38 | UserOnComment.vote == VOTE_UP, 39 | ) 40 | ).all() 41 | 42 | return [op.user for op in ops] 43 | 44 | def __repr__(self): 45 | return '<%s %r>' % (self.__class__.__name__, self.content[0:8]) -------------------------------------------------------------------------------- /FlaskZhihu/models/question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models.user import UserOnQuestion 6 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin, blob_unicode 7 | from flask.ext.sqlalchemy_cache import CachingQuery 8 | 9 | 10 | class Question(DateTimeMixin, FindByIdMixin, db.Model): 11 | __tablename__ = 'question' 12 | query_class = CachingQuery 13 | 14 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 15 | answers_update_time = db.Column('answers_update_time', db.DateTime) 16 | title = db.Column('title', db.String(500)) 17 | excerpt = db.Column('excerpt', db.String(4096)) 18 | answer_ids = db.Column('answer_ids', db.Integer) 19 | 20 | # count cache 21 | voteup_count = db.Column('voteup_count', db.Integer, server_default='0') 22 | votedown_count = db.Column('votedown_count', db.Integer, server_default='0') 23 | answers_count = db.Column('answers_count', db.Integer, server_default='0') 24 | following_count = db.Column('following_count', db.Integer, server_default='0') 25 | comments_count = db.Column('comments_count', db.Integer, server_default='0') 26 | 27 | status = db.Column('status', db.String(45)) 28 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), index=True) 29 | user_hashid = db.Column('user_hashid', db.String(32)) 30 | 31 | answers = db.relationship(u'Answer', backref='question') 32 | comments = db.relationship(u'Comment', backref='question') 33 | 34 | _content = db.Column('content', db.LargeBinary) 35 | content = blob_unicode('_content') 36 | 37 | user_on_question = db.relationship(u'UserOnQuestion', backref='question') 38 | 39 | @property 40 | def following_users(self): 41 | uoqs = UserOnQuestion.query.filter( 42 | db.and_(UserOnQuestion.question_id == self.id, UserOnQuestion.follow==True) 43 | ).all() 44 | 45 | return [uoq.user for uoq in uoqs] 46 | 47 | 48 | def __repr__(self): 49 | return '<%s %r>' % (self.__class__.__name__, self.title) 50 | -------------------------------------------------------------------------------- /FlaskZhihu/models/answer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin, blob_unicode 6 | from FlaskZhihu.models.user import UserOnAnswer 7 | from FlaskZhihu.constants import VOTE_UP, VOTE_DOWN, VOTE_NONE, THANK_ON 8 | from flask.ext.sqlalchemy_cache import CachingQuery 9 | 10 | 11 | class Answer(DateTimeMixin, FindByIdMixin, db.Model): 12 | __tablename__ = 'answer' 13 | query_class = CachingQuery 14 | 15 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 16 | thanks_count = db.Column('thanks_count', db.Integer) 17 | excerpt = db.Column('excerpt', db.String(4096)) 18 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), index=True) 19 | user_hashid = db.Column('user_hashid', db.String(32)) 20 | question_id = db.Column('question_id', db.ForeignKey(u'question.id'), index=True) 21 | collection_id = db.Column('collection_id', db.ForeignKey(u'collection.id'), index=True) 22 | 23 | user_on_answer = db.relationship(u'UserOnAnswer', backref='answer') 24 | comments = db.relationship(u'Comment', backref='answer') 25 | 26 | _content = db.Column('content', db.LargeBinary) 27 | content = blob_unicode('_content') 28 | 29 | # count cache 30 | comments_count = db.Column('comments_count', db.Integer, server_default='0') 31 | voteup_count = db.Column('voteup_count', db.Integer, server_default='0') 32 | votedown_count = db.Column('votedown_count', db.Integer, server_default='0') 33 | 34 | 35 | 36 | @property 37 | def vote_count(self): 38 | return self.voteup_count - self.votedown_count 39 | 40 | @property 41 | def voteup_users(self): 42 | ops = UserOnAnswer.query.filter( 43 | db.and_( 44 | UserOnAnswer.answer_id==self.id, 45 | UserOnAnswer.vote==VOTE_UP, 46 | ) 47 | ).all() 48 | 49 | return [op.user for op in ops] 50 | 51 | @property 52 | def votedown_users(self): 53 | ops = UserOnAnswer.query.filter( 54 | db.and_( 55 | UserOnAnswer.answer_id == self.id, 56 | UserOnAnswer.vote == VOTE_DOWN, 57 | ) 58 | ).all() 59 | 60 | return [op.user for op in ops] 61 | 62 | @property 63 | def thanked_users(self): 64 | ops = UserOnAnswer.query.filter( 65 | db.and_( 66 | UserOnAnswer.answer_id == self.id, 67 | UserOnAnswer.thank == THANK_ON, 68 | ) 69 | ).all() 70 | return [op.user for op in ops] 71 | 72 | 73 | def __repr__(self): 74 | return '<%s %r>' % (self.__class__.__name__, self.id) -------------------------------------------------------------------------------- /FlaskZhihu/views/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | from flask import url_for, abort, redirect, render_template 4 | from flask.ext.classy import FlaskView, route, request 5 | from flask.ext.login import current_user, login_required 6 | 7 | from FlaskZhihu.extensions import db 8 | from FlaskZhihu.forms import CommentAddForm 9 | from FlaskZhihu.models import Answer, Question, Comment, Collection, User 10 | from FlaskZhihu.signals import * 11 | 12 | __all__ = ['CommentView'] 13 | 14 | 15 | class CommentView(FlaskView): 16 | route_base = '/comment' 17 | 18 | @route(r'/add/', methods=['POST']) 19 | @login_required 20 | def add(self): 21 | form = CommentAddForm() 22 | if form.validate_on_submit(): 23 | data = form.data 24 | print data 25 | target_type = str(data['target_type']) 26 | target_id = str(data['target_id']) 27 | 28 | comment = Comment() 29 | comment.user = current_user 30 | comment.content = data['content'] 31 | comment.comment_target = target_type 32 | if target_type == 'answer': 33 | comment.answer = Answer.find_by_id(target_id, abort404=True) 34 | 35 | answer_comment_add.send(comment.answer) 36 | 37 | db.session.add(comment) 38 | db.session.commit() 39 | elif target_type == 'question': 40 | comment.question = Question.find_by_id(target_id, abort404=True) 41 | 42 | question_comment_add.send(comment.question) 43 | 44 | db.session.add(comment) 45 | db.session.commit() 46 | elif target_type == 'collection': 47 | comment.collection = Collection.find_by_id(target_id, abort404=True) 48 | 49 | #collection_comment_add.send(comment.collection) 50 | 51 | db.session.add(comment) 52 | db.session.commit() 53 | elif target_type == 'comment': 54 | comment.quote_comment = Comment.find_by_id(target_id, abort404=True) 55 | db.session.add(comment) 56 | db.session.commit() 57 | else: 58 | abort(500) 59 | 60 | if request.referrer: 61 | return redirect(request.referrer) 62 | else: 63 | return redirect('/question') 64 | else: 65 | print form.data 66 | abort(404) 67 | 68 | @route(r'//voteup/') 69 | @login_required 70 | def voteup(self, id): 71 | comment = Comment.find_by_id(id, abort404=True) 72 | current_user.voteup_comment(comment) 73 | db.session.commit() 74 | 75 | comment_voteup.send(comment) 76 | 77 | return redirect(request.referrer or '/') 78 | 79 | 80 | @route(r'//cancel_vote/') 81 | @login_required 82 | def cancel_vote(self, id): 83 | comment = Comment.find_by_id(id, abort404=True) 84 | current_user.voteup_comment(comment, undo=True) 85 | db.session.commit() 86 | 87 | comment_cancel_vote.send(comment) 88 | 89 | return redirect(request.referrer or '/') 90 | -------------------------------------------------------------------------------- /tests/test_orm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import Flask 5 | 6 | from FlaskZhihu.settings import TestSettings 7 | from FlaskZhihu.models import * 8 | from FlaskZhihu.extensions import db 9 | 10 | import unittest 11 | 12 | class OrmTestCase(unittest.TestCase): 13 | def setUp(self): 14 | app = Flask(__name__) 15 | app.config.from_object(TestSettings()) 16 | db.init_app(app) 17 | 18 | ctx = app.app_context() 19 | 20 | ctx.push() 21 | db.create_all() 22 | session = db.session() 23 | 24 | self.app = app 25 | self.db = db 26 | self.ctx = ctx 27 | self.session = session 28 | print "setUp" 29 | 30 | def tearDown(self): 31 | self.db.drop_all() 32 | self.ctx.pop() 33 | 34 | 35 | def test_1_create_user(self): 36 | user1 = User() 37 | user1.id = 1 38 | user1.username = 'shn7798_1' 39 | user1.password = '123456' 40 | user1.name = 'shn7798_1' 41 | 42 | user2 = User() 43 | user2.id = 2 44 | user2.username = 'shn7798_2' 45 | user2.password = '123456' 46 | user2.name = 'shn7798_2' 47 | 48 | self.session.add_all([user1, user2]) 49 | self.session.commit() 50 | 51 | u1 = User.query.filter(User.username=='shn7798_1').first_or_404() 52 | u2 = User.query.filter(User.id==2).first_or_404() 53 | 54 | self.assertEqual(user1, u1) 55 | self.assertEqual(user2, u2) 56 | 57 | self.user1 = user1 58 | self.user2 = user2 59 | 60 | def test_2_create_question(self): 61 | # 创建问题1 62 | question1 = Question() 63 | question1.id = 1 64 | question1.title = 'question 1' 65 | question1.content = 'question 1 content' 66 | question1.user = self.user1 67 | self.session.add(question1) 68 | self.session.commit() 69 | 70 | q1 = Question.query.filter(Question.id==1).first_or_404() 71 | self.assertEqual(q1.title, 'question 1') 72 | self.assertEqual(q1.content, 'question 1 content') 73 | self.assertEqual(q1.user_id, self.user1.id) 74 | 75 | self.question1 = question1 76 | 77 | def test_3_create_answer(self): 78 | # user2 创建答案 79 | answer1 = Answer() 80 | answer1.id = 11 81 | answer1.content = 'question 1 answer' 82 | answer1.user = self.user2 83 | answer1.question = self.question1 84 | 85 | self.session.add(answer1) 86 | self.session.commit() 87 | 88 | a1 = Answer.query.filter(Answer.id==11).first_or_404() 89 | self.assertEqual(a1.content, 'question 1 answer') 90 | self.assertEqual(a1.user_id, self.user2.id) 91 | 92 | def test_4_user_follow_question(self): 93 | self.user1.op_on_questions.append(self.question1) 94 | uoq = self.user1.op_on_question(self.question1) 95 | uoq.follow = True 96 | 97 | self.session.commit() 98 | assert self.user1 in self.question1.following_users() 99 | 100 | 101 | 102 | if __name__ == '__main__': 103 | unittest.main() 104 | 105 | -------------------------------------------------------------------------------- /FlaskZhihu/views/answer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | from flask import url_for, abort, redirect, render_template, request 4 | from flask.ext.classy import FlaskView, route 5 | from flask.ext.login import current_user, login_required 6 | 7 | from FlaskZhihu.extensions import db 8 | from FlaskZhihu.forms import AnswerAddForm, AnswerEditForm 9 | from FlaskZhihu.models import Answer, Question, User 10 | from FlaskZhihu.signals import * 11 | from FlaskZhihu.helpers import keep_next_url 12 | 13 | __all__ = ['AnswerView'] 14 | 15 | 16 | class AnswerView(FlaskView): 17 | route_base = '/answer' 18 | 19 | @route(r'/add/', methods=['POST']) 20 | @login_required 21 | def add(self): 22 | form = AnswerAddForm() 23 | if form.validate_on_submit(): 24 | question = Question.query.get_or_404(form.question_id.data) 25 | if Answer.query.filter(Answer.question_id == question.id).filter(Answer.user_id == current_user.id).count(): 26 | # answer exists 27 | return redirect(url_for('QuestionView:show', id=str(form.question_id.data))) 28 | answer = Answer() 29 | answer.content = form.content.data 30 | answer.question = question 31 | answer.user = current_user 32 | db.session.add(answer) 33 | db.session.commit() 34 | 35 | question_answer_add.send(question) 36 | 37 | if form.question_id.data: 38 | return redirect(url_for('QuestionView:show', id=str(form.question_id.data))) 39 | else: 40 | abort(404) 41 | 42 | @route(r'//') 43 | def show(self, id): 44 | answer = Answer.find_by_id(id, abort404=True) 45 | 46 | return render_template('answer/show.html', answer=answer) 47 | 48 | @route(r'//voteup/') 49 | @login_required 50 | def voteup(self, id): 51 | answer = Answer.find_by_id(id, abort404=True) 52 | current_user.voteup_answer(answer) 53 | db.session.commit() 54 | 55 | answer_voteup.send(answer) 56 | 57 | return redirect(request.referrer or '/') 58 | 59 | @route(r'//votedown/') 60 | @login_required 61 | def votedown(self, id): 62 | answer = Answer.find_by_id(id, abort404=True) 63 | current_user.votedown_answer(answer) 64 | db.session.commit() 65 | 66 | answer_votedown.send(answer) 67 | 68 | return redirect(request.referrer or '/') 69 | 70 | @route(r'//cancel_vote/') 71 | @login_required 72 | def cancel_vote(self, id): 73 | answer = Answer.find_by_id(id, abort404=True) 74 | current_user.voteup_answer(answer, undo=True) 75 | current_user.votedown_answer(answer, undo=True) 76 | db.session.commit() 77 | 78 | answer_cancel_vote.send(answer) 79 | 80 | return redirect(request.referrer or '/') 81 | 82 | 83 | @route(r'/my/') 84 | @login_required 85 | def my(self): 86 | assert current_user.id 87 | answers = Answer.query.options(db.joinedload('question'))\ 88 | .filter(Answer.user_id == current_user.id).all() 89 | 90 | return render_template('answer/my.html', answers=answers) 91 | 92 | 93 | @route(r'//edit/', methods=['GET', 'POST']) 94 | @login_required 95 | @keep_next_url 96 | def edit(self, id, next_url=None): 97 | answer = Answer.query.get_or_404(id) 98 | form = AnswerEditForm() 99 | if form.validate_on_submit(): 100 | answer.content = form.content.data 101 | db.session.commit() 102 | return redirect(next_url or url_for('QuestionView:show', id=answer.question_id)) 103 | else: 104 | form.content.data = answer.content 105 | return render_template('answer/edit.html', form=form) -------------------------------------------------------------------------------- /tests/orm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import Flask 5 | 6 | from FlaskZhihu.settings import IPythonSettings 7 | from FlaskZhihu.models import * 8 | from FlaskZhihu.extensions import db, cache 9 | from FlaskZhihu.application import create_app 10 | from flask.ext.sqlalchemy_cache import FromCache 11 | from flask.ext.sqlalchemy import get_debug_queries, get_state 12 | 13 | 14 | Base = db.Model 15 | Column = db.Column 16 | Integer = db.Integer 17 | ForeignKey = db.ForeignKey 18 | String = db.String 19 | relationship = db.relationship 20 | backref=db.backref 21 | 22 | # 23 | # class Node1(Base): 24 | # __tablename__ = 'node1' 25 | # id = Column(Integer, primary_key=True) 26 | # parent_id = Column(Integer, ForeignKey('node1.id')) 27 | # data = Column(String(50)) 28 | # parent = relationship("Node1", remote_side=[id]) 29 | # 30 | # class Node2(Base): 31 | # __tablename__ = 'node2' 32 | # id = Column(Integer, primary_key=True) 33 | # parent_id = Column(Integer, ForeignKey('node2.id')) 34 | # data = Column(String(50)) 35 | # children = relationship("Node2", 36 | # backref=backref('parent', remote_side=[id]) 37 | # ) 38 | 39 | 40 | 41 | def init(db): 42 | 43 | db.create_all() 44 | session = db.session() 45 | 46 | user1 = User() 47 | user1.id = 1 48 | user1.username = 'shn7798_1' 49 | user1.password = '123456' 50 | user1.name = 'shn7798_1' 51 | 52 | user2 = User() 53 | user2.id = 2 54 | user2.username = 'shn7798_2' 55 | user2.password = '123456' 56 | user2.name = 'shn7798_2' 57 | 58 | user3 = User() 59 | user3.id = 3 60 | user3.username = 'shn7798_3' 61 | user3.password = '123456' 62 | user3.name = 'shn7798_3' 63 | 64 | session.add_all([user1, user2, user3]) 65 | session.commit() 66 | 67 | u1 = User.query.filter(User.username == 'shn7798_1').first_or_404() 68 | u2 = User.query.filter(User.id == 2).first_or_404() 69 | 70 | assert (user1 == u1) 71 | assert (user2 == u2) 72 | 73 | # 创建问题1 74 | question1 = Question() 75 | question1.id = 1 76 | question1.title = 'question 1' 77 | question1.content = 'question 1 content' 78 | question1.user = user1 79 | session.add(question1) 80 | session.commit() 81 | 82 | q1 = Question.query.filter(Question.id == 1).first_or_404() 83 | assert (q1.title == 'question 1') 84 | assert (q1.content == 'question 1 content') 85 | assert (q1.user_id == user1.id) 86 | 87 | # user2 创建答案 88 | answer1 = Answer() 89 | answer1.id = 11 90 | answer1.content = 'question 1 answer' 91 | answer1.user = user2 92 | answer1.question = question1 93 | 94 | session.add(answer1) 95 | session.commit() 96 | 97 | a1 = Answer.query.filter(Answer.id == 11).first_or_404() 98 | assert (a1.content == 'question 1 answer') 99 | assert (a1.user_id == user2.id) 100 | 101 | user1.op_on_questions.append(question1) 102 | uoq = user1.op_on_question(question1) 103 | uoq.follow = True 104 | 105 | session.commit() 106 | assert user1 in question1.following_users() 107 | 108 | op = user1.op_on_user(user2, edit=True) 109 | op.follow = True 110 | session.commit() 111 | 112 | user1.voteup_answer(answer1) 113 | session.commit() 114 | 115 | user1.block_user(user3) 116 | session.commit() 117 | 118 | 119 | if __name__ == '__main__': 120 | app = create_app(IPythonSettings()) 121 | 122 | ctx = app.app_context() 123 | 124 | ctx.push() 125 | session = db.session() 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /FlaskZhihu/views/question.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | import random 5 | import time 6 | import os 7 | 8 | 9 | from flask import abort, render_template, current_app, redirect, url_for 10 | 11 | from flask.ext.classy import FlaskView, route 12 | from flask.ext.login import login_required, current_user 13 | 14 | from flask.ext.sqlalchemy_cache import FromCache, RelationshipCache 15 | from flask.ext.sqlalchemy import get_debug_queries 16 | 17 | from FlaskZhihu.models import * 18 | from FlaskZhihu.helpers import cached 19 | from FlaskZhihu.extensions import cache 20 | from FlaskZhihu.forms import QuestionAddForm, QuestionEditForm 21 | from FlaskZhihu.helpers import keep_next_url 22 | 23 | from pprint import pprint 24 | 25 | __all__ = ['QuestionView'] 26 | 27 | 28 | 29 | class QuestionView(FlaskView): 30 | route_base = '/question' 31 | 32 | def _random_questions(self, limit=20, max=None): 33 | if max is None: 34 | # max = Question.query.count() 会全表扫描 35 | max = db.session.query(db.func.count(Question.id)).first()[0] 36 | random.seed('%s%d' %(os.umask(24), time.time())) 37 | if max > limit: 38 | offset = random.randint(0, max - limit) 39 | else: 40 | offset = 0 41 | random_questions = Question.query.offset(offset).limit(limit) 42 | random_questions = sorted(random_questions, key=lambda x: x.answers_count, reverse=True) 43 | return random_questions 44 | 45 | @route(r'/') 46 | def index(self): 47 | random_questions = self._random_questions(20) 48 | return render_template('question/index.html', 49 | random_questions=random_questions) 50 | 51 | 52 | @route(r'//') 53 | def show(self, id): 54 | question = Question.query.filter(Question.id == int(id)).first_or_404() 55 | # .join(Answer.user_id == User.id)\ 56 | answers = Answer.query\ 57 | .options(db.joinedload('user'))\ 58 | .filter(Answer.question_id == int(question.id)) \ 59 | .all() 60 | 61 | random_questions = self._random_questions(20) 62 | response = render_template('question/show.html', 63 | question=question, answers=answers, 64 | random_questions=random_questions) 65 | 66 | #pprint(get_debug_queries()) 67 | #print len(get_debug_queries()) 68 | 69 | return response 70 | 71 | @route(r'/add/', methods=['GET', 'POST']) 72 | @login_required 73 | def add(self): 74 | form = QuestionAddForm() 75 | if form.validate_on_submit(): 76 | question = Question() 77 | question.user = current_user 78 | question.title = form.title.data 79 | question.content = form.content.data 80 | 81 | db.session.add(question) 82 | db.session.commit() 83 | 84 | return redirect(url_for('QuestionView:show', id=question.id)) 85 | 86 | return render_template('question/add.html', form=form) 87 | 88 | @route(r'/my/') 89 | @login_required 90 | def my(self): 91 | assert current_user.id 92 | questions = Question.query.filter(Question.user_id == current_user.id).all() 93 | return render_template('question/my.html', questions=questions) 94 | 95 | @route(r'//edit/', methods=['GET', 'POST']) 96 | @login_required 97 | @keep_next_url 98 | def edit(self, id, next_url=None): 99 | print id 100 | question = Question.query.get_or_404(id) 101 | form = QuestionEditForm() 102 | if form.validate_on_submit(): 103 | question.title = form.title.data 104 | question.content = form.content.data 105 | db.session.commit() 106 | return redirect(next_url or url_for('QuestionView:show', id=question.id)) 107 | else: 108 | form.title.data = question.title 109 | form.content.data = question.content 110 | return render_template('question/edit.html', form=form) -------------------------------------------------------------------------------- /FlaskZhihu/views/collection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import url_for, abort, redirect, render_template, request 5 | from flask.ext.classy import FlaskView, route 6 | from flask.ext.login import current_user, login_required 7 | 8 | from FlaskZhihu.extensions import db 9 | from FlaskZhihu.forms import CollectionAddForm, CollectionEditForm 10 | from FlaskZhihu.models import Answer, Question, User, Collection 11 | from FlaskZhihu import signals 12 | from FlaskZhihu.helpers import keep_next_url 13 | 14 | __all__ = ['CollectionView'] 15 | 16 | 17 | class CollectionView(FlaskView): 18 | route_base = '/collection' 19 | 20 | @route(r'//') 21 | def show(self, id): 22 | collection = Collection.query.options(db.joinedload(Collection.answers)).get_or_404(id) 23 | return render_template('collection/show.html', collection=collection) 24 | 25 | @route(r'/add/', methods=['GET', 'POST']) 26 | @login_required 27 | def add(self): 28 | form = CollectionAddForm() 29 | if form.validate_on_submit(): 30 | collection = Collection() 31 | collection.user = current_user 32 | collection.title = form.title.data 33 | collection.description = form.description.data 34 | 35 | db.session.add(collection) 36 | db.session.commit() 37 | 38 | return redirect(url_for('CollectionView:my')) 39 | 40 | return render_template('collection/add.html', form=form) 41 | 42 | @route(r'//delete/', methods=['GET', 'POST']) 43 | @login_required 44 | @keep_next_url 45 | def delete(self, id, next_url=None): 46 | collection = Collection.query.filter( 47 | db.and_( 48 | Collection.id == id, 49 | Collection.user_id == current_user.id, 50 | ) 51 | ).first_or_404() 52 | 53 | collection.answers = [] 54 | db.session.delete(collection) 55 | db.session.commit() 56 | signals.collection_answer_delete.send(collection) 57 | return redirect(next_url or url_for('CollectionView:my')) 58 | 59 | 60 | @route(r'//edit/', methods=['GET', 'POST']) 61 | @login_required 62 | def edit(self, id): 63 | collection = Collection.query.filter( 64 | db.and_( 65 | Collection.id == id, 66 | Collection.user_id == current_user.id 67 | ) 68 | ).first_or_404() 69 | 70 | form = CollectionEditForm() 71 | 72 | if form.validate_on_submit(): 73 | if form.title.data: 74 | collection.title = form.title.data 75 | if form.description.data: 76 | collection.description = form.description.data 77 | 78 | db.session.commit() 79 | return redirect(url_for('CollectionView:show', id=collection.id)) 80 | else: 81 | form.title.data = collection.title 82 | form.description.data = collection.description 83 | return render_template('collection/edit.html', form=form) 84 | 85 | 86 | @route(r'/my/') 87 | @login_required 88 | def my(self): 89 | collections = current_user.collections 90 | return render_template('collection/my.html', collections=collections) 91 | 92 | 93 | @route(r'//answer//add/', methods=['GET', 'POST']) 94 | @login_required 95 | @keep_next_url 96 | def add_answer(self, id, answer_id, next_url=None): 97 | collection = Collection.query.filter( 98 | db.and_( 99 | Collection.id == id, 100 | Collection.user_id == current_user.id 101 | ) 102 | ).first_or_404() 103 | answer = Answer.query.get_or_404(answer_id) 104 | collection.answers.append(answer) 105 | db.session.commit() 106 | 107 | signals.collection_answer_add.send(collection) 108 | return redirect(next_url or url_for('CollectionView:show', id=collection.id)) 109 | 110 | 111 | @route(r'//answer//delete/', methods=['GET', 'POST']) 112 | @login_required 113 | @keep_next_url 114 | def delete_answer(self, id, answer_id, next_url=None): 115 | collection = Collection.query.filter( 116 | db.and_( 117 | Collection.id == id, 118 | Collection.user_id == current_user.id 119 | ) 120 | ).first_or_404() 121 | answer = Answer.query.get_or_404(answer_id) 122 | collection.answers.remove(answer) 123 | db.session.commit() 124 | 125 | signals.collection_answer_delete.send(collection) 126 | return redirect(next_url or url_for('CollectionView:show', id=collection.id)) 127 | 128 | 129 | @route(r'/select/by/answer//') 130 | @login_required 131 | @keep_next_url 132 | def select_by_answer(self, answer_id, next_url=None): 133 | collections = current_user.collections 134 | return render_template('collection/select_by_answer.html', collections=collections, answer_id=answer_id) -------------------------------------------------------------------------------- /FlaskZhihu/views/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from flask import abort, render_template, redirect, request, url_for 5 | from flask.ext.classy import FlaskView, route 6 | from flask.ext.login import login_user, login_required, logout_user, current_user 7 | 8 | from FlaskZhihu.models import User, Collection, UserOnCollection, Answer 9 | from FlaskZhihu.forms import UserLoginForm, UserAddForm, UserEditForm 10 | from FlaskZhihu.extensions import db, cache 11 | from FlaskZhihu.helpers import keep_next_url 12 | from FlaskZhihu.constants import FOLLOW_OFF, FOLLOW_ON 13 | from FlaskZhihu import signals 14 | 15 | __all__ = ['UserView'] 16 | 17 | class UserView(FlaskView): 18 | route_base = '/user' 19 | 20 | @route('/login/', methods=['GET', 'POST']) 21 | @keep_next_url 22 | def login(self, next_url=None): 23 | form = UserLoginForm() 24 | 25 | if form.validate_on_submit(): 26 | user = User.query.filter(User.username==form.username.data).first() 27 | if user and user.check_password(form.password.data): 28 | login_user(user) 29 | 30 | return redirect(next_url or '/') 31 | 32 | return render_template('user/login.html', form=form) 33 | 34 | 35 | @route('/logout/') 36 | @login_required 37 | def logout(self): 38 | logout_user() 39 | return redirect(request.referrer or '/') 40 | 41 | 42 | @route('/add/', methods=['GET', 'POST']) 43 | @keep_next_url 44 | def add(self, next_url=None): 45 | if current_user.is_anonymous: 46 | form = UserAddForm() 47 | if form.validate_on_submit(): 48 | if User.query.filter(User.username==form.username.data).count() == 0: 49 | user = User() 50 | user.name = form.name.data 51 | user.username = form.username.data 52 | user.password = form.password.data 53 | db.session.add(user) 54 | db.session.commit() 55 | return redirect(url_for('UserView:login')) 56 | return render_template('user/add.html', form=form) 57 | else: 58 | return redirect(next_url or '/') 59 | 60 | @route('/edit/', methods=['GET', 'POST']) 61 | @login_required 62 | @keep_next_url 63 | def edit(self, next_url=None): 64 | form = UserEditForm() 65 | if form.validate_on_submit(): 66 | user = current_user 67 | if form.name.data: 68 | user.name = form.name.data 69 | if form.password.data: 70 | user.password = form.password.data 71 | db.session.commit() 72 | # 不管有没有修改, 都返回上个页面 73 | return redirect(next_url or '/') 74 | else: 75 | if request.method.upper() == 'POST': 76 | return redirect(next_url or '/') 77 | else: 78 | return render_template('user/edit.html', form=form) 79 | 80 | 81 | @route('/collection//follow/') 82 | @login_required 83 | @keep_next_url 84 | def follow_collection(self, id, next_url=None): 85 | collection = Collection.query.get_or_404(id) 86 | current_user.follow_collection(collection) 87 | db.session.commit() 88 | 89 | signals.collection_follow.send(collection) 90 | return redirect(next_url or url_for('CollectionView:my')) 91 | 92 | 93 | @route('/collection//unfollow/') 94 | @login_required 95 | @keep_next_url 96 | def unfollow_collection(self, id, next_url=None): 97 | collection = Collection.query.get_or_404(id) 98 | current_user.unfollow_collection(collection) 99 | db.session.commit() 100 | 101 | signals.collection_unfollow.send(collection) 102 | return redirect(next_url or url_for('CollectionView:my')) 103 | 104 | @route('/my/') 105 | @login_required 106 | def my(self): 107 | return self.show(current_user.id) 108 | 109 | @route(r'//') 110 | def show(self, id): 111 | user = User.query.get_or_404(id) 112 | return render_template('user/show.html', user=user) 113 | 114 | @route(r'/answer//thank/', methods=['GET', 'POST']) 115 | @login_required 116 | @keep_next_url 117 | def thank_answer(self, answer_id, next_url=None): 118 | answer = Answer.query.get_or_404(answer_id) 119 | current_user.thank_answer(answer) 120 | db.session.commit() 121 | 122 | signals.answer_thank.send(answer) 123 | return redirect(next_url or url_for('AnswerView:show', id=answer_id)) 124 | 125 | @route(r'/answer//unthank/', methods=['GET', 'POST']) 126 | @login_required 127 | @keep_next_url 128 | def unthank_answer(self, answer_id, next_url=None): 129 | answer = Answer.query.get_or_404(answer_id) 130 | current_user.unthank_answer(answer) 131 | 132 | db.session.commit() 133 | 134 | signals.answer_unthank.send(answer) 135 | return redirect(next_url or url_for('AnswerView:show', id=answer_id)) 136 | 137 | -------------------------------------------------------------------------------- /FlaskZhihu/models/signals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models import * 6 | from FlaskZhihu.constants import * 7 | from FlaskZhihu.signals import * 8 | from FlaskZhihu.helpers import use_signal 9 | 10 | @use_signal(question_answer_add) 11 | def update_question_answers_count(sender): 12 | assert isinstance(sender, Question) 13 | sender.answers_count = \ 14 | Answer.query.filter(Answer.question_id == sender.id).count() 15 | 16 | db.session.commit() 17 | 18 | @use_signal(question_follow) 19 | @use_signal(question_unfollow) 20 | def update_question_following_count(sender): 21 | assert isinstance(sender, Question) 22 | sender.following_count = \ 23 | UserOnQuestion.query.filter( 24 | db.and_(UserOnQuestion.question_id == sender.id, UserOnQuestion.follow == True) 25 | ).count() 26 | 27 | db.session.commit() 28 | 29 | 30 | @use_signal(question_comment_add) 31 | @use_signal(question_comment_delete) 32 | def update_question_comments_count(sender): 33 | assert isinstance(sender, Question) 34 | sender.comments_count = \ 35 | Comment.query.filter( 36 | Comment.question_id == sender.id 37 | ).count() 38 | 39 | db.session.commit() 40 | 41 | ############# answer ############ 42 | @use_signal(answer_comment_add) 43 | @use_signal(answer_comment_delete) 44 | def update_answer_comments_count(sender): 45 | assert isinstance(sender, Answer) 46 | sender.comments_count = \ 47 | Comment.query.filter( 48 | Comment.answer_id == sender.id 49 | ).count() 50 | 51 | db.session.commit() 52 | 53 | 54 | @use_signal(answer_voteup) 55 | @use_signal(answer_votedown) 56 | @use_signal(answer_cancel_vote) 57 | def update_answer_vote_count(sender): 58 | assert isinstance(sender, Answer) 59 | ops = UserOnAnswer.query.filter( 60 | db.and_( 61 | UserOnAnswer.answer_id == sender.id, 62 | UserOnAnswer.vote != VOTE_NONE, 63 | ) 64 | ).all() 65 | 66 | sender.voteup_count = len(filter(lambda x: x.vote == VOTE_UP, ops)) 67 | sender.votedown_count = len(filter(lambda x: x.vote == VOTE_DOWN, ops)) 68 | db.session.commit() 69 | 70 | 71 | @use_signal(answer_thank) 72 | @use_signal(answer_unthank) 73 | def update_answer_thanks_count(sender): 74 | assert isinstance(sender, Answer) 75 | thanks_count = UserOnAnswer.query.filter( 76 | db.and_( 77 | UserOnAnswer.answer_id == sender.id, 78 | UserOnAnswer.thank == THANK_ON, 79 | ) 80 | ).count() 81 | 82 | sender.thanks_count = thanks_count 83 | db.session.commit() 84 | 85 | 86 | ######### comment 87 | @use_signal(comment_voteup) 88 | @use_signal(comment_cancel_vote) 89 | def update_comment_vote_count(sender): 90 | assert isinstance(sender, Comment) 91 | voteup_count = UserOnComment.query.filter( 92 | db.and_( 93 | UserOnComment.comment_id == sender.id, 94 | UserOnComment.vote == VOTE_UP, 95 | ) 96 | ).count() 97 | 98 | sender.voteup_count = voteup_count 99 | db.session.commit() 100 | 101 | 102 | ############# collection ############ 103 | 104 | @use_signal(collection_answer_add) 105 | @use_signal(collection_answer_delete) 106 | def update_collection_answers_count(sender): 107 | assert isinstance(sender, Collection) 108 | answers_count = CollectionAndAnswer.query.filter( 109 | CollectionAndAnswer.collection_id == sender.id 110 | ).count() 111 | 112 | sender.answers_count = answers_count 113 | db.session.commit() 114 | 115 | 116 | @use_signal(collection_follow) 117 | @use_signal(collection_unfollow) 118 | def update_collection_following_count(sender): 119 | assert isinstance(sender, Collection) 120 | following_count = UserOnCollection.query.filter( 121 | db.and_( 122 | UserOnCollection.collection_id == sender.id, 123 | UserOnCollection.follow == FOLLOW_ON, 124 | ) 125 | ).count() 126 | 127 | sender.following_count = following_count 128 | db.session.commit() 129 | 130 | 131 | @use_signal(answer_voteup) 132 | @use_signal(answer_votedown) 133 | @use_signal(answer_cancel_vote) 134 | def update_user_voteup_count(sender): 135 | assert isinstance(sender, Answer) 136 | user = sender.user 137 | 138 | if user: 139 | voteup_count, votedown_count = db.session.execute( 140 | 'select sum(voteup_count), sum(votedown_count) from answer where user_id = :user_id' 141 | , dict(user_id=user.id) 142 | ).first() 143 | 144 | user.voteup_count = voteup_count or 0 145 | user.votedown_count = votedown_count or 0 146 | db.session.commit() 147 | 148 | 149 | @use_signal(answer_thank) 150 | @use_signal(answer_unthank) 151 | def update_user_thanks_count(sender): 152 | assert isinstance(sender, Answer) 153 | user = sender.user 154 | 155 | if user: 156 | thanks_count = db.session.execute( 157 | 'select sum(thanks_count) from answer where user_id = :user_id' 158 | , dict(user_id=user.id) 159 | ).first()[0] 160 | 161 | user.thanks_count = thanks_count or 0 162 | db.session.commit() 163 | 164 | -------------------------------------------------------------------------------- /FlaskZhihu/models/user/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'shn7798' 3 | 4 | from FlaskZhihu.extensions import db 5 | from FlaskZhihu.models.base import DateTimeMixin, FindByIdMixin 6 | from .operation import * 7 | from werkzeug.security import generate_password_hash, check_password_hash 8 | from flask.ext.sqlalchemy_cache import CachingQuery 9 | 10 | 11 | class UserOnUser(DateTimeMixin, FindByIdMixin, db.Model): 12 | __tablename__ = 'user_on_user' 13 | 14 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 15 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 16 | dest_user_id = db.Column('dest_user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 17 | follow = db.Column('follow', db.Integer) 18 | block = db.Column('block', db.Integer) 19 | 20 | 21 | class UserLoginMixin(): 22 | @property 23 | def is_authenticated(self): 24 | return True 25 | 26 | @property 27 | def is_active(self): 28 | return True 29 | 30 | @property 31 | def is_anonymous(self): 32 | return False 33 | 34 | def get_id(self): 35 | return self.id 36 | 37 | 38 | class User( 39 | UserOperationMixin, QuestionOperationMixin, AnswerOperationMixin, CollectionOperationMixin, 40 | CommentOperationMixin, 41 | DateTimeMixin, FindByIdMixin, 42 | UserLoginMixin, 43 | db.Model): 44 | 45 | __tablename__ = 'user' 46 | query_class = CachingQuery 47 | 48 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 49 | user_hashid = db.Column('user_hashid', db.String(32)) 50 | username = db.Column('username', db.String(45), unique=True) 51 | name = db.Column('name', db.String(45)) 52 | gender = db.Column('gender', db.Integer) 53 | description = db.Column('description', db.String(2048)) 54 | headline = db.Column('headline', db.String(200)) 55 | avatar_url = db.Column('avatar_url', db.String(200)) 56 | 57 | # count cache 58 | voteup_count = db.Column('voteup_count', db.Integer, server_default='0') 59 | votedown_count = db.Column('votedown_count', db.Integer, server_default='0') 60 | thanks_count = db.Column('thanks_count', db.Integer, server_default='0') 61 | 62 | answers = db.relationship(u'Answer', backref='user') 63 | collections = db.relationship(u'Collection', backref='user') 64 | comments = db.relationship(u'Comment', backref='user') 65 | questions = db.relationship(u'Question', backref='user') 66 | 67 | _password = db.Column('password', db.String(100), nullable=False) 68 | 69 | def _set_password(self, password): 70 | self._password = generate_password_hash(password) 71 | 72 | def _get_password(self): 73 | return self._password 74 | 75 | password = db.synonym("_password", 76 | descriptor=property(_get_password, 77 | _set_password)) 78 | 79 | op_on_answers = db.relationship(u'Answer', secondary='user_on_answer', backref='op_by_users') 80 | user_on_answer = db.relationship(u'UserOnAnswer', backref='user') 81 | 82 | op_on_collections = db.relationship(u'Collection', secondary='user_on_collection', backref='op_by_users') 83 | user_on_collection = db.relationship(u'UserOnCollection', backref='user') 84 | 85 | op_on_comments = db.relationship(u'Comment', secondary='user_on_comment', backref='op_by_users') 86 | user_on_comment = db.relationship(u'UserOnComment', backref='user') 87 | 88 | op_on_questions = db.relationship(u'Question', secondary='user_on_question', backref='op_by_users') 89 | user_on_question = db.relationship(u'UserOnQuestion', backref='user') 90 | 91 | op_on_topics = db.relationship(u'Topic', secondary='user_on_topic', backref='op_by_users') 92 | user_on_topic = db.relationship(u'UserOnTopic', backref='user') 93 | 94 | op_on_users = db.relationship(u'User', secondary='user_on_user', 95 | primaryjoin=id==UserOnUser.user_id, 96 | secondaryjoin=id==UserOnUser.dest_user_id, 97 | backref='op_by_users') 98 | 99 | user_on_dest_user = db.relationship(u'UserOnUser', foreign_keys=[UserOnUser.user_id], backref='user') 100 | dest_user_on_user = db.relationship(u'UserOnUser', foreign_keys=[UserOnUser.dest_user_id], backref='dest_user') 101 | 102 | @staticmethod 103 | def get_admin(cls): 104 | return User.query.filter(User.id==1).first_or_404() 105 | 106 | @staticmethod 107 | def get_user_by_hashid(hashid): 108 | return User.query.filter(User.user_hashid==hashid).first() 109 | 110 | def check_password(self, password): 111 | assert self._get_password() 112 | return check_password_hash(self._get_password(), password) 113 | 114 | def op_on_answer(self, answer, edit=False): 115 | return self._op_on_x(answer, UserOnAnswer, edit=edit) 116 | 117 | def op_on_collection(self, collection, edit=False): 118 | return self._op_on_x(collection, UserOnCollection, edit=edit) 119 | 120 | def op_on_comment(self, comment, edit=False): 121 | return self._op_on_x(comment, UserOnComment, edit=edit) 122 | 123 | def op_on_question(self, question, edit=False): 124 | return self._op_on_x(question, UserOnQuestion, edit=edit) 125 | 126 | def op_on_topic(self, topic, edit=False): 127 | return self._op_on_x(topic, UserOnTopic, edit=edit) 128 | 129 | def op_on_user(self, user, edit=False): 130 | return self._op_on_x(user, UserOnUser, op_fk=UserOnUser.dest_user_id, one_to_many=self.op_on_users, edit=edit) 131 | 132 | def _op_on_x(self, x_obj, op_table, op_fk=None, one_to_many=None, edit=False): 133 | x_name = x_obj.__class__.__name__ 134 | if not op_fk: 135 | op_fk_name = '%s_id' % x_name.lower() 136 | op_fk = getattr(op_table, op_fk_name) 137 | if not one_to_many: 138 | one_to_many_name = 'op_on_%ss' % x_name.lower() 139 | one_to_many = getattr(self, one_to_many_name) 140 | 141 | op = op_table.query.filter( 142 | db.and_(op_table.user_id == self.id, op_fk == x_obj.id) 143 | ).first() 144 | if not op: 145 | if edit: 146 | one_to_many.append(x_obj) 147 | return self._op_on_x(x_obj, op_table, op_fk=op_fk, one_to_many=one_to_many) 148 | else: 149 | return None 150 | else: 151 | return op 152 | 153 | def __repr__(self): 154 | return '<%s %r>' % (self.__class__.__name__, self.username) 155 | 156 | 157 | class UserOnAnswer(DateTimeMixin, FindByIdMixin, db.Model): 158 | __tablename__ = 'user_on_answer' 159 | 160 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 161 | answer_id = db.Column('answer_id', db.ForeignKey(u'answer.id'), nullable=False, index=True) 162 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 163 | follow = db.Column('follow', db.Integer) 164 | thank = db.Column('thank', db.Integer) 165 | vote = db.Column('vote', db.Integer) 166 | 167 | 168 | class UserOnCollection(DateTimeMixin, FindByIdMixin, db.Model): 169 | __tablename__ = 'user_on_collection' 170 | 171 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 172 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 173 | collection_id = db.Column('collection_id', db.ForeignKey(u'collection.id'), nullable=False, index=True) 174 | public = db.Column('public', db.Integer, server_default=db.text("'0'")) 175 | follow = db.Column('follow', db.Integer, server_default=db.text("'0'")) 176 | 177 | 178 | class UserOnComment(DateTimeMixin, FindByIdMixin, db.Model): 179 | __tablename__ = 'user_on_comment' 180 | 181 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 182 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 183 | comment_id = db.Column('comment_id', db.ForeignKey(u'comment.id'), nullable=False, index=True) 184 | vote = db.Column('vote', db.Integer) 185 | 186 | 187 | class UserOnQuestion(DateTimeMixin, FindByIdMixin, db.Model): 188 | __tablename__ = 'user_on_question' 189 | 190 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 191 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 192 | question_id = db.Column('question_id', db.ForeignKey(u'question.id'), nullable=False, index=True) 193 | follow = db.Column('follow', db.Integer) 194 | 195 | 196 | class UserOnTopic(DateTimeMixin, FindByIdMixin, db.Model): 197 | __tablename__ = 'user_on_topic' 198 | 199 | id = db.Column('id', db.Integer, primary_key=True, autoincrement=True) 200 | user_id = db.Column('user_id', db.ForeignKey(u'user.id'), nullable=False, index=True) 201 | topic_id = db.Column('topic_id', db.ForeignKey(u'topic.id'), nullable=False, index=True) 202 | follow = db.Column('follow', db.Integer) 203 | 204 | --------------------------------------------------------------------------------