├── .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.$('').hide().appendTo("body")[0].contentWindow,this.navigate(a));if(this._hasPushState)g.$(window).on("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)g.$(window).on("hashchange",this.checkUrl);
33 | else this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(z,""),this.history.replaceState({},document.title,
34 | this.root+this.fragment+a.search));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},
35 | loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};a=this.getFragment(a||"");if(this.fragment!==a){this.fragment=a;var c=this.root+a;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,c);else if(this._wantsHashChange)this._updateHash(this.location,a,b.replace),this.iframe&&a!==this.getFragment(this.getHash(this.iframe))&&
36 | (b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,a,b.replace));else return this.location.assign(c);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?(c=a.href.replace(/(javascript:|#).*$/,""),a.replace(c+"#"+b)):a.hash="#"+b}});g.history=new m;var A=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},L=/^(\S+)\s*(.*)$/,M="model collection el id attributes className tagName events".split(" ");
37 | f.extend(A.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=f.result(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);
38 | if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(L),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);if(""===d)this.$el.on(e,c);else this.$el.on(e,d,c)}}},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},f.result(this,"options"),a));f.extend(this,f.pick(a,M));this.options=a},_ensureElement:function(){if(this.el)this.setElement(f.result(this,"el"),!1);else{var a=f.extend({},f.result(this,"attributes"));
39 | this.id&&(a.id=f.result(this,"id"));this.className&&(a["class"]=f.result(this,"className"));a=g.$("<"+f.result(this,"tagName")+">").attr(a);this.setElement(a,!1)}}});var N={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=N[a];f.defaults(c||(c={}),{emulateHTTP:g.emulateHTTP,emulateJSON:g.emulateJSON});var e={type:d,dataType:"json"};c.url||(e.url=f.result(b,"url")||x());if(null==c.data&&b&&("create"===a||"update"===a||"patch"===a))e.contentType="application/json",
40 | e.data=JSON.stringify(c.attrs||b.toJSON(c));c.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(c.emulateHTTP&&("PUT"===d||"DELETE"===d||"PATCH"===d)){e.type="POST";c.emulateJSON&&(e.data._method=d);var h=c.beforeSend;c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d);if(h)return h.apply(this,arguments)}}"GET"!==e.type&&!c.emulateJSON&&(e.processData=!1);var m=c.success;c.success=function(a){m&&m(b,a,c);b.trigger("sync",b,a,c)};
41 | var j=c.error;c.error=function(a){j&&j(b,a,c);b.trigger("error",b,a,c)};a=c.xhr=g.ajax(f.extend(e,c));b.trigger("request",b,a,c);return a};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};r.extend=s.extend=y.extend=A.extend=m.extend=function(a,b){var c=this,d;d=a&&f.has(a,"constructor")?a.constructor:function(){return c.apply(this,arguments)};f.extend(d,c,b);var e=function(){this.constructor=d};e.prototype=c.prototype;d.prototype=new e;a&&f.extend(d.prototype,a);d.__super__=c.prototype;return d};
42 | var x=function(){throw Error('A "url" property or function must be specified');}}).call(this);
43 |
--------------------------------------------------------------------------------
/overholt/frontend/static/js/vendor/bootstrap-2.3.3.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
7 |
--------------------------------------------------------------------------------
/overholt/frontend/static/js/vendor/underscore-1.4.4.min.js:
--------------------------------------------------------------------------------
1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
--------------------------------------------------------------------------------
/overholt/frontend/static/less/elements.less:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------
2 | LESS Elements 0.9
3 | ---------------------------------------------------
4 | A set of useful LESS mixins
5 | More info at: http://lesselements.com
6 | ---------------------------------------------------*/
7 |
8 | .gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {
9 | background: @color;
10 | background: -webkit-gradient(linear,
11 | left bottom,
12 | left top,
13 | color-stop(0, @start),
14 | color-stop(1, @stop));
15 | background: -ms-linear-gradient(bottom,
16 | @start,
17 | @stop);
18 | background: -moz-linear-gradient(center bottom,
19 | @start 0%,
20 | @stop 100%);
21 | background: -o-linear-gradient(@stop,
22 | @start);
23 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@stop,@start));
24 | }
25 | .bw-gradient(@color: #F5F5F5, @start: 0, @stop: 255) {
26 | background: @color;
27 | background: -webkit-gradient(linear,
28 | left bottom,
29 | left top,
30 | color-stop(0, rgb(@start,@start,@start)),
31 | color-stop(1, rgb(@stop,@stop,@stop)));
32 | background: -ms-linear-gradient(bottom,
33 | rgb(@start,@start,@start) 0%,
34 | rgb(@stop,@stop,@stop) 100%);
35 | background: -moz-linear-gradient(center bottom,
36 | rgb(@start,@start,@start) 0%,
37 | rgb(@stop,@stop,@stop) 100%);
38 | background: -o-linear-gradient(rgb(@stop,@stop,@stop),
39 | rgb(@start,@start,@start));
40 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",rgb(@stop,@stop,@stop),rgb(@start,@start,@start)));
41 | }
42 | .bordered(@top-color: #EEE, @right-color: #EEE, @bottom-color: #EEE, @left-color: #EEE) {
43 | border-top: solid 1px @top-color;
44 | border-left: solid 1px @left-color;
45 | border-right: solid 1px @right-color;
46 | border-bottom: solid 1px @bottom-color;
47 | }
48 | .drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) {
49 | -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
50 | -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
51 | box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha);
52 | }
53 | .rounded(@radius: 2px) {
54 | -webkit-border-radius: @radius;
55 | -moz-border-radius: @radius;
56 | border-radius: @radius;
57 | }
58 | .border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) {
59 | -webkit-border-top-right-radius: @topright;
60 | -webkit-border-bottom-right-radius: @bottomright;
61 | -webkit-border-bottom-left-radius: @bottomleft;
62 | -webkit-border-top-left-radius: @topleft;
63 | -moz-border-radius-topright: @topright;
64 | -moz-border-radius-bottomright: @bottomright;
65 | -moz-border-radius-bottomleft: @bottomleft;
66 | -moz-border-radius-topleft: @topleft;
67 | border-top-right-radius: @topright;
68 | border-bottom-right-radius: @bottomright;
69 | border-bottom-left-radius: @bottomleft;
70 | border-top-left-radius: @topleft;
71 | .background-clip(padding-box);
72 | }
73 | .opacity(@opacity: 0.5) {
74 | -moz-opacity: @opacity;
75 | -khtml-opacity: @opacity;
76 | -webkit-opacity: @opacity;
77 | opacity: @opacity;
78 | @opperc: @opacity * 100;
79 | -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{opperc})";
80 | filter: ~"alpha(opacity=@{opperc})";
81 | }
82 | .transition-duration(@duration: 0.2s) {
83 | -moz-transition-duration: @duration;
84 | -webkit-transition-duration: @duration;
85 | -o-transition-duration: @duration;
86 | transition-duration: @duration;
87 | }
88 | .transform(...) {
89 | -webkit-transform: @arguments;
90 | -moz-transform: @arguments;
91 | -o-transform: @arguments;
92 | -ms-transform: @arguments;
93 | transform: @arguments;
94 | }
95 | .rotation(@deg:5deg){
96 | .transform(rotate(@deg));
97 | }
98 | .scale(@ratio:1.5){
99 | .transform(scale(@ratio));
100 | }
101 | .transition(@duration:0.2s, @ease:ease-out) {
102 | -webkit-transition: all @duration @ease;
103 | -moz-transition: all @duration @ease;
104 | -o-transition: all @duration @ease;
105 | transition: all @duration @ease;
106 | }
107 | .inner-shadow(@horizontal:0, @vertical:1px, @blur:2px, @alpha: 0.4) {
108 | -webkit-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
109 | -moz-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
110 | box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha);
111 | }
112 | .box-shadow(@arguments) {
113 | -webkit-box-shadow: @arguments;
114 | -moz-box-shadow: @arguments;
115 | box-shadow: @arguments;
116 | }
117 | .box-sizing(@sizing: border-box) {
118 | -ms-box-sizing: @sizing;
119 | -moz-box-sizing: @sizing;
120 | -webkit-box-sizing: @sizing;
121 | box-sizing: @sizing;
122 | }
123 | .user-select(@argument: none) {
124 | -webkit-user-select: @argument;
125 | -moz-user-select: @argument;
126 | -ms-user-select: @argument;
127 | user-select: @argument;
128 | }
129 | .columns(@colwidth: 250px, @colcount: 0, @colgap: 50px, @columnRuleColor: #EEE, @columnRuleStyle: solid, @columnRuleWidth: 1px) {
130 | -moz-column-width: @colwidth;
131 | -moz-column-count: @colcount;
132 | -moz-column-gap: @colgap;
133 | -moz-column-rule-color: @columnRuleColor;
134 | -moz-column-rule-style: @columnRuleStyle;
135 | -moz-column-rule-width: @columnRuleWidth;
136 | -webkit-column-width: @colwidth;
137 | -webkit-column-count: @colcount;
138 | -webkit-column-gap: @colgap;
139 | -webkit-column-rule-color: @columnRuleColor;
140 | -webkit-column-rule-style: @columnRuleStyle;
141 | -webkit-column-rule-width: @columnRuleWidth;
142 | column-width: @colwidth;
143 | column-count: @colcount;
144 | column-gap: @colgap;
145 | column-rule-color: @columnRuleColor;
146 | column-rule-style: @columnRuleStyle;
147 | column-rule-width: @columnRuleWidth;
148 | }
149 | .translate(@x:0, @y:0) {
150 | .transform(translate(@x, @y));
151 | }
152 | .background-clip(@argument: padding-box) {
153 | -moz-background-clip: @argument;
154 | -webkit-background-clip: @argument;
155 | background-clip: @argument;
156 | }
--------------------------------------------------------------------------------
/overholt/frontend/static/less/overholt.less:
--------------------------------------------------------------------------------
1 | @import "elements.less";
2 |
3 | // body {
4 | // padding-top: 60px;
5 | // }
--------------------------------------------------------------------------------
/overholt/frontend/templates/dashboard.html:
--------------------------------------------------------------------------------
1 | {% extends "layouts/boilerplate.html" %}
2 |
3 | {% block body %}
4 | Dashboard
5 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/errors/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 404 Not Found
4 |
5 |
6 | 404 Not Found
7 |
8 |
9 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/errors/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 500 Internal Server Error
4 |
5 |
6 | 500 Internal Server Error
7 |
8 |
9 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/layouts/_menu.html:
--------------------------------------------------------------------------------
1 |
2 | {% if current_user.is_authenticated() -%}
3 | - Logout
4 | {%- else -%}
5 | - Login
6 | {%- endif %}
7 |
8 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/layouts/_messages.html:
--------------------------------------------------------------------------------
1 | {% with messages = get_flashed_messages(with_categories=true) %}
2 | {% if messages %}
3 |
4 |
5 | {% for category, message in messages %}
6 |
7 |
8 | {{ message }}
9 |
10 | {% endfor %}
11 |
12 |
13 | {% endif %}
14 | {% endwith %}
--------------------------------------------------------------------------------
/overholt/frontend/templates/layouts/boilerplate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% block page_title %}Overholt{% endblock %}
4 | {% assets "css_all" -%}
5 |
6 | {%- endassets %}
7 |
8 |
9 |
10 | {% include "layouts/_menu.html" %}
11 | {% block flash_messages %}
12 | {% include "layouts/_messages.html" %}
13 | {% endblock %}
14 | {% block body %}
15 | Body
16 | {% endblock %}
17 |
18 | {% assets "js_vendor" -%}
19 |
20 | {% endassets %}
21 | {% assets "js_main" -%}
22 |
23 | {% endassets %}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/macros/forms.html:
--------------------------------------------------------------------------------
1 | {% macro render_field_with_errors(field) %}
2 |
3 | {{ field.label }} {{ field(**kwargs)|safe }}
4 | {% if field.errors %}
5 |
6 | {% for error in field.errors %}
7 | - {{ error }}
8 | {% endfor %}
9 |
10 | {% endif %}
11 |
12 | {% endmacro %}
13 |
14 | {% macro render_field(field) %}
15 | {{ field(**kwargs)|safe }}
16 | {% endmacro %}
17 |
--------------------------------------------------------------------------------
/overholt/frontend/templates/security/login_user.html:
--------------------------------------------------------------------------------
1 | {% extends "layouts/boilerplate.html" %}
2 |
3 | {% block body %}
4 | {% from "security/_macros.html" import render_field_with_errors, render_field %}
5 | Login
6 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/overholt/helpers.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.helpers
4 | ~~~~~~~~~~~~~~~~
5 |
6 | overholt helpers module
7 | """
8 |
9 | import pkgutil
10 | import importlib
11 |
12 | from flask import Blueprint
13 | from flask.json import JSONEncoder as BaseJSONEncoder
14 |
15 |
16 | def register_blueprints(app, package_name, package_path):
17 | """Register all Blueprint instances on the specified Flask application found
18 | in all modules for the specified package.
19 |
20 | :param app: the Flask application
21 | :param package_name: the package name
22 | :param package_path: the package path
23 | """
24 | rv = []
25 | for _, name, _ in pkgutil.iter_modules(package_path):
26 | m = importlib.import_module('%s.%s' % (package_name, name))
27 | for item in dir(m):
28 | item = getattr(m, item)
29 | if isinstance(item, Blueprint):
30 | app.register_blueprint(item)
31 | rv.append(item)
32 | return rv
33 |
34 |
35 | class JSONEncoder(BaseJSONEncoder):
36 | """Custom :class:`JSONEncoder` which respects objects that include the
37 | :class:`JsonSerializer` mixin.
38 | """
39 | def default(self, obj):
40 | if isinstance(obj, JsonSerializer):
41 | return obj.to_json()
42 | return super(JSONEncoder, self).default(obj)
43 |
44 |
45 | class JsonSerializer(object):
46 | """A mixin that can be used to mark a SQLAlchemy model class which
47 | implements a :func:`to_json` method. The :func:`to_json` method is used
48 | in conjuction with the custom :class:`JSONEncoder` class. By default this
49 | mixin will assume all properties of the SQLAlchemy model are to be visible
50 | in the JSON output. Extend this class to customize which properties are
51 | public, hidden or modified before being being passed to the JSON serializer.
52 | """
53 |
54 | __json_public__ = None
55 | __json_hidden__ = None
56 | __json_modifiers__ = None
57 |
58 | def get_field_names(self):
59 | for p in self.__mapper__.iterate_properties:
60 | yield p.key
61 |
62 | def to_json(self):
63 | field_names = self.get_field_names()
64 |
65 | public = self.__json_public__ or field_names
66 | hidden = self.__json_hidden__ or []
67 | modifiers = self.__json_modifiers__ or dict()
68 |
69 | rv = dict()
70 | for key in public:
71 | rv[key] = getattr(self, key)
72 | for key, modifier in modifiers.items():
73 | value = getattr(self, key)
74 | rv[key] = modifier(value, self)
75 | for key in hidden:
76 | rv.pop(key, None)
77 | return rv
78 |
--------------------------------------------------------------------------------
/overholt/manage/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.manage
4 | ~~~~~~~~~~~~~~~
5 |
6 | overholt manager package
7 | """
8 |
9 | from .products import *
10 | from .stores import *
11 | from .users import *
12 |
--------------------------------------------------------------------------------
/overholt/manage/products.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.manage.products
4 | ~~~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | product management commands
7 | """
8 |
--------------------------------------------------------------------------------
/overholt/manage/stores.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.manage.stores
4 | ~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | store management commands
7 | """
8 |
--------------------------------------------------------------------------------
/overholt/manage/users.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.manage.users
4 | ~~~~~~~~~~~~~~~~~~~~~
5 |
6 | user management commands
7 | """
8 |
9 | from flask import current_app
10 | from flask.ext.script import Command, prompt, prompt_pass
11 | from flask_security.forms import RegisterForm
12 | from flask_security.registerable import register_user
13 | from werkzeug.datastructures import MultiDict
14 |
15 | from ..services import users
16 |
17 |
18 | class CreateUserCommand(Command):
19 | """Create a user"""
20 |
21 | def run(self):
22 | email = prompt('Email')
23 | password = prompt_pass('Password')
24 | password_confirm = prompt_pass('Confirm Password')
25 | data = MultiDict(dict(email=email, password=password, password_confirm=password_confirm))
26 | form = RegisterForm(data, csrf_enabled=False)
27 | if form.validate():
28 | user = register_user(email=email, password=password)
29 | print '\nUser created successfully'
30 | print 'User(id=%s email=%s)' % (user.id, user.email)
31 | return
32 | print '\nError creating user:'
33 | for errors in form.errors.values():
34 | print '\n'.join(errors)
35 |
36 |
37 | class DeleteUserCommand(Command):
38 | """Delete a user"""
39 |
40 | def run(self):
41 | email = prompt('Email')
42 | user = users.first(email=email)
43 | if not user:
44 | print 'Invalid user'
45 | return
46 | users.delete(user)
47 | print 'User deleted successfully'
48 |
49 |
50 | class ListUsersCommand(Command):
51 | """List all users"""
52 |
53 | def run(self):
54 | for u in users.all():
55 | print 'User(id=%s email=%s)' % (u.id, u.email)
56 |
--------------------------------------------------------------------------------
/overholt/middleware.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.middleware
4 | ~~~~~~~~~~~~~~~~~~~
5 |
6 | middleware module
7 | """
8 |
9 | from werkzeug import url_decode
10 |
11 |
12 | class HTTPMethodOverrideMiddleware(object):
13 | """The HTTPMethodOverrideMiddleware middleware implements the hidden HTTP
14 | method technique. Not all web browsers support every HTTP method, such as
15 | DELETE and PUT. This middleware class allows clients to provide a method
16 | override parameter via an HTTP header value or a querystring parameter. This
17 | middleware will look for the header paramter first followed by the
18 | querystring. The default HTTP header name is `X-HTTP-METHOD-OVERRIDE` and
19 | the default querystring parameter name is `__METHOD__`. These can be changed
20 | via the constructor parameters `header_name` and `querystring_param`
21 | respectively. Additionally, a list of allowed HTTP methods may be specified
22 | via the `allowed_methods` constructor parameter. The default allowed methods
23 | are GET, HEAD, POST, DELETE, PUT, PATCH, and OPTIONS.
24 | """
25 |
26 | bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE'])
27 |
28 | def __init__(self, app, header_name=None,
29 | querystring_param=None, allowed_methods=None):
30 | header_name = header_name or 'X-HTTP-METHOD-OVERRIDE'
31 |
32 | self.app = app
33 | self.header_name = 'HTTP_' + header_name.replace('-', '_')
34 | self.querystring_param = querystring_param or '__METHOD__'
35 | self.allowed_methods = frozenset(allowed_methods or
36 | ['GET', 'HEAD', 'POST', 'DELETE', 'PUT', 'PATCH', 'OPTIONS'])
37 |
38 | def _get_from_querystring(self, environ):
39 | if self.querystring_param in environ.get('QUERY_STRING', ''):
40 | args = url_decode(environ['QUERY_STRING'])
41 | return args.get(self.querystring_param)
42 | return None
43 |
44 | def _get_method_override(self, environ):
45 | return environ.get(self.header_name, None) or \
46 | self._get_from_querystring(environ) or ''
47 |
48 | def __call__(self, environ, start_response):
49 | method = self._get_method_override(environ).upper()
50 |
51 | if method in self.allowed_methods:
52 | method = method.encode('ascii', 'replace')
53 | environ['REQUEST_METHOD'] = method
54 |
55 | if method in self.bodyless_methods:
56 | environ['CONTENT_LENGTH'] = '0'
57 |
58 | return self.app(environ, start_response)
59 |
--------------------------------------------------------------------------------
/overholt/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.models
4 | ~~~~~~~~~~~~~~~
5 |
6 | consolodated models module
7 | """
8 |
9 | from .products.models import *
10 | from .stores.models import *
11 | from .users.models import *
12 |
--------------------------------------------------------------------------------
/overholt/products/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.products
4 | ~~~~~~~~~~~~~~~~~
5 |
6 | overholt products package
7 | """
8 |
9 | from ..core import Service
10 | from .models import Product, Category
11 |
12 |
13 | class CategoryService(Service):
14 | __model__ = Category
15 |
16 |
17 | class ProductsService(Service):
18 | __model__ = Product
19 |
20 | def __init__(self, *args, **kwargs):
21 | super(ProductsService, self).__init__(*args, **kwargs)
22 | self.categories = CategoryService()
23 |
24 | def _preprocess_params(self, kwargs):
25 | kwargs = super(ProductsService, self)._preprocess_params(kwargs)
26 | categories = kwargs.get('categories', [])
27 | if categories and all(isinstance(c, int) for c in categories):
28 | kwargs['categories'] = self.categories.get_all(*categories)
29 | return kwargs
30 |
--------------------------------------------------------------------------------
/overholt/products/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.products.forms
4 | ~~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | Product forms
7 | """
8 |
9 | from flask_wtf import Form, TextField, SelectMultipleField, Required, \
10 | Optional
11 |
12 | from ..services import products
13 |
14 | __all__ = ['NewProductForm', 'UpdateProductForm']
15 |
16 |
17 | class ProductFormMixin(object):
18 |
19 | def __init__(self, *args, **kwargs):
20 | super(ProductFormMixin, self).__init__(*args, **kwargs)
21 | self.categories.choices = [(c.id, c.name) for c in products.categories.all()]
22 |
23 |
24 | class NewProductForm(ProductFormMixin, Form):
25 | name = TextField('Name', validators=[Required()])
26 | categories = SelectMultipleField(
27 | 'Categories', coerce=int, validators=[Required()])
28 |
29 |
30 | class UpdateProductForm(ProductFormMixin, Form):
31 | name = TextField('Name', validators=[Optional()])
32 | categories = SelectMultipleField(
33 | 'Categories', coerce=int, validators=[Optional()])
34 |
--------------------------------------------------------------------------------
/overholt/products/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.products.models
4 | ~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | Product models
7 | """
8 |
9 | from ..core import db
10 | from ..helpers import JsonSerializer
11 |
12 |
13 | products_categories = db.Table(
14 | 'products_categories',
15 | db.Column('product_id', db.Integer(), db.ForeignKey('products.id')),
16 | db.Column('category_id', db.Integer(), db.ForeignKey('categories.id')))
17 |
18 |
19 | class CategoryJsonSerializer(JsonSerializer):
20 | __json_hidden__ = ['products']
21 |
22 |
23 | class Category(CategoryJsonSerializer, db.Model):
24 | __tablename__ = 'categories'
25 |
26 | id = db.Column(db.Integer(), primary_key=True)
27 | name = db.Column(db.String(255))
28 | description = db.Column(db.String(255))
29 |
30 |
31 | class ProductJsonSerializer(JsonSerializer):
32 | __json_hidden__ = ['stores']
33 |
34 |
35 | class Product(ProductJsonSerializer, db.Model):
36 | __tablename__ = 'products'
37 |
38 | id = db.Column(db.Integer(), primary_key=True)
39 | name = db.Column(db.String(255))
40 |
41 | categories = db.relationship('Category',
42 | secondary=products_categories,
43 | backref=db.backref('products', lazy='joined'))
44 |
--------------------------------------------------------------------------------
/overholt/services.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.services
4 | ~~~~~~~~~~~~~~~~~
5 |
6 | services module
7 | """
8 |
9 | from .products import ProductsService
10 | from .stores import StoresService
11 | from .users import UsersService
12 |
13 | #: An instance of the :class:`ProductsService` class
14 | products = ProductsService()
15 |
16 | #: An instance of the :class:`StoresService` class
17 | stores = StoresService()
18 |
19 | #: An instance of the :class:`UsersService` class
20 | users = UsersService()
21 |
--------------------------------------------------------------------------------
/overholt/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.settings
4 | ~~~~~~~~~~~~~~~
5 |
6 | overholt settings module
7 | """
8 |
9 | DEBUG = True
10 | SECRET_KEY = 'super-secret-key'
11 |
12 | SQLALCHEMY_DATABASE_URI = 'mysql://root@33.33.33.10:3306/overholt'
13 | CELERY_BROKER_URL = 'redis://33.33.33.10:6379/0'
14 |
15 | MAIL_DEFAULT_SENDER = 'info@overholt.com'
16 | MAIL_SERVER = 'smtp.postmarkapp.com'
17 | MAIL_PORT = 25
18 | MAIL_USE_TLS = True
19 | MAIL_USERNAME = 'username'
20 | MAIL_PASSWORD = 'password'
21 |
22 | SECURITY_POST_LOGIN_VIEW = '/'
23 | SECURITY_PASSWORD_HASH = 'plaintext'
24 | SECURITY_PASSWORD_SALT = 'password_salt'
25 | SECURITY_REMEMBER_SALT = 'remember_salt'
26 | SECURITY_RESET_SALT = 'reset_salt'
27 | SECURITY_RESET_WITHIN = '5 days'
28 | SECURITY_CONFIRM_WITHIN = '5 days'
29 | SECURITY_SEND_REGISTER_EMAIL = False
30 |
--------------------------------------------------------------------------------
/overholt/stores/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.stores
4 | ~~~~~~~~~~~~~~~
5 |
6 | overholt stores package
7 | """
8 |
9 | from ..core import Service, OverholtError
10 | from .models import Store
11 |
12 |
13 | class StoresService(Service):
14 | __model__ = Store
15 |
16 | def add_manager(self, store, user):
17 | if user in store.managers:
18 | raise OverholtError(u'Manager exists')
19 | store.managers.append(user)
20 | return self.save(store), user
21 |
22 | def remove_manager(self, store, user):
23 | if user not in store.managers:
24 | raise OverholtError(u'Invalid manager')
25 | store.managers.remove(user)
26 | return self.save(store), user
27 |
28 | def add_product(self, store, product):
29 | if product in store.products:
30 | raise OverholtError(u'Product exists')
31 | store.products.append(product)
32 | return self.save(store)
33 |
34 | def remove_product(self, store, product):
35 | if product not in store.products:
36 | raise OverholtError(u'Invalid product')
37 | store.products.remove(product)
38 | return self.save(store)
39 |
--------------------------------------------------------------------------------
/overholt/stores/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.stores.forms
4 | ~~~~~~~~~~~~~~~~~~~~~
5 |
6 | Store forms
7 | """
8 |
9 | from flask_wtf import Form, TextField, Required, Optional
10 |
11 | __all__ = ['NewStoreForm', 'UpdateStoreForm']
12 |
13 |
14 | class NewStoreForm(Form):
15 | name = TextField('Name', validators=[Required()])
16 | address = TextField('Address', validators=[Required()])
17 | city = TextField('City', validators=[Required()])
18 | state = TextField('State', validators=[Required()])
19 | zip_code = TextField('Zip Code', validators=[Required()])
20 |
21 |
22 | class UpdateStoreForm(Form):
23 | name = TextField('Name', validators=[Optional()])
24 | address = TextField('Address', validators=[Optional()])
25 | city = TextField('City', validators=[Optional()])
26 | state = TextField('State', validators=[Optional()])
27 | zip_code = TextField('Zip Code', validators=[Optional()])
28 |
--------------------------------------------------------------------------------
/overholt/stores/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.stores.models
4 | ~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | Store models
7 | """
8 |
9 | from ..core import db
10 | from ..helpers import JsonSerializer
11 |
12 |
13 | stores_products = db.Table(
14 | 'stores_products',
15 | db.Column('product_id', db.Integer(), db.ForeignKey('products.id')),
16 | db.Column('store_id', db.Integer(), db.ForeignKey('stores.id')))
17 |
18 |
19 | stores_managers = db.Table(
20 | 'stores_managers',
21 | db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
22 | db.Column('store_id', db.Integer(), db.ForeignKey('stores.id')))
23 |
24 |
25 | class StoreJsonSerializer(JsonSerializer):
26 | pass
27 |
28 |
29 | class Store(StoreJsonSerializer, db.Model):
30 | __tablename__ = 'stores'
31 |
32 | id = db.Column(db.Integer(), primary_key=True)
33 | name = db.Column(db.String(255))
34 | address = db.Column(db.String(255))
35 | city = db.Column(db.String(255))
36 | state = db.Column(db.String(255))
37 | zip_code = db.Column(db.String(255))
38 | manager_id = db.Column(db.ForeignKey('users.id'))
39 |
40 | managers = db.relationship('User', secondary=stores_managers,
41 | backref=db.backref('stores', lazy='dynamic'))
42 |
43 | products = db.relationship('Product', secondary=stores_products,
44 | backref=db.backref('stores', lazy='dynamic'))
45 |
--------------------------------------------------------------------------------
/overholt/tasks.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.tasks
4 | ~~~~~~~~~~~~~~
5 |
6 | overholt tasks module
7 | """
8 |
9 | from .core import mail
10 | from .factory import create_celery_app
11 |
12 | celery = create_celery_app()
13 |
14 |
15 | @celery.task
16 | def send_manager_added_email(*recipients):
17 | print 'sending manager added email...'
18 |
19 |
20 | @celery.task
21 | def send_manager_removed_email(*recipients):
22 | print 'sending manager removed email...'
23 |
--------------------------------------------------------------------------------
/overholt/users/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.users
4 | ~~~~~~~~~~~~~~
5 |
6 | overholt users package
7 | """
8 |
9 | from ..core import Service
10 | from .models import User
11 |
12 |
13 | class UsersService(Service):
14 | __model__ = User
15 |
--------------------------------------------------------------------------------
/overholt/users/forms.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.users.forms
4 | ~~~~~~~~~~~~~~~~~~~~
5 |
6 | User forms
7 | """
8 |
--------------------------------------------------------------------------------
/overholt/users/models.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | overholt.users.models
4 | ~~~~~~~~~~~~~~~~~~~~~
5 |
6 | User models
7 | """
8 |
9 | from flask_security import UserMixin, RoleMixin
10 |
11 | from ..core import db
12 | from ..helpers import JsonSerializer
13 |
14 |
15 | roles_users = db.Table(
16 | 'roles_users',
17 | db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
18 | db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))
19 |
20 |
21 | class Role(RoleMixin, db.Model):
22 | __tablename__ = 'roles'
23 |
24 | id = db.Column(db.Integer(), primary_key=True)
25 | name = db.Column(db.String(80), unique=True)
26 | description = db.Column(db.String(255))
27 |
28 | def __eq__(self, other):
29 | return (self.name == other or
30 | self.name == getattr(other, 'name', None))
31 |
32 | def __ne__(self, other):
33 | return (self.name != other and
34 | self.name != getattr(other, 'name', None))
35 |
36 |
37 | class UserJsonSerializer(JsonSerializer):
38 | __json_public__ = ['id', 'email']
39 |
40 |
41 | class User(UserJsonSerializer, UserMixin, db.Model):
42 | __tablename__ = 'users'
43 |
44 | id = db.Column(db.Integer, primary_key=True)
45 | email = db.Column(db.String(255), unique=True)
46 | password = db.Column(db.String(120))
47 | active = db.Column(db.Boolean())
48 | confirmed_at = db.Column(db.DateTime())
49 | last_login_at = db.Column(db.DateTime())
50 | current_login_at = db.Column(db.DateTime())
51 | last_login_ip = db.Column(db.String(100))
52 | current_login_ip = db.Column(db.String(100))
53 | login_count = db.Column(db.Integer)
54 | registered_at = db.Column(db.DateTime())
55 |
56 | roles = db.relationship('Role', secondary=roles_users,
57 | backref=db.backref('users', lazy='dynamic'))
58 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==0.10.1
2 | Flask-Assets==0.8
3 | Flask-Login==0.2.3
4 | Flask-Mail==0.7.3
5 | Flask-Principal==0.3.3
6 | Flask-SQLAlchemy==0.16
7 | Flask-Script==0.5.3
8 | Flask-Security==1.6.4
9 | Flask-WTF==0.8
10 | Jinja2==2.7
11 | Mako==0.8.1
12 | MarkupSafe==0.18
13 | MySQL-python==1.2.4
14 | Pygments==1.6
15 | SQLAlchemy==0.8.1
16 | Sphinx==1.2b1
17 | WTForms==1.0.4
18 | Werkzeug==0.9.1
19 | alembic==0.5.0
20 | amqp==1.0.12
21 | anyjson==0.3.3
22 | billiard==2.7.3.30
23 | blinker==1.2
24 | celery==3.0.20
25 | cssmin==0.1.4
26 | docutils==0.10
27 | factory-boy==1.3.0
28 | itsdangerous==0.21
29 | jsmin==2.0.3
30 | kombu==2.5.12
31 | mock==1.0.1
32 | nose==1.3.0
33 | passlib==1.6.1
34 | python-dateutil==2.1
35 | redis==2.7.6
36 | simplejson==3.3.0
37 | six==1.3.0
38 | sphinxcontrib-httpdomain==1.1.8
39 | webassets==0.8
40 | wsgiref==0.1.2
41 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests
4 | ~~~~~
5 |
6 | tests package
7 | """
8 |
9 | from unittest import TestCase
10 |
11 | from overholt.core import db
12 |
13 | from .factories import UserFactory
14 | from .utils import FlaskTestCaseMixin
15 |
16 |
17 | class OverholtTestCase(TestCase):
18 | pass
19 |
20 |
21 | class OverholtAppTestCase(FlaskTestCaseMixin, OverholtTestCase):
22 |
23 | def _create_app(self):
24 | raise NotImplementedError
25 |
26 | def _create_fixtures(self):
27 | self.user = UserFactory()
28 |
29 | def setUp(self):
30 | super(OverholtAppTestCase, self).setUp()
31 | self.app = self._create_app()
32 | self.client = self.app.test_client()
33 | self.app_context = self.app.app_context()
34 | self.app_context.push()
35 | db.create_all()
36 | self._create_fixtures()
37 | self._create_csrf_token()
38 |
39 | def tearDown(self):
40 | super(OverholtAppTestCase, self).tearDown()
41 | db.drop_all()
42 | self.app_context.pop()
43 |
44 | def _login(self, email=None, password=None):
45 | email = email or self.user.email
46 | password = password or 'password'
47 | return self.post('/login', data={'email': email, 'password': password},
48 | follow_redirects=False)
49 |
--------------------------------------------------------------------------------
/tests/api/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.api
4 | ~~~~~~~~~
5 |
6 | api tests package
7 | """
8 |
9 | from overholt.api import create_app
10 |
11 | from .. import OverholtAppTestCase, settings
12 |
13 |
14 | class OverholtApiTestCase(OverholtAppTestCase):
15 |
16 | def _create_app(self):
17 | return create_app(settings, register_security_blueprint=True)
18 |
19 | def setUp(self):
20 | super(OverholtApiTestCase, self).setUp()
21 | self._login()
22 |
--------------------------------------------------------------------------------
/tests/api/product_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.api.product_tests
4 | ~~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | api product tests module
7 | """
8 |
9 | from ..factories import CategoryFactory, ProductFactory
10 | from . import OverholtApiTestCase
11 |
12 |
13 | class ProductApiTestCase(OverholtApiTestCase):
14 |
15 | def _create_fixtures(self):
16 | super(ProductApiTestCase, self)._create_fixtures()
17 | self.category = CategoryFactory()
18 | self.product = ProductFactory(categories=[self.category])
19 |
20 | def test_get_products(self):
21 | r = self.jget('/products')
22 | self.assertOkJson(r)
23 |
24 | def test_get_product(self):
25 | r = self.jget('/products/%s' % self.product.id)
26 | self.assertOkJson(r)
27 |
28 | def test_create_product(self):
29 | r = self.jpost('/products', data={
30 | 'name': 'New Product',
31 | 'categories': [self.category.id]
32 | })
33 | self.assertOkJson(r)
34 |
35 | def test_create_invalid_product(self):
36 | r = self.jpost('/products', data={
37 | 'categories': [self.category.id]
38 | })
39 | self.assertBadJson(r)
40 |
41 | def test_update_product(self):
42 | r = self.jput('/products/%s' % self.product.id, data={
43 | 'name': 'New Product'
44 | })
45 | self.assertOkJson(r)
46 |
47 | def test_delete_product(self):
48 | r = self.jdelete('/products/%s' % self.product.id)
49 | self.assertStatusCode(r, 204)
50 |
--------------------------------------------------------------------------------
/tests/api/store_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.api.store_tests
4 | ~~~~~~~~~~~~~~~~~~~~~
5 |
6 | api store tests module
7 | """
8 |
9 | from ..factories import StoreFactory, ProductFactory
10 | from . import OverholtApiTestCase
11 |
12 |
13 | class StoreApiTestCase(OverholtApiTestCase):
14 |
15 | def _create_fixtures(self):
16 | super(StoreApiTestCase, self)._create_fixtures()
17 | self.product = ProductFactory()
18 | self.store = StoreFactory(products=[self.product])
19 |
20 | def test_get_stores(self):
21 | r = self.jget('/stores')
22 | self.assertOkJson(r)
23 |
24 | def test_get_store(self):
25 | r = self.jget('/stores/%s' % self.store.id)
26 | self.assertOkJson(r)
27 |
28 | def test_create_store(self):
29 | r = self.jpost('/stores', data={
30 | 'name': 'My Store',
31 | 'address': '123 Overholt Drive',
32 | 'city': 'Brooklyn',
33 | 'state': 'New York',
34 | 'zip_code': '12345'
35 | })
36 | self.assertOkJson(r)
37 | self.assertIn('"name": "My Store"', r.data)
38 |
39 | def test_create_invalid_store(self):
40 | r = self.jpost('/stores', data={
41 | 'name': 'My Store'
42 | })
43 | self.assertBadJson(r)
44 | self.assertIn('"errors": {', r.data)
45 |
46 | def test_update_store(self):
47 | r = self.jput('/stores/%s' % self.store.id, data={
48 | 'name': 'My New Store'
49 | })
50 | self.assertOkJson(r)
51 | self.assertIn('"name": "My New Store"', r.data)
52 |
53 | def test_delete_store(self):
54 | r = self.jdelete('/stores/%s' % self.store.id)
55 | self.assertStatusCode(r, 204)
56 |
57 | def test_get_products(self):
58 | r = self.jget('/stores/%s/products' % self.store.id)
59 | self.assertOkJson(r)
60 |
61 | def test_add_product(self):
62 | p = ProductFactory()
63 | e = '/stores/%s/products/%s' % (self.store.id, p.id)
64 | r = self.jput(e)
65 | self.assertOkJson(r)
66 |
67 | def test_remove_product(self):
68 | e = '/stores/%s/products/%s' % (self.store.id, self.product.id)
69 | r = self.jdelete(e)
70 | self.assertStatusCode(r, 204)
71 |
72 | def test_add_manager(self):
73 | e = '/stores/%s/managers/%s' % (self.store.id, self.user.id)
74 | r = self.jput(e)
75 | self.assertOkJson(r)
76 |
77 | def test_add_existing_manager(self):
78 | e = '/stores/%s/managers/%s' % (self.store.id, self.user.id)
79 | self.jput(e)
80 | r = self.jput(e)
81 | self.assertBadJson(r)
82 |
83 | def test_remove_manager(self):
84 | e = '/stores/%s/managers/%s' % (self.store.id, self.user.id)
85 | self.jput(e)
86 | r = self.jdelete(e)
87 | self.assertStatusCode(r, 204)
88 |
--------------------------------------------------------------------------------
/tests/api/user_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.api.user_tests
4 | ~~~~~~~~~~~~~~~~~~~~
5 |
6 | api user tests module
7 | """
8 |
9 | from . import OverholtApiTestCase
10 |
11 |
12 | class UserApiTestCase(OverholtApiTestCase):
13 |
14 | def test_get_current_user(self):
15 | r = self.jget('/users')
16 | self.assertOkJson(r)
17 |
18 | def test_get_user(self):
19 | r = self.jget('/users/%s' % self.user.id)
20 | self.assertOkJson(r)
21 |
--------------------------------------------------------------------------------
/tests/factories.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.factories
4 | ~~~~~~~~~~~~~~~
5 |
6 | Overholt test factories module
7 | """
8 |
9 | from datetime import datetime
10 |
11 | from factory import Factory, Sequence, LazyAttribute
12 | from flask_security.utils import encrypt_password
13 |
14 | from overholt.core import db
15 | from overholt.models import *
16 |
17 |
18 | def create_sqlalchemy_model_function(class_to_create, *args, **kwargs):
19 | entity = class_to_create(**kwargs)
20 | db.session.add(entity)
21 | db.session.commit()
22 | return entity
23 |
24 | Factory.set_creation_function(create_sqlalchemy_model_function)
25 |
26 |
27 | class RoleFactory(Factory):
28 | FACTORY_FOR = Role
29 | name = 'admin'
30 | description = 'Administrator'
31 |
32 |
33 | class UserFactory(Factory):
34 | FACTORY_FOR = User
35 | email = Sequence(lambda n: 'user{0}@overholt.com'.format(n))
36 | password = LazyAttribute(lambda a: encrypt_password('password'))
37 | last_login_at = datetime.utcnow()
38 | current_login_at = datetime.utcnow()
39 | last_login_ip = '127.0.0.1'
40 | current_login_ip = '127.0.0.1'
41 | login_count = 1
42 | roles = LazyAttribute(lambda _: [RoleFactory()])
43 | active = True
44 |
45 |
46 | class StoreFactory(Factory):
47 | FACTORY_FOR = Store
48 | name = Sequence(lambda n: 'Store Number {0}'.format(n))
49 | address = '123 Overholt Alley'
50 | city = 'Overholt'
51 | state = 'New York'
52 | zip_code = '12345'
53 |
54 |
55 | class ProductFactory(Factory):
56 | FACTORY_FOR = Product
57 | name = Sequence(lambda n: 'Product Number {0}'.format(n))
58 |
59 |
60 | class CategoryFactory(Factory):
61 | FACTORY_FOR = Category
62 | name = Sequence(lambda n: 'Category {0}'.format(n))
63 |
--------------------------------------------------------------------------------
/tests/frontend/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.frontend
4 | ~~~~~~~~~~~~~~
5 |
6 | frontend tests package
7 | """
8 |
9 | from overholt.frontend import create_app
10 |
11 | from .. import OverholtAppTestCase, settings
12 |
13 |
14 | class OverholtFrontendTestCase(OverholtAppTestCase):
15 |
16 | def _create_app(self):
17 | return create_app(settings)
18 |
19 | def setUp(self):
20 | super(OverholtFrontendTestCase, self).setUp()
21 | self._login()
22 |
--------------------------------------------------------------------------------
/tests/frontend/dashboard_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.api.user_tests
4 | ~~~~~~~~~~~~~~~~~~~~
5 |
6 | api user tests module
7 | """
8 |
9 | from . import OverholtFrontendTestCase
10 |
11 |
12 | class DashboardTestCase(OverholtFrontendTestCase):
13 |
14 | def test_authenticated_dashboard_access(self):
15 | r = self.get('/')
16 | self.assertOk(r)
17 | self.assertIn('Dashboard
', r.data)
18 |
19 | def test_unauthenticated_dashboard_access(self):
20 | self.get('/logout')
21 | r = self.get('/')
22 | self.assertOk(r)
23 | self.assertNotIn('Dashboard
', r.data)
24 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.settings
4 | ~~~~~~~~~~~~~~
5 |
6 | tests settings module
7 | """
8 |
9 | DEBUG = False
10 | TESTING = True
11 |
12 | SQLALCHEMY_POOL_SIZE = None
13 | SQLALCHEMY_POOL_TIMEOUT = None
14 | SQLALCHEMY_POOL_RECYCLE = None
15 | SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
16 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tests.utils
4 | ~~~~~~~~~~~
5 |
6 | test utilities
7 | """
8 |
9 | import base64
10 | import hmac
11 |
12 | from datetime import datetime, timedelta
13 | from hashlib import sha1
14 |
15 | import simplejson as json
16 |
17 | from werkzeug.utils import parse_cookie
18 |
19 |
20 | def get_auth_headers(username=None, password=None):
21 | username = username or 'username'
22 | password = password or 'password'
23 | encoded = base64.b64encode('%s:%s' % (username, password))
24 | return {'Authorization': 'Basic ' + encoded}
25 |
26 |
27 | class FlaskTestCaseMixin(object):
28 |
29 | def _create_csrf_token(self):
30 | csrf_key = 'csrf_token'
31 | with self.client.session_transaction() as session:
32 | session['csrf'] = csrf_key
33 | secret_key = self.app.config['SECRET_KEY']
34 | expires = (datetime.now() + timedelta(minutes=30)).strftime('%Y%m%d%H%M%S')
35 | csrf_build = '%s%s' % (csrf_key, expires)
36 | csrf_token = csrf_build.encode('utf8')
37 | csrf_hmac = hmac.new(secret_key, csrf_token, digestmod=sha1)
38 | self.csrf_token = '%s##%s' % (expires, csrf_hmac.hexdigest())
39 |
40 | def _html_data(self, kwargs):
41 | if 'data' in kwargs:
42 | kwargs['data']['csrf_token'] = self.csrf_token
43 | if not kwargs.get('content_type'):
44 | kwargs['content_type'] = 'application/x-www-form-urlencoded'
45 | return kwargs
46 |
47 | def _json_data(self, kwargs, csrf_enabled=True):
48 | if 'data' in kwargs:
49 | kwargs['data']['csrf_token'] = self.csrf_token
50 | kwargs['data'] = json.dumps(kwargs['data'])
51 | if not kwargs.get('content_type'):
52 | kwargs['content_type'] = 'application/json'
53 | return kwargs
54 |
55 | def _request(self, method, *args, **kwargs):
56 | kwargs.setdefault('content_type', 'text/html')
57 | kwargs.setdefault('follow_redirects', True)
58 | return method(*args, **kwargs)
59 |
60 | def _jrequest(self, *args, **kwargs):
61 | return self._request(*args, **kwargs)
62 |
63 | def get(self, *args, **kwargs):
64 | return self._request(self.client.get, *args, **kwargs)
65 |
66 | def post(self, *args, **kwargs):
67 | return self._request(self.client.post, *args, **self._html_data(kwargs))
68 |
69 | def put(self, *args, **kwargs):
70 | return self._request(self.client.put, *args, **self._html_data(kwargs))
71 |
72 | def delete(self, *args, **kwargs):
73 | return self._request(self.client.delete, *args, **kwargs)
74 |
75 | def jget(self, *args, **kwargs):
76 | return self._jrequest(self.client.get, *args, **kwargs)
77 |
78 | def jpost(self, *args, **kwargs):
79 | return self._jrequest(self.client.post, *args, **self._json_data(kwargs))
80 |
81 | def jput(self, *args, **kwargs):
82 | return self._jrequest(self.client.put, *args, **self._json_data(kwargs))
83 |
84 | def jdelete(self, *args, **kwargs):
85 | return self._jrequest(self.client.delete, *args, **kwargs)
86 |
87 |
88 | def getCookies(self, response):
89 | cookies = {}
90 | for value in response.headers.get_all("Set-Cookie"):
91 | cookies.update(parse_cookie(value))
92 | return cookies
93 |
94 | def assertStatusCode(self, response, status_code):
95 | """Assert the status code of a Flask test client response
96 |
97 | :param response: The test client response object
98 | :param status_code: The expected status code
99 | """
100 | self.assertEquals(status_code, response.status_code)
101 | return response
102 |
103 | def assertOk(self, response):
104 | """Test that response status code is 200
105 |
106 | :param response: The test client response object
107 | """
108 | return self.assertStatusCode(response, 200)
109 |
110 | def assertBadRequest(self, response):
111 | """Test that response status code is 400
112 |
113 | :param response: The test client response object
114 | """
115 | return self.assertStatusCode(response, 400)
116 |
117 | def assertForbidden(self, response):
118 | """Test that response status code is 403
119 |
120 | :param response: The test client response object
121 | """
122 | return self.assertStatusCode(response, 403)
123 |
124 | def assertNotFound(self, response):
125 | """Test that response status code is 404
126 |
127 | :param response: The test client response object
128 | """
129 | return self.assertStatusCode(response, 404)
130 |
131 | def assertContentType(self, response, content_type):
132 | """Assert the content-type of a Flask test client response
133 |
134 | :param response: The test client response object
135 | :param content_type: The expected content type
136 | """
137 | self.assertEquals(content_type, response.headers['Content-Type'])
138 | return response
139 |
140 | def assertOkHtml(self, response):
141 | """Assert the response status code is 200 and an HTML response
142 |
143 | :param response: The test client response object
144 | """
145 | return self.assertOk(
146 | self.assertContentType(response, 'text/html; charset=utf-8'))
147 |
148 | def assertJson(self, response):
149 | """Test that content returned is in JSON format
150 |
151 | :param response: The test client response object
152 | """
153 | return self.assertContentType(response, 'application/json')
154 |
155 | def assertOkJson(self, response):
156 | """Assert the response status code is 200 and a JSON response
157 |
158 | :param response: The test client response object
159 | """
160 | return self.assertOk(self.assertJson(response))
161 |
162 | def assertBadJson(self, response):
163 | """Assert the response status code is 400 and a JSON response
164 |
165 | :param response: The test client response object
166 | """
167 | return self.assertBadRequest(self.assertJson(response))
168 |
169 | def assertCookie(self, response, name):
170 | """Assert the response contains a cookie with the specified name
171 |
172 | :param response: The test client response object
173 | :param key: The cookie name
174 | :param value: The value of the cookie
175 | """
176 | self.assertIn(name, self.getCookies(response))
177 |
178 | def assertCookieEquals(self, response, name, value):
179 | """Assert the response contains a cookie with the specified value
180 |
181 | :param response: The test client response object
182 | :param name: The cookie name
183 | :param value: The value of the cookie
184 | """
185 | self.assertEquals(value, self.getCookies(response).get(name, None))
186 |
--------------------------------------------------------------------------------
/wercker.yml:
--------------------------------------------------------------------------------
1 | box: wercker-labs/docker
2 |
3 | build:
4 | steps:
5 | - script:
6 | name: Build the image
7 | code: docker build -t mattupstate/overholt:$WERCKER_GIT_COMMIT .
8 |
9 | after-steps:
10 | - script:
11 | name: Set the Docker config
12 | code: echo $DOCKER_CFG > ~/.dockercfg
13 | - script:
14 | name: Push the image up to the Docker hub
15 | code: docker push mattupstate/overholt:$WERCKER_GIT_COMMIT
16 |
--------------------------------------------------------------------------------
/wsgi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | wsgi
4 | ~~~~
5 |
6 | overholt wsgi module
7 | """
8 |
9 | from werkzeug.serving import run_simple
10 | from werkzeug.wsgi import DispatcherMiddleware
11 |
12 | from overholt import api, frontend
13 |
14 | application = DispatcherMiddleware(frontend.create_app(), {
15 | '/api': api.create_app()
16 | })
17 |
18 | if __name__ == "__main__":
19 | run_simple('0.0.0.0', 5000, application, use_reloader=True, use_debugger=True)
20 |
--------------------------------------------------------------------------------