├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── babel.cfg ├── celery_run.py ├── entry.py ├── local.example.cfg ├── manage.py ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ └── ee69294e63e_init_state.py ├── packages.txt ├── project ├── __init__.py ├── api │ ├── __init__.py │ └── views.py ├── app.py ├── auth │ ├── __init__.py │ ├── forms.py │ ├── models.py │ ├── templates │ │ └── auth │ │ │ ├── index.html │ │ │ ├── macros.html │ │ │ ├── profile.html │ │ │ └── settings.html │ └── views.py ├── config.py ├── docs │ └── index.md ├── extensions.py ├── frontend │ ├── __init__.py │ ├── templates │ │ └── frontend │ │ │ ├── index.html │ │ │ └── user_profile.html │ └── views.py ├── models.py ├── tasks.py ├── templates │ ├── base.html │ ├── counter.html │ ├── macros.html │ ├── misc │ │ ├── 403.html │ │ ├── 404.html │ │ ├── 405.html │ │ ├── 500.html │ │ └── base.html │ ├── nav.html │ └── page.html ├── translations │ ├── en │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── messages.pot │ └── ru │ │ └── LC_MESSAGES │ │ └── messages.po └── utils.py ├── requirements.txt ├── setup.py └── static ├── .bowerrc ├── .gitattributes ├── bower.json ├── code.js ├── favicon.png ├── robots.txt └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | __pycache__ 3 | 4 | # Translations compiled files 5 | *.mo 6 | 7 | # Local development ignores 8 | venv 9 | data.sqlite 10 | 11 | # Production config 12 | local.cfg 13 | 14 | # Static 15 | static/libs/* 16 | 17 | # Other stuff here 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Fresh installation test 2 | # 3 | # To run this: sudo docker build . 4 | 5 | FROM stackbrew/ubuntu:saucy 6 | MAINTAINER Flask developers 7 | 8 | RUN apt-get update 9 | 10 | ADD ./ /code/ 11 | 12 | ENV DEBIAN_FRONTEND noninteractive 13 | RUN cat /code/packages.txt | xargs apt-get -y --force-yes install 14 | RUN npm install -g bower 15 | RUN ldconfig 16 | 17 | RUN cd /code/ && make setup 18 | 19 | RUN cd /code/src/ && python manage.py test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mikhail Kashkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: setup 2 | 3 | venv/bin/activate: 4 | if which virtualenv-2.7 >/dev/null; then virtualenv-2.7 venv; else virtualenv venv; fi 5 | 6 | run: venv/bin/activate requirements.txt 7 | . venv/bin/activate; python manage.py runserver -h 0.0.0.0 -d -r 8 | 9 | setup: venv/bin/activate requirements.txt 10 | . venv/bin/activate; pip install -Ur requirements.txt 11 | cd static && bower install 12 | 13 | init: venv/bin/activate requirements.txt 14 | . venv/bin/activate; python manage.py db upgrade 15 | 16 | babel: venv/bin/activate 17 | . venv/bin/activate; pybabel extract -F babel.cfg -o project/translations/messages.pot project 18 | 19 | # lazy babel scan 20 | lazybabel: venv/bin/activate 21 | . venv/bin/activate; pybabel extract -F babel.cfg -k lazy_gettext -o project/translations/messages.pot project 22 | 23 | # run: 24 | # $ LANG=en make addlang 25 | addlang: venv/bin/activate 26 | . venv/bin/activate; pybabel init -i project/translations/messages.pot -d project/translations -l $(LANG) 27 | 28 | updlang: venv/bin/activate 29 | . venv/bin/activate; pybabel update -i project/translations/messages.pot -d project/translations 30 | 31 | celery: 32 | . venv/bin/activate; python celery_run.py worker 33 | 34 | # celery in debug state 35 | dcelery: 36 | . venv/bin/activate; python celery_run.py worker -l info --autoreload 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask project template 2 | 3 | `Flask project template` contains a working example of a Flask project with features: 4 | 5 | - Ready to ship Flask project template 6 | - **Database migrations** out-of-the-box (uses Alembic) 7 | - Simple setup `make setup && make run` which make local virtualenv isolated environment and doesn't trash your system python. 8 | - Contains `Dockerfile` that allows to setup full Linux environment on any host OS supported by Docker 9 | - Static files managed by `bower`. By default, templates use `Bootstrap` but don't force you to use this UI framework. 10 | - Have a working example of GitHub **OAuth authorization**, you only need to provide your security and secret key (will work with example keys only on `127.0.0.1:5000`). Allow user login/logout 11 | - **i18n** and **l10n** via integrated Babel support and `make` targets 12 | - User settings page with the ability to switch the site language 13 | - `Flask-FlatPages` support to simplify static pages management 14 | - Built-In `Flask-Script` shell commands 15 | - **Fixtures** dump/restore support 16 | - Integrated [Celery](http://celeryproject.org/) background tasks management 17 | - Cache using [Flask-Cache](https://pythonhosted.org/Flask-Cache/) 18 | - Logging with an example of how to make email notifications when something goes wrong on the server 19 | 20 | ## How to start 21 | 22 | There are two ways. First way is use [`cookiecutter`](https://github.com/audreyr/cookiecutter) template (it located in [different repository](https://github.com/xen/flask-ptc)): 23 | 24 | cookiecutter https://github.com/xen/flask-ptc.git 25 | 26 | The second way is to manually clone this repository and change it later by own. Project is ready to run (with some requirements). You need to clone and run: 27 | 28 | ```sh 29 | $ mkdir Project 30 | $ cd Project 31 | $ git clone git@github.com:xen/flask-project-template.git . 32 | $ make 33 | $ make run 34 | ``` 35 | 36 | Open http://127.0.0.1:5000/, customize project files and **have fun**. 37 | 38 | If you have any ideas about how to improve it [Fork project](https://github.com/xen/flask-project-template/fork) and send me a pull request. 39 | 40 | ## Requirements 41 | 42 | If you never worked with python projects then the simplest way is run project inside Docker. Follow instruction [how to install Docker in your OS](https://docs.docker.com/installation/). 43 | 44 | If you familiar with web development then you need Python and Node.js: 45 | - Recent Python supported version with SQLite library (usually it is included) 46 | - Working `virtualenv` command, a name can vary, so you can change it inside `Makefile` 47 | - `make` 48 | - [`bower`](http://bower.io/), if you already have `node.js` with `npm` then run this command: 49 | 50 | ```sh 51 | npm install -g bower 52 | ``` 53 | 54 | ## macOS 55 | 56 | How to make full Python setup on macOS is not topic that can be cowered quickly (because you will need XCode and few more steps). One of the preferred ways to install required packages is to use `brew`. Memcached and Redis are not necessary for all sites, but I have included them in the project since my projects usually depend on them. If you need them also then install [`brew`](http://brew.sh) and then run this command: 57 | 58 | ```sh 59 | brew install python # or python3 or pypy 60 | brew install memcached libmemcached redis 61 | ``` 62 | 63 | You also can use `brew` to install your preferred RDBMS, nginx or whatever you need for your development. 64 | 65 | **Can I use Python 3?** 66 | 67 | This Flask project template is Python 3 ready, but unfortunately, some of the Flask extensions or python modules can be incompatible with Python 3.x. If you are sure that you don't need one of them then try to use Python 3.x or PyPy. 68 | 69 | ## Included modules support 70 | 71 | - [`Flask`](http://flask.pocoo.org/) & [`Werkzeug`](http://werkzeug.pocoo.org/) — base for everything. 72 | - [`Babel`](http://babel.pocoo.org/) & [`Flask-Babel`](https://pythonhosted.org/Flask-Babel/) — i18n support. 73 | - [`Flask-FlatPages`](https://pythonhosted.org/Flask-FlatPages/) with [`Markdown`](https://pythonhosted.org/Markdown/) — to maintain auxiliary pages (About, Contacts, etc). 74 | - [`Flask-Script`](http://flask-script.readthedocs.org/) — simplify management tasks. 75 | - [`Flask-WTF`](https://flask-wtf.readthedocs.org/) — form validation. 76 | - [`flask-restless`](http://flask-restless.readthedocs.org/) — restful API generator. 77 | - [`Flask-SQLAlchemy`](https://pythonhosted.org/Flask-SQLAlchemy/) — database ORM layer build on top of SQLAlchemy, best python ORM with depth and flexibility. 78 | - [`flask-migrate`](http://flask-migrate.readthedocs.org/) — database schema migration support. 79 | - [`python_social_auth`](https://github.com/omab/python-social-auth) & [`Flask-Login`](https://flask-login.readthedocs.org/) — social networks login. 80 | - [`Celery`](http://celeryproject.org/) — background and deferred tasks broker. 81 | - [`Flask-Cache`](https://pythonhosted.org/Flask-Cache/) — tiny cache extension. Since I found myself using cache in most projects added this package to the list. 82 | 83 | ## `make` commands overview 84 | 85 | There are several useful targets in `Makefile`: 86 | 87 | - `setup` — will make local virtualenv environment and install all dependencies including JavaScript libraries in `static` folder 88 | - `run` — local run server in DEBUG mode 89 | - `init` — synchronize database scheme and apply migrations. This target should be idempotent (if you run in several times you will get the same results in the end), but if you work with several databases at once sometimes in need manual tuning. 90 | - `babel` and `lazybabel` — generate `project/translations/messages.pot` file with different strategy. 91 | - `addlang` — add new language translation with code taken from shell variable `LANG`. Simple usage example `$ LANG=en make addlang` 92 | - `updlang` — update language files in language folders made by `addlang` command. 93 | - `celery` — run celery broker 94 | - `dcelery` — run celery broker in debug state with code reload and verbose output. Sometimes require manual reloads, but handier during development 95 | 96 | Translation workflow in nutshell: 97 | 98 | - Edit templates and py files 99 | - Run `lazybabel` if new translations strings were added or modified 100 | - Run `updlang` to apply master `.pot` files changes to `.po` language files 101 | - Run `addlang` if you need to support another language 102 | - Update `project/config.py` `LANGUAGES` dict to allow users to see new translations in the Settings page 103 | - Use [Poedit](http://poedit.net/) to translate strings 104 | 105 | ## `manage.py` command overview 106 | 107 | `Flask-Script` added flavor of Django `manage.py` to Flask development. Put your common management tasks inside this file. Migration commands already available with `db` prefix. For example how to make new migration and upgrade your database: 108 | 109 | ```sh 110 | $ python manage.py db migrate -m "new models" 111 | $ python manage.py db upgrade 112 | # don't forget to add a new file under git control 113 | $ git add migrations/versions/*.py 114 | ``` 115 | 116 | By default `manage.py` already have these commands: 117 | 118 | * `init` — recreate all tables in the database. Usually, you don't need to use this command since it will erase all your data, but on the empty environment can be useful in the local environment. 119 | * `dump` — make fixture and save all data contained in models defined in `dump_models` variable. 120 | * `restore` — restore fixtures from a file created by `dump` command. 121 | 122 | ## Project structure 123 | 124 | After you check out this code you may need to rename folder `project` to something more relevant your needs. I prefer to have own name for each project. Next step to change all mentions of the word `project` in your code. I don't add any code generators for this project since anyway make code reviews every time starting new Flask project by adding or removing extensions or some parts of the source code. 125 | 126 | . 127 | ├── Dockerfile 128 | 129 | If you need to run the project inside `Docker` 130 | 131 | ├── Makefile 132 | ├── README.md 133 | ├── babel.cfg 134 | 135 | Configuration for `Flask-Babel`, generally you don't need to edit this file unless you use a different template system. 136 | 137 | ├── celery_run.py 138 | 139 | To run Celery broker use this file. 140 | 141 | ├── entry.py 142 | 143 | To run Flask server use this file, it is already prepared for `uwsgi`, `mod_wsgi` or other wsgi web server modules. 144 | 145 | ├── local.example.cfg 146 | 147 | Rename this file to `local.cfg` and use on different versions on product, test and development environment. 148 | 149 | ├── manage.py 150 | 151 | Use this file to register management commands. Alembic commands set already included with `db` prefix. 152 | 153 | ├── migrations 154 | │ ├── README 155 | │ ├── alembic.ini 156 | │ ├── env.py 157 | │ ├── script.py.mako 158 | │ └── versions 159 | │ └── ee69294e63e_init_state.py 160 | 161 | Migrations folder contains all your database migrations. 162 | 163 | ├── packages.txt 164 | 165 | This file used by Docker and contains all Ubuntu packages that need to be installed on a fresh Ubuntu server. 166 | 167 | ├── project 168 | 169 | Your project code is here 170 | 171 | │ ├── __init__.py 172 | │ ├── api 173 | │ │ ├── __init__.py 174 | │ │ └── views.py 175 | 176 | Put here your admin or API views created by `Flask-Admin` or `Flask-Restless`. 177 | 178 | │ ├── app.py 179 | 180 | This cornerstone part of the project structure. But export only two functions `create_app` and `create_celery`. More info [inside file](https://github.com/xen/flask-project-template/blob/master/project/app.py). 181 | 182 | │ ├── auth 183 | │ │ ├── __init__.py 184 | │ │ ├── forms.py 185 | │ │ ├── models.py 186 | │ │ ├── templates 187 | │ │ │ └── auth 188 | │ │ │ ├── index.html 189 | │ │ │ ├── macros.html 190 | │ │ │ ├── profile.html 191 | │ │ │ └── settings.html 192 | │ │ └── views.py 193 | 194 | `project.auth` is working example of blueprint which shows how to organize user authentication using different OAuth providers, such as Facebook, GitHub, Twitter, etc. [Full list of supported social backends](http://psa.matiasaguirre.net/docs/backends/index.html#social-backends) available in `python-social-auth` documentation page. 195 | 196 | │ ├── config.py 197 | 198 | The file contains default configuration for the project. My approach to have code that can run with defaults. When you don't need special Postgres or other database features on deployment environment for testing purpose enough to use SQLite, but a set of projects that are database agnostic is very limited in real life. More about [configuration](#configuration) is separate chapter. 199 | 200 | │ ├── docs 201 | │ │ └── index.md 202 | 203 | Have a section with simple text files is common for sites. Sometimes you need to have "Contacts" or "Features" page without dynamic elements. Just simple HTML. Here are these files. By default available by `frontend.page` route, if you need to change it see inside [`frontend/views.py`](https://github.com/xen/flask-project-template/blob/master/project/frontend/views.py). 204 | 205 | │ ├── extensions.py 206 | 207 | All Flask extensions registered here. You can access them by import from this file. More information is available in [configuration](#configuration) chapter. 208 | 209 | │ ├── frontend 210 | │ │ ├── __init__.py 211 | │ │ ├── templates 212 | │ │ │ └── frontend 213 | │ │ │ ├── index.html 214 | │ │ │ └── user_profile.html 215 | │ │ └── views.py 216 | 217 | Frontpage of the site and some useful common pages combined in one blueprint. Generally, each site section has its blueprint, but if you are not sure where to put something small put it here. 218 | 219 | │ ├── models.py 220 | 221 | Helper to access `SQLAlchemy` models. I found very comfortable to have all models collected together in one place. Since your models always mapped into the database you never should have conflict errors using the same name because the database doesn't allow to have several tables with the same name. 222 | 223 | │ ├── tasks.py 224 | 225 | Celery tasks placed here. If you have worked with Celery you will found yourself familiar with this concept. If you need to spit tasks in different files then follow the idea of `models.py`. 226 | 227 | │ ├── templates 228 | │ │ ├── base.html 229 | │ │ ├── counter.html 230 | │ │ ├── macros.html 231 | │ │ ├── misc 232 | │ │ │ ├── 403.html 233 | │ │ │ ├── 404.html 234 | │ │ │ ├── 405.html 235 | │ │ │ ├── 500.html 236 | │ │ │ └── base.html 237 | │ │ ├── nav.html 238 | │ │ └── page.html 239 | 240 | There are basic site templates. Each blueprint has it's own `template/` folder because of the recommendation of Jinja documentation. If you don't want to read how the Jinja environment lookup working then just follow this pattern. For your convenience `misc` folder contains templates for common error pages. 241 | 242 | │ ├── translations 243 | │ │ ├── en 244 | │ │ │ └── LC_MESSAGES 245 | │ │ │ └── messages.po 246 | │ │ ├── messages.pot 247 | │ │ └── ru 248 | │ │ └── LC_MESSAGES 249 | │ │ ├── messages.mo 250 | │ │ └── messages.po 251 | 252 | If you don't need internationalization you can ignore this folder. If you don't then your translation strings located here. `Po` files are standard for translation and internationalization of different projects. Always cover text inside `_` (underscore) function, project code contains all needed examples. 253 | 254 | │ └── utils.py 255 | 256 | Trash-can for all common usefulness that can't find a place in other parts of the code. 257 | 258 | ├── requirements.txt 259 | 260 | All project dependencies installed by `pip`. 261 | 262 | ├── setup.py 263 | 264 | This file makes your folder python project that can be wrapped into an egg and distributed by DevOps. This part is not covered in the documentation and usually needed on past stages of the project. 265 | 266 | └── static 267 | ├── bower.json 268 | ├── code.js 269 | ├── favicon.png 270 | ├── libs 271 | ├── robots.txt 272 | └── style.css 273 | 274 | All static dependencies are installed by [`bower`](http://bower.io/) packages manager. Of course, you can get rid of `node.js` as a dependency, but I found that full automation saves a vast amount of time and it is almost impossible to avoid of using popular JavaScript frameworks or compilers. Why avoid such things as `browserify` or CoffeScript? By default site already use Bootstrap. 275 | 276 | ## Configuration 277 | 278 | Already mention my approach: the project should be able to start with minimum external dependencies. Of course, if the project grows up the probability of using individual features increase. For example, Postgres has different capabilities then SQLite, but this Flask project template trying to be as much agnostic as it can. That is why `config.py` is not empty, I attempt to make you able to start your simple Flask application after git checkout. 279 | 280 | Of course, you will make changes to your configuration, but more importantly how your project will work after the development phase and how it flexible will be if you have a team of developers. 281 | 282 | My recommendation store everything common and insensitive inside `config.py`. But if you need to have personal settings then store in `local.cfg` and put this file in the root folder of the project. 283 | 284 | But still, it is not flexible enough. What if you need to connect staging or test database? Then you can use the option `-c` to define different config file: 285 | 286 | $ python manage.py runserver -c test.cfg 287 | # if you need to apply the migration to test database 288 | $ python manage.py -c test.cfg db upgrade 289 | 290 | This time Flask will read configuration this order: 291 | 292 | 1. `config.py` inside the project folder 293 | 2. Try to find `local.cfg` in the parent folder 294 | 3. Configuration file provided by command line 295 | 296 | Notice that `cfg` files don't execute it is simple text files. In the end configuration variable will have the last value it was mentioned. For example if `MAIL_SERVER` defined in `config.py` and redefined in `local.cfg` then the last value will be used by Flask application. 297 | 298 | This approach covers most cases I have in my practice. Show your DevOp how to use `cfg` and put inside variables he/she needs to change. 299 | 300 | Also `local.cfg` is ignored in `.gitignore` so you will not accidentally put your database passwords to a public repository. 301 | 302 | ## `app.py` — cornerstone part of your application 303 | -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **.html] 3 | extensions=jinja2.ext.autoescape,jinja2.ext.with_ 4 | -------------------------------------------------------------------------------- /celery_run.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | from project import create_app, create_celery 4 | 5 | app = create_app(config='../local.cfg') 6 | celery = create_celery(app) 7 | celery.start() 8 | -------------------------------------------------------------------------------- /entry.py: -------------------------------------------------------------------------------- 1 | from project import create_app 2 | 3 | app = create_app(config='../local.cfg') 4 | 5 | if __name__ == '__main__': 6 | app.run() 7 | -------------------------------------------------------------------------------- /local.example.cfg: -------------------------------------------------------------------------------- 1 | # production mode, yopta 2 | DEBUG = False 3 | 4 | SQLALCHEMY_ECHO = True 5 | SQLALCHEMY_RECORD_QUERIES = False 6 | 7 | # this is example deployment configuration 8 | 9 | # run python and execute this code 10 | # >>> import os; os.urandom(24) 11 | # value place here as result 12 | 13 | # SECRET_KEY = '' 14 | 15 | # BROKER_URL = "mongodb://localhost" 16 | # CELERY_RESULT_BACKEND = 'mongodb' 17 | # CELERY_IMPORTS = ('backend.tasks',) 18 | # CELERY_MONGODB_BACKEND_SETTINGS = { 19 | # 'host': 'localhost', 20 | # 'port': 27017, 21 | # 'database': 'digdata_celery', 22 | # #'user': user, 23 | # #'password': password, 24 | # 'taskmeta_collection': 'teskmeta' 25 | # } 26 | # CELERY_ANNOTATIONS = {"*": {"rate_limit": "10/s"}} 27 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import json 5 | 6 | from flask_script import Manager 7 | from flask_migrate import MigrateCommand 8 | 9 | from project import create_app 10 | from project.extensions import db 11 | 12 | from sqlalchemy.exc import OperationalError 13 | from sqlalchemy.ext.serializer import dumps, loads 14 | 15 | manager = Manager(create_app) 16 | 17 | # Add Flask-Migrate commands under `db` prefix, for example: 18 | # $ python manage.py db init 19 | # $ python manage.py db migrate 20 | manager.add_command('db', MigrateCommand) 21 | 22 | 23 | @manager.command 24 | def init(): 25 | """Run in local machine.""" 26 | syncdb() 27 | 28 | 29 | @manager.command 30 | def syncdb(): 31 | """Init/reset database.""" 32 | db.drop_all() 33 | db.create_all() 34 | 35 | 36 | @manager.option('-s', '--source', dest='source', 37 | default='data/serialized_dump.txt', 38 | required=False, help='Restore fixture from dump') 39 | def restore(source='data/serialized_dump.txt'): 40 | print("Start importing data") 41 | with open(source, 'rb') as f: 42 | data = json.loads(f.readline()) 43 | for model_data in data: 44 | try: 45 | restored = loads(model_data, db.metadata, db.session) 46 | except AttributeError as e: 47 | print('Table does not exist: {}'.format(e)) 48 | continue 49 | if restored: 50 | print('Importing {} table...'.format(restored[0].__table__.name)) 51 | for item in restored: 52 | db.session.merge(item) 53 | 54 | db.session.commit() 55 | 56 | print('Done') 57 | 58 | 59 | @manager.option('-d', '--destination', dest='destination', default=None, required=True, help='Output file') 60 | def dump(destination): 61 | dump_models = [] # List of models you want to dump 62 | serialized = list() 63 | for model in dump_models: 64 | print('Dumping {}'.format(model)) 65 | serialized.append(unicode(dumps(db.session.query(model).all()), errors='ignore')) 66 | with open(destination, 'w') as f: 67 | f.writelines(json.dumps(serialized)) 68 | print('Done.') 69 | 70 | 71 | manager.add_option('-c', '--config', dest="config", required=False, 72 | help="config file") 73 | 74 | if __name__ == "__main__": 75 | manager.run() 76 | -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | 47 | [alembic:exclude] 48 | tables = -------------------------------------------------------------------------------- /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 | 28 | def exclude_tables_from_config(config_): 29 | tables_ = config_.get("tables", None) 30 | if tables_ is not None: 31 | tables = tables_.split(",") 32 | return tables 33 | 34 | exclude_tables = exclude_tables_from_config(config.get_section('alembic:exclude')) 35 | 36 | 37 | def include_object(object, name, type_, reflected, compare_to): 38 | if type_ == "table" and name in exclude_tables: 39 | return False 40 | else: 41 | return True 42 | 43 | 44 | def run_migrations_offline(): 45 | """Run migrations in 'offline' mode. 46 | 47 | This configures the context with just a URL 48 | and not an Engine, though an Engine is acceptable 49 | here as well. By skipping the Engine creation 50 | we don't even need a DBAPI to be available. 51 | 52 | Calls to context.execute() here emit the given string to the 53 | script output. 54 | 55 | """ 56 | url = config.get_main_option("sqlalchemy.url") 57 | context.configure(url=url) 58 | 59 | with context.begin_transaction(): 60 | context.run_migrations() 61 | 62 | 63 | def run_migrations_online(): 64 | """Run migrations in 'online' mode. 65 | 66 | In this scenario we need to create an Engine 67 | and associate a connection with the context. 68 | 69 | """ 70 | engine = engine_from_config( 71 | config.get_section(config.config_ini_section), 72 | prefix='sqlalchemy.', 73 | poolclass=pool.NullPool) 74 | 75 | connection = engine.connect() 76 | context.configure( 77 | connection=connection, 78 | target_metadata=target_metadata 79 | ) 80 | 81 | try: 82 | with context.begin_transaction(): 83 | context.run_migrations() 84 | finally: 85 | connection.close() 86 | 87 | if context.is_offline_mode(): 88 | run_migrations_offline() 89 | else: 90 | run_migrations_online() 91 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /migrations/versions/ee69294e63e_init_state.py: -------------------------------------------------------------------------------- 1 | """init state 2 | 3 | Revision ID: ee69294e63e 4 | Revises: None 5 | Create Date: 2014-07-12 03:54:08.227618 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = 'ee69294e63e' 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('social_auth_code', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('email', sa.String(length=200), nullable=True), 22 | sa.Column('code', sa.String(length=32), nullable=True), 23 | sa.PrimaryKeyConstraint('id'), 24 | sa.UniqueConstraint('code', 'email') 25 | ) 26 | op.create_index(op.f('ix_social_auth_code_code'), 'social_auth_code', ['code'], unique=False) 27 | op.create_table('social_auth_nonce', 28 | sa.Column('id', sa.Integer(), nullable=False), 29 | sa.Column('server_url', sa.String(length=255), nullable=True), 30 | sa.Column('timestamp', sa.Integer(), nullable=True), 31 | sa.Column('salt', sa.String(length=40), nullable=True), 32 | sa.PrimaryKeyConstraint('id'), 33 | sa.UniqueConstraint('server_url', 'timestamp', 'salt') 34 | ) 35 | op.create_table('social_auth_association', 36 | sa.Column('id', sa.Integer(), nullable=False), 37 | sa.Column('server_url', sa.String(length=255), nullable=True), 38 | sa.Column('handle', sa.String(length=255), nullable=True), 39 | sa.Column('secret', sa.String(length=255), nullable=True), 40 | sa.Column('issued', sa.Integer(), nullable=True), 41 | sa.Column('lifetime', sa.Integer(), nullable=True), 42 | sa.Column('assoc_type', sa.String(length=64), nullable=True), 43 | sa.PrimaryKeyConstraint('id'), 44 | sa.UniqueConstraint('server_url', 'handle') 45 | ) 46 | op.create_table('users', 47 | sa.Column('id', sa.Integer(), nullable=False), 48 | sa.Column('username', sa.String(length=200), nullable=True), 49 | sa.Column('password', sa.String(length=200), nullable=True), 50 | sa.Column('name', sa.String(length=100), nullable=True), 51 | sa.Column('email', sa.String(length=200), nullable=True), 52 | sa.Column('active', sa.Boolean(), nullable=True), 53 | sa.Column('ui_lang', sa.String(length=2), nullable=True), 54 | sa.Column('url', sa.String(length=200), nullable=True), 55 | sa.PrimaryKeyConstraint('id'), 56 | sa.UniqueConstraint('username') 57 | ) 58 | op.create_table('social_auth_usersocialauth', 59 | sa.Column('id', sa.Integer(), nullable=False), 60 | sa.Column('provider', sa.String(length=32), nullable=True), 61 | sa.Column('uid', sa.String(length=255), nullable=True), 62 | sa.Column('extra_data', sa.PickleType(), nullable=True), 63 | sa.Column('user_id', sa.Integer(), nullable=False), 64 | sa.ForeignKeyConstraint(['user_id'], [u'users.id'], ), 65 | sa.PrimaryKeyConstraint('id'), 66 | sa.UniqueConstraint('provider', 'uid') 67 | ) 68 | op.create_index(op.f('ix_social_auth_usersocialauth_user_id'), 'social_auth_usersocialauth', ['user_id'], unique=False) 69 | ### end Alembic commands ### 70 | 71 | 72 | def downgrade(): 73 | ### commands auto generated by Alembic - please adjust! ### 74 | op.drop_index(op.f('ix_social_auth_usersocialauth_user_id'), table_name='social_auth_usersocialauth') 75 | op.drop_table('social_auth_usersocialauth') 76 | op.drop_table('users') 77 | op.drop_table('social_auth_association') 78 | op.drop_table('social_auth_nonce') 79 | op.drop_index(op.f('ix_social_auth_code_code'), table_name='social_auth_code') 80 | op.drop_table('social_auth_code') 81 | ### end Alembic commands ### 82 | -------------------------------------------------------------------------------- /packages.txt: -------------------------------------------------------------------------------- 1 | build-essential 2 | python 3 | python-software-properties 4 | python-pip 5 | python-setuptools 6 | python-dev 7 | python-virtualenv 8 | python-pip 9 | curl 10 | git 11 | git-core 12 | nodejs 13 | openssl 14 | libssl-dev 15 | redis 16 | memcached -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- 1 | from .app import create_app, create_celery 2 | -------------------------------------------------------------------------------- /project/api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .views import * 4 | -------------------------------------------------------------------------------- /project/api/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import (Blueprint, render_template, current_app) 4 | 5 | from ..extensions import manager 6 | # from ..models import MyModel 7 | 8 | 9 | def initialize_api(app): 10 | with app.app_context(): 11 | # List all Flask-Restless APIs here 12 | # model_api = manager.create_api(MyModel, methods=['GET'], 13 | # app=app, 14 | # url_prefix='/api') 15 | pass 16 | 17 | 18 | api = Blueprint('api', __name__) 19 | -------------------------------------------------------------------------------- /project/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint:disable-msg=W0612 3 | 4 | import os 5 | from flask import Flask, request, render_template, g 6 | from flask_login import current_user 7 | from celery import Celery 8 | 9 | from .extensions import ( 10 | db, 11 | mail, 12 | pages, 13 | manager, 14 | login_manager, 15 | babel, 16 | migrate, 17 | csrf, 18 | cache, 19 | celery, 20 | ) 21 | 22 | # blueprints 23 | from .frontend import frontend 24 | from .auth import auth 25 | from .api import api, initialize_api 26 | 27 | from social.apps.flask_app.routes import social_auth 28 | from social.apps.flask_app.default.models import init_social 29 | from social.apps.flask_app.template_filters import backends 30 | 31 | __all__ = ("create_app", "create_celery") 32 | 33 | BLUEPRINTS = (frontend, social_auth, auth, api) 34 | 35 | 36 | def create_app(config=None, app_name="project", blueprints=None): 37 | app = Flask( 38 | app_name, 39 | static_folder=os.path.join(os.path.dirname(__file__), "..", "static"), 40 | template_folder="templates", 41 | ) 42 | 43 | app.config.from_object("project.config") 44 | app.config.from_pyfile("../local.cfg", silent=True) 45 | if config: 46 | app.config.from_pyfile(config) 47 | 48 | if blueprints is None: 49 | blueprints = BLUEPRINTS 50 | 51 | blueprints_fabrics(app, blueprints) 52 | extensions_fabrics(app) 53 | api_fabrics(api) # this must be called after extensions_fabrics 54 | configure_logging(app) 55 | 56 | error_pages(app) 57 | gvars(app) 58 | 59 | return app 60 | 61 | 62 | def create_celery(app): 63 | celery = Celery(app.import_name, broker=app.config["CELERY_BROKER_URL"]) 64 | celery.conf.update(app.config) 65 | TaskBase = celery.Task 66 | 67 | class ContextTask(TaskBase): 68 | abstract = True 69 | 70 | def __call__(self, *args, **kwargs): 71 | with app.app_context(): 72 | return TaskBase.__call__(self, *args, **kwargs) 73 | 74 | celery.Task = ContextTask 75 | return celery 76 | 77 | 78 | def blueprints_fabrics(app, blueprints): 79 | """Configure blueprints in views.""" 80 | 81 | for blueprint in blueprints: 82 | app.register_blueprint(blueprint) 83 | 84 | 85 | def extensions_fabrics(app): 86 | db.init_app(app) 87 | mail.init_app(app) 88 | babel.init_app(app) 89 | pages.init_app(app) 90 | init_social(app, db) 91 | login_manager.init_app(app) 92 | manager.init_app(app, flask_sqlalchemy_db=db) 93 | migrate.init_app(app, db) 94 | csrf.init_app(app) 95 | cache.init_app(app) 96 | celery.config_from_object(app.config) 97 | 98 | 99 | def api_fabrics(app): 100 | # initialize_api(app) 101 | pass 102 | 103 | 104 | def error_pages(app): 105 | # HTTP error pages definitions 106 | 107 | @app.errorhandler(403) 108 | def forbidden_page(error): 109 | return render_template("misc/403.html"), 403 110 | 111 | @app.errorhandler(404) 112 | def page_not_found(error): 113 | return render_template("misc/404.html"), 404 114 | 115 | @app.errorhandler(405) 116 | def method_not_allowed(error): 117 | return render_template("misc/405.html"), 404 118 | 119 | @app.errorhandler(500) 120 | def server_error_page(error): 121 | return render_template("misc/500.html"), 500 122 | 123 | 124 | def gvars(app): 125 | @app.before_request 126 | def gdebug(): 127 | if app.debug: 128 | g.debug = True 129 | else: 130 | g.debug = False 131 | 132 | app.context_processor(backends) 133 | 134 | from .models import User 135 | 136 | @login_manager.user_loader 137 | def load_user(userid): 138 | try: 139 | return User.query.get(int(userid)) 140 | except (TypeError, ValueError): 141 | pass 142 | 143 | @app.before_request 144 | def guser(): 145 | g.user = current_user 146 | 147 | @app.context_processor 148 | def inject_user(): 149 | try: 150 | return {"user": g.user} 151 | except AttributeError: 152 | return {"user": None} 153 | 154 | @babel.localeselector 155 | def get_locale(): 156 | if g.user: 157 | if hasattr(g.user, "ui_lang"): 158 | return g.user.ui_lang 159 | 160 | accept_languages = app.config.get("ACCEPT_LANGUAGES") 161 | return request.accept_languages.best_match(accept_languages) 162 | 163 | 164 | def configure_logging(app): 165 | """Configure file(info) and email(error) logging.""" 166 | 167 | if app.debug or app.testing: 168 | # Skip debug and test mode. Just check standard output. 169 | return 170 | 171 | import logging 172 | 173 | # Set info level on logger, which might be overwritten by handers. 174 | # Suppress DEBUG messages. 175 | app.logger.setLevel(logging.INFO) 176 | 177 | info_log = os.path.join(app.config["LOG_FOLDER"], "info.log") 178 | info_file_handler = logging.handlers.RotatingFileHandler( 179 | info_log, maxBytes=100000, backupCount=10 180 | ) 181 | info_file_handler.setLevel(logging.INFO) 182 | info_file_handler.setFormatter( 183 | logging.Formatter( 184 | "%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]" 185 | ) 186 | ) 187 | app.logger.addHandler(info_file_handler) 188 | 189 | # Testing 190 | # app.logger.info("testing info.") 191 | # app.logger.warn("testing warn.") 192 | # app.logger.error("testing error.") 193 | 194 | mail_handler = logging.handlers.SMTPHandler( 195 | app.config["MAIL_SERVER"], 196 | app.config["MAIL_USERNAME"], 197 | app.config["ADMINS"], 198 | "O_ops... %s failed!" % app.config["PROJECT"], 199 | (app.config["MAIL_USERNAME"], app.config["MAIL_PASSWORD"]), 200 | ) 201 | mail_handler.setLevel(logging.ERROR) 202 | mail_handler.setFormatter( 203 | logging.Formatter( 204 | "%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]" 205 | ) 206 | ) 207 | app.logger.addHandler(mail_handler) 208 | -------------------------------------------------------------------------------- /project/auth/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .views import auth 4 | -------------------------------------------------------------------------------- /project/auth/forms.py: -------------------------------------------------------------------------------- 1 | import re 2 | from flask_wtf import Form 3 | from flask_babel import lazy_gettext 4 | 5 | from wtforms.fields import TextField, SelectField 6 | from wtforms.fields.html5 import URLField 7 | from wtforms.validators import url, length, regexp, optional 8 | 9 | 10 | class SettingsForm(Form): 11 | """docstring for SettingsForm""" 12 | 13 | ui_lang = SelectField( 14 | label=lazy_gettext("Primary site language"), 15 | description=lazy_gettext("Site will try to show UI labels using this " + 16 | "language. User data will be shown in original languages."), 17 | ) 18 | url = URLField( 19 | label=lazy_gettext("Personal site URL"), 20 | description=lazy_gettext("If you have personal site and want to share " + 21 | "with other people, please fill this field"), 22 | validators=[optional(), url(message=lazy_gettext("Invalid URL."))]) 23 | username = TextField( 24 | label=lazy_gettext("Public profile address"), 25 | description=lazy_gettext("Will be part of your public profile URL. Can " + 26 | "be from 2 up to 40 characters length, can start start from [a-z] " + 27 | "and contains only latin [0-9a-zA-Z] chars."), 28 | validators=[ 29 | length(2, 40, message=lazy_gettext("Field must be between 2 and 40" + 30 | " characters long.")), 31 | regexp(r"[a-zA-Z]{1}[0-9a-zA-Z]*", 32 | re.IGNORECASE, 33 | message=lazy_gettext("Username should start from [a-z] and " + 34 | "contains only latin [0-9a-zA-Z] chars")) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /project/auth/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from hashlib import md5 3 | from ..extensions import db 4 | from flask_login import UserMixin 5 | 6 | 7 | class User(db.Model, UserMixin): 8 | __tablename__ = 'users' 9 | # tables name convention is contradictory topic, usually I'm against plural forms 10 | # when name tables, but user is reserved word in post databases, 11 | # so this is only case when it is allowed to use plural in my teams. 12 | id = db.Column(db.Integer, primary_key=True) 13 | username = db.Column(db.String(200), unique=True) 14 | password = db.Column(db.String(200), default='') 15 | name = db.Column(db.String(100)) 16 | email = db.Column(db.String(200)) 17 | active = db.Column(db.Boolean, default=True) 18 | ui_lang = db.Column(db.String(2), default='en') 19 | url = db.Column(db.String(200)) 20 | 21 | def gavatar(self, size=14): 22 | if self.email: 23 | return 'http://www.gravatar.com/avatar/{hashd}?d=mm&s={size}'.format( 24 | hashd=md5(self.email).hexdigest(), size=str(size)) 25 | else: 26 | return None 27 | 28 | def is_active(self): 29 | return self.active 30 | 31 | def get_access_token(self, provider, param_name='access_token'): 32 | """ Method can be used for social network API access. 33 | 34 | >>> import requests 35 | >>> user = User.query.one() 36 | >>> r = requests.get('https://api.github.com/user', 37 | ... params={'access_token': user.get_access_token('github')}) 38 | >>> r.json()['html_url'] 39 | u'https://github.com/xen' 40 | 41 | """ 42 | # provider should be from social providers list, for example 'github' 43 | s = self.social_auth.filter_by(provider=provider).one() 44 | return s.extra_data.get(param_name, None) 45 | -------------------------------------------------------------------------------- /project/auth/templates/auth/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = _('Login') %} 3 | 4 | {% block content %} 5 |

