├── .gitattributes ├── .gitignore ├── .travis.yml ├── .tx └── .gitkeep ├── AUTHORS ├── CHANGES.rst ├── CONTRIBUTING.rst ├── INSTALL ├── LICENSE ├── MAKEFILE ├── MANIFEST.in ├── README.rst ├── docs └── model.md ├── examples └── .gitkeep ├── qingmi ├── __about__.py ├── __init__.py ├── _compat.py ├── admin │ ├── __init__.py │ └── formatters.py ├── api │ ├── __init__.py │ ├── auth │ │ ├── __init__.py │ │ └── views.py │ └── third │ │ ├── __init__.py │ │ └── express.py ├── app.py ├── base │ └── __init__.py ├── cert │ ├── __init__.py │ └── idcard.py ├── cli.py ├── config │ └── __init__.py ├── contrib │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── mongoengine │ │ │ ├── __init__.py │ │ │ ├── ajax.py │ │ │ ├── filters.py │ │ │ ├── form.py │ │ │ ├── formatters.py │ │ │ ├── models.py │ │ │ └── view.py │ ├── auth │ │ └── __init__.py │ └── csrf │ │ └── __init__.py ├── data │ └── fonts │ │ ├── 1.ttf │ │ ├── 2.ttf │ │ ├── 3.ttf │ │ └── 4.ttf ├── db │ ├── __init__.py │ ├── mongoengine │ │ ├── __init__.py │ │ ├── fields.py │ │ ├── generators.py │ │ ├── helpers.py │ │ └── pagination.py │ └── sqla │ │ └── .gitkeep ├── deploy │ ├── __init__.py │ └── web.py ├── fileupload.py ├── form │ ├── __init__.py │ ├── fields.py │ ├── form.py │ ├── formmaters.py │ ├── validators.py │ └── widgets.py ├── http │ └── __init__.py ├── jinja.py ├── lib │ └── __init__.py ├── logging.py ├── model │ ├── __init__.py │ └── models.py ├── oauth │ ├── __init__.py │ ├── alipay.py │ ├── unionpay.py │ └── wxpay.py ├── script │ └── __init__.py ├── service.py ├── settings.py ├── sms │ ├── __init__.py │ └── send.py ├── static │ ├── admin │ │ ├── css │ │ │ ├── bootstrap2 │ │ │ │ ├── admin.css │ │ │ │ └── rediscli.css │ │ │ └── bootstrap3 │ │ │ │ ├── admin.css │ │ │ │ └── rediscli.css │ │ └── js │ │ │ ├── actions.js │ │ │ ├── bs2_modal.js │ │ │ ├── bs3_modal.js │ │ │ ├── details_filter.js │ │ │ ├── filters.js │ │ │ ├── form.js │ │ │ └── rediscli.js │ ├── sb-admin-2 │ │ ├── css │ │ │ ├── sb-admin-2.css │ │ │ └── sb-admin-2.min.css │ │ └── js │ │ │ ├── sb-admin-2.js │ │ │ └── sb-admin-2.min.js │ └── vendor │ │ ├── bootstrap-daterangepicker │ │ ├── README.md │ │ ├── daterangepicker-bs2.css │ │ ├── daterangepicker-bs3.css │ │ └── daterangepicker.js │ │ ├── bootstrap-social │ │ ├── bootstrap-social.css │ │ ├── bootstrap-social.less │ │ └── bootstrap-social.scss │ │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ │ ├── datatables-plugins │ │ ├── dataTables.bootstrap.css │ │ ├── dataTables.bootstrap.js │ │ ├── dataTables.bootstrap.min.js │ │ └── index.html │ │ ├── datatables-responsive │ │ ├── dataTables.responsive.css │ │ ├── dataTables.responsive.css.map │ │ ├── dataTables.responsive.js │ │ └── dataTables.responsive.scss │ │ ├── datatables │ │ ├── css │ │ │ ├── dataTables.bootstrap.css │ │ │ ├── dataTables.bootstrap.min.css │ │ │ ├── dataTables.bootstrap4.css │ │ │ ├── dataTables.bootstrap4.min.css │ │ │ ├── dataTables.foundation.css │ │ │ ├── dataTables.foundation.min.css │ │ │ ├── dataTables.jqueryui.css │ │ │ ├── dataTables.jqueryui.min.css │ │ │ ├── dataTables.material.css │ │ │ ├── dataTables.material.min.css │ │ │ ├── dataTables.semanticui.css │ │ │ ├── dataTables.semanticui.min.css │ │ │ ├── dataTables.uikit.css │ │ │ ├── dataTables.uikit.min.css │ │ │ ├── jquery.dataTables.css │ │ │ ├── jquery.dataTables.min.css │ │ │ └── jquery.dataTables_themeroller.css │ │ ├── images │ │ │ ├── Sorting icons.psd │ │ │ ├── favicon.ico │ │ │ ├── sort_asc.png │ │ │ ├── sort_asc_disabled.png │ │ │ ├── sort_both.png │ │ │ ├── sort_desc.png │ │ │ └── sort_desc_disabled.png │ │ └── js │ │ │ ├── dataTables.bootstrap.js │ │ │ ├── dataTables.bootstrap.min.js │ │ │ ├── dataTables.bootstrap4.js │ │ │ ├── dataTables.bootstrap4.min.js │ │ │ ├── dataTables.foundation.js │ │ │ ├── dataTables.foundation.min.js │ │ │ ├── dataTables.jqueryui.js │ │ │ ├── dataTables.jqueryui.min.js │ │ │ ├── dataTables.material.js │ │ │ ├── dataTables.material.min.js │ │ │ ├── dataTables.semanticui.js │ │ │ ├── dataTables.semanticui.min.js │ │ │ ├── dataTables.uikit.js │ │ │ ├── dataTables.uikit.min.js │ │ │ ├── jquery.dataTables.js │ │ │ ├── jquery.dataTables.min.js │ │ │ └── jquery.js │ │ ├── flot-tooltip │ │ ├── jquery.flot.tooltip.js │ │ ├── jquery.flot.tooltip.min.js │ │ └── jquery.flot.tooltip.source.js │ │ ├── flot │ │ ├── excanvas.js │ │ ├── excanvas.min.js │ │ ├── jquery.colorhelpers.js │ │ ├── jquery.flot.canvas.js │ │ ├── jquery.flot.categories.js │ │ ├── jquery.flot.crosshair.js │ │ ├── jquery.flot.errorbars.js │ │ ├── jquery.flot.fillbetween.js │ │ ├── jquery.flot.image.js │ │ ├── jquery.flot.js │ │ ├── jquery.flot.navigate.js │ │ ├── jquery.flot.pie.js │ │ ├── jquery.flot.resize.js │ │ ├── jquery.flot.selection.js │ │ ├── jquery.flot.stack.js │ │ ├── jquery.flot.symbol.js │ │ ├── jquery.flot.threshold.js │ │ ├── jquery.flot.time.js │ │ └── jquery.js │ │ ├── font-awesome │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ ├── font-awesome.css.map │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── extras.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── spinning.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _extras.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _spinning.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ ├── font-awesome.css │ │ │ ├── font-awesome.css.map │ │ │ └── font-awesome.scss │ │ ├── jquery.min.js │ │ ├── jquery │ │ ├── jquery.js │ │ └── jquery.min.js │ │ ├── leaflet │ │ ├── images │ │ │ ├── layers-2x.png │ │ │ ├── layers.png │ │ │ ├── marker-icon-2x.png │ │ │ ├── marker-icon.png │ │ │ ├── marker-shadow.png │ │ │ ├── spritesheet-2x.png │ │ │ ├── spritesheet.png │ │ │ └── spritesheet.svg │ │ ├── leaflet.css │ │ ├── leaflet.draw.css │ │ ├── leaflet.draw.js │ │ └── leaflet.js │ │ ├── metisMenu │ │ ├── metisMenu.css │ │ ├── metisMenu.js │ │ ├── metisMenu.min.css │ │ └── metisMenu.min.js │ │ ├── moment.min.js │ │ ├── morrisjs │ │ ├── morris.css │ │ ├── morris.js │ │ └── morris.min.js │ │ ├── raphael │ │ ├── raphael.js │ │ └── raphael.min.js │ │ ├── select2 │ │ ├── LICENSE │ │ ├── select2-bootstrap3.css │ │ ├── select2-spinner.gif │ │ ├── select2.css │ │ ├── select2.min.js │ │ ├── select2.png │ │ └── select2x2.png │ │ └── x-editable │ │ ├── css │ │ ├── bootstrap2-editable.css │ │ └── bootstrap3-editable.css │ │ ├── img │ │ ├── clear.png │ │ └── loading.gif │ │ └── js │ │ ├── bootstrap2-editable.min.js │ │ └── bootstrap3-editable.min.js ├── stats.py ├── storage │ ├── __init__.py │ ├── _compat.py │ ├── aliyunoss.py │ ├── base.py │ ├── local.py │ ├── qiniu.py │ ├── upyun.py │ └── utils.py ├── task │ └── __init__.py ├── templates │ └── qingmi │ │ ├── admin │ │ ├── actions.html │ │ ├── base.html │ │ ├── file │ │ │ ├── form.html │ │ │ ├── list.html │ │ │ └── modals │ │ │ │ └── form.html │ │ ├── index.html │ │ ├── layout.html │ │ ├── lib.html │ │ ├── master.html │ │ ├── model │ │ │ ├── create.html │ │ │ ├── details.html │ │ │ ├── edit.html │ │ │ ├── inline_field_list.html │ │ │ ├── inline_form.html │ │ │ ├── inline_list_base.html │ │ │ ├── layout.html │ │ │ ├── list.html │ │ │ ├── modals │ │ │ │ ├── create.html │ │ │ │ ├── details.html │ │ │ │ └── edit.html │ │ │ └── row_actions.html │ │ ├── pages │ │ │ ├── blank.html │ │ │ ├── buttons.html │ │ │ ├── flot.html │ │ │ ├── forms.html │ │ │ ├── grid.html │ │ │ ├── icons.html │ │ │ ├── index.html │ │ │ ├── login.html │ │ │ ├── morris.html │ │ │ ├── notifications.html │ │ │ ├── panels-wells.html │ │ │ ├── tables.html │ │ │ └── typography.html │ │ ├── rediscli │ │ │ ├── console.html │ │ │ └── response.html │ │ └── static.html │ │ ├── base.html │ │ └── layout.html ├── test │ └── __init__.py ├── utils │ ├── __init__.py │ ├── browser.py │ ├── crypto.py │ ├── dateformat.py │ ├── dates.py │ ├── encoding.py │ ├── file.py │ ├── functional.py │ ├── hash.py │ ├── helper.py │ ├── http.py │ ├── ip.py │ ├── json.py │ ├── json_msg.py │ ├── log.py │ ├── random.py │ ├── redpacket.py │ ├── regex.py │ ├── six.py │ ├── string.py │ ├── time.py │ └── version.py ├── verify.py └── web │ └── __init__.py ├── requirements ├── README.rst ├── default.txt ├── extras │ ├── bcrypt.txt │ └── testing.txt └── test.txt ├── scripts └── .gitkeep ├── setup.cfg ├── setup.py ├── tests └── .gitkeep └── tox.ini /.gitattributes: -------------------------------------------------------------------------------- 1 | */static/* linguist-vendored 2 | *.html linguist-language=Python 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .env 4 | .flaskenv 5 | *.pyc 6 | *.pyo 7 | env 8 | env* 9 | dist 10 | build 11 | *.egg 12 | *.egg-info 13 | _mailinglist 14 | .tox 15 | .cache 16 | .pytest_cache 17 | .idea 18 | docs/_build 19 | 20 | # Coverage reports 21 | htmlcov 22 | .coverage 23 | .coverage.* 24 | *,cover 25 | 26 | # other 27 | test.py 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.6 5 | -------------------------------------------------------------------------------- /.tx/.gitkeep: -------------------------------------------------------------------------------- 1 | # 多语言翻译 -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/AUTHORS -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/CHANGES.rst -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | How to contribute to Qingmi 3 | =========================== 4 | Thanks for considering contributing to Qingmi. 5 | 6 | 7 | Running the testsuite 8 | ===================== 9 | 10 | You probably want to set up a virtualenv or virtualenvwrapper. 11 | 12 | The minimal requirement for running the testsuite is `py.test`. You can install it with: 13 | 14 | pip install pytest 15 | 16 | For a more isolated test environment, you can also install `tox` instead of 17 | `pytest`. You can install it with:: 18 | 19 | pip install tox 20 | 21 | The `tox` command will then run all tests against multiple combinations of 22 | Python versions and dependency versions. -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/INSTALL -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, zhuxiongxian 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | SUCH DAMAGE. -------------------------------------------------------------------------------- /MAKEFILE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/MAKEFILE -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst LICENSE AUTHORS tox.ini -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | qingmi 3 | =============== 4 | 5 | Qingmi(青咪, 取自 ``情迷`` 谐音, 有 ``亲昵`` or ``亲密`` 之意)是一个基于Python3+Flask二次开发的应用层框架, 其内部封装了常用的模块和工具集, 主要用于针对flask web快速高效开发。 6 | 7 | 安装 8 | ==== 9 | 10 | pypi安装 11 | -------- 12 | :: 13 | 14 | pip install qingmi 15 | 16 | 源码安装 17 | ------- 18 | python的版本需求为 ``Python3+``, 推荐 ``python3.6.7`` : 19 | 20 | :: 21 | 22 | git clone https://github.com/xiongxianzhu/qingmi.git 23 | cd qingmi 24 | python setup.py install 25 | 26 | 采用的技术栈 27 | ========== 28 | 29 | - python3 30 | - flask 31 | - Werkzeug 32 | - mongoengine 33 | - celery 34 | - fabric 35 | - httpie 36 | - Flask-Script 37 | - Flask-WTF 38 | - flask-mongoengine 39 | - Flask-Login 40 | - Flask-RESTful 41 | - Flask-DebugToolbar 42 | - Flask-Celery-Helper 43 | - requests 44 | - Flask-Caching 45 | - Flask-Admin 46 | - Flask-Uploads 47 | - ipython 48 | - Pillow 49 | - click 50 | - wheezy.captcha 51 | 52 | 功能特性 53 | ======= 54 | 55 | - 管理后台-admin(flask-admin+sb-admin-2) 56 | - 数据统计-stats 57 | - 短信模块-sms 58 | - 邮件发送-email 59 | - 文件上传-fileupload 60 | - 数据库(mongodb/mysql/postgresql/sqlite3)-db 61 | - 验证码(图片验证码/短信验证码/邮箱验证码)-verify 62 | - 静态文件-static 63 | - IP处理-ip 64 | - 日志-logging 65 | - 认证模块(用户/角色/权限)-auth 66 | - 加密模块-crypto 67 | - http模块-http 68 | - 权限管理-permission 69 | - 配置模块-conf 70 | - 定时任务(任务调度)-task 71 | - 通用API(登录认证/获取token等)-api 72 | - 第三方认证模块(QQ/微信/微博/github等)-oauth 73 | - 第三方支付(支付宝/微信支付/京东支付/翼支付等) 74 | - 部署(faric/rsync)-deploy 75 | - 辅助工具(helper or utils, 加解密[hash/md5]/json/http/ip/日志/正则表达式/时间和日期/...)-utils 76 | - 单元测试-test 77 | 78 | 目录结构详解 79 | ========== 80 | 81 | 用法 82 | ==== 83 | 84 | 测试 85 | ==== 86 | 87 | 在有setup.py文件目录下, 执行 ``tox`` 命令可生成tox.ini文件。 88 | 89 | :: 90 | 91 | $ cd qingmi 92 | # Install tox 93 | $ sudo pip install tox 94 | # Run the test suites 95 | $ tox 96 | 97 | 98 | 99 | 文档 100 | ==== 101 | 102 | 103 | 104 | 参考项目 105 | ======= 106 | 107 | - `celery `_ 108 | - `requests `_ 109 | - `django `_ 110 | - `flask-login `_ 111 | - `flask-admin `_ 112 | - `flask-security `_ 113 | - `startbootstrap-sb-admin-2 `_ 114 | - `sb-admin-2-python `_ 115 | -------------------------------------------------------------------------------- /docs/model.md: -------------------------------------------------------------------------------- 1 | 2 | # Model文档 3 | 4 | #### 1. 用户日志-UserLog 5 | 6 | 属性 | 类型 | 必填 | 默认值 | 备注 7 | :-|:-:|:-:|:-:|:-: 8 | id | ObjectId | 是 | | ID(mongodb自创建) 9 | key | string(80) | 否 | | 键名 10 | uid | string(80) | 否 | | 用户ID 11 | log_type | string | 否 | 'ACTIVE' | 日志类型 12 | useragent | string | 否 | | 用户代理(UA) 13 | ip | string(20) | 否 | | IP 14 | created_at | datetime | 是 | datetime.now | 创建时间 15 | updated_at | datetime | 是 | datetime.now | 更新时间 16 | 17 | `log_type`的值有: 18 | 19 | - LOGIN='登录' 20 | - LOGIN_ERROR='登录错误' 21 | - LOGOUT='退出登录' 22 | - REGISTER='注册' 23 | - BIND_PHONE='绑定手机' 24 | - BIND_EMAIL='绑定邮箱' 25 | - UPDATE_PASSWORD='修改密码' 26 | - RESET_PASSWORD='重置密码' 27 | - IDCARD='实名认证' 28 | - ACTIVE='活跃' 29 | 30 | #### 2. 统计日志-StatsLog 31 | 32 | 属性 | 类型 | 必填 | 默认值 | 备注 33 | :-|:-:|:-:|:-:|:-: 34 | id | ObjectId | 是 | | ID(mongodb自创建) 35 | key | string(40) | 否 | | 键名 36 | name | string(40) | 否 | | 名称 37 | label | string(128) | 否 | | 标签 38 | data_type | string(20) | 否 | | 类型 39 | uid | string(128) | 否 | | 用户ID 40 | xid | string(128) | 否 | | 其他ID 41 | day | string(20) | 否 | | 日期(yyyy-MM-dd) 42 | hour | int | 否 | 0 | 小时[0, 23] or [-1] 43 | minute | int | 否 | 0 | 分钟[0, 59] or [-1] 44 | value | Dynamic | 否 | | 值 45 | created_at | datetime | 是 | datetime.now | 创建时间 46 | updated_at | datetime | 是 | datetime.now | 更新时间 47 | 48 | `data_type`的值有: 49 | 50 | - INT='整数' 51 | - FLOAT='浮点数' 52 | - STRING='字符串' 53 | - BOOLEAN='布尔值' 54 | 55 | 56 | #### 3. 选项-Item 57 | 58 | 属性 | 类型 | 必填 | 默认值 | 备注 59 | :-|:-:|:-:|:-:|:-: 60 | id | ObjectId | 是 | | ID(mongodb自创建) 61 | name | string(40) | 否 | | 名称 62 | key | string(40) | 否 | | 键名 63 | data_type | string | 否 | | 值类型 64 | value | Dynamic | 否 | | 值 65 | created_at | datetime | 是 | datetime.now | 创建时间 66 | updated_at | datetime | 是 | datetime.now | 更新时间 67 | 68 | `data_type`的值有: 69 | 70 | - INT='整数' 71 | - FLOAT='浮点数' 72 | - STRING='字符串' 73 | - BOOLEAN='布尔值' 74 | 75 | #### 4. 管理员-AdminUser 76 | 77 | 属性 | 类型 | 必填 | 默认值 | 备注 78 | :-|:-:|:-:|:-:|:-: 79 | id | ObjectId | 是 | | ID(mongodb自创建) 80 | uid | string(50) | 是 | | UID 81 | username | string(20) | 是 | | 用户名 82 | password | string(50) | 否 | | 密码 83 | group | AdminGroup | 否 | | 管理组 84 | is_root | boolean | 否 | False | 是否超级管理员 85 | active | boolean | 否 | True | 是否激活 86 | freezed_at | datetime | 是 | | 冻结时间 87 | created_at | datetime | 是 | datetime.now | 创建时间 88 | updated_at | datetime | 是 | datetime.now | 更新时间 89 | 90 | #### 5. 管理组-AdminGroup 91 | 92 | 属性 | 类型 | 必填 | 默认值 | 备注 93 | :-|:-:|:-:|:-:|:-: 94 | id | ObjectId | 是 | | ID(mongodb自创建) 95 | name | string(40) | 是 | | 组名 96 | created_at | datetime | 是 | datetime.now | 创建时间 97 | updated_at | datetime | 是 | datetime.now | 更新时间 98 | 99 | #### 6. 管理员登录日志-AdminLoginLog 100 | 101 | 属性 | 类型 | 必填 | 默认值 | 备注 102 | :-|:-:|:-:|:-:|:-: 103 | id | ObjectId | 是 | | ID(mongodb自创建) 104 | user | AdminUser | 是 | | 管理员 105 | log_type | string | 是 | | 类型 106 | useragent | string | 否 | | 用户代理(UA) 107 | ip | string(20) | 否 | | IP 108 | created_at | datetime | 是 | datetime.now | 创建时间 109 | 110 | `log_type`的值有: 111 | 112 | - LOGIN='登录' 113 | - LOGOUT='退出登录' 114 | - ERROR='登录认证失败' 115 | 116 | #### 7. 管理员操作日志-AdminChangeLog 117 | 118 | 属性 | 类型 | 必填 | 默认值 | 备注 119 | :-|:-:|:-:|:-:|:-: 120 | id | ObjectId | 是 | | ID(mongodb自创建) 121 | user | AdminUser | 是 | | 管理员 122 | log_type | string | 是 | | 类型 123 | model | string | 是 | | 模块(如'User') 124 | model_object_id | string | 是 | | 模块对象ID 125 | before_data | string | 是 | | 操作前数据 126 | after_data | string | 是 | | 操作后数据 127 | useragent | string | 否 | | 用户代理(UA) 128 | ip | string(20) | 否 | | IP 129 | created_at | datetime | 是 | datetime.now | 创建时间 130 | 131 | `log_type`的值有: 132 | 133 | - CREATE='创建' 134 | - EDIT='编辑' 135 | - DELETE='删除' 136 | -------------------------------------------------------------------------------- /examples/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/examples/.gitkeep -------------------------------------------------------------------------------- /qingmi/__about__.py: -------------------------------------------------------------------------------- 1 | __name__ = 'qingmi' 2 | __description__ = 'Common modules and toolsets for rapid and efficient development of flask Web.' 3 | __url__ = 'https://github.com/xiongxianzhu/qingmi' 4 | __version_info__ = ('0', '1', '4') 5 | __version__ = '.'.join(__version_info__) 6 | __fullname__ = '-'.join((__name__, __version__)) 7 | __author__ = 'zhuxiongxian' 8 | __author_email__ = 'zhuxiongxian@gmail.com' 9 | __maintainer__ = 'zhuxiongxian' 10 | __maintainer_email__ = 'zhuxiongxian@gmail.com' 11 | __license__ = 'BSD' 12 | __copyright__ = '(c) 2018 by zhuxiongxian' 13 | __source__ = 'https://github.com/xiongxianzhu/qingmi' 14 | __keywords__ = 'qingmi flask' -------------------------------------------------------------------------------- /qingmi/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .__about__ import * 4 | from .jinja import * 5 | from .stats import StatsHelper -------------------------------------------------------------------------------- /qingmi/_compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | from urlparse import urljoin 4 | import urllib2 as http 5 | except ImportError: 6 | from urllib.parse import urljoin 7 | from urllib import request as http 8 | 9 | if sys.version_info[0] == 3: 10 | string_type = str 11 | else: 12 | string_type = unicode 13 | 14 | 15 | __all__ = ['urljoin', 'http', 'string_type', 'to_bytes'] 16 | 17 | 18 | def to_bytes(text): 19 | if isinstance(text, string_type): 20 | text = text.encode('utf-8') 21 | return text -------------------------------------------------------------------------------- /qingmi/admin/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_admin import Admin as _Admin, AdminIndexView 3 | 4 | ADMIN_MENUS_JSON = """""" 5 | 6 | 7 | class Admin(_Admin): 8 | 9 | def __init__(self, app=None, name=None, 10 | url=None, subdomain=None, 11 | index_view=None, 12 | translations_path=None, 13 | endpoint=None, 14 | static_url_path=None, 15 | base_template=None, 16 | template_mode=None, 17 | category_icon_classes=None): 18 | super(Admin, self).__init__( 19 | app=app, name=name, 20 | url=url, subdomain=subdomain, 21 | index_view=index_view, 22 | translations_path=translations_path, 23 | endpoint=endpoint, 24 | static_url_path=static_url_path, 25 | base_template=base_template, 26 | template_mode=template_mode, 27 | category_icon_classes=category_icon_classes, 28 | ) 29 | 30 | def _set_admin_index_view(self, index_view=None, 31 | endpoint=None, url=None): 32 | self.index_view = (index_view or self.index_view or 33 | AdminIndexView(endpoint=endpoint, url=url)) 34 | self.endpoint = endpoint or self.index_view.endpoint 35 | self.url = url or self.index_view.url 36 | 37 | if len(self._views) > 0: 38 | self._views[0] = self.index_view 39 | else: 40 | self.add_view(self.index_view) 41 | -------------------------------------------------------------------------------- /qingmi/api/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from flask_restful import Resource as _Resource, reqparse 4 | 5 | 6 | class Resource(_Resource): 7 | 8 | def __init__(self): 9 | super(Resource, self).__init__() 10 | self.parser = reqparse.RequestParser() 11 | self.add_args() 12 | 13 | def add_args(self): 14 | pass 15 | 16 | def get_args(self): 17 | return self.parser.parse_args() 18 | -------------------------------------------------------------------------------- /qingmi/api/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/api/auth/__init__.py -------------------------------------------------------------------------------- /qingmi/api/auth/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/api/auth/views.py -------------------------------------------------------------------------------- /qingmi/api/third/__init__.py: -------------------------------------------------------------------------------- 1 | from .express import * -------------------------------------------------------------------------------- /qingmi/api/third/express.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ Third party express apis. """ 3 | import sys 4 | import urllib.request 5 | import json 6 | from qingmi.utils import base64_md5 7 | 8 | def kdniao(userid, appkey, shipper_code, logistic_code): 9 | """ 快递鸟 """ 10 | express_api = 'http://api.kdniao.cc/Ebusiness/EbusinessOrderHandle.aspx' 11 | request_data = dict( 12 | OrderCode='', 13 | ShipperCode=shipper_code, 14 | LogisticCode=logistic_code, 15 | ) 16 | request_data_str = json.dumps(request_data) 17 | datasign = base64_md5(request_data_str+appkey) 18 | data = dict( 19 | EBusinessID=userid, 20 | DataType=2, 21 | RequestType=1002, 22 | RequestData=request_data_str, 23 | DataSign=datasign, 24 | ) 25 | url_values = urllib.parse.urlencode(data) 26 | express_url = express_api + '?' + url_values 27 | response = urllib.request.urlopen(express_url) 28 | context = response.read().decode('utf-8') 29 | return json.loads(context) 30 | -------------------------------------------------------------------------------- /qingmi/app.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | 4 | __all__ = [ 5 | "init_app" 6 | ] 7 | 8 | 9 | def init_app(config_object=None): 10 | 11 | pass 12 | 13 | 14 | def init_web(init=None, config=None, pyfile=None, 15 | template_folder='templates', index=False, error=True): 16 | pass 17 | 18 | 19 | def init_api(): 20 | pass 21 | 22 | 23 | def init_redis(app): 24 | """ initialize redis """ 25 | pass 26 | 27 | 28 | def init_db(db): 29 | """ initialize database """ 30 | pass 31 | -------------------------------------------------------------------------------- /qingmi/base/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from flask import Blueprint, current_app 4 | from qingmi.db.mongoengine import MongoEngine 5 | from flask_caching import Cache 6 | from flask_bcrypt import Bcrypt 7 | 8 | 9 | db = MongoEngine() 10 | cache = Cache() 11 | bcrypt = Bcrypt() 12 | -------------------------------------------------------------------------------- /qingmi/cert/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .idcard import * 4 | 5 | 6 | __all__ = [ 7 | 'idcardcert' 8 | ] -------------------------------------------------------------------------------- /qingmi/cert/idcard.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 身份证实名认证 """ 4 | import sys 5 | import urllib.request 6 | import json 7 | 8 | 9 | def idcardcert(appcode, card_no): 10 | """ 身份证实名认证身份证二要素一致性验证 """ 11 | host = 'http://idquery.market.alicloudapi.com' 12 | path = '/idcard/query' 13 | # method = 'GET' 14 | appcode = appcode 15 | querys = 'number=%s' % card_no 16 | # bodys = {} 17 | url = host + path + '?' + querys 18 | try: 19 | request = urllib.request.Request(url) 20 | request.add_header('Authorization', 'APPCODE ' + appcode) 21 | response = urllib.request.urlopen(request) 22 | content = response.read() 23 | if content: 24 | return json.loads(content.decode("unicode-escape")) 25 | return content 26 | except BaseException: 27 | return None 28 | -------------------------------------------------------------------------------- /qingmi/cli.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import os 4 | import sys 5 | import re 6 | import click 7 | 8 | 9 | def main(): 10 | pass -------------------------------------------------------------------------------- /qingmi/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/config/__init__.py -------------------------------------------------------------------------------- /qingmi/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/contrib/__init__.py -------------------------------------------------------------------------------- /qingmi/contrib/admin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/contrib/admin/__init__.py -------------------------------------------------------------------------------- /qingmi/contrib/admin/mongoengine/__init__.py: -------------------------------------------------------------------------------- 1 | from .models import * -------------------------------------------------------------------------------- /qingmi/contrib/admin/mongoengine/form.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_mongoengine.wtf import orm 3 | from flask_admin import form 4 | from flask_admin.contrib.mongoengine.form import (CustomModelConverter 5 | as _CustomModelConverter) 6 | from flask_admin.model.fields import AjaxSelectField 7 | from wtforms import fields as f 8 | from qingmi.form.fields import XFileField, XImageField, AreaField 9 | 10 | 11 | class CustomModelConverter(_CustomModelConverter): 12 | """ 13 | Customized MongoEngine form conversion class. 14 | 15 | Injects various Flask-Admin widgets and handles lists with 16 | customized InlineFieldList field. 17 | """ 18 | 19 | @orm.converts('DynamicField') 20 | def conv_dynamic(self, model, field, kwargs): 21 | textarea_field = kwargs.pop('textarea', True) 22 | if textarea_field: 23 | return f.TextAreaField(**kwargs) 24 | return f.StringField(**kwargs) 25 | 26 | @orm.converts('XFileField') 27 | def conv_xfile(self, model, field, kwargs): 28 | return XFileField(max_size=field.max_size, allowed_extensions=field.allowed_extensions, 29 | place=field.place, **kwargs) 30 | 31 | @orm.converts('XImageField') 32 | def conv_ximage(self, model, field, kwargs): 33 | return XImageField(max_size=field.max_size, allowed_extensions=field.allowed_extensions, 34 | place=field.place, **kwargs) 35 | 36 | @orm.converts('AreaField') 37 | def conv_area(self, model, field, kwargs): 38 | return AreaField(**kwargs) 39 | 40 | # @orm.converts('ReferenceField') 41 | # def conv_Reference(self, model, field, kwargs): 42 | # kwargs['allow_blank'] = not field.required 43 | 44 | # loader = getattr(self.view, '_form_ajax_refs', {}).get(field.name) 45 | # if loader: 46 | # return AjaxSelectField(loader, **kwargs) 47 | 48 | # kwargs['widget'] = form.Select2Widget() 49 | # queryset = kwargs.get('queryset') 50 | # if callable(queryset): 51 | # kwargs['queryset'] = queryset(field.document_type) 52 | # return orm.ModelConverter.conv_Reference(self, model, field, kwargs) 53 | -------------------------------------------------------------------------------- /qingmi/contrib/admin/mongoengine/formatters.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | 4 | -------------------------------------------------------------------------------- /qingmi/contrib/auth/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | -------------------------------------------------------------------------------- /qingmi/contrib/csrf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/contrib/csrf/__init__.py -------------------------------------------------------------------------------- /qingmi/data/fonts/1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/data/fonts/1.ttf -------------------------------------------------------------------------------- /qingmi/data/fonts/2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/data/fonts/2.ttf -------------------------------------------------------------------------------- /qingmi/data/fonts/3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/data/fonts/3.ttf -------------------------------------------------------------------------------- /qingmi/data/fonts/4.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/data/fonts/4.ttf -------------------------------------------------------------------------------- /qingmi/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/db/__init__.py -------------------------------------------------------------------------------- /qingmi/db/mongoengine/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from flask_mongoengine import (MongoEngine as _MongoEngine, 4 | BaseQuerySet as _BaseQuerySet, 5 | Document as _Document, 6 | DynamicDocument as _DynamicDocument) 7 | from . import pagination 8 | from . import fields 9 | 10 | 11 | class Choices(object): 12 | 13 | def __init__(self, **kwargs): 14 | self.CHOICES = [] 15 | for key, value in kwargs.items(): 16 | self.CHOICES.append((key, value)) 17 | setattr(self, key.upper(), key) 18 | self.DICT = dict(self.CHOICES) 19 | self.VALUES = self.DICT.keys() 20 | 21 | def text(self, key): 22 | return self.DICT.get(key) 23 | 24 | 25 | class MongoEngine(_MongoEngine): 26 | 27 | def __init__(self, app=None, config=None): 28 | super(MongoEngine, self).__init__(app) 29 | _include_custom(self) 30 | 31 | self.Document = Document 32 | self.DynamicDocument = DynamicDocument 33 | 34 | def choices(self, **kwargs): 35 | return Choices(**kwargs) 36 | 37 | 38 | class BaseQuerySet(_BaseQuerySet): 39 | 40 | def paginate(self, **kwargs): 41 | return pagination.Pagination(self, **kwargs) 42 | 43 | 44 | class Document(_Document): 45 | 46 | meta = {'abstract': True, 47 | 'queryset_class': BaseQuerySet} 48 | 49 | 50 | class DynamicDocument(_DynamicDocument): 51 | 52 | meta = {'abstract': True, 53 | 'queryset_class': BaseQuerySet} 54 | 55 | 56 | def abstract(model): 57 | model._meta['abstract'] = True 58 | 59 | 60 | def _include_custom(obj): 61 | for attr_name in fields.__all__: 62 | if not hasattr(obj, attr_name): 63 | setattr(obj, attr_name, getattr(fields, attr_name)) 64 | setattr(obj, 'abstract', abstract) 65 | -------------------------------------------------------------------------------- /qingmi/db/mongoengine/generators.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import os 4 | import time 5 | import hashlib 6 | 7 | 8 | class BaseGenerator(object): 9 | 10 | def __init__(self, **kwargs): 11 | pass 12 | 13 | 14 | class RandomGenerator(BaseGenerator): 15 | 16 | def __call__(self): 17 | md5_str = hashlib.md5(os.urandom(32)).hexdigest() 18 | return md5_str 19 | 20 | -------------------------------------------------------------------------------- /qingmi/db/mongoengine/helpers.py: -------------------------------------------------------------------------------- 1 | from flask_mongoengine import Document 2 | from qingmi.utils.encoding import smart_text 3 | 4 | 5 | def get_model_field(model): 6 | """ get the verbose_name of all fields in the model """ 7 | field_dict = dict() 8 | for field in model._fields: 9 | attr = getattr(model, field) 10 | if hasattr(attr, 'verbose_name'): 11 | verbose_name = attr.verbose_name 12 | if verbose_name: 13 | field_dict[field] = verbose_name 14 | return field_dict 15 | 16 | 17 | def get_fields_in_model(instance): 18 | """ 19 | Returns the list of fields in the given model instance. Checks whether to use the official _meta API or use the raw 20 | data. This method excludes many to many fields. 21 | :param instance: The model instance to get the fields for 22 | :type instance: Model 23 | :return: The list of fields for the given model (instance) 24 | :rtype: list 25 | """ 26 | assert isinstance(instance, Document) 27 | return instance._fields 28 | 29 | 30 | def model_instance_diff(old, new): 31 | """ 32 | Calculates the differences between two model instances. One of the instances may be ``None`` (i.e., a newly 33 | created model or deleted model). This will cause all fields with a value to have changed (from ``None``). 34 | :param old: The old state of the model instance. 35 | :type old: Model 36 | :param new: The new state of the model instance. 37 | :type new: Model 38 | :return: A dictionary with the names of the changed fields as keys and a two tuple of the old and new field values 39 | as value. 40 | :rtype: dict 41 | """ 42 | if not(old is None or isinstance(old, Document)): 43 | raise TypeError("The supplied old instance is not a valid model instance.") 44 | if not(new is None or isinstance(new, Document)): 45 | raise TypeError("The supplied new instance is not a valid model instance.") 46 | 47 | diff = {} 48 | 49 | if old is not None and new is not None: 50 | fields = set(list(old._fields.keys()) + list(new._fields.keys())) 51 | elif old is not None: 52 | fields = set(list(get_fields_in_model(old).keys())) 53 | elif new is not None: 54 | fields = set(list(get_fields_in_model(new).keys())) 55 | else: 56 | fields = set() 57 | 58 | for field in fields: 59 | try: 60 | old_value = smart_text(getattr(old, field, None)) 61 | except Exception as e: 62 | old_value = None 63 | 64 | try: 65 | new_value = smart_text(getattr(new, field, None)) 66 | except Exception as e: 67 | new_value = None 68 | 69 | if old_value != new_value: 70 | diff[field] = (smart_text(old_value), smart_text(new_value)) 71 | 72 | if len(diff) == 0: 73 | diff = None 74 | 75 | return diff 76 | -------------------------------------------------------------------------------- /qingmi/db/mongoengine/pagination.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import abort, request, url_for, render_template 3 | from flask_mongoengine.pagination import Pagination as _Pagination 4 | from qingmi.utils import success 5 | 6 | class Pagination(_Pagination): 7 | 8 | def __init__(self, iterable, page=None, per_page=None, 9 | endpoint=None, **kwargs): 10 | if page < 1: 11 | page = 1 12 | page = page or max(1, request.args.get('page', 1, int)) 13 | per_page = per_page or max(1, min(100, 14 | request.args.get('per_page', 10, int))) 15 | super(Pagination, self).__init__(iterable, page, per_page) 16 | self.endpoint = endpoint if endpoint else request.endpoint 17 | self.kwargs = kwargs 18 | 19 | @property 20 | def has_pages(self): 21 | return self.pages > 1 22 | 23 | @property 24 | def next_link(self): 25 | if self.has_next: 26 | return self.get_link(self.next_num) 27 | return '' 28 | 29 | def get_link(self, page): 30 | return url_for(self.endpoint, page=page, per_page=self.per_page, 31 | **self.kwargs) 32 | 33 | def iter_links(self, pages=5, next=False, last=True, 34 | first_page='«', prev_page='<', 35 | last_page='»', next_page='>'): 36 | if last: 37 | yield first_page, self.get_link(1) if self.page > 1 else None 38 | if next: 39 | yield prev_page, self.get_link(self.prev_num) if self.has_prev else None 40 | 41 | start = max(1, self.page - (pages - 1) / 2) 42 | end = min(self.pages + 1, start + pages) 43 | start = max(1, end - pages) 44 | for i in range(start, end): 45 | yield i, self.get_link(i) 46 | 47 | if next: 48 | yield next_page, self.get_link(self.next_num) if self.has_next else None 49 | if last: 50 | yield last_page, self.get_link(self.pages) if self.page < self.pages else None 51 | 52 | def json(self, tojson=lambda x: x.json, **kwargs): 53 | return success( 54 | items=[tojson(x) for x in self.items], 55 | next=self.next_link, **kwargs) 56 | -------------------------------------------------------------------------------- /qingmi/db/sqla/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/db/sqla/.gitkeep -------------------------------------------------------------------------------- /qingmi/deploy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/deploy/__init__.py -------------------------------------------------------------------------------- /qingmi/deploy/web.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | -------------------------------------------------------------------------------- /qingmi/fileupload.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/fileupload.py -------------------------------------------------------------------------------- /qingmi/form/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .validators import * -------------------------------------------------------------------------------- /qingmi/form/formmaters.py: -------------------------------------------------------------------------------- 1 | from html import escape, unescape 2 | # 与上等效 3 | # from xml.sax.saxutils import escape, unescape 4 | 5 | 6 | __all__ = [ 7 | 'escape_data', 8 | 'unescape_data', 9 | ] 10 | 11 | 12 | def escape_data(data): 13 | """ 14 | 将数据里的字符串数据进行安全过滤, 防止XSS攻击 15 | :param data: 数据 16 | :return: 17 | """ 18 | if isinstance(data, str): 19 | data = escape(data) 20 | elif isinstance(data, dict): 21 | for key, value in data.items(): 22 | data[key] = escape_data(value) 23 | elif isinstance(data, list): 24 | for index, item in enumerate(data): 25 | data[index] = escape_data(item) 26 | return data 27 | 28 | 29 | def unescape_data(data): 30 | """ 31 | 将数据里的字符串数据进行安全过滤, 防止XSS攻击 32 | :param data: 数据 33 | :return: 34 | """ 35 | if isinstance(data, str): 36 | data = unescape(data) 37 | elif isinstance(data, dict): 38 | for key, value in data.items(): 39 | data[key] = unescape_data(value) 40 | elif isinstance(data, list): 41 | for index, item in enumerate(data): 42 | data[index] = unescape_data(item) 43 | return data 44 | -------------------------------------------------------------------------------- /qingmi/form/validators.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | """ Form validators """ 3 | 4 | from wtforms.compat import string_types 5 | from wtforms.validators import Email, Regexp, ValidationError 6 | 7 | 8 | __all__ = [ 9 | 'Strip', 'Lower', 'Upper', 'Length', 'DataRequired', 'Email', 'Regexp', 10 | ] 11 | 12 | 13 | class Strip(object): 14 | 15 | def __call__(self, form, field): 16 | if isinstance(field.data, string_types): 17 | field.data = field.data.strip() 18 | 19 | 20 | class Lower(object): 21 | 22 | def __call__(self, form, field): 23 | if isinstance(field.data, string_types): 24 | field.data = field.data.lower() 25 | 26 | 27 | class Upper(object): 28 | 29 | def __call__(self, form, field): 30 | if isinstance(field.data, string_types): 31 | field.data = field.data.upper() 32 | 33 | 34 | class Length(object): 35 | 36 | def __init__(self, 37 | min=-1, 38 | max=-1, 39 | min_message='%(label)s长度不能小于%(min)d个字符', 40 | max_message='%(label)s长度不能超过%(max)d个字符'): 41 | assert min != -1 or max != -1, 'min和max必须至少设置一个' 42 | assert max != -1 or min > max, 'min必须小于等于max' 43 | self.min = min 44 | self.max = max 45 | self.min_message = min_message 46 | self.max_message = max_message 47 | 48 | def __call__(self, form, field): 49 | _len = field.data and len(field.data) or 0 50 | if _len < self.min: 51 | raise ValidationError(self.min_message % dict( 52 | label=field.label.text, 53 | min=self.min, 54 | max=self.max, 55 | length=_len, 56 | ) 57 | ) 58 | elif self.max != -1 and _len > self.max: 59 | raise ValidationError(self.max_message % dict( 60 | label=field.label.text, 61 | min=self.min, 62 | max=self.max, 63 | length=_len, 64 | ) 65 | ) 66 | 67 | 68 | class DataRequired(object): 69 | 70 | field_flags = ('required', ) 71 | 72 | def __init__(self, message='%(label)s不能为空'): 73 | self.message = message 74 | 75 | def __call__(self, form, field): 76 | if not field.data or isinstance( 77 | field.data, string_types) and not field.data.strip(): 78 | raise ValidationError(self.message % dict(label=field.label.text)) 79 | -------------------------------------------------------------------------------- /qingmi/http/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/http/__init__.py -------------------------------------------------------------------------------- /qingmi/jinja.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import re 4 | from datetime import datetime 5 | from flask import current_app, get_flashed_messages 6 | from jinja2 import Environment, Markup 7 | from xml.sax.saxutils import escape 8 | from qingmi.utils import parse_datetime as _parse_datetime 9 | 10 | 11 | __all__ = [ 12 | 'markup', 'markupper', 'first_error', 'JinjaManager', 'init_jinja', 13 | ] 14 | 15 | 16 | def markup(html): 17 | return Markup(html) if current_app.jinja_env.autoescape else html 18 | 19 | 20 | def markupper(func): 21 | def wrapper(*args, **kwargs): 22 | return markup(func(*args, **kwargs)) 23 | return wrapper 24 | 25 | 26 | def first_error(form): 27 | if form: 28 | for field in form: 29 | if field.errors: 30 | return field.errors[0] 31 | 32 | 33 | class JinjaManager(object): 34 | 35 | def __init__(self, app=None): 36 | if app is not None: 37 | self.init_app(app) 38 | 39 | def init_app(self, app): 40 | app.jinja_env.filters.update(self.filters) 41 | 42 | @property 43 | def filters(self): 44 | """ 自定义jinja过滤器 """ 45 | return dict( 46 | datetimeformat=self.datetimeformat, 47 | parse_datetime=self.parse_datetime, 48 | alert=self.alert_filter, 49 | ) 50 | 51 | def datetimeformat(self, value, format='%Y-%m-%d %H:%M:%S'): 52 | if value: 53 | return value.strftime(format) 54 | return '' 55 | 56 | def parse_datetime(self, data): 57 | if data: 58 | return _parse_datetime(data) 59 | return '' 60 | 61 | @markupper 62 | def alert_msg(self, msg, style='danger'): 63 | style = 'info' if style == 'message' else style 64 | return '
%s
' % (msg) 67 | # 下面代码与上等效 68 | # return markup('
%s
' % (style, msg)) 71 | 72 | def alert_filter(self, form=None, style='danger'): 73 | error = first_error(form) 74 | if error: 75 | return self.alert_msg(error, style) 76 | 77 | messages = get_flashed_messages(with_categories=True) 78 | if messages and messages[-1][1] != 'Please log in to access this page.': 79 | return self.alert_msg(messages[-1][1], messages[-1][0] or 'danger') 80 | return '' 81 | 82 | 83 | def init_jinja(app): 84 | jinja = JinjaManager() 85 | jinja.init_app(app) 86 | -------------------------------------------------------------------------------- /qingmi/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/lib/__init__.py -------------------------------------------------------------------------------- /qingmi/logging.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/logging.py -------------------------------------------------------------------------------- /qingmi/model/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .models import * 4 | 5 | 6 | __all__ = [ 7 | 'Item', 'StatsLog', 8 | ] -------------------------------------------------------------------------------- /qingmi/oauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/oauth/__init__.py -------------------------------------------------------------------------------- /qingmi/oauth/alipay.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """支付宝支付""" 4 | -------------------------------------------------------------------------------- /qingmi/oauth/unionpay.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """银联支付""" 4 | -------------------------------------------------------------------------------- /qingmi/oauth/wxpay.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """微信支付""" 4 | -------------------------------------------------------------------------------- /qingmi/script/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/script/__init__.py -------------------------------------------------------------------------------- /qingmi/service.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/service.py -------------------------------------------------------------------------------- /qingmi/settings.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Default qingmi settings. 4 | """ 5 | 6 | import os 7 | 8 | ROOT = os.path.dirname(os.path.abspath(__file__)) 9 | DATA_ROOT = os.path.join(ROOT, 'data') 10 | FONT_ROOT = os.path.join(DATA_ROOT, 'fonts') 11 | 12 | DEBUG = False 13 | 14 | # People who get code error notifications. 15 | # In the format [('Full Name', 'email@example.com'), ('Full Name', 16 | # 'anotheremail@example.com')] 17 | ADMINS = [] 18 | -------------------------------------------------------------------------------- /qingmi/sms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/sms/__init__.py -------------------------------------------------------------------------------- /qingmi/sms/send.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import re 3 | import urllib 4 | import json 5 | from flask import current_app 6 | from qingmi.utils import md5 7 | 8 | __all__ = [ 9 | 'send_smsbao_sms' 10 | ] 11 | 12 | 13 | def send_smsbao_sms(phone, text): 14 | """ 使用短信宝接口发送短信 """ 15 | 16 | smsbao_settings = current_app.config.get('SMSBAO_SETTINGS') 17 | 18 | statusStr = { 19 | '0': '短信发送成功', 20 | '-1': '参数不全', 21 | '-2': '服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间', 22 | '30': '密码错误', 23 | '40': '账号不存在', 24 | '41': '余额不足', 25 | '42': '账户已过期', 26 | '43': 'IP地址限制', 27 | '50': '内容含有敏感词' 28 | } 29 | 30 | smsapi = "http://api.smsbao.com/" 31 | # 短信平台账号 32 | user = smsbao_settings['user'] 33 | # 短信平台密码 34 | password = md5(smsbao_settings['password']) 35 | 36 | data = dict( 37 | u=user, 38 | p=password, 39 | m=phone, 40 | c=text 41 | ) 42 | 43 | url_values = urllib.parse.urlencode(data) 44 | send_url = smsapi + 'sms?' + url_values 45 | response = urllib.request.urlopen(send_url) 46 | the_page = response.read().decode('utf-8') 47 | 48 | res = dict(code=the_page, msg=statusStr[the_page]) 49 | return res -------------------------------------------------------------------------------- /qingmi/static/admin/css/bootstrap2/admin.css: -------------------------------------------------------------------------------- 1 | /* List View - fix trash icon inside table column */ 2 | .model-list form.icon { 3 | display: inline; 4 | } 5 | 6 | .model-list form.icon button { 7 | border: none; 8 | background: transparent; 9 | text-decoration: none; 10 | padding: 0; 11 | line-height: normal; 12 | } 13 | 14 | /* List View - link icons - prevent underline */ 15 | .model-list a.icon { 16 | text-decoration: none; 17 | } 18 | 19 | /* List View - fix checkbox column width */ 20 | .list-checkbox-column { 21 | width: 14px; 22 | } 23 | 24 | /* List View - prevent word wrap on buttons column, to keep it on one line */ 25 | .list-buttons-column { 26 | white-space: nowrap; 27 | } 28 | 29 | /* List View - fix gap between actions and table */ 30 | .model-list { 31 | position: static; 32 | margin-top: -1px; 33 | z-index: 999; 34 | } 35 | 36 | .actions-nav { 37 | margin-bottom: 0; 38 | margin-left: 4px; 39 | margin-right: 4px; 40 | } 41 | 42 | #filter_form { 43 | margin-bottom: 0; 44 | } 45 | 46 | /* List View Search Form - fix gap between form and table */ 47 | .actions-nav form.search-form { 48 | margin: -1px 0 0 0; 49 | } 50 | 51 | /* Filters */ 52 | table.filters { 53 | border-collapse: collapse; 54 | border-spacing: 4px; 55 | } 56 | 57 | /* prevents gap between table and actions while there are no filters set */ 58 | table.filters:not(:empty) { 59 | margin: 12px 0px 20px 0px; 60 | } 61 | 62 | /* spacing between filter X button, operation, and value field */ 63 | /* uses tables instead of form classes for bootstrap2-3 compatibility */ 64 | table.filters tr td { 65 | padding-right: 5px; 66 | padding-bottom: 3px; 67 | } 68 | 69 | /* match filter operation drop-down height with bootstrap input */ 70 | .filters .filter-op > a { 71 | height: 28px; 72 | line-height: 28px; 73 | } 74 | 75 | /* Image thumbnails */ 76 | .image-thumbnail img { 77 | max-width: 100px; 78 | max-height: 100px; 79 | } 80 | 81 | /* Forms */ 82 | .admin-form .control-label { 83 | width: 100px; 84 | text-align: left; 85 | margin-left: 4px; 86 | } 87 | 88 | /* add spacing between labels and form fields */ 89 | .admin-form .controls { 90 | margin-left: 110px; 91 | } 92 | 93 | @media only screen and (max-width: 800px) { 94 | 95 | /* Force table to not be like tables anymore */ 96 | #no-more-tables table, 97 | #no-more-tables thead, 98 | #no-more-tables tbody, 99 | #no-more-tables th, 100 | #no-more-tables td, 101 | #no-more-tables tr { 102 | display: block; 103 | } 104 | 105 | /* Hide table headers (but not display: none;, for accessibility) */ 106 | #no-more-tables thead tr { 107 | position: absolute; 108 | top: -9999px; 109 | left: -9999px; 110 | } 111 | 112 | #no-more-tables tr { border: 1px solid #ccc; } 113 | 114 | #no-more-tables td { 115 | /* Behave like a "row" */ 116 | border: none; 117 | border-bottom: 1px solid #eee; 118 | position: relative; 119 | padding-left: 50%; 120 | white-space: normal; 121 | text-align:left; 122 | } 123 | 124 | #no-more-tables td:before { 125 | /* Now like a table header */ 126 | position: absolute; 127 | /* Top/left values mimic padding */ 128 | top: 6px; 129 | left: 6px; 130 | width: 45%; 131 | padding-right: 10px; 132 | white-space: nowrap; 133 | text-align:left; 134 | font-weight: bold; 135 | } 136 | 137 | /* 138 | Label the data 139 | */ 140 | #no-more-tables td:before { content: attr(data-title); } 141 | } 142 | 143 | .editable-input .select2-container { 144 | min-width: 220px; 145 | } 146 | -------------------------------------------------------------------------------- /qingmi/static/admin/css/bootstrap2/rediscli.css: -------------------------------------------------------------------------------- 1 | .console { 2 | position: relative; 3 | width: 100%; 4 | min-height: 400px; 5 | } 6 | 7 | .console-container { 8 | border-radius: 4px; 9 | position: absolute; 10 | border: 1px solid #d4d4d4; 11 | padding: 2px; 12 | overflow: scroll; 13 | top: 2px; 14 | left: 2px; 15 | right: 2px; 16 | bottom: 5em; 17 | } 18 | 19 | .console-line { 20 | position: absolute; 21 | left: 2px; 22 | right: 2px; 23 | bottom: 2px; 24 | } 25 | 26 | .console-line input { 27 | width: 100%; 28 | box-sizing: border-box; 29 | -moz-box-sizing: border-box; 30 | -webkit-box-sizing: border-box; 31 | height: 2em; 32 | } 33 | 34 | .console .cmd { 35 | background-color: #f5f5f5; 36 | padding: 2px; 37 | margin: 1px; 38 | } 39 | 40 | .console .response { 41 | background-color: #f0f0f0; 42 | padding: 2px; 43 | margin: 1px; 44 | } 45 | 46 | .console .error { 47 | color: red; 48 | } -------------------------------------------------------------------------------- /qingmi/static/admin/css/bootstrap3/admin.css: -------------------------------------------------------------------------------- 1 | /* List View - fix trash icon inside table column */ 2 | .model-list form.icon { 3 | display: inline; 4 | } 5 | 6 | .model-list form.icon button { 7 | border: none; 8 | background: transparent; 9 | text-decoration: none; 10 | padding: 0; 11 | line-height: normal; 12 | } 13 | 14 | .model-list a.icon:first-child { 15 | margin-left: 10px; 16 | } 17 | 18 | /* List View - prevent link icons from differing from trash icon */ 19 | .model-list a.icon { 20 | text-decoration: none; 21 | color: inherit; 22 | } 23 | 24 | /* List View - fix checkbox column width */ 25 | .list-checkbox-column { 26 | width: 14px; 27 | } 28 | 29 | /* List View - fix overlapping border between actions and table */ 30 | .model-list { 31 | position: static; 32 | margin-top: -1px; 33 | z-index: 999; 34 | } 35 | 36 | /* List View Search Form - fix gap between form and table */ 37 | .actions-nav form.navbar-form { 38 | margin: 1px 0 0 0; 39 | } 40 | 41 | /* List View - prevent word wrap on buttons column, to keep it on one line */ 42 | .list-buttons-column { 43 | white-space: nowrap; 44 | } 45 | 46 | /* Filters */ 47 | table.filters { 48 | border-collapse: collapse; 49 | border-spacing: 4px; 50 | } 51 | 52 | /* prevents gap between table and actions while there are no filters set */ 53 | table.filters:not(:empty) { 54 | margin: 12px 0px 20px 0px; 55 | } 56 | 57 | /* spacing between filter X button, operation, and value field */ 58 | /* uses tables instead of form classes for bootstrap2-3 compatibility */ 59 | table.filters tr td { 60 | padding-right: 5px; 61 | padding-bottom: 3px; 62 | } 63 | 64 | /* Filters - Select2 Boxes */ 65 | .filters .filter-op { 66 | width: 130px; 67 | } 68 | 69 | .filters .filter-val { 70 | width: 220px; 71 | } 72 | 73 | /* Image thumbnails */ 74 | .image-thumbnail img { 75 | max-width: 100px; 76 | max-height: 100px; 77 | } 78 | 79 | /* Forms */ 80 | /* adds spacing between navbar and edit/create form (non-modal only) */ 81 | /* required because form-horizontal removes top padding */ 82 | div.container > .admin-form { 83 | margin-top: 35px; 84 | } 85 | 86 | /* Form Field Description - Appears when field has 'description' attribute */ 87 | /* Test with: form_args = {'name':{'description': 'test'}} */ 88 | /* prevents awkward gap after help-block - This is default for bootstrap2 */ 89 | .admin-form .help-block { 90 | margin-bottom: 0px; 91 | } 92 | 93 | /* Modals */ 94 | /* hack to prevent cut-off left side of select2 inside of modal */ 95 | /* may be able to remove this after Bootstrap v3.3.5 */ 96 | body.modal-open { 97 | overflow-y: scroll; 98 | padding-right: 0 !important; 99 | } 100 | 101 | /* Details View - add space between navbar and results */ 102 | .fa_filter_container { 103 | margin-top: 20px; 104 | margin-bottom: 10px; 105 | } 106 | 107 | .table-responsive 108 | { 109 | overflow-x: auto; 110 | } 111 | -------------------------------------------------------------------------------- /qingmi/static/admin/css/bootstrap3/rediscli.css: -------------------------------------------------------------------------------- 1 | .console { 2 | position: relative; 3 | width: 100%; 4 | min-height: 400px; 5 | } 6 | 7 | .console-container { 8 | border-radius: 4px; 9 | position: absolute; 10 | border: 1px solid #d4d4d4; 11 | padding: 2px; 12 | overflow: scroll; 13 | top: 2px; 14 | left: 2px; 15 | right: 2px; 16 | bottom: 5em; 17 | } 18 | 19 | .console-line { 20 | position: absolute; 21 | left: 2px; 22 | right: 2px; 23 | bottom: 2px; 24 | } 25 | 26 | .console-line input { 27 | width: 100%; 28 | box-sizing: border-box; 29 | -moz-box-sizing: border-box; 30 | -webkit-box-sizing: border-box; 31 | height: 2em; 32 | } 33 | 34 | .console .cmd { 35 | background-color: #f5f5f5; 36 | padding: 2px; 37 | margin: 1px; 38 | } 39 | 40 | .console .response { 41 | background-color: #f0f0f0; 42 | padding: 2px; 43 | margin: 1px; 44 | } 45 | 46 | .console .error { 47 | color: red; 48 | } -------------------------------------------------------------------------------- /qingmi/static/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | var AdminModelActions = function(actionErrorMessage, actionConfirmations) { 2 | // Actions helpers. TODO: Move to separate file 3 | this.execute = function(name) { 4 | var selected = $('input.action-checkbox:checked').length; 5 | 6 | if (selected === 0) { 7 | alert(actionErrorMessage); 8 | return false; 9 | } 10 | 11 | var msg = actionConfirmations[name]; 12 | 13 | if (!!msg) 14 | if (!confirm(msg)) 15 | return false; 16 | 17 | // Update hidden form and submit it 18 | var form = $('#action_form'); 19 | $('#action', form).val(name); 20 | 21 | $('input.action-checkbox', form).remove(); 22 | $('input.action-checkbox:checked').each(function() { 23 | form.append($(this).clone()); 24 | }); 25 | 26 | form.submit(); 27 | 28 | return false; 29 | }; 30 | 31 | $(function() { 32 | $('.action-rowtoggle').change(function() { 33 | $('input.action-checkbox').prop('checked', this.checked); 34 | }); 35 | }); 36 | 37 | $(function() { 38 | var inputs = $('input.action-checkbox'); 39 | inputs.change(function() { 40 | var allInputsChecked = true; 41 | for (var i = 0; i < inputs.length; i++) { 42 | if (!inputs[i].checked) { 43 | allInputsChecked = false; 44 | break; 45 | } 46 | } 47 | $('.action-rowtoggle').attr('checked', allInputsChecked); 48 | }); 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /qingmi/static/admin/js/bs2_modal.js: -------------------------------------------------------------------------------- 1 | // fixes "remote modal shows same content every time" 2 | $('.modal').on('hidden', function() { 3 | $(this).removeData('modal'); 4 | }); 5 | -------------------------------------------------------------------------------- /qingmi/static/admin/js/bs3_modal.js: -------------------------------------------------------------------------------- 1 | // fixes "remote modal shows same content every time", avoiding the flicker 2 | $('body').on('hidden.bs.modal', '.modal', function () { 3 | $(this).removeData('bs.modal').find(".modal-content").empty(); 4 | }); 5 | -------------------------------------------------------------------------------- /qingmi/static/admin/js/details_filter.js: -------------------------------------------------------------------------------- 1 | // filters the details table based on input 2 | $(document).ready(function () { 3 | $('#fa_filter').keyup(function () { 4 | var rex = new RegExp($(this).val(), 'i'); 5 | $('.searchable tr').hide(); 6 | $('.searchable tr').filter(function () { 7 | return rex.test($(this).text()); 8 | }).show(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /qingmi/static/admin/js/rediscli.js: -------------------------------------------------------------------------------- 1 | var RedisCli = function(postUrl) { 2 | // Constants 3 | var KEY_UP = 38; 4 | var KEY_DOWN = 40; 5 | var MAX_ITEMS = 128; 6 | 7 | var $con = $('.console'); 8 | var $container = $con.find('.console-container'); 9 | var $input = $con.find('input'); 10 | 11 | var history = []; 12 | var historyPos = 0; 13 | 14 | function resizeConsole() { 15 | var height = $(window).height(); 16 | 17 | var offset = $con.offset(); 18 | $con.height(height - offset.top); 19 | } 20 | 21 | function scrollBottom() { 22 | $container.animate({scrollTop: $container[0].scrollHeight}, 100); 23 | } 24 | 25 | function createEntry(cmd) { 26 | var $entry = $('
').addClass('entry').appendTo($container); 27 | $entry.append($('
').addClass('cmd').html(cmd)); 28 | 29 | if ($container.find('>div').length > MAX_ITEMS) 30 | $container.find('>div:first-child').remove(); 31 | 32 | scrollBottom(); 33 | return $entry; 34 | } 35 | 36 | function addResponse($entry, response) { 37 | $entry.append($('
').addClass('response').html(response)); 38 | scrollBottom(); 39 | } 40 | function addError($entry, response) { 41 | $entry.append($('
').addClass('response').addClass('error').html(response)); 42 | scrollBottom(); 43 | } 44 | 45 | function addHistory(cmd) { 46 | history.push(cmd); 47 | 48 | if (history > MAX_ITEMS) 49 | history.splice(0, 1); 50 | 51 | historyPos = history.length; 52 | } 53 | 54 | function sendCommand(val) { 55 | var $entry = createEntry('> ' + val); 56 | 57 | addHistory(val); 58 | 59 | $.ajax({ 60 | type: 'POST', 61 | url: postUrl, 62 | data: {'cmd': val}, 63 | success: function(response) { 64 | addResponse($entry, response); 65 | }, 66 | error: function() { 67 | addError($entry, 'Failed to communicate with server.'); 68 | } 69 | }); 70 | 71 | return false; 72 | } 73 | 74 | function submitCommand() { 75 | var val = $input.val().trim(); 76 | if (!val.length) 77 | return false; 78 | 79 | sendCommand(val); 80 | 81 | $input.val(''); 82 | return false; 83 | } 84 | 85 | function onKeyPress(e) { 86 | if (e.keyCode == KEY_UP) { 87 | historyPos -= 1; 88 | if (historyPos < 0) 89 | historyPos = 0; 90 | 91 | if (historyPos < history.length) 92 | $input.val(history[historyPos]); 93 | } else 94 | if (e.keyCode == KEY_DOWN) { 95 | historyPos += 1; 96 | 97 | if (historyPos >= history.length) { 98 | $input.val(''); 99 | historyPos = history.length; 100 | } else { 101 | $input.val(history[historyPos]); 102 | } 103 | } 104 | } 105 | 106 | // Setup 107 | $con.find('form').submit(submitCommand); 108 | 109 | $input.keydown(onKeyPress); 110 | 111 | $(window).resize(resizeConsole); 112 | resizeConsole(); 113 | 114 | $input.focus(); 115 | 116 | sendCommand('ping'); 117 | }; 118 | -------------------------------------------------------------------------------- /qingmi/static/sb-admin-2/js/sb-admin-2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | $(function() { 7 | $('#side-menu').metisMenu(); 8 | }); 9 | 10 | //Loads the correct sidebar on window load, 11 | //collapses the sidebar on window resize. 12 | // Sets the min-height of #page-wrapper to window size 13 | $(function() { 14 | $(window).bind("load resize", function() { 15 | var topOffset = 50; 16 | var width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 17 | if (width < 768) { 18 | $('div.navbar-collapse').addClass('collapse'); 19 | topOffset = 100; // 2-row-menu 20 | } else { 21 | $('div.navbar-collapse').removeClass('collapse'); 22 | } 23 | 24 | var height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1; 25 | height = height - topOffset; 26 | if (height < 1) height = 1; 27 | if (height > topOffset) { 28 | $("#page-wrapper").css("min-height", (height) + "px"); 29 | } 30 | }); 31 | 32 | var url = window.location; 33 | // var element = $('ul.nav a').filter(function() { 34 | // return this.href == url; 35 | // }).addClass('active').parent().parent().addClass('in').parent(); 36 | var element = $('ul.nav a').filter(function() { 37 | return this.href == url; 38 | }).addClass('active').parent(); 39 | 40 | while (true) { 41 | if (element.is('li')) { 42 | element = element.parent().addClass('in').parent(); 43 | } else { 44 | break; 45 | } 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /qingmi/static/sb-admin-2/js/sb-admin-2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin 2 v3.3.7+1 (http://startbootstrap.com/template-overviews/sb-admin-2) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | $(function(){$("#side-menu").metisMenu()}),$(function(){$(window).bind("load resize",function(){var i=50,n=this.window.innerWidth>0?this.window.innerWidth:this.screen.width;n<768?($("div.navbar-collapse").addClass("collapse"),i=100):$("div.navbar-collapse").removeClass("collapse");var e=(this.window.innerHeight>0?this.window.innerHeight:this.screen.height)-1;e-=i,e<1&&(e=1),e>i&&$("#page-wrapper").css("min-height",e+"px")});for(var i=window.location,n=$("ul.nav a").filter(function(){return this.href==i}).addClass("active").parent();;){if(!n.is("li"))break;n=n.parent().addClass("in").parent()}}); -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap-social/bootstrap-social.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Social Buttons for Bootstrap 3 | * 4 | * Copyright 2013-2014 Panayiotis Lipiridis 5 | * Licensed under the MIT License 6 | * 7 | * https://github.com/lipis/bootstrap-social 8 | */ 9 | 10 | @bs-height-base: (@line-height-computed + @padding-base-vertical * 2); 11 | @bs-height-lg: (floor(@font-size-large * @line-height-base) + @padding-large-vertical * 2); 12 | @bs-height-sm: (floor(@font-size-small * 1.5) + @padding-small-vertical * 2); 13 | @bs-height-xs: (floor(@font-size-small * 1.2) + @padding-small-vertical + 1); 14 | 15 | .btn-social { 16 | position: relative; 17 | padding-left: (@bs-height-base + @padding-base-horizontal); 18 | text-align: left; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | > :first-child { 23 | position: absolute; 24 | left: 0; 25 | top: 0; 26 | bottom: 0; 27 | width: @bs-height-base; 28 | line-height: (@bs-height-base + 2); 29 | font-size: 1.6em; 30 | text-align: center; 31 | border-right: 1px solid rgba(0, 0, 0, 0.2); 32 | } 33 | &.btn-lg { 34 | padding-left: (@bs-height-lg + @padding-large-horizontal); 35 | :first-child { 36 | line-height: @bs-height-lg; 37 | width: @bs-height-lg; 38 | font-size: 1.8em; 39 | } 40 | } 41 | &.btn-sm { 42 | padding-left: (@bs-height-sm + @padding-small-horizontal); 43 | :first-child { 44 | line-height: @bs-height-sm; 45 | width: @bs-height-sm; 46 | font-size: 1.4em; 47 | } 48 | } 49 | &.btn-xs { 50 | padding-left: (@bs-height-xs + @padding-small-horizontal); 51 | :first-child { 52 | line-height: @bs-height-xs; 53 | width: @bs-height-xs; 54 | font-size: 1.2em; 55 | } 56 | } 57 | } 58 | 59 | .btn-social-icon { 60 | .btn-social; 61 | height: (@bs-height-base + 2); 62 | width: (@bs-height-base + 2); 63 | padding: 0; 64 | :first-child { 65 | border: none; 66 | text-align: center; 67 | width: 100%!important; 68 | } 69 | &.btn-lg { 70 | height: @bs-height-lg; 71 | width: @bs-height-lg; 72 | padding-left: 0; 73 | padding-right: 0; 74 | } 75 | &.btn-sm { 76 | height: (@bs-height-sm + 2); 77 | width: (@bs-height-sm + 2); 78 | padding-left: 0; 79 | padding-right: 0; 80 | } 81 | &.btn-xs { 82 | height: (@bs-height-xs + 2); 83 | width: (@bs-height-xs + 2); 84 | padding-left: 0; 85 | padding-right: 0; 86 | } 87 | } 88 | 89 | .btn-social(@color-bg, @color: #fff) { 90 | background-color: @color-bg; 91 | .button-variant(@color, @color-bg, rgba(0,0,0,.2)); 92 | } 93 | 94 | 95 | .btn-adn { .btn-social(#d87a68); } 96 | .btn-bitbucket { .btn-social(#205081); } 97 | .btn-dropbox { .btn-social(#1087dd); } 98 | .btn-facebook { .btn-social(#3b5998); } 99 | .btn-flickr { .btn-social(#ff0084); } 100 | .btn-foursquare { .btn-social(#f94877); } 101 | .btn-github { .btn-social(#444444); } 102 | .btn-google-plus { .btn-social(#dd4b39); } 103 | .btn-instagram { .btn-social(#3f729b); } 104 | .btn-linkedin { .btn-social(#007bb6); } 105 | .btn-microsoft { .btn-social(#2672ec); } 106 | .btn-openid { .btn-social(#f7931e); } 107 | .btn-pinterest { .btn-social(#cb2027); } 108 | .btn-reddit { .btn-social(#eff7ff, #000); } 109 | .btn-soundcloud { .btn-social(#ff5500); } 110 | .btn-tumblr { .btn-social(#2c4762); } 111 | .btn-twitter { .btn-social(#55acee); } 112 | .btn-vimeo { .btn-social(#1ab7ea); } 113 | .btn-vk { .btn-social(#587ea3); } 114 | .btn-yahoo { .btn-social(#720e9e); } 115 | -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap-social/bootstrap-social.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Social Buttons for Bootstrap 3 | * 4 | * Copyright 2013-2014 Panayiotis Lipiridis 5 | * Licensed under the MIT License 6 | * 7 | * https://github.com/lipis/bootstrap-social 8 | */ 9 | 10 | $bs-height-base: ($line-height-computed + $padding-base-vertical * 2); 11 | $bs-height-lg: (floor($font-size-large * $line-height-base) + $padding-large-vertical * 2); 12 | $bs-height-sm: (floor($font-size-small * 1.5) + $padding-small-vertical * 2); 13 | $bs-height-xs: (floor($font-size-small * 1.2) + $padding-small-vertical + 1); 14 | 15 | .btn-social { 16 | position: relative; 17 | padding-left: ($bs-height-base + $padding-base-horizontal); 18 | text-align: left; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | > :first-child { 23 | position: absolute; 24 | left: 0; 25 | top: 0; 26 | bottom: 0; 27 | width: $bs-height-base; 28 | line-height: ($bs-height-base + 2); 29 | font-size: 1.6em; 30 | text-align: center; 31 | border-right: 1px solid rgba(0, 0, 0, 0.2); 32 | } 33 | &.btn-lg { 34 | padding-left: ($bs-height-lg + $padding-large-horizontal); 35 | :first-child { 36 | line-height: $bs-height-lg; 37 | width: $bs-height-lg; 38 | font-size: 1.8em; 39 | } 40 | } 41 | &.btn-sm { 42 | padding-left: ($bs-height-sm + $padding-small-horizontal); 43 | :first-child { 44 | line-height: $bs-height-sm; 45 | width: $bs-height-sm; 46 | font-size: 1.4em; 47 | } 48 | } 49 | &.btn-xs { 50 | padding-left: ($bs-height-xs + $padding-small-horizontal); 51 | :first-child { 52 | line-height: $bs-height-xs; 53 | width: $bs-height-xs; 54 | font-size: 1.2em; 55 | } 56 | } 57 | } 58 | 59 | .btn-social-icon { 60 | @extend .btn-social; 61 | height: ($bs-height-base + 2); 62 | width: ($bs-height-base + 2); 63 | padding: 0; 64 | :first-child { 65 | border: none; 66 | text-align: center; 67 | width: 100%!important; 68 | } 69 | &.btn-lg { 70 | height: $bs-height-lg; 71 | width: $bs-height-lg; 72 | padding-left: 0; 73 | padding-right: 0; 74 | } 75 | &.btn-sm { 76 | height: ($bs-height-sm + 2); 77 | width: ($bs-height-sm + 2); 78 | padding-left: 0; 79 | padding-right: 0; 80 | } 81 | &.btn-xs { 82 | height: ($bs-height-xs + 2); 83 | width: ($bs-height-xs + 2); 84 | padding-left: 0; 85 | padding-right: 0; 86 | } 87 | } 88 | 89 | @mixin btn-social($color-bg, $color: #fff) { 90 | background-color: $color-bg; 91 | @include button-variant($color, $color-bg, rgba(0,0,0,.2)); 92 | } 93 | 94 | 95 | .btn-adn { @include btn-social(#d87a68); } 96 | .btn-bitbucket { @include btn-social(#205081); } 97 | .btn-dropbox { @include btn-social(#1087dd); } 98 | .btn-facebook { @include btn-social(#3b5998); } 99 | .btn-flickr { @include btn-social(#ff0084); } 100 | .btn-foursquare { @include btn-social(#f94877); } 101 | .btn-github { @include btn-social(#444444); } 102 | .btn-google-plus { @include btn-social(#dd4b39); } 103 | .btn-instagram { @include btn-social(#3f729b); } 104 | .btn-linkedin { @include btn-social(#007bb6); } 105 | .btn-microsoft { @include btn-social(#2672ec); } 106 | .btn-openid { @include btn-social(#f7931e); } 107 | .btn-pinterest { @include btn-social(#cb2027); } 108 | .btn-reddit { @include btn-social(#eff7ff, #000); } 109 | .btn-soundcloud { @include btn-social(#ff5500); } 110 | .btn-tumblr { @include btn-social(#2c4762); } 111 | .btn-twitter { @include btn-social(#55acee); } 112 | .btn-vimeo { @include btn-social(#1ab7ea); } 113 | .btn-vk { @include btn-social(#587ea3); } 114 | .btn-yahoo { @include btn-social(#720e9e); } 115 | -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiongxianzhu/qingmi/ae5a446abec3982ebf2c5dde8546ef72f9453137/qingmi/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /qingmi/static/vendor/datatables-plugins/dataTables.bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 3 integration 3 | ©2011-2014 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(){var f=function(c,b){c.extend(!0,b.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>",renderer:"bootstrap"});c.extend(b.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"});b.ext.renderer.pageButton.bootstrap=function(g,f,p,k,h,l){var q=new b.Api(g),r=g.oClasses,i=g.oLanguage.oPaginate,d,e,o=function(b,f){var j,m,n,a,k=function(a){a.preventDefault(); 6 | c(a.currentTarget).hasClass("disabled")||q.page(a.data.action).draw(!1)};j=0;for(m=f.length;j",{"class":r.sPageButton+" "+ 7 | e,"aria-controls":g.sTableId,tabindex:g.iTabIndex,id:0===p&&"string"===typeof a?g.sTableId+"_"+a:null}).append(c("",{href:"#"}).html(d)).appendTo(b),g.oApi._fnBindAction(n,{action:a},k))}};o(c(f).empty().html('