├── .gitattributes ├── .gitignore ├── README.md ├── Screenshots ├── index.png ├── manage.png ├── message.png ├── new.png ├── post.png ├── posts.png ├── q.png ├── qa.png ├── topic.png └── user.png ├── blog ├── __init__.py ├── api_1_0 │ ├── __init__.py │ ├── answers.py │ ├── authentication.py │ ├── comments.py │ ├── decorators.py │ ├── errors.py │ ├── posts.py │ ├── questions.py │ └── users.py ├── auth │ ├── __init__.py │ ├── forms.py │ └── views.py ├── exceptions.py ├── main │ ├── __init__.py │ ├── errors.py │ ├── forms.py │ └── views.py ├── manage │ ├── __init__.py │ └── views.py ├── models.py ├── static │ ├── back │ │ └── background-image-html.jpg │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ └── main.css │ ├── fonts │ │ ├── default.css │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ ├── glyphicons-halflings-regular.woff2 │ │ ├── normalize.css │ │ └── style.css │ ├── icon │ │ ├── close-icon.png │ │ ├── github.ico │ │ ├── home-icon.png │ │ ├── outside-icon.png │ │ ├── settings-3-icon.png │ │ ├── shortcut.ico │ │ └── wechat.jpg │ ├── js │ │ ├── app.js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── bootstrap.min.js.map │ │ ├── default.css │ │ ├── jquery-3.2.1.min.js │ │ ├── jquery-3.2.1.slim.min.js │ │ ├── moment-with-locales.js │ │ ├── moment.js │ │ ├── normalize.css │ │ ├── npm.js │ │ ├── particles.js │ │ ├── particles.min.js │ │ ├── wangEditor.min.js │ │ └── wangEditor.min.js.map │ ├── topics │ │ ├── 123.jpg │ │ ├── 9.jpg │ │ ├── Bootstrap.jpg │ │ ├── HTML.jpg │ │ ├── Nginx.jpg │ │ ├── None.jpg │ │ ├── Python.jpg │ │ ├── ada.jpg │ │ ├── asd.jpg │ │ ├── sda.jpg │ │ ├── sf.jpg │ │ ├── ubuntu.jpg │ │ ├── 数据库.jpg │ │ ├── 编程.jpg │ │ └── 编程日记.jpg │ └── user │ │ └── avatar │ │ ├── 004874id1.jpg │ │ ├── 074822id1.jpg │ │ ├── 175167id1.jpg │ │ ├── 2017-11-21 16_00_52.043297#id=1.jpg │ │ ├── 224247id1.jpg │ │ ├── 241441#id=1.jpg │ │ ├── 355385id1.jpg │ │ ├── 625711id1.jpg │ │ ├── 635828id1.jpg │ │ ├── 649225id1.jpg │ │ └── dafulte.png └── templates │ ├── 404.html │ ├── 500.html │ ├── _comment.html │ ├── _formhelpers.html │ ├── _macros.html │ ├── _user_left.html │ ├── ask │ ├── ask_index.html │ └── question.html │ ├── base.html │ ├── home.html │ ├── index.html │ ├── manage │ ├── manage-index.html │ ├── manage-user.html │ ├── manage_base.html │ └── manage_topic.html │ ├── post_for_tag.html │ ├── search.html │ ├── topics │ ├── new_post.html │ ├── post.html │ ├── topic.html │ └── topics.html │ ├── user │ ├── _followed_all.html │ ├── _follower_all.html │ ├── _user_index_right.html │ ├── _user_post.html │ ├── messages.html │ ├── user_login.html │ └── user_register.html │ └── user_index.html ├── config.py ├── manage.py └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=python 2 | *.css linguist-language=python 3 | *.html linguist-language=python 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | .static_storage/ 57 | .media/ 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | .idea 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask-blog 2 | 3 | 这是一个使用python的flask框架编写的多人博客网站 4 | 5 | **** 6 | |Author|simonzhoup| 7 | |---|--- 8 | |E-mail|simonzhoup@gmail.com 9 | 10 | ### 功能 11 | 12 | * 注册/登录系统 13 | * 个人信息设置、头像上传 14 | * 用户动态展示 15 | * 站内消息功能 16 | * 用户关注系统/话题关注系统 17 | * 博客包含技术问答板块和主题文章板块 18 | * 文章评论 19 | * 问答系统 20 | * 文章标题和内容支持关键字搜索 21 | * 响应式布局,支持移动设备 22 | * 文章标签 23 | * 简单的后台管理系统(完成中... 24 | 25 | 26 | ## 截图 27 | 28 | ### 主页 29 | ![](/Screenshots/index.png) 30 | ### 主题 31 | ![](/Screenshots/topic.png) 32 | ### 文章 33 | ![](/Screenshots/post.png) 34 | ### 文章列表 35 | ![](/Screenshots/posts.png) 36 | ### 新文章 37 | ![](/Screenshots/new.png) 38 | ### 用户主页 39 | ![](/Screenshots/user.png) 40 | ### 消息 41 | ![](/Screenshots/message.png) 42 | ### 问答 43 | ![](/Screenshots/qa.png) 44 | ### 问题 45 | ![](/Screenshots/q.png) 46 | ### 后台管理 47 | ![](/Screenshots/manage.png) 48 | 49 | 50 | ## 如何使用 51 | 52 | * 创建数据库 53 | ```python 54 | #项目使用Flask-Migrate扩展, 用来管理数据库 55 | #创建迁移仓库 56 | $ python manage.py db init 57 | 58 | #自动创建迁移脚本 59 | $ python manage.py db migrate -m "initial migration" 60 | 61 | #更新数据库 62 | $ python manage.py db upgrade 63 | ``` 64 | 65 | 66 | * 启动程序 67 | ```python 68 | $ python manage.py runserver 69 | ``` 70 | 71 | 72 | ## 访问 73 | 浏览器打开 http://127.0.0.1:5000 74 | 75 | 76 | 77 | ## 其它 78 | 79 | * [点击这里](http://ocooc.cc) 可以查看网站效果 80 | -------------------------------------------------------------------------------- /Screenshots/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/index.png -------------------------------------------------------------------------------- /Screenshots/manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/manage.png -------------------------------------------------------------------------------- /Screenshots/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/message.png -------------------------------------------------------------------------------- /Screenshots/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/new.png -------------------------------------------------------------------------------- /Screenshots/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/post.png -------------------------------------------------------------------------------- /Screenshots/posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/posts.png -------------------------------------------------------------------------------- /Screenshots/q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/q.png -------------------------------------------------------------------------------- /Screenshots/qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/qa.png -------------------------------------------------------------------------------- /Screenshots/topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/topic.png -------------------------------------------------------------------------------- /Screenshots/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/Screenshots/user.png -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask, request, Response 3 | from config import config, Config 4 | from flask_bootstrap import Bootstrap 5 | from flask_sqlalchemy import SQLAlchemy 6 | from flask_login import LoginManager 7 | try: 8 | import pymysql 9 | pymysql.install_as_MySQLdb() 10 | except ImportError: 11 | pass 12 | 13 | basedir = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | 16 | bootstrap = Bootstrap() 17 | db = SQLAlchemy() 18 | login_manager = LoginManager() 19 | login_manager.session_protection = 'strong' 20 | login_manager.login_view = 'auth.user_login' 21 | 22 | 23 | def create_blog(): 24 | blog = Flask(__name__) 25 | blog.config.from_object(Config) 26 | Config.init_blog(blog) 27 | 28 | bootstrap.init_app(blog) 29 | db.init_app(blog) 30 | login_manager.init_app(blog) 31 | 32 | from .main import main as main_buleprint 33 | blog.register_blueprint(main_buleprint) 34 | 35 | from .auth import auth as auth_buleprint 36 | blog.register_blueprint(auth_buleprint, url_prefix='/auth') 37 | 38 | from .manage import manage as manage_buleprint 39 | blog.register_blueprint(manage_buleprint, url_prefix='/manage') 40 | 41 | from .api_1_0 import api as api_1_0_blueprint 42 | blog.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0') 43 | 44 | return blog 45 | -------------------------------------------------------------------------------- /blog/api_1_0/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | api = Blueprint('api', __name__) 4 | 5 | from . import authentication, posts, users, comments, errors, questions, answers 6 | -------------------------------------------------------------------------------- /blog/api_1_0/answers.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..models import Answer 3 | from flask import jsonify 4 | from .authentication import auth 5 | 6 | 7 | @api.route('/answers/') 8 | @auth.login_required 9 | def get_answers(): 10 | answers = Answer.query.all() 11 | return jsonify({'answers': [answer.to_json() for answer in answers]}) 12 | 13 | 14 | @api.route('/answers/') 15 | @auth.login_required 16 | def get_answer(id): 17 | answer = Answer.query.get_or_404(id) 18 | return jsonify(answer.to_json()) 19 | -------------------------------------------------------------------------------- /blog/api_1_0/authentication.py: -------------------------------------------------------------------------------- 1 | from flask_httpauth import HTTPBasicAuth 2 | auth = HTTPBasicAuth() 3 | from .errors import forbidden, unauthorized 4 | from . import api 5 | from ..models import AnonymousUser, User 6 | from flask import g, jsonify 7 | 8 | 9 | @api.before_request 10 | @auth.login_required 11 | def before_request(): 12 | if not g.current_user.is_anonymous and not g.current_user.ban: 13 | return forbidden('Unconfirmed account') 14 | 15 | 16 | @auth.verify_password 17 | def verify_password(email_or_token, password): 18 | if email_or_token == '': 19 | g.current_user = AnonymousUser() 20 | return True 21 | if password == '': 22 | g.current_user = User.verify_auth_token(email_or_token) 23 | g.token_used = True 24 | return g.current_user is not None 25 | user = User.query.filter_by(email=email_or_token).first() 26 | print(user) 27 | if not user: 28 | return False 29 | g.current_user = user 30 | g.token_used = False 31 | return user.verify_password(password) 32 | 33 | 34 | @auth.error_handler 35 | def auth_error(): 36 | return unauthorized('Invalid credentials') 37 | 38 | 39 | @api.route('/token') 40 | def get_token(): 41 | if g.current_user.is_anonymous() or g.token_used: 42 | return unauthorized('Invalid credentials') 43 | return jsonify({'token': g.current_user.generate_auth_token(expiration=3600), 'expiration': 3600}) 44 | -------------------------------------------------------------------------------- /blog/api_1_0/comments.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..models import Comments 3 | from flask import jsonify 4 | from .authentication import auth 5 | 6 | 7 | @api.route('/comments/') 8 | @auth.login_required 9 | def get_comments(): 10 | comments = Comments.query.all() 11 | return jsonify({'comments': [comment.to_json() for comment in comments]}) 12 | 13 | 14 | @api.route('/comments/') 15 | @auth.login_required 16 | def get_comment(id): 17 | comment = Comments.query.get_or_404(id) 18 | return jsonify(comment.to_json()) 19 | -------------------------------------------------------------------------------- /blog/api_1_0/decorators.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/api_1_0/decorators.py -------------------------------------------------------------------------------- /blog/api_1_0/errors.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..exceptions import ValidationError 3 | from flask import jsonify 4 | 5 | 6 | def forbidden(message): 7 | response = jsonify({'error': 'forbidden', 'message': message}) 8 | response.status_code = 403 9 | return response 10 | 11 | 12 | @api.errorhandler(ValidationError) 13 | def validation_error(e): 14 | return bad_request(e.args[0]) 15 | 16 | 17 | def unauthorized(message): 18 | response = jsonify({'error': 'unauthorized', 'message': message}) 19 | response.status_code = 401 20 | return response 21 | 22 | 23 | @api.errorhandler(404) 24 | def page_not_found(e): 25 | response = jsonify({'error': 'Notfound', 'message': 'Page Not Found'}) 26 | response.status_code = 404 27 | return response 28 | 29 | 30 | @api.errorhandler(500) 31 | def internal_server_error(e): 32 | response = jsonify({'error': 'Internal server error', 33 | 'message': 'Internal server error'}) 34 | response.status_code = 500 35 | return response 36 | -------------------------------------------------------------------------------- /blog/api_1_0/posts.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..models import Post, Comments 3 | from flask import jsonify 4 | from .authentication import auth 5 | 6 | 7 | @api.route('/posts/') 8 | def get_posts(): 9 | posts = Post.query.all() 10 | return jsonify({'posts': [post.to_json() for post in posts]}) 11 | 12 | 13 | @api.route('/posts/') 14 | @auth.login_required 15 | def get_post(id): 16 | post = Post.query.get_or_404(id) 17 | return jsonify(post.to_json()) 18 | 19 | 20 | @api.route('/posts/', methods=['POST']) 21 | @auth.login_required 22 | def new_post(): 23 | post = Post.from_json(request.json) 24 | post.author = g.current_user.id 25 | db.session.add(post) 26 | db.session.commit() 27 | return jsonify(post.to_json()), 201, {'Location': url_for('apt.get_post', id=post.id, _external=True)} 28 | 29 | 30 | @api.route('/posts/', methods=['PUT']) 31 | @auth.login_required 32 | def edit_post(id): 33 | post = Post.query.get_or_404(id) 34 | if g.current_user != post.author: 35 | return forbidden('Insufficient permissions') 36 | post.body = request.json.get('body', post.body) 37 | db.session.add(post) 38 | return jsonify(post.to_json()) 39 | 40 | 41 | @api.route('/posts//comments') 42 | @auth.login_required 43 | def get_post_comments(id): 44 | post = Post.query.get_or_404(id) 45 | comments = Comments.query.filter_by(post_id=id).all() 46 | return jsonify({'comments': [comment.to_json() for comment in comments]}) 47 | -------------------------------------------------------------------------------- /blog/api_1_0/questions.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..models import Question, Answer 3 | from flask import jsonify 4 | from .authentication import auth 5 | 6 | 7 | @api.route('/questions/') 8 | @auth.login_required 9 | def get_questions(): 10 | questions = Question.query.all() 11 | return jsonify({'questions': [question.to_json() for question in questions]}) 12 | 13 | 14 | @api.route('/questions/') 15 | @auth.login_required 16 | def get_question(id): 17 | question = Question.query.get_or_404(id) 18 | return jsonify({'question': question.to_json()}) 19 | 20 | 21 | @api.route('/questions//answers') 22 | @auth.login_required 23 | def get_question_answers(id): 24 | question = Question.query.get_or_404(id) 25 | answers = Answer.query.filter_by(q_id=id).all() 26 | return jsonify({'answers': [answer.to_json() for answer in answers]}) 27 | -------------------------------------------------------------------------------- /blog/api_1_0/users.py: -------------------------------------------------------------------------------- 1 | from . import api 2 | from ..models import User, Answer, Comments, Post, Question 3 | from flask import jsonify 4 | from .authentication import auth 5 | 6 | 7 | @api.route('/') 8 | @auth.login_required 9 | def get_index(): 10 | users = User.query.all() 11 | posts = Post.query.all() 12 | comments = Comments.query.all() 13 | questions = Question.query.all() 14 | answers = Answer.query.all() 15 | return jsonify({ 16 | 'users': [user.to_json() for user in users], 17 | 'posts': [post.to_json() for post in posts], 18 | 'comments': [comment.to_json() for comment in comments], 19 | 'questions': [question.to_json() for question in questions], 20 | 'answers': [answer.to_json() for answer in answers], 21 | }) 22 | 23 | 24 | @api.route('/users/') 25 | @auth.login_required 26 | def get_user(id): 27 | user = User.query.get_or_404(id) 28 | return jsonify(user.to_json()) 29 | 30 | 31 | @api.route('/users//posts/') 32 | @auth.login_required 33 | def get_user_posts(id): 34 | user = User.query.get_or_404(id) 35 | posts = user.post 36 | return jsonify({'posts': [post.to_json() for post in posts]}) 37 | 38 | 39 | @api.route('/users//questions/') 40 | @auth.login_required 41 | def get_user_questions(id): 42 | user = User.query.get_or_404(id) 43 | questions = user.question 44 | return jsonify({'questions': [question.to_json() for question in questions]}) 45 | -------------------------------------------------------------------------------- /blog/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | auth = Blueprint('auth', __name__) 4 | 5 | from . import views 6 | -------------------------------------------------------------------------------- /blog/auth/forms.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask_wtf import FlaskForm 3 | from wtforms import StringField, SubmitField, PasswordField, ValidationError 4 | from wtforms.validators import Required, Length, Email, EqualTo, Regexp 5 | from ..models import User 6 | 7 | 8 | class UserRegister(FlaskForm): 9 | username = StringField(u'用户名:', validators=[Required(), Length(1, 64), Regexp( 10 | '^[A-Za-z][A-Za-z0-9]*$', 0, 'Usernames must have only letters,numbers,')]) 11 | email = StringField(u'邮箱:', validators=[ 12 | Required(), Email(), Length(1, 64)]) 13 | password = PasswordField( 14 | u'密码:', validators=[Required(), EqualTo('password2', message=u'密码不一致')]) 15 | password2 = PasswordField(u'确认密码:', validators=[Required()]) 16 | submit = SubmitField(u'注册') 17 | 18 | def validate_username(self, field): 19 | if User.query.filter_by(username=field.data).first(): 20 | raise ValidationError(u'用户名已存在') 21 | 22 | def validate_email(self, field): 23 | if User.query.filter_by(email=field.data).first(): 24 | raise ValidationError(u'此邮箱已经注册过了' ) 25 | 26 | 27 | class UserLogin(FlaskForm): 28 | email = StringField(u'邮箱:', validators=[ 29 | Required(), Email(), Length(1, 64)]) 30 | password = PasswordField( 31 | u'密码:', validators=[Required(), Length(1, 64)]) 32 | submit = SubmitField(u'登录') 33 | -------------------------------------------------------------------------------- /blog/auth/views.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask import render_template, redirect, url_for, flash 3 | from . import auth 4 | from .forms import UserRegister, UserLogin 5 | from ..main.forms import SearchForm 6 | from ..models import User 7 | from .. import db 8 | from flask_login import login_user, login_required, logout_user 9 | 10 | 11 | @auth.route('/register', methods=['GET', 'POST']) 12 | def user_register(): 13 | form = UserRegister() 14 | if form.validate_on_submit(): 15 | user = User(email=form.email.data, username=form.username.data, 16 | password=form.password.data) 17 | db.session.add(user) 18 | db.session.commit() 19 | user.admin() 20 | flash('账号注册成功,现在您可以登录了') 21 | return redirect(url_for('main.user_login')) 22 | return render_template('user/user_register.html', form=form, search=SearchForm()) 23 | 24 | 25 | @auth.route('/login', methods=['GET', 'POST']) 26 | def user_login(): 27 | form = UserLogin() 28 | if form.validate_on_submit(): 29 | user = User.query.filter_by(email=form.email.data).first() 30 | if user and user.verify_password(form.password.data): 31 | login_user(user) 32 | flash(u'登录成功') 33 | return redirect(url_for('main.home')) 34 | flash('Invalid username or password.') 35 | return render_template('user/user_login.html', form=form, search=SearchForm()) 36 | 37 | 38 | @auth.route('/logout') 39 | @login_required 40 | def user_logout(): 41 | logout_user() 42 | flash('You have been logged out.') 43 | return redirect(url_for('main.home')) 44 | -------------------------------------------------------------------------------- /blog/exceptions.py: -------------------------------------------------------------------------------- 1 | class ValidationError(ValueError): 2 | pass 3 | -------------------------------------------------------------------------------- /blog/main/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | main = Blueprint('main', __name__) 4 | 5 | from . import views, errors 6 | -------------------------------------------------------------------------------- /blog/main/errors.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request 2 | from . import main 3 | from .forms import SearchForm 4 | 5 | 6 | @main.app_errorhandler(404) 7 | def page_not_found(e): 8 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 9 | response = jsonify({'error': 'not found'}) 10 | response.status_code = 404 11 | return response 12 | return render_template('404.html', search=SearchForm()), 404 13 | 14 | 15 | @main.app_errorhandler(500) 16 | def error_500(e): 17 | if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: 18 | response = jsonify({'error': 'Internal server error'}) 19 | response.status_code = 500 20 | return response 21 | return render_template('500.html', search=SearchForm()), 500 22 | -------------------------------------------------------------------------------- /blog/main/forms.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from flask_wtf import FlaskForm 3 | from flask_wtf.file import FileField, FileAllowed, FileRequired 4 | from wtforms import StringField, SubmitField, PasswordField, ValidationError, TextAreaField, SelectField 5 | from wtforms.validators import Required, Length, Email, EqualTo, Regexp 6 | from flask_login import current_user 7 | from ..models import Topic 8 | from flask_uploads import UploadSet, IMAGES 9 | 10 | images = UploadSet('images', IMAGES) 11 | 12 | 13 | class UserInfo(FlaskForm): 14 | realname = StringField('真实姓名', validators=[Length(1, 64)]) 15 | location = StringField('居住地', validators=[Length(1, 64)]) 16 | abortme = TextAreaField('个人介绍', validators=[Length(1, 64)]) 17 | submit = SubmitField('提交') 18 | 19 | 20 | class UserPasswd(FlaskForm): 21 | oldpassword = PasswordField('旧的密码', validators=[Required()]) 22 | newpassword = PasswordField( 23 | '新的密码', validators=[Required(), EqualTo('newpassword2', message=u'密码不一致')]) 24 | newpassword2 = PasswordField('确认新密码', validators=[Required()]) 25 | submit = SubmitField('提交') 26 | 27 | def validate_oldpassword(self, field): 28 | if not current_user.verify_password(field.data): 29 | raise ValidationError('密码输入有误') 30 | 31 | 32 | class Avatar(FlaskForm): 33 | avatar = FileField('上传头像', validators=[ 34 | FileRequired(), FileAllowed(['jpg', 'png'], '只能上传图片')]) 35 | submit = SubmitField('提交') 36 | 37 | 38 | class TopicForm(FlaskForm): 39 | topic_name = StringField('话题名', validators=[Required(), Length(1, 64)]) 40 | topic_info = TextAreaField('话题描述') 41 | topic_img = FileField('话题图片', validators=[ 42 | FileRequired(), FileAllowed(['jpg', 'png'], '只能上传图片')]) 43 | submit1 = SubmitField('提交') 44 | 45 | 46 | class PostForm(FlaskForm): 47 | head = StringField('标题', validators=[Required(), Length(1, 64)]) 48 | postbody = TextAreaField('内容', validators=[Required()]) 49 | tag = StringField( 50 | '标签', render_kw={'placeholder': '标签必须以’#‘符号开头,空格结尾。可以同时添加多个标签。'}) 51 | topic = SelectField('所属话题', coerce=int) 52 | submit = SubmitField('提交') 53 | 54 | def __init__(self, *args, **kwargs): 55 | super(PostForm, self).__init__(*args, **kwargs) 56 | self.topic.choices = [(t.id, t.topic) 57 | for t in Topic.query.order_by(Topic.id).all()] 58 | 59 | def validate_tag(self, field): 60 | if '#' not in field.data or ' ' not in field.data: 61 | raise ValidationError('标签必须以’#‘符号开头,空格结尾。') 62 | 63 | 64 | class EditTopic(FlaskForm): 65 | topic_info = TextAreaField('话题描述') 66 | submit1 = SubmitField('提交') 67 | 68 | 69 | class CommentForm(FlaskForm): 70 | body = TextAreaField('内容') 71 | submit = SubmitField('发表') 72 | 73 | 74 | class SearchForm(FlaskForm): 75 | key1 = StringField('搜索', validators=[Length(1, 64)], render_kw={ 76 | 'placeholder': '请输入关键字'}) 77 | search = SubmitField('搜索') 78 | 79 | 80 | class AskForm(FlaskForm): 81 | title = StringField('标题', validators=[Required(), Length(1, 64)]) 82 | body = TextAreaField('问题描述', validators=[Required()]) 83 | submit1 = SubmitField('提交') 84 | 85 | 86 | class ManageSearch(FlaskForm): 87 | key = StringField('', validators=[Length(1, 64)]) 88 | search = SubmitField('搜索') 89 | -------------------------------------------------------------------------------- /blog/main/views.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 视图 3 | from flask import render_template, make_response, redirect, url_for, request, flash, abort, g, current_app, json 4 | from datetime import datetime 5 | from . import main 6 | from flask_login import current_user, login_required 7 | from ..models import User, Follow, Topic, Post, TopicFollows, Comments, Messages, Answer, Question, Tag, PostTag 8 | from .forms import UserInfo, UserPasswd, Avatar, TopicForm, PostForm, EditTopic, CommentForm, SearchForm, AskForm 9 | from .. import db 10 | from sqlalchemy import and_, or_ 11 | from flask_sqlalchemy import get_debug_queries 12 | import re 13 | import random 14 | from config import Config 15 | import logging 16 | from logging.handlers import SMTPHandler 17 | 18 | 19 | @main.context_processor 20 | def utility_processor(): 21 | '''文处理器,在每个视图函数调用之前运行,可以向模板中传递参数,以字典方式''' 22 | questions = Question.query.order_by(Question.timestamp).limit(5).all() 23 | tags = Tag.query.all() 24 | random.shuffle(tags) 25 | return dict(tags=tags, random=random, search=SearchForm(), questions=questions[::-1]) 26 | 27 | 28 | @main.route('/search/', methods=['GET', 'POST']) 29 | def Searchs(xxx): 30 | sss = '%' + xxx + '%' 31 | posts = Post.query.filter( 32 | or_(Post.body.like(sss), Post.head.like(sss))).all() 33 | return render_template('search.html', posts=posts) 34 | 35 | 36 | @main.before_app_request 37 | def before_request(): 38 | search = SearchForm() 39 | if not current_user.is_ban(): 40 | abort(404) 41 | elif search.validate_on_submit(): 42 | s = search.key1.data 43 | # return Search(s) 44 | return redirect(url_for('main.Searchs', xxx=s)) 45 | elif current_user.is_authenticated: 46 | current_user.ping() 47 | current_user.noread_messages = (Messages.query.filter_by( 48 | the_id=current_user.id).filter_by(is_read=False).count() + Messages.query.filter_by( 49 | post_author_id=current_user.id).filter_by(is_read=False).count() + Messages.query.filter_by( 50 | q_author_id=current_user.id).filter_by(is_read=False).count()) 51 | db.session.add(current_user) 52 | 53 | 54 | @main.route('/user/seting/', methods=['GET', 'POST']) 55 | @login_required 56 | def user_seting(): 57 | index = request.cookies.get('xx', 'seting1') 58 | form = UserInfo() 59 | if index == 'seting1': 60 | form = UserInfo() 61 | if form.validate_on_submit(): 62 | current_user.name = form.realname.data 63 | current_user.location = form.location.data 64 | current_user.abortme = form.abortme.data 65 | db.session.add(current_user) 66 | flash('资料已更新') 67 | return redirect(url_for('main.info_resp')) 68 | form.realname.data = current_user.name 69 | form.location.data = current_user.location 70 | form.abortme.data = current_user.abortme 71 | # return render_template('index.html', user=current_user, index=index, 72 | # form=form, title='个人信息', id=id) 73 | if index == 'seting2': 74 | form = UserPasswd() 75 | if form.validate_on_submit(): 76 | current_user.password = form.newpassword.data 77 | db.session.add(current_user) 78 | flash('密码已更改') 79 | return redirect(url_for('main.passwd_resp')) 80 | if index == 'seting3': 81 | form = Avatar() 82 | if form.validate_on_submit(): 83 | img = form.avatar.data 84 | filename = str(datetime.now()).split( 85 | '.')[-1] + 'id%d' % current_user.id 86 | img.save('blog/static/user/avatar/%s.jpg' % filename) 87 | current_user.avatar = '%s.jpg' % filename 88 | db.session.add(current_user) 89 | flash('头像修改成功') 90 | return redirect(url_for('main.email_resp')) 91 | # return render_template('index.html', user=current_user, index=index, 92 | # form=form, title='头像设置', id=id) 93 | 94 | return render_template('index.html', user=current_user, index=index, form=form, title='用户设置', search=SearchForm()) 95 | 96 | 97 | @main.route('/', methods=['GET', 'POST']) 98 | def home(): 99 | page = request.args.get('page', 1, type=int) 100 | pagination = Post.query.order_by(Post.timestamp.desc()).paginate( 101 | page, per_page=10, error_out=False) 102 | posts = pagination.items[::-1] 103 | uc = User.query.count() 104 | pc = Post.query.count() 105 | qc = Question.query.count() 106 | ac = Answer.query.count() 107 | cc = Comments.query.count() 108 | data = { 109 | 'uc': uc, 110 | 'pc': pc, 111 | 'qc': qc, 112 | 'ac': ac, 113 | 'cc': cc, 114 | 'pagination': pagination, 115 | } 116 | return render_template('home.html', data=data, user=current_user, posts=posts[::-1], Tag=Tag, random=random) 117 | 118 | # 用户主页 119 | 120 | 121 | @main.route('/user/') 122 | def user_index(id): 123 | user = User.query.get_or_404(id) 124 | if not user.ban: 125 | abort(404) 126 | c1 = Follow.query.filter_by(follower_id=user.id).count() 127 | c2 = Follow.query.filter_by(followed_id=user.id).count() 128 | show = request.cookies.get('show', '1') 129 | page = request.args.get('page', 1, type=int) 130 | if show == '1': 131 | q = Post 132 | elif show == '2': 133 | q = Answer 134 | elif show == '3': 135 | q = Comments 136 | elif show == '4': 137 | q = Question 138 | pagination = q.query.filter_by( 139 | author=user.id).order_by(q.timestamp).paginate( 140 | page, per_page=10, error_out=False) 141 | posts = pagination.items[::-1] 142 | return render_template('index.html', show=show, user=user, Topic=Topic, pagination=pagination, posts=posts, index='index,info', c1=c1, c2=c2, Comments=Comments, Post=Post, Question=Question) 143 | 144 | 145 | @main.route('/all/') 146 | def show_all(id): 147 | resp = make_response(redirect(url_for('.user_index', id=id))) 148 | resp.set_cookie('show', '0', max_age=30 * 24 * 60 * 60) 149 | return resp 150 | 151 | 152 | @main.route('/posts/') 153 | def show_post(id): 154 | resp = make_response(redirect(url_for('.user_index', id=id))) 155 | resp.set_cookie('show', '1', max_age=30 * 24 * 60 * 60) 156 | return resp 157 | 158 | 159 | @main.route('/answer/') 160 | def show_answer(id): 161 | resp = make_response(redirect(url_for('.user_index', id=id))) 162 | resp.set_cookie('show', '2', max_age=30 * 24 * 60 * 60) 163 | return resp 164 | 165 | 166 | @main.route('/comment/') 167 | def show_comment(id): 168 | resp = make_response(redirect(url_for('.user_index', id=id))) 169 | resp.set_cookie('show', '3', max_age=30 * 24 * 60 * 60) 170 | return resp 171 | 172 | 173 | @main.route('/question/') 174 | def show_question(id): 175 | resp = make_response(redirect(url_for('.user_index', id=id))) 176 | resp.set_cookie('show', '4', max_age=30 * 24 * 60 * 60) 177 | return resp 178 | 179 | # 用户设置 180 | 181 | 182 | @main.route('/seting/info') 183 | @login_required 184 | def info_resp(): 185 | resp = make_response(redirect(url_for('.user_seting'))) 186 | resp.set_cookie('xx', 'seting1', max_age=60) 187 | return resp 188 | 189 | 190 | @main.route('/seting/passwd') 191 | @login_required 192 | def passwd_resp(): 193 | resp = make_response(redirect(url_for('.user_seting'))) 194 | resp.set_cookie('xx', 'seting2', max_age=60) 195 | return resp 196 | 197 | 198 | @main.route('/seting/email') 199 | @login_required 200 | def email_resp(): 201 | resp = make_response(redirect(url_for('.user_seting'))) 202 | resp.set_cookie('xx', 'seting3', max_age=60) 203 | return resp 204 | 205 | # 所有关注者界面 206 | 207 | 208 | @main.route('/user/follower-all/') 209 | def user_follower_all(id): 210 | user = User.query.get_or_404(id) 211 | all_follower = user.all_follower() 212 | return render_template('index.html', user=user, index='follower-all,index', all_follower=all_follower) 213 | 214 | # 所有关注的人界面 215 | 216 | 217 | @main.route('/user/followed-all/') 218 | def user_followed_all(id): 219 | user = User.query.get_or_404(id) 220 | all_followed = user.all_follow() 221 | return render_template('index.html', user=user, index='followed-all,index', all_followed=all_followed) 222 | 223 | # 关注用户 224 | 225 | 226 | @main.route('/user/follow/') 227 | @login_required 228 | def user_follow(id): 229 | user = User.query.get_or_404(id) 230 | current_user.follow(user) 231 | flash('关注成功') 232 | return redirect(url_for('main.user_index', id=user.id)) 233 | 234 | # 取消关注用户 235 | 236 | 237 | @main.route('/user/unfollow/') 238 | @login_required 239 | def user_unfollow(id): 240 | user = User.query.get_or_404(id) 241 | current_user.unfollow(user) 242 | flash('已取消关注') 243 | return redirect(url_for('main.user_index', id=user.id)) 244 | 245 | # 所有话题 246 | 247 | 248 | @main.route('/topics', methods=['GET', 'POST']) 249 | def topics(): 250 | topics = Topic.query.filter_by(activation=True).all() 251 | form = TopicForm() 252 | if current_user.is_authenticated and form.validate_on_submit(): 253 | t = Topic(topic=form.topic_name.data, 254 | info=form.topic_info.data, author=current_user.id) 255 | db.session.add(t) 256 | db.session.commit() 257 | img = form.topic_img.data 258 | img.save('blog/static/topics/%s.jpg' % t.id) 259 | t.img = '%s.jpg' % t.id 260 | db.session.add(t) 261 | return redirect(url_for('main.topics')) 262 | return render_template('topics/topics.html', form=form, topics=topics, Post=Post) 263 | 264 | # 话题 265 | 266 | 267 | @main.route('/topics/', methods=['GET', 'POST']) 268 | def topic(topic): 269 | form = EditTopic() 270 | t = Topic.query.filter_by(topic=topic).first() 271 | if not t or not t.activation: 272 | abort(404) 273 | if form.validate_on_submit(): 274 | t.info = form.topic_info.data 275 | db.session.add(t) 276 | return redirect(url_for('main.topic', topic=t.topic)) 277 | t.ping() 278 | form.topic_info.data = t.info 279 | f = None 280 | if current_user.is_authenticated: 281 | f = current_user.is_follow_t(t) 282 | return render_template('topics/topic.html', t=t, f=f, title=topic, Post=Post, form=form, Comments=Comments) 283 | 284 | # 新帖子 285 | 286 | 287 | @main.route('/new-post', methods=['GET', 'POST']) 288 | @login_required 289 | def new_post(): 290 | form = PostForm() 291 | if Topic.query.all() == []: 292 | flash('还没有任何主题,添加一个吧。') 293 | return redirect(url_for('main.topics')) 294 | if request.method == 'POST': 295 | p = Post(author=current_user.id, tpoic=form.topic.data, 296 | head=form.head.data, body=form.postbody.data) 297 | # data = json.loads(request.form.get('data')) 298 | # body = data['body'] 299 | db.session.add(p) 300 | db.session.commit() 301 | if form.tag.data: 302 | tags = re.findall(r'#(.+?)\s', form.tag.data) 303 | for tag in tags: 304 | t = Tag.query.filter_by(tag_name=tag).first() 305 | if t: 306 | pt = PostTag(post_id=p.id, tags_id=t.id) 307 | db.session.add(pt) 308 | else: 309 | t = Tag(tag_name=tag) 310 | db.session.add(t) 311 | db.session.commit() 312 | pt = PostTag(post_id=p.id, tags_id=t.id) 313 | db.session.add(pt) 314 | return redirect(url_for('main.post', id=p.id)) 315 | topics = Topic.query.order_by(Topic.id).all() 316 | return render_template('topics/new_post.html', form=form, topics=topics) 317 | 318 | 319 | @main.route('/delete-post/') 320 | @login_required 321 | def delete_post(id): 322 | '''删除帖子''' 323 | p = Post.query.get_or_404(id) 324 | topic = p.topic.topic 325 | user = User.query.filter_by(id=p.author).first() 326 | if user.is_self(current_user): 327 | db.session.delete(p) 328 | cs = Comments.query.filter_by(post_id=p.id).all() 329 | for c in cs: 330 | db.session.delete(c) 331 | db.session.commit() 332 | flash('帖子已删除') 333 | return redirect(url_for('main.topic', topic=topic)) 334 | else: 335 | flash('无法删除') 336 | return redirect(url_for('main.topic', topic=topic)) 337 | # 帖子 338 | 339 | 340 | @main.route('/post/', methods=['GET', 'POST']) 341 | def post(id): 342 | commentform = CommentForm() 343 | p = Post.query.get_or_404(id) 344 | if current_user.is_authenticated and request.method == 'POST': 345 | comment = Comments(author=current_user.id, 346 | post_id=p.id, body=commentform.body.data, post_author_id=p.author) 347 | db.session.add(comment) 348 | db.session.commit() 349 | x = re.match('.*@(.+) .*', commentform.body.data) 350 | if x: 351 | username = x.group(1) 352 | u = User.query.filter_by(username=username).first() 353 | if u: 354 | mes = Messages(post_author_id=p.author, the_id=u.id, from_id=current_user.id, 355 | comment_id=p.id, post_id=p.id, body_id=comment.id) 356 | db.session.add(mes) 357 | else: 358 | mes = Messages(post_author_id=p.author, from_id=current_user.id, 359 | post_id=p.id, body_id=comment.id) 360 | db.session.add(mes) 361 | return redirect(url_for('main.post', id=p.id)) 362 | p.ping() 363 | author = User.query.filter_by(id=p.author).first() 364 | comments = Comments.query.filter_by(post_id=p.id).all() 365 | return render_template('topics/post.html', p=p, author=author, commentform=commentform, comments=comments) 366 | 367 | 368 | @main.route('/topic/follow/') 369 | @login_required 370 | def follow_topic(topic): 371 | t = Topic.query.filter_by(topic=topic).first() 372 | if t is None: 373 | abort(404) 374 | current_user.follow_t(t) 375 | return redirect(url_for('main.topic', topic=topic)) 376 | 377 | 378 | @main.route('/topic/unfollow/') 379 | @login_required 380 | def unfollow_topic(topic): 381 | t = Topic.query.filter_by(topic=topic).first() 382 | if t is None: 383 | abort(404) 384 | current_user.unfollow_t(t) 385 | return redirect(url_for('main.topic', topic=topic)) 386 | 387 | 388 | @main.route('/user/messages') 389 | @login_required 390 | def messages(): 391 | # messages = Comments.query.filter_by( 392 | # post_author_id=current_user.id).all()[::-1] 393 | post_ms = Messages.query.filter_by( 394 | post_author_id=current_user.id).filter_by(is_read=False).all() 395 | at_ms = Messages.query.filter_by( 396 | the_id=current_user.id).filter_by(is_read=False).all() 397 | a_ms = Messages.query.filter_by( 398 | q_author_id=current_user.id).filter_by(is_read=False).all() 399 | read_ms = Messages.query.filter_by(is_read=True).all() 400 | return render_template('user/messages.html', a_ms=a_ms, Question=Question, post_ms=post_ms, at_ms=at_ms, User=User, Post=Post, Comments=Comments, read_ms=read_ms) 401 | 402 | 403 | @main.route('/user/read_all_messages') 404 | @login_required 405 | def read_all_messages(): 406 | Messages.read_all(current_user.id) 407 | return redirect(url_for('main.messages')) 408 | 409 | 410 | @main.route('/user/messages/read/') 411 | @login_required 412 | def read_message(id): 413 | ms = Messages.query.get_or_404(id) 414 | ms.read() 415 | return redirect(url_for('main.post', id=ms.post_id)) 416 | 417 | 418 | @main.route('/user/messagess/read/') 419 | @login_required 420 | def read_messages(id): 421 | ms = Messages.query.get_or_404(id) 422 | ms.read() 423 | return redirect(url_for('main.question', id=ms.q_id)) 424 | 425 | 426 | @main.route('/ask', methods=['GET', 'POST']) 427 | def ask(): 428 | qs = Question.query.order_by(Question.timestamp).all() 429 | form = AskForm() 430 | if request.method == 'POST': 431 | q = Question(author=current_user.id, 432 | title=form.title.data, body=form.body.data) 433 | db.session.add(q) 434 | flash('提问已发布') 435 | return redirect(url_for('main.ask')) 436 | return render_template('ask/ask_index.html', qs=qs, form=form, User=User) 437 | 438 | 439 | @main.route('/sak/question/', methods=['GET', 'POST']) 440 | def question(id): 441 | q = Question.query.get_or_404(id) 442 | q.read() 443 | commentform = CommentForm() 444 | answers = Answer.query.filter_by(q_id=id).all() 445 | if current_user.is_authenticated and commentform.validate_on_submit(): 446 | a = Answer(q_id=id, q_author_id=q.author, 447 | author=current_user.id, body=commentform.body.data) 448 | db.session.add(a) 449 | x = re.match('.*@(.+)\s.*', commentform.body.data) 450 | if x: 451 | username = x.group(1) 452 | u = User.query.filter_by(username=username).first() 453 | if u: 454 | mes = Messages(q_author_id=q.author, the_id=u.id, from_id=current_user.id, 455 | comment_id=q.id, q_id=q.id, body_id=a.id) 456 | db.session.add(mes) 457 | else: 458 | mes = Messages(q_author_id=q.author, from_id=current_user.id, 459 | q_id=q.id, body_id=a.id) 460 | db.session.add(mes) 461 | return redirect(url_for('main.question', id=id)) 462 | return render_template('ask/question.html', q=q, User=User, commentform=commentform, answers=answers) 463 | 464 | 465 | @main.route('/sak/question/app/%') 466 | @login_required 467 | def app_answer(id, q_id): 468 | a = Answer.query.get_or_404(id) 469 | if a: 470 | a.up() 471 | return redirect(url_for('main.question', id=q_id)) 472 | 473 | 474 | @main.route('/sak/question/opp/%') 475 | @login_required 476 | def opp_answer(id, q_id): 477 | a = Answer.query.get_or_404(id) 478 | if a: 479 | a.down() 480 | return redirect(url_for('main.question', id=q_id)) 481 | 482 | 483 | @main.route('/sak/question/answer') 484 | @login_required 485 | def is_answer(id, ad): 486 | q = Question.query.get_or_404(id) 487 | q.answer = ad 488 | a = Answer.query.get_or_404(ad) 489 | a.adopt = True 490 | db.session.add(q) 491 | db.session.add(a) 492 | return redirect(url_for('main.question', id=id)) 493 | 494 | 495 | @main.route('/post/tag/', methods=['GET', 'POST']) 496 | def post_for_tag(tag): 497 | tag = Tag.query.filter_by(id=tag).first() 498 | if not tag: 499 | abort(404) 500 | posts = tag.posts.all() 501 | tag = tag.tag_name 502 | return render_template('post_for_tag.html', posts=posts, tag=tag) 503 | -------------------------------------------------------------------------------- /blog/manage/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | manage = Blueprint('manage', __name__) 4 | 5 | from . import views 6 | -------------------------------------------------------------------------------- /blog/manage/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, redirect, url_for, flash, request 2 | from flask_login import current_user, login_required 3 | from ..models import admin_required, User, Post, Comments, Question, Topic 4 | from ..main.forms import ManageSearch 5 | from . import manage 6 | from .. import db 7 | from sqlalchemy import and_, or_ 8 | 9 | 10 | @manage.route('/') 11 | @admin_required 12 | def index(): 13 | users = User.query.order_by(User.last_seen).limit(3).all() 14 | posts = Post.query.order_by(Post.timestamp).limit(3).all() 15 | questions = Question.query.order_by(Question.timestamp).limit(3).all() 16 | comments = Comments.query.order_by(Comments.timestamp).limit(3).all() 17 | return render_template('manage/manage-index.html', info='status', users=users[::-1], posts=posts[::-1], 18 | Comments=Comments, questions=questions[::-1], comments=comments[::-1]) 19 | 20 | 21 | @manage.route('/user',methods=['GET','POST']) 22 | @admin_required 23 | def manage_user(): 24 | s = ManageSearch() 25 | ids = request.args.get('view', 'n') 26 | if s.validate_on_submit(): 27 | key = s.key.data 28 | ids = 's' 29 | users = User.query.filter_by(username=key).all() 30 | elif ids == 'n': 31 | users = User.query.order_by(User.last_seen).filter_by(ban=True).all() 32 | elif ids == 'd': 33 | users = User.query.order_by(User.last_seen).filter_by(ban=False).all() 34 | return render_template('manage/manage-user.html', info='user', users=users, User=User, ids=ids, s=ManageSearch()) 35 | 36 | 37 | @manage.route('/user/delete') 38 | @admin_required 39 | def delete_user(): 40 | ids = request.args.get('ids') 41 | users = ids.split(',') 42 | for user in users: 43 | u = User.query.filter_by(username=user).first() 44 | u.ban = False 45 | db.session.add(u) 46 | return redirect(url_for('manage.manage_user')) 47 | 48 | 49 | @manage.route('/user/undelete') 50 | @admin_required 51 | def undelete_user(): 52 | ids = request.args.get('ids') 53 | users = ids.split(',') 54 | for user in users: 55 | u = User.query.filter_by(username=user).first() 56 | u.ban = True 57 | db.session.add(u) 58 | return redirect(url_for('manage.manage_user')) 59 | 60 | 61 | @manage.route('/topic', methods=['GET', 'POST']) 62 | @admin_required 63 | def manage_topic(): 64 | s = ManageSearch() 65 | ids = request.args.get('view', 'n') 66 | if s.validate_on_submit(): 67 | key = s.key.data 68 | ids = 's' 69 | topics = Topic.query.filter(or_(Topic.topic.like( 70 | key), Topic.info.like(key))).order_by(Topic.timestamp).all() 71 | elif ids == 'n': 72 | topics = Topic.query.order_by( 73 | Topic.timestamp).filter_by(activation=True).all() 74 | elif ids == 'd': 75 | topics = Topic.query.order_by( 76 | Topic.timestamp).filter_by(activation=False).all() 77 | return render_template('manage/manage_topic.html', info='topic', topics=topics, ids=ids, s=ManageSearch()) 78 | 79 | 80 | @manage.route('/topic/delete') 81 | @admin_required 82 | def delete_topic(): 83 | ids = request.args.get('ids') 84 | topics = ids.split(',') 85 | print(topics) 86 | for topic in topics: 87 | t = Topic.query.filter_by(id=int(topic)).first() 88 | t.activation = False 89 | db.session.add(t) 90 | return redirect(url_for('manage.manage_topic')) 91 | 92 | 93 | @manage.route('/topic/undelete') 94 | @admin_required 95 | def undelete_topic(): 96 | ids = request.args.get('ids') 97 | topics = ids.split(',') 98 | for topic in topics: 99 | t = Topic.query.filter_by(id=int(topic)).first() 100 | t.activation = True 101 | db.session.add(t) 102 | return redirect(url_for('manage.manage_topic')) 103 | -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from . import db, login_manager 3 | from werkzeug.security import generate_password_hash, check_password_hash 4 | from flask_login import UserMixin, current_user, AnonymousUserMixin 5 | from datetime import datetime 6 | from sqlalchemy import and_, or_ 7 | from flask import abort, url_for 8 | from functools import wraps 9 | from .exceptions import ValidationError 10 | 11 | 12 | def admin_required(f): 13 | @wraps(f) 14 | def warpper(*args, **kw): 15 | if not current_user.is_admin(): 16 | abort(404) 17 | return f(*args, **kw) 18 | return warpper 19 | 20 | 21 | @login_manager.user_loader 22 | def load_user(user_id): 23 | return User.query.get(int(user_id)) 24 | 25 | 26 | class Follow(db.Model): 27 | __tablename__ = 'follows' 28 | follower_id = db.Column( 29 | db.Integer, db.ForeignKey('users.id'), primary_key=True) 30 | followed_id = db.Column(db.Integer, db.ForeignKey( 31 | 'users.id'), primary_key=True) 32 | timestamp = db.Column(db.DateTime(), default=datetime.now) 33 | 34 | 35 | class User(UserMixin, db.Model): 36 | __tablename__ = 'users' 37 | id = db.Column(db.Integer, primary_key=True) 38 | username = db.Column(db.String(64), unique=True, index=True) 39 | email = db.Column(db.String(64), unique=True) 40 | password_hash = db.Column(db.String(128)) 41 | name = db.Column(db.String(64)) 42 | location = db.Column(db.String(64)) 43 | abortme = db.Column(db.Text()) 44 | member_since = db.Column(db.DateTime, default=datetime.now) 45 | last_seen = db.Column(db.DateTime, default=datetime.now) 46 | avatar = db.Column(db.String(64), default='dafulte.png') 47 | noread_messages = db.Column(db.Integer, index=True, default=0) 48 | administrator = db.Column(db.Boolean(), default=False) 49 | ban = db.Column(db.Boolean(), default=False) 50 | # 关系 51 | followed = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref( 52 | 'follower', lazy='joined'), lazy='dynamic', cascade='all,delete-orphan') 53 | followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], backref=db.backref( 54 | 'followed', lazy='joined'), lazy='dynamic', cascade='all,delete-orphan') 55 | topic = db.relationship('Topic', backref='topic_author', lazy='dynamic') 56 | post = db.relationship('Post', backref='post_author', lazy='dynamic') 57 | follow_topic = db.relationship('TopicFollows', backref=db.backref( 58 | 'users', lazy='joined'), lazy='dynamic', cascade='all,delete-orphan') 59 | comments = db.relationship( 60 | 'Comments', backref='comment_author', lazy='dynamic') 61 | question = db.relationship( 62 | 'Question', backref='q_author', lazy='dynamic') 63 | 64 | def to_json(self): 65 | json_user = { 66 | 'url': url_for('api.get_user', id=self.id, _external=True), 67 | 'username': self.username, 68 | 'member_since': self.member_since, 69 | 'last_seen': self.last_seen, 70 | 'posts': url_for('api.get_user_posts', id=self.id, _external=True), 71 | 'questions': url_for('api.get_user_questions', id=self.id, _external=True), 72 | 'post_count': self.post.count() 73 | } 74 | return json_user 75 | 76 | @property 77 | def password(self): 78 | raise AttributeError(u'密码属性不可读') 79 | 80 | @password.setter 81 | def password(self, password): 82 | self.password_hash = generate_password_hash(password) 83 | 84 | def verify_password(self, password): 85 | return check_password_hash(self.password_hash, password) 86 | 87 | def ping(self): 88 | self.last_seen = datetime.now() 89 | db.session.add(self) 90 | 91 | def admin(self): 92 | if User.query.count() == 1: 93 | self.admin = True 94 | db.session.add(self) 95 | 96 | def is_ban(self): 97 | return self.ban 98 | 99 | def is_admin(self): 100 | return self.administrator 101 | 102 | def is_self(self, current_user): 103 | return current_user.is_authenticated and self.id == current_user.id 104 | 105 | def follow(self, user): 106 | if not self.is_follow(user): 107 | f = Follow(follower=self, followed=user) 108 | db.session.add(f) 109 | 110 | def unfollow(self, user): 111 | f = self.followed.filter_by(followed_id=user.id).first() 112 | if f: 113 | db.session.delete(f) 114 | 115 | def all_follow(self): 116 | return self.followed.all() 117 | 118 | def all_follower(self): 119 | return self.followers.all() 120 | 121 | def is_follow(self, user): 122 | return self.followed.filter_by(followed_id=user.id).first() is not None 123 | 124 | def follow_t(self, topic): 125 | if not self.is_follow(topic): 126 | f = TopicFollows(user_id=self.id, topic_id=topic.id) 127 | db.session.add(f) 128 | 129 | def unfollow_t(self, topic): 130 | f = TopicFollows.query.filter_by( 131 | user_id=self.id, topic_id=topic.id).first() 132 | if f: 133 | db.session.delete(f) 134 | 135 | def is_follow_t(self, topic): 136 | return self.follow_topic.filter_by(topic_id=topic.id).first() is not None 137 | 138 | def generate_auth_token(self, expiration): 139 | s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration) 140 | return s.dumps({'id': self.id}) 141 | 142 | @staticmethod 143 | def verify_auth_token(token): 144 | s = Serializer(current_app.config['SECRET_KEY']) 145 | try: 146 | data = s.loads(token) 147 | except: 148 | return None 149 | return User.query.get(data['id']) 150 | 151 | 152 | class AnonymousUser(AnonymousUserMixin): 153 | '''匿名用户的检查方法''' 154 | 155 | def is_admin(self): 156 | return False 157 | 158 | def is_self(self, user): 159 | return False 160 | 161 | def is_ban(self): 162 | return True 163 | 164 | 165 | login_manager.anonymous_user = AnonymousUser 166 | 167 | 168 | class Topic(db.Model): 169 | __tablename__ = 'topics' 170 | id = db.Column(db.Integer, primary_key=True) 171 | topic = db.Column(db.String(64), unique=True, index=True) 172 | info = db.Column(db.Text()) 173 | img = db.Column(db.String(64), nullable=True) 174 | timestamp = db.Column(db.DateTime(), default=datetime.now) 175 | clink = db.Column(db.Integer, default=1) 176 | author = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 177 | activation = db.Column(db.Boolean(), default=True) 178 | posts = db.relationship('Post', backref='topic', lazy='dynamic') 179 | 180 | def ping(self): 181 | self.clink += 1 182 | db.session.add(self) 183 | 184 | 185 | class TopicFollows(db.Model): 186 | __tablename__ = 'topicfollows' 187 | user_id = db.Column(db.Integer, db.ForeignKey( 188 | 'users.id'), primary_key=True) 189 | topic_id = db.Column(db.Integer, db.ForeignKey( 190 | 'topics.id'), primary_key=True) 191 | timestamp = db.Column(db.DateTime(), default=datetime.now) 192 | 193 | 194 | class Comments(db.Model): 195 | __tablename__ = 'comment' 196 | id = db.Column(db.Integer, primary_key=True) 197 | author = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 198 | post_id = db.Column(db.Integer, index=True, nullable=True) 199 | post_author_id = db.Column(db.Integer, index=True) 200 | comment_id = db.Column(db.Integer, index=True, nullable=True) 201 | body = db.Column(db.Text()) 202 | body_html = db.Column(db.Text()) 203 | read = db.Column(db.Boolean(), default=False) 204 | timestamp = db.Column(db.DateTime(), default=datetime.now) 205 | activation = db.Column(db.Boolean(), default=True) 206 | 207 | def read_comment(self): 208 | self.read = True 209 | db.session.add(self) 210 | 211 | def to_json(self): 212 | json_data = { 213 | 'url': url_for('api.get_comment', id=self.id, _external=True), 214 | 'post': url_for('api.get_post', id=self.post_id, _external=True), 215 | 'author': url_for('api.get_user', id=self.author, _external=True), 216 | 'body': self.body, 217 | 'timestamp': self.timestamp 218 | } 219 | return json_data 220 | 221 | 222 | class Answer(db.Model): 223 | """database for QA""" 224 | __tablename__ = 'answers' 225 | id = db.Column(db.Integer, primary_key=True) 226 | q_author_id = db.Column(db.Integer, index=True) 227 | q_id = db.Column(db.Integer, index=True) 228 | author = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 229 | app = db.Column(db.Integer, default=0) 230 | opp = db.Column(db.Integer, default=0) 231 | adopt = db.Column(db.Boolean(), default=False) 232 | body = db.Column(db.Text()) 233 | timestamp = db.Column(db.DateTime(), default=datetime.now) 234 | activation = db.Column(db.Boolean(), default=True) 235 | 236 | def up(self): 237 | self.app += 1 238 | db.session.add(self) 239 | 240 | def down(self): 241 | self.opp += 1 242 | db.session.add(self) 243 | 244 | def to_json(self): 245 | json_data = { 246 | 'url': url_for('api.get_answer', id=self.id, _external=True), 247 | 'anthor': url_for('api.get_user', id=self.author, _external=True), 248 | 'question': url_for('api.get_question', id=self.q_id, _external=True), 249 | 'agree': self.app, 250 | 'oppose': self.opp, 251 | 'timestamp': self.timestamp, 252 | 'answer': self.body 253 | } 254 | return json_data 255 | 256 | 257 | class Question(db.Model): 258 | __tablename__ = 'questions' 259 | id = db.Column(db.Integer, primary_key=True) 260 | author = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 261 | clink = db.Column(db.Integer, default=0) 262 | reply = db.Column(db.Integer, default=0) 263 | answer = db.Column(db.Integer) 264 | title = db.Column(db.Text()) 265 | body = db.Column(db.Text()) 266 | timestamp = db.Column(db.DateTime(), default=datetime.now) 267 | activation = db.Column(db.Boolean(), default=True) 268 | 269 | @staticmethod 270 | def changde_reply(target, value, oldvalue, initiator): 271 | q = Question.query.filter_by(id=value).first() 272 | q.reply += 1 273 | db.session.add(q) 274 | 275 | def read(self): 276 | self.clink += 1 277 | db.session.add(self) 278 | 279 | def to_json(self): 280 | json_data = { 281 | 'url': url_for('api.get_question', id=self.id, _external=True), 282 | 'author': url_for('api.get_user', id=self.author, _external=True), 283 | 'timestamp': self.timestamp, 284 | 'question': self.body, 285 | 'answers': url_for('api.get_question_answers', id=self.id, _external=True) 286 | } 287 | return json_data 288 | 289 | db.event.listen(Answer.q_id, 'set', Question.changde_reply) 290 | 291 | 292 | class Messages(db.Model): 293 | __tablename__ = 'messages' 294 | id = db.Column(db.Integer, primary_key=True) 295 | the_id = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 296 | from_id = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 297 | post_id = db.Column(db.Integer, index=True, nullable=True) 298 | post_author_id = db.Column(db.Integer, db.ForeignKey( 299 | 'users.id'), index=True, nullable=True) 300 | comment_id = db.Column(db.Integer, index=True, nullable=True) 301 | q_id = db.Column(db.Integer, index=True, nullable=True) 302 | q_author_id = db.Column(db.Integer, index=True, nullable=True) 303 | body_id = db.Column(db.Integer, index=True) 304 | is_read = db.Column(db.Boolean(), default=False) 305 | timestamp = db.Column(db.DateTime(), default=datetime.now) 306 | 307 | def read(self): 308 | self.is_read = True 309 | db.session.add(self) 310 | 311 | def read_all(id): 312 | ms = Messages.query.filter( 313 | or_(Messages.the_id == id, Messages.post_author_id == id, Messages.q_author_id == id)).all() 314 | for m in ms: 315 | m.is_read = True 316 | db.session.add(m) 317 | 318 | 319 | class PostTag(db.Model): 320 | __tablename__ = 'posttag' 321 | post_id = db.Column(db.Integer, db.ForeignKey( 322 | 'posts.id'), primary_key=True) 323 | tags_id = db.Column(db.Integer, db.ForeignKey('tags.id'), primary_key=True) 324 | 325 | 326 | class Tag(db.Model): 327 | __tablename__ = 'tags' 328 | id = db.Column(db.Integer, primary_key=True) 329 | tag_name = db.Column(db.String(64), index=True) 330 | posts = db.relationship('PostTag', foreign_keys=[ 331 | PostTag.tags_id], backref=db.backref('tag', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') 332 | 333 | 334 | class Post(db.Model): 335 | __tablename__ = 'posts' 336 | id = db.Column(db.Integer, primary_key=True) 337 | author = db.Column(db.Integer, db.ForeignKey('users.id'), index=True) 338 | tpoic = db.Column(db.Integer, db.ForeignKey('topics.id'), index=True) 339 | timestamp = db.Column(db.DateTime(), default=datetime.now) 340 | clink = db.Column(db.Integer, default=1) 341 | head = db.Column(db.String(64)) 342 | body = db.Column(db.Text()) 343 | activation = db.Column(db.Boolean(), default=True) 344 | tags = db.relationship('PostTag', foreign_keys=[ 345 | PostTag.post_id], backref=db.backref('post', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') 346 | 347 | def ping(self): 348 | self.clink += 1 349 | db.session.add(self) 350 | 351 | def to_json(self): 352 | json_data = { 353 | 'url': url_for('api.get_post', id=self.id, _external=True), 354 | 'body': self.body, 355 | 'timestamp': self.timestamp, 356 | 'author': url_for('api.get_user', id=self.author, _external=True), 357 | 'comments': url_for('api.get_post_comments', id=self.id, _external=True), 358 | 'comment_count': Comments.query.filter_by(post_id=self.id).count(), 359 | } 360 | return json_data 361 | 362 | @staticmethod 363 | def form_json(json_post): 364 | body = json_post.get('body') 365 | if body is None or body == '': 366 | raise ValidationError('post does not have a body') 367 | return Post(body=body) 368 | -------------------------------------------------------------------------------- /blog/static/back/background-image-html.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/back/background-image-html.jpg -------------------------------------------------------------------------------- /blog/static/css/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | background-color: #333333; 4 | /* The html and body elements cannot have any padding or margin. */ 5 | /* font-family: "Microsoft YaHei" ! important; 6 | */} 7 | a:link { 8 | text-decoration: none; 9 | } 10 | a:hover { 11 | text-decoration: none; 12 | } 13 | footer { 14 | padding-top: 20px; 15 | background-color: #ffffff; 16 | } 17 | .navbar { 18 | /*opacity:0.9;*/ 19 | /*box-shadow: 0px 5px 8px #B8B8B8;*/ 20 | } 21 | #warp { 22 | min-height: 100%; 23 | /*background-color: #004869;*/ 24 | height: auto; 25 | margin-top: 0px; 26 | /*margin-bottom: -50px;*/ 27 | /*padding-bottom: 50px;*/ 28 | } 29 | .register-form { 30 | /* padding-top:10px; 31 | padding-bottom:30px; 32 | padding-right:30px; 33 | padding-left:30px; 34 | margin-bottom: 20px; 35 | background-color: #ffffff; 36 | box-shadow: 5px 5px 3px #B8B8B8;*/ 37 | margin-top: 100px; 38 | } 39 | .container { 40 | min-height: 100%; 41 | height: auto; 42 | } 43 | .jumbotron { 44 | min-height: 100%; 45 | height: auto; 46 | 47 | } 48 | .panel { 49 | background-color: #f2f2f2; 50 | /*box-shadow: 5px 5px 10px #B8B8B8;*/ 51 | padding: 0px; 52 | } 53 | li.list-group-item { 54 | background-color: #f2f2f2; 55 | } 56 | .user-seting { 57 | background-color: #fff; 58 | padding-top:10px; 59 | padding-bottom:5px; 60 | padding-left:20px; 61 | padding-right:20px; 62 | } 63 | .topic { 64 | padding-right: 20px; 65 | } 66 | #submit { 67 | float:right; 68 | background-color: #5bc0de; 69 | color: #fff; 70 | } 71 | .list-group-item { 72 | padding-bottom: 20px; 73 | } 74 | .list-group-item { 75 | padding-top: 10px; 76 | padding-bottom: 10px; 77 | } 78 | div.go-top { 79 | display: none; 80 | opacity: 0.6; 81 | z-index: 999999; 82 | position: fixed; 83 | bottom: 113px; 84 | left: 85%; 85 | margin-left: 40px; 86 | border: 1px solid #337ab7; 87 | width: 38px; 88 | height: 38px; 89 | border-radius: 3px; 90 | cursor: pointer; 91 | } 92 | 93 | div.go-top:hover { 94 | opacity: 1; 95 | filter: alpha(opacity=100); 96 | } 97 | div.go-top p{ 98 | font-size: 28px;color: #337ab7; 99 | margin-top: 4px; 100 | margin-left: 3px; 101 | } 102 | ol.breadcrumb { 103 | background-color: #337ab7; 104 | padding: 0px; 105 | margin-bottom: 0px; 106 | } 107 | div.modal { 108 | padding-top: 40px; 109 | } 110 | #footer { 111 | /*opacity:0.7;*/ 112 | } 113 | canvas { 114 | position: fixed; 115 | left: 0px; 116 | top: 0px; 117 | z-index: 0; 118 | } 119 | #body { 120 | height: 200px; 121 | } 122 | #postbody { 123 | height: 400px; 124 | } 125 | .post-body { 126 | display:-webkit-box; 127 | text-overflow:ellipsis; 128 | overflow:hidden; 129 | -webkit-line-clamp: 10; 130 | -webkit-box-orient:vertical; 131 | line-height:24px; 132 | letter-spacing:2px; 133 | font-size: 15px; 134 | padding-left: 20px; 135 | padding-right: 20px; 136 | margin-bottom: 10px; 137 | color: #383838; 138 | } 139 | 140 | @media screen and (max-width: 1081px) { 141 | #left, #right { 142 | margin-left: 0px; 143 | margin-right: 0px; 144 | padding-left: 10px; 145 | padding-right: 10px; 146 | } 147 | .container { 148 | margin-left: 10px; 149 | margin-right: 10px; 150 | padding-left: 0px; 151 | padding-right: 0px; 152 | width: auto; 153 | } 154 | .post-body { 155 | padding-left: 0px; 156 | padding-right: 0px; 157 | } 158 | } 159 | ol.breadcrumb { 160 | background-color: rgba(8,0,0,0); 161 | } 162 | -------------------------------------------------------------------------------- /blog/static/fonts/default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../fonts/icomoon.eot?yrquyl'); 4 | src:url('../fonts/icomoon.eot?#iefixyrquyl') format('embedded-opentype'), 5 | url('../fonts/icomoon.woff?yrquyl') format('woff'), 6 | url('../fonts/icomoon.ttf?yrquyl') format('truetype'), 7 | url('../fonts/icomoon.svg?yrquyl#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | [class^="icon-"], [class*=" icon-"] { 12 | font-family: 'icomoon'; 13 | speak: none; 14 | font-style: normal; 15 | font-weight: normal; 16 | font-variant: normal; 17 | text-transform: none; 18 | line-height: 1; 19 | 20 | /* Better Font Rendering =========== */ 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | body, html { font-size: 100%; padding: 0; margin: 0;} 26 | 27 | /* Reset */ 28 | *, 29 | *:after, 30 | *:before { 31 | -webkit-box-sizing: border-box; 32 | -moz-box-sizing: border-box; 33 | box-sizing: border-box; 34 | } 35 | 36 | /* Clearfix hack by Nicolas Gallagher: http://nicolasgallagher.com/micro-clearfix-hack/ */ 37 | .clearfix:before, 38 | .clearfix:after { 39 | content: " "; 40 | display: table; 41 | } 42 | 43 | .clearfix:after { 44 | clear: both; 45 | } 46 | 47 | body{ 48 | background: #f9f7f6; 49 | color: #404d5b; 50 | font-weight: 500; 51 | font-size: 1.05em; 52 | font-family: "Segoe UI", "Lucida Grande", Helvetica, Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans", "wenquanyi micro hei", "Hiragino Sans GB", "Hiragino Sans GB W3", "FontAwesome", sans-serif; 53 | } 54 | a{color: #2fa0ec;text-decoration: none;outline: none;} 55 | a:hover,a:focus{color:#74777b;}; 56 | 57 | .container{ 58 | margin: 0 auto; 59 | text-align: center; 60 | overflow: hidden; 61 | } 62 | .content { 63 | font-size: 150%; 64 | padding: 3em 0; 65 | } 66 | 67 | .content h2 { 68 | margin: 0 0 2em; 69 | opacity: 0.1; 70 | } 71 | 72 | .content p { 73 | margin: 1em 0; 74 | padding: 5em 0 0 0; 75 | font-size: 0.65em; 76 | } 77 | .bgcolor-1 { background: #f0efee; } 78 | .bgcolor-2 { background: #f9f9f9; } 79 | .bgcolor-3 { background: #e8e8e8; } 80 | .bgcolor-4 { background: #2f3238; color: #fff; } 81 | .bgcolor-5 { background: #df6659; color: #521e18; } 82 | .bgcolor-6 { background: #2fa8ec; } 83 | .bgcolor-7 { background: #d0d6d6; } 84 | .bgcolor-8 { background: #3d4444; color: #fff; } 85 | /* Header */ 86 | .htmleaf-header{ 87 | padding: 3em 190px 4em; 88 | letter-spacing: -1px; 89 | text-align: center; 90 | } 91 | .htmleaf-header h1 { 92 | font-weight: 600; 93 | font-size: 2em; 94 | line-height: 1; 95 | margin-bottom: 0; 96 | } 97 | .htmleaf-header h1 span { 98 | font-family: 'Raleway'; 99 | display: block; 100 | font-size: 60%; 101 | font-weight: 400; 102 | padding: 0.8em 0 0.5em 0; 103 | color: #c3c8cd; 104 | } 105 | /*nav*/ 106 | .htmleaf-demo a{color: #1d7db1;text-decoration: none;} 107 | .htmleaf-demo{width: 100%;padding-bottom: 1.2em;} 108 | .htmleaf-demo a{display: inline-block;margin: 0.5em;padding: 0.6em 1em;border: 3px solid #1d7db1;font-weight: 700;} 109 | .htmleaf-demo a:hover{opacity: 0.6;} 110 | .htmleaf-demo a.current{background:#1d7db1;color: #fff; } 111 | /* Top Navigation Style */ 112 | .htmleaf-links { 113 | position: relative; 114 | display: inline-block; 115 | white-space: nowrap; 116 | font-size: 1.5em; 117 | text-align: center; 118 | } 119 | 120 | .htmleaf-links::after { 121 | position: absolute; 122 | top: 0; 123 | left: 50%; 124 | margin-left: -1px; 125 | width: 2px; 126 | height: 100%; 127 | background: #dbdbdb; 128 | content: ''; 129 | -webkit-transform: rotate3d(0,0,1,22.5deg); 130 | transform: rotate3d(0,0,1,22.5deg); 131 | } 132 | 133 | .htmleaf-icon { 134 | display: inline-block; 135 | margin: 0.5em; 136 | padding: 0em 0; 137 | width: 1.5em; 138 | text-decoration: none; 139 | } 140 | 141 | .htmleaf-icon span { 142 | display: none; 143 | } 144 | 145 | .htmleaf-icon:before { 146 | margin: 0 5px; 147 | text-transform: none; 148 | font-weight: normal; 149 | font-style: normal; 150 | font-variant: normal; 151 | font-family: 'icomoon'; 152 | line-height: 1; 153 | speak: none; 154 | -webkit-font-smoothing: antialiased; 155 | } 156 | /* footer */ 157 | .htmleaf-footer{width: 100%;padding-top: 10px;} 158 | .htmleaf-small{font-size: 0.8em;} 159 | .center{text-align: center;} 160 | /* icomoon */ 161 | .icon-home:before { 162 | content: "\e600"; 163 | } 164 | 165 | .icon-pacman:before { 166 | content: "\e623"; 167 | } 168 | 169 | .icon-users2:before { 170 | content: "\e678"; 171 | } 172 | 173 | .icon-bug:before { 174 | content: "\e699"; 175 | } 176 | 177 | .icon-eye:before { 178 | content: "\e610"; 179 | } 180 | 181 | .icon-eye-blocked:before { 182 | content: "\e611"; 183 | } 184 | 185 | .icon-eye2:before { 186 | content: "\e612"; 187 | } 188 | 189 | .icon-arrow-up-left3:before { 190 | content: "\e72f"; 191 | } 192 | 193 | .icon-arrow-up3:before { 194 | content: "\e730"; 195 | } 196 | 197 | .icon-arrow-up-right3:before { 198 | content: "\e731"; 199 | } 200 | 201 | .icon-arrow-right3:before { 202 | content: "\e732"; 203 | } 204 | 205 | .icon-arrow-down-right3:before { 206 | content: "\e733"; 207 | } 208 | 209 | .icon-arrow-down3:before { 210 | content: "\e734"; 211 | } 212 | 213 | .icon-arrow-down-left3:before { 214 | content: "\e735"; 215 | } 216 | 217 | .icon-arrow-left3:before { 218 | content: "\e736"; 219 | } 220 | 221 | 222 | 223 | @media screen and (max-width: 50em) { 224 | .htmleaf-header { 225 | padding: 3em 10% 4em; 226 | } 227 | .htmleaf-header h1 { 228 | font-size:2em; 229 | } 230 | } 231 | 232 | 233 | @media screen and (max-width: 40em) { 234 | .htmleaf-header h1 { 235 | font-size: 1.5em; 236 | } 237 | } 238 | 239 | @media screen and (max-width: 30em) { 240 | .htmleaf-header h1 { 241 | font-size:1.2em; 242 | } 243 | } -------------------------------------------------------------------------------- /blog/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /blog/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /blog/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /blog/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /blog/static/fonts/normalize.css: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} -------------------------------------------------------------------------------- /blog/static/fonts/style.css: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | HTML5 CSS Reset Minified - Eric Meyer 3 | ========================================================================== */ 4 | 5 | /*html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent} 6 | body{line-height:1} 7 | article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} 8 | nav ul{list-style:none} 9 | blockquote,q{quotes:none} 10 | blockquote:before,blockquote:after,q:before,q:after{content:none} 11 | a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;text-decoration:none} 12 | mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold} 13 | del{text-decoration:line-through} 14 | abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help} 15 | table{border-collapse:collapse;border-spacing:0} 16 | hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0} 17 | input,select{vertical-align:middle} 18 | li{list-style:none}*/ 19 | 20 | 21 | /* ============================================================================= 22 | My CSS 23 | ========================================================================== */ 24 | 25 | html,body{ 26 | width:100%; 27 | height:80%; 28 | background:#000; 29 | } 30 | 31 | /* remove canvas default margin */ 32 | canvas{ 33 | display:block; 34 | vertical-align:bottom; 35 | } 36 | 37 | #particles-js{ 38 | width:100%; 39 | height:95%; 40 | } 41 | .htmleaf-header{color: #fff;} -------------------------------------------------------------------------------- /blog/static/icon/close-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/close-icon.png -------------------------------------------------------------------------------- /blog/static/icon/github.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/github.ico -------------------------------------------------------------------------------- /blog/static/icon/home-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/home-icon.png -------------------------------------------------------------------------------- /blog/static/icon/outside-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/outside-icon.png -------------------------------------------------------------------------------- /blog/static/icon/settings-3-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/settings-3-icon.png -------------------------------------------------------------------------------- /blog/static/icon/shortcut.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/shortcut.ico -------------------------------------------------------------------------------- /blog/static/icon/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonzhoup/flask-blog/8ba61dd00dd792ad715f8ae890a90263462b6500/blog/static/icon/wechat.jpg -------------------------------------------------------------------------------- /blog/static/js/app.js: -------------------------------------------------------------------------------- 1 | /* default dom id (particles-js) */ 2 | //particlesJS(); 3 | 4 | /* config dom id */ 5 | //particlesJS('dom-id'); 6 | 7 | /* config dom id (optional) + config particles params */ 8 | particlesJS('particles-js', { 9 | particles: { 10 | color: '#666666', 11 | shape: 'circle', // "circle", "edge" or "triangle" 12 | opacity: 1, 13 | size: 4, 14 | size_random: true, 15 | nb: 150, 16 | line_linked: { 17 | enable_auto: true, 18 | distance: 100, 19 | color: '#666666', 20 | opacity: 1, 21 | width: 1, 22 | condensed_mode: { 23 | enable: false, 24 | rotateX: 600, 25 | rotateY: 600 26 | } 27 | }, 28 | anim: { 29 | enable: true, 30 | speed: 1 31 | } 32 | }, 33 | interactivity: { 34 | enable: true, 35 | mouse: { 36 | distance: 300 37 | }, 38 | detect_on: 'canvas', // "canvas" or "window" 39 | mode: 'grab', 40 | line_linked: { 41 | opacity: .5 42 | }, 43 | events: { 44 | onclick: { 45 | enable: true, 46 | mode: 'push', // "push" or "remove" 47 | nb: 4 48 | } 49 | } 50 | }, 51 | /* Retina Display Support */ 52 | retina_detect: true 53 | }); -------------------------------------------------------------------------------- /blog/static/js/default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../fonts/icomoon.eot?yrquyl'); 4 | src:url('../fonts/icomoon.eot?#iefixyrquyl') format('embedded-opentype'), 5 | url('../fonts/icomoon.woff?yrquyl') format('woff'), 6 | url('../fonts/icomoon.ttf?yrquyl') format('truetype'), 7 | url('../fonts/icomoon.svg?yrquyl#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | [class^="icon-"], [class*=" icon-"] { 12 | font-family: 'icomoon'; 13 | speak: none; 14 | font-style: normal; 15 | font-weight: normal; 16 | font-variant: normal; 17 | text-transform: none; 18 | line-height: 1; 19 | 20 | /* Better Font Rendering =========== */ 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | body, html { font-size: 100%; padding: 0; margin: 0;} 26 | 27 | /* Reset */ 28 | *, 29 | *:after, 30 | *:before { 31 | -webkit-box-sizing: border-box; 32 | -moz-box-sizing: border-box; 33 | box-sizing: border-box; 34 | } 35 | 36 | /* Clearfix hack by Nicolas Gallagher: http://nicolasgallagher.com/micro-clearfix-hack/ */ 37 | .clearfix:before, 38 | .clearfix:after { 39 | content: " "; 40 | display: table; 41 | } 42 | 43 | .clearfix:after { 44 | clear: both; 45 | } 46 | 47 | body{ 48 | background: #f9f7f6; 49 | color: #404d5b; 50 | font-weight: 500; 51 | font-size: 1.05em; 52 | font-family: "Segoe UI", "Lucida Grande", Helvetica, Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans", "wenquanyi micro hei", "Hiragino Sans GB", "Hiragino Sans GB W3", "FontAwesome", sans-serif; 53 | } 54 | a{color: #2fa0ec;text-decoration: none;outline: none;} 55 | a:hover,a:focus{color:#74777b;}; 56 | 57 | .container{ 58 | margin: 0 auto; 59 | text-align: center; 60 | overflow: hidden; 61 | } 62 | .content { 63 | font-size: 150%; 64 | padding: 3em 0; 65 | } 66 | 67 | .content h2 { 68 | margin: 0 0 2em; 69 | opacity: 0.1; 70 | } 71 | 72 | .content p { 73 | margin: 1em 0; 74 | padding: 5em 0 0 0; 75 | font-size: 0.65em; 76 | } 77 | .bgcolor-1 { background: #f0efee; } 78 | .bgcolor-2 { background: #f9f9f9; } 79 | .bgcolor-3 { background: #e8e8e8; } 80 | .bgcolor-4 { background: #2f3238; color: #fff; } 81 | .bgcolor-5 { background: #df6659; color: #521e18; } 82 | .bgcolor-6 { background: #2fa8ec; } 83 | .bgcolor-7 { background: #d0d6d6; } 84 | .bgcolor-8 { background: #3d4444; color: #fff; } 85 | /* Header */ 86 | .htmleaf-header{ 87 | padding: 3em 190px 4em; 88 | letter-spacing: -1px; 89 | text-align: center; 90 | } 91 | .htmleaf-header h1 { 92 | font-weight: 600; 93 | font-size: 2em; 94 | line-height: 1; 95 | margin-bottom: 0; 96 | } 97 | .htmleaf-header h1 span { 98 | font-family: 'Raleway'; 99 | display: block; 100 | font-size: 60%; 101 | font-weight: 400; 102 | padding: 0.8em 0 0.5em 0; 103 | color: #c3c8cd; 104 | } 105 | /*nav*/ 106 | .htmleaf-demo a{color: #1d7db1;text-decoration: none;} 107 | .htmleaf-demo{width: 100%;padding-bottom: 1.2em;} 108 | .htmleaf-demo a{display: inline-block;margin: 0.5em;padding: 0.6em 1em;border: 3px solid #1d7db1;font-weight: 700;} 109 | .htmleaf-demo a:hover{opacity: 0.6;} 110 | .htmleaf-demo a.current{background:#1d7db1;color: #fff; } 111 | /* Top Navigation Style */ 112 | .htmleaf-links { 113 | position: relative; 114 | display: inline-block; 115 | white-space: nowrap; 116 | font-size: 1.5em; 117 | text-align: center; 118 | } 119 | 120 | .htmleaf-links::after { 121 | position: absolute; 122 | top: 0; 123 | left: 50%; 124 | margin-left: -1px; 125 | width: 2px; 126 | height: 100%; 127 | background: #dbdbdb; 128 | content: ''; 129 | -webkit-transform: rotate3d(0,0,1,22.5deg); 130 | transform: rotate3d(0,0,1,22.5deg); 131 | } 132 | 133 | .htmleaf-icon { 134 | display: inline-block; 135 | margin: 0.5em; 136 | padding: 0em 0; 137 | width: 1.5em; 138 | text-decoration: none; 139 | } 140 | 141 | .htmleaf-icon span { 142 | display: none; 143 | } 144 | 145 | .htmleaf-icon:before { 146 | margin: 0 5px; 147 | text-transform: none; 148 | font-weight: normal; 149 | font-style: normal; 150 | font-variant: normal; 151 | font-family: 'icomoon'; 152 | line-height: 1; 153 | speak: none; 154 | -webkit-font-smoothing: antialiased; 155 | } 156 | /* footer */ 157 | .htmleaf-footer{width: 100%;padding-top: 10px;} 158 | .htmleaf-small{font-size: 0.8em;} 159 | .center{text-align: center;} 160 | /* icomoon */ 161 | .icon-home:before { 162 | content: "\e600"; 163 | } 164 | 165 | .icon-pacman:before { 166 | content: "\e623"; 167 | } 168 | 169 | .icon-users2:before { 170 | content: "\e678"; 171 | } 172 | 173 | .icon-bug:before { 174 | content: "\e699"; 175 | } 176 | 177 | .icon-eye:before { 178 | content: "\e610"; 179 | } 180 | 181 | .icon-eye-blocked:before { 182 | content: "\e611"; 183 | } 184 | 185 | .icon-eye2:before { 186 | content: "\e612"; 187 | } 188 | 189 | .icon-arrow-up-left3:before { 190 | content: "\e72f"; 191 | } 192 | 193 | .icon-arrow-up3:before { 194 | content: "\e730"; 195 | } 196 | 197 | .icon-arrow-up-right3:before { 198 | content: "\e731"; 199 | } 200 | 201 | .icon-arrow-right3:before { 202 | content: "\e732"; 203 | } 204 | 205 | .icon-arrow-down-right3:before { 206 | content: "\e733"; 207 | } 208 | 209 | .icon-arrow-down3:before { 210 | content: "\e734"; 211 | } 212 | 213 | .icon-arrow-down-left3:before { 214 | content: "\e735"; 215 | } 216 | 217 | .icon-arrow-left3:before { 218 | content: "\e736"; 219 | } 220 | 221 | 222 | 223 | @media screen and (max-width: 50em) { 224 | .htmleaf-header { 225 | padding: 3em 10% 4em; 226 | } 227 | .htmleaf-header h1 { 228 | font-size:2em; 229 | } 230 | } 231 | 232 | 233 | @media screen and (max-width: 40em) { 234 | .htmleaf-header h1 { 235 | font-size: 1.5em; 236 | } 237 | } 238 | 239 | @media screen and (max-width: 30em) { 240 | .htmleaf-header h1 { 241 | font-size:1.2em; 242 | } 243 | } -------------------------------------------------------------------------------- /blog/static/js/normalize.css: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} -------------------------------------------------------------------------------- /blog/static/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /blog/static/js/particles.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------- 2 | /* Author : Vincent Garreau - vincentgarreau.com 3 | /* MIT license: http://opensource.org/licenses/MIT 4 | /* GitHub : https://github.com/VincentGarreau/particles.js 5 | /* How to use? : Check the GitHub README 6 | /* v1.0.3 7 | /* ----------------------------------------------- */ 8 | 9 | function launchParticlesJS(tag_id, params){ 10 | 11 | var canvas_el = document.querySelector('#'+tag_id+' > canvas'); 12 | 13 | /* particles.js variables with default values */ 14 | pJS = { 15 | canvas: { 16 | el: canvas_el, 17 | w: canvas_el.offsetWidth, 18 | h: canvas_el.offsetHeight 19 | }, 20 | particles: { 21 | color: '#fff', 22 | shape: 'circle', 23 | opacity: 1, 24 | size: 2.5, 25 | size_random: true, 26 | nb: 200, 27 | line_linked: { 28 | enable_auto: true, 29 | distance: 100, 30 | color: '#fff', 31 | opacity: 1, 32 | width: 1, 33 | condensed_mode: { 34 | enable: true, 35 | rotateX: 65000, 36 | rotateY: 65000 37 | } 38 | }, 39 | anim: { 40 | enable: true, 41 | speed: 1 42 | }, 43 | array: [] 44 | }, 45 | interactivity: { 46 | enable: true, 47 | mouse: { 48 | distance: 100 49 | }, 50 | detect_on: 'canvas', 51 | mode: 'grab', 52 | line_linked: { 53 | opacity: 1 54 | }, 55 | events: { 56 | onclick: { 57 | enable: true, 58 | mode: 'push', 59 | nb: 4 60 | } 61 | } 62 | }, 63 | retina_detect: false, 64 | fn: { 65 | vendors:{ 66 | interactivity: {} 67 | } 68 | } 69 | }; 70 | 71 | /* params settings */ 72 | if(params){ 73 | if(params.particles){ 74 | var paramsForParticles = params.particles; 75 | if(paramsForParticles.color) pJS.particles.color = paramsForParticles.color; 76 | if(paramsForParticles.shape) pJS.particles.shape = paramsForParticles.shape; 77 | if(paramsForParticles.opacity) pJS.particles.opacity = paramsForParticles.opacity; 78 | if(paramsForParticles.size) pJS.particles.size = paramsForParticles.size; 79 | if(paramsForParticles.size_random == false) pJS.particles.size_random = paramsForParticles.size_random; 80 | if(paramsForParticles.nb) pJS.particles.nb = paramsForParticles.nb; 81 | if(paramsForParticles.line_linked){ 82 | var paramsForLineLinked = paramsForParticles.line_linked; 83 | if(paramsForLineLinked.enable_auto == false) pJS.particles.line_linked.enable_auto = paramsForLineLinked.enable_auto; 84 | if(paramsForLineLinked.distance) pJS.particles.line_linked.distance = paramsForLineLinked.distance; 85 | if(paramsForLineLinked.color) pJS.particles.line_linked.color = paramsForLineLinked.color; 86 | if(paramsForLineLinked.opacity) pJS.particles.line_linked.opacity = paramsForLineLinked.opacity; 87 | if(paramsForLineLinked.width) pJS.particles.line_linked.width = paramsForLineLinked.width; 88 | if(paramsForLineLinked.condensed_mode){ 89 | var paramsForCondensedMode = paramsForLineLinked.condensed_mode; 90 | if(paramsForCondensedMode.enable == false) pJS.particles.line_linked.condensed_mode.enable = paramsForCondensedMode.enable; 91 | if(paramsForCondensedMode.rotateX) pJS.particles.line_linked.condensed_mode.rotateX = paramsForCondensedMode.rotateX; 92 | if(paramsForCondensedMode.rotateY) pJS.particles.line_linked.condensed_mode.rotateY = paramsForCondensedMode.rotateY; 93 | } 94 | } 95 | if(paramsForParticles.anim){ 96 | var paramsForAnim = paramsForParticles.anim; 97 | if(paramsForAnim.enable == false) pJS.particles.anim.enable = paramsForAnim.enable; 98 | if(paramsForAnim.speed) pJS.particles.anim.speed = paramsForAnim.speed; 99 | } 100 | } 101 | if(params.interactivity){ 102 | var paramsForInteractivity = params.interactivity; 103 | if(paramsForInteractivity.enable == false) pJS.interactivity.enable = paramsForInteractivity.enable; 104 | if(paramsForInteractivity.mouse){ 105 | if(paramsForInteractivity.mouse.distance) pJS.interactivity.mouse.distance = paramsForInteractivity.mouse.distance; 106 | } 107 | if(paramsForInteractivity.detect_on) pJS.interactivity.detect_on = paramsForInteractivity.detect_on; 108 | if(paramsForInteractivity.mode) pJS.interactivity.mode = paramsForInteractivity.mode; 109 | if(paramsForInteractivity.line_linked){ 110 | if(paramsForInteractivity.line_linked.opacity) pJS.interactivity.line_linked.opacity = paramsForInteractivity.line_linked.opacity; 111 | } 112 | if(paramsForInteractivity.events){ 113 | var paramsForEvents = paramsForInteractivity.events; 114 | if(paramsForEvents.onclick){ 115 | var paramsForOnclick = paramsForEvents.onclick; 116 | if(paramsForOnclick.enable == false) pJS.interactivity.events.onclick.enable = false; 117 | if(paramsForOnclick.mode != 'push') pJS.interactivity.events.onclick.mode = paramsForOnclick.mode; 118 | if(paramsForOnclick.nb) pJS.interactivity.events.onclick.nb = paramsForOnclick.nb; 119 | } 120 | } 121 | } 122 | pJS.retina_detect = params.retina_detect; 123 | } 124 | 125 | /* convert hex colors to rgb */ 126 | pJS.particles.color_rgb = hexToRgb(pJS.particles.color); 127 | pJS.particles.line_linked.color_rgb_line = hexToRgb(pJS.particles.line_linked.color); 128 | 129 | /* detect retina */ 130 | if(pJS.retina_detect && window.devicePixelRatio > 1){ 131 | pJS.retina = true; 132 | 133 | pJS.canvas.pxratio = window.devicePixelRatio 134 | pJS.canvas.w = pJS.canvas.el.offsetWidth * pJS.canvas.pxratio; 135 | pJS.canvas.h = pJS.canvas.el.offsetHeight * pJS.canvas.pxratio; 136 | pJS.particles.anim.speed = pJS.particles.anim.speed * pJS.canvas.pxratio; 137 | pJS.particles.line_linked.distance = pJS.particles.line_linked.distance * pJS.canvas.pxratio; 138 | pJS.particles.line_linked.width = pJS.particles.line_linked.width * pJS.canvas.pxratio; 139 | pJS.interactivity.mouse.distance = pJS.interactivity.mouse.distance * pJS.canvas.pxratio; 140 | } 141 | 142 | 143 | /* ---------- CANVAS functions ------------ */ 144 | 145 | pJS.fn.canvasInit = function(){ 146 | pJS.canvas.ctx = pJS.canvas.el.getContext('2d'); 147 | }; 148 | 149 | pJS.fn.canvasSize = function(){ 150 | pJS.canvas.el.width = pJS.canvas.w; 151 | pJS.canvas.el.height = pJS.canvas.h; 152 | 153 | window.onresize = function(){ 154 | if(pJS){ 155 | pJS.canvas.w = pJS.canvas.el.offsetWidth; 156 | pJS.canvas.h = pJS.canvas.el.offsetHeight; 157 | 158 | /* resize canvas */ 159 | if(pJS.retina){ 160 | pJS.canvas.w *= pJS.canvas.pxratio; 161 | pJS.canvas.h *= pJS.canvas.pxratio; 162 | } 163 | 164 | pJS.canvas.el.width = pJS.canvas.w; 165 | pJS.canvas.el.height = pJS.canvas.h; 166 | 167 | /* repaint canvas */ 168 | pJS.fn.canvasPaint(); 169 | if(!pJS.particles.anim.enable){ 170 | pJS.fn.particlesRemove(); 171 | pJS.fn.canvasRemove(); 172 | launchParticles(); 173 | } 174 | } 175 | } 176 | }; 177 | 178 | pJS.fn.canvasPaint = function(){ 179 | pJS.canvas.ctx.fillRect(0, 0, pJS.canvas.w, pJS.canvas.h); 180 | }; 181 | 182 | pJS.fn.canvasRemove = function(){ 183 | pJS.canvas.ctx.clearRect(0, 0, pJS.canvas.w, pJS.canvas.h); 184 | } 185 | 186 | 187 | /* --------- PARTICLES functions ----------- */ 188 | 189 | pJS.fn.particle = function(color, opacity, position){ 190 | 191 | /* position */ 192 | this.x = position ? position.x : Math.random() * pJS.canvas.w; 193 | this.y = position ? position.y : Math.random() * pJS.canvas.h; 194 | 195 | /* size */ 196 | this.radius = (pJS.particles.size_random ? Math.random() : 1) * pJS.particles.size; 197 | if (pJS.retina) this.radius *= pJS.canvas.pxratio; 198 | 199 | /* color */ 200 | this.color = color; 201 | 202 | /* opacity */ 203 | this.opacity = opacity; 204 | 205 | /* animation - velocity for speed */ 206 | this.vx = -.5 + Math.random(); 207 | this.vy = -.5 + Math.random(); 208 | 209 | /* draw function */ 210 | this.draw = function(){ 211 | pJS.canvas.ctx.fillStyle = 'rgba('+this.color.r+','+this.color.g+','+this.color.b+','+this.opacity+')'; 212 | pJS.canvas.ctx.beginPath(); 213 | 214 | switch(pJS.particles.shape){ 215 | case 'circle': 216 | pJS.canvas.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); 217 | break; 218 | 219 | case 'edge': 220 | pJS.canvas.ctx.rect(this.x, this.y, this.radius*2, this.radius*2); 221 | break; 222 | 223 | case 'triangle': 224 | pJS.canvas.ctx.moveTo(this.x,this.y-this.radius); 225 | pJS.canvas.ctx.lineTo(this.x+this.radius,this.y+this.radius); 226 | pJS.canvas.ctx.lineTo(this.x-this.radius,this.y+this.radius); 227 | pJS.canvas.ctx.closePath(); 228 | break; 229 | } 230 | 231 | pJS.canvas.ctx.fill(); 232 | } 233 | 234 | }; 235 | 236 | pJS.fn.particlesCreate = function(){ 237 | for(var i = 0; i < pJS.particles.nb; i++) { 238 | pJS.particles.array.push(new pJS.fn.particle(pJS.particles.color_rgb, pJS.particles.opacity)); 239 | } 240 | }; 241 | 242 | pJS.fn.particlesAnimate = function(){ 243 | for(var i = 0; i < pJS.particles.array.length; i++){ 244 | /* the particle */ 245 | var p = pJS.particles.array[i]; 246 | 247 | /* move the particle */ 248 | p.x += p.vx * (pJS.particles.anim.speed/2); 249 | p.y += p.vy * (pJS.particles.anim.speed/2); 250 | 251 | /* change particle position if it is out of canvas */ 252 | if(p.x - p.radius > pJS.canvas.w) p.x = p.radius; 253 | else if(p.x + p.radius < 0) p.x = pJS.canvas.w + p.radius; 254 | if(p.y - p.radius > pJS.canvas.h) p.y = p.radius; 255 | else if(p.y + p.radius < 0) p.y = pJS.canvas.h + p.radius; 256 | 257 | /* Check distance between each particle and mouse position */ 258 | for(var j = i + 1; j < pJS.particles.array.length; j++){ 259 | var p2 = pJS.particles.array[j]; 260 | 261 | /* link particles if enable */ 262 | if(pJS.particles.line_linked.enable_auto){ 263 | pJS.fn.vendors.distanceParticles(p,p2); 264 | } 265 | 266 | /* set interactivity if enable */ 267 | if(pJS.interactivity.enable){ 268 | 269 | /* interactivity mode */ 270 | switch(pJS.interactivity.mode){ 271 | case 'grab': 272 | pJS.fn.vendors.interactivity.grabParticles(p,p2); 273 | break; 274 | } 275 | 276 | } 277 | 278 | 279 | } 280 | } 281 | }; 282 | 283 | pJS.fn.particlesDraw = function(){ 284 | /* clear canvas */ 285 | pJS.canvas.ctx.clearRect(0, 0, pJS.canvas.w, pJS.canvas.h); 286 | 287 | /* move particles */ 288 | pJS.fn.particlesAnimate(); 289 | 290 | /* draw each particle */ 291 | for(var i = 0; i < pJS.particles.array.length; i++){ 292 | var p = pJS.particles.array[i]; 293 | p.draw('rgba('+p.color.r+','+p.color.g+','+p.color.b+','+p.opacity+')'); 294 | } 295 | 296 | }; 297 | 298 | pJS.fn.particlesRemove = function(){ 299 | pJS.particles.array = []; 300 | }; 301 | 302 | 303 | /* ---------- VENDORS functions ------------ */ 304 | 305 | pJS.fn.vendors.distanceParticles = function(p1, p2){ 306 | 307 | var dx = p1.x - p2.x, 308 | dy = p1.y - p2.y, 309 | dist = Math.sqrt(dx*dx + dy*dy); 310 | 311 | /* Check distance between particle and mouse mos */ 312 | if(dist <= pJS.particles.line_linked.distance) { 313 | 314 | /* draw the line */ 315 | var color_line = pJS.particles.line_linked.color_rgb_line; 316 | pJS.canvas.ctx.beginPath(); 317 | pJS.canvas.ctx.strokeStyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+ (pJS.particles.line_linked.opacity-dist/pJS.particles.line_linked.distance) +')'; 318 | pJS.canvas.ctx.moveTo(p1.x, p1.y); 319 | pJS.canvas.ctx.lineTo(p2.x, p2.y); 320 | pJS.canvas.ctx.lineWidth = pJS.particles.line_linked.width; 321 | pJS.canvas.ctx.stroke(); 322 | pJS.canvas.ctx.closePath(); 323 | 324 | /* condensed particles */ 325 | if(pJS.particles.line_linked.condensed_mode.enable){ 326 | var dx = p1.x - p2.x, 327 | dy = p1.y - p2.y, 328 | ax = dx/(pJS.particles.line_linked.condensed_mode.rotateX*1000), 329 | ay = dy/(pJS.particles.line_linked.condensed_mode.rotateY*1000); 330 | p2.vx += ax; 331 | p2.vy += ay; 332 | } 333 | 334 | } 335 | }; 336 | 337 | pJS.fn.vendors.interactivity.listeners = function(){ 338 | 339 | /* init el */ 340 | if(pJS.interactivity.detect_on == 'window'){ 341 | var detect_el = window; 342 | }else{ 343 | var detect_el = pJS.canvas.el; 344 | } 345 | 346 | /* el on mousemove */ 347 | detect_el.onmousemove = function(e){ 348 | 349 | if(detect_el == window){ 350 | var pos_x = e.clientX, 351 | pos_y = e.clientY; 352 | } 353 | else{ 354 | var pos_x = e.offsetX||e.clientX, 355 | pos_y = e.offsetY||e.clientY; 356 | } 357 | 358 | if(pJS){ 359 | 360 | pJS.interactivity.mouse.pos_x = pos_x; 361 | pJS.interactivity.mouse.pos_y = pos_y; 362 | 363 | if(pJS.retina){ 364 | pJS.interactivity.mouse.pos_x *= pJS.canvas.pxratio; 365 | pJS.interactivity.mouse.pos_y *= pJS.canvas.pxratio; 366 | } 367 | 368 | pJS.interactivity.status = 'mousemove'; 369 | } 370 | 371 | }; 372 | 373 | /* el on onmouseleave */ 374 | detect_el.onmouseleave = function(e){ 375 | 376 | if(pJS){ 377 | pJS.interactivity.mouse.pos_x = 0; 378 | pJS.interactivity.mouse.pos_y = 0; 379 | pJS.interactivity.status = 'mouseleave'; 380 | } 381 | 382 | }; 383 | 384 | /* el on onclick */ 385 | if(pJS.interactivity.events.onclick.enable){ 386 | switch(pJS.interactivity.events.onclick.mode){ 387 | case 'push': 388 | detect_el.onclick = function(e){ 389 | if(pJS){ 390 | for(var i = 0; i < pJS.interactivity.events.onclick.nb; i++){ 391 | pJS.particles.array.push( 392 | new pJS.fn.particle( 393 | pJS.particles.color_rgb, 394 | pJS.particles.opacity, 395 | { 396 | 'x': pJS.interactivity.mouse.pos_x, 397 | 'y': pJS.interactivity.mouse.pos_y 398 | } 399 | ) 400 | ) 401 | } 402 | } 403 | } 404 | break; 405 | 406 | case 'remove': 407 | detect_el.onclick = function(e){ 408 | pJS.particles.array.splice(0, pJS.interactivity.events.onclick.nb); 409 | } 410 | break; 411 | } 412 | } 413 | }; 414 | 415 | 416 | pJS.fn.vendors.interactivity.grabParticles = function(p1, p2){ 417 | var dx = p1.x - p2.x, 418 | dy = p1.y - p2.y, 419 | dist = Math.sqrt(dx*dx + dy*dy); 420 | 421 | var dx_mouse = p1.x - pJS.interactivity.mouse.pos_x, 422 | dy_mouse = p1.y - pJS.interactivity.mouse.pos_y, 423 | dist_mouse = Math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse); 424 | 425 | /* Check distance between 2 particles + Check distance between 1 particle and mouse position */ 426 | if(dist <= pJS.particles.line_linked.distance && dist_mouse <= pJS.interactivity.mouse.distance && pJS.interactivity.status == 'mousemove'){ 427 | /* Draw the line */ 428 | var color_line = pJS.particles.line_linked.color_rgb_line; 429 | pJS.canvas.ctx.beginPath(); 430 | pJS.canvas.ctx.strokeStyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+ (pJS.interactivity.line_linked.opacity-dist_mouse/pJS.interactivity.mouse.distance) +')'; 431 | pJS.canvas.ctx.moveTo(p1.x, p1.y); 432 | pJS.canvas.ctx.lineTo(pJS.interactivity.mouse.pos_x, pJS.interactivity.mouse.pos_y); 433 | pJS.canvas.ctx.lineWidth = pJS.particles.line_linked.width; 434 | pJS.canvas.ctx.stroke(); 435 | pJS.canvas.ctx.closePath(); 436 | } 437 | }; 438 | 439 | pJS.fn.vendors.destroy = function(){ 440 | cancelAnimationFrame(pJS.fn.requestAnimFrame); 441 | canvas_el.remove(); 442 | delete pJS; 443 | }; 444 | 445 | 446 | /* --------- LAUNCH ----------- */ 447 | 448 | function launchParticles(){ 449 | pJS.fn.canvasInit(); 450 | pJS.fn.canvasSize(); 451 | pJS.fn.canvasPaint(); 452 | pJS.fn.particlesCreate(); 453 | pJS.fn.particlesDraw(); 454 | }; 455 | 456 | 457 | function launchAnimation(){ 458 | pJS.fn.particlesDraw(); 459 | pJS.fn.requestAnimFrame = requestAnimFrame(launchAnimation); 460 | }; 461 | 462 | 463 | launchParticles(); 464 | 465 | if(pJS.particles.anim.enable){ 466 | launchAnimation(); 467 | } 468 | 469 | if(pJS.interactivity.enable){ 470 | pJS.fn.vendors.interactivity.listeners(); 471 | } 472 | 473 | 474 | }; 475 | 476 | /* --- VENDORS --- */ 477 | 478 | window.requestAnimFrame = (function(){ 479 | return window.requestAnimationFrame || 480 | window.webkitRequestAnimationFrame || 481 | window.mozRequestAnimationFrame || 482 | window.oRequestAnimationFrame || 483 | window.msRequestAnimationFrame || 484 | function(callback){ 485 | window.setTimeout(callback, 1000 / 60); 486 | }; 487 | })(); 488 | 489 | window.cancelRequestAnimFrame = ( function() { 490 | return window.cancelAnimationFrame || 491 | window.webkitCancelRequestAnimationFrame || 492 | window.mozCancelRequestAnimationFrame || 493 | window.oCancelRequestAnimationFrame || 494 | window.msCancelRequestAnimationFrame || 495 | clearTimeout 496 | } )(); 497 | 498 | function hexToRgb(hex){ 499 | // By Tim Down - http://stackoverflow.com/a/5624139/3493650 500 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 501 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 502 | hex = hex.replace(shorthandRegex, function(m, r, g, b) { 503 | return r + r + g + g + b + b; 504 | }); 505 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 506 | return result ? { 507 | r: parseInt(result[1], 16), 508 | g: parseInt(result[2], 16), 509 | b: parseInt(result[3], 16) 510 | } : null; 511 | }; 512 | 513 | 514 | /* --- LAUNCH --- */ 515 | 516 | window.particlesJS = function(tag_id, params){ 517 | 518 | /* no string id? so it's object params, and set the id with default id */ 519 | if(typeof(tag_id) != 'string'){ 520 | params = tag_id; 521 | tag_id = 'particles-js'; 522 | } 523 | 524 | /* no id? set the id to default id */ 525 | if(!tag_id){ 526 | tag_id = 'particles-js'; 527 | } 528 | 529 | /* create canvas element */ 530 | var canvas_el = document.createElement('canvas'); 531 | 532 | /* set size canvas */ 533 | canvas_el.style.width = "100%"; 534 | canvas_el.style.height = "100%"; 535 | 536 | /* append canvas */ 537 | var canvas = document.getElementById(tag_id).appendChild(canvas_el); 538 | 539 | /* launch particle.js */ 540 | if(canvas != null){ 541 | launchParticlesJS(tag_id, params); 542 | } 543 | 544 | }; -------------------------------------------------------------------------------- /blog/static/js/particles.min.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------- 2 | /* Author : Vincent Garreau - vincentgarreau.com 3 | /* MIT license: http://opensource.org/licenses/MIT 4 | /* GitHub : https://github.com/VincentGarreau/particles.js 5 | /* How to use? : Check the GitHub README 6 | /* v1.0.3 7 | /* ----------------------------------------------- */ 8 | function launchParticlesJS(a,e){var i=document.querySelector("#"+a+" > canvas");pJS={canvas:{el:i,w:i.offsetWidth,h:i.offsetHeight},particles:{color:"#fff",shape:"circle",opacity:1,size:2.5,size_random:true,nb:200,line_linked:{enable_auto:true,distance:100,color:"#fff",opacity:1,width:1,condensed_mode:{enable:true,rotateX:65000,rotateY:65000}},anim:{enable:true,speed:1},array:[]},interactivity:{enable:true,mouse:{distance:100},detect_on:"canvas",mode:"grab",line_linked:{opacity:1},events:{onclick:{enable:true,mode:"push",nb:4}}},retina_detect:false,fn:{vendors:{interactivity:{}}}};if(e){if(e.particles){var b=e.particles;if(b.color){pJS.particles.color=b.color}if(b.shape){pJS.particles.shape=b.shape}if(b.opacity){pJS.particles.opacity=b.opacity}if(b.size){pJS.particles.size=b.size}if(b.size_random==false){pJS.particles.size_random=b.size_random}if(b.nb){pJS.particles.nb=b.nb}if(b.line_linked){var j=b.line_linked;if(j.enable_auto==false){pJS.particles.line_linked.enable_auto=j.enable_auto}if(j.distance){pJS.particles.line_linked.distance=j.distance}if(j.color){pJS.particles.line_linked.color=j.color}if(j.opacity){pJS.particles.line_linked.opacity=j.opacity}if(j.width){pJS.particles.line_linked.width=j.width}if(j.condensed_mode){var g=j.condensed_mode;if(g.enable==false){pJS.particles.line_linked.condensed_mode.enable=g.enable}if(g.rotateX){pJS.particles.line_linked.condensed_mode.rotateX=g.rotateX}if(g.rotateY){pJS.particles.line_linked.condensed_mode.rotateY=g.rotateY}}}if(b.anim){var k=b.anim;if(k.enable==false){pJS.particles.anim.enable=k.enable}if(k.speed){pJS.particles.anim.speed=k.speed}}}if(e.interactivity){var c=e.interactivity;if(c.enable==false){pJS.interactivity.enable=c.enable}if(c.mouse){if(c.mouse.distance){pJS.interactivity.mouse.distance=c.mouse.distance}}if(c.detect_on){pJS.interactivity.detect_on=c.detect_on}if(c.mode){pJS.interactivity.mode=c.mode}if(c.line_linked){if(c.line_linked.opacity){pJS.interactivity.line_linked.opacity=c.line_linked.opacity}}if(c.events){var d=c.events;if(d.onclick){var h=d.onclick;if(h.enable==false){pJS.interactivity.events.onclick.enable=false}if(h.mode!="push"){pJS.interactivity.events.onclick.mode=h.mode}if(h.nb){pJS.interactivity.events.onclick.nb=h.nb}}}}pJS.retina_detect=e.retina_detect}pJS.particles.color_rgb=hexToRgb(pJS.particles.color);pJS.particles.line_linked.color_rgb_line=hexToRgb(pJS.particles.line_linked.color);if(pJS.retina_detect&&window.devicePixelRatio>1){pJS.retina=true;pJS.canvas.pxratio=window.devicePixelRatio;pJS.canvas.w=pJS.canvas.el.offsetWidth*pJS.canvas.pxratio;pJS.canvas.h=pJS.canvas.el.offsetHeight*pJS.canvas.pxratio;pJS.particles.anim.speed=pJS.particles.anim.speed*pJS.canvas.pxratio;pJS.particles.line_linked.distance=pJS.particles.line_linked.distance*pJS.canvas.pxratio;pJS.particles.line_linked.width=pJS.particles.line_linked.width*pJS.canvas.pxratio;pJS.interactivity.mouse.distance=pJS.interactivity.mouse.distance*pJS.canvas.pxratio}pJS.fn.canvasInit=function(){pJS.canvas.ctx=pJS.canvas.el.getContext("2d")};pJS.fn.canvasSize=function(){pJS.canvas.el.width=pJS.canvas.w;pJS.canvas.el.height=pJS.canvas.h;window.onresize=function(){if(pJS){pJS.canvas.w=pJS.canvas.el.offsetWidth;pJS.canvas.h=pJS.canvas.el.offsetHeight;if(pJS.retina){pJS.canvas.w*=pJS.canvas.pxratio;pJS.canvas.h*=pJS.canvas.pxratio}pJS.canvas.el.width=pJS.canvas.w;pJS.canvas.el.height=pJS.canvas.h;pJS.fn.canvasPaint();if(!pJS.particles.anim.enable){pJS.fn.particlesRemove();pJS.fn.canvasRemove();f()}}}};pJS.fn.canvasPaint=function(){pJS.canvas.ctx.fillRect(0,0,pJS.canvas.w,pJS.canvas.h)};pJS.fn.canvasRemove=function(){pJS.canvas.ctx.clearRect(0,0,pJS.canvas.w,pJS.canvas.h)};pJS.fn.particle=function(n,o,m){this.x=m?m.x:Math.random()*pJS.canvas.w;this.y=m?m.y:Math.random()*pJS.canvas.h;this.radius=(pJS.particles.size_random?Math.random():1)*pJS.particles.size;if(pJS.retina){this.radius*=pJS.canvas.pxratio}this.color=n;this.opacity=o;this.vx=-0.5+Math.random();this.vy=-0.5+Math.random();this.draw=function(){pJS.canvas.ctx.fillStyle="rgba("+this.color.r+","+this.color.g+","+this.color.b+","+this.opacity+")";pJS.canvas.ctx.beginPath();switch(pJS.particles.shape){case"circle":pJS.canvas.ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,false);break;case"edge":pJS.canvas.ctx.rect(this.x,this.y,this.radius*2,this.radius*2);break;case"triangle":pJS.canvas.ctx.moveTo(this.x,this.y-this.radius);pJS.canvas.ctx.lineTo(this.x+this.radius,this.y+this.radius);pJS.canvas.ctx.lineTo(this.x-this.radius,this.y+this.radius);pJS.canvas.ctx.closePath();break}pJS.canvas.ctx.fill()}};pJS.fn.particlesCreate=function(){for(var m=0;mpJS.canvas.w){q.x=q.radius}else{if(q.x+q.radius<0){q.x=pJS.canvas.w+q.radius}}if(q.y-q.radius>pJS.canvas.h){q.y=q.radius}else{if(q.y+q.radius<0){q.y=pJS.canvas.h+q.radius}}for(var m=n+1;m围脖blog -- page not found 7 | 10 | {% endblock %} 11 | 12 | {% block inside_row %} 13 |
14 |
15 |

