Browse Source

That's ugly

pull/1/head
Dashie der otter 2 years ago
parent
commit
0cebb67f58
Signed by: Dashie <dashie@sigpipe.me> GPG Key ID: C2D57B325840B755
100 changed files with 28782 additions and 0 deletions
  1. 7
    0
      .gitignore
  2. 114
    0
      app.py
  3. 35
    0
      config.py.sample
  4. 0
    0
      controllers/__init__.py
  5. 43
    0
      controllers/admin.py
  6. 36
    0
      controllers/api.py
  7. 33
    0
      controllers/main.py
  8. 61
    0
      controllers/sound.py
  9. 61
    0
      controllers/users.py
  10. 15
    0
      crons.py
  11. 46
    0
      dbseed.py
  12. 71
    0
      forms.py
  13. 1
    0
      migrations/README
  14. 45
    0
      migrations/alembic.ini
  15. 88
    0
      migrations/env.py
  16. 22
    0
      migrations/script.py.mako
  17. 91
    0
      migrations/versions/00_795db5a5e99b_.py
  18. 49
    0
      migrations/versions/01_da3273ca0f0f_.py
  19. 152
    0
      models.py
  20. 78
    0
      reel2bits.py
  21. 55
    0
      requirements.txt
  22. 587
    0
      static/css/bootstrap-theme.css
  23. 1
    0
      static/css/bootstrap-theme.css.map
  24. 6
    0
      static/css/bootstrap-theme.min.css
  25. 1
    0
      static/css/bootstrap-theme.min.css.map
  26. 6760
    0
      static/css/bootstrap.css
  27. 1
    0
      static/css/bootstrap.css.map
  28. 6
    0
      static/css/bootstrap.min.css
  29. 1
    0
      static/css/bootstrap.min.css.map
  30. BIN
      static/css/chosen-sprite.png
  31. BIN
      static/css/chosen-sprite@2x.png
  32. 450
    0
      static/css/chosen.css
  33. 3
    0
      static/css/chosen.min.css
  34. 239
    0
      static/css/font-awesome.css
  35. 4
    0
      static/css/font-awesome.min.css
  36. 15
    0
      static/css/ie10-viewport-bug-workaround.css
  37. BIN
      static/css/images/ui-anim_basic_16x16.gif
  38. BIN
      static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png
  39. BIN
      static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png
  40. BIN
      static/css/images/ui-bg_flat_0_888888_40x100.png
  41. BIN
      static/css/images/ui-bg_flat_0_aaaaaa_40x100.png
  42. BIN
      static/css/images/ui-bg_flat_10_000000_40x100.png
  43. BIN
      static/css/images/ui-bg_flat_75_ffffff_40x100.png
  44. BIN
      static/css/images/ui-bg_glass_100_f6f6f6_1x400.png
  45. BIN
      static/css/images/ui-bg_glass_100_fdf5ce_1x400.png
  46. BIN
      static/css/images/ui-bg_glass_25_e1f0f5_1x400.png
  47. BIN
      static/css/images/ui-bg_glass_55_444444_1x400.png
  48. BIN
      static/css/images/ui-bg_glass_65_ffffff_1x400.png
  49. BIN
      static/css/images/ui-bg_glass_75_dadada_1x400.png
  50. BIN
      static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png
  51. BIN
      static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
  52. BIN
      static/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png
  53. BIN
      static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
  54. BIN
      static/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png
  55. BIN
      static/css/images/ui-icons_222222_256x240.png
  56. BIN
      static/css/images/ui-icons_228ef1_256x240.png
  57. BIN
      static/css/images/ui-icons_309bbf_256x240.png
  58. BIN
      static/css/images/ui-icons_454545_256x240.png
  59. BIN
      static/css/images/ui-icons_bf3030_256x240.png
  60. BIN
      static/css/images/ui-icons_ef8c08_256x240.png
  61. BIN
      static/css/images/ui-icons_ffd27a_256x240.png
  62. BIN
      static/css/images/ui-icons_ffffff_256x240.png
  63. 1225
    0
      static/css/jquery-ui.css
  64. 7
    0
      static/css/jquery-ui.min.css
  65. 833
    0
      static/css/jquery-ui.structure.css
  66. 5
    0
      static/css/jquery-ui.structure.min.css
  67. 410
    0
      static/css/jquery-ui.theme.css
  68. 5
    0
      static/css/jquery-ui.theme.min.css
  69. 62
    0
      static/css/pygments.css
  70. 74
    0
      static/css/style.css
  71. 180
    0
      static/css/timeline.css
  72. 18
    0
      static/datatables/Contributing.md
  73. 53
    0
      static/datatables/Readme.md
  74. 762
    0
      static/datatables/examples/advanced_init/column_render.html
  75. 755
    0
      static/datatables/examples/advanced_init/complex_header.html
  76. 746
    0
      static/datatables/examples/advanced_init/defaults.html
  77. 755
    0
      static/datatables/examples/advanced_init/dom_multiple_elements.html
  78. 756
    0
      static/datatables/examples/advanced_init/dom_toolbar.html
  79. 761
    0
      static/datatables/examples/advanced_init/dt_events.html
  80. 745
    0
      static/datatables/examples/advanced_init/events_live.html
  81. 743
    0
      static/datatables/examples/advanced_init/footer_callback.html
  82. 745
    0
      static/datatables/examples/advanced_init/html5-data-attributes.html
  83. 744
    0
      static/datatables/examples/advanced_init/html5-data-options.html
  84. 73
    0
      static/datatables/examples/advanced_init/index.html
  85. 742
    0
      static/datatables/examples/advanced_init/language_file.html
  86. 744
    0
      static/datatables/examples/advanced_init/length_menu.html
  87. 771
    0
      static/datatables/examples/advanced_init/object_dom_read.html
  88. 755
    0
      static/datatables/examples/advanced_init/row_callback.html
  89. 807
    0
      static/datatables/examples/advanced_init/row_grouping.html
  90. 764
    0
      static/datatables/examples/advanced_init/sort_direction_control.html
  91. 310
    0
      static/datatables/examples/ajax/custom_data_flat.html
  92. 296
    0
      static/datatables/examples/ajax/custom_data_property.html
  93. 460
    0
      static/datatables/examples/ajax/data/arrays.txt
  94. 460
    0
      static/datatables/examples/ajax/data/arrays_custom_prop.txt
  95. 688
    0
      static/datatables/examples/ajax/data/arrays_subobjects.txt
  96. 460
    0
      static/datatables/examples/ajax/data/objects.txt
  97. 688
    0
      static/datatables/examples/ajax/data/objects_deep.txt
  98. 458
    0
      static/datatables/examples/ajax/data/objects_root_array.txt
  99. 745
    0
      static/datatables/examples/ajax/data/objects_subarrays.txt
  100. 0
    0
      static/datatables/examples/ajax/data/orthogonal.txt

