├── README.md ├── app.py ├── app.pyc ├── data.sqlite ├── migrations ├── README ├── alembic.ini ├── env.py ├── env.pyc ├── script.py.mako └── versions │ ├── a3ea5d4b480b_initial_migration.py │ └── a3ea5d4b480b_initial_migration.pyc ├── requirements.txt ├── static ├── styles.css └── view.jpg └── templates ├── 404.html ├── 500.html ├── add_user.html ├── base.html ├── edit_user.html ├── index.html └── login.html /README.md: -------------------------------------------------------------------------------- 1 | # StudentBMS 2 | ## 准备: 3 | 1. 安装virtualenv: 4 | `pip install virtualenv` 5 | 6 | 2. 创建虚拟环境: 7 | `virtualenv venv` 8 | 9 | 3. 进入虚拟环境: 10 | `venv\Scripts\activate` 11 | 12 | 4. 安装依赖的包: 13 | `pip install -r requirements.txt` 14 | 15 | ## 运行: 16 | 1. 更新数据库:`python app.py db upgrade` 17 | 18 | 2. 生成管理员用户:`python app.py init` 19 | 20 | 3. 运行:`python app.py runserver` 21 | 22 | ## 初始管理员账户: 23 | ``` 24 | 学号:000000 25 | 密码:666666 26 | ``` 27 | 28 | ## 预览 29 | 30 | ![](https://github.com/xiongsyao/StudentBMS/blob/master/static/view.jpg) 31 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | import os 3 | from flask import Flask, render_template, session, redirect, \ 4 | url_for, flash, current_app, request 5 | from flask_script import Manager, Shell 6 | from flask_migrate import Migrate, MigrateCommand 7 | from flask_bootstrap import Bootstrap 8 | from flask_login import UserMixin, LoginManager, login_required, \ 9 | login_user, logout_user, current_user 10 | from flask_wtf import FlaskForm 11 | from wtforms import StringField, PasswordField, SubmitField, SelectField, \ 12 | BooleanField, IntegerField, ValidationError 13 | from wtforms.validators import Required, Length, Regexp 14 | from flask_sqlalchemy import SQLAlchemy 15 | from werkzeug.security import generate_password_hash, check_password_hash 16 | 17 | 18 | ''' 19 | Config 20 | ''' 21 | basedir = os.path.abspath(os.path.dirname(__file__)) 22 | 23 | def make_shell_context(): 24 | return dict(app=app, db=db, User=User, Role=Role) 25 | 26 | app = Flask(__name__) 27 | app.config['SQLALCHEMY_DATABASE_URI'] =\ 28 | 'sqlite:///' + os.path.join(basedir, 'data.sqlite') 29 | app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True 30 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 31 | app.config['AdminPassword'] = 666666 32 | app.config['SECRET_KEY'] = "this is a secret_key" 33 | db = SQLAlchemy(app) 34 | manager = Manager(app) 35 | bootstrap = Bootstrap(app) 36 | migrate = Migrate(app, db) 37 | manager.add_command('db', MigrateCommand) 38 | manager.add_command('shell', Shell(make_shell_context)) 39 | login_manager = LoginManager(app) 40 | 41 | login_manager.session_protection = 'strong' 42 | login_manager.login_view = 'login' 43 | login_manager.login_message = u"你需要登录才能访问这个页面." 44 | 45 | 46 | ''' 47 | Models 48 | ''' 49 | class Role(db.Model): 50 | __tablename__ = 'roles' 51 | id = db.Column(db.Integer, primary_key=True) 52 | name = db.Column(db.String(64), unique=True) 53 | users = db.relationship('User', backref='role', lazy='dynamic') 54 | 55 | @staticmethod 56 | def insert_roles(): 57 | roles = ('Student','Admin') 58 | for r in roles: 59 | role = Role.query.filter_by(name=r).first() 60 | if role is None: 61 | role = Role(name=r) 62 | db.session.add(role) 63 | db.session.commit() 64 | 65 | 66 | def __repr__(self): 67 | return '' %self.name 68 | 69 | class User(UserMixin, db.Model): 70 | __tablename__ = 'users' 71 | id = db.Column(db.Integer, primary_key=True) 72 | number = db.Column(db.SmallInteger, unique=True, index=True) 73 | username = db.Column(db.String(64), index=True) 74 | password = db.Column(db.String(128), default=123456) 75 | role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) 76 | 77 | def __init__(self, **kwargs): 78 | super(User, self).__init__(**kwargs) 79 | #新添加的用户,初始其角色为学生。 80 | if self.role is None: 81 | self.role = Role.query.filter_by(name='Student').first() 82 | 83 | def __repr__(self): 84 | return '' %self.username 85 | 86 | #初次运行程序时生成初始管理员的静态方法 87 | @staticmethod 88 | def generate_admin(): 89 | admin = Role.query.filter_by(name='Admin').first() 90 | u = User.query.filter_by(role=admin).first() 91 | if u is None: 92 | u = User(number = 000000, username = 'Admin',\ 93 | password = current_app.config['AdminPassword'],\ 94 | role = Role.query.filter_by(name='Admin').first()) 95 | db.session.add(u) 96 | db.session.commit() 97 | 98 | def verify_password(self, password): 99 | return self.password == password 100 | 101 | 102 | ''' 103 | Forms 104 | ''' 105 | class LoginForm(FlaskForm): 106 | number = StringField(u'考号', validators=[Required()]) 107 | password = PasswordField(u'密码', validators=[Required()]) 108 | remember_me = BooleanField(u'记住我') 109 | submit = SubmitField(u'登录') 110 | 111 | 112 | class SearchForm(FlaskForm): 113 | number = IntegerField(u'考号', validators=[Required(message=u'请输入数字')]) 114 | submit = SubmitField(u'搜索') 115 | 116 | 117 | class UserForm(FlaskForm): 118 | username = StringField(u'姓名', validators=[Required()]) 119 | number = IntegerField(u'考号', validators=[Required(message=u'请输入数字')]) 120 | submit = SubmitField(u'添加') 121 | 122 | def validate_number(self, field): 123 | if User.query.filter_by(number=field.data).first(): 124 | raise ValidationError(u'此学生已存在,请检查考号!') 125 | 126 | 127 | class EditForm(FlaskForm): 128 | username = StringField(u'姓名', validators=[Required()]) 129 | number = IntegerField(u'考号', validators=[Required(message=u'请输入数字')]) 130 | password = StringField(u'密码', validators=[Required(), Length(1,64),\ 131 | Regexp('^[a-zA-Z0-9_.]*$', 0, \ 132 | u'密码由字母、数字和_.组成')]) 133 | role = SelectField(u'身份', coerce=int) 134 | submit = SubmitField(u'修改') 135 | 136 | def __init__(self, user, *args, **kargs): 137 | super(EditForm, self).__init__(*args, **kargs) 138 | self.role.choices = [(role.id, role.name) 139 | for role in Role.query.order_by(Role.name).all()] 140 | self.user = user 141 | 142 | def validate_number(self, field): 143 | if field.data != self.user.number and \ 144 | User.query.filter_by(number=field.data).first(): 145 | raise ValidationError(u'此学生已存在,请检查考号!') 146 | 147 | 148 | ''' 149 | views 150 | ''' 151 | @app.route('/', methods=['GET', 'POST']) 152 | @login_required 153 | def index(): 154 | form = SearchForm() 155 | admin = Role.query.filter_by(name='Admin').first() 156 | if form.validate_on_submit(): 157 | #获得学生列表,其学号包含form中的数字 158 | students = User.query.filter(User.number.like \ 159 | ('%{}%'.format(form.number.data))).all() 160 | else: 161 | students = User.query.order_by(User.role_id.desc(), User.number.asc()).all() 162 | return render_template('index.html', form=form, students=students, admin=admin) 163 | 164 | 165 | #增加新考生 166 | @app.route('/add-user', methods=['GET', 'POST']) 167 | @login_required 168 | def add_user(): 169 | form = UserForm() 170 | if form.validate_on_submit(): 171 | user = User(username=form.username.data, 172 | number=form.number.data) 173 | db.session.add(user) 174 | flash(u'成功添加考生') 175 | return redirect(url_for('index')) 176 | return render_template('add_user.html', form=form) 177 | 178 | 179 | #删除考生 180 | @app.route('/remove-user/', methods=['GET', 'POST']) 181 | @login_required 182 | def remove_user(id): 183 | user = User.query.get_or_404(id) 184 | if user.role == Role.query.filter_by(name='Admin').first(): 185 | flash(u'不能删除管理员') 186 | else: 187 | db.session.delete(user) 188 | flash(u'成功删除此考生') 189 | return redirect(url_for('index')) 190 | 191 | 192 | #修改考生资料 193 | @app.route('/edit-user/', methods=['GET', 'POST']) 194 | @login_required 195 | def edit_user(id): 196 | user = User.query.get_or_404(id) 197 | form = EditForm(user=user) 198 | if form.validate_on_submit(): 199 | user.username = form.username.data 200 | user.number = form.number.data 201 | user.password = form.password.data 202 | user.role = Role.query.get(form.role.data) 203 | db.session.add(user) 204 | flash(u'个人信息已更改') 205 | return redirect(url_for('index')) 206 | form.username.data = user.username 207 | form.number.data = user.number 208 | form.password.data = user.password 209 | form.role.data = user.role_id 210 | return render_template('edit_user.html', form=form, user=user) 211 | 212 | 213 | #登录,系统只允许管理员登录 214 | @app.route('/login', methods=['GET', 'POST']) 215 | def login(): 216 | form = LoginForm() 217 | if form.validate_on_submit(): 218 | user = User.query.filter_by(number=form.number.data).first() 219 | if user is not None and user.verify_password(form.password.data): 220 | if user.role != Role.query.filter_by(name='Admin').first(): 221 | flash(u'系统只对管理员开放,请联系管理员获得权限!') 222 | else: 223 | login_user(user, form.remember_me.data) 224 | return redirect(url_for('index')) 225 | flash(u'用户名或密码错误!') 226 | return render_template('login.html', form=form) 227 | 228 | 229 | @app.route('/logout') 230 | @login_required 231 | def logout(): 232 | logout_user() 233 | flash(u'成功注销!') 234 | return redirect(url_for('login')) 235 | 236 | @app.errorhandler(404) 237 | def page_not_found(e): 238 | return render_template('404.html'), 404 239 | 240 | @app.errorhandler(500) 241 | def internal_server_error(e): 242 | return render_template('500.html'), 500 243 | 244 | #加载用户的回调函数 245 | @login_manager.user_loader 246 | def load_user(user_id): 247 | return User.query.get(int(user_id)) 248 | 249 | ''' 250 | 增加命令'python app.py init' 251 | 以增加身份与初始管理员帐号 252 | ''' 253 | @manager.command 254 | def init(): 255 | from app import Role, User 256 | Role.insert_roles() 257 | User.generate_admin() 258 | 259 | 260 | if __name__=='__main__': 261 | manager.run() -------------------------------------------------------------------------------- /app.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/app.pyc -------------------------------------------------------------------------------- /data.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/data.sqlite -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from alembic import context 3 | from sqlalchemy import engine_from_config, pool 4 | from logging.config import fileConfig 5 | import logging 6 | 7 | # this is the Alembic Config object, which provides 8 | # access to the values within the .ini file in use. 9 | config = context.config 10 | 11 | # Interpret the config file for Python logging. 12 | # This line sets up loggers basically. 13 | fileConfig(config.config_file_name) 14 | logger = logging.getLogger('alembic.env') 15 | 16 | # add your model's MetaData object here 17 | # for 'autogenerate' support 18 | # from myapp import mymodel 19 | # target_metadata = mymodel.Base.metadata 20 | from flask import current_app 21 | config.set_main_option('sqlalchemy.url', 22 | current_app.config.get('SQLALCHEMY_DATABASE_URI')) 23 | target_metadata = current_app.extensions['migrate'].db.metadata 24 | 25 | # other values from the config, defined by the needs of env.py, 26 | # can be acquired: 27 | # my_important_option = config.get_main_option("my_important_option") 28 | # ... etc. 29 | 30 | 31 | def run_migrations_offline(): 32 | """Run migrations in 'offline' mode. 33 | 34 | This configures the context with just a URL 35 | and not an Engine, though an Engine is acceptable 36 | here as well. By skipping the Engine creation 37 | we don't even need a DBAPI to be available. 38 | 39 | Calls to context.execute() here emit the given string to the 40 | script output. 41 | 42 | """ 43 | url = config.get_main_option("sqlalchemy.url") 44 | context.configure(url=url) 45 | 46 | with context.begin_transaction(): 47 | context.run_migrations() 48 | 49 | 50 | def run_migrations_online(): 51 | """Run migrations in 'online' mode. 52 | 53 | In this scenario we need to create an Engine 54 | and associate a connection with the context. 55 | 56 | """ 57 | 58 | # this callback is used to prevent an auto-migration from being generated 59 | # when there are no changes to the schema 60 | # reference: http://alembic.readthedocs.org/en/latest/cookbook.html 61 | def process_revision_directives(context, revision, directives): 62 | if getattr(config.cmd_opts, 'autogenerate', False): 63 | script = directives[0] 64 | if script.upgrade_ops.is_empty(): 65 | directives[:] = [] 66 | logger.info('No changes in schema detected.') 67 | 68 | engine = engine_from_config(config.get_section(config.config_ini_section), 69 | prefix='sqlalchemy.', 70 | poolclass=pool.NullPool) 71 | 72 | connection = engine.connect() 73 | context.configure(connection=connection, 74 | target_metadata=target_metadata, 75 | process_revision_directives=process_revision_directives, 76 | **current_app.extensions['migrate'].configure_args) 77 | 78 | try: 79 | with context.begin_transaction(): 80 | context.run_migrations() 81 | finally: 82 | connection.close() 83 | 84 | if context.is_offline_mode(): 85 | run_migrations_offline() 86 | else: 87 | run_migrations_online() 88 | -------------------------------------------------------------------------------- /migrations/env.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/migrations/env.pyc -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/a3ea5d4b480b_initial_migration.py: -------------------------------------------------------------------------------- 1 | """initial migration 2 | 3 | Revision ID: a3ea5d4b480b 4 | Revises: 5 | Create Date: 2017-04-23 11:20:26.284000 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'a3ea5d4b480b' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('roles', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.String(length=64), nullable=True), 24 | sa.PrimaryKeyConstraint('id'), 25 | sa.UniqueConstraint('name') 26 | ) 27 | op.create_table('users', 28 | sa.Column('id', sa.Integer(), nullable=False), 29 | sa.Column('number', sa.SmallInteger(), nullable=True), 30 | sa.Column('username', sa.String(length=64), nullable=True), 31 | sa.Column('password', sa.String(length=128), nullable=True), 32 | sa.Column('role_id', sa.Integer(), nullable=True), 33 | sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ), 34 | sa.PrimaryKeyConstraint('id') 35 | ) 36 | op.create_index(op.f('ix_users_number'), 'users', ['number'], unique=True) 37 | op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=False) 38 | # ### end Alembic commands ### 39 | 40 | 41 | def downgrade(): 42 | # ### commands auto generated by Alembic - please adjust! ### 43 | op.drop_index(op.f('ix_users_username'), table_name='users') 44 | op.drop_index(op.f('ix_users_number'), table_name='users') 45 | op.drop_table('users') 46 | op.drop_table('roles') 47 | # ### end Alembic commands ### 48 | -------------------------------------------------------------------------------- /migrations/versions/a3ea5d4b480b_initial_migration.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/migrations/versions/a3ea5d4b480b_initial_migration.pyc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/requirements.txt -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | .table th, .table td { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /static/view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsymphony/flask-demo/2618a81ee619b58578dc15e32cc98f1e500d2f59/static/view.jpg -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Weibo - Page Not Found{% endblock %} 4 | 5 | {% block page_content %} 6 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server-Error{% endblock %} 4 | 5 | {% block page_content %} 6 | 9 | {% endblock %} -------------------------------------------------------------------------------- /templates/add_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block page_content %} 5 |
6 | {{ wtf.quick_form(form) }} 7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}ManageSys{% endblock %} 4 | 5 | {% block head %} 6 | {{ super() }} 7 | 8 | {% endblock %} 9 | 10 | {% block navbar %} 11 | 40 | {% endblock %} 41 | 42 | {% block content %} 43 |
44 | {% for message in get_flashed_messages() %} 45 |
46 | 47 | {{ message }} 48 |
49 | {% endfor %} 50 | 51 | {% block page_content %}{% endblock %} 52 |
53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /templates/edit_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | 5 | {% block page_content %} 6 |
7 | {{ wtf.quick_form(form) }} 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | 5 | {% block page_content %} 6 |
7 |
8 | 添加新考生 9 |
10 |
11 | {{ form.hidden_tag() }} 12 | {{ wtf.form_errors(form, hiddens="True") }} 13 |
14 |
15 | {{ form.number(class="form-control", placeholder="输入考号查询考生") }} 16 | 17 | {{ wtf.form_field(form.submit) }} 18 | 19 |
20 |
21 |
22 |
23 | 24 | {% if students %} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% for student in students %} 36 | 37 | 38 | 39 | 40 | 45 | 48 | 49 | {% endfor %} 50 |
身份考号姓名
{{ student.role }}{{ student.number }}{{ student.username }} 41 | 42 | 修改资料 43 | 44 | 46 | 删除 47 |
51 | {% else %} 52 |

看来没有符合条件的人

53 | {% endif %} 54 | {% endblock %} -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}Login{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 |
11 | {{ wtf.quick_form(form) }} 12 |
13 | {% endblock %} --------------------------------------------------------------------------------