404

16 |

page not found

17 |
18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block head %} 5 | {{ super() }} 6 | 围脖blog -- 500 7 | 10 | {% endblock %} 11 | 12 | {% block inside_row %} 13 |
14 |
15 |

500

16 |
17 |
18 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/_comment.html: -------------------------------------------------------------------------------- 1 | {% import "bootstrap/wtf.html" as wtf %} 2 | 3 |
4 |
5 | 发表评论 6 |
7 |
8 | {% if current_user.is_authenticated %} 9 |
10 | {{ commentform.csrf_token }} 11 |
12 | {{ commentform.body.label }} 13 | 16 |
17 |
18 |
19 | 20 |
21 | 22 | 54 | {% else %} 55 |
你还没有登录
56 | {% endif %} 57 |
58 |
-------------------------------------------------------------------------------- /blog/templates/_formhelpers.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field) %} 2 |
{{ field.label }} 3 |
{{ field(**kwargs)|safe }} {% if field.errors %} 4 |
    5 | {% for error in field.errors %} 6 |
  • {{ error }}
  • 7 | {% endfor %} 8 |
9 | {% endif %} 10 |
11 | {% endmacro %} -------------------------------------------------------------------------------- /blog/templates/_macros.html: -------------------------------------------------------------------------------- 1 | {% macro pagination_widget(pagination, endpoint) %} 2 |
    3 | 4 | 6 | « 7 | 8 | 9 | {% for p in pagination.iter_pages() %} 10 | {% if p %} 11 | {% if p == pagination.page %} 12 |
  • 13 | {{ p }} 14 |
  • 15 | {% else %} 16 |
  • 17 | {{ p }} 18 |
  • 19 | {% endif %} 20 | {% else %} 21 |
  • 22 | {% endif %} 23 | {% endfor %} 24 | 25 | 27 | » 28 | 29 | 30 |