{{ _("Social login") }}

6 |

{{ _("Login using your favorite social service ")}}

7 | {% from 'auth/macros.html' import login_button with context %} 8 |
9 | {# {{ login_button(provider='google-oauth2') }} 10 | {{ login_button(provider='twitter') }} 11 | {{ login_button(provider='facebook') }} 12 | {{ login_button(provider='yahoo-oauth') }} #} 13 | {{ login_button(provider='github') }} 14 |
15 | 16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /project/auth/templates/auth/macros.html: -------------------------------------------------------------------------------- 1 | {% macro login_button(provider='') -%} 2 | {% if provider == 'google-oauth2' %} 3 | Google 4 | {% elif provider == 'github' %} 5 | Github 6 | {% else %} 7 | {{ provider }} 8 | {% endif %} 9 | {% endmacro -%} 10 | 11 | 12 | {% macro logout_button(provider='', id=None) -%} 13 |
14 | 22 |
23 | 24 | {% endmacro -%} 25 | 26 | -------------------------------------------------------------------------------- /project/auth/templates/auth/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = _('Your profile') %} 3 | 4 | {% block content %} 5 | {% from 'auth/macros.html' import subnav with context%} 6 | {{ subnav('profile') }} 7 | 8 |

{{ _("Social profiles") }}

