diff --git a/PersonalBlog-Flask/README.md b/PersonalBlog-Flask/README.md new file mode 100644 index 0000000..9bbf910 --- /dev/null +++ b/PersonalBlog-Flask/README.md @@ -0,0 +1,35 @@ +# flask-blog +A simple blog web project, use python-flask. + +## run +### install dep and env +>(install at global or virtualenv) +``` +pip install -r ./requirements.txt +``` +### create db +``` +python ./db_migrate.py +``` + +### run +`server: ` +``` +python ./run.py +``` +> default: Running on http://127.0.0.1:5000/ + +`browser:` +``` +http://127.0.0.1:5000/ +``` + +## feature +- sign in +- login +- logout +- avatar +- micro-blog +- follow/unfollow user +- user profile + \ No newline at end of file diff --git a/PersonalBlog-Flask/app/__init__.py b/PersonalBlog-Flask/app/__init__.py new file mode 100644 index 0000000..a27b577 --- /dev/null +++ b/PersonalBlog-Flask/app/__init__.py @@ -0,0 +1,16 @@ +import os +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_login import LoginManager +from config import basedir + +app = Flask(__name__) +app.config.from_object('config') + +db = SQLAlchemy(app) + +lm = LoginManager() +lm.init_app(app) +lm.login_view = 'login' + +from app import views, models #视图/模型模块 \ No newline at end of file diff --git a/PersonalBlog-Flask/app/forms.py b/PersonalBlog-Flask/app/forms.py new file mode 100644 index 0000000..64c7d53 --- /dev/null +++ b/PersonalBlog-Flask/app/forms.py @@ -0,0 +1,33 @@ +from flask_wtf import Form +from wtforms import StringField, BooleanField, TextAreaField +from wtforms.validators import DataRequired, Length +from app.models import User + +class LoginForm(Form): + # openid = StringField('openid', validators=[DataRequired()]) + nickname = StringField('nickname', validators=[DataRequired()]) + password = StringField('password', validators=[DataRequired()]) + email = StringField('email') + remember_me = BooleanField('remember_me', default=False) + +class EditForm(Form): + nickname = StringField('nicknamae', validators=[DataRequired()]) + about_me = TextAreaField('about_me', validators=[Length(min=0, max=140)]) + + def __init__(self, original_nickname, *args, **kwargs): + Form.__init__(self, *args, **kwargs) + self.original_nickname = original_nickname + + def validate(self): + if not Form.validate(self): + return false + if self.nickname.data == self.original_nickname: + return True + user = User.query.filter_by(nickname = self.nickname.data).first() + if user != None: + self.nickname.errors.append('This Name is already used! Please choose another one!') + return False + return True + +class PostForm(Form): + post = StringField('post', validators=[DataRequired()]) \ No newline at end of file diff --git a/PersonalBlog-Flask/app/models.py b/PersonalBlog-Flask/app/models.py new file mode 100644 index 0000000..4fdc99e --- /dev/null +++ b/PersonalBlog-Flask/app/models.py @@ -0,0 +1,77 @@ +from app import db +from hashlib import md5 + +followers = db.Table('followers', + db.Column('follower_id', db.Integer, db.ForeignKey('user.id')), + db.Column('followed_id', db.Integer, db.ForeignKey('user.id')) +) + +class User(db.Model): + id = db.Column(db.Integer, primary_key = True) + nickname = db.Column(db.String(64), index=True, unique = True) + password = db.Column(db.String(120), index=True) + email = db.Column(db.String(120), index = True) + about_me = db.Column(db.String(140)) + last_seen = db.Column(db.DateTime) + + posts = db.relationship('Post', backref='author', lazy='dynamic') + followed = db.relationship('User', + secondary = followers, + primaryjoin = (followers.c.follower_id == id), + secondaryjoin = (followers.c.followed_id == id), + backref = db.backref('followers',lazy='dynamic'), + lazy = 'dynamic') + + is_authenticated = True + is_active = True + is_anonymous = False + + def get_id(self): + try: + return unicode(self.id) + except NameError: + return str(self.id) + + def avatar(self, size): + return 'http://www.gravatar.com/avatar/' + md5(self.email.encode("utf-8")).hexdigest() + '?d=mm&s=' + str(size) + + # be used to debug + def __repr__(self): + return '' % (self.nickname) + + @staticmethod + def make_unique_nickname(nickname): + if User.query.filter_by(nickname = nickname).first() == None: + return nickname + version = 2 + while True: + new_nickname = nickname + str(version) + if User.query.filter_by(nickname = new_nickname).first() == None: + break + version += 1 + return new_nickname + + def follow(self, user): + if not self.is_following(user): + self.followed.append(user) + return self + def unfollow(self, user): + if self.is_following(user): + self.followed.remove(user) + return self + def is_following(self, user): + return self.followed.filter(followers.c.followed_id == user.id).count() > 0 + + def followed_posts(self): + return Post.query.join(followers, + (followers.c.followed_id == Post.user_id)).filter(followers.c.follower_id == self.id).order_by(Post.timestamp.desc()) + + +class Post(db.Model): + id = db.Column(db.Integer, primary_key = True) + body = db.Column(db.String(140)) + timestamp = db.Column(db.DateTime) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + + def __repr__(self): + return '' % (self.body) \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/404.html b/PersonalBlog-Flask/app/templates/404.html new file mode 100644 index 0000000..82f34c4 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/404.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} + +{% block content %} +