31 | {% endmacro %} -------------------------------------------------------------------------------- /blog/templates/_user_left.html: -------------------------------------------------------------------------------- 1 | {% if current_user.is_authenticated %} 2 |
3 |
4 |
5 |
6 |
7 |
8 |

{{ current_user.username }}

9 | {% if current_user.abortme %} 10 |

{{ current_user.abortme }}

11 | {% endif %} 12 |
13 |
14 |
15 | 26 |
27 |
28 | 31 |
32 | {% endif %} 33 | {% if data %} 34 |
35 |
36 |

37 | 网站统计 38 |

39 |
40 |
41 |

会员:{{ data['uc'] }}

42 |

文章:{{ data['pc'] }}

43 |

问题:{{ data['qc'] }}

44 |

回答:{{ data['ac'] }}

45 |

评论:{{ data['cc'] }}

46 |
47 |
48 | {% endif %} 49 |
50 |
51 |

52 | 文章标签 53 |

54 |
55 |
56 | {% for tag in tags %} 57 | {% set color = random.choice(['default','primary','info']) %} 58 | {% set size = random.choice(['sm','xs']) %} 59 | 60 | {% endfor %} 61 |
62 |
63 | 64 |
65 |
66 |

67 | 问答区动态 68 |

69 |
70 |
    71 | {% for q in questions %} 72 |
  • 73 |

    74 | {{ q.title }} 75 |

    76 |
  • 77 | {% endfor %} 78 |
