├── .gitignore ├── CHANGES.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── flask_boost ├── __init__.py ├── cli.py ├── project │ ├── .gitignore │ ├── README.md │ ├── application │ │ ├── __init__.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ ├── account.py │ │ │ └── site.py │ │ ├── fis-conf.js │ │ ├── forms │ │ │ ├── __init__.py │ │ │ ├── _helper.py │ │ │ └── account.py │ │ ├── macros │ │ │ ├── _form.html │ │ │ ├── _utils.html │ │ │ └── user │ │ │ │ └── users │ │ │ │ ├── _users.html │ │ │ │ ├── _users.js │ │ │ │ └── _users.less │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── _base.py │ │ │ └── user.py │ │ ├── pages │ │ │ ├── account │ │ │ │ ├── signin │ │ │ │ │ ├── signin.html │ │ │ │ │ ├── signin.js │ │ │ │ │ └── signin.less │ │ │ │ └── signup │ │ │ │ │ ├── signup.html │ │ │ │ │ ├── signup.js │ │ │ │ │ └── signup.less │ │ │ ├── layout.html │ │ │ └── site │ │ │ │ ├── 403 │ │ │ │ └── 403.html │ │ │ │ ├── 404 │ │ │ │ └── 404.html │ │ │ │ ├── 500 │ │ │ │ └── 500.html │ │ │ │ ├── about │ │ │ │ ├── about.html │ │ │ │ ├── about.js │ │ │ │ └── about.less │ │ │ │ └── index │ │ │ │ ├── index.html │ │ │ │ ├── index.js │ │ │ │ └── index.less │ │ ├── static │ │ │ ├── css │ │ │ │ ├── bootstrap.theme.less │ │ │ │ ├── common.less │ │ │ │ ├── layout.less │ │ │ │ ├── libs │ │ │ │ │ ├── bootstrap.min.css │ │ │ │ │ └── font-awesome.min.css │ │ │ │ └── utils.less │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── image │ │ │ │ └── favicon.png │ │ │ └── js │ │ │ │ ├── init.js │ │ │ │ ├── layout.js │ │ │ │ └── libs │ │ │ │ ├── bootstrap.min.js │ │ │ │ ├── jquery.min.js │ │ │ │ └── respond.min.js │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── account.py │ │ │ ├── decorators.py │ │ │ ├── filters.py │ │ │ ├── helpers.py │ │ │ ├── permissions.py │ │ │ ├── rules.py │ │ │ ├── security.py │ │ │ └── sentry.py │ ├── bower.json │ ├── config │ │ ├── __init__.py │ │ ├── default.py │ │ ├── development_sample.py │ │ ├── production_sample.py │ │ └── testing.py │ ├── db │ │ └── testing.sqlite3 │ ├── deploy │ │ ├── flask_env.sh │ │ ├── gunicorn.conf │ │ ├── nginx.conf │ │ └── supervisor.conf │ ├── fabfile.py │ ├── gulpfile.js │ ├── manage.py │ ├── migrations │ │ ├── README │ │ ├── alembic.ini │ │ ├── env.py │ │ ├── script.py.mako │ │ └── versions │ │ │ └── 20150101130345_3d8f34e83d23_add_user_model.py │ ├── package.json │ ├── pylintrc │ ├── requirements.txt │ ├── tests │ │ ├── __init__.py │ │ ├── suite.py │ │ └── test_site.py │ └── wsgi.py └── templates │ ├── action.html │ ├── action.js │ ├── action.less │ ├── action.py │ ├── action_without_template.py │ ├── controller.py │ ├── form.py │ ├── macro.html │ ├── model.py │ └── unittest.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | test/ 4 | test3/ 5 | venv/ 6 | venv3/ 7 | /dist/ 8 | docs/ 9 | Flask_Boost.egg-info/ 10 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Flask Boost Changelog 2 | ===================== 3 | 4 | Version 0.7.5 5 | ------------- 6 | 7 | * Fix a bug in cli. 8 | 9 | Version 0.7.4 10 | ------------- 11 | 12 | * Upgrade FontAwesome to 4.5.0. 13 | * Update doc. 14 | 15 | Version 0.7.3 16 | ------------- 17 | 18 | * Fix encoding bug in cli. 19 | 20 | Version 0.7.2 21 | ------------- 22 | 23 | * Compile macro files via FIS. 24 | * Run gulp in build command. 25 | 26 | Version 0.7.1 27 | ------------- 28 | 29 | * Fix a bug in action template. 30 | * Upgrade coverage to 4.0.2. 31 | * Update doc. 32 | 33 | Version 0.7.0 34 | ------------- 35 | 36 | * Fix livereload. 37 | * Set SQLALCHEMY_TRACK_MODIFICATIONS to False. 38 | * Update doc. 39 | 40 | Version 0.6.4 41 | ------------- 42 | 43 | * Fix bugs when generating project in Windows. 44 | * Exclude useless files from package. 45 | 46 | Version 0.6.3 47 | ------------- 48 | 49 | * Upgrade ``Flask-SQLAlchemy`` to 2.1. 50 | * Upgrade ``PyMySQL`` to 0.6.7. 51 | * Upgrade ``Pillow`` to 3.0.0. 52 | * Upgrade ``six`` to 1.10.0. 53 | * Upgrade ``raven`` to 5.8.1. 54 | * Upgrade ``coverage`` to 4.0.1. 55 | 56 | Version 0.6.2 57 | ------------- 58 | 59 | * Remove ``Flask-Uploads`` to support Python 3.x. 60 | 61 | Version 0.6.1 62 | ------------- 63 | 64 | * Replace ``shutil.move`` with ``shutil.copy`` to avoid crash in Windows. 65 | * Update doc. 66 | 67 | Version 0.6.0 68 | ------------- 69 | 70 | * Move page stuff to ``application/pages`` dir. 71 | * Move macro stuff to ``application/macros`` dir. 72 | * Use Gulp_ to build macro assets in development. 73 | * Use FIS_ to build assets in production. 74 | * Lock package version in ``requirements.txt``. 75 | 76 | .. _Gulp: http://gulpjs.com 77 | .. _FIS: http://fex-team.github.io/fis-site/ 78 | 79 | Version 0.5.6 80 | ------------- 81 | 82 | * Freeze version of packages. 83 | * Upgrade fontaswsome to 4.4.0. 84 | * Add jsonify decorator. 85 | 86 | Version 0.5.5 87 | ------------- 88 | 89 | * Update form templates. 90 | * Update doc & clean codes & fix typos. 91 | 92 | Version 0.5.4 93 | ------------- 94 | 95 | * Init sentry in production. 96 | * Update doc. 97 | * Clean codes. 98 | 99 | Version 0.5.3 100 | ------------- 101 | 102 | * Add action less template file. 103 | * Upgrade Bootstrap to 0.5.3. 104 | 105 | 106 | Version 0.5.2 107 | ------------- 108 | 109 | * Fix a bug when compile app.js & app.css. 110 | * Add some actions' js & less files. 111 | * Clean codes. 112 | * Update doc. 113 | 114 | Version 0.5.1 115 | ------------- 116 | 117 | * Log errors to stderr in production mode. 118 | * Enable Sentry only when in production and SENTRY_DSN config is not empty. 119 | * Set db password to empty in dev config. 120 | * Set db host to localhost in production config. 121 | * Update doc. 122 | 123 | Version 0.5.0 124 | ------------- 125 | 126 | * Split ``app.css`` to ``libs.css`` and ``app.css``. 127 | * Rename ``build/page.js`` to ``build/app.js``. 128 | * Do not rewrite inner url starts with ``data:``. 129 | * Upgrade jQuery to 1.11.3. 130 | * Clean codes. 131 | 132 | Version 0.4.18 133 | -------------- 134 | 135 | * Create js & less file when create action with template. 136 | * Do not create action when create controller. 137 | * Refactor codes. 138 | 139 | Version 0.4.17 140 | -------------- 141 | 142 | * Quit if controller file does't exist when new action. 143 | * Mkdir if controller template dir not exist when new action. 144 | * Fix typo. 145 | 146 | Version 0.4.16 147 | -------------- 148 | 149 | * Add generate action command. 150 | * Change command ``generate`` to ``new`` 151 | * Add generate action command. 152 | * Add clearfix style to form-group. 153 | * Indent html files with 4 spaces. 154 | * Update doc. 155 | 156 | Version 0.4.15 157 | -------------- 158 | 159 | * Fix a bug when generate template file. 160 | 161 | Version 0.4.14 162 | -------------- 163 | 164 | * Generate template file when generate controller. 165 | * Fix a route bug in controller template. 166 | * Clean codes. 167 | 168 | Version 0.4.13 169 | -------------- 170 | 171 | * Use ``UglifyJS`` to compile js codes. 172 | 173 | Version 0.4.12 174 | -------------- 175 | 176 | * Include templates files in dist. 177 | 178 | Version 0.4.11 179 | -------------- 180 | 181 | * Add ``boost generate model`` command. 182 | * Generate import statement when generating form. 183 | 184 | Version 0.4.10 185 | -------------- 186 | 187 | * Generate test file when generating controller. 188 | 189 | Version 0.4.9 190 | ------------- 191 | 192 | * Dynamic load controllers. 193 | * Add ``boost generate controller`` command. 194 | * Add ``boost generate form`` command. 195 | * Update doc. 196 | 197 | Version 0.4.8 198 | ------------- 199 | 200 | * Update doc. 201 | * Use glob2 instead of formic in livereload support. 202 | * Clean requirements.txt. 203 | * Update some codes to support Python3. (However the package ``Flask-Upload`` does't support Python3) 204 | 205 | Version 0.4.7 206 | ------------- 207 | 208 | * Fix a bug in requirements.txt. 209 | 210 | Version 0.4.6 211 | ------------- 212 | 213 | * Fix project generation logic to support Python3. 214 | * Use PyMySQL instead of MySQL-python to support Python3. 215 | 216 | Version 0.4.5 217 | ------------- 218 | 219 | * Translate Chinese to English. 220 | * Add ``g.signin`` js variable. 221 | * Add screen sizes from Bootstrap. 222 | * Update doc. 223 | 224 | Version 0.4.4 225 | ------------- 226 | 227 | * Update url rewrite logic when build assets. 228 | * Do not rewrite url in js codes. 229 | * Fix a bug when process absolute path in YAML file. 230 | * Add ``g.method`` js variable. 231 | * Fix a bug in ``timesince``. 232 | * Add form helper ``check_url``. 233 | * Remove useless codes and files. 234 | 235 | Version 0.4.3 236 | ------------- 237 | 238 | * Refactor macro's structure. 239 | * Split component.less into macros/*.less. 240 | 241 | Version 0.4.2 242 | ------------- 243 | 244 | * Use jsmin instead of uglipyjs to compile js codes because of bugs from latter. 245 | 246 | Version 0.4.1 247 | ------------- 248 | 249 | * Exclude libs with full url. 250 | * Add global js function ``registerContext`` to register context into global variable g. 251 | * Extract _rewrite_relative_url function 252 | * Rewrite relative path in js lib files. 253 | * Use uglipyjs instead of jsmin to compile js codes. 254 | 255 | Version 0.4.0 256 | ------------- 257 | 258 | * Use js.yml & css.yml to declare assets. 259 | * Now can build assets via console command ``python manage.py build_assets``. 260 | * Fix bugs & add external paramter & better warning info for urlFor js function. 261 | * Upgrade permission to 0.3.0. 262 | * Upgrade bootstrap to 3.3.4. 263 | * Rm url_prefix when register blueprints. 264 | * Add viewport meta tag to head. 265 | * Add absolute_url_for helper, and inject as jinja2 global. 266 | * Add mkdir_p to helpers. 267 | * Refactor utils.uploadsets. 268 | * Track avatars default image. 269 | * Update color vars in ``utils.less``. 270 | 271 | 272 | Version 0.3.4 273 | ------------- 274 | 275 | * Add g as global JavaScript variable. 276 | * Add urlFor as global JavaScript function. 277 | * Add page_vars block to inject JavaScript variables to a page. 278 | * Move rules & permissions to jinja2 globals instead of global context. 279 | 280 | Version 0.3.3 281 | ------------- 282 | 283 | * Add csrf token header for Ajax request. 284 | * Add avatar_url property to User model. 285 | * Update filters.timesince. 286 | * Upgrade bootstrap to 3.3.2. 287 | * Clean codes and comments. 288 | 289 | Version 0.3.2 290 | ------------- 291 | 292 | * Remove Flask-Mail support. 293 | * Upgrade font-awesome to 4.3.0. 294 | * Remove useless configs. 295 | * Add app.production attr. 296 | * Remove no-margin-top css style. 297 | * Enable Sentry only in production mode. 298 | * Add highlight to account.signup & account.signin page. 299 | * Fix typo. 300 | 301 | Version 0.3.1 302 | ------------- 303 | 304 | * Remove fab pull 305 | * Clean codes. 306 | 307 | Version 0.3.0 308 | ------------- 309 | 310 | * Remove Flask-Admin support. 311 | * Add hash to assets url. 312 | * Log render time into HTTP header when the user is admin. 313 | 314 | Version 0.2.0 315 | ------------- 316 | 317 | * Add account system. 318 | 319 | Version 0.1.7 320 | ------------- 321 | 322 | * Now can title the project name by #{project|title}. 323 | * Track bower components. 324 | * Bump bootstrap version to 3.3.1, and fix jquery version to 1.11.1. 325 | * Add a migration file for initialization. 326 | 327 | Version 0.1.6 328 | ------------- 329 | 330 | * Add default favicon. 331 | * Clean requirements.txt. 332 | * Update code example for nav highlight. 333 | * Add page class to body tag. 334 | 335 | Version 0.1.5 336 | ------------- 337 | 338 | * Add ``fab pull`` command to update codes on server. 339 | * Add ``flask_env.sh`` to set environment variables when shell runs. 340 | * Fix some HTML bugs. 341 | * Fix Supervisor config file bug. 342 | 343 | Version 0.1.4 344 | ------------- 345 | 346 | * Include ``versions`` in ``migrations`` directory. 347 | 348 | Version 0.1.3 349 | ------------- 350 | 351 | * Add README file. 352 | * Bump bootstrap to 3.3.0 and font-awesome to 4.2.0. 353 | 354 | Version 0.1.2 355 | ------------- 356 | 357 | * Fix the page script bug. 358 | 359 | Version 0.1.1 360 | ------------- 361 | 362 | * Add help messages. 363 | 364 | Version 0.1.0 365 | ------------- 366 | 367 | * First public preview release. 368 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 hustlzp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES LICENSE README.rst requirements.txt 2 | recursive-include tests * 3 | recursive-include docs * 4 | prune docs/_build 5 | recursive-include flask_boost/project * 6 | recursive-include flask_boost/templates * 7 | prune flask_boost/project/node_modules 8 | prune flask_boost/project/bower_components 9 | prune flask_boost/project/output 10 | prune flask_boost/project/application/static/output 11 | global-exclude *.pyc 12 | global-exclude .DS_Store -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-Boost 2 | =========== 3 | 4 | .. image:: http://img.shields.io/pypi/v/flask-boost.svg 5 | :target: https://pypi.python.org/pypi/flask-boost 6 | :alt: Latest Version 7 | .. image:: http://img.shields.io/pypi/dm/flask-boost.svg 8 | :target: https://pypi.python.org/pypi/flask-boost 9 | :alt: Downloads Per Month 10 | .. image:: http://img.shields.io/pypi/pyversions/flask-boost.svg 11 | :target: https://pypi.python.org/pypi/flask-boost 12 | :alt: Python Versions 13 | .. image:: http://img.shields.io/badge/license-MIT-blue.svg 14 | :target: https://github.com/hustlzp/Flask-Boost/blob/master/LICENSE 15 | :alt: The MIT License 16 | 17 | Flask application generator for boosting your development. 18 | 19 | Features 20 | -------- 21 | 22 | * **Well Defined Project Structure** 23 | 24 | * Use factory pattern to generate Flask app. 25 | * Use Blueprints to organize controllers. 26 | * Split controllers, models, forms, utilities, assets, Jinja2 pages, Jinja2 macros into different directories. 27 | * Organize Jinja2 page assets (HTML, JavaScript, CSS) to the same directory. 28 | * Organize Jinja2 macro assets (HTML, JavaScript, CSS) to the same directory. 29 | 30 | * **Batteries Included** 31 | 32 | * Use Flask-SQLAlchemy and Flask-Migrate as database tools. 33 | * Use Flask-WTF to validate forms. 34 | * Use Flask-Script to help writing scripts. 35 | * Use permission_ to define permissions. 36 | * Use Bootstrap as frontend framework. 37 | * Use Bower to manage frontend packages. 38 | * Use Gulp and FIS_ to compile static assets. 39 | * Use Gunicorn to run Flask app and Supervisor to manage Gunicorn processes. 40 | * Use Fabric as deployment tool. 41 | * Use Sentry to log exceptions. 42 | * Use Nginx to serve static files. 43 | 44 | * **Scaffold Commands** 45 | 46 | * Generate project files: ``boost new `` 47 | * Generate controller files: ``boost new controller `` 48 | * Generate action files: ``boost new action [-t]`` 49 | * Generate form files: ``boost new form
`` 50 | * Generate model files: ``boost new model `` 51 | * Generate macro files: ``boost new macro `` or ``boost new macro `` 52 | 53 | .. _permission: https://github.com/hustlzp/permission 54 | 55 | Installation 56 | ------------ 57 | 58 | :: 59 | 60 | pip install flask-boost 61 | 62 | Development Guide 63 | ----------------- 64 | 65 | Init project 66 | ~~~~~~~~~~~~ 67 | 68 | :: 69 | 70 | boost new 71 | 72 | Setup backend requirements 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | :: 76 | 77 | cd 78 | virtualenv venv 79 | . venv/bin/activate (venv\Scripts\activate in Windows) 80 | pip install -r requirements.txt 81 | 82 | **Note**: if you failed in ``pip install -r requirements.txt`` in Windows, try to install package binaries directly: 83 | 84 | * pycrpyto: try to follow this article compiling-pycrypto-on-win7-64_, or get the complied pycrypyto library directly: archive_pycrpyto_library_. 85 | 86 | .. _compiling-pycrypto-on-win7-64: https://yorickdowne.wordpress.com/2010/12/22/compiling-pycrypto-on-win7-64/ 87 | .. _archive_pycrpyto_library: http://archive.warshaft.com/pycrypto-2.3.1.win7x64-py2.7x64.7z 88 | 89 | Init database 90 | ~~~~~~~~~~~~~ 91 | 92 | Create database with name ``your_project_name`` and encoding ``utf8``. 93 | 94 | Update ``SQLALCHEMY_DATABASE_URI`` in ``config/development.py`` as needed. 95 | 96 | Then init tables:: 97 | 98 | python manage.py db upgrade 99 | 100 | Run app 101 | ~~~~~~~ 102 | 103 | Run local server:: 104 | 105 | python manage.py run 106 | 107 | Setup frontend requirements 108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 109 | 110 | Install Node.js first and then install Bower_, FIS_ and Gulp_ globally:: 111 | 112 | npm install -g bower 113 | npm install -g fis 114 | npm install -g fis-postpackager-simple 115 | npm install -g gulp 116 | 117 | Install local packages:: 118 | 119 | npm install 120 | bower install 121 | 122 | Run Gulp watch task 123 | ~~~~~~~~~~~~~~~~~~~ 124 | 125 | :: 126 | 127 | gulp watch 128 | 129 | LiveReload support 130 | ~~~~~~~~~~~~~~~~~~ 131 | 132 | Install LiveReload browser extension from here_. 133 | 134 | And use ``python manage.py live`` instead of ``python manage.py run`` to start app. 135 | 136 | .. _here: http://livereload.com/extensions/ 137 | 138 | Scaffold commands 139 | ~~~~~~~~~~~~~~~~~ 140 | 141 | :: 142 | 143 | boost new 144 | boost new controller 145 | boost new action [-t] 146 | boost new form 147 | boost new model 148 | boost new macro 149 | boost new macro 150 | boost -v 151 | boost -h 152 | 153 | Recommended IDE 154 | ~~~~~~~~~~~~~~~ 155 | 156 | PyCharm_ is the recommended IDE for Flask-Boost. 157 | 158 | Recommended preferences: 159 | 160 | * In ``Preferences -> Project -> Project Interpreter``, set ``venv`` as project interpreter. 161 | * In ``Preferences -> Project -> Project Structure``, set ``application/pages`` and ``application/macros`` as template folders, set ``application`` and ``application/static/css`` as resource folders. 162 | * In ``Language & Frameworks -> JavaScript -> Bower``, set ``bower.json`` as bower.json. 163 | 164 | Recommended PyCharm plugins: 165 | 166 | * .ignore 167 | * Markdown 168 | * Bootstrap3 169 | 170 | .. _PyCharm: https://www.jetbrains.com/pycharm/ 171 | 172 | First Production Deploy 173 | ----------------------- 174 | 175 | Config server 176 | ~~~~~~~~~~~~~ 177 | 178 | Install mysql-server, python-virtualenv, git, supervisor, nginx, g++, python-dev, libmysqlclient-dev, libxml2-dev, libxslt-dev on your server. 179 | 180 | Install requirements 181 | ~~~~~~~~~~~~~~~~~~~~ 182 | 183 | :: 184 | 185 | git clone **.git 186 | cd 187 | virtualenv venv 188 | . venv/bin/activate 189 | pip install -r requirements.txt 190 | 191 | Config app 192 | ~~~~~~~~~~ 193 | 194 | Save ``config/production_sample.py`` as ``config/production.py``, update configs in ``config/production.py`` as needed and transfer it to server. 195 | 196 | **Note**: remember to update ``SECRET_KEY`` in ``config/production.py``! You can generate random secret key as follows:: 197 | 198 | >>> import os 199 | >>> os.urandom(24) 200 | 201 | Init database 202 | ~~~~~~~~~~~~~ 203 | 204 | Create database with name ``your_project_name`` and encoding ``utf8``. 205 | 206 | And run:: 207 | 208 | export MODE=PRODUCTION 209 | python manage.py db upgrade 210 | 211 | Copy config files 212 | ~~~~~~~~~~~~~~~~~ 213 | 214 | Update project root path as needed in ``deploy/nginx.conf`` and ``deploy/supervisor.conf``. 215 | 216 | :: 217 | 218 | cp deploy/flask_env.sh /etc/profile.d/ 219 | cp deploy/nginx.conf /etc/nginx/conf.d/.conf 220 | cp deploy/supervisor.conf /etc/supervisor/conf.d/.conf 221 | 222 | Build assets 223 | ~~~~~~~~~~~~ 224 | 225 | Install Node.js first and then install Bower_, FIS_ and Gulp_ globally:: 226 | 227 | npm install -g bower 228 | npm install -g fis 229 | npm install -g fis-postpackager-simple 230 | npm install -g gulp 231 | 232 | Install local packages:: 233 | 234 | npm install 235 | bower install 236 | 237 | Then:: 238 | 239 | gulp 240 | python manage.py build 241 | 242 | .. _Bower: http://bower.io 243 | .. _FIS: http://fex-team.github.io/fis-site/ 244 | .. _Gulp: http://gulpjs.com 245 | 246 | Start app 247 | ~~~~~~~~~ 248 | 249 | :: 250 | 251 | service nginx restart 252 | service supervisor restart 253 | 254 | Daily Production Deploy 255 | ----------------------- 256 | 257 | Update ``HOST_STRING`` in config with the format ``user@ip``. 258 | 259 | Commit your codes and run:: 260 | 261 | git push && fab deploy 262 | 263 | P.S. If you wanna to deploy flask with Apache2, see this_ post. 264 | 265 | .. _this: https://www.digitalocean.com/community/tutorials/how-to-use-apache-http-server-as-reverse-proxy-using-mod_proxy-extension 266 | 267 | License 268 | ------- 269 | 270 | MIT 271 | -------------------------------------------------------------------------------- /flask_boost/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.7.5" -------------------------------------------------------------------------------- /flask_boost/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | Flask Boost 6 | 7 | Usage: 8 | boost new 9 | boost new controller 10 | boost new action [-t] 11 | boost new form 12 | boost new model 13 | boost new macro 14 | boost new macro 15 | boost -v | --version 16 | boost -h | --help 17 | 18 | Options: 19 | -h, --help Help information. 20 | -v, --version Show version. 21 | """ 22 | 23 | import sys 24 | import os 25 | 26 | # Insert project root path to sys.path 27 | project_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 28 | if project_path not in sys.path: 29 | sys.path.insert(0, project_path) 30 | 31 | import logging 32 | import io 33 | from logging import StreamHandler, DEBUG 34 | from os.path import dirname, abspath 35 | from tempfile import mkstemp 36 | from docopt import docopt 37 | import shutil 38 | import errno 39 | from flask_boost import __version__ 40 | 41 | # If you add #{project} in a file, add the file ext here 42 | REWRITE_FILE_EXTS = ('.html', '.conf', '.py', '.json', '.md') 43 | 44 | logger = logging.getLogger(__name__) 45 | logger.setLevel(DEBUG) 46 | logger.addHandler(StreamHandler()) 47 | 48 | 49 | def generate_project(args): 50 | """New project.""" 51 | # Project templates path 52 | src = os.path.join(dirname(abspath(__file__)), 'project') 53 | 54 | project_name = args.get('') 55 | 56 | if not project_name: 57 | logger.warning('Project name cannot be empty.') 58 | return 59 | 60 | # Destination project path 61 | dst = os.path.join(os.getcwd(), project_name) 62 | 63 | if os.path.isdir(dst): 64 | logger.warning('Project directory already exists.') 65 | return 66 | 67 | logger.info('Start generating project files.') 68 | 69 | _mkdir_p(dst) 70 | 71 | for src_dir, sub_dirs, filenames in os.walk(src): 72 | # Build and create destination directory path 73 | relative_path = src_dir.split(src)[1].lstrip(os.path.sep) 74 | dst_dir = os.path.join(dst, relative_path) 75 | 76 | if src != src_dir: 77 | _mkdir_p(dst_dir) 78 | 79 | # Copy, rewrite and move project files 80 | for filename in filenames: 81 | if filename in ['development.py', 'production.py']: 82 | continue 83 | 84 | src_file = os.path.join(src_dir, filename) 85 | dst_file = os.path.join(dst_dir, filename) 86 | 87 | if filename.endswith(REWRITE_FILE_EXTS): 88 | _rewrite_and_copy(src_file, dst_file, project_name) 89 | else: 90 | shutil.copy(src_file, dst_file) 91 | logger.info("New: %s" % dst_file) 92 | 93 | if filename in ['development_sample.py', 'production_sample.py']: 94 | dst_file = os.path.join(dst_dir, "%s.py" % filename.split('_')[0]) 95 | _rewrite_and_copy(src_file, dst_file, project_name) 96 | logger.info("New: %s" % dst_file) 97 | 98 | logger.info('Finish generating project files.') 99 | 100 | 101 | def generate_controller(args): 102 | """Generate controller, include the controller file, template & css & js directories.""" 103 | controller_template = os.path.join(dirname(abspath(__file__)), 'templates/controller.py') 104 | test_template = os.path.join(dirname(abspath(__file__)), 'templates/unittest.py') 105 | controller_name = args.get('') 106 | current_path = os.getcwd() 107 | 108 | logger.info('Start generating controller.') 109 | 110 | if not controller_name: 111 | logger.warning('Controller name cannot be empty.') 112 | return 113 | 114 | # controller file 115 | with open(controller_template, 'r') as template_file: 116 | controller_file_path = os.path.join(current_path, 'application/controllers', 117 | controller_name + '.py') 118 | with open(controller_file_path, 'w+') as controller_file: 119 | for line in template_file: 120 | new_line = line.replace('#{controller}', controller_name) 121 | controller_file.write(new_line) 122 | logger.info("New: %s" % _relative_path(controller_file_path)) 123 | 124 | # test file 125 | with open(test_template, 'r') as template_file: 126 | test_file_path = os.path.join(current_path, 'tests', 127 | 'test_%s.py' % controller_name) 128 | with open(test_file_path, 'w+') as test_file: 129 | for line in template_file: 130 | new_line = line.replace('#{controller}', controller_name) \ 131 | .replace('#{controller|title}', controller_name.title()) 132 | test_file.write(new_line) 133 | logger.info("New: %s" % _relative_path(test_file_path)) 134 | 135 | # assets dir 136 | assets_dir_path = os.path.join(current_path, 'application/pages/%s' % controller_name) 137 | _mkdir_p(assets_dir_path) 138 | 139 | # form file 140 | _generate_form(controller_name) 141 | 142 | logger.info('Finish generating controller.') 143 | 144 | 145 | def generate_action(args): 146 | """Generate action.""" 147 | controller = args.get('') 148 | action = args.get('') 149 | with_template = args.get('-t') 150 | current_path = os.getcwd() 151 | 152 | logger.info('Start generating action.') 153 | 154 | controller_file_path = os.path.join(current_path, 'application/controllers', controller + '.py') 155 | if not os.path.exists(controller_file_path): 156 | logger.warning("The controller %s does't exist." % controller) 157 | return 158 | 159 | if with_template: 160 | action_source_path = os.path.join(dirname(abspath(__file__)), 'templates/action.py') 161 | else: 162 | action_source_path = os.path.join(dirname(abspath(__file__)), 'templates/action_without_template.py') 163 | 164 | # Add action source codes 165 | with open(action_source_path, 'r') as action_source_file: 166 | with open(controller_file_path, 'a') as controller_file: 167 | for action_line in action_source_file: 168 | new_line = action_line.replace('#{controller}', controller). \ 169 | replace('#{action}', action) 170 | controller_file.write(new_line) 171 | logger.info("Updated: %s" % _relative_path(controller_file_path)) 172 | 173 | if with_template: 174 | # assets dir 175 | assets_dir_path = os.path.join(current_path, 'application/pages/%s/%s' % (controller, action)) 176 | _mkdir_p(assets_dir_path) 177 | 178 | # html 179 | action_html_template_path = os.path.join(dirname(abspath(__file__)), 'templates/action.html') 180 | action_html_path = os.path.join(assets_dir_path, '%s.html' % action) 181 | with open(action_html_template_path, 'r') as action_html_template_file: 182 | with open(action_html_path, 'w') as action_html_file: 183 | for line in action_html_template_file: 184 | new_line = line.replace('#{action}', action) \ 185 | .replace('#{action|title}', action.title()) \ 186 | .replace('#{controller}', controller) 187 | action_html_file.write(new_line) 188 | logger.info("New: %s" % _relative_path(action_html_path)) 189 | 190 | # js 191 | action_js_template_path = os.path.join(dirname(abspath(__file__)), 'templates/action.js') 192 | action_js_path = os.path.join(assets_dir_path, '%s.js' % action) 193 | shutil.copy(action_js_template_path, action_js_path) 194 | logger.info("New: %s" % _relative_path(action_js_path)) 195 | 196 | # less 197 | action_less_template_path = os.path.join(dirname(abspath(__file__)), 'templates/action.less') 198 | action_less_path = os.path.join(assets_dir_path, '%s.less' % action) 199 | shutil.copy(action_less_template_path, action_less_path) 200 | logger.info("New: %s" % _relative_path(action_less_path)) 201 | 202 | logger.info('Finish generating action.') 203 | 204 | 205 | def generate_form(args): 206 | """Generate form.""" 207 | form_name = args.get('') 208 | logger.info('Start generating form.') 209 | _generate_form(form_name) 210 | logger.info('Finish generating form.') 211 | 212 | 213 | def generate_model(args): 214 | """Generate model.""" 215 | model_name = args.get('') 216 | if not model_name: 217 | logger.warning('Model name cannot be empty.') 218 | return 219 | 220 | logger.info('Start generating model.') 221 | 222 | model_template = os.path.join(dirname(abspath(__file__)), 'templates/model.py') 223 | current_path = os.getcwd() 224 | 225 | with open(model_template, 'r') as template_file: 226 | model_file_path = os.path.join(current_path, 'application/models', 227 | model_name + '.py') 228 | with open(model_file_path, 'w+') as model_file: 229 | for line in template_file: 230 | new_line = line.replace('#{model|title}', model_name.title()) 231 | model_file.write(new_line) 232 | logger.info("New: %s" % _relative_path(model_file_path)) 233 | 234 | with open(os.path.join(current_path, 'application/models/__init__.py'), 'a') as package_file: 235 | package_file.write('\nfrom .%s import *' % model_name) 236 | 237 | logger.info('Finish generating model.') 238 | 239 | 240 | def generate_macro(args): 241 | """Genarate macro.""" 242 | macro = args.get('').replace('-', '_') 243 | category = args.get('') 244 | 245 | if not macro: 246 | logger.warning('Macro name cannot be empty.') 247 | return 248 | 249 | logger.info('Start generating macro.') 250 | 251 | current_path = os.getcwd() 252 | 253 | if category: 254 | macro_root_path = os.path.join(current_path, 'application/macros', category, macro) 255 | else: 256 | macro_root_path = os.path.join(current_path, 'application/macros', macro) 257 | 258 | _mkdir_p(macro_root_path) 259 | 260 | macro_html_path = os.path.join(macro_root_path, '_%s.html' % macro) 261 | macro_css_path = os.path.join(macro_root_path, '_%s.less' % macro) 262 | macro_js_path = os.path.join(macro_root_path, '_%s.js' % macro) 263 | 264 | # html 265 | macro_html_template_path = os.path.join(dirname(abspath(__file__)), 'templates/macro.html') 266 | with open(macro_html_template_path, 'r') as template_file: 267 | with open(macro_html_path, 'w+') as html_file: 268 | for line in template_file: 269 | new_line = line.replace('#{macro}', macro) 270 | html_file.write(new_line) 271 | logger.info("New: %s" % _relative_path(macro_html_path)) 272 | 273 | # css 274 | open(macro_css_path, 'a').close() 275 | logger.info("New: %s" % _relative_path(macro_css_path)) 276 | 277 | # js 278 | open(macro_js_path, 'a').close() 279 | logger.info("New: %s" % _relative_path(macro_js_path)) 280 | 281 | logger.info('Finish generating macro.') 282 | 283 | 284 | def main(): 285 | args = docopt(__doc__, version="Flask-Boost {0}".format(__version__)) 286 | if args.get('new'): 287 | if args.get('controller'): 288 | generate_controller(args) 289 | elif args.get('form'): 290 | generate_form(args) 291 | elif args.get('model'): 292 | generate_model(args) 293 | elif args.get('action'): 294 | generate_action(args) 295 | elif args.get('macro'): 296 | generate_macro(args) 297 | else: 298 | generate_project(args) 299 | else: 300 | print(args) 301 | 302 | 303 | def _mkdir_p(path): 304 | """mkdir -p path""" 305 | try: 306 | os.makedirs(path) 307 | except OSError as exc: 308 | if exc.errno == errno.EEXIST and os.path.isdir(path): 309 | pass 310 | else: 311 | raise 312 | else: 313 | logger.info("New: %s%s", path, os.path.sep) 314 | 315 | 316 | def _rewrite_and_copy(src_file, dst_file, project_name): 317 | """Replace vars and copy.""" 318 | # Create temp file 319 | fh, abs_path = mkstemp() 320 | 321 | with io.open(abs_path, 'w', encoding='utf-8') as new_file: 322 | with io.open(src_file, 'r', encoding='utf-8') as old_file: 323 | for line in old_file: 324 | new_line = line.replace('#{project}', project_name). \ 325 | replace('#{project|title}', project_name.title()) 326 | new_file.write(new_line) 327 | 328 | # Copy to new file 329 | shutil.copy(abs_path, dst_file) 330 | os.close(fh) 331 | 332 | 333 | def _generate_form(form_name): 334 | form_template = os.path.join(dirname(abspath(__file__)), 'templates/form.py') 335 | current_path = os.getcwd() 336 | 337 | if not form_name: 338 | logger.warning('Form name cannot be empty.') 339 | return 340 | 341 | form_file_path = os.path.join(current_path, 'application/forms', form_name + '.py') 342 | shutil.copy(form_template, form_file_path) 343 | logger.info("New: %s" % _relative_path(form_file_path)) 344 | 345 | with open(os.path.join(current_path, 'application/forms/__init__.py'), 'a') as package_file: 346 | package_file.write('\nfrom .%s import *' % form_name) 347 | 348 | 349 | def _relative_path(absolute_path): 350 | current_path = os.getcwd() 351 | return absolute_path.split(current_path)[1][1:] 352 | 353 | 354 | if __name__ == "__main__": 355 | main() 356 | -------------------------------------------------------------------------------- /flask_boost/project/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/ 3 | venv/ 4 | config/production.py 5 | config/development.py 6 | bower_components/ 7 | output/ 8 | uploads/* 9 | !uploads/avatars/ 10 | uploads/avatars/* 11 | !uploads/avatars/default.png 12 | node_modules/ 13 | application/pages/**/*.css 14 | application/static/css/**/*.css 15 | !application/static/css/libs/*.css 16 | application/static/output/ 17 | -------------------------------------------------------------------------------- /flask_boost/project/README.md: -------------------------------------------------------------------------------- 1 | #{project} 2 | ======= 3 | 4 | Introduction to #{project}. -------------------------------------------------------------------------------- /flask_boost/project/application/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import sys 3 | import os 4 | 5 | # Insert project root path to sys.path 6 | project_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 7 | if project_path not in sys.path: 8 | sys.path.insert(0, project_path) 9 | 10 | import time 11 | import logging 12 | from flask import Flask, request, url_for, g, render_template 13 | from flask_wtf.csrf import CsrfProtect 14 | from flask_debugtoolbar import DebugToolbarExtension 15 | from werkzeug.wsgi import SharedDataMiddleware 16 | from werkzeug.contrib.fixers import ProxyFix 17 | from six import iteritems 18 | from .utils.account import get_current_user 19 | from config import load_config 20 | 21 | # convert python's encoding to utf8 22 | try: 23 | from imp import reload 24 | 25 | reload(sys) 26 | sys.setdefaultencoding('utf8') 27 | except (AttributeError, NameError): 28 | pass 29 | 30 | 31 | def create_app(): 32 | """Create Flask app.""" 33 | config = load_config() 34 | 35 | app = Flask(__name__) 36 | app.config.from_object(config) 37 | 38 | # Proxy fix 39 | app.wsgi_app = ProxyFix(app.wsgi_app) 40 | 41 | # CSRF protect 42 | CsrfProtect(app) 43 | 44 | if app.debug or app.testing: 45 | DebugToolbarExtension(app) 46 | 47 | # Serve static files 48 | app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { 49 | '/pages': os.path.join(app.config.get('PROJECT_PATH'), 'application/pages') 50 | }) 51 | else: 52 | # Log errors to stderr in production mode 53 | app.logger.addHandler(logging.StreamHandler()) 54 | app.logger.setLevel(logging.ERROR) 55 | 56 | # Enable Sentry 57 | if app.config.get('SENTRY_DSN'): 58 | from .utils.sentry import sentry 59 | 60 | sentry.init_app(app, dsn=app.config.get('SENTRY_DSN')) 61 | 62 | # Serve static files 63 | app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { 64 | '/static': os.path.join(app.config.get('PROJECT_PATH'), 'output/static'), 65 | '/pkg': os.path.join(app.config.get('PROJECT_PATH'), 'output/pkg'), 66 | '/pages': os.path.join(app.config.get('PROJECT_PATH'), 'output/pages') 67 | }) 68 | 69 | # Register components 70 | register_db(app) 71 | register_routes(app) 72 | register_jinja(app) 73 | register_error_handle(app) 74 | register_hooks(app) 75 | 76 | return app 77 | 78 | 79 | def register_jinja(app): 80 | """Register jinja filters, vars, functions.""" 81 | import jinja2 82 | from .utils import filters, permissions, helpers 83 | 84 | if app.debug or app.testing: 85 | my_loader = jinja2.ChoiceLoader([ 86 | app.jinja_loader, 87 | jinja2.FileSystemLoader([ 88 | os.path.join(app.config.get('PROJECT_PATH'), 'application/macros'), 89 | os.path.join(app.config.get('PROJECT_PATH'), 'application/pages') 90 | ]) 91 | ]) 92 | else: 93 | my_loader = jinja2.ChoiceLoader([ 94 | app.jinja_loader, 95 | jinja2.FileSystemLoader([ 96 | os.path.join(app.config.get('PROJECT_PATH'), 'output/macros'), 97 | os.path.join(app.config.get('PROJECT_PATH'), 'output/pages') 98 | ]) 99 | ]) 100 | app.jinja_loader = my_loader 101 | 102 | app.jinja_env.filters.update({ 103 | 'timesince': filters.timesince 104 | }) 105 | 106 | def url_for_other_page(page): 107 | """Generate url for pagination.""" 108 | view_args = request.view_args.copy() 109 | args = request.args.copy().to_dict() 110 | combined_args = dict(view_args.items() + args.items()) 111 | combined_args['page'] = page 112 | return url_for(request.endpoint, **combined_args) 113 | 114 | rules = {} 115 | for endpoint, _rules in iteritems(app.url_map._rules_by_endpoint): 116 | if any(item in endpoint for item in ['_debug_toolbar', 'debugtoolbar', 'static']): 117 | continue 118 | rules[endpoint] = [{'rule': rule.rule} for rule in _rules] 119 | 120 | app.jinja_env.globals.update({ 121 | 'absolute_url_for': helpers.absolute_url_for, 122 | 'url_for_other_page': url_for_other_page, 123 | 'rules': rules, 124 | 'permissions': permissions 125 | }) 126 | 127 | 128 | def register_db(app): 129 | """Register models.""" 130 | from .models import db 131 | 132 | db.init_app(app) 133 | 134 | 135 | def register_routes(app): 136 | """Register routes.""" 137 | from . import controllers 138 | from flask.blueprints import Blueprint 139 | 140 | for module in _import_submodules_from_package(controllers): 141 | bp = getattr(module, 'bp') 142 | if bp and isinstance(bp, Blueprint): 143 | app.register_blueprint(bp) 144 | 145 | 146 | def register_error_handle(app): 147 | """Register HTTP error pages.""" 148 | 149 | @app.errorhandler(403) 150 | def page_403(error): 151 | return render_template('site/403/403.html'), 403 152 | 153 | @app.errorhandler(404) 154 | def page_404(error): 155 | return render_template('site/404/404.html'), 404 156 | 157 | @app.errorhandler(500) 158 | def page_500(error): 159 | return render_template('site/500/500.html'), 500 160 | 161 | 162 | def register_hooks(app): 163 | """Register hooks.""" 164 | 165 | @app.before_request 166 | def before_request(): 167 | g.user = get_current_user() 168 | if g.user and g.user.is_admin: 169 | g._before_request_time = time.time() 170 | 171 | @app.after_request 172 | def after_request(response): 173 | if hasattr(g, '_before_request_time'): 174 | delta = time.time() - g._before_request_time 175 | response.headers['X-Render-Time'] = delta * 1000 176 | return response 177 | 178 | 179 | def _get_template_name(template_reference): 180 | """Get current template name.""" 181 | return template_reference._TemplateReference__context.name 182 | 183 | 184 | def _import_submodules_from_package(package): 185 | import pkgutil 186 | 187 | modules = [] 188 | for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, 189 | prefix=package.__name__ + "."): 190 | modules.append(__import__(modname, fromlist="dummy")) 191 | return modules 192 | -------------------------------------------------------------------------------- /flask_boost/project/application/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/controllers/__init__.py -------------------------------------------------------------------------------- /flask_boost/project/application/controllers/account.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import render_template, Blueprint, redirect, request, url_for 3 | from ..forms import SigninForm, SignupForm 4 | from ..utils.account import signin_user, signout_user 5 | from ..utils.permissions import VisitorPermission, UserPermission 6 | from ..models import db, User 7 | 8 | bp = Blueprint('account', __name__) 9 | 10 | 11 | @bp.route('/signin', methods=['GET', 'POST']) 12 | @VisitorPermission() 13 | def signin(): 14 | """Signin""" 15 | form = SigninForm() 16 | if form.validate_on_submit(): 17 | signin_user(form.user) 18 | return redirect(url_for('site.index')) 19 | return render_template('account/signin/signin.html', form=form) 20 | 21 | 22 | @bp.route('/signup', methods=['GET', 'POST']) 23 | @VisitorPermission() 24 | def signup(): 25 | """Signup""" 26 | form = SignupForm() 27 | if form.validate_on_submit(): 28 | params = form.data.copy() 29 | params.pop('repassword') 30 | user = User(**params) 31 | db.session.add(user) 32 | db.session.commit() 33 | signin_user(user) 34 | return redirect(url_for('site.index')) 35 | return render_template('account/signup/signup.html', form=form) 36 | 37 | 38 | @bp.route('/signout') 39 | def signout(): 40 | """Signout""" 41 | signout_user() 42 | return redirect(request.referrer or url_for('site.index')) 43 | -------------------------------------------------------------------------------- /flask_boost/project/application/controllers/site.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import render_template, Blueprint 3 | 4 | bp = Blueprint('site', __name__) 5 | 6 | 7 | @bp.route('/') 8 | def index(): 9 | """Index page.""" 10 | return render_template('site/index/index.html') 11 | 12 | 13 | @bp.route('/about') 14 | def about(): 15 | """About page.""" 16 | return render_template('site/about/about.html') 17 | -------------------------------------------------------------------------------- /flask_boost/project/application/fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.config.set('project.include', ['pages/**', 'macros/**', 'static/**']); 2 | fis.config.set('project.exclude', ['pages/**.less', 'macros/**.less', 'static/**.less']); 3 | fis.config.set('modules.postpackager', 'simple'); 4 | fis.config.set('pack', { 5 | 'pkg/libs.js': [ 6 | 'static/js/libs/jquery.min.js', 7 | 'static/js/libs/bootstrap.min.js', 8 | 'static/js/libs/respond.min.js', 9 | 'static/js/init.js' 10 | ], 11 | 'pkg/layout.js': [ 12 | 'static/js/layout.js', 13 | 'static/output/macros.js' 14 | ], 15 | 'pkg/libs.css': [ 16 | 'static/css/libs/*.css' 17 | ], 18 | 'pkg/layout.css': [ 19 | 'static/css/bootstrap.theme.css', 20 | 'static/css/common.css', 21 | 'static/output/macros.css', 22 | 'static/css/layout.css' 23 | ] 24 | }); 25 | -------------------------------------------------------------------------------- /flask_boost/project/application/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .account import * -------------------------------------------------------------------------------- /flask_boost/project/application/forms/_helper.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import re 3 | from urlparse import urlparse 4 | 5 | 6 | def check_url(form, field): 7 | """Check url schema.""" 8 | url = field.data.strip() 9 | if not url: 10 | return 11 | result = urlparse(url) 12 | if result.scheme == "": 13 | field.data = "http://%s" % re.sub(r'^:?/*', '', url) -------------------------------------------------------------------------------- /flask_boost/project/application/forms/account.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField, PasswordField 4 | from wtforms.validators import DataRequired, Email, EqualTo 5 | from ..models import User 6 | 7 | 8 | class SigninForm(Form): 9 | """Form for signin""" 10 | email = StringField('Email', 11 | validators=[ 12 | DataRequired("Email shouldn't be empty."), 13 | Email('Email format is not correct.') 14 | ]) 15 | 16 | password = PasswordField('Password', 17 | validators=[DataRequired("Password shouldn't be empty.")]) 18 | 19 | def validate_email(self, field): 20 | user = User.query.filter(User.email == self.email.data).first() 21 | if not user: 22 | raise ValueError("Account doesn't exist.") 23 | 24 | def validate_password(self, field): 25 | if self.email.data: 26 | user = User.query.filter(User.email == self.email.data).first() 27 | if not user or not user.check_password(self.password.data): 28 | raise ValueError('Password is not correct.') 29 | else: 30 | self.user = user 31 | 32 | 33 | class SignupForm(Form): 34 | """Form for signin""" 35 | name = StringField('Username', 36 | validators=[DataRequired("Username shouldn't be empty.")]) 37 | 38 | email = StringField('Email', 39 | validators=[ 40 | DataRequired(message="Email shouldn't be empty."), 41 | Email(message='Email format is not correct.') 42 | ]) 43 | 44 | password = PasswordField('Password', 45 | validators=[DataRequired("Password shouldn't be empty.")]) 46 | 47 | repassword = PasswordField('Retype password', 48 | validators=[ 49 | DataRequired("Please retype the password."), 50 | EqualTo('password', message="Passwords must match.") 51 | ]) 52 | 53 | def validate_name(self, field): 54 | user = User.query.filter(User.name == self.name.data).first() 55 | if user: 56 | raise ValueError('This username already exists.') 57 | 58 | def validate_email(self, field): 59 | user = User.query.filter(User.email == self.email.data).first() 60 | if user: 61 | raise ValueError('This email already exists.') 62 | -------------------------------------------------------------------------------- /flask_boost/project/application/macros/_form.html: -------------------------------------------------------------------------------- 1 | {% macro field_errors(field) %} 2 |
    3 |
  • {{ field.errors[0] }}
  • 4 |