+ 7
- 0
.gitignore View File

@@ -0,0 +1,7 @@
*.pyc
config.py
TODO.org
.idea
*.db
uploads/pictures/*
tmp/cty.xml

+ 114
- 0
app.py View File

@@ -0,0 +1,114 @@
# encoding: utf-8
import logging
import os
import subprocess
from logging.handlers import RotatingFileHandler

from flask import Flask, render_template, g, send_from_directory, jsonify
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_migrate import Migrate
from flask_security import Security
from flask_uploads import configure_uploads, UploadSet, IMAGES

from controllers.admin import bp_admin
from controllers.main import bp_main
from controllers.users import bp_users
from forms import ExtendedRegisterForm
from models import db, user_datastore
from utils import dt_utc_to_user_tz, InvalidUsage, show_date_no_offset, is_admin, gcfg

__VERSION__ = "0.0.1"


def create_app(cfg=None):
# App Configuration
if cfg is None:
cfg = {}
app = Flask(__name__)
app.config.from_pyfile("config.py")
app.config.update(cfg)

Bootstrap(app)

app.jinja_env.add_extension('jinja2.ext.with_')
app.jinja_env.add_extension('jinja2.ext.do')
app.jinja_env.globals.update(is_admin=is_admin)
app.jinja_env.globals.update(gcfg=gcfg)

# Logging
if not app.debug:
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), 'a', 1000000, 1)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)

mail = Mail(app)
migrate = Migrate(app, db)

db.init_app(app)

# Setup Flask-Security
security = Security(app, user_datastore,
register_form=ExtendedRegisterForm)

git_version = ""
gitpath = os.path.join(os.getcwd(), ".git")
if os.path.isdir(gitpath):
git_version = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
if git_version:
git_version = git_version.strip().decode('UTF-8')

@app.before_request
def before_request():
g.cfg = {
'REEL2BITS_VERSION_VER': __VERSION__,
'REEL2BITS_VERSION_GIT': git_version,
'REEL2BITS_VERSION': "{0} ({1})".format(__VERSION__, git_version),
}

@app.errorhandler(InvalidUsage)
def handle_invalid_usage(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response

pictures = UploadSet('pictures', IMAGES)
configure_uploads(app, pictures)

app.register_blueprint(bp_main)
app.register_blueprint(bp_users)
app.register_blueprint(bp_admin)

# Used in development
@app.route('/uploads/<path:stuff>', methods=['GET'])
def get_uploads_stuff(stuff):
print("Get {0} from {1}".format(stuff, app.config['UPLOADS_DEFAULT_DEST']))
return send_from_directory(app.config['UPLOADS_DEFAULT_DEST'], stuff, as_attachment=False)

@app.errorhandler(404)
def page_not_found(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 404, "message": "Page not found", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 404

@app.errorhandler(403)
def err_forbidden(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 403, "message": "Access forbidden", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 403

@app.errorhandler(410)
def err_gone(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 410, "message": "Gone", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 410

if not app.debug:
@app.errorhandler(500)
def err_failed(msg):
pcfg = {"title": "Whoops, something failed.", "error": 500, "message": "Something is broken", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 500

return app

+ 35
- 0
config.py.sample View File

@@ -0,0 +1,35 @@
DEBUG = True

SECRET_KEY = 'aglaglawx2r31ht1g0m1vcq'
# SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://dashie@localhost/reel2bits'
SQLALCHEMY_DATABASE_URI = 'postgresql+pg8000://dashie@localhost/reel2bits'
# SQLALCHEMY_DATABASE_URI = 'sqlite:///reel2bits.db'
SQLALCHEMY_ECHO = False
SQLALCHEMY_TRACK_MODIFICATIONS = False

SECURITY_CONFIRMABLE = False
SECURITY_REGISTERABLE = False # deactivate registration
SECURITY_RECOVERABLE = True
SECURITY_TRACKABLE = False
SECURITY_CHANGEABLE = True
SECURITY_PASSWORD_HASH = 'bcrypt'
SECURITY_PASSWORD_SALT = 'ottersrulestheworld'
# SECURITY_URL_PREFIX = '/sec'

SECURITY_SEND_REGISTER_EMAIL = False
SECURITY_SEND_PASSWORD_CHANGE_EMAIL = False
SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL = False

BOOTSTRAP_USE_MINIFIED = True
BOOTSTRAP_SERVE_LOCAL = True
BOOTSTRAP_CDN_FORCE_SSL = True
BOOTSTRAP_QUERYSTRING_REVVING = True

DEBUG_TB_PROFILER_ENABLED = True
DEBUG_TB_INTERCEPT_REDIRECTS = False

# Paginations

UPLOADED_SND_DEST = "/home/reel2bits/uploads/snd"
UPLOADS_DEFAULT_DEST = "/home/reel2bits/uploads"
TEMP_DOWNLOAD_FOLDER = "/home/reel2bits/tmp"

+ 0
- 0
controllers/__init__.py View File


+ 43
- 0
controllers/admin.py View File

@@ -0,0 +1,43 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_security import login_required

from forms import ConfigForm
from models import db, Logging, Config
from utils import is_admin

bp_admin = Blueprint('bp_admin', __name__)


@bp_admin.route('/admin/logs', methods=['GET'])
@login_required
def logs():
if not is_admin():
return redirect(url_for('bp_home.home'))
pcfg = {"title": "Application Logs"}
_logs = Logging.query.order_by(Logging.timestamp.desc()).limit(100).all()
return render_template('admin/logs.jinja2', pcfg=pcfg, logs=_logs)


@bp_admin.route('/admin/config', methods=['GET', 'POST'])
@login_required
def config():
if not is_admin():
return redirect(url_for('bp_main.home'))

pcfg = {"title": "Application Config"}

_config = Config.query.one()
if not _config:
flash("Config not found", 'error')
return redirect(url_for("bp_main.home"))

form = ConfigForm(request.form, _config)

if form.validate_on_submit():
_config.app_name = form.app_name.data

db.session.commit()
flash("Configuration updated", "info")
return redirect(url_for('bp_admin.config'))

return render_template('admin/config.jinja2', pcfg=pcfg, form=form)

+ 36
- 0
controllers/api.py View File

@@ -0,0 +1,36 @@
from flask import Blueprint, redirect, url_for, abort, flash
from flask_security import login_required, current_user

from models import db, Apitoken
from utils import generate_uniques_apitoken

bp_api = Blueprint('bp_api', __name__)


@bp_api.route('/api/token/new')
@login_required
def apitoken_new():
apitoken = generate_uniques_apitoken()
if not apitoken:
return abort(500)

a = Apitoken()
a.user_id = current_user.id
a.token = apitoken["token"]
a.secret = apitoken["secret"]
db.session.add(a)
db.session.commit()
return redirect(url_for('bp_users.user_profile'))


@bp_api.route('/api/token/<string:apit>/del')
@login_required
def apitoken_del(apit):
apitoken = Apitoken.query.filter(Apitoken.id == apit).first()
if not apitoken:
flash("API Token not found", "error")
return redirect(url_for("bp_main.home"))

db.session.delete(apitoken)
db.session.commit()
return redirect(url_for('bp_users.user_profile'))

+ 33
- 0
controllers/main.py View File

@@ -0,0 +1,33 @@
from flask import Blueprint, render_template, flash, redirect, url_for
from flask_security import current_user
from sqlalchemy import func

from models import db, User, Config

bp_main = Blueprint('bp_main', __name__)


# Show public logbooks
@bp_main.route('/')
def home():
_config = Config.query.one()
if not _config:
flash("Config not found", 'error')
return redirect(url_for("bp_main.home"))

pcfg = {"title": "Home", "app_name": _config.app_name}
users = User.query.all()

return render_template('home.jinja2', pcfg=pcfg, users=users)


@bp_main.route('/about')
def about():
_config = Config.query.one()
if not _config:
flash("Config not found", 'error')
return redirect(url_for("bp_main.home"))

pcfg = {"title": _config.app_name}

return render_template('about.jinja2', pcfg=pcfg)

+ 61
- 0
controllers/sound.py View File

@@ -0,0 +1,61 @@
import pytz
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_security import login_required, current_user
from sqlalchemy import func

from forms import UserProfileForm
from models import db, User, UserLogging

bp_users = Blueprint('bp_users', __name__)


@bp_users.route('/user/logs', methods=['GET'])
@login_required
def logs():
level = request.args.get('level')
pcfg = {"title": "User Logs"}
if level:
_logs = UserLogging.query.filter(UserLogging.level == level.upper(),
UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
else:
_logs = UserLogging.query.filter(UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
return render_template('users/user_logs.jinja2', pcfg=pcfg, logs=_logs)


@bp_users.route('/user', methods=['GET'])
@login_required
def profile():
pcfg = {"title": "My Profile"}

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
return redirect(url_for("bp_main.home"))

return render_template('users/profile.jinja2', pcfg=pcfg, user=user)


@bp_users.route('/user/edit', methods=['GET', 'POST'])
@login_required
def edit():
pcfg = {"title": "Edit my profile"}

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
return redirect(url_for("bp_main.home"))

form = UserProfileForm(request.form, user)
form.timezone.choices = [[str(i), str(i)] for i in pytz.all_timezones]

if form.validate_on_submit():
user.lastname = form.lastname.data
user.firstname = form.firstname.data
user.timezone = form.timezone.data

db.session.commit()
return redirect(url_for('bp_users.profile'))

return render_template('users/edit.jinja2', pcfg=pcfg, form=form, user=user)

+ 61
- 0
controllers/users.py View File

@@ -0,0 +1,61 @@
import pytz
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_security import login_required, current_user
from sqlalchemy import func

from forms import UserProfileForm
from models import db, User, UserLogging

bp_users = Blueprint('bp_users', __name__)


@bp_users.route('/user/logs', methods=['GET'])
@login_required
def logs():
level = request.args.get('level')
pcfg = {"title": "User Logs"}
if level:
_logs = UserLogging.query.filter(UserLogging.level == level.upper(),
UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
else:
_logs = UserLogging.query.filter(UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
return render_template('users/user_logs.jinja2', pcfg=pcfg, logs=_logs)


@bp_users.route('/user', methods=['GET'])
@login_required
def profile():
pcfg = {"title": "My Profile"}

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
return redirect(url_for("bp_main.home"))

return render_template('users/profile.jinja2', pcfg=pcfg, user=user)


@bp_users.route('/user/edit', methods=['GET', 'POST'])
@login_required
def edit():
pcfg = {"title": "Edit my profile"}

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
return redirect(url_for("bp_main.home"))

form = UserProfileForm(request.form, user)
form.timezone.choices = [[str(i), str(i)] for i in pytz.all_timezones]

if form.validate_on_submit():
user.lastname = form.lastname.data
user.firstname = form.firstname.data
user.timezone = form.timezone.data

db.session.commit()
return redirect(url_for('bp_users.profile'))

return render_template('users/edit.jinja2', pcfg=pcfg, form=form, user=user)

+ 15
- 0
crons.py View File

@@ -0,0 +1,15 @@
from __future__ import print_function

import gzip
import os
import shutil
import urllib.parse
import urllib.error
import urllib.request
import xml.etree.ElementTree as ElementTree
import re
import datetime

from dateutil import parser
from flask import current_app


+ 46
- 0
dbseed.py View File

@@ -0,0 +1,46 @@
from models import user_datastore, Config, Role


def make_db_seed(db):
print("== Seeding database")
db.session.begin(subtransactions=True)
try:
print("++ Seeding config")
seed_config(db)
seed_users(db) # after timezones because not null relation
# also seeds roles admin/user
except:
db.session.rollback()
raise


def seed_users(db):
print("++ Seeding users")
role_usr = Role()
role_usr.name = 'user'
role_usr.description = 'Simple user'

role_adm = Role()
role_adm.name = 'admin'
role_adm.description = 'Admin user'

db.session.add(role_usr)
db.session.add(role_adm)

user_datastore.create_user(
email='dashie@sigpipe.me',
password='fluttershy',
name='toto',
timezone='UTC',
roles=[role_adm]
)
db.session.commit()
return


def seed_config(db):
a = Config(app_name='My reel2bits instance')
db.session.add(a)
db.session.commit()
db.session.commit()
# Bug, two commit necessary

+ 71
- 0
forms.py View File

@@ -0,0 +1,71 @@
import datetime
from libqth import is_valid_qth

from flask_security import RegisterForm, current_user
from flask_uploads import UploadSet, IMAGES
from flask_wtf import Form
from flask_wtf.file import FileField, FileAllowed, FileRequired
from wtforms import PasswordField, SubmitField, TextAreaField, SelectField, IntegerField, \
HiddenField, BooleanField
from wtforms.ext.dateutil.fields import DateTimeField
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import DataRequired, ValidationError, Optional
from wtforms_alchemy import model_form_factory
from wtforms_components.fields import SelectField as WTFComponentsSelectField
from wtforms import widgets
from wtforms.fields.core import StringField

from models import db, User
from utils import dt_utc_to_user_tz

BaseModelForm = model_form_factory(Form)

pictures = UploadSet('pictures', IMAGES)


class PasswordFieldNotHidden(StringField):
"""
Original source: https://github.com/wtforms/wtforms/blob/2.0.2/wtforms/fields/simple.py#L35-L42

A StringField, except renders an ``<input type="password">``.
Also, whatever value is accepted by this field is not rendered back
to the browser like normal fields.
"""
widget = widgets.PasswordInput(hide_value=False)


class ModelForm(BaseModelForm):
@classmethod
def get_session(cls):
return db.session


class ExtendedRegisterForm(RegisterForm):
name = StringField('Name', [DataRequired()])

def validate_name(form, field):
if len(field.data) <= 0:
raise ValidationError("Username required")

u = User.query.filter(User.name == field.data).first()
if u:
raise ValidationError("Username already taken")


class UserProfileForm(ModelForm):
class Meta:
model = User

password = PasswordField('Password')
name = StringField('Name')
email = StringField('Email')
firstname = StringField('Firstname')
lastname = StringField('Lastname')
timezone = SelectField(coerce=str, label='Timezone', default='UTC')
submit = SubmitField('Update profile')


class ConfigForm(Form):
app_name = StringField('App Name', [DataRequired()])

submit = SubmitField('Update config')

+ 1
- 0
migrations/README View File

@@ -0,0 +1 @@
Generic single-database configuration.

+ 45
- 0
migrations/alembic.ini View File

@@ -0,0 +1,45 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

+ 88
- 0
migrations/env.py View File

@@ -0,0 +1,88 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
import logging

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
"""Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.

"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)

with context.begin_transaction():
context.run_migrations()


def run_migrations_online():
"""Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.

"""

# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.readthedocs.org/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')

engine = engine_from_config(config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)

connection = engine.connect()
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
transaction_per_migration=True,
**current_app.extensions['migrate'].configure_args)

try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()

if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

+ 22
- 0
migrations/script.py.mako View File

@@ -0,0 +1,22 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}

"""

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

def upgrade():
${upgrades if upgrades else "pass"}


def downgrade():
${downgrades if downgrades else "pass"}

+ 91
- 0
migrations/versions/00_795db5a5e99b_.py View File

@@ -0,0 +1,91 @@
"""Initial revision

Revision ID: 795db5a5e99b
Revises: None
Create Date: 2016-12-31 14:00:38.437184

"""

# revision identifiers, used by Alembic.
revision = '795db5a5e99b'
down_revision = None

from alembic import op
import sqlalchemy as sa


def upgrade():
op.create_table('config',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('app_name', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False),
sa.Column('active', sa.Boolean(), nullable=True),
sa.Column('confirmed_at', sa.DateTime(), nullable=True),
sa.Column('firstname', sa.String(length=32), nullable=True),
sa.Column('lastname', sa.String(length=32), nullable=True),
sa.Column('timezone', sa.String(length=255), nullable=False),
sa.Column('slug', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('name'),
sa.UniqueConstraint('slug')
)
op.create_table('apitoken',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('token', sa.String(length=255), nullable=False),
sa.Column('secret', sa.String(length=255), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('secret'),
sa.UniqueConstraint('token')
)
op.create_table('logging',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('category', sa.String(length=255), nullable=False),
sa.Column('level', sa.String(length=255), nullable=False),
sa.Column('message', sa.Text(), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('roles_users',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], )
)
op.create_table('user_logging',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('category', sa.String(length=255), nullable=False),
sa.Column('level', sa.String(length=255), nullable=False),
sa.Column('message', sa.Text(), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)


def downgrade():
op.drop_table('user_logging')
op.drop_table('roles_users')
op.drop_table('logging')
op.drop_table('apitoken')
op.drop_table('user')
op.drop_table('role')
op.drop_table('config')

+ 49
- 0
migrations/versions/01_da3273ca0f0f_.py View File

@@ -0,0 +1,49 @@
"""Add Sound and SoundInfo

Revision ID: da3273ca0f0f
Revises: 795db5a5e99b
Create Date: 2016-12-31 14:43:11.818727

"""

# revision identifiers, used by Alembic.
revision = 'da3273ca0f0f'
down_revision = '795db5a5e99b'

from alembic import op
import sqlalchemy as sa


def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('sound',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('uploaded', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
sa.Column('description', sa.UnicodeText(), nullable=True),
sa.Column('public', sa.Boolean(), nullable=False),
sa.Column('slug', sa.String(length=255), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('slug')
)
op.create_table('sound_info',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('duration', sa.Float(), nullable=True),
sa.Column('format', sa.String(length=255), nullable=True),
sa.Column('rate', sa.String(length=255), nullable=True),
sa.Column('channels', sa.Integer(), nullable=True),
sa.Column('codec', sa.String(length=255), nullable=True),
sa.Column('sound_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['sound_id'], ['sound.id'], ),
sa.PrimaryKeyConstraint('id')
)
### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('sound_info')
op.drop_table('sound')
### end Alembic commands ###

+ 152
- 0
models.py View File

@@ -0,0 +1,152 @@
import datetime
from libqth import is_valid_qth, qth_to_coords, coords_to_qth

from flask_security import SQLAlchemyUserDatastore, UserMixin, RoleMixin
from flask_sqlalchemy import SQLAlchemy, BaseQuery
from geohelper import distance
from sqlalchemy.sql import func
from sqlalchemy_searchable import make_searchable, SearchQueryMixin
from sqlalchemy import event
from slugify import slugify
from sqlalchemy_utils.types import TSVectorType

db = SQLAlchemy()
make_searchable()


class LogQuery(BaseQuery, SearchQueryMixin):
pass


roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))


class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False, info={'label': 'Name'})
description = db.Column(db.String(255), info={'label': 'Description'})

__mapper_args__ = {"order_by": name}


class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False, info={'label': 'Email'})
name = db.Column(db.String(255), unique=True, nullable=False, info={'label': 'Name'})
password = db.Column(db.String(255), nullable=False, info={'label': 'Password'})
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())

firstname = db.Column(db.String(32))
lastname = db.Column(db.String(32))

timezone = db.Column(db.String(255), nullable=False, default='UTC') # Managed and fed by pytz

slug = db.Column(db.String(255), unique=True, nullable=True)

roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
apitokens = db.relationship('Apitoken', backref='user', lazy='dynamic', cascade="delete")

user_loggings = db.relationship('UserLogging', backref='user', lazy='dynamic', cascade="delete")
loggings = db.relationship('Logging', backref='user', lazy='dynamic', cascade="delete")

sounds = db.relationship('Sound', backref='user', lazy='dynamic', cascade="delete")

__mapper_args__ = {"order_by": name}

def join_roles(self, string):
return string.join([i.description for i in self.roles])


class Apitoken(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False)
token = db.Column(db.String(255), unique=True, nullable=False, info={'label': 'Token'})
secret = db.Column(db.String(255), unique=True, nullable=False, info={'label': 'Secret'})


user_datastore = SQLAlchemyUserDatastore(db, User, Role)


class Config(db.Model):
__tablename__ = "config"

id = db.Column(db.Integer, primary_key=True)
app_name = db.Column(db.String(255), default=None)


class Logging(db.Model):
__tablename__ = "logging"

id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.String(255), nullable=False, default="General")
level = db.Column(db.String(255), nullable=False, default="INFO")
message = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())

user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=True)


class UserLogging(db.Model):
__tablename__ = "user_logging"

id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.String(255), nullable=False, default="General")
level = db.Column(db.String(255), nullable=False, default="INFO")
message = db.Column(db.Text, nullable=False)
timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())

#log_id = db.Column(db.Integer(), db.ForeignKey('log.id'), nullable=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False)


class SoundInfo(db.Model):
__tablename__ = "sound_info"

id = db.Column(db.Integer, primary_key=True)
duration = db.Column(db.Float, nullable=True)
format = db.Column(db.String(255), nullable=True)
rate = db.Column(db.String(255), nullable=True)
channels = db.Column(db.Integer, nullable=True)
codec = db.Column(db.String(255), nullable=True)

sound_id = db.Column(db.Integer(), db.ForeignKey('sound.id'), nullable=False)


class Sound(db.Model):
__tablename__ = "sound"

id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=True)
uploaded = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())
# TODO genre
# TODO tags
# TODO picture ?
description = db.Column(db.UnicodeText(), nullable=True)
public = db.Column(db.Boolean(), default=True, nullable=False)
slug = db.Column(db.String(255), unique=True, nullable=True)

user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False)
sound_infos = db.relationship('SoundInfo', backref='sound_info', lazy='dynamic', cascade="delete")

@event.listens_for(User, 'after_update')
@event.listens_for(User, 'after_insert')
def make_slug(mapper, connection, target):
title = "{0} {1}".format(target.id, target.name)
slug = slugify(title)
connection.execute(
User.__table__.update().where(User.__table__.c.id == target.id).values(slug=slug)
)


@event.listens_for(Sound, 'after_update')
@event.listens_for(Sound, 'after_insert')
def make_slug(mapper, connection, target):
if not target.slug or target.slug == "":
title = "{0} {1}".format(target.id, target.title)
slug = slugify(title)
connection.execute(
User.__table__.update().where(User.__table__.c.id == target.id).values(slug=slug)
)

+ 78
- 0
reel2bits.py View File

@@ -0,0 +1,78 @@
# encoding: utf-8
from pprint import pprint as pp
import datetime
import os
import texttable
from flask_debugtoolbar import DebugToolbarExtension
from flask_migrate import MigrateCommand
from flask_script import Manager
from dbseed import make_db_seed
from models import db

try:
from raven.contrib.flask import Sentry
import raven
print(" * Sentry support loaded")
HAS_SENTRY = True
except ImportError as e:
print(" * No Sentry support")
HAS_SENTRY = False

from app import create_app

app = create_app()

if HAS_SENTRY:
app.config['SENTRY_RELEASE'] = raven.fetch_git_sha(os.path.dirname(__file__))
sentry = Sentry(app, dsn=app.config['SENTRY_DSN'])
print(" * Sentry support activated")
print(" * Sentry DSN: %s" % app.config['SENTRY_DSN'])

toolbar = DebugToolbarExtension(app)
manager = Manager(app)


# Other commands
@manager.command
def routes():
"""Dump all routes of defined app"""
table = texttable.Texttable()
table.set_deco(texttable.Texttable().HEADER)
table.set_cols_dtype(['t', 't', 't'])
table.set_cols_align(["l", "l", "l"])
table.set_cols_width([60, 30, 90])

table.add_rows([["Prefix", "Verb", "URI Pattern"]])

for rule in sorted(app.url_map.iter_rules(), key=lambda x: str(x)):
methods = ','.join(rule.methods)
table.add_row([rule.endpoint, methods, rule])

print(table.draw())


@manager.command
def config():
"""Dump config"""
pp(app.config)


@MigrateCommand.command
def seed():
"""Seed database with default content"""
make_db_seed(db)

CacheCommand = Manager(usage='Perform cache actions')
CronCommand = Manager(usage='Perform crons actions')


manager.add_command('db', MigrateCommand)
manager.add_command('cache', CacheCommand)
manager.add_command('cron', CronCommand)

if __name__ == '__main__':
try:
manager.run()
except KeyboardInterrupt as e:
print("Got KeyboardInterrupt, halting...")
print(e)

+ 55
- 0
requirements.txt View File

@@ -0,0 +1,55 @@
alembic==0.8.6
bcrypt==2.0.0
blinker==1.4
cffi==1.6.0
click==6.6
dateutils==0.6.6
decorator==4.0.10
dominate==2.2.0
Flask==0.10.1
Flask-Bootstrap==3.3.6.0
Flask-DebugToolbar==0.10.0
Flask-Login==0.3.2
Flask-Mail==0.9.1
Flask-Migrate==1.8.0
Flask-Principal==0.4.0
Flask-Script==2.0.5
Flask-Security==1.7.5
Flask-SQLAlchemy==2.1
Flask-WTF==0.12
geohelper==0.2.0
gunicorn==19.6.0
infinity==1.4
intervals==0.7.1
itsdangerous==0.24
Jinja2==2.8
Mako==1.0.4
MarkupSafe==0.23
parsedatetime==2.1
passlib==1.6.5
pycparser==2.14
pyparsing==2.1.4
python-dateutil==2.5.3
python-editor==1.0
pytz==2016.6.1
requests==2.10.0
six==1.10.0
SQLAlchemy==1.0.13
SQLAlchemy-Searchable==0.10.1
SQLAlchemy-Utils==0.32.7
texttable==0.8.4
Unidecode==0.4.19
validators==0.10.1
virtualenv==1.11.6
visitor==0.1.3
Werkzeug==0.11.10
WTForms==2.1
WTForms-Alchemy==0.16.1
WTForms-Components==0.10.0
mysqlclient==1.3.7
beautifulsoup4==4.4.1
flask-uploads
python-slugify
LatLon
git+http://dev.sigpipe.me/DashieHam/pyHamQth.git#egg=pyhamqth
git+https://github.com/ggramaize/libqth.git#egg=libqth

+ 587
- 0
static/css/bootstrap-theme.css View File

@@ -0,0 +1,587 @@
/*!
* Bootstrap v3.3.6 (http://getbootstrap.com)
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #2e6da4;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
background-repeat: repeat-x;
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

+ 1
- 0
static/css/bootstrap-theme.css.map
File diff suppressed because it is too large
View File


+ 6
- 0
static/css/bootstrap-theme.min.css
File diff suppressed because it is too large
View File


+ 1
- 0
static/css/bootstrap-theme.min.css.map
File diff suppressed because it is too large
View File


+ 6760
- 0
static/css/bootstrap.css
File diff suppressed because it is too large
View File


+ 1
- 0
static/css/bootstrap.css.map
File diff suppressed because it is too large
View File


+ 6
- 0
static/css/bootstrap.min.css
File diff suppressed because it is too large
View File


+ 1
- 0
static/css/bootstrap.min.css.map
File diff suppressed because it is too large
View File


BIN
static/css/chosen-sprite.png View File


BIN
static/css/chosen-sprite@2x.png View File


+ 450
- 0
static/css/chosen.css View File

@@ -0,0 +1,450 @@
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com

Version 1.4.2
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011-2015 Harvest http://getharvest.com

MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/

/* @group Base */
.chosen-container {
position: relative;
display: inline-block;
vertical-align: middle;
font-size: 13px;
zoom: 1;
*display: inline;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.chosen-container * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.chosen-container .chosen-drop {
position: absolute;
top: 100%;
left: -9999px;
z-index: 1010;
width: 100%;
border: 1px solid #aaa;
border-top: 0;
background: #fff;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
}
.chosen-container.chosen-with-drop .chosen-drop {
left: 0;
}
.chosen-container a {
cursor: pointer;
}
.chosen-container .search-choice .group-name, .chosen-container .chosen-single .group-name {
margin-right: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: normal;
color: #999999;
}
.chosen-container .search-choice .group-name:after, .chosen-container .chosen-single .group-name:after {
content: ":";
padding-left: 2px;
vertical-align: top;
}

/* @end */
/* @group Single Chosen */
.chosen-container-single .chosen-single {
position: relative;
display: block;
overflow: hidden;
padding: 0 0 0 8px;
height: 25px;
border: 1px solid #aaa;
border-radius: 5px;
background-color: #fff;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
background-clip: padding-box;
box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
color: #444;
text-decoration: none;
white-space: nowrap;
line-height: 24px;
}
.chosen-container-single .chosen-default {
color: #999;
}
.chosen-container-single .chosen-single span {
display: block;
overflow: hidden;
margin-right: 26px;
text-overflow: ellipsis;
white-space: nowrap;
}
.chosen-container-single .chosen-single-with-deselect span {
margin-right: 38px;
}
.chosen-container-single .chosen-single abbr {
position: absolute;
top: 6px;
right: 26px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-single .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
background-position: -42px -10px;
}
.chosen-container-single .chosen-single div {
position: absolute;
top: 0;
right: 0;
display: block;
width: 18px;
height: 100%;
}
.chosen-container-single .chosen-single div b {
display: block;
width: 100%;
height: 100%;
background: url('chosen-sprite.png') no-repeat 0px 2px;
}
.chosen-container-single .chosen-search {
position: relative;
z-index: 1010;
margin: 0;
padding: 3px 4px;
white-space: nowrap;
}
.chosen-container-single .chosen-search input[type="text"] {
margin: 1px 0;
padding: 4px 20px 4px 5px;
width: 100%;
height: auto;
outline: 0;
border: 1px solid #aaa;
background: white url('chosen-sprite.png') no-repeat 100% -20px;
background: url('chosen-sprite.png') no-repeat 100% -20px;
font-size: 1em;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-single .chosen-drop {
margin-top: -1px;
border-radius: 0 0 4px 4px;
background-clip: padding-box;
}
.chosen-container-single.chosen-container-single-nosearch .chosen-search {
position: absolute;
left: -9999px;
}

/* @end */
/* @group Results */
.chosen-container .chosen-results {
color: #444;
position: relative;
overflow-x: hidden;
overflow-y: auto;
margin: 0 4px 4px 0;
padding: 0 0 0 4px;
max-height: 240px;
-webkit-overflow-scrolling: touch;
}
.chosen-container .chosen-results li {
display: none;
margin: 0;
padding: 5px 6px;
list-style: none;
line-height: 15px;
word-wrap: break-word;
-webkit-touch-callout: none;
}
.chosen-container .chosen-results li.active-result {
display: list-item;
cursor: pointer;
}
.chosen-container .chosen-results li.disabled-result {
display: list-item;
color: #ccc;
cursor: default;
}
.chosen-container .chosen-results li.highlighted {
background-color: #3875d7;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
color: #fff;
}
.chosen-container .chosen-results li.no-results {
color: #777;
display: list-item;
background: #f4f4f4;
}
.chosen-container .chosen-results li.group-result {
display: list-item;
font-weight: bold;
cursor: default;
}
.chosen-container .chosen-results li.group-option {
padding-left: 15px;
}
.chosen-container .chosen-results li em {
font-style: normal;
text-decoration: underline;
}

/* @end */
/* @group Multi Chosen */
.chosen-container-multi .chosen-choices {
position: relative;
overflow: hidden;
margin: 0;
padding: 0 5px;
width: 100%;
height: auto !important;
height: 1%;
border: 1px solid #aaa;
background-color: #fff;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
cursor: text;
}
.chosen-container-multi .chosen-choices li {
float: left;
list-style: none;
}
.chosen-container-multi .chosen-choices li.search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
margin: 1px 0;
padding: 0;
height: 25px;
outline: 0;
border: 0 !important;
background: transparent !important;
box-shadow: none;
color: #999;
font-size: 100%;
font-family: sans-serif;
line-height: normal;
border-radius: 0;
}
.chosen-container-multi .chosen-choices li.search-choice {
position: relative;
margin: 3px 5px 3px 0;
padding: 3px 20px 3px 5px;
border: 1px solid #aaa;
max-width: 100%;
border-radius: 3px;
background-color: #eeeeee;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-size: 100% 19px;
background-repeat: repeat-x;
background-clip: padding-box;
box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
color: #333;
line-height: 13px;
cursor: default;
}
.chosen-container-multi .chosen-choices li.search-choice span {
word-wrap: break-word;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
position: absolute;
top: 4px;
right: 3px;
display: block;
width: 12px;
height: 12px;
background: url('chosen-sprite.png') -42px 1px no-repeat;
font-size: 1px;
}
.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-choices li.search-choice-disabled {
padding-right: 5px;
border: 1px solid #ccc;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
color: #666;
}
.chosen-container-multi .chosen-choices li.search-choice-focus {
background: #d4d4d4;
}
.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
background-position: -42px -10px;
}
.chosen-container-multi .chosen-results {
margin: 0;
padding: 0;
}
.chosen-container-multi .chosen-drop .result-selected {
display: list-item;
color: #ccc;
cursor: default;
}