79 |
80 | 81 | 99 | 100 | -------------------------------------------------------------------------------- /blog/templates/ask/ask_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} 5 | 技术问答 -- 围脖blog 6 | {% endblock %} 7 | 8 | {% block right %} 9 |
10 |
11 |

技术问答

12 |
新问题
13 |
14 | 46 |
47 | {% endblock %} 48 | 49 | {% block modal %} 50 | {{ super() }} 51 | 52 | 127 | {% endblock %} 128 | 129 | -------------------------------------------------------------------------------- /blog/templates/ask/question.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} 5 | {{ q.title }} -- 围脖blog 6 | {% endblock %} 7 | 8 | 9 | {% block right %} 10 |
11 |
12 | 16 |
17 |
    18 |
  • 19 | {% if q.answer %} 20 |

    21 | {{ q.title }}

    24 | {% else %} 25 |

    {{ q.title }}

    26 | {% endif %} 27 | 32 | 33 | 36 | 37 | 40 | 43 | 50 | {% if User.query.filter_by(id=q.author).first().is_self(current_user) %} 51 | 54 | {% endif %} 55 |
  • 56 |
  • 57 |

    {{ q.body|safe }}

    58 |
  • 59 |
60 | 63 |
64 | 65 |
66 |
67 | {{ q.reply }} 个答案 68 |
69 | {% if answers == [] %} 70 |
71 |
这个问题暂时还没有人回答
72 |
73 | {% endif %} 74 | 127 |
128 | 129 | {% include '_comment.html' %} 130 | {% endblock %} 131 | 132 | 133 | -------------------------------------------------------------------------------- /blog/templates/base.html: -------------------------------------------------------------------------------- 1 | {% import "bootstrap/wtf.html" as wtf %} 2 | 3 | 4 | {% block head %} 5 | 6 | 7 | {% block title %} 8 | 围脖blog 9 | {% endblock %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 30 | 31 | {% endblock %} 32 | 33 | 34 |
35 |
36 | {% block navbar %} 37 | 100 | {% endblock %} 101 | 102 | {% block content %} 103 |
104 |
105 |
106 | {% block inside_row %} 107 | 109 | 118 |
119 | {% block left %} 120 | {% include '_user_left.html' %} 121 | {% endblock %} 122 |
123 | 124 | {% endblock %} 125 |
126 |
127 |
128 | {% endblock %} 129 | 130 | {% block modal %} 131 | 145 | 160 | {% endblock %} 161 |
162 |

163 |
164 | 165 | {% block scripts %} 166 | 167 | 168 | {% endblock %} 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /blog/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | {% import "_macros.html" as macros %} 4 | 5 | {% block right %} 6 |
7 | {% for p in posts %} 8 |
9 |
10 |

11 | {{ p.topic.topic }} 12 |

13 |
14 |
15 |
16 |

{{ p.head }}

17 |
18 |   22 | {{ p.post_author.username }}  23 | {{ p.clink }} Views 24 |
25 |
26 |
27 |     {{ (p.body|striptags) }} 28 |
29 |
30 | 37 |
38 | {% endfor %} 39 | {% if data['pagination'] %} 40 | 43 | {% endif %} 44 |
45 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block head %} {{ super() }} {% block title %} 2 | {{ user.username }}的主页 -- 围脖blog 3 | {% endblock %} 4 | 5 | {% endblock %} {% block right %} {% include 'user/_user_index_right.html' %} {% endblock %} -------------------------------------------------------------------------------- /blog/templates/manage/manage-index.html: -------------------------------------------------------------------------------- 1 | {% extends "manage/manage_base.html" %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 | {% block title %} 6 | 网站动态 -- 后台管理 -- 围脖blog 7 | {% endblock %} 8 | 17 | {% endblock %} 18 | 19 | 20 | {% block row %} 21 | 22 |
23 |
24 |

25 | 最近登录用户 26 |

27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for user in users %} 38 | 39 | 40 | 46 | 47 | 67 | {% endfor %} 68 | 69 |
用户名登录时间
{{ user.username }} 41 | 45 |
70 |
71 |
72 | 73 |
74 |
75 |