5 | {% endmacro %} 6 | 7 | 8 | {% macro vertical_field(field) %} 9 |
10 | {{ field.label() }} 11 | {{ field(class="form-control", placeholder=field.description, **kwargs) }} 12 | {{ field_errors(field) }} 13 |
14 | {% endmacro %} 15 | -------------------------------------------------------------------------------- /flask_boost/project/application/macros/_utils.html: -------------------------------------------------------------------------------- 1 | {% macro pagination(paginator) %} 2 | {% if paginator.pages > 1 %} 3 |
    4 | {% if paginator.has_prev %} 5 |
  • 上一页
  • 6 | {% endif %} 7 | 8 | {% for page in paginator.iter_pages() %} 9 | {% if page %} 10 | {% if page != paginator.page %} 11 |
  • {{ page }}
  • 12 | {% else %} 13 |
  • {{ page }}
  • 14 | {% endif %} 15 | {% else %} 16 |
  • ...
  • 17 | {% endif %} 18 | {% endfor %} 19 | 20 | {% if paginator.has_next %} 21 |
  • 下一页
  • 22 | {% endif %} 23 |
24 | {% endif %} 25 | {% endmacro %} 26 | -------------------------------------------------------------------------------- /flask_boost/project/application/macros/user/users/_users.html: -------------------------------------------------------------------------------- 1 | {% macro render_users(users) %} 2 | {% for user in users %} 3 |
4 |
{{ user.name }}
5 |
6 | {% endfor %} 7 | {% endmacro %} 8 | -------------------------------------------------------------------------------- /flask_boost/project/application/macros/user/users/_users.js: -------------------------------------------------------------------------------- 1 | $(document).on('click', '.g-user', function () { 2 | console.log('user clicked'); 3 | }); 4 | -------------------------------------------------------------------------------- /flask_boost/project/application/macros/user/users/_users.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | .g-user { 4 | padding: 10px; 5 | } -------------------------------------------------------------------------------- /flask_boost/project/application/models/__init__.py: -------------------------------------------------------------------------------- 1 | from ._base import db 2 | from .user import * -------------------------------------------------------------------------------- /flask_boost/project/application/models/_base.py: -------------------------------------------------------------------------------- 1 | from flask.ext.sqlalchemy import SQLAlchemy 2 | 3 | db = SQLAlchemy() -------------------------------------------------------------------------------- /flask_boost/project/application/models/user.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from datetime import datetime 3 | from werkzeug.security import generate_password_hash, check_password_hash 4 | from ._base import db 5 | 6 | 7 | class User(db.Model): 8 | id = db.Column(db.Integer, primary_key=True) 9 | name = db.Column(db.String(50), unique=True) 10 | email = db.Column(db.String(50), unique=True) 11 | avatar = db.Column(db.String(200), default='default.png') 12 | password = db.Column(db.String(200)) 13 | is_admin = db.Column(db.Boolean, default=False) 14 | created_at = db.Column(db.DateTime, default=datetime.now) 15 | 16 | def __setattr__(self, name, value): 17 | # Hash password when set it. 18 | if name == 'password': 19 | value = generate_password_hash(value) 20 | super(User, self).__setattr__(name, value) 21 | 22 | def check_password(self, password): 23 | return check_password_hash(self.password, password) 24 | 25 | def __repr__(self): 26 | return '' % self.name 27 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signin/signin.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% from '_form.html' import vertical_field %} 3 | 4 | {% block page_title %}Sign in{% endblock %} 5 | {% block page_js %} 6 | {% endblock %} 7 | {% block page_css %} 8 | {% endblock %} 9 | {% block page_content %} 10 |
11 |
12 |
13 |