File Not Found!

+

Back to Index .

+{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/500.html b/PersonalBlog-Flask/app/templates/500.html new file mode 100644 index 0000000..d381a63 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/500.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} + +{% block content %} +

An unexpected error has occurred!

+

Back to Index .

+{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/base.html b/PersonalBlog-Flask/app/templates/base.html new file mode 100644 index 0000000..3782655 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/base.html @@ -0,0 +1,31 @@ + + + {%if title %} + {{title}} - microblog + {%else%} + microblog + {%endif%} + + +
MicroBlog: + Home + {% if g.user.is_authenticated %} + | Log out + | My Profile + {% else %} + Sign In + {% endif %} +
+
+ {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} + {% block content %} {% endblock %} + + \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/edit.html b/PersonalBlog-Flask/app/templates/edit.html new file mode 100644 index 0000000..a50c2dc --- /dev/null +++ b/PersonalBlog-Flask/app/templates/edit.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} +

Edit Your Profile

+
+ {{ form.hidden_tag() }} + + + + + + + + + + + + + +
Your nickname: + {{ form.nickname(size=24) }} + {% for error in form.errors.nickname %} +
[ {{ error }}] + {% endfor %} +
About yourself:{{ form.about_me(cols=32, rows=4) }}
+
+{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/index.html b/PersonalBlog-Flask/app/templates/index.html new file mode 100644 index 0000000..2848ad4 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/index.html @@ -0,0 +1,42 @@ + +{% extends "base.html" %} +{%block content %} +

Hello, {{g.user.nickname}}!

+ +
+ {{ form.hidden_tag() }} + + + + + + + + + + + +
Say something: {{ form.post(size = 30, maxlength=140) }} + {% for error in form.errors.post %} + [{{ error }}] + {% endfor %} +
+
+ +
+ {% for post in posts.items %} + {% include 'post.html' %} +
+ {% endfor%} + + {% if posts.has_prev %} + 上一页 + {% else %} + 上一页 + {% endif %} | + {% if posts.has_next %} + 下一页 + {% else %} + 下一页 + {% endif %} +{% endblock %} diff --git a/PersonalBlog-Flask/app/templates/login.html b/PersonalBlog-Flask/app/templates/login.html new file mode 100644 index 0000000..071a17f --- /dev/null +++ b/PersonalBlog-Flask/app/templates/login.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} + +

Login

+
+ {{form.hidden_tag()}} +

+ Please enter your name and password!
+ UserName: {{ form.nickname(size=80) }}
+ Password: {{ form.password(size=120) }}
+ Email: {{ form.email(size=120) }}
+

+ +

{{form.remember_me}} Remember Me

+

+
+{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/post.html b/PersonalBlog-Flask/app/templates/post.html new file mode 100644 index 0000000..2087134 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/post.html @@ -0,0 +1,6 @@ + + + + + +
{{ post.author.nickname }} says:
{{ post.body }}
\ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/sign_in.html b/PersonalBlog-Flask/app/templates/sign_in.html new file mode 100644 index 0000000..9c64f2c --- /dev/null +++ b/PersonalBlog-Flask/app/templates/sign_in.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block content %} + +