76 | 新文章 77 |

78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {% for post in posts %} 89 | 90 | 91 | 97 | 98 | 119 | {% endfor %} 120 | 121 |
作者发表时间
{{ post.post_author.username }} 92 | 96 |
122 |
123 |
124 | 125 |
126 |
127 |

128 | 新问题 129 |

130 |
131 |
132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | {% for q in questions %} 141 | 142 | 143 | 149 | 150 | 171 | {% endfor %} 172 | 173 |
提问者发布时间
{{ q.q_author.username }} 144 | 148 |
174 |
175 |
176 | 177 |
178 |
179 |

180 | 新评论 181 |

182 |
183 |
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | {% for c in comments %} 193 | 194 | 195 | 201 | 202 | 220 | {% endfor %} 221 | 222 |
评论人发表时间
{{ c.comment_author.username }} 196 | 200 |
223 |
224 |
225 | 226 | {% endblock %} 227 | 228 | 229 | -------------------------------------------------------------------------------- /blog/templates/manage/manage-user.html: -------------------------------------------------------------------------------- 1 | {% extends "manage/manage_base.html" %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 | {% block title %} 6 | 用户管理 -- 后台管理 -- 围脖blog 7 | {% endblock %} 8 | 38 | 39 | 40 | {% endblock %} 41 | 42 | 43 | {% block row %} 44 | 45 |
46 |
47 |

48 | 用户管理 / {% if ids == 'n' %}正常用户{% elif ids == 'd' %}屏蔽用户{% elif ids=='s' %}搜索结果{% endif %} 49 |

50 |
51 |
52 | 55 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 88 | 89 | 91 | 92 | 93 | 94 | 100 | 101 | 102 | 103 | {% endfor %} 125 | 126 |
ID用户名邮箱真实姓名状态最后登录文章提问
83 |
84 | 86 |
87 |
{{ user.id }}{{ user.username }}{{ user.email }}{{ user.name }}{{ user.ban }} 95 | 99 | {{ user.post.count() }}{{ user.question.count() }}
127 | {% if ids == 'n' %} 128 | 129 | {% elif ids == 'd' %} 130 | 131 | {% endif %} 132 |
133 |
134 | 156 | {% endblock %} 157 | -------------------------------------------------------------------------------- /blog/templates/manage/manage_base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block head %} 5 | {{ super() }} 6 | {% endblock %} 7 | 8 | {% block navbar %} 9 | 42 | {% endblock %} 43 | 44 | {% block inside_row %} 45 |
46 | {% block row %} 47 | {% endblock %} 48 |
49 | {% endblock %} 50 | 51 | -------------------------------------------------------------------------------- /blog/templates/manage/manage_topic.html: -------------------------------------------------------------------------------- 1 | {% extends "manage/manage_base.html" %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 | {% block title %} 6 | 主题管理 -- 后台管理 -- 围脖blog 7 | {% endblock %} 8 | 33 | 34 | 35 | {% endblock %} 36 | 37 | 38 | {% block row %} 39 | 40 |
41 |
42 |

43 | 主题管理 / {% if ids == 'n' %}正常主题{% elif ids == 'd' %}屏蔽主题{% elif ids == 's' %}搜索结果{% endif %} 44 |

45 |
46 |
47 | 50 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 78 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | 95 | 96 | 97 | 115 | {% endfor %} 116 | 117 |
ID主题名创建者创建时间激活点击量附属文章详情
79 |
80 | 81 |
82 |
{{ topic.id }}{{ topic.topic }}{{ topic.topic_author.username }} 87 | 91 | {{ topic.activation }}{{ topic.clink }}{{ topic.posts.count() }}查看
118 | {% if ids == 'n' %} 119 | 120 | {% elif ids == 'd' %} 121 | 122 | {% endif %} 123 |
124 |
125 | 147 | {% endblock %} 148 | -------------------------------------------------------------------------------- /blog/templates/post_for_tag.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | 5 | {% block title %} 6 | 围脖blog -- {{ tag }} 7 | {% endblock %} 8 | 9 | {% block right %} 10 |
11 |
12 |

标签: {{ tag }}

13 |
14 | {% if posts == [] %} 15 |
16 |
这里还没有文章哦~
17 |
18 | {% else %} 19 | 48 | {% endif %} 49 |
50 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | 5 | {% block title %} 6 | 围脖blog -- 搜索结果 7 | {% endblock %} 8 | 9 | 10 | {% block right %} 11 |
12 |
13 | 搜索结果 14 |
15 | {% if posts == [] %} 16 |
17 |
没有搜索到任何东西~
18 |
19 | {% else %} 20 | 49 | {% endif %} 50 |
{% endblock %} 51 | -------------------------------------------------------------------------------- /blog/templates/topics/new_post.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block head %} 5 | {{ super() }} 6 | 围脖blog -- 新帖子 7 | 15 | {% endblock %} 16 | 17 | 18 | {% block right %} 19 |
20 |
21 | 新帖子 22 |
23 |
24 |
25 | {{ form.csrf_token }} 26 |
27 | {{ form.head.label }} {{ form.head(class ="form-control")}} 28 |
29 |
30 | {{ form.postbody.label }} 31 | 34 |
35 |
36 |
37 |
38 | {{ form.tag.label }} {{ form.tag(class ="form-control")}} 39 |
40 |
41 | {{ form.topic.label }} {{ form.topic(class ="form-control")}} 42 |
43 | 44 |
45 | 46 | 56 |
57 | 58 |
59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /blog/templates/topics/post.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} 5 | {{ p.head }} -- 围脖blog 6 | {% endblock %} 7 | 8 | 9 | {% block right %} 10 |
11 |
12 | 17 |
18 | 54 | 61 |
62 | 63 |
64 |
65 | 文章评论 66 |
67 | {% if comments == [] %} 68 |
69 |
这篇文章暂时没有评论
70 |
71 | {% endif %} 72 |
    73 | {% for c in comments %} 74 |
    75 |
    76 | 77 |
    78 |
    79 | {{ c.comment_author.username }} 80 |

    81 | 85 |

    86 |
    87 |
    88 |

    89 | 92 |
    93 |
    95 |

    {{ c.body|safe }}

    96 |
    97 |
    98 | {% endfor %} 99 |