Sign in

14 | 15 | 16 | {{ form.csrf_token() }} 17 | 18 | {{ vertical_field(form.email) }} 19 | 20 | {{ vertical_field(form.password) }} 21 | 22 | 23 | 24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signin/signin.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signin/signin.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | #main { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signup/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% from '_form.html' import vertical_field, form_submit %} 3 | 4 | {% block page_title %}Sign up{% endblock %} 5 | {% block page_js %} 6 | {% endblock %} 7 | {% block page_css %} 8 | {% endblock %} 9 | {% block page_content %} 10 |
11 |
12 |
13 |

Sign up

14 | 15 |
16 | {{ form.csrf_token() }} 17 | 18 | {{ vertical_field(form.name) }} 19 | 20 | {{ vertical_field(form.email) }} 21 | 22 | {{ vertical_field(form.password) }} 23 | 24 | {{ vertical_field(form.repassword) }} 25 | 26 | 27 |
28 |
29 |
30 |
31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signup/signup.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/account/signup/signup.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | #main { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block page_title %}{% endblock %} 5 | 6 | 7 | 8 | 9 | 10 | {% block meta %}{% endblock %} 11 | 12 | {# CSS libs #} 13 | 14 | 15 | {# CSS layout #} 16 | 17 | 18 | 19 | 20 | {# CSS page #} 21 | {% block page_css %}{% endblock %} 22 | {# JS global vars #} 23 | 33 | {# JS libs #} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {# Flash message #} 43 | {% with message = get_flashed_messages()[0] %} 44 | {% if message %} 45 |
{{ message }}
46 | {% endif %} 47 | {% endwith %} 48 | 49 | 99 | 100 | {# Please DO NOT change the id below #} 101 |
102 | {% block page_content %}{% endblock %} 103 |
104 | 105 |
106 |
107 | 110 | 111 | 114 |
115 |
116 | 117 | {# JS layout #} 118 | 119 | 120 | {# JS macros #} 121 | 122 | 123 | {# JS page #} 124 | {% block page_js %}{% endblock %} 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/403/403.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}403{% endblock %} 4 | {% block page_content %} 5 |
6 |

403 Error

7 | 8 |

Sorry, you don't have permission to access on this.

9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/404/404.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}404{% endblock %} 4 | {% block page_content %} 5 |
6 |

404 Error

7 | 8 |

Sorry, the page you requested was not found.

9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/500/500.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}500{% endblock %} 4 | {% block page_content %} 5 |
6 |

