├── .gitignore ├── Berksfile ├── Dockerfile ├── LICENSE ├── README.md ├── Vagrantfile ├── alembic.ini ├── alembic ├── README ├── env.py ├── script.py.mako └── versions │ ├── 4fe474604dbb_add_stores_managers_.py │ └── 5a0e003fafb2_first_revision.py ├── docs ├── Makefile ├── _static │ └── .gitignore ├── _templates │ └── .gitignore ├── api.rst ├── architecture.rst ├── conf.py ├── index.rst └── make.bat ├── manage.py ├── overholt ├── __init__.py ├── api │ ├── __init__.py │ ├── products.py │ ├── stores.py │ └── users.py ├── core.py ├── factory.py ├── forms.py ├── frontend │ ├── __init__.py │ ├── assets.py │ ├── dashboard.py │ ├── static │ │ ├── coffee │ │ │ ├── dashboard.coffee │ │ │ └── utils.coffee │ │ ├── css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.min.css │ │ │ ├── overholt.css │ │ │ └── overholt.min.css │ │ ├── img │ │ │ ├── glyphicons-halflings-white.png │ │ │ └── glyphicons-halflings.png │ │ ├── js │ │ │ ├── main.js │ │ │ ├── vendor.min.js │ │ │ └── vendor │ │ │ │ ├── backbone-1.0.0.min.js │ │ │ │ ├── bootstrap-2.3.3.min.js │ │ │ │ ├── jquery-1.10.1.min.js │ │ │ │ └── underscore-1.4.4.min.js │ │ └── less │ │ │ ├── elements.less │ │ │ └── overholt.less │ └── templates │ │ ├── dashboard.html │ │ ├── errors │ │ ├── 404.html │ │ └── 500.html │ │ ├── layouts │ │ ├── _menu.html │ │ ├── _messages.html │ │ └── boilerplate.html │ │ ├── macros │ │ └── forms.html │ │ └── security │ │ └── login_user.html ├── helpers.py ├── manage │ ├── __init__.py │ ├── products.py │ ├── stores.py │ └── users.py ├── middleware.py ├── models.py ├── products │ ├── __init__.py │ ├── forms.py │ └── models.py ├── services.py ├── settings.py ├── stores │ ├── __init__.py │ ├── forms.py │ └── models.py ├── tasks.py └── users │ ├── __init__.py │ ├── forms.py │ └── models.py ├── requirements.txt ├── tests ├── __init__.py ├── api │ ├── __init__.py │ ├── product_tests.py │ ├── store_tests.py │ └── user_tests.py ├── factories.py ├── frontend │ ├── __init__.py │ └── dashboard_tests.py ├── settings.py └── utils.py ├── wercker.yml └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Common Files 2 | .DS_Store 3 | .project 4 | .pydevproject 5 | .settings 6 | .coverage 7 | .vagrant* 8 | *.pyc 9 | rcfile.* 10 | !*.example 11 | *#* 12 | *~* 13 | nosetests.xml 14 | build/ 15 | docs/_build 16 | *.backup* 17 | 18 | # Project Files 19 | Gemfile* 20 | Berksfile.lock 21 | .webassets-cache 22 | instance 23 | settings.cfg 24 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | site :opscode 2 | 3 | cookbook 'apt' 4 | cookbook 'build-essential' 5 | cookbook 'mysql' 6 | cookbook 'redisio' 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04.1 2 | 3 | RUN apt-get update && apt-get install -yq python-dev python-pip libpq-dev libmysqlclient-dev 4 | 5 | ADD . /code 6 | WORKDIR /code 7 | 8 | RUN pip install -q -r requirements.txt 9 | 10 | EXPOSE 5000 11 | 12 | CMD python wsgi.py 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2013 by Matthew Wright 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 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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, 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overholt 2 | 3 | Overholt is an example Flask application illustrating some of my common practices 4 | 5 | ## Development Environment 6 | 7 | At the bare minimum you'll need the following for your development environment: 8 | 9 | 1. [Python](http://www.python.org/) 10 | 2. [MySQL](http://www.mysql.com/) 11 | 3. [Redis](http://redis.io/) 12 | 13 | It is strongly recommended to also install and use the following tools: 14 | 15 | 1. [virtualenv](https://python-guide.readthedocs.org/en/latest/dev/virtualenvs/#virtualenv) 16 | 2. [virtualenvwrapper](https://python-guide.readthedocs.org/en/latest/dev/virtualenvs/#virtualenvwrapper) 17 | 3. [Vagrant](http://vagrantup.com) 18 | 3. [Berkshelf](http://berkshelf.com) 19 | 20 | ### Local Setup 21 | 22 | The following assumes you have all of the recommended tools listed above installed. 23 | 24 | #### 1. Clone the project: 25 | 26 | $ git clone git@github.com:mattupstate/overholt.git 27 | $ cd overholt 28 | 29 | #### 2. Create and initialize virtualenv for the project: 30 | 31 | $ mkvirtualenv overholt 32 | $ pip install -r requirements.txt 33 | 34 | #### 3. Install the required cookbooks: 35 | 36 | $ berks install 37 | 38 | #### 4. Install the Berkshelf plugin for Vagrant: 39 | 40 | $ vagrant plugin install vagrant-berkshelf 41 | 42 | #### 5. Start virtual machine: 43 | 44 | $ vagrant up 45 | 46 | #### 6. Upgrade the database: 47 | 48 | $ alembic upgrade head 49 | 50 | #### 7. Run the development server: 51 | 52 | $ python wsgi.py 53 | 54 | #### 8. In another console run the Celery app: 55 | 56 | $ celery -A overholt.tasks worker 57 | 58 | #### 9. Open [http://localhost:5000](http://localhost:5000) 59 | 60 | 61 | ### Development 62 | 63 | If all went well in the setup above you will be ready to start hacking away on 64 | the application. 65 | 66 | #### Database Migrations 67 | 68 | This application uses [Alembic](http://alembic.readthedocs.org/) for database 69 | migrations and schema management. Changes or additions to the application data 70 | models will require the database be updated with the new tables and fields. 71 | Additionally, ensure that any new models are imported into the consolidated 72 | models file at `overholt.models`. To generate a migration file based on the 73 | current set of models run the following command: 74 | 75 | $ alembic revision --autogenerate -m "" 76 | 77 | Review the resulting version file located in the `alembic/versions` folder. If 78 | the file is to your liking upgrade the database with the following command: 79 | 80 | $ alembic upgrade head 81 | 82 | For anything beyond this workflow please read the Alembic documentation. 83 | 84 | #### Management Commands 85 | 86 | Management commands can be listed with the following command: 87 | 88 | $ python manage.py 89 | 90 | These can sometimes be useful to manipulate data while debugging in the browser. 91 | 92 | 93 | #### Tests 94 | 95 | To run the tests use the following command: 96 | 97 | $ nosetests 98 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | # All Vagrant configuration is done here. The most common configuration 6 | # options are documented and commented below. For a complete reference, 7 | # please see the online documentation at vagrantup.com. 8 | 9 | config.vm.hostname = "flask-example-berkshelf" 10 | 11 | # Every Vagrant virtual environment requires a box to build off of. 12 | config.vm.box = "quantal64-vanilla" 13 | 14 | # The url from where the 'config.vm.box' box will be fetched if it 15 | # doesn't already exist on the user's system. 16 | config.vm.box_url = "https://dl.dropboxusercontent.com/u/165709740/boxes/quantal64-vanilla.box" 17 | 18 | # Assign this VM to a host-only network IP, allowing you to access it 19 | # via the IP. Host-only networks can talk to the host machine as well as 20 | # any other machines on the same network, but cannot be accessed (through this 21 | # network interface) by any external networks. 22 | config.vm.network :private_network, ip: "33.33.33.10" 23 | 24 | # Create a public network, which generally matched to bridged network. 25 | # Bridged networks make the machine appear as another physical device on 26 | # your network. 27 | 28 | # config.vm.network :public_network 29 | 30 | # Create a forwarded port mapping which allows access to a specific port 31 | # within the machine from a port on the host machine. In the example below, 32 | # accessing "localhost:8080" will access port 80 on the guest machine. 33 | 34 | # Share an additional folder to the guest VM. The first argument is 35 | # the path on the host to the actual folder. The second argument is 36 | # the path on the guest to mount the folder. And the optional third 37 | # argument is a set of non-required options. 38 | # config.vm.synced_folder "../data", "/vagrant_data" 39 | 40 | # Provider-specific configuration so you can fine-tune various 41 | # backing providers for Vagrant. These expose provider-specific options. 42 | # Example for VirtualBox: 43 | # 44 | # config.vm.provider :virtualbox do |vb| 45 | # # Don't boot with headless mode 46 | # vb.gui = true 47 | # 48 | # # Use VBoxManage to customize the VM. For example to change memory: 49 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 50 | # end 51 | # 52 | # View the documentation for the provider you're using for more 53 | # information on available options. 54 | 55 | # The path to the Berksfile to use with Vagrant Berkshelf 56 | # config.berkshelf.berksfile_path = "./Berksfile" 57 | 58 | # Enabling the Berkshelf plugin. To enable this globally, add this configuration 59 | # option to your ~/.vagrant.d/Vagrantfile file 60 | config.berkshelf.enabled = true 61 | 62 | # An array of symbols representing groups of cookbook described in the Vagrantfile 63 | # to exclusively install and copy to Vagrant's shelf. 64 | # config.berkshelf.only = [] 65 | 66 | # An array of symbols representing groups of cookbook described in the Vagrantfile 67 | # to skip installing and copying to Vagrant's shelf. 68 | # config.berkshelf.except = [] 69 | 70 | config.vm.provision :shell do |sh| 71 | sh.inline = <<-EOF 72 | sudo apt-get update 73 | sudo apt-get install ruby1.9.3 --yes 74 | sudo gem install chef --version 11.4.4 --no-ri --no-rdoc 75 | EOF 76 | end 77 | 78 | config.vm.provision :chef_solo do |chef| 79 | chef.json = { 80 | :mysql => { 81 | :bind_address => '0.0.0.0', 82 | :allow_remote_root => true, 83 | :server_root_password => '', 84 | :server_debian_password => '', 85 | :server_repl_password => '' 86 | } 87 | } 88 | 89 | chef.run_list = [ 90 | "recipe[apt]", 91 | "recipe[build-essential]", 92 | "recipe[mysql::server]", 93 | "recipe[redisio::install]", 94 | "recipe[redisio::enable]" 95 | ] 96 | end 97 | 98 | config.vm.provision :shell do |sh| 99 | sh.inline = <<-EOF 100 | mysql -e 'create database if not exists overholt;' 101 | EOF 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = alembic 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # set to 'true' to run the environment during 11 | # the 'revision' command, regardless of autogenerate 12 | # revision_environment = false 13 | 14 | # Logging configuration 15 | [loggers] 16 | keys = root,sqlalchemy,alembic 17 | 18 | [handlers] 19 | keys = console 20 | 21 | [formatters] 22 | keys = generic 23 | 24 | [logger_root] 25 | level = WARN 26 | handlers = console 27 | qualname = 28 | 29 | [logger_sqlalchemy] 30 | level = WARN 31 | handlers = 32 | qualname = sqlalchemy.engine 33 | 34 | [logger_alembic] 35 | level = INFO 36 | handlers = 37 | qualname = alembic 38 | 39 | [handler_console] 40 | class = StreamHandler 41 | args = (sys.stderr,) 42 | level = NOTSET 43 | formatter = generic 44 | 45 | [formatter_generic] 46 | format = %(levelname)-5.5s [%(name)s] %(message)s 47 | datefmt = %H:%M:%S 48 | -------------------------------------------------------------------------------- /alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import sys 4 | from os.path import dirname, abspath 5 | sys.path.append(dirname(dirname(abspath(__file__)))) 6 | 7 | from alembic import context 8 | from sqlalchemy import engine_from_config, pool 9 | from logging.config import fileConfig 10 | 11 | from overholt.models import * 12 | from overholt.core import db 13 | from overholt.api import create_app 14 | 15 | app = create_app() 16 | 17 | # this is the Alembic Config object, which provides 18 | # access to the values within the .ini file in use. 19 | config = context.config 20 | 21 | # Interpret the config file for Python logging. 22 | # This line sets up loggers basically. 23 | fileConfig(config.config_file_name) 24 | 25 | # other values from the config, defined by the needs of env.py, 26 | # can be acquired: 27 | # my_important_option = config.get_main_option("my_important_option") 28 | # ... etc. 29 | 30 | 31 | def run_migrations_offline(): 32 | """Run migrations in 'offline' mode. 33 | 34 | This configures the context with just a URL 35 | and not an Engine, though an Engine is acceptable 36 | here as well. By skipping the Engine creation 37 | we don't even need a DBAPI to be available. 38 | 39 | Calls to context.execute() here emit the given string to the 40 | script output. 41 | 42 | """ 43 | url = config.get_main_option("sqlalchemy.url") 44 | context.configure(url=url) 45 | 46 | with context.begin_transaction(): 47 | context.run_migrations() 48 | 49 | 50 | def run_migrations_online(): 51 | """Run migrations in 'online' mode. 52 | 53 | In this scenario we need to create an Engine 54 | and associate a connection with the context. 55 | 56 | """ 57 | 58 | alembic_config = config.get_section(config.config_ini_section) 59 | alembic_config['sqlalchemy.url'] = app.config['SQLALCHEMY_DATABASE_URI'] 60 | 61 | engine = engine_from_config( 62 | alembic_config, 63 | prefix='sqlalchemy.', 64 | poolclass=pool.NullPool) 65 | 66 | connection = engine.connect() 67 | context.configure( 68 | connection=connection, 69 | target_metadata=db.metadata 70 | ) 71 | 72 | try: 73 | with context.begin_transaction(): 74 | context.run_migrations() 75 | finally: 76 | connection.close() 77 | 78 | if context.is_offline_mode(): 79 | run_migrations_offline() 80 | else: 81 | run_migrations_online() 82 | -------------------------------------------------------------------------------- /alembic/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 | -------------------------------------------------------------------------------- /alembic/versions/4fe474604dbb_add_stores_managers_.py: -------------------------------------------------------------------------------- 1 | """Add `stores_managers` table 2 | 3 | Revision ID: 4fe474604dbb 4 | Revises: 5a0e003fafb2 5 | Create Date: 2013-06-28 22:18:42.292040 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '4fe474604dbb' 11 | down_revision = '5a0e003fafb2' 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('stores_managers', 20 | sa.Column('user_id', sa.Integer(), nullable=True), 21 | sa.Column('store_id', sa.Integer(), nullable=True), 22 | sa.ForeignKeyConstraint(['store_id'], ['stores.id'], ), 23 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 24 | sa.PrimaryKeyConstraint() 25 | ) 26 | ### end Alembic commands ### 27 | 28 | 29 | def downgrade(): 30 | ### commands auto generated by Alembic - please adjust! ### 31 | op.drop_table('stores_managers') 32 | ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/5a0e003fafb2_first_revision.py: -------------------------------------------------------------------------------- 1 | """First revision 2 | 3 | Revision ID: 5a0e003fafb2 4 | Revises: None 5 | Create Date: 2013-06-24 11:37:45.113288 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '5a0e003fafb2' 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('users', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('email', sa.String(length=255), nullable=True), 22 | sa.Column('password', sa.String(length=120), nullable=True), 23 | sa.Column('active', sa.Boolean(), nullable=True), 24 | sa.Column('confirmed_at', sa.DateTime(), nullable=True), 25 | sa.Column('last_login_at', sa.DateTime(), nullable=True), 26 | sa.Column('current_login_at', sa.DateTime(), nullable=True), 27 | sa.Column('last_login_ip', sa.String(length=100), nullable=True), 28 | sa.Column('current_login_ip', sa.String(length=100), nullable=True), 29 | sa.Column('login_count', sa.Integer(), nullable=True), 30 | sa.Column('registered_at', sa.DateTime(), nullable=True), 31 | sa.PrimaryKeyConstraint('id'), 32 | sa.UniqueConstraint('email') 33 | ) 34 | op.create_table('categories', 35 | sa.Column('id', sa.Integer(), nullable=False), 36 | sa.Column('name', sa.String(length=255), nullable=True), 37 | sa.Column('description', sa.String(length=255), nullable=True), 38 | sa.PrimaryKeyConstraint('id') 39 | ) 40 | op.create_table('products', 41 | sa.Column('id', sa.Integer(), nullable=False), 42 | sa.Column('name', sa.String(length=255), nullable=True), 43 | sa.PrimaryKeyConstraint('id') 44 | ) 45 | op.create_table('roles', 46 | sa.Column('id', sa.Integer(), nullable=False), 47 | sa.Column('name', sa.String(length=80), nullable=True), 48 | sa.Column('description', sa.String(length=255), nullable=True), 49 | sa.PrimaryKeyConstraint('id'), 50 | sa.UniqueConstraint('name') 51 | ) 52 | op.create_table('roles_users', 53 | sa.Column('user_id', sa.Integer(), nullable=True), 54 | sa.Column('role_id', sa.Integer(), nullable=True), 55 | sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ), 56 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 57 | sa.PrimaryKeyConstraint() 58 | ) 59 | op.create_table('products_categories', 60 | sa.Column('product_id', sa.Integer(), nullable=True), 61 | sa.Column('category_id', sa.Integer(), nullable=True), 62 | sa.ForeignKeyConstraint(['category_id'], ['categories.id'], ), 63 | sa.ForeignKeyConstraint(['product_id'], ['products.id'], ), 64 | sa.PrimaryKeyConstraint() 65 | ) 66 | op.create_table('stores', 67 | sa.Column('id', sa.Integer(), nullable=False), 68 | sa.Column('name', sa.String(length=255), nullable=True), 69 | sa.Column('address', sa.String(length=255), nullable=True), 70 | sa.Column('city', sa.String(length=255), nullable=True), 71 | sa.Column('state', sa.String(length=255), nullable=True), 72 | sa.Column('zip_code', sa.String(length=255), nullable=True), 73 | sa.Column('manager_id', sa.Integer(), nullable=True), 74 | sa.ForeignKeyConstraint(['manager_id'], ['users.id'], ), 75 | sa.PrimaryKeyConstraint('id') 76 | ) 77 | op.create_table('stores_products', 78 | sa.Column('product_id', sa.Integer(), nullable=True), 79 | sa.Column('store_id', sa.Integer(), nullable=True), 80 | sa.ForeignKeyConstraint(['product_id'], ['products.id'], ), 81 | sa.ForeignKeyConstraint(['store_id'], ['stores.id'], ), 82 | sa.PrimaryKeyConstraint() 83 | ) 84 | ### end Alembic commands ### 85 | 86 | 87 | def downgrade(): 88 | ### commands auto generated by Alembic - please adjust! ### 89 | op.drop_table('stores_products') 90 | op.drop_table('stores') 91 | op.drop_table('products_categories') 92 | op.drop_table('roles_users') 93 | op.drop_table('roles') 94 | op.drop_table('products') 95 | op.drop_table('categories') 96 | op.drop_table('users') 97 | ### end Alembic commands ### 98 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Overholt.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Overholt.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Overholt" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Overholt" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattupstate/overholt/e03209f7a059d165a9154355d090738af3159028/docs/_static/.gitignore -------------------------------------------------------------------------------- /docs/_templates/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattupstate/overholt/e03209f7a059d165a9154355d090738af3159028/docs/_templates/.gitignore -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Overholt API Documentation 2 | ========================== 3 | 4 | Products 5 | -------------------------- 6 | 7 | .. autoflask:: overholt.api:create_app() 8 | :undoc-static: 9 | :undoc-blueprints: users, stores 10 | 11 | Stores 12 | -------------------------- 13 | 14 | .. autoflask:: overholt.api:create_app() 15 | :undoc-static: 16 | :undoc-blueprints: users, products 17 | 18 | 19 | Users 20 | -------------------------- 21 | 22 | .. autoflask:: overholt.api:create_app() 23 | :undoc-static: 24 | :undoc-blueprints: products, stores 25 | 26 | -------------------------------------------------------------------------------- /docs/architecture.rst: -------------------------------------------------------------------------------- 1 | Overholt Architecture Documentation 2 | =================================== 3 | 4 | Documentation describing the application's architecture goes here. 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Overholt documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jun 25 13:03:08 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | sys.path.append(os.path.abspath('..')) 17 | sys.path.append(os.path.abspath('_themes')) 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ----------------------------------------------------- 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be extensions 30 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 33 | 'sphinx.ext.doctest', 34 | 'sphinx.ext.intersphinx', 35 | 'sphinxcontrib.autohttp.flask' 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'Overholt' 52 | copyright = u'2013, Matt Wright' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '0.1.0' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '0.1.0' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | #language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = ['_build'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | #keep_warnings = False 99 | 100 | 101 | # -- Options for HTML output --------------------------------------------------- 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | html_theme = 'default' 106 | 107 | # Theme options are theme-specific and customize the look and feel of a theme 108 | # further. For a list of options available for each theme, see the 109 | # documentation. 110 | #html_theme_options = {} 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | #html_theme_path = [] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 137 | # using the given strftime format. 138 | #html_last_updated_fmt = '%b %d, %Y' 139 | 140 | # If true, SmartyPants will be used to convert quotes and dashes to 141 | # typographically correct entities. 142 | #html_use_smartypants = True 143 | 144 | # Custom sidebar templates, maps document names to template names. 145 | #html_sidebars = {} 146 | 147 | # Additional templates that should be rendered to pages, maps page names to 148 | # template names. 149 | #html_additional_pages = {} 150 | 151 | # If false, no module index is generated. 152 | #html_domain_indices = True 153 | 154 | # If false, no index is generated. 155 | #html_use_index = True 156 | 157 | # If true, the index is split into individual pages for each letter. 158 | #html_split_index = False 159 | 160 | # If true, links to the reST sources are added to the pages. 161 | #html_show_sourcelink = True 162 | 163 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 164 | #html_show_sphinx = True 165 | 166 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 167 | #html_show_copyright = True 168 | 169 | # If true, an OpenSearch description file will be output, and all pages will 170 | # contain a tag referring to it. The value of this option must be the 171 | # base URL from which the finished HTML is served. 172 | #html_use_opensearch = '' 173 | 174 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 175 | #html_file_suffix = None 176 | 177 | # Output file base name for HTML help builder. 178 | htmlhelp_basename = 'Overholtdoc' 179 | 180 | 181 | # -- Options for LaTeX output -------------------------------------------------- 182 | 183 | latex_elements = { 184 | # The paper size ('letterpaper' or 'a4paper'). 185 | #'papersize': 'letterpaper', 186 | 187 | # The font size ('10pt', '11pt' or '12pt'). 188 | #'pointsize': '10pt', 189 | 190 | # Additional stuff for the LaTeX preamble. 191 | #'preamble': '', 192 | } 193 | 194 | # Grouping the document tree into LaTeX files. List of tuples 195 | # (source start file, target name, title, author, documentclass [howto/manual]). 196 | latex_documents = [ 197 | ('index', 'Overholt.tex', u'Overholt Documentation', 198 | u'Matt Wright', 'manual'), 199 | ] 200 | 201 | # The name of an image file (relative to this directory) to place at the top of 202 | # the title page. 203 | #latex_logo = None 204 | 205 | # For "manual" documents, if this is true, then toplevel headings are parts, 206 | # not chapters. 207 | #latex_use_parts = False 208 | 209 | # If true, show page references after internal links. 210 | #latex_show_pagerefs = False 211 | 212 | # If true, show URL addresses after external links. 213 | #latex_show_urls = False 214 | 215 | # Documents to append as an appendix to all manuals. 216 | #latex_appendices = [] 217 | 218 | # If false, no module index is generated. 219 | #latex_domain_indices = True 220 | 221 | 222 | # -- Options for manual page output -------------------------------------------- 223 | 224 | # One entry per manual page. List of tuples 225 | # (source start file, name, description, authors, manual section). 226 | man_pages = [ 227 | ('index', 'overholt', u'Overholt Documentation', 228 | [u'Matt Wright'], 1) 229 | ] 230 | 231 | # If true, show URL addresses after external links. 232 | #man_show_urls = False 233 | 234 | 235 | # -- Options for Texinfo output ------------------------------------------------ 236 | 237 | # Grouping the document tree into Texinfo files. List of tuples 238 | # (source start file, target name, title, author, 239 | # dir menu entry, description, category) 240 | texinfo_documents = [ 241 | ('index', 'Overholt', u'Overholt Documentation', 242 | u'Matt Wright', 'Overholt', 'One line description of project.', 243 | 'Miscellaneous'), 244 | ] 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #texinfo_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #texinfo_domain_indices = True 251 | 252 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 253 | #texinfo_show_urls = 'footnote' 254 | 255 | # If true, do not generate a @detailmenu in the "Top" node's menu. 256 | #texinfo_no_detailmenu = False 257 | 258 | 259 | # Example configuration for intersphinx: refer to the Python standard library. 260 | intersphinx_mapping = {'http://docs.python.org/': None} 261 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Overholt's documentation! 2 | ==================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | architecture 10 | api 11 | 12 | 13 | Indices and tables 14 | ================== 15 | 16 | * :ref:`genindex` 17 | * :ref:`modindex` 18 | * :ref:`search` 19 | 20 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Overholt.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Overholt.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | manage 4 | ~~~~~~ 5 | 6 | Manager module 7 | """ 8 | 9 | from flask.ext.script import Manager 10 | 11 | from overholt.api import create_app 12 | from overholt.manage import CreateUserCommand, DeleteUserCommand, ListUsersCommand 13 | 14 | manager = Manager(create_app()) 15 | manager.add_command('create_user', CreateUserCommand()) 16 | manager.add_command('delete_user', DeleteUserCommand()) 17 | manager.add_command('list_users', ListUsersCommand()) 18 | 19 | if __name__ == "__main__": 20 | manager.run() 21 | -------------------------------------------------------------------------------- /overholt/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt 4 | ~~~~~~~~ 5 | 6 | overholt application package 7 | """ 8 | -------------------------------------------------------------------------------- /overholt/api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.api 4 | ~~~~~~~~~~~~~ 5 | 6 | overholt api application package 7 | """ 8 | 9 | from functools import wraps 10 | 11 | from flask import jsonify 12 | from flask_security import login_required 13 | 14 | from ..core import OverholtError, OverholtFormError 15 | from ..helpers import JSONEncoder 16 | from .. import factory 17 | 18 | 19 | def create_app(settings_override=None, register_security_blueprint=False): 20 | """Returns the Overholt API application instance""" 21 | 22 | app = factory.create_app(__name__, __path__, settings_override, 23 | register_security_blueprint=register_security_blueprint) 24 | 25 | # Set the default JSON encoder 26 | app.json_encoder = JSONEncoder 27 | 28 | # Register custom error handlers 29 | app.errorhandler(OverholtError)(on_overholt_error) 30 | app.errorhandler(OverholtFormError)(on_overholt_form_error) 31 | app.errorhandler(404)(on_404) 32 | 33 | return app 34 | 35 | 36 | def route(bp, *args, **kwargs): 37 | kwargs.setdefault('strict_slashes', False) 38 | 39 | def decorator(f): 40 | @bp.route(*args, **kwargs) 41 | @login_required 42 | @wraps(f) 43 | def wrapper(*args, **kwargs): 44 | sc = 200 45 | rv = f(*args, **kwargs) 46 | if isinstance(rv, tuple): 47 | sc = rv[1] 48 | rv = rv[0] 49 | return jsonify(dict(data=rv)), sc 50 | return f 51 | 52 | return decorator 53 | 54 | 55 | def on_overholt_error(e): 56 | return jsonify(dict(error=e.msg)), 400 57 | 58 | 59 | def on_overholt_form_error(e): 60 | return jsonify(dict(errors=e.errors)), 400 61 | 62 | 63 | def on_404(e): 64 | return jsonify(dict(error='Not found')), 404 65 | -------------------------------------------------------------------------------- /overholt/api/products.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.api.products 4 | ~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Product endpoints 7 | """ 8 | 9 | from flask import Blueprint, request 10 | 11 | from ..forms import NewProductForm, UpdateProductForm 12 | from ..services import products 13 | from . import OverholtFormError, route 14 | 15 | bp = Blueprint('products', __name__, url_prefix='/products') 16 | 17 | 18 | @route(bp, '/') 19 | def list(): 20 | """Returns a list of product instances.""" 21 | return products.all() 22 | 23 | 24 | @route(bp, '/', methods=['POST']) 25 | def create(): 26 | """Creates a new product. Returns the new product instance.""" 27 | form = NewProductForm() 28 | if form.validate_on_submit(): 29 | return products.create(**request.json) 30 | raise OverholtFormError(form.errors) 31 | 32 | 33 | @route(bp, '/') 34 | def show(product_id): 35 | """Returns a product instance.""" 36 | return products.get_or_404(product_id) 37 | 38 | 39 | @route(bp, '/', methods=['PUT']) 40 | def update(product_id): 41 | """Updates a product. Returns the updated product instance.""" 42 | form = UpdateProductForm() 43 | if form.validate_on_submit(): 44 | return products.update(products.get_or_404(product_id), **request.json) 45 | raise(OverholtFormError(form.errors)) 46 | 47 | 48 | @route(bp, '/', methods=['DELETE']) 49 | def delete(product_id): 50 | """Deletes a product. Returns a 204 response.""" 51 | products.delete(products.get_or_404(product_id)) 52 | return None, 204 53 | -------------------------------------------------------------------------------- /overholt/api/stores.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.api.stores 4 | ~~~~~~~~~~~~~~~~~~~ 5 | 6 | Store endpoints 7 | """ 8 | 9 | from flask import Blueprint, request 10 | 11 | from ..forms import NewStoreForm, UpdateStoreForm 12 | from ..services import stores as _stores, products as _products, users as _users 13 | from ..tasks import send_manager_added_email, send_manager_removed_email 14 | from . import OverholtFormError, route 15 | 16 | bp = Blueprint('stores', __name__, url_prefix='/stores') 17 | 18 | 19 | @route(bp, '/') 20 | def list(): 21 | """Returns a list of all store instances.""" 22 | return _stores.all() 23 | 24 | 25 | @route(bp, '/', methods=['POST']) 26 | def new(): 27 | """Creates a new store. Returns the new store instance.""" 28 | form = NewStoreForm() 29 | if form.validate_on_submit(): 30 | return _stores.create(**request.json) 31 | raise OverholtFormError(form.errors) 32 | 33 | 34 | @route(bp, '/') 35 | def show(store_id): 36 | """Returns a store instance.""" 37 | return _stores.get_or_404(store_id) 38 | 39 | 40 | @route(bp, '/', methods=['PUT']) 41 | def update(store_id): 42 | """Updates a store. Returns the updated store instance.""" 43 | form = UpdateStoreForm() 44 | if form.validate_on_submit(): 45 | return _stores.update(_stores.get_or_404(store_id), **request.json) 46 | raise OverholtFormError(form.errors) 47 | 48 | 49 | @route(bp, '/', methods=['DELETE']) 50 | def delete(store_id): 51 | """Deletes a store. Returns a 204 response.""" 52 | _stores.delete(_stores.get_or_404(store_id)) 53 | return None, 204 54 | 55 | 56 | @route(bp, '//products') 57 | def products(store_id): 58 | """Returns a list of product instances belonging to a store.""" 59 | return _stores.get_or_404(store_id).products 60 | 61 | 62 | @route(bp, '//products/', methods=['PUT']) 63 | def add_product(store_id, product_id): 64 | """Adds a product to a store. Returns the product instance.""" 65 | return _stores.add_product(_stores.get_or_404(store_id), 66 | _products.get_or_404(product_id)) 67 | 68 | 69 | @route(bp, '//products/', methods=['DELETE']) 70 | def remove_product(store_id, product_id): 71 | """Removes a product form a store. Returns a 204 response.""" 72 | _stores.remove_product(_stores.get_or_404(store_id), 73 | _products.get_or_404(product_id)) 74 | return None, 204 75 | 76 | 77 | @route(bp, '//managers') 78 | def managers(store_id): 79 | return _stores.get_or_404(store_id).managers 80 | 81 | 82 | @route(bp, '//managers/', methods=['PUT']) 83 | def add_manager(store_id, user_id): 84 | store, manager = _stores.add_manager(_stores.get_or_404(store_id), 85 | _users.get_or_404(user_id)) 86 | send_manager_added_email.delay(manager.email) 87 | return store 88 | 89 | 90 | @route(bp, '//managers/', methods=['DELETE']) 91 | def remove_manager(store_id, user_id): 92 | store, manager = _stores.remove_manager(_stores.get_or_404(store_id), 93 | _users.get_or_404(user_id)) 94 | send_manager_removed_email.delay(manager.email) 95 | return None, 204 96 | -------------------------------------------------------------------------------- /overholt/api/users.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.api.users 4 | ~~~~~~~~~~~~~~~~~~ 5 | 6 | User endpoints 7 | """ 8 | 9 | from flask import Blueprint 10 | from flask_login import current_user 11 | 12 | from ..services import users 13 | from . import route 14 | 15 | bp = Blueprint('users', __name__, url_prefix='/users') 16 | 17 | 18 | @route(bp, '/') 19 | def whoami(): 20 | """Returns the user instance of the currently authenticated user.""" 21 | return current_user._get_current_object() 22 | 23 | 24 | @route(bp, '/') 25 | def show(user_id): 26 | """Returns a user instance.""" 27 | return users.get_or_404(user_id) 28 | -------------------------------------------------------------------------------- /overholt/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.core 4 | ~~~~~~~~~~~~~ 5 | 6 | core module 7 | """ 8 | 9 | from flask_mail import Mail 10 | from flask_sqlalchemy import SQLAlchemy 11 | from flask_security import Security 12 | 13 | #: Flask-SQLAlchemy extension instance 14 | db = SQLAlchemy() 15 | 16 | #: Flask-Mail extension instance 17 | mail = Mail() 18 | 19 | #: Flask-Security extension instance 20 | security = Security() 21 | 22 | 23 | class OverholtError(Exception): 24 | """Base application error class.""" 25 | 26 | def __init__(self, msg): 27 | self.msg = msg 28 | 29 | 30 | class OverholtFormError(Exception): 31 | """Raise when an error processing a form occurs.""" 32 | 33 | def __init__(self, errors=None): 34 | self.errors = errors 35 | 36 | 37 | class Service(object): 38 | """A :class:`Service` instance encapsulates common SQLAlchemy model 39 | operations in the context of a :class:`Flask` application. 40 | """ 41 | __model__ = None 42 | 43 | def _isinstance(self, model, raise_error=True): 44 | """Checks if the specified model instance matches the service's model. 45 | By default this method will raise a `ValueError` if the model is not the 46 | expected type. 47 | 48 | :param model: the model instance to check 49 | :param raise_error: flag to raise an error on a mismatch 50 | """ 51 | rv = isinstance(model, self.__model__) 52 | if not rv and raise_error: 53 | raise ValueError('%s is not of type %s' % (model, self.__model__)) 54 | return rv 55 | 56 | def _preprocess_params(self, kwargs): 57 | """Returns a preprocessed dictionary of parameters. Used by default 58 | before creating a new instance or updating an existing instance. 59 | 60 | :param kwargs: a dictionary of parameters 61 | """ 62 | kwargs.pop('csrf_token', None) 63 | return kwargs 64 | 65 | def save(self, model): 66 | """Commits the model to the database and returns the model 67 | 68 | :param model: the model to save 69 | """ 70 | self._isinstance(model) 71 | db.session.add(model) 72 | db.session.commit() 73 | return model 74 | 75 | def all(self): 76 | """Returns a generator containing all instances of the service's model. 77 | """ 78 | return self.__model__.query.all() 79 | 80 | def get(self, id): 81 | """Returns an instance of the service's model with the specified id. 82 | Returns `None` if an instance with the specified id does not exist. 83 | 84 | :param id: the instance id 85 | """ 86 | return self.__model__.query.get(id) 87 | 88 | def get_all(self, *ids): 89 | """Returns a list of instances of the service's model with the specified 90 | ids. 91 | 92 | :param *ids: instance ids 93 | """ 94 | return self.__model__.query.filter(self.__model__.id.in_(ids)).all() 95 | 96 | def find(self, **kwargs): 97 | """Returns a list of instances of the service's model filtered by the 98 | specified key word arguments. 99 | 100 | :param **kwargs: filter parameters 101 | """ 102 | return self.__model__.query.filter_by(**kwargs) 103 | 104 | def first(self, **kwargs): 105 | """Returns the first instance found of the service's model filtered by 106 | the specified key word arguments. 107 | 108 | :param **kwargs: filter parameters 109 | """ 110 | return self.find(**kwargs).first() 111 | 112 | def get_or_404(self, id): 113 | """Returns an instance of the service's model with the specified id or 114 | raises an 404 error if an instance with the specified id does not exist. 115 | 116 | :param id: the instance id 117 | """ 118 | return self.__model__.query.get_or_404(id) 119 | 120 | def new(self, **kwargs): 121 | """Returns a new, unsaved instance of the service's model class. 122 | 123 | :param **kwargs: instance parameters 124 | """ 125 | return self.__model__(**self._preprocess_params(kwargs)) 126 | 127 | def create(self, **kwargs): 128 | """Returns a new, saved instance of the service's model class. 129 | 130 | :param **kwargs: instance parameters 131 | """ 132 | return self.save(self.new(**kwargs)) 133 | 134 | def update(self, model, **kwargs): 135 | """Returns an updated instance of the service's model class. 136 | 137 | :param model: the model to update 138 | :param **kwargs: update parameters 139 | """ 140 | self._isinstance(model) 141 | for k, v in self._preprocess_params(kwargs).items(): 142 | setattr(model, k, v) 143 | self.save(model) 144 | return model 145 | 146 | def delete(self, model): 147 | """Immediately deletes the specified model instance. 148 | 149 | :param model: the model instance to delete 150 | """ 151 | self._isinstance(model) 152 | db.session.delete(model) 153 | db.session.commit() 154 | -------------------------------------------------------------------------------- /overholt/factory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.factory 4 | ~~~~~~~~~~~~~~~~ 5 | 6 | overholt factory module 7 | """ 8 | 9 | import os 10 | 11 | from celery import Celery 12 | from flask import Flask 13 | from flask_security import SQLAlchemyUserDatastore 14 | 15 | from .core import db, mail, security 16 | from .helpers import register_blueprints 17 | from .middleware import HTTPMethodOverrideMiddleware 18 | from .models import User, Role 19 | 20 | 21 | def create_app(package_name, package_path, settings_override=None, 22 | register_security_blueprint=True): 23 | """Returns a :class:`Flask` application instance configured with common 24 | functionality for the Overholt platform. 25 | 26 | :param package_name: application package name 27 | :param package_path: application package path 28 | :param settings_override: a dictionary of settings to override 29 | :param register_security_blueprint: flag to specify if the Flask-Security 30 | Blueprint should be registered. Defaults 31 | to `True`. 32 | """ 33 | app = Flask(package_name, instance_relative_config=True) 34 | 35 | app.config.from_object('overholt.settings') 36 | app.config.from_pyfile('settings.cfg', silent=True) 37 | app.config.from_object(settings_override) 38 | 39 | db.init_app(app) 40 | mail.init_app(app) 41 | security.init_app(app, SQLAlchemyUserDatastore(db, User, Role), 42 | register_blueprint=register_security_blueprint) 43 | 44 | register_blueprints(app, package_name, package_path) 45 | 46 | app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app) 47 | 48 | return app 49 | 50 | 51 | def create_celery_app(app=None): 52 | app = app or create_app('overholt', os.path.dirname(__file__)) 53 | celery = Celery(__name__, broker=app.config['CELERY_BROKER_URL']) 54 | celery.conf.update(app.config) 55 | TaskBase = celery.Task 56 | 57 | class ContextTask(TaskBase): 58 | abstract = True 59 | 60 | def __call__(self, *args, **kwargs): 61 | with app.app_context(): 62 | return TaskBase.__call__(self, *args, **kwargs) 63 | 64 | celery.Task = ContextTask 65 | return celery 66 | -------------------------------------------------------------------------------- /overholt/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.forms 4 | ~~~~~~~~~~~~~~ 5 | 6 | consolodated forms module 7 | """ 8 | 9 | from .products.forms import * 10 | from .stores.forms import * 11 | from .users.forms import * 12 | -------------------------------------------------------------------------------- /overholt/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.frontend 4 | ~~~~~~~~~~~~~~~~~~ 5 | 6 | launchpad frontend application package 7 | """ 8 | 9 | from functools import wraps 10 | 11 | from flask import render_template 12 | from flask_security import login_required 13 | 14 | from .. import factory 15 | from . import assets 16 | 17 | 18 | def create_app(settings_override=None): 19 | """Returns the Overholt dashboard application instance""" 20 | app = factory.create_app(__name__, __path__, settings_override) 21 | 22 | # Init assets 23 | assets.init_app(app) 24 | 25 | # Register custom error handlers 26 | if not app.debug: 27 | for e in [500, 404]: 28 | app.errorhandler(e)(handle_error) 29 | 30 | return app 31 | 32 | 33 | def handle_error(e): 34 | return render_template('errors/%s.html' % e.code), e.code 35 | 36 | 37 | def route(bp, *args, **kwargs): 38 | def decorator(f): 39 | @bp.route(*args, **kwargs) 40 | @login_required 41 | @wraps(f) 42 | def wrapper(*args, **kwargs): 43 | return f(*args, **kwargs) 44 | return f 45 | 46 | return decorator 47 | -------------------------------------------------------------------------------- /overholt/frontend/assets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | overholt.frontend.assets 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | frontend application asset "pipeline" 7 | """ 8 | 9 | from flask_assets import Environment, Bundle 10 | 11 | 12 | #: application css bundle 13 | css_overholt = Bundle("less/overholt.less", 14 | filters="less", output="css/overholt.css", 15 | debug=False) 16 | 17 | #: consolidated css bundle 18 | css_all = Bundle("css/bootstrap.min.css", css_overholt, 19 | "css/bootstrap-responsive.min.css", 20 | filters="cssmin", output="css/overholt.min.css") 21 | 22 | #: vendor js bundle 23 | js_vendor = Bundle("js/vendor/jquery-1.10.1.min.js", 24 | "js/vendor/bootstrap-2.3.3.min.js", 25 | "js/vendor/underscore-1.4.4.min.js", 26 | "js/vendor/backbone-1.0.0.min.js", 27 | filters="jsmin", output="js/vendor.min.js") 28 | 29 | #: application js bundle 30 | js_main = Bundle("coffee/*.coffee", filters="coffeescript", output="js/main.js") 31 | 32 | 33 | def init_app(app): 34 | webassets = Environment(app) 35 | webassets.register('css_all', css_all) 36 | webassets.register('js_vendor', js_vendor) 37 | webassets.register('js_main', js_main) 38 | webassets.manifest = 'cache' if not app.debug else False 39 | webassets.cache = not app.debug 40 | webassets.debug = app.debug 41 | -------------------------------------------------------------------------------- /overholt/frontend/dashboard.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from flask import Blueprint, render_template 4 | 5 | from . import route 6 | 7 | bp = Blueprint('dashboard', __name__) 8 | 9 | 10 | @route(bp, '/') 11 | def index(): 12 | """Returns the dashboard interface.""" 13 | return render_template('dashboard.html') 14 | -------------------------------------------------------------------------------- /overholt/frontend/static/coffee/dashboard.coffee: -------------------------------------------------------------------------------- 1 | # dashboard.coffee 2 | 3 | class Store extends Backbone.Model 4 | urlRoot: "/api/stores" 5 | 6 | defaults: 7 | id: null 8 | name: "" 9 | address: "" 10 | city: "" 11 | state: "" 12 | zip_code: "" 13 | 14 | initialize: -> 15 | 16 | 17 | class Stores extends Backbone.Collection 18 | url: "/api/stores" 19 | model: Store 20 | 21 | 22 | class StoresView extends Backbone.View 23 | el: $ '.stores' 24 | 25 | events: 26 | 'click .btn-add-store': 'onAddStoreClicked' 27 | 28 | initialize: -> 29 | _.bindAll @ 30 | 31 | onAddStoreClicked: (e) -> 32 | e.preventDefault() 33 | console.log 'onAddStoreClicked' 34 | 35 | 36 | class DashboardRouter extends Backbone.Router 37 | initialize: -> 38 | @storeView = new StoresView 39 | 40 | $ -> 41 | window.app = new DashboardRouter 42 | -------------------------------------------------------------------------------- /overholt/frontend/static/coffee/utils.coffee: -------------------------------------------------------------------------------- 1 | # utils.coffee 2 | -------------------------------------------------------------------------------- /overholt/frontend/static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /overholt/frontend/static/css/overholt.css: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------- 2 | LESS Elements 0.9 3 | --------------------------------------------------- 4 | A set of useful LESS mixins 5 | More info at: http://lesselements.com 6 | ---------------------------------------------------*/ 7 | -------------------------------------------------------------------------------- /overholt/frontend/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattupstate/overholt/e03209f7a059d165a9154355d090738af3159028/overholt/frontend/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /overholt/frontend/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattupstate/overholt/e03209f7a059d165a9154355d090738af3159028/overholt/frontend/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /overholt/frontend/static/js/main.js: -------------------------------------------------------------------------------- 1 | var DashboardRouter, Store, Stores, StoresView, 2 | __hasProp = {}.hasOwnProperty, 3 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 4 | 5 | Store = (function(_super) { 6 | 7 | __extends(Store, _super); 8 | 9 | function Store() { 10 | return Store.__super__.constructor.apply(this, arguments); 11 | } 12 | 13 | Store.prototype.urlRoot = "/api/stores"; 14 | 15 | Store.prototype.defaults = { 16 | id: null, 17 | name: "", 18 | address: "", 19 | city: "", 20 | state: "", 21 | zip_code: "" 22 | }; 23 | 24 | Store.prototype.initialize = function() {}; 25 | 26 | return Store; 27 | 28 | })(Backbone.Model); 29 | 30 | Stores = (function(_super) { 31 | 32 | __extends(Stores, _super); 33 | 34 | function Stores() { 35 | return Stores.__super__.constructor.apply(this, arguments); 36 | } 37 | 38 | Stores.prototype.url = "/api/stores"; 39 | 40 | Stores.prototype.model = Store; 41 | 42 | return Stores; 43 | 44 | })(Backbone.Collection); 45 | 46 | StoresView = (function(_super) { 47 | 48 | __extends(StoresView, _super); 49 | 50 | function StoresView() { 51 | return StoresView.__super__.constructor.apply(this, arguments); 52 | } 53 | 54 | StoresView.prototype.el = $('.stores'); 55 | 56 | StoresView.prototype.events = { 57 | 'click .btn-add-store': 'onAddStoreClicked' 58 | }; 59 | 60 | StoresView.prototype.initialize = function() { 61 | return _.bindAll(this); 62 | }; 63 | 64 | StoresView.prototype.onAddStoreClicked = function(e) { 65 | e.preventDefault(); 66 | return console.log('onAddStoreClicked'); 67 | }; 68 | 69 | return StoresView; 70 | 71 | })(Backbone.View); 72 | 73 | DashboardRouter = (function(_super) { 74 | 75 | __extends(DashboardRouter, _super); 76 | 77 | function DashboardRouter() { 78 | return DashboardRouter.__super__.constructor.apply(this, arguments); 79 | } 80 | 81 | DashboardRouter.prototype.initialize = function() { 82 | return this.storeView = new StoresView; 83 | }; 84 | 85 | return DashboardRouter; 86 | 87 | })(Backbone.Router); 88 | 89 | $(function() { 90 | return window.app = new DashboardRouter; 91 | }); 92 | -------------------------------------------------------------------------------- /overholt/frontend/static/js/vendor/backbone-1.0.0.min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.9.10 2 | 3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. 4 | // Backbone may be freely distributed under the MIT license. 5 | // For all details and documentation: 6 | // http://backbonejs.org 7 | (function(){var n=this,B=n.Backbone,h=[],C=h.push,u=h.slice,D=h.splice,g;g="undefined"!==typeof exports?exports:n.Backbone={};g.VERSION="0.9.10";var f=n._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=n.jQuery||n.Zepto||n.ender;g.noConflict=function(){n.Backbone=B;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var v=/\s+/,q=function(a,b,c,d){if(!c)return!0;if("object"===typeof c)for(var e in c)a[b].apply(a,[e,c[e]].concat(d));else if(v.test(c)){c=c.split(v);e=0;for(var f=c.length;e< 8 | f;e++)a[b].apply(a,[c[e]].concat(d))}else return!0},w=function(a,b){var c,d=-1,e=a.length;switch(b.length){case 0:for(;++d=b);this.root=("/"+this.root+"/").replace(I,"/");b&&this._wantsHashChange&&(this.iframe=g.$('