100 |
101 | 102 | {% include '_comment.html' %} 103 | {% endblock %} 104 | -------------------------------------------------------------------------------- /blog/templates/topics/topic.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% if title %} 5 | {% block title %} 6 | {{ title }} -- 围脖blog 7 | {% endblock %} 8 | {% endif %} 9 | 10 | 11 | {% block right %} 12 |
13 |
14 | 18 |
19 |
20 |
21 | 22 |

{{ t.topic }}

23 |
24 |
25 |

话题介绍:

26 |

{{ t.info }}

27 |
28 |
29 |

话题创建者: 30 | {{ t.topic_author.username }}

31 |

话题创建时间: 32 | 36 |

37 | 40 | 43 |

44 | 45 | 46 | 47 | {% if not f %} 48 | 49 | 50 | 51 | {% else %} 52 | 53 | 54 | 55 | {% endif %} 56 |

57 |
58 | 61 |
62 | 63 |
64 |
65 | 话题 {{ t.topic }} 下的帖子 66 |
67 | {% if Post.query.filter_by(tpoic=t.id).all() == [] %} 68 |
69 |
这个话题下还没有发表过帖子~
70 |
71 | {% endif %} 72 | 102 |
103 | 104 | 127 | {% endblock %} 128 | 129 | {% block scripts %} 130 | {{ super() }} 131 | 139 | 144 | {% endblock %} 145 | -------------------------------------------------------------------------------- /blog/templates/topics/topics.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %} 5 | 话题 -- 围脖blog 6 | {% endblock %} 7 | 8 | 9 | {% block right %} 10 | {% if topics %} 11 | {% for t in topics %} 12 |
13 |
{{ Post.query.filter_by(tpoic=t.id).count() }}
14 | 18 |
19 | {% endfor %} 20 | {% endif %} 21 | 22 |
23 |
24 | 25 |