500 Error

7 | 8 |

Sorry, something went wrong with the server.

9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/about/about.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}About{% endblock %} 4 | {% block page_js %} 5 | {% endblock %} 6 | {% block page_css %} 7 | {% endblock %} 8 | {% block page_content %} 9 |
10 |

About

11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/about/about.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/about/about.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | #main { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/index/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}#{project|title}{% endblock %} 4 | {% block page_js %} 5 | {% endblock %} 6 | {% block page_css %} 7 | {% endblock %} 8 | {% block page_content %} 9 |
10 |

Home

11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/index/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /flask_boost/project/application/pages/site/index/index.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | #main { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /flask_boost/project/application/static/css/bootstrap.theme.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | body { 4 | } -------------------------------------------------------------------------------- /flask_boost/project/application/static/css/common.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | // Text 4 | .text-error { 5 | color: red; 6 | } 7 | 8 | .text-light { 9 | color: gray; 10 | } 11 | 12 | // Table 13 | .table-vertical-middle > tbody > tr > td { 14 | vertical-align: middle; 15 | } 16 | 17 | // Avatar 18 | .avatar { 19 | width: 72px; 20 | height: 72px; 21 | } 22 | 23 | .avatar-sm { 24 | width: 48px; 25 | height: 48px; 26 | } 27 | 28 | .avatar-lg { 29 | width: 96px; 30 | height: 96px; 31 | } 32 | 33 | .avatar-xl { 34 | width: 176px; 35 | height: 176px; 36 | } 37 | 38 | // Layout 39 | .col-top-gap { 40 | margin-top: 20px; 41 | } 42 | -------------------------------------------------------------------------------- /flask_boost/project/application/static/css/layout.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | html { 4 | position: relative; 5 | min-height: 100%; 6 | } 7 | 8 | body { 9 | margin-bottom: 60px; 10 | } 11 | 12 | // Flash message 13 | .flash-message { 14 | background-color: @main-color; 15 | border: 0; 16 | color: #ffffff; 17 | margin-bottom: 0; 18 | position: fixed; 19 | width: 100%; 20 | top: 0; 21 | left: 0; 22 | z-index: 100000; 23 | border-radius: 0; 24 | text-align: center; 25 | display: none; 26 | padding: 0; 27 | // By changing the two style below 28 | // You can keep the height of flash message equal to nav height. 29 | height: 50px; 30 | line-height: 50px; 31 | } 32 | 33 | // Nav highlight 34 | .page-site-index #nav-index, 35 | .page-site-about #nav-about, 36 | .page-account-signin #nav-signin, 37 | .page-account-signup #nav-signup { 38 | a { 39 | color: #555; 40 | background-color: #e7e7e7; 41 | } 42 | } 43 | 44 | .navbar-form { 45 | margin: 10px 0; 46 | } 47 | 48 | footer { 49 | position: absolute; 50 | bottom: 0; 51 | width: 100%; 52 | /* Set the fixed height of the footer here */ 53 | height: 60px; 54 | border-top: 1px solid #e2e2e2; 55 | padding-top: 20px; 56 | } 57 | -------------------------------------------------------------------------------- /flask_boost/project/application/static/css/libs/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('/static/fonts/fontawesome-webfont.eot?v=4.3.0');src:url('/static/fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('/static/fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('/static/fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('/static/fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('/static/fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"} -------------------------------------------------------------------------------- /flask_boost/project/application/static/css/utils.less: -------------------------------------------------------------------------------- 1 | // Colors 2 | @main-color: #3fa7cb; 3 | @dark-main: darken(@main-color, 10%); 4 | @light-main: lighten(@main-color, 10%); 5 | 6 | // Sizes 7 | @screen-sm-min: 768px; 8 | @screen-md-min: 992px; 9 | @screen-lg-min: 1200px; 10 | @screen-xs-max: (@screen-sm-min - 1); 11 | @screen-sm-max: (@screen-md-min - 1); 12 | @screen-md-max: (@screen-lg-min - 1); 13 | 14 | .border-radius( @radius:5px ) { 15 | -webkit-border-radius: @radius; 16 | -moz-border-radius: @radius; 17 | -ms-border-radius: @radius; 18 | -o-border-radius: @radius; 19 | border-radius: @radius; 20 | } -------------------------------------------------------------------------------- /flask_boost/project/application/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /flask_boost/project/application/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /flask_boost/project/application/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /flask_boost/project/application/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /flask_boost/project/application/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /flask_boost/project/application/static/image/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/application/static/image/favicon.png -------------------------------------------------------------------------------- /flask_boost/project/application/static/js/init.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | // Find out params in routing rules 5 | var pattern = new RegExp("<[^:]*:?([^>]+)>", "g"); 6 | var result = null; 7 | 8 | $.each(g.rules, function (endpoint, rules) { 9 | $.each(rules, function (index, rule) { 10 | rule.params = []; 11 | while ((result = pattern.exec(rule.rule)) !== null) { 12 | rule.params.push(result[1]); 13 | } 14 | }); 15 | }); 16 | 17 | /** 18 | * Generate url for the endpoint. 19 | * urlFor(endpoint [, parameters] [, external]) 20 | * @param endpoint 21 | * @param parameters 22 | * @param external 23 | * @returns url for the endpoint. 24 | */ 25 | function urlFor(endpoint, parameters, external) { 26 | var url = null, 27 | params = [], 28 | maxMatchDegree = 0.0, 29 | keys; 30 | 31 | if ($.type(parameters) === "boolean") { 32 | external = parameters 33 | } 34 | 35 | parameters = ($.type(parameters) !== 'undefined') ? parameters : {}; 36 | external = ($.type(external) !== 'undefined') ? external : false; 37 | 38 | if (g.rules[endpoint] === undefined) { 39 | throw new Error("Uncorrect endpoint in urlFor(\"" + endpoint + "\", " + 40 | JSON.stringify(parameters) + ")"); 41 | } 42 | 43 | keys = $.map(parameters, function (value, key) { 44 | return key; 45 | }); 46 | 47 | // Find the first matched rule among rules in this endpoint. 48 | $.each(g.rules[endpoint], function (index, rule) { 49 | var match = true, 50 | currentMatchDegree = 0.0; 51 | 52 | $.each(rule.params, function (index, param) { 53 | if ($.inArray(param, keys) === -1) { 54 | match = false; 55 | return false; 56 | } 57 | }); 58 | 59 | if (match) { 60 | currentMatchDegree = parseFloat(rule.params.length) / keys.length; 61 | if (currentMatchDegree > maxMatchDegree || url === null) { 62 | maxMatchDegree = currentMatchDegree; 63 | url = rule.rule; 64 | params = rule.params; 65 | } 66 | } 67 | }); 68 | 69 | if (url) { 70 | $.each(keys, function (index, key) { 71 | // Build in params 72 | if ($.inArray(key, params) > -1) { 73 | url = url.replace(new RegExp("<[^:]*:?" + key + ">"), parameters[key]); 74 | } else { 75 | // Query string params 76 | if (url.indexOf("?") === -1) { 77 | url += "?"; 78 | } 79 | if (!endsWith(url, '?')) { 80 | url += "&"; 81 | } 82 | url += key + "=" + parameters[key]; 83 | } 84 | }); 85 | } else { 86 | throw new Error("Uncorrect parameters in urlFor(\"" + endpoint + "\", " + 87 | JSON.stringify(parameters) + ")"); 88 | } 89 | 90 | if (external) { 91 | url = g.domain + url 92 | } 93 | 94 | return url; 95 | } 96 | 97 | /** 98 | * Check whether str ends with suffix. 99 | * @param str 100 | * @param suffix 101 | * @returns {boolean} 102 | */ 103 | function endsWith(str, suffix) { 104 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 105 | } 106 | 107 | /** 108 | * Register context into global variable g. 109 | * @param context 110 | */ 111 | function registerContext(context) { 112 | if (typeof g === 'undefined') { 113 | throw new Error("Global variable g is not defined."); 114 | } 115 | 116 | $.each(context, function (key, value) { 117 | if (g.hasOwnProperty(key)) { 118 | throw new Error("The key '" + key + "' already exists in the global variable g."); 119 | } 120 | g[key] = value; 121 | }); 122 | } 123 | 124 | /** 125 | * Find elements in #main 126 | * @param selector 127 | * @returns {*|jQuery} 128 | */ 129 | function $page(selector) { 130 | return $('#main').find(selector); 131 | } 132 | 133 | window.$page = $page; 134 | window.urlFor = urlFor; 135 | window.registerContext = registerContext; 136 | })(); 137 | -------------------------------------------------------------------------------- /flask_boost/project/application/static/js/layout.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | // Add CSRF token header for Ajax request 5 | $.ajaxSetup({ 6 | beforeSend: function (xhr, settings) { 7 | if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) { 8 | xhr.setRequestHeader("X-CSRFToken", g.csrfToken); 9 | } 10 | } 11 | }); 12 | 13 | // Flash message 14 | setTimeout(showFlash, 200); 15 | setTimeout(hideFlash, 2000); 16 | 17 | /** 18 | * Show flash message. 19 | */ 20 | function showFlash() { 21 | $('.flash-message').slideDown('fast'); 22 | } 23 | 24 | /** 25 | * Hide flash message. 26 | */ 27 | function hideFlash() { 28 | $('.flash-message').slideUp('fast'); 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /flask_boost/project/application/static/js/libs/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /flask_boost/project/application/static/js/libs/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b now: 17 | return "right now" 18 | elif delta.days > 365: 19 | return '%d years ago' % (delta.days / 365) 20 | elif delta.days > 30: 21 | return '%d months ago' % (delta.days / 30) 22 | elif delta.days > 0: 23 | return '%d days ago' % delta.days 24 | elif delta.seconds > 3600: 25 | return '%d hours ago' % (delta.seconds / 3600) 26 | elif delta.seconds > 60: 27 | return '%d minutes ago' % (delta.seconds / 60) 28 | else: 29 | return 'right now' 30 | -------------------------------------------------------------------------------- /flask_boost/project/application/utils/helpers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | import errno 4 | from flask import current_app, url_for 5 | 6 | 7 | def absolute_url_for(endpoint, **values): 8 | """Absolute url for endpoint.""" 9 | config = current_app.config 10 | site_domain = config.get('SITE_DOMAIN') 11 | relative_url = url_for(endpoint, **values) 12 | return join_url(site_domain, relative_url) 13 | 14 | 15 | def join_url(pre_url, pro_url): 16 | return "%s/%s" % (pre_url.rstrip('/'), pro_url.lstrip('/')) 17 | 18 | 19 | def mkdir_p(path): 20 | try: 21 | os.makedirs(path) 22 | except OSError as exc: # Python >2.5 23 | if exc.errno == errno.EEXIST and os.path.isdir(path): 24 | pass 25 | else: 26 | raise 27 | -------------------------------------------------------------------------------- /flask_boost/project/application/utils/permissions.py: -------------------------------------------------------------------------------- 1 | from permission import Permission 2 | from .rules import VisitorRule, UserRule, AdminRule 3 | 4 | 5 | class VisitorPermission(Permission): 6 | def rule(self): 7 | return VisitorRule() 8 | 9 | 10 | class UserPermission(Permission): 11 | def rule(self): 12 | return UserRule() 13 | 14 | 15 | class AdminPermission(Permission): 16 | def rule(self): 17 | return AdminRule() 18 | -------------------------------------------------------------------------------- /flask_boost/project/application/utils/rules.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import session, abort, flash, redirect, url_for 3 | from permission import Rule 4 | from ..models import User 5 | 6 | 7 | class VisitorRule(Rule): 8 | def check(self): 9 | return 'user_id' not in session 10 | 11 | def deny(self): 12 | return redirect(url_for('site.index')) 13 | 14 | 15 | class UserRule(Rule): 16 | def check(self): 17 | return 'user_id' in session 18 | 19 | def deny(self): 20 | flash('Sign in first.') 21 | return redirect(url_for('account.signin')) 22 | 23 | 24 | class AdminRule(Rule): 25 | def base(self): 26 | return UserRule() 27 | 28 | def check(self): 29 | user_id = int(session['user_id']) 30 | user = User.query.filter(User.id == user_id).first() 31 | return user and user.is_admin 32 | 33 | def deny(self): 34 | abort(403) 35 | -------------------------------------------------------------------------------- /flask_boost/project/application/utils/security.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from itsdangerous import URLSafeSerializer, BadSignature 3 | from flask import current_app 4 | 5 | 6 | def encode(something): 7 | """Encode something with SECRET_KEY.""" 8 | secret_key = current_app.config.get('SECRET_KEY') 9 | s = URLSafeSerializer(secret_key) 10 | return s.dumps(something) 11 | 12 | 13 | def decode(something): 14 | """Decode something with SECRET_KEY.""" 15 | secret_key = current_app.config.get('SECRET_KEY') 16 | s = URLSafeSerializer(secret_key) 17 | try: 18 | return s.loads(something) 19 | except BadSignature: 20 | return None 21 | -------------------------------------------------------------------------------- /flask_boost/project/application/utils/sentry.py: -------------------------------------------------------------------------------- 1 | from raven.contrib.flask import Sentry 2 | 3 | sentry = Sentry() 4 | -------------------------------------------------------------------------------- /flask_boost/project/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "#{project}", 3 | "version": "0.0.1", 4 | "homepage": "", 5 | "authors": [], 6 | "description": "", 7 | "license": "MIT", 8 | "private": true, 9 | "dependencies": { 10 | "bootstrap": "3.3.5", 11 | "font-awesome": "4.5.0", 12 | "respond": "1.4.2", 13 | "jquery": "1.11.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /flask_boost/project/config/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: UTF-8 2 | import os 3 | 4 | 5 | def load_config(): 6 | """Load config.""" 7 | mode = os.environ.get('MODE') 8 | try: 9 | if mode == 'PRODUCTION': 10 | from .production import ProductionConfig 11 | return ProductionConfig 12 | elif mode == 'TESTING': 13 | from .testing import TestingConfig 14 | return TestingConfig 15 | else: 16 | from .development import DevelopmentConfig 17 | return DevelopmentConfig 18 | except ImportError: 19 | from .default import Config 20 | return Config -------------------------------------------------------------------------------- /flask_boost/project/config/default.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | 4 | 5 | class Config(object): 6 | """Base config class.""" 7 | # Flask app config 8 | DEBUG = False 9 | TESTING = False 10 | SECRET_KEY = "\xb5\xb3}#\xb7A\xcac\x9d0\xb6\x0f\x80z\x97\x00\x1e\xc0\xb8+\xe9)\xf0}" 11 | PERMANENT_SESSION_LIFETIME = 3600 * 24 * 7 12 | SESSION_COOKIE_NAME = '#{project}_session' 13 | 14 | # Root path of project 15 | PROJECT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 16 | 17 | # Site domain 18 | SITE_TITLE = "#{project}" 19 | SITE_DOMAIN = "http://localhost:5000" 20 | 21 | # SQLAlchemy config 22 | # See: 23 | # https://pythonhosted.org/Flask-SQLAlchemy/config.html#connection-uri-format 24 | # http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls 25 | SQLALCHEMY_DATABASE_URI = "mysql+pymysql://user:password@host/database" 26 | SQLALCHEMY_TRACK_MODIFICATIONS = False 27 | 28 | # Flask-DebugToolbar 29 | DEBUG_TB_INTERCEPT_REDIRECTS = False 30 | 31 | # Sentry config 32 | SENTRY_DSN = '' 33 | 34 | # Host string, used by fabric 35 | HOST_STRING = "root@12.34.56.78" 36 | -------------------------------------------------------------------------------- /flask_boost/project/config/development_sample.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .default import Config 3 | 4 | 5 | class DevelopmentConfig(Config): 6 | # App config 7 | DEBUG = True 8 | 9 | # SQLAlchemy config 10 | SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@localhost/#{project}" 11 | -------------------------------------------------------------------------------- /flask_boost/project/config/production_sample.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .default import Config 3 | 4 | 5 | class ProductionConfig(Config): 6 | # App config 7 | SECRET_KEY = "\xb5\xb3}#\xb7A\xcac\x9d0\xb6\x0f\x80z\x97\x00\x1e\xc0\xb8+\xe9)\xf0}" 8 | PERMANENT_SESSION_LIFETIME = 3600 * 24 * 7 9 | SESSION_COOKIE_NAME = '#{project}_session' 10 | 11 | # Site domain 12 | SITE_DOMAIN = "http://www.#{project}.com" 13 | 14 | # Db config 15 | SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:password@localhost/#{project}" 16 | 17 | # Sentry 18 | SENTRY_DSN = '' 19 | -------------------------------------------------------------------------------- /flask_boost/project/config/testing.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .default import Config 3 | 4 | 5 | class TestingConfig(Config): 6 | # App config 7 | TESTING = True 8 | 9 | # Disable csrf while testing 10 | WTF_CSRF_ENABLED = False 11 | 12 | # Db config 13 | SQLALCHEMY_DATABASE_URI = "sqlite:///%s/db/testing.sqlite3" % Config.PROJECT_PATH 14 | 15 | -------------------------------------------------------------------------------- /flask_boost/project/db/testing.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/db/testing.sqlite3 -------------------------------------------------------------------------------- /flask_boost/project/deploy/flask_env.sh: -------------------------------------------------------------------------------- 1 | export MODE=PRODUCTION 2 | -------------------------------------------------------------------------------- /flask_boost/project/deploy/gunicorn.conf: -------------------------------------------------------------------------------- 1 | workers = 2 2 | bind = '127.0.0.1:8888' 3 | proc_name = '#{project}' 4 | pidfile = '/tmp/#{project}.pid' -------------------------------------------------------------------------------- /flask_boost/project/deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | # Website 2 | server { 3 | listen 80; 4 | server_name www.#{project}.com; 5 | root /var/www/#{project}; 6 | 7 | location / { 8 | proxy_pass http://127.0.0.1:8888/; 9 | proxy_redirect off; 10 | proxy_set_header Host $host; 11 | proxy_set_header X-Real-IP $remote_addr; 12 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 | } 14 | 15 | location /static { 16 | root /var/www/#{project}/output; 17 | expires 15d; 18 | } 19 | 20 | location /pkg { 21 | root /var/www/#{project}/output; 22 | expires 15d; 23 | } 24 | 25 | location /pages { 26 | root /var/www/#{project}/output; 27 | expires 15d; 28 | } 29 | 30 | location /uploads { 31 | root /var/www/#{project}/; 32 | expires 15d; 33 | } 34 | } 35 | 36 | # 301 redirect 37 | server { 38 | listen 80; 39 | server_name #{project}.com; 40 | return 301 http://www.#{project}.com$request_uri; 41 | } -------------------------------------------------------------------------------- /flask_boost/project/deploy/supervisor.conf: -------------------------------------------------------------------------------- 1 | [program:#{project}] 2 | command=/var/www/#{project}/venv/bin/gunicorn -c deploy/gunicorn.conf wsgi:app 3 | directory=/var/www/#{project} 4 | user=root 5 | autostart=true 6 | autorestart=true 7 | environment = MODE="PRODUCTION" -------------------------------------------------------------------------------- /flask_boost/project/fabfile.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from fabric.api import run, env, cd, prefix, shell_env, local 3 | from config import load_config 4 | 5 | config = load_config() 6 | host_string = config.HOST_STRING 7 | 8 | 9 | def deploy(): 10 | env.host_string = config.HOST_STRING 11 | with cd('/var/www/#{project}'): 12 | with shell_env(MODE='PRODUCTION'): 13 | run('git reset --hard HEAD') 14 | run('git pull') 15 | run('npm install') 16 | run('gulp') 17 | with prefix('source venv/bin/activate'): 18 | run('pip install -r requirements.txt') 19 | run('python manage.py db upgrade') 20 | run('python manage.py build') 21 | run('supervisorctl restart #{project}') 22 | 23 | 24 | def restart(): 25 | env.host_string = config.HOST_STRING 26 | run('supervisorctl restart #{project}') 27 | -------------------------------------------------------------------------------- /flask_boost/project/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var less = require('gulp-less'); 4 | var concat = require('gulp-concat'); 5 | var watch = require('gulp-watch'); 6 | var batch = require('gulp-batch'); 7 | var plumber = require('gulp-plumber'); 8 | var header = require('gulp-header'); 9 | var footer = require('gulp-footer'); 10 | var rename = require("gulp-rename"); 11 | 12 | var root = './application'; 13 | 14 | gulp.task('macros-css', function () { 15 | return gulp 16 | .src(path.join(root, 'macros/**/_*.less')) 17 | .pipe(plumber()) 18 | .pipe(less({ 19 | paths: [path.join(root, 'static/css')] 20 | })) 21 | .pipe(concat('macros.css')) 22 | .pipe(gulp.dest(path.join(root, 'static/output/'))); 23 | }); 24 | 25 | gulp.task('macros-js', function () { 26 | return gulp 27 | .src(path.join(root, 'macros/**/_*.js')) 28 | .pipe(plumber()) 29 | .pipe(header('(function () {')) 30 | .pipe(footer('})();')) 31 | .pipe(concat('macros.js')) 32 | .pipe(gulp.dest(path.join(root, 'static/output/'))); 33 | }); 34 | 35 | gulp.task('pages-css', function () { 36 | return gulp 37 | .src(path.join(root, 'pages/**/*.less')) 38 | .pipe(plumber()) 39 | .pipe(less({ 40 | paths: [path.join(root, 'static/css')] 41 | })) 42 | .pipe(rename(function (path) { 43 | path.extname = ".css"; 44 | return path; 45 | })) 46 | .pipe(gulp.dest(path.join(root, 'pages'))); 47 | }); 48 | 49 | gulp.task('global-css', function () { 50 | return gulp 51 | .src(path.join(root, 'static/css/**/*.less')) 52 | .pipe(plumber()) 53 | .pipe(less({ 54 | paths: [path.join(root, 'static/css')] 55 | })) 56 | .pipe(rename(function (path) { 57 | path.extname = ".css"; 58 | return path; 59 | })) 60 | .pipe(gulp.dest(path.join(root, 'static/css'))); 61 | }); 62 | 63 | gulp.task('build', ['macros-css', 'macros-js', 'pages-css', 'global-css']); 64 | 65 | gulp.task('watch', ['build'], function () { 66 | watch(path.join(root, 'macros/**/_*.js'), batch(function (events, done) { 67 | gulp.start('macros-js', done); 68 | })); 69 | watch(path.join(root, 'macros/**/_*.less'), batch(function (events, done) { 70 | gulp.start('macros-css', done); 71 | })); 72 | watch(path.join(root, 'pages/**/*.less'), batch(function (events, done) { 73 | gulp.start('pages-css', done); 74 | })); 75 | watch(path.join(root, 'static/css/**/*.less'), batch(function (events, done) { 76 | gulp.start('global-css', done); 77 | })); 78 | }); 79 | 80 | gulp.task('default', ['build']); 81 | -------------------------------------------------------------------------------- /flask_boost/project/manage.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | import glob2 4 | from flask.ext.script import Manager 5 | from flask.ext.migrate import Migrate, MigrateCommand 6 | from application import create_app 7 | from application.models import db 8 | 9 | # Used by app debug & livereload 10 | PORT = 5000 11 | 12 | app = create_app() 13 | manager = Manager(app) 14 | 15 | # db migrate commands 16 | migrate = Migrate(app, db) 17 | manager.add_command('db', MigrateCommand) 18 | 19 | 20 | @manager.command 21 | def run(): 22 | """Run app.""" 23 | app.run(port=PORT) 24 | 25 | 26 | @manager.command 27 | def live(): 28 | """Run livereload server""" 29 | from livereload import Server 30 | 31 | server = Server(app) 32 | 33 | map(server.watch, glob2.glob("application/pages/**/*.*")) # pages 34 | map(server.watch, glob2.glob("application/macros/**/*.html")) # macros 35 | map(server.watch, glob2.glob("application/static/**/*.*")) # public assets 36 | 37 | server.serve(port=PORT) 38 | 39 | 40 | @manager.command 41 | def build(): 42 | """Use FIS to compile assets.""" 43 | os.system('gulp') 44 | os.chdir('application') 45 | os.system('fis release -d ../output -opmD') 46 | 47 | 48 | if __name__ == "__main__": 49 | manager.run() 50 | -------------------------------------------------------------------------------- /flask_boost/project/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /flask_boost/project/migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | file_template = %%(year)d%%(month).2d%%(day).2d%%(hour).2d%%(minute).2d%%(second).2d_%%(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 | -------------------------------------------------------------------------------- /flask_boost/project/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 | 6 | # this is the Alembic Config object, which provides 7 | # access to the values within the .ini file in use. 8 | config = context.config 9 | 10 | # Interpret the config file for Python logging. 11 | # This line sets up loggers basically. 12 | fileConfig(config.config_file_name) 13 | 14 | # add your model's MetaData object here 15 | # for 'autogenerate' support 16 | # from myapp import mymodel 17 | # target_metadata = mymodel.Base.metadata 18 | from flask import current_app 19 | config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) 20 | target_metadata = current_app.extensions['migrate'].db.metadata 21 | 22 | # other values from the config, defined by the needs of env.py, 23 | # can be acquired: 24 | # my_important_option = config.get_main_option("my_important_option") 25 | # ... etc. 26 | 27 | def run_migrations_offline(): 28 | """Run migrations in 'offline' mode. 29 | 30 | This configures the context with just a URL 31 | and not an Engine, though an Engine is acceptable 32 | here as well. By skipping the Engine creation 33 | we don't even need a DBAPI to be available. 34 | 35 | Calls to context.execute() here emit the given string to the 36 | script output. 37 | 38 | """ 39 | url = config.get_main_option("sqlalchemy.url") 40 | context.configure(url=url) 41 | 42 | with context.begin_transaction(): 43 | context.run_migrations() 44 | 45 | def run_migrations_online(): 46 | """Run migrations in 'online' mode. 47 | 48 | In this scenario we need to create an Engine 49 | and associate a connection with the context. 50 | 51 | """ 52 | engine = engine_from_config( 53 | config.get_section(config.config_ini_section), 54 | prefix='sqlalchemy.', 55 | poolclass=pool.NullPool) 56 | 57 | connection = engine.connect() 58 | context.configure( 59 | connection=connection, 60 | target_metadata=target_metadata 61 | ) 62 | 63 | try: 64 | with context.begin_transaction(): 65 | context.run_migrations() 66 | finally: 67 | connection.close() 68 | 69 | if context.is_offline_mode(): 70 | run_migrations_offline() 71 | else: 72 | run_migrations_online() 73 | 74 | -------------------------------------------------------------------------------- /flask_boost/project/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /flask_boost/project/migrations/versions/20150101130345_3d8f34e83d23_add_user_model.py: -------------------------------------------------------------------------------- 1 | """Add user model 2 | 3 | Revision ID: 3d8f34e83d23 4 | Revises: None 5 | Create Date: 2015-01-01 13:03:45.048687 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '3d8f34e83d23' 11 | down_revision = None 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.create_table('user', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('name', sa.String(length=50), nullable=True), 22 | sa.Column('email', sa.String(length=50), nullable=True), 23 | sa.Column('avatar', sa.String(length=200), nullable=True), 24 | sa.Column('password', sa.String(length=200), nullable=True), 25 | sa.Column('is_admin', sa.Boolean(), nullable=True), 26 | sa.Column('created_at', sa.DateTime(), nullable=True), 27 | sa.PrimaryKeyConstraint('id'), 28 | sa.UniqueConstraint('email'), 29 | sa.UniqueConstraint('name') 30 | ) 31 | ### end Alembic commands ### 32 | 33 | 34 | def downgrade(): 35 | ### commands auto generated by Alembic - please adjust! ### 36 | op.drop_table('user') 37 | ### end Alembic commands ### 38 | -------------------------------------------------------------------------------- /flask_boost/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flask-boost", 3 | "version": "0.5.2", 4 | "description": "=======", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/hustlzp/Flask-Boost.git" 15 | }, 16 | "author": "hustlzp", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/hustlzp/Flask-Boost/issues" 20 | }, 21 | "homepage": "https://github.com/hustlzp/Flask-Boost#readme", 22 | "devDependencies": { 23 | "fis": "^1.9.33", 24 | "fis-postpackager-simple": "0.0.26", 25 | "gulp": "^3.9.0", 26 | "gulp-batch": "^1.0.5", 27 | "gulp-concat": "^2.6.0", 28 | "gulp-footer": "^1.0.5", 29 | "gulp-header": "^1.7.1", 30 | "gulp-if": "^1.2.5", 31 | "gulp-less": "^3.0.3", 32 | "gulp-minify-css": "^1.2.1", 33 | "gulp-plumber": "^1.0.1", 34 | "gulp-rename": "^1.2.2", 35 | "gulp-tap": "^0.1.3", 36 | "gulp-uglify": "^1.4.1", 37 | "gulp-watch": "^4.3.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /flask_boost/project/pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # DEPRECATED 25 | include-ids=no 26 | 27 | # DEPRECATED 28 | symbols=no 29 | 30 | 31 | [MESSAGES CONTROL] 32 | 33 | # Enable the message, report, category or checker with the given id(s). You can 34 | # either give multiple identifier separated by comma (,) or put this option 35 | # multiple time. See also the "--disable" option for examples. 36 | #enable= 37 | 38 | # Disable the message, report, category or checker with the given id(s). You 39 | # can either give multiple identifiers separated by comma (,) or put this 40 | # option multiple times (only on the command line, not in the configuration 41 | # file where it should appear only once).You can also use "--disable=all" to 42 | # disable everything first and then reenable specific checks. For example, if 43 | # you want to run only the similarities checker, you can use "--disable=all 44 | # --enable=similarities". If you want to run only the classes checker, but have 45 | # no Warning level messages displayed, use"--disable=all --enable=classes 46 | # --disable=W" 47 | #disable= 48 | 49 | 50 | [REPORTS] 51 | 52 | # Set the output format. Available formats are text, parseable, colorized, msvs 53 | # (visual studio) and html. You can also give a reporter class, eg 54 | # mypackage.mymodule.MyReporterClass. 55 | output-format=parseable 56 | 57 | # Put messages in a separate file for each module / package specified on the 58 | # command line instead of printing them on stdout. Reports (if any) will be 59 | # written in a file name "pylint_global.[txt|html]". 60 | files-output=no 61 | 62 | # Tells whether to display a full report or only the messages 63 | reports=yes 64 | 65 | # Python expression which should return a note less than 10 (10 is the highest 66 | # note). You have access to the variables errors warning, statement which 67 | # respectively contain the number of errors / warnings messages and the total 68 | # number of statements analyzed. This is used by the global evaluation report 69 | # (RP0004). 70 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 71 | 72 | # Add a comment according to your evaluation note. This is used by the global 73 | # evaluation report (RP0004). 74 | comment=no 75 | 76 | # Template used to display messages. This is a python new-style format string 77 | # used to format the message information. See doc for all details 78 | #msg-template= 79 | 80 | 81 | [BASIC] 82 | 83 | # Required attributes for module, separated by a comma 84 | required-attributes= 85 | 86 | # List of builtins function names that should not be used, separated by a comma 87 | bad-functions=map,filter,apply,input,file 88 | 89 | # Good variable names which should always be accepted, separated by a comma 90 | good-names=i,j,k,ex,Run,_ 91 | 92 | # Bad variable names which should always be refused, separated by a comma 93 | bad-names=foo,bar,baz,toto,tutu,tata 94 | 95 | # Colon-delimited sets of names that determine each other's naming style when 96 | # the name regexes allow several styles. 97 | name-group= 98 | 99 | # Include a hint for the correct naming format with invalid-name 100 | include-naming-hint=no 101 | 102 | # Regular expression matching correct function names 103 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 104 | 105 | # Naming hint for function names 106 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 107 | 108 | # Regular expression matching correct variable names 109 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 110 | 111 | # Naming hint for variable names 112 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 113 | 114 | # Regular expression matching correct constant names 115 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 116 | 117 | # Naming hint for constant names 118 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 119 | 120 | # Regular expression matching correct attribute names 121 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 122 | 123 | # Naming hint for attribute names 124 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 125 | 126 | # Regular expression matching correct argument names 127 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 128 | 129 | # Naming hint for argument names 130 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 131 | 132 | # Regular expression matching correct class attribute names 133 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 134 | 135 | # Naming hint for class attribute names 136 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 137 | 138 | # Regular expression matching correct inline iteration names 139 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 140 | 141 | # Naming hint for inline iteration names 142 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 143 | 144 | # Regular expression matching correct class names 145 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 146 | 147 | # Naming hint for class names 148 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 149 | 150 | # Regular expression matching correct module names 151 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 152 | 153 | # Naming hint for module names 154 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 155 | 156 | # Regular expression matching correct method names 157 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 158 | 159 | # Naming hint for method names 160 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 161 | 162 | # Regular expression which should only match function or class names that do 163 | # not require a docstring. 164 | no-docstring-rgx=__.*__ 165 | 166 | # Minimum line length for functions/classes that require docstrings, shorter 167 | # ones are exempt. 168 | docstring-min-length=-1 169 | 170 | 171 | [FORMAT] 172 | 173 | # Maximum number of characters on a single line. 174 | max-line-length=100 175 | 176 | # Regexp for a line that is allowed to be longer than the limit. 177 | ignore-long-lines=^\s*(# )??$ 178 | 179 | # Allow the body of an if to be on the same line as the test if there is no 180 | # else. 181 | single-line-if-stmt=no 182 | 183 | # List of optional constructs for which whitespace checking is disabled 184 | no-space-check=trailing-comma,dict-separator 185 | 186 | # Maximum number of lines in a module 187 | max-module-lines=1000 188 | 189 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 190 | # tab). 191 | indent-string=' ' 192 | 193 | # Number of spaces of indent required inside a hanging or continued line. 194 | indent-after-paren=4 195 | 196 | 197 | [LOGGING] 198 | 199 | # Logging modules to check that the string format arguments are in logging 200 | # function parameter format 201 | logging-modules=logging 202 | 203 | 204 | [MISCELLANEOUS] 205 | 206 | # List of note tags to take in consideration, separated by a comma. 207 | notes=FIXME,XXX,TODO 208 | 209 | 210 | [SIMILARITIES] 211 | 212 | # Minimum lines number of a similarity. 213 | min-similarity-lines=4 214 | 215 | # Ignore comments when computing similarities. 216 | ignore-comments=yes 217 | 218 | # Ignore docstrings when computing similarities. 219 | ignore-docstrings=yes 220 | 221 | # Ignore imports when computing similarities. 222 | ignore-imports=no 223 | 224 | 225 | [TYPECHECK] 226 | 227 | # Tells whether missing members accessed in mixin class should be ignored. A 228 | # mixin class is detected if its name ends with "mixin" (case insensitive). 229 | ignore-mixin-members=yes 230 | 231 | # List of module names for which member attributes should not be checked 232 | # (useful for modules/projects where namespaces are manipulated during runtime 233 | # and thus existing member attributes cannot be deduced by static analysis 234 | ignored-modules= 235 | 236 | # List of classes names for which member attributes should not be checked 237 | # (useful for classes with attributes dynamically set). 238 | ignored-classes=SQLObject 239 | 240 | # When zope mode is activated, add a predefined set of Zope acquired attributes 241 | # to generated-members. 242 | zope=no 243 | 244 | # List of members which are set dynamically and missed by pylint inference 245 | # system, and so shouldn't trigger E0201 when accessed. Python regular 246 | # expressions are accepted. 247 | generated-members=REQUEST,acl_users,aq_parent 248 | 249 | 250 | [VARIABLES] 251 | 252 | # Tells whether we should check for unused import in __init__ files. 253 | init-import=no 254 | 255 | # A regular expression matching the name of dummy variables (i.e. expectedly 256 | # not used). 257 | dummy-variables-rgx=_$|dummy 258 | 259 | # List of additional names supposed to be defined in builtins. Remember that 260 | # you should avoid to define new builtins when possible. 261 | additional-builtins= 262 | 263 | 264 | [CLASSES] 265 | 266 | # List of interface methods to ignore, separated by a comma. This is used for 267 | # instance to not check methods defines in Zope's Interface base class. 268 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 269 | 270 | # List of method names used to declare (i.e. assign) instance attributes. 271 | defining-attr-methods=__init__,__new__,setUp 272 | 273 | # List of valid names for the first argument in a class method. 274 | valid-classmethod-first-arg=cls 275 | 276 | # List of valid names for the first argument in a metaclass class method. 277 | valid-metaclass-classmethod-first-arg=mcs 278 | 279 | 280 | [DESIGN] 281 | 282 | # Maximum number of arguments for function / method 283 | max-args=5 284 | 285 | # Argument names that match this expression will be ignored. Default to name 286 | # with leading underscore 287 | ignored-argument-names=_.* 288 | 289 | # Maximum number of locals for function / method body 290 | max-locals=15 291 | 292 | # Maximum number of return / yield for function / method body 293 | max-returns=6 294 | 295 | # Maximum number of branch for function / method body 296 | max-branches=12 297 | 298 | # Maximum number of statements in function / method body 299 | max-statements=50 300 | 301 | # Maximum number of parents for a class (see R0901). 302 | max-parents=7 303 | 304 | # Maximum number of attributes for a class (see R0902). 305 | max-attributes=7 306 | 307 | # Minimum number of public methods for a class (see R0903). 308 | min-public-methods=2 309 | 310 | # Maximum number of public methods for a class (see R0904). 311 | max-public-methods=20 312 | 313 | 314 | [IMPORTS] 315 | 316 | # Deprecated modules which should not be used, separated by a comma 317 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 318 | 319 | # Create a graph of every (i.e. internal and external) dependencies in the 320 | # given file (report RP0402 must not be disabled) 321 | import-graph= 322 | 323 | # Create a graph of external dependencies in the given file (report RP0402 must 324 | # not be disabled) 325 | ext-import-graph= 326 | 327 | # Create a graph of internal dependencies in the given file (report RP0402 must 328 | # not be disabled) 329 | int-import-graph= 330 | 331 | 332 | [EXCEPTIONS] 333 | 334 | # Exceptions that will emit a warning when being caught. Defaults to 335 | # "Exception" 336 | overgeneral-exceptions=Exception 337 | -------------------------------------------------------------------------------- /flask_boost/project/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-WTF==0.12 3 | Flask-Script==2.0.5 4 | Flask-SQLAlchemy==2.1 5 | Flask-DebugToolbar==0.10.0 6 | Flask-Migrate==1.6.0 7 | PyMySQL==0.6.7 8 | itsdangerous==0.24 9 | permission==0.3.0 10 | six==1.10.0 11 | raven==5.8.1 12 | 13 | # dev 14 | livereload==2.4.0 15 | nose==1.3.7 16 | coverage==4.0.2 17 | pylint==1.4.4 18 | glob2==0.4.1 19 | 20 | # deploy 21 | gunicorn==19.3.0 22 | Fabric==1.10.2 23 | -------------------------------------------------------------------------------- /flask_boost/project/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hustlzp/Flask-Boost/d0308408ebb248dd752b77123b845f8ec637fab2/flask_boost/project/tests/__init__.py -------------------------------------------------------------------------------- /flask_boost/project/tests/suite.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import os 3 | from application import create_app 4 | from application.models import db 5 | 6 | 7 | class BaseSuite(object): 8 | def setup(self): 9 | os.environ['MODE'] = 'TESTING' 10 | 11 | app = create_app() 12 | self.app = app 13 | self.client = app.test_client() 14 | 15 | with app.app_context(): 16 | db.drop_all() 17 | db.create_all() 18 | 19 | def teardown(self): 20 | with self.app.app_context(): 21 | db.drop_all() 22 | -------------------------------------------------------------------------------- /flask_boost/project/tests/test_site.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .suite import BaseSuite 3 | 4 | 5 | class TestSite(BaseSuite): 6 | def test_index(self): 7 | rv = self.client.get('/') 8 | assert rv.status_code == 200 -------------------------------------------------------------------------------- /flask_boost/project/wsgi.py: -------------------------------------------------------------------------------- 1 | from application import create_app 2 | 3 | app = create_app() -------------------------------------------------------------------------------- /flask_boost/templates/action.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block page_title %}#{action|title}{% endblock %} 4 | {% block page_js %} 5 | {% endblock %} 6 | {% block page_css %} 7 | {% endblock %} 8 | {% block page_content %} 9 |
10 |