Sign In

+
+ {{form.hidden_tag()}} +

+ Please enter your name and password!
+ UserName: {{ form.nickname(size=80) }}
+ Password: {{ form.password(size=120) }}
+ Email: {{ form.email(size=120) }}
+

+ +

{{form.remember_me}} Remember Me

+

+
+{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/templates/user.html b/PersonalBlog-Flask/app/templates/user.html new file mode 100644 index 0000000..e26b965 --- /dev/null +++ b/PersonalBlog-Flask/app/templates/user.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% block content %} + + + + + +
+

User: {{user.nickname}}

+ {% if user.email %}

{{ user.email }}

{% endif %} + {% if user.about_me %}

{{ user.about_me }}

{% endif %} + {% if user.last_seen %}

Last Seen on: {{ user.last_seen }}

{% endif %} +

+ {{ user.followers.count() }} followers | + {% if user.id == g.user.id %} + Edit AboutMe + {% elif not g.user.is_following(user) %} + Follow + {% else %} + UnFollow + {% endif %} +

+
+ +
+ {% for post in posts.items %} + {% include 'post.html' %} +
+ {% endfor %} + + {% if posts.has_prev %} + 上一页 + {% else %} + 上一页 + {% endif %} | + {% if posts.has_next %} + 下一页 + {% else %} + 下一页 + {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/PersonalBlog-Flask/app/views.py b/PersonalBlog-Flask/app/views.py new file mode 100644 index 0000000..cebf3fa --- /dev/null +++ b/PersonalBlog-Flask/app/views.py @@ -0,0 +1,198 @@ +from flask import render_template, flash, redirect, session, url_for, request, g +from app import app, db, lm +from .models import User, Post +from .forms import LoginForm, EditForm, PostForm +from flask_login import login_user, logout_user, current_user, login_required +from datetime import datetime +from config import POSTS_PER_PAGE + +@app.route('/', methods=['GET', 'POST']) +@app.route('/index', methods=['GET', 'POST']) +@app.route('/index/', methods=['GET', 'POST']) +@login_required +def index(page = 1): + form = PostForm() + if form.validate_on_submit(): + post = Post(body=form.post.data, timestamp=datetime.utcnow(), author=g.user) + db.session.add(post) + db.session.commit() + flash('Your post is now released!') + return redirect(url_for('index')) + + posts = [] + + if g.user is None or not g.user.is_authenticated : + users = User.query.all() + for u in users: + posts.append(u.followed_posts()) + else: + posts = g.user.followed_posts() + posts = posts.paginate(page, POSTS_PER_PAGE, False)#paginate对象,items属性才是list + return render_template("index.html", title="Home",form=form, posts=posts) + +#注册 +@app.route('/sign_in', methods=['GET', 'POST']) +def sign_in(): + if g.user != None and g.user.is_authenticated : + flash("Current user exist! Need not to sign in.") + return redirect(url_for('index')) + + form = LoginForm() + if form.validate_on_submit(): + session['remember_me'] = form.remember_me.data + if create_user(form): + flash('Create User Succeed.') + return redirect(request.args.get('next') or url_for('index')) + else: + flash('Sign in failed,Name have been used! Please choose another one.') + + return render_template('sign_in.html', title = "Sign In", form=form) + +#创建用户,没有时便创建加登录 +def create_user(user_form): + if user_form.nickname is None or user_form.nickname.data == "": + return False + + user = User.query.filter_by(nickname=user_form.nickname.data).first() + if user is not None: + return False + nickname = User.make_unique_nickname(user_form.nickname.data) + user = User(nickname=user_form.nickname.data, password=user_form.password.data,email=user_form.email.data) + db.session.add(user) + db.session.commit() + db.session.add(user.follow(user)) + db.session.commit() + + remember_me = False + if 'remember_me' in session: + remember_me = session['remember_me'] + session.pop('remember_me', None) + login_user(user, remember= remember_me) + return True + +@app.route('/login', methods = ['GET', 'POST']) +def login(): + if g.user is not None and g.user.is_authenticated: + flash('Current user exist! Do not login repetitious.') + return redirect(url_for('index')) + + form = LoginForm() + if form.validate_on_submit(): + session['remember_me'] = form.remember_me.data + if validate_user(form): + return redirect(request.args.get('next') or url_for('index')) + else: + flash("User Info is invalid! Can not login.") + + return render_template('login.html', title = "Login", form=form) + +#验证用户是否存在于数据库,不存在则提示进入注册页面 +def validate_user(user_form): + if user_form.nickname is None or user_form.nickname.data == "": + return False + + user = User.query.filter_by(nickname=user_form.nickname.data).first() + if user is None: + return False + if user.nickname != user_form.nickname.data or user.password != user_form.password.data: + return False + + remember_me = False + if 'remember_me' in session: + remember_me = session['remember_me'] + session.pop('remember_me', None) + login_user(user, remember= remember_me) + return True + +@app.route('/logout') +def logout(): + logout_user() + return redirect(url_for('index')) + +@lm.user_loader +def load_user(id): + return User.query.get(int(id)) + +@app.before_request +def before_request(): + g.user = current_user + if g.user.is_authenticated: + g.user.last_seen = datetime.utcnow() + db.session.add(g.user) + db.session.commit() + +@app.route('/user/') +@app.route('/user//') +@login_required +def user(nickname, page=1): + user = User.query.filter_by(nickname=nickname).first() + if user == None: + flash('User ' + nickname + ' not found.') + return redirect(url_for('index')) + posts = user.posts.paginate(page, POSTS_PER_PAGE, False) + return render_template('user.html', user = user, posts = posts) + +@app.route('/edit', methods=['GET', 'POST']) +@login_required +def edit(): + form = EditForm(g.user.nickname) + if form.validate_on_submit(): + g.user.nickname = form.nickname.data + g.user.about_me = form.about_me.data + db.session.add(g.user) + db.session.commit() + flash('Your changes have been saved!') + return redirect(url_for('edit')) + else: + form.nickname.data = g.user.nickname + form.about_me.data = g.user.about_me + return render_template('edit.html', form=form) + +@app.errorhandler(404) +def internal_error(error): + return render_template('404.html'), 404 + +@app.errorhandler(500) +def internal_error(error): + db.session.rollback() + return render_template('500.html'),500 + +@app.route('/follow/') +@login_required +def follow(nickname): + user = User.query.filter_by(nickname=nickname).first() + if user is None: + flash('User %s is not found!' % nickname) + return redirect(url_for('index')) + + if user == g.user: + flash('You can not follow yourself! ') + return redirect(url_for('user', nickname=nickname)) + + u = g.user.follow(user) + if u is None: + flash('Can not follow ' + nickname + ".") + return redirect(url_for('user',nickname=nickname)) + db.session.add(u) + db.session.commit() + flash('You are now following ' + nickname +"!") + return redirect(url_for('user', nickname=nickname)) + +@app.route('/unfollow/') +@login_required +def unfollow(nickname): + user = User.query.filter_by(nickname=nickname).first() + if user is None: + flash('User %s not found.' % nickname) + return redirect(url_for('index')) + if user == g.user: + flash('You can\'t unfollow yourself!') + return redirect(url_for('user', nickname=nickname)) + u = g.user.unfollow(user) + if u is None: + flash('Cannot unfollow ' + nickname + '.') + return redirect(url_for('user', nickname=nickname)) + db.session.add(u) + db.session.commit() + flash('You have stopped following ' + nickname + '.') + return redirect(url_for('user', nickname=nickname)) \ No newline at end of file diff --git a/PersonalBlog-Flask/config.py b/PersonalBlog-Flask/config.py new file mode 100644 index 0000000..f896b2d --- /dev/null +++ b/PersonalBlog-Flask/config.py @@ -0,0 +1,21 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + +SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') +SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') + +CSRF_ENABLED = True +SECRET_KEY = "you-will-never-guess" + +#pagination +POSTS_PER_PAGE = 5 + +#open id examples +OPENID_PROVIDERS = [ + { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' }, + { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' }, + { 'name': 'AOL', 'url': 'http://openid.aol.com/' }, + { 'name': 'Flickr', 'url': 'http://www.flickr.com/' }, + { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }] + + diff --git a/PersonalBlog-Flask/db_downgrade.py b/PersonalBlog-Flask/db_downgrade.py new file mode 100644 index 0000000..fe0473e --- /dev/null +++ b/PersonalBlog-Flask/db_downgrade.py @@ -0,0 +1,7 @@ +from migrate.versioning import api +from config import SQLALCHEMY_MIGRATE_REPO, SQLALCHEMY_DATABASE_URI + +v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1) +v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +print('Current database version: ' + str(v)) \ No newline at end of file diff --git a/PersonalBlog-Flask/db_migrate.py b/PersonalBlog-Flask/db_migrate.py new file mode 100644 index 0000000..a8e4e4b --- /dev/null +++ b/PersonalBlog-Flask/db_migrate.py @@ -0,0 +1,35 @@ +import imp +import os +from migrate.versioning import api +from app import db +from config import SQLALCHEMY_DATABASE_URI +from config import SQLALCHEMY_MIGRATE_REPO + +# 第一步:创建数据库, +# 如果数据库存在,create_all()是自动不执行的。 +try: + db.create_all() + if not os.path.exists(SQLALCHEMY_MIGRATE_REPO): + api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository') + api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + else: + api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO)) + +except:#屏蔽数据库存在的警告,继续执行数据迁移 + pass + +# 第二步:迁移数据 +v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v + 1)) +tmp_module = imp.new_module('old_model') +old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +exec(old_model, tmp_module.__dict__) +script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata) +open(migration, "wt").write(script) +api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + +#第三步:报告结果 +print('数 据 库位置:\t' + SQLALCHEMY_DATABASE_URI) +print('数据迁移代码:\t' + migration) +print('当前数据版本:\tv' + str(v)) diff --git a/PersonalBlog-Flask/db_upgrade.py b/PersonalBlog-Flask/db_upgrade.py new file mode 100644 index 0000000..6d061c2 --- /dev/null +++ b/PersonalBlog-Flask/db_upgrade.py @@ -0,0 +1,6 @@ +from migrate.versioning import api +from config import SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO + +api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) +print('Current data base version: ' + str(v)) \ No newline at end of file diff --git a/PersonalBlog-Flask/requirements.txt b/PersonalBlog-Flask/requirements.txt new file mode 100644 index 0000000..8fd1b01 --- /dev/null +++ b/PersonalBlog-Flask/requirements.txt @@ -0,0 +1,25 @@ +astroid==2.4.2 +click==7.1.2 +colorama==0.4.3 +decorator==4.4.2 +Flask==1.1.2 +Flask-Login==0.5.0 +Flask-SQLAlchemy==2.4.4 +Flask-WTF==0.14.3 +isort==4.3.21 +itsdangerous==1.1.0 +Jinja2==2.11.2 +lazy-object-proxy==1.4.3 +MarkupSafe==1.1.1 +mccabe==0.6.1 +pbr==5.4.5 +pylint==2.5.3 +six==1.15.0 +SQLAlchemy==1.3.18 +sqlalchemy-migrate==0.13.0 +sqlparse==0.3.1 +Tempita==0.5.2 +toml==0.10.1 +Werkzeug==1.0.1 +wrapt==1.12.1 +WTForms==2.3.1 diff --git a/PersonalBlog-Flask/run.py b/PersonalBlog-Flask/run.py new file mode 100644 index 0000000..13576b6 --- /dev/null +++ b/PersonalBlog-Flask/run.py @@ -0,0 +1,4 @@ +from app import app + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/README.md b/README.md index 6cd733c..03222e5 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,6 @@ 这个仓库放的是`python`的一些demo。会不定期的更新。 ### 目录 -1. [pictureDownload](./pictureDownload) -2. [scoreLine](./scoreLine) \ No newline at end of file +1. [pictureDownload 爬虫下载图片](./pictureDownload) +2. [scoreLine 爬虫查询高考分数](./scoreLine) +3. [personalBlog 个人博客](./PersonalBlog-Flask) \ No newline at end of file