新话题

26 |
27 | 28 |
29 | {% endblock %} 30 | 31 | {% block modal %} 32 | {{ super() }} 33 | 34 | 65 | {% endblock %} 66 | 67 | -------------------------------------------------------------------------------- /blog/templates/user/_followed_all.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for f in all_followed %} 13 | 14 | 15 | 21 | 22 | {% endfor %} 23 | 24 |
{{ user.username }}关注的人
关注的人关注时间
{{ f.follower.username }} 16 | 20 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /blog/templates/user/_follower_all.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for f in all_follower %} 13 | 14 | 15 | 21 | 22 | {% endfor %} 23 | 24 |
关注{{ user.username }}的人
关注者关注时间
{{ f.followed.username }} 16 | 20 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /blog/templates/user/_user_index_right.html: -------------------------------------------------------------------------------- 1 | {% import "bootstrap/wtf.html" as wtf %} 2 | {% if 'index' in index %} 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |

@{{ user.username }} {% if user.abortme %} {{ user.abortme }}{% endif %}

11 |

{{ user.email }}

12 |
关注者 {{ user.followers.count() }} 关注了 {{ user.followed.count() }}
13 |
14 |
15 | {% if current_user.is_authenticated and current_user.is_follow(user) %} 16 | 17 | 20 | {% elif not user.is_self(current_user) %} 21 | 22 | 25 | {% endif %} 26 | {% if current_user.is_authenticated and user.is_follow(current_user) %} 27 | 30 | {% endif %} 31 |
32 |
33 |
34 |
35 | {% if 'info' in index %} 36 | {% include 'user/_user_post.html' %} 37 | {% endif %} 38 | {% elif 'seting' in index %} 39 |
40 |
41 | 46 |
47 |
48 |
49 | {{ wtf.quick_form(form) }} 50 |
51 | ​
52 |
53 | 54 | {% endif %} 55 | {% if 'follower-all' in index %} 56 |
57 | {% include 'user/_follower_all.html' %} 58 |
59 | {% elif 'followed-all' in index %} 60 |
61 | {% include 'user/_followed_all.html' %} 62 |
63 | {% endif %} 64 | -------------------------------------------------------------------------------- /blog/templates/user/_user_post.html: -------------------------------------------------------------------------------- 1 | {% import "_macros.html" as macros %} 2 | 3 | 4 |
5 |
6 | 13 |
14 | {% if posts == [] %} 15 |
16 |
暂时没有动态
17 |
18 | {% else %} 19 |
    20 | {% for p in posts %} 21 |
  • 22 | {% if show == '1' %} 23 |
    {{ p.head }}
    24 |

    25 | 26 | 30 | 33 | 40 | 43 |

    44 | 45 | {% elif show == '2' %} 46 |
    回答了问题《{{ Question.query.filter_by(id=p.q_id).first().title }}》
    47 |

    48 | 55 |

    56 | {% elif show == '3' %} 57 |
    评论了文章《{{ Post.query.filter_by(id=p.post_id).first().head }}》
    58 |

    59 | 66 |

    67 | {% elif show == '4' %} 68 |
    {{ p.title }}
    69 |

    70 | 73 | 76 | 83 |

    84 | {% endif %} 85 |
  • 86 | {% endfor %} 87 | {% if pagination %} 88 | 91 | {% endif %} 92 |