#{action|title}

11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /flask_boost/templates/action.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /flask_boost/templates/action.less: -------------------------------------------------------------------------------- 1 | @import "utils"; 2 | 3 | #main { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /flask_boost/templates/action.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | @bp.route('/#{controller}/#{action}') 4 | def #{action}(): 5 | return render_template('#{controller}/#{action}/#{action}.html') 6 | -------------------------------------------------------------------------------- /flask_boost/templates/action_without_template.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | @bp.route('/#{controller}/#{action}') 4 | def #{action}(): 5 | pass 6 | -------------------------------------------------------------------------------- /flask_boost/templates/controller.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask import Blueprint, render_template 3 | 4 | bp = Blueprint('#{controller}', __name__) 5 | -------------------------------------------------------------------------------- /flask_boost/templates/form.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from flask_wtf import Form 3 | from wtforms import StringField 4 | from wtforms.validators import DataRequired 5 | 6 | 7 | class ExampleForm(Form): 8 | field = StringField('', validators=[DataRequired()]) 9 | -------------------------------------------------------------------------------- /flask_boost/templates/macro.html: -------------------------------------------------------------------------------- 1 | {% macro render_#{macro}() %} 2 | 3 | {% endmacro %} 4 | -------------------------------------------------------------------------------- /flask_boost/templates/model.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from datetime import datetime 3 | from ._base import db 4 | 5 | 6 | class #{model|title}(db.Model): 7 | id = db.Column(db.Integer, primary_key=True) 8 | name = db.Column(db.String(50), unique=True) 9 | created_at = db.Column(db.DateTime, default=datetime.now) 10 | 11 | def __repr__(self): 12 | return '<#{model|title} %s>' % self.name 13 | -------------------------------------------------------------------------------- /flask_boost/templates/unittest.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .suite import BaseSuite 3 | 4 | 5 | class Test#{controller|title}(BaseSuite): 6 | def test_action(self): 7 | rv = self.client.get('/#{controller}/action') 8 | assert rv.status_code == 200 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docopt==0.6.2 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import flask_boost 3 | 4 | entry_points = { 5 | "console_scripts": [ 6 | "boost = flask_boost.cli:main", 7 | ] 8 | } 9 | 10 | with open("requirements.txt") as f: 11 | requires = [l for l in f.read().splitlines() if l] 12 | 13 | setup( 14 | name='Flask-Boost', 15 | version=flask_boost.__version__, 16 | packages=find_packages(), 17 | include_package_data=True, 18 | description='Flask application generator for boosting your development.', 19 | long_description=open('README.rst').read(), 20 | url='https://github.com/hustlzp/Flask-Boost', 21 | author='hustlzp', 22 | author_email='hustlzp@gmail.com', 23 | license='MIT', 24 | keywords='flask sample generator', 25 | install_requires=requires, 26 | entry_points=entry_points, 27 | classifiers=[ 28 | 'Development Status :: 4 - Beta', 29 | 'Environment :: Web Environment', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Programming Language :: Python :: 2', 35 | 'Programming Language :: Python :: 2.7', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.5', 38 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 39 | 'Topic :: Internet :: WWW/HTTP :: WSGI', 40 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 41 | 'Topic :: Software Development :: Libraries :: Python Modules' 42 | ], 43 | ) --------------------------------------------------------------------------------