9 | 10 |

{{ _("You are logged in as %(user)s!", user = user.username) }}!

11 | 12 |

{{ _("Associate to this profile: ") }}

13 | 14 | {% from 'auth/macros.html' import login_button with context%} 15 |

{{ _("For your convenience, add new social services to your account")}}

16 |
17 | {% for name in backends.not_associated %} 18 | {{ login_button(provider=name) }} 19 | {% else %} 20 |

{{ _("Wee! You connected all available social services!") }}

21 | {% endfor %} 22 |
23 | 24 |

{{ _("Associated: ") }}

25 |

{{ _("If you want to disconnect any service from the list then click item from the list") }}

26 | {% from 'auth/macros.html' import logout_button with context%} 27 |
28 | {% for assoc in backends.associated %} 29 | {{ logout_button(provider=assoc.provider, id=assoc.id) }} 30 | {% endfor %} 31 |
32 | 33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /project/auth/templates/auth/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = _('Settings') %} 3 | 4 | {% block content %} 5 |

{{ _("Personal settings") }}

6 | 7 | {% from 'macros.html' import render_bootstrap_field with context%} 8 | 9 |
10 | 11 | {{ form.csrf_token }} 12 | {{ render_bootstrap_field(form.ui_lang) }} 13 | {{ render_bootstrap_field(form.url) }} 14 | {{ render_bootstrap_field(form.username) }} 15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /project/auth/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Blueprint, render_template, redirect, request, current_app, g, flash, url_for 4 | from flask_login import login_required, logout_user 5 | from flask_babel import gettext as _ 6 | from .models import User 7 | from ..extensions import db 8 | from .forms import SettingsForm 9 | 10 | auth = Blueprint('auth', __name__, url_prefix='/auth/', template_folder="templates") 11 | 12 | 13 | @auth.route('login') 14 | def login(): 15 | next_url = request.args.get('next') or request.referrer or None 16 | return render_template('auth/index.html', next=next_url) 17 | 18 | 19 | @auth.route('loggedin') 20 | def loggedin(): 21 | return redirect(request.args.get('next') or url_for('frontend.index')) 22 | 23 | 24 | @auth.route('profile') 25 | @login_required 26 | def profile(): 27 | return render_template('auth/profile.html') 28 | 29 | 30 | @auth.route('set_lang') 31 | @login_required 32 | def set_lang(): 33 | if request.args.get('lang') in current_app.config['LANGUAGES']: 34 | user = User.query.get_or_404(g.user.id) 35 | user.ui_lang = request.args.get('lang') 36 | db.session.add(user) 37 | db.session.commit() 38 | return redirect('/') 39 | 40 | 41 | @auth.route('settings', methods=['GET', 'POST']) 42 | @login_required 43 | def settings(): 44 | form = SettingsForm(request.form, g.user) 45 | form.ui_lang.choices = current_app.config['LANGUAGES'].items() 46 | 47 | if form.validate_on_submit(): 48 | form.populate_obj(g.user) 49 | db.session.add(g.user) 50 | db.session.commit() 51 | flash(_("Settings saved")) 52 | 53 | return render_template('auth/settings.html', languages=current_app.config['LANGUAGES'], form=form) 54 | 55 | 56 | @auth.route('logout') 57 | def logout(): 58 | logout_user() 59 | return redirect('/') 60 | -------------------------------------------------------------------------------- /project/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | DEBUG = True 4 | SECRET_KEY = 'change me please' 5 | 6 | from os.path import dirname, abspath, join 7 | SQLALCHEMY_DATABASE_URI = 'sqlite:////%s/data.sqlite' % dirname(abspath(__file__)) 8 | SQLALCHEMY_ECHO = True 9 | 10 | # flatpages 11 | FLATPAGES_EXTENSION = '.md' 12 | FLATPAGES_ROOT = join(dirname(__file__), 'docs') 13 | del dirname, abspath, join 14 | 15 | # default babel values 16 | BABEL_DEFAULT_LOCALE = 'en' 17 | BABEL_DEFAULT_TIMEZONE = 'UTC' 18 | ACCEPT_LANGUAGES = ['en', 'ru', ] 19 | 20 | # available languages 21 | LANGUAGES = { 22 | 'en': u'English', 23 | 'ru': u'Русский' 24 | } 25 | 26 | # make sure that you have started debug mail server using command 27 | # $ make mail 28 | MAIL_SERVER = 'localhost' 29 | MAIL_PORT = 20025 30 | MAIL_USE_SSL = False 31 | MAIL_USERNAME = 'your@email.address' 32 | #MAIL_PASSWORD = 'topsecret' 33 | 34 | # Celery 35 | BROKER_TRANSPORT = 'redis' 36 | CELERY_BROKER_URL = 'redis://localhost:6379/0' 37 | CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' 38 | CELERY_TASK_SERIALIZER = 'json' 39 | CELERY_DISABLE_RATE_LIMITS = True 40 | CELERY_ACCEPT_CONTENT = ['json',] 41 | 42 | # cache 43 | CACHE_TYPE = 'memcached' 44 | CACHE_MEMCACHED_SERVERS = ['127.0.0.1:11211', ] 45 | # CACHE_MEMCACHED_USERNAME = 46 | # CACHE_MEMCACHED_PASSWORD = 47 | 48 | # Auth 49 | SESSION_COOKIE_NAME = 'session' 50 | 51 | SOCIAL_AUTH_LOGIN_URL = '/' 52 | SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/auth/loggedin' 53 | SOCIAL_AUTH_USER_MODEL = 'project.models.User' 54 | SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 55 | 'social.backends.github.GithubOAuth2', 56 | ) 57 | 58 | # Documnetation http://psa.matiasaguirre.net/docs/backends/index.html 59 | # https://github.com/settings/applications/ 60 | SOCIAL_AUTH_GITHUB_KEY = '8b643b3f60a3dd4470e7' 61 | SOCIAL_AUTH_GITHUB_SECRET = '3e07a41b69244ec209db690e1babab3ca1d33291' 62 | SOCIAL_AUTH_GITHUB_SCOPE = ['user:email'] 63 | -------------------------------------------------------------------------------- /project/docs/index.md: -------------------------------------------------------------------------------- 1 | title: Page title 2 | 3 | # About the project 4 | 5 | Welcome to our new Flask site. 6 | -------------------------------------------------------------------------------- /project/extensions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_mail import Mail 4 | mail = Mail() 5 | 6 | from flask_sqlalchemy import SQLAlchemy 7 | db = SQLAlchemy() 8 | 9 | from flask_flatpages import FlatPages 10 | pages = FlatPages() 11 | 12 | import flask_restless 13 | manager = flask_restless.APIManager() 14 | 15 | from flask_login import LoginManager 16 | login_manager = LoginManager() 17 | login_manager.login_view = 'auth.login' 18 | 19 | from flask_babel import Babel 20 | babel = Babel() 21 | 22 | from flask_migrate import Migrate 23 | migrate = Migrate() 24 | 25 | from flask_wtf.csrf import CsrfProtect 26 | csrf = CsrfProtect() 27 | 28 | from flask_cache import Cache 29 | cache = Cache() 30 | 31 | from celery import Celery 32 | celery = Celery() 33 | -------------------------------------------------------------------------------- /project/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | from .views import frontend 2 | -------------------------------------------------------------------------------- /project/frontend/templates/frontend/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = _('Welcome to our new site') %} 3 | 4 | {% block content %} 5 |