93 | {% endif %} 94 |
-------------------------------------------------------------------------------- /blog/templates/user/messages.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | 5 | {% block title %} 6 | 站内消息 -- 围脖blog 7 | {% endblock %} 8 | 9 | 10 | {% block right %} 11 |
12 | 93 |
94 | 95 | 96 | 97 | 98 | 126 | 127 | {% endblock %} 128 | -------------------------------------------------------------------------------- /blog/templates/user/user_login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block head %} 5 | {{ super() }} 6 | 围脖blog -- 登录 7 | 18 | {% endblock %} 19 | 20 | {% block inside_row %} 21 | {% for message in get_flashed_messages() %} 22 |
23 | 24 | {{ message }} 25 |
26 | {% endfor %} 27 |
28 |

登录

29 | {{ wtf.quick_form(form) }} 30 |
31 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/user/user_register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block head %} 5 | {{ super() }} 6 | 围脖blog -- 注册 7 | 18 | {% endblock %} 19 | 20 | {% block inside_row %} 21 | {% for message in get_flashed_messages() %} 22 |
23 | 24 | {{ message }} 25 |
26 | {% endfor %} 27 | 28 | 32 |
33 |

注册

34 | {{ wtf.quick_form(form) }} 35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /blog/templates/user_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block left %} 3 |
4 |
5 |
6 |
7 |
8 |
9 |

用户名

10 |

用户个性签名

11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | 4 | basedir = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | class Config: 8 | SECRET_KEY = os.environ.get('SECRET_KEY') or '!@#$%^&*12345678' 9 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 10 | SQLALCHEMY_TRACK_MODIFICATIONS = False 11 | JSON_AS_ASCII = False 12 | SQLALCHEMY_RECORD_QUERIES = True 13 | FLASKY_DB_QUERY_TIMEOUT = 0.5 14 | DEBUG = True 15 | SQLALCHEMY_DATABASE_URI = 'mysql://root:1234@localhost:3306/weblog' 16 | # 邮件系统配置 17 | MAIL_USERNAME = os.environ.get('MAIL_USERNAME') 18 | MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') 19 | MAIL_SERVER = 'smtp.qq.com' 20 | MAIL_PORT = 465 21 | MAIL_USE_SSL = True 22 | FLASKY_MAIL_SENDER = "Weblog Admin <540918220@qq.com>" 23 | FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN') or '540918220@qq.com' 24 | FLASKY_MAIL_SUBJECT_PREFIX = '[Weblog]' 25 | 26 | @staticmethod 27 | def init_blog(blog): 28 | pass 29 | 30 | 31 | class DevelopmentConfig(Config): 32 | pass 33 | 34 | 35 | config = { 36 | 'development': DevelopmentConfig, 37 | } 38 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | from blog import create_blog 2 | from flask_script import Manager 3 | from blog import db 4 | from flask_migrate import Migrate, MigrateCommand 5 | 6 | blog = create_blog() 7 | manager = Manager(blog) 8 | migrate = Migrate(blog, db) 9 | manager.add_command('db', MigrateCommand) 10 | 11 | 12 | if __name__ == '__main__': 13 | manager.run() 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==0.9.7 2 | click==6.7 3 | dominate==2.3.1 4 | Flask==0.12.2 5 | Flask-Bootstrap==3.3.7.1 6 | Flask-Login==0.4.1 7 | Flask-Migrate==2.1.1 8 | Flask-Script==2.0.6 9 | Flask-SQLAlchemy==2.3.2 10 | Flask-Uploads==0.2.1 11 | Flask-WTF==0.14.2 12 | itsdangerous==0.24 13 | Jinja2==2.10 14 | Mako==1.0.7 15 | MarkupSafe==1.0 16 | mysqlclient==1.3.12 17 | pkg-resources==0.0.0 18 | python-dateutil==2.6.1 19 | python-editor==1.0.3 20 | six==1.11.0 21 | SQLAlchemy==1.2.1 22 | visitor==0.1.3 23 | Werkzeug==0.14.1 24 | WTForms==2.1 25 | --------------------------------------------------------------------------------