{{ _("Welcome to our new site") }}

6 |

This is special splash page.

7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /project/frontend/templates/frontend/user_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = user_profile.name %} 3 | 4 | {% block include_css %} 5 | {# #} 6 | {% endblock %} 7 | 8 | {% block include_js %} 9 | {# #} 10 | {% endblock %} 11 | 12 | {% block content %} 13 |

{{ user_profile.name }}

14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /project/frontend/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import (Blueprint, render_template, g, request, url_for, 4 | current_app, send_from_directory, json, redirect, make_response, abort) 5 | 6 | from flask_login import login_required 7 | 8 | from ..extensions import pages, csrf 9 | from ..tasks import do_some_stuff 10 | 11 | frontend = Blueprint('frontend', __name__, template_folder="templates") 12 | 13 | 14 | @frontend.route('/') 15 | def index(): 16 | # Run background task inside of view 17 | do_some_stuff.delay() 18 | return render_template('frontend/index.html') 19 | 20 | 21 | @frontend.route('/docs/', defaults={'path': 'index'}) 22 | @frontend.route('/docs//', endpoint='page') 23 | def page(path): 24 | # Documentation views 25 | _page = pages.get_or_404(path) 26 | return render_template('page.html', page=_page) 27 | 28 | 29 | @frontend.route('/robots.txt') 30 | def static_from_root(): 31 | # Static items 32 | return send_from_directory(current_app.static_folder, request.path[1:]) 33 | 34 | 35 | @frontend.route('/favicon.ico') 36 | def favicon(): 37 | return redirect('/static/favicon.png') 38 | 39 | -------------------------------------------------------------------------------- /project/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .auth.models import User 4 | -------------------------------------------------------------------------------- /project/tasks.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from flask import current_app 3 | from celery.signals import task_postrun 4 | 5 | from .extensions import celery, db 6 | 7 | @celery.task(ignore_result=True) 8 | def do_some_stuff(): 9 | current_app.logger.info("I have the application context") 10 | #you can now use the db object from extensions 11 | 12 | @task_postrun.connect 13 | def close_session(*args, **kwargs): 14 | from extensions import db 15 | # Flask SQLAlchemy will automatically create new sessions for you from 16 | # a scoped session factory, given that we are maintaining the same app 17 | # context, this ensures tasks have a fresh session (e.g. session errors 18 | # won't propagate across tasks) 19 | db.session.remove() 20 | 21 | -------------------------------------------------------------------------------- /project/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{{ page_title|default(_('New site')) }}{% endblock %} 12 | 13 | 14 | {% block include_css %} 15 | {% endblock %} 16 | 17 | 18 | 19 | 20 | {% block include_js %} 21 | {% endblock %} 22 | 23 | 24 | 28 | {% block extramedia %}{% endblock %} 29 | {% include "counter.html" %} 30 | 31 | 32 | 33 |
34 | 35 | {% include "nav.html" %} 36 | 37 | {% block fullcontent %} 38 | 39 |
40 | {% with messages = get_flashed_messages() %} 41 | {% if messages %} 42 | {% for message in messages %} 43 |
44 | 45 | {{ message }} 46 |
47 | {% endfor %} 48 | {% endif %} 49 | {% endwith %} 50 | {# Body #} 51 | {% block content %}{% endblock %} 52 |
53 | {% endblock %} 54 | 55 |
56 | 57 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /project/templates/counter.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/templates/macros.html: -------------------------------------------------------------------------------- 1 | {% macro render_bootstrap_field(field) %} 2 | 3 |
4 | 5 | 6 |
7 | {{ field(class='form-control')|safe }} 8 | {% if field.errors %} 9 | {% for error in field.errors %} 10 | {{ error }}
11 | {% endfor %} 12 | {% endif %} 13 | {% if field.description %} 14 | {{field.description}} 15 | {% endif %} 16 |
17 |
18 | {% endmacro %} 19 | -------------------------------------------------------------------------------- /project/templates/misc/403.html: -------------------------------------------------------------------------------- 1 | {% extends "misc/base.html" %} 2 | {% set page_title = _('No access') %} 3 | {% block content %} 4 |

{{_("Error 403")}}

5 |

{{ _("You don't have access to page %(url)s.", url=request.url) }}

6 |

{{ _("← Back to front page") }}

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /project/templates/misc/404.html: -------------------------------------------------------------------------------- 1 | {% extends "misc/base.html" %} 2 | {% set page_title = _('Page not found') %} 3 | {% block content %} 4 |

{{ _("Error 404") }}

5 |

{{ _("The page %(url)s you are looking for is not found.", url=request.url) }}

6 |

{{ _("← Back to front page") }}

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /project/templates/misc/405.html: -------------------------------------------------------------------------------- 1 | {% extends "misc/base.html" %} 2 | {% set page_title = _('Method not allowed') %} 3 | {% block content %} 4 |

{{ _("Error 405") }}

5 |

{{_("You are trying to access page by methods that is not allowed.")}}

6 |

{{ _("← Back to front page") }}

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /project/templates/misc/500.html: -------------------------------------------------------------------------------- 1 | {% extends "misc/base.html" %} 2 | {% set page_title = _('Server error') %} 3 | {% block content %} 4 |

{{ _("Error 500") }}

5 |

{{ _("Error generating page %(url)s. Please contact us.", url=request.url) }}

6 |

{{ _("← Back to front page") }}

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /project/templates/misc/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% endblock %} 5 | 6 | {% block footer %} 7 | {% endblock %} 8 | 9 | {% block js_btm %} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /project/templates/nav.html: -------------------------------------------------------------------------------- 1 | 2 | 29 | -------------------------------------------------------------------------------- /project/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% set page_title = page.title %} 3 | 4 | {% block content %} 5 | 6 | {{ page.html|safe }} 7 | 8 | {% endblock %} -------------------------------------------------------------------------------- /project/translations/en/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # English translations for PROJECT. 2 | # Copyright (C) 2014 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PROJECT VERSION\n" 10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 11 | "POT-Creation-Date: 2014-07-12 04:22+0300\n" 12 | "PO-Revision-Date: 2014-07-12 04:01+0300\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: en \n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 1.3\n" 20 | 21 | #: project/auth/forms.py:14 22 | msgid "Primary site language" 23 | msgstr "" 24 | 25 | #: project/auth/forms.py:15 26 | msgid "" 27 | "Site will try to show UI labels using this language. User data will be " 28 | "shown in original languages." 29 | msgstr "" 30 | 31 | #: project/auth/forms.py:19 32 | msgid "Personal site URL" 33 | msgstr "" 34 | 35 | #: project/auth/forms.py:20 36 | msgid "" 37 | "If you have personal site and want to share with other people, please " 38 | "fill this field" 39 | msgstr "" 40 | 41 | #: project/auth/forms.py:22 42 | msgid "Invalid URL." 43 | msgstr "" 44 | 45 | #: project/auth/forms.py:24 46 | msgid "Public profile address" 47 | msgstr "" 48 | 49 | #: project/auth/forms.py:25 50 | msgid "" 51 | "Will be part of your public profile URL. Can be from 2 up to 40 " 52 | "characters length, can start start from [a-z] and contains only latin [0" 53 | "-9a-zA-Z] chars." 54 | msgstr "" 55 | 56 | #: project/auth/forms.py:29 57 | msgid "Field must be between 2 and 40 characters long." 58 | msgstr "" 59 | 60 | #: project/auth/forms.py:33 61 | msgid "Username should start from [a-z] and contains only latin [0-9a-zA-Z] chars" 62 | msgstr "" 63 | 64 | #: project/auth/views.py:51 65 | msgid "Settings saved" 66 | msgstr "" 67 | 68 | #: project/auth/templates/auth/index.html:2 project/templates/nav.html:23 69 | msgid "Login" 70 | msgstr "" 71 | 72 | #: project/auth/templates/auth/index.html:5 73 | msgid "Social login" 74 | msgstr "" 75 | 76 | #: project/auth/templates/auth/index.html:6 77 | msgid "Login using your favorite social service " 78 | msgstr "" 79 | 80 | #: project/auth/templates/auth/macros.html:14 81 | msgid "Do you want to disconnect this service from your profile?" 82 | msgstr "" 83 | 84 | #: project/auth/templates/auth/macros.html:14 85 | msgid "Are you sure?" 86 | msgstr "" 87 | 88 | #: project/auth/templates/auth/profile.html:2 89 | msgid "Your profile" 90 | msgstr "" 91 | 92 | #: project/auth/templates/auth/profile.html:8 93 | msgid "Social profiles" 94 | msgstr "" 95 | 96 | #: project/auth/templates/auth/profile.html:10 97 | #, python-format 98 | msgid "You are logged in as %(user)s!" 99 | msgstr "" 100 | 101 | #: project/auth/templates/auth/profile.html:12 102 | msgid "Associate to this profile: " 103 | msgstr "" 104 | 105 | #: project/auth/templates/auth/profile.html:15 106 | msgid "For your convenience, add new social services to your account" 107 | msgstr "" 108 | 109 | #: project/auth/templates/auth/profile.html:20 110 | msgid "Wee! You connected all available social services!" 111 | msgstr "" 112 | 113 | #: project/auth/templates/auth/profile.html:24 114 | msgid "Associated: " 115 | msgstr "" 116 | 117 | #: project/auth/templates/auth/profile.html:25 118 | msgid "" 119 | "If you want to disconnect any service from the list then click item from " 120 | "the list" 121 | msgstr "" 122 | 123 | #: project/auth/templates/auth/settings.html:2 project/templates/nav.html:20 124 | msgid "Settings" 125 | msgstr "" 126 | 127 | #: project/auth/templates/auth/settings.html:5 128 | msgid "Personal settings" 129 | msgstr "" 130 | 131 | #: project/auth/templates/auth/settings.html:18 132 | msgid "Update settings" 133 | msgstr "" 134 | 135 | #: project/frontend/templates/frontend/index.html:2 136 | #: project/frontend/templates/frontend/index.html:5 137 | msgid "Welcome to our new site" 138 | msgstr "" 139 | 140 | #: project/templates/base.html:11 project/templates/base.html:59 141 | msgid "New site" 142 | msgstr "" 143 | 144 | #: project/templates/nav.html:10 145 | msgid "Site" 146 | msgstr "" 147 | 148 | #: project/templates/nav.html:14 149 | msgid "Splash page" 150 | msgstr "" 151 | 152 | #: project/templates/nav.html:17 153 | msgid "About site " 154 | msgstr "" 155 | 156 | #: project/templates/nav.html:21 157 | msgid "Do you want to logout?" 158 | msgstr "" 159 | 160 | #: project/templates/nav.html:21 161 | msgid "Logout?" 162 | msgstr "" 163 | 164 | #: project/templates/nav.html:21 165 | msgid "Logout" 166 | msgstr "" 167 | 168 | #: project/templates/misc/404.html:2 169 | msgid "Page not found" 170 | msgstr "" 171 | 172 | #: project/templates/misc/404.html:4 173 | msgid "Error 404" 174 | msgstr "" 175 | 176 | #: project/templates/misc/404.html:5 177 | #, python-format 178 | msgid "The page %(url)s you are looking for is not found." 179 | msgstr "" 180 | 181 | #: project/templates/misc/404.html:6 project/templates/misc/405.html:6 182 | #: project/templates/misc/500.html:6 183 | msgid "← Back to front page" 184 | msgstr "" 185 | 186 | #: project/templates/misc/405.html:2 187 | msgid "Method not allowed" 188 | msgstr "" 189 | 190 | #: project/templates/misc/405.html:4 191 | msgid "Error 405" 192 | msgstr "" 193 | 194 | #: project/templates/misc/405.html:5 195 | msgid "You are trying to access page by methods that is not allowed." 196 | msgstr "" 197 | 198 | #: project/templates/misc/500.html:2 199 | msgid "Server error" 200 | msgstr "" 201 | 202 | #: project/templates/misc/500.html:4 203 | msgid "Error 500" 204 | msgstr "" 205 | 206 | #: project/templates/misc/500.html:5 207 | #, python-format 208 | msgid "" 209 | "Error generating page %(url)s. Please contact us." 211 | msgstr "" 212 | 213 | #~ msgid "" 214 | #~ msgstr "" 215 | 216 | -------------------------------------------------------------------------------- /project/translations/messages.pot: -------------------------------------------------------------------------------- 1 | # Translations template for PROJECT. 2 | # Copyright (C) 2014 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PROJECT VERSION\n" 10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 11 | "POT-Creation-Date: 2014-07-12 04:22+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 1.3\n" 19 | 20 | #: project/auth/forms.py:14 21 | msgid "Primary site language" 22 | msgstr "" 23 | 24 | #: project/auth/forms.py:15 25 | msgid "" 26 | "Site will try to show UI labels using this language. User data will be " 27 | "shown in original languages." 28 | msgstr "" 29 | 30 | #: project/auth/forms.py:19 31 | msgid "Personal site URL" 32 | msgstr "" 33 | 34 | #: project/auth/forms.py:20 35 | msgid "" 36 | "If you have personal site and want to share with other people, please " 37 | "fill this field" 38 | msgstr "" 39 | 40 | #: project/auth/forms.py:22 41 | msgid "Invalid URL." 42 | msgstr "" 43 | 44 | #: project/auth/forms.py:24 45 | msgid "Public profile address" 46 | msgstr "" 47 | 48 | #: project/auth/forms.py:25 49 | msgid "" 50 | "Will be part of your public profile URL. Can be from 2 up to 40 " 51 | "characters length, can start start from [a-z] and contains only latin [0" 52 | "-9a-zA-Z] chars." 53 | msgstr "" 54 | 55 | #: project/auth/forms.py:29 56 | msgid "Field must be between 2 and 40 characters long." 57 | msgstr "" 58 | 59 | #: project/auth/forms.py:33 60 | msgid "Username should start from [a-z] and contains only latin [0-9a-zA-Z] chars" 61 | msgstr "" 62 | 63 | #: project/auth/views.py:51 64 | msgid "Settings saved" 65 | msgstr "" 66 | 67 | #: project/auth/templates/auth/index.html:2 project/templates/nav.html:23 68 | msgid "Login" 69 | msgstr "" 70 | 71 | #: project/auth/templates/auth/index.html:5 72 | msgid "Social login" 73 | msgstr "" 74 | 75 | #: project/auth/templates/auth/index.html:6 76 | msgid "Login using your favorite social service " 77 | msgstr "" 78 | 79 | #: project/auth/templates/auth/macros.html:14 80 | msgid "Do you want to disconnect this service from your profile?" 81 | msgstr "" 82 | 83 | #: project/auth/templates/auth/macros.html:14 84 | msgid "Are you sure?" 85 | msgstr "" 86 | 87 | #: project/auth/templates/auth/profile.html:2 88 | msgid "Your profile" 89 | msgstr "" 90 | 91 | #: project/auth/templates/auth/profile.html:8 92 | msgid "Social profiles" 93 | msgstr "" 94 | 95 | #: project/auth/templates/auth/profile.html:10 96 | #, python-format 97 | msgid "You are logged in as %(user)s!" 98 | msgstr "" 99 | 100 | #: project/auth/templates/auth/profile.html:12 101 | msgid "Associate to this profile: " 102 | msgstr "" 103 | 104 | #: project/auth/templates/auth/profile.html:15 105 | msgid "For your convenience, add new social services to your account" 106 | msgstr "" 107 | 108 | #: project/auth/templates/auth/profile.html:20 109 | msgid "Wee! You connected all available social services!" 110 | msgstr "" 111 | 112 | #: project/auth/templates/auth/profile.html:24 113 | msgid "Associated: " 114 | msgstr "" 115 | 116 | #: project/auth/templates/auth/profile.html:25 117 | msgid "" 118 | "If you want to disconnect any service from the list then click item from " 119 | "the list" 120 | msgstr "" 121 | 122 | #: project/auth/templates/auth/settings.html:2 project/templates/nav.html:20 123 | msgid "Settings" 124 | msgstr "" 125 | 126 | #: project/auth/templates/auth/settings.html:5 127 | msgid "Personal settings" 128 | msgstr "" 129 | 130 | #: project/auth/templates/auth/settings.html:18 131 | msgid "Update settings" 132 | msgstr "" 133 | 134 | #: project/frontend/templates/frontend/index.html:2 135 | #: project/frontend/templates/frontend/index.html:5 136 | msgid "Welcome to our new site" 137 | msgstr "" 138 | 139 | #: project/templates/base.html:11 project/templates/base.html:59 140 | msgid "New site" 141 | msgstr "" 142 | 143 | #: project/templates/nav.html:10 144 | msgid "Site" 145 | msgstr "" 146 | 147 | #: project/templates/nav.html:14 148 | msgid "Splash page" 149 | msgstr "" 150 | 151 | #: project/templates/nav.html:17 152 | msgid "About site " 153 | msgstr "" 154 | 155 | #: project/templates/nav.html:21 156 | msgid "Do you want to logout?" 157 | msgstr "" 158 | 159 | #: project/templates/nav.html:21 160 | msgid "Logout?" 161 | msgstr "" 162 | 163 | #: project/templates/nav.html:21 164 | msgid "Logout" 165 | msgstr "" 166 | 167 | #: project/templates/misc/404.html:2 168 | msgid "Page not found" 169 | msgstr "" 170 | 171 | #: project/templates/misc/404.html:4 172 | msgid "Error 404" 173 | msgstr "" 174 | 175 | #: project/templates/misc/404.html:5 176 | #, python-format 177 | msgid "The page %(url)s you are looking for is not found." 178 | msgstr "" 179 | 180 | #: project/templates/misc/404.html:6 project/templates/misc/405.html:6 181 | #: project/templates/misc/500.html:6 182 | msgid "← Back to front page" 183 | msgstr "" 184 | 185 | #: project/templates/misc/405.html:2 186 | msgid "Method not allowed" 187 | msgstr "" 188 | 189 | #: project/templates/misc/405.html:4 190 | msgid "Error 405" 191 | msgstr "" 192 | 193 | #: project/templates/misc/405.html:5 194 | msgid "You are trying to access page by methods that is not allowed." 195 | msgstr "" 196 | 197 | #: project/templates/misc/500.html:2 198 | msgid "Server error" 199 | msgstr "" 200 | 201 | #: project/templates/misc/500.html:4 202 | msgid "Error 500" 203 | msgstr "" 204 | 205 | #: project/templates/misc/500.html:5 206 | #, python-format 207 | msgid "" 208 | "Error generating page %(url)s. Please contact us." 210 | msgstr "" 211 | 212 | -------------------------------------------------------------------------------- /project/translations/ru/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # Russian translations for PROJECT. 2 | # Copyright (C) 2014 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2014-07-12 04:22+0300\n" 11 | "PO-Revision-Date: 2014-07-12 04:26+0200\n" 12 | "Last-Translator: Mikhail Kashkin \n" 13 | "Language-Team: ru \n" 14 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 15 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 1.3\n" 20 | "Language: ru\n" 21 | "X-Generator: Poedit 1.6.6\n" 22 | 23 | #: project/auth/forms.py:14 24 | msgid "Primary site language" 25 | msgstr "Язык сайта" 26 | 27 | #: project/auth/forms.py:15 28 | msgid "" 29 | "Site will try to show UI labels using this language. User data will be shown " 30 | "in original languages." 31 | msgstr "" 32 | "Служебные тексты сайта будут переведены, пользовательский контент не " 33 | "изменится" 34 | 35 | #: project/auth/forms.py:19 36 | msgid "Personal site URL" 37 | msgstr "Персональный сайт" 38 | 39 | #: project/auth/forms.py:20 40 | msgid "" 41 | "If you have personal site and want to share with other people, please fill " 42 | "this field" 43 | msgstr "Если у вас есть персональный сайт, то укажите ссылку на него." 44 | 45 | #: project/auth/forms.py:22 46 | msgid "Invalid URL." 47 | msgstr "Ошибка в адресе сайта" 48 | 49 | #: project/auth/forms.py:24 50 | msgid "Public profile address" 51 | msgstr "Адрес профайла" 52 | 53 | #: project/auth/forms.py:25 54 | msgid "" 55 | "Will be part of your public profile URL. Can be from 2 up to 40 characters " 56 | "length, can start start from [a-z] and contains only latin [0-9a-zA-Z] chars." 57 | msgstr "" 58 | "Будет частью вашего публичного профайла на сайте. Может быть от 2 до 40 " 59 | "символов, начинаться на [a-z] и содержать только латинские символы в " 60 | "диапазоне [0-9a-zA-Z]." 61 | 62 | #: project/auth/forms.py:29 63 | msgid "Field must be between 2 and 40 characters long." 64 | msgstr "Длинна должна быть от 2 до 40 символов" 65 | 66 | #: project/auth/forms.py:33 67 | msgid "" 68 | "Username should start from [a-z] and contains only latin [0-9a-zA-Z] chars" 69 | msgstr "" 70 | "Имя профайла может начинаться только с символов [a-z] и содержать только " 71 | "латинские символы из диапазона [0-9a-zA-Z]." 72 | 73 | #: project/auth/views.py:51 74 | msgid "Settings saved" 75 | msgstr "Настройки сохранены" 76 | 77 | #: project/auth/templates/auth/index.html:2 project/templates/nav.html:23 78 | msgid "Login" 79 | msgstr "Войти" 80 | 81 | #: project/auth/templates/auth/index.html:5 82 | msgid "Social login" 83 | msgstr "Вход через социальные сети" 84 | 85 | #: project/auth/templates/auth/index.html:6 86 | msgid "Login using your favorite social service " 87 | msgstr "Войдите через социальную сеть" 88 | 89 | #: project/auth/templates/auth/macros.html:14 90 | msgid "Do you want to disconnect this service from your profile?" 91 | msgstr "Вы хотите отключить эту социальную сеть?" 92 | 93 | #: project/auth/templates/auth/macros.html:14 94 | msgid "Are you sure?" 95 | msgstr "Вы уверены?" 96 | 97 | #: project/auth/templates/auth/profile.html:2 98 | msgid "Your profile" 99 | msgstr "Ваш профайл" 100 | 101 | #: project/auth/templates/auth/profile.html:8 102 | msgid "Social profiles" 103 | msgstr "Профайлы в социальных сетях" 104 | 105 | #: project/auth/templates/auth/profile.html:10 106 | #, python-format 107 | msgid "You are logged in as %(user)s!" 108 | msgstr "Вы вошли как %(user)s!" 109 | 110 | #: project/auth/templates/auth/profile.html:12 111 | msgid "Associate to this profile: " 112 | msgstr "Связать со своей учетной записью" 113 | 114 | #: project/auth/templates/auth/profile.html:15 115 | msgid "For your convenience, add new social services to your account" 116 | msgstr "Привяжите другие социальные сети для вашего удобства" 117 | 118 | #: project/auth/templates/auth/profile.html:20 119 | msgid "Wee! You connected all available social services!" 120 | msgstr "Ура! Вы привязали все доступные социальные сети!" 121 | 122 | #: project/auth/templates/auth/profile.html:24 123 | msgid "Associated: " 124 | msgstr "Уже добавлены:" 125 | 126 | #: project/auth/templates/auth/profile.html:25 127 | msgid "" 128 | "If you want to disconnect any service from the list then click item from the " 129 | "list" 130 | msgstr "" 131 | "Если вы хотите отключить социальную сеть, то кликните на элемент списка." 132 | 133 | #: project/auth/templates/auth/settings.html:2 project/templates/nav.html:20 134 | msgid "Settings" 135 | msgstr "Настройки" 136 | 137 | #: project/auth/templates/auth/settings.html:5 138 | msgid "Personal settings" 139 | msgstr "Персональные настройки" 140 | 141 | #: project/auth/templates/auth/settings.html:18 142 | msgid "Update settings" 143 | msgstr "Сохранить настройки" 144 | 145 | #: project/frontend/templates/frontend/index.html:2 146 | #: project/frontend/templates/frontend/index.html:5 147 | msgid "Welcome to our new site" 148 | msgstr "Добро пожаловать на наш новый сайт" 149 | 150 | #: project/templates/base.html:11 project/templates/base.html:59 151 | msgid "New site" 152 | msgstr "Новый сайт" 153 | 154 | #: project/templates/nav.html:10 155 | msgid "Site" 156 | msgstr "Сайт" 157 | 158 | #: project/templates/nav.html:14 159 | msgid "Splash page" 160 | msgstr "Главная страница" 161 | 162 | #: project/templates/nav.html:17 163 | msgid "About site " 164 | msgstr "О сайте" 165 | 166 | #: project/templates/nav.html:21 167 | msgid "Do you want to logout?" 168 | msgstr "Вы хотите выйти?" 169 | 170 | #: project/templates/nav.html:21 171 | msgid "Logout?" 172 | msgstr "Выйти?" 173 | 174 | #: project/templates/nav.html:21 175 | msgid "Logout" 176 | msgstr "Выйти" 177 | 178 | #: project/templates/misc/404.html:2 179 | msgid "Page not found" 180 | msgstr "Страница не найдена" 181 | 182 | #: project/templates/misc/404.html:4 183 | msgid "Error 404" 184 | msgstr "Ошибка 404" 185 | 186 | #: project/templates/misc/404.html:5 187 | #, python-format 188 | msgid "The page %(url)s you are looking for is not found." 189 | msgstr "Страница %(url)s на сайте не найдена." 190 | 191 | #: project/templates/misc/404.html:6 project/templates/misc/405.html:6 192 | #: project/templates/misc/500.html:6 193 | msgid "← Back to front page" 194 | msgstr "← Вернуться на главную страницу" 195 | 196 | #: project/templates/misc/405.html:2 197 | msgid "Method not allowed" 198 | msgstr "Метод доступа запрещен" 199 | 200 | #: project/templates/misc/405.html:4 201 | msgid "Error 405" 202 | msgstr "Ошибка 405" 203 | 204 | #: project/templates/misc/405.html:5 205 | msgid "You are trying to access page by methods that is not allowed." 206 | msgstr "Вы пытаетесь обратиться к странице неправильным методом." 207 | 208 | #: project/templates/misc/500.html:2 209 | msgid "Server error" 210 | msgstr "Ошибка сервера" 211 | 212 | #: project/templates/misc/500.html:4 213 | msgid "Error 500" 214 | msgstr "Ошибка 500" 215 | 216 | #: project/templates/misc/500.html:5 217 | #, python-format 218 | msgid "" 219 | "Error generating page %(url)s. Please contact us." 221 | msgstr "" 222 | "Ошибка обращения к странице %(url)s. Пожалуйста свяжитесь с нами." 224 | -------------------------------------------------------------------------------- /project/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY2 = sys.version_info[0] == 2 4 | VER = sys.version_info 5 | 6 | if not PY2: 7 | text_type = str 8 | string_types = (str,) 9 | integer_types = (int, ) 10 | 11 | iterkeys = lambda d: iter(d.keys()) 12 | itervalues = lambda d: iter(d.values()) 13 | iteritems = lambda d: iter(d.items()) 14 | 15 | def as_unicode(s): 16 | if isinstance(s, bytes): 17 | return s.decode('utf-8') 18 | 19 | return str(s) 20 | 21 | # Various tools 22 | from functools import reduce 23 | from urllib.parse import urljoin, urlparse 24 | else: 25 | text_type = unicode 26 | string_types = (str, unicode) 27 | integer_types = (int, long) 28 | 29 | iterkeys = lambda d: d.iterkeys() 30 | itervalues = lambda d: d.itervalues() 31 | iteritems = lambda d: d.iteritems() 32 | 33 | def as_unicode(s): 34 | if isinstance(s, str): 35 | return s.decode('utf-8') 36 | 37 | return unicode(s) 38 | 39 | # Helpers 40 | reduce = __builtins__['reduce'] if isinstance(__builtins__, dict) else __builtins__.reduce 41 | from urlparse import urljoin, urlparse 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # base 2 | Babel 3 | Flask 4 | Flask-Babel 5 | Flask-FlatPages 6 | Flask-Mail 7 | Flask-Script 8 | Flask-WTF 9 | Jinja2 10 | Markdown 11 | PyYAML 12 | Werkzeug 13 | itsdangerous 14 | requests 15 | speaklater 16 | 17 | # Cache and Celery 18 | Flask-Cache 19 | pylibmc 20 | redis 21 | celery 22 | 23 | # SQLAlchemy 24 | flask-restless 25 | Flask-SQLAlchemy 26 | flask-migrate 27 | 28 | # auth 29 | python_social_auth==0.2.1 30 | Flask-Login 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup 3 | 4 | setup( 5 | name="flask-project-template", 6 | version='0.1', 7 | url='', 8 | description='', 9 | author='Mikhail Kashkin', 10 | author_email='mkashkin@gmail.com', 11 | packages=["project"], 12 | include_package_data=True, 13 | zip_safe=False, 14 | install_requires=[ 15 | 'Flask', 16 | ], 17 | classifiers=[ 18 | 'Environment :: Web Environment', 19 | 'Intended Audience :: Developers', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python', 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /static/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "libs" 3 | } 4 | -------------------------------------------------------------------------------- /static/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vmstatic", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "*", 6 | "jquery": "*", 7 | "font-awesome": "*" 8 | }, 9 | "devDependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /static/code.js: -------------------------------------------------------------------------------- 1 | /* Custom project Javascript */ 2 | /* Pro tip: you can use Grunt or other automation tools 3 | to compile or minimize for production environment */ -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xen/flask-project-template/dae3e3145dfdee474ee758a68ebf8100beadad4b/static/favicon.png -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* Custom project CSS styles */ 2 | /* Pro tip: CSS can be compiled by LESS/SASS */ --------------------------------------------------------------------------------