diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..f2a5aece --- /dev/null +++ b/.dockerignore @@ -0,0 +1,50 @@ +# .dockerignore + +# Ignore Python bytecode files +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Ignore virtual environment directories +venv/ +*.virtualenv/ +.env/ + +# Ignore Django migration files +*/migrations/*.pyc +*/migrations/__pycache__/ + +# Ignore logs +logs/ +*.log + +# Ignore configuration files +*.ini + +# Ignore user-specific files (e.g., editor settings) +*.swp +*.swo +*.swn +*.bak +*.tmp +*.sublime* +*.vscode/ + +# Ignore local media files +media/ + +# Ignore local database files (SQLite) +*.sqlite3 +*.sqlite3-journal + +# Ignore test coverage reports +.coverage +htmlcov/ + +# Ignore build artifacts and distribution files +build/ +dist/ +*.egg-info/ +*.egg +*.wheel diff --git a/.editorconfig b/.editorconfig index c3f7261e..80aa3020 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,16 +12,6 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.py] -# https://github.com/timothycrosley/isort/wiki/isort-Settings -line_length=120 -known_first_party=junction -multi_line_output=3 -default_section=THIRDPARTY -import_heading_stdlib=Standard Library -import_heading_firstparty=Junction Stuff -import_heading_thirdparty=Third Party Stuff - [*.yml] indent_style = space indent_size = 2 diff --git a/.env.sample b/.env.sample new file mode 100644 index 00000000..e88b734a --- /dev/null +++ b/.env.sample @@ -0,0 +1,27 @@ +DEBUG=TRUE +POSTGRES_USER=postgres +POSTGRES_PASSWORD=junction +POSTGRES_DB=junction +HOST_NAME=db +DB_PORT=5432 +REDIS_HOST_PASSWORD=password +BROKER_URL=redis://:password@redis:6379/0 +CELERY_RESULT_BACKEND=redis://:password@redis:6379/0 +SITE_PREFIX= +SITE_NAME=junction +SERVER_PORT=8888 +GOOGLE_ANALYTICS_ID=google_analytics_id +FACEBOOK_APP_ID=fb_app_id +EMAIL_HOST_USER=email_host_user +EMAIL_HOST_PASSWORD=email_host_pass +SECRET_KEY=secret_key +GITHUB_CLIENT_ID=github_client_id +GITHUB_CLIENT_SECRET=github_client_secret +GOOGLE_CLIENT_ID=google_oauth_client_id +GOOGLE_CLIENT_SECRET=google_oauth_client_secret +TWITTER_CONSUMER_KEY=twitter_consume_key +TWITTER_CONSUMER_SECRET=twitter_consume_secret +TWITTER_ACCESS_TOKEN_KEY=twitter_access_token +TWITTER_ACCESS_TOKEN_SECRET=twitter_access_token_secret +USE_ASYNC_FOR_EMAIL=boolean +DJANGO_LOG_LEVEL=DEBUG diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 21256661..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b15afc93 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Code of Conduct + +Please note that all interactions related to Junction are covered by +the [PSF Code of Conduct](https://www.python.org/psf/codeofconduct/), +which includes all infrastructure used in the development of Junction +(e.g. mailing lists, issue trackers, GitHub, etc.). + +In general this means everyone is expected to be open, considerate, and +respectful of others no matter what their position is within the project. diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 00000000..9738b711 --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,53 @@ +Thank you for your interest in contributing to Junction. We welcome all +contributions and greatly appreciate your effort! + +Bugs and Features +----------------- + +If you have found any bugs or would like to request a new feature, please do +check in the project's GitHub `issue tracker`_, if there is a similar existing +issue already filed. If not, please file a new issue. + +If you want to help out by fixing bugs, choose an issue from the `issue +tracker`_ to work on and claim it by posting a comment saying "I would like to +work on this.". Feel free to ask any doubts in the issue thread. + +Once you have implemented the feature to an extent, go ahead and file a pull +request by following the tips below. File a pull request early to get feedback +as early as possible. + +Pull Requests +------------- + +Pull Requests should be small to facilitate easier review. Keep them +self-contained, and limited in scope. Studies have shown that review quality +falls off as patch size grows. Sometimes this will result in many small PRs to +land a single large feature. +Checklist: + +1. Always create a new branch to work on a new issue:: + + $ git checkout -b + +2. Make sure your branch is up-to-date with upstream master before you file + a pull request. +3. All pull requests *must* be made against the ``master`` branch. +4. Include tests for any functionality you implement. Contributions that + improve existing tests are welcome. +5. Update documentation as necessary and provide documentation for any new + functionality. +6. In case of UI changes, please include screenshots. + +If you do make any changes to models (modification or addition), make sure to +run ``python manage.py makemigrations`` to enable the server to migrate existing +data to the new models. + +Code Convention +--------------- + +We follow the `Black Coding Style`_, and sort our imports with `isort`_. This +code style is enforced with automation. + +.. _`issue tracker`: https://github.com/pythonindia/junction/issues +.. _`isort`: https://isort.readthedocs.org/en/latest/ +.. _`Black Coding Style`: https://black.readthedocs.io/en/latest/the_black_code_style.html diff --git a/.gitignore b/.gitignore index a37dec95..89402d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ var/ *.egg-info/ .installed.cfg *.egg +.idea/* # PyInstaller # Usually these files are written by a python script from a template @@ -41,10 +42,13 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .cache +.pytest_cache/ nosetests.xml coverage.xml +temp/ # Translations *.mo @@ -66,7 +70,7 @@ target/ .pydevproject # SQLite DB -db.sqlite3 +*.sqlite3 # Logs debug.log @@ -76,5 +80,19 @@ debug.log # Node modules & bower components junction/static/node_modules/ -junction/static/bower_components/ +junction/static/bower_components _docs_html/ + +# generated by unit tests +qr_files/ + +# mypy cache +**/.mypy_cache + +#VSCode +.vscode/ + +tmp/ + +# Env +.env diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2dd651ac --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +repos: + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.5.0 + hooks: + - id: check-builtin-literals + - id: check-added-large-files + - id: check-case-conflict + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: trailing-whitespace + +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.4.1 + hooks: + - id: python-no-log-warn + - id: python-no-eval + - id: rst-backticks + +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.9 + hooks: + - id: flake8 + # These are only excluded because they're a mess right now. + exclude: settings/.* + +- repo: https://github.com/timothycrosley/isort + rev: 4.3.21 + hooks: + - id: isort + files: \.py$ + +- repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + language_version: python3.6 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..c128bc1f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +formats: + - epub + - pdf + +python: + version: 3.8 + install: + - requirements: tools/requirements-docs.txt + +sphinx: + configuration: docs/source/conf.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6db83cc8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: python -sudo: false -python: -- '2.7' -addons: - postgresql: "9.3" - -cache: - directories: - - $HOME/.pip-cache/ - -install: - - pip install -r requirements-dev.txt --allow-all-external --download-cache $HOME/.pip-cache - -script: -- flake8 -- coverage run --source=junction --omit='*tests*,*commands*,*migrations*,*admin*,*wsgi*' -m py.test -v --tb=native -- coverage report - -notifications: - email: - on_success: change # [always|never|change] - on_failure: always # [always|never|change] diff --git a/.travis.yml.old b/.travis.yml.old new file mode 100644 index 00000000..185e6654 --- /dev/null +++ b/.travis.yml.old @@ -0,0 +1,62 @@ +# System information +os: linux +addons: + postgresql: "12.2" +services: + - redis + +language: python +python: 3.5 +cache: pip + +# Use a two-stage approach, to minimize CI time on failures. +stages: +- primary +- secondary + +env: DJANGO_SETTINGS_MODULE="settings.test_settings" + +jobs: + include: + # Basic Checks + - stage: primary + env: NOXSESSION=lint-3.5 + - env: NOXSESSION=test-2.7 + - env: NOXSESSION=test-3.5 + - env: NOXSESSION=docs + + - stage: secondary + env: NOXSESSION=test-3.6 + python: 3.6 + - env: NOXSESSION=test-3.7 + python: 3.7 + +install: + # Enable Python 3.6.x, when python: 3.5 (because black is Py3.6+) + - | + if [ "${TRAVIS_PYTHON_VERSION}" == "3.5" ]; then + pyenv shell system:3.6.7 + fi + + # Get the latest pip + - python -m pip install --upgrade pip + # Reinstall pip, using the latest pip + - python -m pip install --force-reinstall pip + + # Install the CI dependencies + - python -m pip install celery==3.1.20 # pin from requirements.txt + - python -m pip install Django==1.9 # pin from requirements.txt + - python -m pip install nox + - python -m pip install coveralls + +before_script: + - celery -A junction worker -l info --detach +script: + - nox +after_success: + - coveralls + +notifications: + email: + on_success: change # [always|never|change] + on_failure: always # [always|never|change] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 21bd7b5b..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,40 +0,0 @@ -# Contributing - -All contributions are much welcome and greatly appreciated! Expect to be credited for you effort. - - -## General - -Generally try to limit the scope of any Pull Request to an atomic update if possible. This way, it's much easier to assess and review your changes. - -You should expect a considerably faster turn around if you submit two or more PRs instead of baking them all into one major PR. - - -## Pull Request Guidelines - -Before you submit a pull request, check that it meets these guidelines: - -1. All the pull requests are made against `master` branch. - -2. The pull request should include tests. - -3. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md. - -4. The pull request containing UI changes should have screen shots. - -5. If you are already not added to `CONTRIBUTORS.txt`, please add yourself in :) - -## Conventions - -- Read and pay attention to current code in the repository -- For the Python part, we follow pep8 in most cases. We use [`flake8`](http://flake8.readthedocs.org/en/latest/) to check for linting errors. Once you're ready to commit changes, check your code with `flake8` with this command - - - flake8 --max-complexity=24 --statistics --benchmark --ignore=E5,F4 / - -If there is any error, fix it and then commit. - -- For the Django part, we follow standard [Django coding style](https://docs.djangoproject.com/en/1.7/internals/contributing/writing-code/coding-style/). - -- If you are changing/creating any model, use `./manage.py makemigrations ` to generate the migrations. Send PR. Let other's review the models. - -- And always remember the Zen. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt deleted file mode 100644 index 3afa1e1a..00000000 --- a/CONTRIBUTORS.txt +++ /dev/null @@ -1,22 +0,0 @@ -Code Contributors -================= - -Sivasubramaniam Arunachalam (gh:sivaa / @sivaa_in)* -Saurabh Kumar (gh:theskumar / @_theskumar)* -Kracekumar Ramaraju (gh:kracekumar / @kracetheking)* -Kishor Bhat (gh:kbhat95) -Ganeshkumar S (gh:ganeshks) -Mudassir Ali (gh:mudassir0909) -Vignesh Sarma K (gh: vigneshsarma) -Anand Reddy Pandikunta (gh:ChillarAnand) -Anuvrat Parashar (gh:bhanuvrat/ @bhanuvrat) - -* Possesses commit rights - -Special Thanks -============== - -The following haven't provided code directly, but have provided guidance and advice. - -Vijay Bung (gh:vnbang2003 / @vnbang2003) - diff --git a/Dockerfile b/Dockerfile index 21c3dce2..42e6a179 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,32 @@ -FROM ubuntu:14.04 -MAINTAINER Anuvrat Parashar "anuvrat@anuvrat.in" +FROM python:3.10-slim-buster -RUN apt-get update && apt-get -y upgrade && apt-get install -y git python2.7 python-pip python-dev postgresql-server-dev-all -ADD requirements.txt /srv/requirements.txt -ADD requirements-dev.txt /srv/requirements-dev.txt -WORKDIR /srv/ -RUN pip install -r /srv/requirements.txt -RUN rm -rf /usr/local/lib/python2.7/dist-packages/requests -RUN pip install -r /srv/requirements-dev.txt +WORKDIR /code +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc \ + postgresql-client \ + build-essential \ + nodejs \ + npm \ + libpq-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt /code/ +RUN pip install --no-cache-dir -r requirements.txt + +# Install requirements for running tests +COPY ./tools/requirements-test.txt /code/ +RUN pip install --no-cache-dir -r requirements-test.txt + +RUN npm install -g yarn +RUN npm install -g grunt-cli + +COPY . /code/ + +RUN chmod +x bin/install-static.sh +RUN bin/install-static.sh +# not getting used at this moment +RUN chmod +x bin/wait-for-it.sh + +ENV PYTHONUNBUFFERED=1 diff --git a/LICENSE b/LICENSE index 804ccecb..b37ae8c4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-15 Python Software Society of India +Copyright (c) 2014-20 Python India Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..1693c49a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: uwsgi uwsgi.ini diff --git a/README.md b/README.md index 014befdd..7985b078 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,27 @@ -junction -======== +Junction +--- -[![Build Status](https://travis-ci.org/pythonindia/junction.svg)](https://travis-ci.org/pythonindia/junction) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pythonindia/junction?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -**Version**: 0.2.0-dev +[![Documentation Status](https://readthedocs.org/projects/in-junction/badge/?version=latest)](https://in-junction.readthedocs.io/en/latest/?badge=latest) Junction is a software to manage proposals, reviews, schedule, feedback during conference. -Setup -===== - -It is advised to install all the requirements inside [virtualenv], use [virtualenvwrapper] to manage virtualenvs. - -[virtualenv]: https://virtualenv.pypa.io/en/latest/ -[virtualenvwrapper]: https://virtualenvwrapper.readthedocs.org/en/latest/ - -``` -pip install -r requirements-dev.txt -cp settings/dev.py.sample settings/dev.py -python manage.py migrate --noinput -python manage.py sample_data -``` - -Initial auth data: admin/123123 - -If docker and fig are not installed already: --------------------------------------------- -Refer to (http://docs.docker.com/installation/) for detailed installation instructions. - -``` -curl -sSL https://get.docker.com/ubuntu/ | sudo sh -sudo pip install fig -``` - -Create aliases for docker and fig to avoid running them with sudo everytime. -Append the following lines to your ~/.bashrc or ~/.zshrc - -``` -alias docker='sudo docker' -alias fig='sudo fig' -``` - -Finally, run -``` -fig up -``` - - -Configuring Django-allauth ---------------------------- - - - Go to `http://localhost:8000/nimda/sites/site/` - - Change the default site's(the one with ID = 1) name and display to `localhost:8000` - - Go to `Social Applications` in admin panel and add [Github](http://django-allauth.readthedocs.org/en/latest/providers.html#github) and [Google](http://django-allauth.readthedocs.org/en/latest/providers.html#google)'s auth details - -Making Frontend Changes ---------------------------- -Make sure you have nodejs, npm, bower, grunt-cli & grunt installed - -``` -$ cd junction/static -$ npm install -$ bower install -$ grunt // This starts a watcher to watch for file changes -``` - - Contributing ------------ 1. Choose an [issue][issue-list] and ask any doubts in the issue thread. -2. Report any bugs/feature request as github [new issue][new-issue], if it's already not present. +2. Report any bugs/feature request as Github [new issue][new-issue], if it's already not present. 3. If you are starting to work on an issue, please leave a comment saying "I am working on it". -4. Once you are done with feature/bug fix, send a pull request according to the [guidelines]. +4. You can set up the project using the [Getting Started][getting-started] guide. +5. Once you are done with feature/bug fix, send a pull request according to the [guidelines][guidelines]. [issue-list]: https://github.com/pythonindia/junction/issues/ [new-issue]: https://github.com/pythonindia/junction/issues/new -[guidelines]: https://github.com/pythonindia/junction/blob/master/CONTRIBUTING.md +[guidelines]: .github/CONTRIBUTING.rst +[getting-started]: https://in-junction.readthedocs.io/en/latest/development/getting-started.html + +License +------- + +This software is licensed under The MIT License(MIT). See the [LICENSE][LICENSE] file in the top distribution directory for the full license text. + +[LICENSE]: https://github.com/pythonindia/junction/blob/master/LICENSE diff --git a/bin/install-static.sh b/bin/install-static.sh new file mode 100644 index 00000000..1294e028 --- /dev/null +++ b/bin/install-static.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd junction/static +yarn install +grunt less +cd ../.. diff --git a/bin/post_compile b/bin/post_compile new file mode 100755 index 00000000..5fd426cf --- /dev/null +++ b/bin/post_compile @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# Run Django migrations. +./manage.py migrate diff --git a/regenerate.sh b/bin/regenerate.sh similarity index 87% rename from regenerate.sh rename to bin/regenerate.sh index 8d9e742b..3eaaa4e4 100755 --- a/regenerate.sh +++ b/bin/regenerate.sh @@ -1,5 +1,8 @@ #!/bin/bash +echo "-> Install requirements:" +pip install -r requirements.txt + echo "-> Remove database:" rm db.sqlite3 diff --git a/bin/wait-for-it.sh b/bin/wait-for-it.sh new file mode 100755 index 00000000..cd9e315f --- /dev/null +++ b/bin/wait-for-it.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# wait-for-it.sh: Wait for a service to be ready. + +set -e + +host="$1" +port="$2" +shift 2 +cmd="$@" + +until PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$host" -U "$POSTGRES_USER" -c '\q'; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +>&2 echo "Postgres is up - executing command" +exec $cmd diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..22debc94 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,46 @@ +version: '3.8' + +services: + db: + image: postgres:15-alpine + ports: + - "5432:5432" + restart: always + volumes: + - postgres_data:/var/lib/postgresql/data/ + env_file: + - .env + + redis: + image: redis:latest + ports: + - "6379:6379" + restart: always + command: sh -c 'redis-server --requirepass ${REDIS_HOST_PASSWORD}' + + web: + image: ananyo2012/junction:1.1 + volumes: + - .:/code + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + restart: always + depends_on: + - db + env_file: + - .env + command: sh -c 'python manage.py migrate && python manage.py collectstatic --noinput --clear && gunicorn -c gunicorn.conf.py' + + celery: + image: ananyo2012/junction:1.1 + depends_on: + - db + - redis + - web + restart: always + env_file: + - .env + command: sh -c 'celery -A junction worker -l info -E' + +volumes: + postgres_data: diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 00000000..d72885fe --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,10 @@ +version: '3.8' + +services: + test: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./temp:/temp + command: sh -c 'pytest --junitxml=/temp/testresults/test-results.xml --cov=junction --cov-report=xml:/temp/testresults/coverage.xml -v' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..fe0eb06f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +version: '3.8' + +services: + db: + image: postgres:15-alpine + ports: + - "5432:5432" + restart: always + volumes: + - postgres_data:/var/lib/postgresql/data/ + env_file: + - .env + + redis: + image: redis:latest + ports: + - "6379:6379" + restart: always + command: sh -c 'redis-server --requirepass ${REDIS_HOST_PASSWORD}' + + web: + build: + context: . + dockerfile: Dockerfile + image: junction_local + volumes: + - .:/code + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + restart: always + depends_on: + - db + env_file: + - .env + command: sh -c 'python manage.py migrate && python manage.py runsslserver 0.0.0.0:${SERVER_PORT}' + + celery: + image: junction_local + depends_on: + - db + - redis + - web + restart: always + env_file: + - .env + command: sh -c 'celery -A junction worker -l info -E' + +volumes: + postgres_data: diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..6e47b4aa --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,23 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build +ALLSPHINXOPTS = -W -d $(BUILDDIR)/_doctrees/html $(SPHINXOPTS) + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help html Makefile + +html: + $(SPHINXBUILD) $(ALLSPHINXOPTS) -b html $(SOURCEDIR) $(BUILDDIR)/html + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..7665eb05 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/_static/images/add_new_proposal_reviewer.png b/docs/source/_static/images/add_new_proposal_reviewer.png new file mode 100644 index 00000000..5e03b8d9 Binary files /dev/null and b/docs/source/_static/images/add_new_proposal_reviewer.png differ diff --git a/docs/source/_static/images/add_proposal_section_reviewer.png b/docs/source/_static/images/add_proposal_section_reviewer.png new file mode 100644 index 00000000..de470945 Binary files /dev/null and b/docs/source/_static/images/add_proposal_section_reviewer.png differ diff --git a/docs/source/_static/images/list_reviews.png b/docs/source/_static/images/list_reviews.png new file mode 100644 index 00000000..937b8c88 Binary files /dev/null and b/docs/source/_static/images/list_reviews.png differ diff --git a/docs/source/_static/images/proposal_reviewers_list.png b/docs/source/_static/images/proposal_reviewers_list.png new file mode 100644 index 00000000..d06261e8 Binary files /dev/null and b/docs/source/_static/images/proposal_reviewers_list.png differ diff --git a/docs/source/_static/images/proposal_section_reviewer_list.png b/docs/source/_static/images/proposal_section_reviewer_list.png new file mode 100644 index 00000000..8a2e4947 Binary files /dev/null and b/docs/source/_static/images/proposal_section_reviewer_list.png differ diff --git a/docs/source/_static/images/reviewers_proposal_detail.png b/docs/source/_static/images/reviewers_proposal_detail.png new file mode 100644 index 00000000..d4b929b5 Binary files /dev/null and b/docs/source/_static/images/reviewers_proposal_detail.png differ diff --git a/docs/source/_static/images/reviewers_talk.png b/docs/source/_static/images/reviewers_talk.png new file mode 100644 index 00000000..5a0d5987 Binary files /dev/null and b/docs/source/_static/images/reviewers_talk.png differ diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..734b1e5f --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,48 @@ +project = "Junction" +copyright = "2019, Junction Developers" +author = "Junction Developers" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["recommonmark"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The suffix of source filenames. +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "alabaster" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# -- Recommonmark/Markdown stuff --------------------------------------------- +# NOTE: This entire section should be removed once the old/ folder is removed +# from the docs/ directory. + +from recommonmark.transform import AutoStructify # noqa: E402 + + +def setup(app): + app.add_config_value("recommonmark_config", {"enable_auto_toc_tree": True}, True) + app.add_transform(AutoStructify) diff --git a/docs/source/development/getting-started.rst b/docs/source/development/getting-started.rst new file mode 100644 index 00000000..3959f7b9 --- /dev/null +++ b/docs/source/development/getting-started.rst @@ -0,0 +1,89 @@ +=============== +Getting Started +=============== + +We’re pleased that you are interested in working on Junction. + +This document is meant to get you setup to work on Junction and to act as a +guide and reference to the the development setup. If you face any issues during +this process, please `open an issue`_ about it on the issue tracker. + + +Initial Setup +============= + +Junction's development workflow is automated using Docker with docker-compose. +We also use Docker for production deployment. + +To setup Docker and docker-compose locally follow the `Docker Getting started`_ doc. + +After Docker and docker-compose is setup locally follow these steps + +.. code-block:: console + + $ cp .env.sample .env + $ docker-compose build + $ docker-compose up -d + +This will build and run the application after running database migrations + +Access the application at https://localhost:8888 + +Backend +-------- + +Populate Database with sample data + +.. code-block:: console + + $ docker-compose exec web /bin/sh + # python manage.py sample_data + +Admin Access +^^^^^^^^^^^^ + +When sample data is generated, a superuser is created with the username ``admin`` and password ``123123``. +Go to https://localhost:8888/nimda to access Django Admin Panel + + +Development workflow +==================== + +Running tests +------------- + +For running the tests, run: + +.. code-block:: console + + $ docker-compose -f docker-compose.test.yml up -d + +Running linters +--------------- + +We use `pre-commit`_ for linting. Install pre-commit then run: + +.. code-block:: console + + $ pre-commit install + +This will install all linters in form of git hooks. To manually run the linter run: + +.. code-block:: console + + $ pre-commit run --all-files + +Building documentation +---------------------- + +For building the documentation, run: + +.. code-block:: console + + $ python -m pip install -r tools/requirements-docs.txt + $ cd docs + $ make html + +.. _`open an issue`: https://github.com/pythonindia/junction/issues +.. _`Docker Getting started`: https://www.docker.com/get-started +.. _`pre-commit`: https://pre-commit.com/ diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst new file mode 100644 index 00000000..b73c7711 --- /dev/null +++ b/docs/source/development/index.rst @@ -0,0 +1,13 @@ +Development +=========== + +Junction is a volunteer maintained open source project and we welcome +contributions of all forms. The sections below will help you get started with +development, testing, and documentation. + +.. toctree:: + :maxdepth: 2 + + getting-started + + nox-setup diff --git a/docs/source/development/nox-setup.rst b/docs/source/development/nox-setup.rst new file mode 100644 index 00000000..487cf62d --- /dev/null +++ b/docs/source/development/nox-setup.rst @@ -0,0 +1,130 @@ +========================== +Development Setup with nox +========================== + +**Note**: `nox`_ is the older way of setting up development environment. This section is stale. +If you find *nox* helpful consider updating this section + + +Initial Setup +============= + +Junction's development workflow is automated using `nox`_. Thus, you need +the ``nox`` command to be installed on your system. We recommend using ``pipx`` +to install ``nox`` in its own isolated environment. + +.. code-block:: console + + $ python -m pip install pipx + $ pipx install nox + +You will need to have a working Redis server on your system. You may +additionally want to install Postgres, although it is optional. + +.. note:: + + On Debian based systems, these can be installed using: + + .. code-block:: console + + $ sudo apt install redis-server postgres + +Backend +------- + +Create a "settings" file for local development with Django. + +.. code-block:: console + + $ cp settings/dev.py.sample settings/dev.py + +Create the database structure and populate it with sample data. + +.. code-block:: console + + $ nox -- migrate --noinput + $ nox -- sample_data + +Admin Access +^^^^^^^^^^^^ + +When sample data is generated with ``nox -- sample_data``, a superuser is +created with the username ``admin`` and password ``123123``. + + +Frontend +-------- + +Working on Junction's frontend requires `NodeJS`_ and `yarn`_ to be installed on your +system. The frontend is built using `grunt`_. To setup the working +environment, run the following: + +.. code-block:: console + + $ cd junction/static + $ yarn install + +Development workflow +==================== + +Frontend Autobuilding +--------------------- + +Junction has a Grunt configuration that is useful when working on the frontend. +The following command starts a build watcher which rebuilds the frontend on +every file change. + +.. code-block:: console + + $ grunt + +For ease of development ``app.css`` is checked in to the source code. It is not +recommended to directly make changes to ``app.css``, rather update the less files +and run ``grunt``, then commit ``app.css`` + +Invoking ``manage.py`` +---------------------- + +Junction's ``nox`` configuration is set up to invoke manage.py when no other +session (i.e. ``-s ...``) is specified. This also automatically sets up an +isolated environment that contains the dependencies of Junction. + +.. code-block:: console + + $ nox # equivalent to 'python manage.py' + $ nox -- runserver # equivalent to 'python manage.py runserver' + $ nox -- migrate # equivalent to 'python manage.py migrate' + +Running tests +------------- + +For running the tests, run: + +.. code-block:: console + + $ nox -s test + +Running linters +--------------- + +For running the linters, run: + +.. code-block:: console + + $ nox -s lint + +Building documentation +---------------------- + +For building the documentation, run: + +.. code-block:: console + + $ nox -s docs + +.. _`open an issue`: https://github.com/pythonindia/junction/issues +.. _`virtualenv`: https://virtualenv.pypa.io/en/stable/ +.. _`nox`: https://nox.readthedocs.io/en/stable/ +.. _`NodeJS`: https://nodejs.org/ +.. _`yarn`: https://yarnpkg.com/ +.. _`grunt`: https://gruntjs.com/ diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..6056fec0 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,21 @@ +Junction +======== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + development/index + +.. toctree:: + :glob: + :hidden: + + old/index + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/old/api.md b/docs/source/old/api.md new file mode 100644 index 00000000..cba51222 --- /dev/null +++ b/docs/source/old/api.md @@ -0,0 +1,206 @@ +# API + +**NOTE: This document and linked sections may be out of date.** + +Junction provides API to access information about the conference, schedule, and feedback. The API is for mobile clients to assist conference attendees. All the request and response format is `application/json`. + +- Demo site: `http://junctiondemo.herokuapp.com/` + + +### Conference - List + +- Endpoint: `/api/v1/conferences/` + +- Allowed Method: `GET`. + +- Returns all conferences. There is no pagination. + +- Sample Response: + +``` + +[ { "id": 5, "name": "PyCon India 2016", "slug": "2016", "description": "...", "start_date": "2016-09-23", "end_date": "2016-09-25", "status": "Proposal submission closed", "venue": "http://in.pycon.org/cfp/api/v1/venues/2/" },] + +``` + +- `venue` key holds URL of the venue details. + +### Venue - List + +- Endpoint: `/api/v1/venues/` + +- Allowed Methods: `GET` + +- List all the venues. + +- Sample Response: + +``` + +[ { "id": 1, "name": "Nihmans", "address": " Hosur Road, Lakkasandra, Behind Bus Stop, Bengaluru, Karnataka 560030", "latitude": "12.943112200000000", "longitudes": "77.5968643000000000" }, ] + +``` + +### Venue - Detail + +- Endpoint: `/api/v1/venues//` + +- Allowed Method: `GET` + +- Return specific venue details. + +- Sample Response: + +``` + +{ "id": 1, "name": "Nihmans", "address": " Hosur Road, Lakkasandra, Behind Bus Stop, Bengaluru, Karnataka 560030", "latitude": "12.943112200000000", "longitudes": "77.5968643000000000" } + +``` + +### Room - List + +- Endpoint: `/api/v1/rooms/` + +- Allowed Method: `GET` + +- List all rooms of all venues.. + +- Sample Response: + +``` + +[ { "id": 4, "name": "Buffet Area", "venue": "http://in.pycon.org/cfp/api/v1/venues/1/", "note": "Left end of the ground floor" },..] + +``` + +### Room - Venue specific + +- Endpoint: `/api/v1/rooms/?venue=` + +- Display list of rooms in the venue. + +- Allowed Method: `GET` + +- Sample Response: + +``` + +[ { "id": 4, "name": "Buffet Area", "venue": "http://in.pycon.org/cfp/api/v1/venues/1/", "note": "Left end of the ground floor" }, { "id": 3, "name": "Room 3", "venue": "http://in.pycon.org/cfp/api/v1/venues/1/", "note": "Ground Floor" }, { "id": 2, "name": "Room 2", "venue": "http://in.pycon.org/cfp/api/v1/venues/1/", "note": "Ground Floor" }, { "id": 1, "name": "Room 1", "venue": "http://in.pycon.org/cfp/api/v1/venues/1/", "note": "Ground Floor" } ] + +``` + +### Schedule: List + +- Endpoint: `/api/v1/schedules/` + +- Allowed Methods: `GET` + +- All the schedule items of all conferences. + +- Sample Response: + +``` + +{ "2015-10-04": { "08:30:00 - 09:15:00": [ { "conference": "http://in.pycon.org/cfp/api/v1/conferences/1/", "session": { "description": "" }, "room_id": 4, "end_time": "09:15:00", "event_date": "2015-10-04", "start_time": "08:30:00", "type": "Break", "id": 37, "name": "Registration & Breakfast" } ], "09:30:00 - 10:15:00": [ { "conference": "http://in.pycon.org/cfp/api/v1/conferences/1/", "session": { "description": "" }, "room_id": 1, "end_time": "10:15:00", "event_date": "2015-10-04", "start_time": "09:30:00", "type": "Talk", "id": 38, "name": "Keynote - Nicholas H.Tollervey" } ], + +``` + +- Response data keys are conference day date and its schedule grouped by time. + +- `2015-10-04` - Conference day date. + +- `08:30:00 - 09:15:00` - Start of the session - End of the session. + +- `session` dictionary contains details about the session. + +- `type`: Type of the session like `break, lunch, talk, workshop` etc ... + +- `room_id`: Place of the session. The Client should use API or cached data to fetch room name. + +### Schedule: List of sessions for the conference + +- Endpoint: `/api/v1/schedules/?conference=` + +- Allowed Methods: `GET` + +- All the schedule items of the conference. + +- Sample Response: + +``` + +{ "2015-10-04": { "08:30:00 - 09:15:00": [ { "conference": "http://in.pycon.org/cfp/api/v1/conferences/1/", "session": { "description": "" }, "room_id": 4, "end_time": "09:15:00", "event_date": "2015-10-04", "start_time": "08:30:00", "type": "Break", "id": 37, "name": "Registration & Breakfast" } ], "09:30:00 - 10:15:00": [ { "conference": "http://in.pycon.org/cfp/api/v1/conferences/1/", "session": { "description": "" }, "room_id": 1, "end_time": "10:15:00", "event_date": "2015-10-04", "start_time": "09:30:00", "type": "Talk", "id": 38, "name": "Keynote - Nicholas H.Tollervey" } ],} + +``` + +### Device - Register + +- Endpoint: `/api/v1/devices/` + +- Allowed Method: `POST` + +- Junction accepts feedback via API. We support anonymous feedback, but the device registration is mandatory. Registered device can only submit the feedback. + +- Payload: `{'uuid': 'uuid-1'}`. + +- Response: If UUID exists status code is `400` else the status code is `201` with data. + +### Feedback Questions - List + +- Endpoint: `/api/v1/feedback_questions/?conference_id=` + +- Allowed Method: `GET` + +- Fetch all feedback questions for the conference. + +- Sample Response: + +``` +{ "Workshop": { "text": [ { "schedule_item_type": "Workshop", "is_required": false, "type": "text", "id": 2, "title": "Any other feedback for workshop ?" } ], "choice": [ { "title": "Does the speaker have experience on the subject?", "schedule_item_type": "Workshop", "allowed_choices": [ { "id": 15, "value": 2, "title": "Good" }, { "id": 14, "value": 1, "title": "Ok" }, { "id": 13, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 5 }, { "title": "How hands on was the workshop ?", "schedule_item_type": "Workshop", "allowed_choices": [ { "id": 6, "value": 2, "title": "Good" }, { "id": 5, "value": 1, "title": "Ok" }, { "id": 4, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 2 }, { "title": "How was the content ?", "schedule_item_type": "Workshop", "allowed_choices": [ { "id": 3, "value": 2, "title": "Good" }, { "id": 2, "value": 1, "title": "Ok" }, { "id": 1, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 1 } ] }, "Talk": { "text": [ { "schedule_item_type": "Talk", "is_required": false, "type": "text", "id": 1, "title": "Any other feedback for the talk ?" } ], "choice": [ { "title": "Does the speaker have experience on the subject?", "schedule_item_type": "Talk", "allowed_choices": [ { "id": 18, "value": 2, "title": "Good" }, { "id": 17, "value": 1, "title": "Ok" }, { "id": 16, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 6 }, { "title": "How was the presentation ?", "schedule_item_type": "Talk", "allowed_choices": [ { "id": 12, "value": 2, "title": "Good" }, { "id": 11, "value": 1, "title": "Ok" }, { "id": 10, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 4 }, { "title": "How was the content ?", "schedule_item_type": "Talk", "allowed_choices": [ { "id": 9, "value": 2, "title": "Good" }, { "id": 8, "value": 1, "title": "Ok" }, { "id": 7, "value": 0, "title": "Bad" } ], "is_required": true, "type": "choice", "id": 3 } ] } } + +``` + +- Response data keys are session types. `Workshop` and `Talk` session type questions are values of the respective key. Each of the session types contains two keys, `text` and `choice`. `text` dictionary contains text type questions and `choice` dictionary contains choice based questions. Both the question type has some common fields. `id` is the unique identifier of the question. `title` is the displayable text of the question. `is_required` is the boolean field, `true` means feedback should have an answer for the item. `choice` questions have `allowed_choices`, which contains allowed values. Each item is a dictionary containing `id`, `title`, `value`. Use `title` to display the answer. + +### Feedback submission + +- Endpoint: `/api/v1/feedback` + +- Allowed Methods: `POST`. + +- Header: 'Token: `. + +- Sample Payload: + +``` +{'text': [{'text': 'Ok', 'id': 1}], 'schedule_item_id': 1, 'choices': [{'id': 1, 'value_id': 1}]} + +``` + +- `schedule_item_id` is the `id` of the session accepting feedback. The payload contains `text` and `choices` answers. + +- When request succeeds, the status code is `200` and when the request fails, the status code is `400` or `403`. When the input data is incorrect code is `400` and `403` when device token is missing. + +- Sample success response: 201 + +``` + +{'text': [{'text': 'Ok', 'id': 1}], 'schedule_item_id': 1, 'choices': [{'id': 1, 'value_id': 1}]} + +``` + +- Sample failure response: 400 + +``` + +{'choices': [{u'non_field_errors': [u"The multiple choice value isn't associated with question"]}]} + +``` + +- Sample failure response: 403 + +``` + +{u'detail': u'Authentication credentials were not provided.'} + +``` diff --git a/docs/source/old/conference_reviewers.md b/docs/source/old/conference_reviewers.md new file mode 100644 index 00000000..0ecb3ee9 --- /dev/null +++ b/docs/source/old/conference_reviewers.md @@ -0,0 +1,22 @@ +# Add conference moderators + +**NOTE: This document and linked sections may be out of date.** + +First add users to `Proposal Reviewers` model and assign them to `Proposal Sections` as reviewers. + +![List Proposal Reviewers](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_proposal_reviewers_list.png) +![Add proposal Reviewers](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_add_new_proposal_reviewer.png) + +- Assign unique name to reviewer. This name will be shown in review comments to proposers. All reviewers can see other reviewers comments. Section reviewers can only add reviews. + +![List Proposal Section Reviewer](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_proposal_section_reviewer_list.png) +![Add Proposal Section Reviewer](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_add_proposal_section_reviewer.png) + + +## Reviewer Comments + +On the proposal page, reviewers can see all `Reviews` and `Reviewers Talk` section. + +![Proposal Page](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_reviewers_proposal_detail.png) +![Proposal Review](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_list_reviews.png) +![Proposal Reviewer Talk](https://raw.githubusercontent.com/pythonindia/junction/master/docs/_static/images/_reviewers_talk.png) diff --git a/docs/index.md b/docs/source/old/index.md similarity index 83% rename from docs/index.md rename to docs/source/old/index.md index 4c2b2232..3f916343 100644 --- a/docs/index.md +++ b/docs/source/old/index.md @@ -1,8 +1,14 @@ -# Junction [![Build Status](https://travis-ci.org/pythonindia/junction.svg)](https://travis-ci.org/pythonindia/junction) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pythonindia/junction?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Junction (old) + +[![Build Status](https://travis-ci.org/pythonindia/junction.svg)](https://travis-ci.org/pythonindia/junction) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pythonindia/junction?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +**NOTE: This document and linked sections may be out of date.** ---- +* [API](../api.md) +* [Release notes](../release_notes.md) +* [Conference reviewers](../conference_reviewers.md) **Version**: 0.2.0-dev @@ -16,7 +22,7 @@ It is advised to install all the requirements inside [virtualenv], use [virtuale [virtualenvwrapper]: https://virtualenvwrapper.readthedocs.org/en/latest/ ``` -pip install -r requirements-dev.txt +pip install -r requirements.txt cp settings/dev.py.sample settings/dev.py python manage.py migrate --noinput python manage.py sample_data @@ -26,7 +32,7 @@ Initial auth data: admin/123123 ### Configuring Django-allauth - - Go to `http://localhost:8000/nimda/sites/site/` + - Go to `http://localhost:8000/nimda/sites/site/` - Change the default site's(the one with ID = 1) name and display to `localhost:8000` - Go to `Social Applications` in admin panel and add [Github](http://django-allauth.readthedocs.org/en/latest/providers.html#github) and [Google](http://django-allauth.readthedocs.org/en/latest/providers.html#google)'s auth details @@ -47,7 +53,7 @@ $ grunt // This starts a watcher to watch for file changes 1. Choose an [issue][issue-list] and ask any doubts in the issue thread. 2. Report any bugs/feature request as github [new issue][new-issue], if it's already not present. 3. If you are starting to work on an issue, please leave a comment saying "I am working on it". -4. Once you are done with feature/bug fix, send a pull request according to the [guidelines]. +4. Once you are done with feature/bug fix, send a pull request according to the [guidelines]. [issue-list]: https://github.com/pythonindia/junction/issues/ [new-issue]: https://github.com/pythonindia/junction/issues/new diff --git a/docs/release_notes.md b/docs/source/old/release_notes.md similarity index 60% rename from docs/release_notes.md rename to docs/source/old/release_notes.md index 8cb21c9e..c5d11254 100644 --- a/docs/release_notes.md +++ b/docs/source/old/release_notes.md @@ -1,5 +1,33 @@ # Release Notes +**NOTE: This document and linked sections may be out of date.** + + +## [0.4.0][0.4.0] + +- Fix filters on the proposal review form #418 (@ChillarAnand) +- Update proposal urls to use hashids. The slug will now update itself when the title of proposal changes. +- Conference duration is displayed in a short form. +- Add ability to remove a casted vote on a proposal. +- ... + +## [0.3.0][0.3.0] + +__Date:__ 31st March 2016 + +__Features__ + +- conference moderators can be added per conference using a `conference_moderator` management command. +- Improved conference admin. +- Basic REST API for accessing conference details and proposals. +- REST API to submit feedback for a talk/workshop. +- UI updates + +__Fixes__ +- fix the tag filtering for all the proposal sections inside proposal list (#170) +- Update proposal detail styling (#207) + + ## [0.2.0][0.2.0] __Date:__ 29th March 2015 @@ -8,7 +36,7 @@ __Added__ - add support for fig based development environment (#129) - django admin got a new theme based on django-flat-theme - setup social sharing on proposal detail pages (#185) -- add `SITE_URL` settings to support path based root url of site. +- add `SITE_URL` settings to support path based root url of site. - add docker/fig setup - add celery support - send mail to reviewers for new proposals @@ -28,6 +56,8 @@ __Date:__ 8th February 2015 - initial release with core functionality working +[0.4.0]: https://github.com/pythonindia/junction/compare/0.3.0...master +[0.3.0]: https://github.com/pythonindia/junction/compare/0.2.0...0.3.0 [0.2.0]: https://github.com/pythonindia/junction/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/pythonindia/junction/issues?q=milestone%3A%22release+0.1.0+-+initial+release%22 diff --git a/fig.yml b/fig.yml deleted file mode 100644 index a44b7e97..00000000 --- a/fig.yml +++ /dev/null @@ -1,30 +0,0 @@ -redis: - image: redis:2.8.17 - volumes: - - /var/docker/junction/redis/:/data/ - ports: - - "6380:6379" - privileged: true - -web: - build: . - command: python manage.py runserver 0.0.0.0:8000 - volumes: - - .:/srv/ - links: - - redis:redis - ports: - - "8000:8000" - privileged: true - -celery: - image: junction_web - command: celery worker -A junction -l info - volumes: - - .:/srv/ - links: - - redis:redis - - web:web - environment: - - C_FORCE_ROOT=true - diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 00000000..5e469558 --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,7 @@ +import os +port = os.environ.get("SERVER_PORT", "8888") + +wsgi_app = "wsgi" +bind = f"0.0.0.0:{port}" +workers = 2 +loglevel = "debug" diff --git a/junction/__init__.py b/junction/__init__.py index 02fab240..fa726c3f 100644 --- a/junction/__init__.py +++ b/junction/__init__.py @@ -1,4 +1,11 @@ # -*- coding: utf-8 -*- +__version__ = "0.3.2" +__version_info__ = tuple( + [ + int(num) if num.isdigit() else num + for num in __version__.replace("-", ".", 1).split(".") + ] +) # This will make sure the app is always imported when # Django starts so that shared_task will use this app. diff --git a/junction/base/admin.py b/junction/base/admin.py index b8bdfdbe..66935741 100644 --- a/junction/base/admin.py +++ b/junction/base/admin.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.contrib import admin @@ -17,12 +16,18 @@ def save_model(self, request, obj, form, change): class TimeAuditAdmin(admin.ModelAdmin): - list_display = ('created_at', 'modified_at',) + list_display = ( + "created_at", + "modified_at", + ) class AuditAdmin(TimeAuditAdmin): - list_display = ('created_by', 'modified_by',) + TimeAuditAdmin.list_display - exclude = ('created_by', 'modified_by',) + list_display = ("created_by", "modified_by",) + TimeAuditAdmin.list_display + exclude = ( + "created_by", + "modified_by", + ) def save_model(self, request, obj, form, change): save_model(self, request, obj, form, change) diff --git a/junction/base/apps.py b/junction/base/apps.py index 065172e8..1929dacb 100644 --- a/junction/base/apps.py +++ b/junction/base/apps.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -# Standard Library import sys -# Third Party Stuff from django.apps import AppConfig from . import monkey diff --git a/junction/base/constants.py b/junction/base/constants.py index 58319bdf..3feb5529 100644 --- a/junction/base/constants.py +++ b/junction/base/constants.py @@ -1,53 +1,115 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Conference Application Choice Fields -CONFERENCE_STATUS_ACCEPTING_CFP = "Accepting Call for Proposals" -CONFERENCE_STATUS_CLOSED_CFP = "Closed for Proposals" -CONFERENCE_STATUS_ACCEPTING_VOTES = "Accepting Votes" -CONFERENCE_STATUS_SCHEDULE_PUBLISHED = "Schedule Published" - -CONFERENCE_STATUS_LIST = ((1, CONFERENCE_STATUS_ACCEPTING_CFP), - (2, CONFERENCE_STATUS_CLOSED_CFP), - (3, CONFERENCE_STATUS_ACCEPTING_VOTES), - (4, CONFERENCE_STATUS_SCHEDULE_PUBLISHED), - ) - -# Proposal Application Choice Fields -PROPOSAL_STATUS_DRAFT = 1 -PROPOSAL_STATUS_PUBLIC = 2 -PROPOSAL_STATUS_CANCELLED = 3 - -PROPOSAL_STATUS_LIST = ((PROPOSAL_STATUS_PUBLIC, "Public"), - (PROPOSAL_STATUS_DRAFT, "Draft"), - (PROPOSAL_STATUS_CANCELLED, "Cancelled"), - ) - -PROPOSAL_REVIEW_STATUS_YET_TO_BE_REVIEWED = 1 -PROPOSAL_REVIEW_STATUS_SELECTED = 2 -PROPOSAL_REVIEW_STATUS_REJECTED = 3 -PROPOSAL_REVIEW_STATUS_ON_HOLD = 4 -PROPOSAL_REVIEW_STATUS_WAIT_LISTED = 5 - -PROPOSAL_REVIEW_STATUS_LIST = ((PROPOSAL_REVIEW_STATUS_YET_TO_BE_REVIEWED, "Yet to be reviewed"), - (PROPOSAL_REVIEW_STATUS_SELECTED, "Selected"), - (PROPOSAL_REVIEW_STATUS_REJECTED, "Rejected"), - (PROPOSAL_REVIEW_STATUS_ON_HOLD, "On hold"), - (PROPOSAL_REVIEW_STATUS_WAIT_LISTED, "Wait-listed"), - ) - -PROPOSAL_TARGET_AUDIENCE_BEGINNER = "Beginner" -PROPOSAL_TARGET_AUDIENCE_INTERMEDIATE = "Intermediate" -PROPOSAL_TARGET_AUDIENCE_ADVANCED = "Advanced" - -PROPOSAL_TARGET_AUDIENCES = ((1, PROPOSAL_TARGET_AUDIENCE_BEGINNER), - (2, PROPOSAL_TARGET_AUDIENCE_INTERMEDIATE), - (3, PROPOSAL_TARGET_AUDIENCE_ADVANCED), - ) - -PROPOSAL_USER_VOTE_ROLE_PUBLIC = "Public" -PROPOSAL_USER_VOTE_ROLE_REVIEWER = "Reviewer" - -PROPOSAL_USER_VOTE_ROLES = ((1, PROPOSAL_USER_VOTE_ROLE_PUBLIC), - (2, PROPOSAL_USER_VOTE_ROLE_REVIEWER), - ) +import inspect + + +def _user_attributes(cls): + defaults = dir(type(str("defaults"), (object,), {})) # gives all inbuilt attrs + return [item[0] for item in inspect.getmembers(cls) if item[0] not in defaults] + + +def choices(cls): + """ + Decorator to set `CHOICES` and other attributes + """ + _choices = [] + for attr in _user_attributes(cls): + val = getattr(cls, attr) + setattr(cls, attr[1:], val[0]) + _choices.append((val[0], val[1])) + setattr(cls, "CHOICES", tuple(_choices)) + return cls + + +@choices +class ConferenceStatus: + _ACCEPTING_CFP = [1, "Accepting Proposals"] + _CLOSED_CFP = [2, "Proposal submission closed"] + _ACCEPTING_VOTES = [3, "Accepting Votes"] + _SCHEDULE_PUBLISHED = [4, "Schedule Published"] + + +@choices +class ProposalStatus: + _DRAFT = [1, "Draft"] + _PUBLIC = [2, "Public"] + _CANCELLED = [3, "Cancelled"] + + +@choices +class ProposalReviewStatus: + _YET_TO_BE_REVIEWED = [1, "Yet to be reviewed"] + _SELECTED = [2, "Selected"] + _REJECTED = [3, "Rejected"] + _ON_HOLD = [4, "On hold"] + _WAIT_LISTED = [5, "Wait-listed"] + + +@choices +class ProposalTargetAudience: + _BEGINNER = [1, "Beginner"] + _INTERMEDIATE = [2, "Intermediate"] + _ADVANCED = [3, "Advanced"] + + +@choices +class ProposalUserVoteRole: + _PUBLIC = [1, "Public"] + _REVIEWER = [2, "Reviewer"] + + +@choices +class ProposalReviewVote: + _MUST_HAVE = [2, "Must have"] + _GOOD = [1, "Good"] + _NOT_BAD = [0, "Not Bad"] + _NOT_ALLOWED = [-1, "Shouldn't be allowed"] + + +# FIXME: `ProposalReviewerComment` should be Boolean +@choices +class ProposalReviewerComment: + _COMMENTED = ["True", "Yes"] + _NOT_COMMENTED = ["False", "No"] + + +@choices +class ProposalVotesFilter: + _NO_VOTES = [0, "No votes"] + _MIN_ONE_VOTE = [1, "Minimum 1 vote"] + _SORT_BY_SUM = [2, "Sort by total votes"] + _SORT_BY_REVIEWER = [3, "Sort by your votes"] + _SORT_BY_SELECTION = [4, "Sort by selection"] + + +class ConferenceSettingConstants: + ALLOW_PUBLIC_VOTING_ON_PROPOSALS = { + "name": "allow_public_voting_on_proposals", + "value": True, + "description": "Allow public to vote on proposals", + } + + DISPLAY_PROPOSALS_IN_PUBLIC = { + "name": "display_proposals_in_public", + "value": True, + "description": "Display proposals in public", + } + + ALLOW_PLUS_ZERO_REVIEWER_VOTE = { + "name": "allow_plus_zero_reviewer_vote", + "value": True, + "description": "Allow +0 vote in reviewer votes", + } + + +@choices +class PSRVotePhase: + _PRIMARY = [0, "Initial voting"] + _SECONDARY = [1, "Second phase voting"] + + +@choices +class ProposalCommentType: + _GENERAL = [0, "All general comments"] + _SECONDARY_VOTING = [1, "Second phase voting"] diff --git a/junction/base/context_processors.py b/junction/base/context_processors.py index d3d70504..baa17751 100644 --- a/junction/base/context_processors.py +++ b/junction/base/context_processors.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.conf import settings def site_info(request): - return {'SITE_INFO': settings.SITE_VARIABLES} + return {"SITE_INFO": settings.SITE_VARIABLES} diff --git a/junction/base/emailer.py b/junction/base/emailer.py index 86d71f69..7a2b41bd 100644 --- a/junction/base/emailer.py +++ b/junction/base/emailer.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Standard Library from os import path -# Third Party Stuff from django.conf import settings from django.core.mail import send_mail from django.template.loader import render_to_string @@ -21,17 +19,24 @@ def send_email(to, context, template_dir): :rtype: None """ - expected_templates = {'message': 'message.txt', 'subject': 'subject.txt', - 'html_message': 'message.html'} - kwargs = {key: render_to_string(path.join(template_dir, template), - context).strip() - for key, template in expected_templates.items()} + def to_str(template_name): + return render_to_string(path.join(template_dir, template_name), context).strip() - return send_mail(from_email=settings.DEFAULT_FROM_EMAIL, - recipient_list=[_format_email(to)], **kwargs) + subject = to_str("subject.txt") + text_message = to_str("message.txt") + html_message = to_str("message.html") + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [_format_email(to)] + + return send_mail( + subject, text_message, from_email, recipient_list, html_message=html_message + ) def _format_email(user): - return user.email if user.first_name and user.last_name else \ - '"{} {}" <{}>'.format(user.first_name, user.last_name, user.email) + return ( + user.email + if user.first_name and user.last_name + else '"{} {}" <{}>'.format(user.first_name, user.last_name, user.email) + ) diff --git a/junction/base/models.py b/junction/base/models.py index f388dc1e..fdc9c9b1 100644 --- a/junction/base/models.py +++ b/junction/base/models.py @@ -1,36 +1,46 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.contrib.auth.models import User from django.db import models class TimeAuditModel(models.Model): + """To track when the record was created and last modified + """ - """ To track when the record was created and last modified """ - created_at = models.DateTimeField( - auto_now_add=True, verbose_name="Created At",) - modified_at = models.DateTimeField( - auto_now=True, verbose_name="Last Modified At") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created At") + modified_at = models.DateTimeField(auto_now=True, verbose_name="Last Modified At") class Meta: abstract = True class UserAuditModel(models.Model): - - """ To track who created and last modified the record """ - created_by = models.ForeignKey(User, related_name='created_%(class)s_set', - null=True, blank=True, verbose_name="Created By") - modified_by = models.ForeignKey(User, related_name='updated_%(class)s_set', - null=True, blank=True, verbose_name="Modified By") + """ To track who created and last modified the record + """ + + created_by = models.ForeignKey( + User, + related_name="created_%(class)s_set", + null=True, + blank=True, + verbose_name="Created By", + on_delete=models.SET_NULL, + ) + modified_by = models.ForeignKey( + User, + related_name="updated_%(class)s_set", + null=True, + blank=True, + verbose_name="Modified By", + on_delete=models.SET_NULL, + ) class Meta: abstract = True class AuditModel(TimeAuditModel, UserAuditModel): - class Meta: abstract = True diff --git a/junction/base/monkey.py b/junction/base/monkey.py index f5bd8014..89fb475f 100644 --- a/junction/base/monkey.py +++ b/junction/base/monkey.py @@ -28,20 +28,23 @@ def new_render(cls, context): def patch_urlresolvers(): - from django.core import urlresolvers # noqa + from django.urls import get_urlconf, get_resolver # noqa from django.conf import settings # noqa - - if hasattr(urlresolvers, "_patched"): + urlconf = get_urlconf() + resolver = get_resolver(urlconf) + if hasattr(resolver, "_patched"): return - old_reverse = urlresolvers.reverse + old_reverse = resolver.reverse - def new_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None): - path = old_reverse(viewname, urlconf=urlconf, args=args, kwargs=kwargs, prefix=prefix, current_app=current_app) + def new_reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None): + path = old_reverse( + viewname, urlconf=urlconf, args=args, kwargs=kwargs, current_app=current_app + ) if is_absolute_url(path): return path return settings.SITE_URL + path - urlresolvers._patched = True - urlresolvers.reverse = new_reverse + resolver._patched = True + resolver.reverse = new_reverse diff --git a/junction/base/templatetags/__init__.py b/junction/base/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/base/templatetags/date.py b/junction/base/templatetags/date.py new file mode 100644 index 00000000..c15375c3 --- /dev/null +++ b/junction/base/templatetags/date.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +from __future__ import unicode_literals + +from datetime import date + +import arrow +from django import template + +register = template.Library() + + +@register.filter +def fromnow(value): + """ + A wrapper around arrow.humanize(), returns natural time which is less precise than + django's naturaltime filter. It doesn't display weeks and combination of days & hours. + """ + if not ( + isinstance(value, date) or isinstance(value, arrow.Arrow) + ): # datetime is a subclass of date + return value + + return arrow.get(value).humanize() diff --git a/junction/base/utils.py b/junction/base/utils.py new file mode 100644 index 00000000..10c70725 --- /dev/null +++ b/junction/base/utils.py @@ -0,0 +1,24 @@ +import datetime as dt + + +def get_date_diff_display(start, end): + if end.year != start.year: + return f"{start.strftime('%d %b %Y')} - {end.strftime('%d %b %Y')}" + + # year are same now + if end.month != start.month: + return f"{start.strftime('%d %b')} - {end.strftime('%d %b')}, {start.year}" + + # month and year are same now + if end.day != start.day: + return f"{start.strftime('%d')} - {end.strftime('%d')} {start.strftime('%b')}, {start.year}" + + # day, month and year are same now + if isinstance(start, dt.date): + return f"{start.strftime('%d %b %Y')}" + + # am/pm, day, month and year are same now + if end.strftime("%p") != start.strftime("%p"): + return f"{start.strftime('%I:%M%p')} - {end.strftime('%I:%M%p')}, {start.strftime('%d %b %Y')}" + + return f"{start.strftime('%I:%M')} - {end.strftime('%I:%M')}{start.strftime('%p, %d %b %Y')}" diff --git a/junction/base/views.py b/junction/base/views.py index db284219..a5d30a18 100644 --- a/junction/base/views.py +++ b/junction/base/views.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt diff --git a/junction/celery_app.py b/junction/celery_app.py index 56b7955d..a966aef6 100644 --- a/junction/celery_app.py +++ b/junction/celery_app.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Standard Library import os -# Third Party Stuff from celery import Celery from django.conf import settings # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") -app = Celery('junction') +app = Celery("junction") # Using a string here means the worker will not have to # pickle the object when using Windows. -app.config_from_object('settings') +app.config_from_object("settings") app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) diff --git a/junction/conferences/admin.py b/junction/conferences/admin.py index a02bfb32..4abc9e21 100644 --- a/junction/conferences/admin.py +++ b/junction/conferences/admin.py @@ -1,28 +1,74 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin -# Junction Stuff from junction.base.admin import AuditAdmin -from . import models +from . import models, service class ConferenceAdmin(AuditAdmin): - list_display = ('name', 'slug', 'start_date', 'end_date', 'status') + AuditAdmin.list_display - prepopulated_fields = {'slug': ('name',), } + list_display = ( + "name", + "slug", + "start_date", + "end_date", + "status", + ) + AuditAdmin.list_display + prepopulated_fields = { + "slug": ("name",), + } + + def get_queryset(self, request): + qs = super(ConferenceAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(moderators__in=moderators) class ConferenceModeratorAdmin(AuditAdmin): - list_display = ('conference', 'moderator', 'active') + AuditAdmin.list_display + list_display = ("conference", "moderator", "active") + AuditAdmin.list_display + list_filter = ("conference",) + + def get_queryset(self, request): + qs = super(ConferenceModeratorAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + + +class ConferenceProposallReviewerAdmin(AuditAdmin, SimpleHistoryAdmin): + list_display = ("conference", "reviewer", "active") + AuditAdmin.list_display + autocomplete_fields = ("reviewer",) + list_filter = ("conference",) + + def get_queryset(self, request): + qs = super(ConferenceProposallReviewerAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + +class ConferenceSettingAdmin(AuditAdmin, SimpleHistoryAdmin): + list_display = ("conference", "name", "value") + AuditAdmin.list_display + list_filter = ("conference",) -class ConferenceProposallReviewerAdmin(AuditAdmin): - list_display = ('conference', 'reviewer', 'active') + AuditAdmin.list_display + def get_queryset(self, request): + qs = super(ConferenceSettingAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) admin.site.register(models.Conference, ConferenceAdmin) admin.site.register(models.ConferenceModerator, ConferenceModeratorAdmin) admin.site.register(models.ConferenceProposalReviewer, ConferenceProposallReviewerAdmin) +admin.site.register(models.ConferenceVenue) +admin.site.register(models.Room) +admin.site.register(models.ConferenceSetting, ConferenceSettingAdmin) diff --git a/junction/conferences/management/commands/conference_moderator.py b/junction/conferences/management/commands/conference_moderator.py new file mode 100644 index 00000000..9a5f273a --- /dev/null +++ b/junction/conferences/management/commands/conference_moderator.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Permission +from django.core.management.base import BaseCommand, CommandError + +from junction.conferences.models import Conference + +APP_PERMISSIONS = { + "conferences": [ + "change_conferencesetting", + "add_conferencevenue", + "change_conferencevenue", + "delete_conferencevenue", + "change_conference", + "add_conferencemoderator", + "change_conferencemoderator", + "delete_conferencemoderator", + "add_conferencemoderator", + "change_conferencemoderator", + "delete_conferencemoderator", + "add_conferenceproposalreviewer", + "change_conferenceproposalreviewer", + "delete_conferenceproposalreviewer", + "add_room", + "change_room", + "delete_room", + ], + "proposals": [ + "add_proposalsection", + "change_proposalsection", + "delete_proposalsection", + "add_proposalsectionreviewer", + "change_proposalsectionreviewer", + "delete_proposalsectionreviewer", + "add_proposaltype", + "change_proposaltype", + "delete_proposaltype", + "change_proposal", + "change_proposalvote", + "add_proposalsectionreviewervotevalue", + "change_proposalsectionreviewervotevalue", + "add_proposalsectionreviewervote", + "change_proposalsectionreviewervote", + "add_proposalcomment", + "change_proposalcomment", + "add_proposalcommentvote", + "change_proposalcommentvote", + ], + "schedule": [ + "add_scheduleitem", + "change_scheduleitem", + "delete_scheduleitem", + "add_scheduleitemtype", + "change_scheduleitemtype", + "delete_scheduleitemtype", + ], + "devices": ["add_device", "change_device", "delete_device"], + "feedback": [ + "add_textfeedbackquestion", + "change_textfeedbackquestion", + "delete_textfeedbackquestion", + "add_choicefeedbackquestion", + "change_choicefeedbackquestion", + "delete_choicefeedbackquestion", + "add_scheduleitemtextfeedback", + "change_scheduleitemtextfeedback", + "delete_scheduleitemtextfeedback", + "add_scheduleitemchoicefeedback", + "change_scheduleitemchoicefeedback", + "delete_scheduleitemchoicefeedback", + "add_choicefeedbackquestionvalue", + "change_choicefeedbackquestionvalue", + "delete_choicefeedbackquestionvalue", + ], + "tickets": ["add_ticket", "change_ticket", "delete_ticket"], +} + + +class Command(BaseCommand): + def add_argument(self, parser): + parser.add_argument("slug", nargs=1, type=str) + parser.add_argument("email", nargs=1, type=str) + + def has_conference(self, slug): + try: + conference = Conference.objects.get(slug=slug) + return conference + except Conference.DoesNotExist: + raise CommandError('Conference "{}" does not exist'.format(slug)) + + def create_user(self, email): + Users = get_user_model() + try: + user = Users.objects.get(email=email) + user.is_staff = True + user.is_active = True + user.save() + return user + except Users.DoesNotExist: + raise CommandError("User '{}' doesn't exist".format(user)) + + def add_permissions(self, user): + for app_label, permissions in APP_PERMISSIONS.items(): + for perm in permissions: + term = ".".join([app_label, perm]) + if user.has_perm(term): + print("User has perm: '{}'".format(term)) + else: + print("User doesn't have perm: '{}'".format(term)) + permission = Permission.objects.get(codename=perm) + user.user_permissions.add(permission) + print("Added permission '{}'".format(permission)) + + def handle(self, *args, **kwargs): + # Check conference and short circuit if missing + if len(args) < 2: + print("Two arguments are required") + return + self.has_conference(slug=args[0]) + # Check and create user + user = self.create_user(email=args[1]) + # Add all models to user permission + self.add_permissions(user=user) diff --git a/junction/conferences/management/commands/sample_data.py b/junction/conferences/management/commands/sample_data.py index 58cf42c9..01cafe86 100644 --- a/junction/conferences/management/commands/sample_data.py +++ b/junction/conferences/management/commands/sample_data.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals, print_function +from __future__ import absolute_import, print_function, unicode_literals -# Standard Library import datetime import random -# Third Party Stuff +import six from allauth.account.models import EmailAddress from django.conf import settings from django.contrib.auth import get_user_model @@ -15,17 +14,27 @@ from django.utils.timezone import now from sampledatahelper.helper import SampleDataHelper -# Junction Stuff from junction.base import constants from junction.conferences.models import Conference -from junction.proposals.models import ProposalSection, ProposalType +from junction.proposals.models import ( + Proposal, + ProposalComment, + ProposalSection, + ProposalSectionReviewerVoteValue, + ProposalType, +) +from junction.schedule.models import ScheduleItem -NUM_USERS = getattr(settings, "SAMPLE_DATA_NUM_USERS", 10) -NUM_CONFERENCES = getattr(settings, "SAMPLE_DATA_NUM_CONFERENCES", 4) -NUM_EMPTY_CONFERENCES = getattr(settings, "SAMPLE_DATA_NUM_EMPTY_CONFERENCES", - 2) +NUM_USERS = getattr(settings, "NUM_USERS", 10) +NUM_CONFERENCES = getattr(settings, "NUM_CONFERENCES", 4) +NUM_EMPTY_CONFERENCES = getattr(settings, "NUM_EMPTY_CONFERENCES", 2) NUM_PROPOSAL_SECTIONS = getattr(settings, "NUM_PROPOSAL_SECTIONS", 5) NUM_PROPOSAL_TYPES = getattr(settings, "NUM_PROPOSAL_TYPES", 8) +NUM_PUBLIC_PROPOSALS = getattr(settings, "NUM_PUBLIC_PROPOSALS", 20) +NUM_DRAFT_PROPOSALS = getattr(settings, "NUM_DRAFT_PROPOSALS", 7) +NUM_CANCELLED_PROPOSALS = getattr(settings, "NUM_CANCELLED_PROPOSALS", 7) +NUM_PUBLIC_COMMENTS = getattr(settings, "NUM_PUBLIC_COMMENTS", 10) +NUM_REVIEWER_COMMENTS = getattr(settings, "NUM_REVIEWER_COMMENTS", 10) class Command(BaseCommand): @@ -35,42 +44,42 @@ class Command(BaseCommand): def handle(self, *args, **options): self.users = [] + self.conferences = [] + self.proposals = [] + self.proposal_reviewers = [] - # Update site url - print(' Updating domain to localhost:8000') + print(" Updating domain to localhost:8000") # Update site url site = Site.objects.get_current() - site.domain = 'localhost:8000' - site.name = 'Local' + site.domain, site.name = "localhost:8000", "Local" site.save() - # create superuser - print(' Creating Superuser') - super_user = self.create_user(is_superuser=True, username='admin', - is_active=True) - EmailAddress.objects.get_or_create(user=super_user, - verified=True, - primary=True, - email=super_user.email) - - # create users - print(' Creating sample Users') + print(" Creating Superuser") # create superuser + super_user = self.create_user( + is_superuser=True, username="admin", is_active=True + ) + EmailAddress.objects.get_or_create( + user=super_user, verified=True, primary=True, email=super_user.email + ) + + print(" Creating sample Users") # create users for x in range(NUM_USERS): self.users.append(self.create_user(counter=x)) - print(' Creating proposal sections') + print(" Creating proposal sections") self.proposal_sections = self.create_proposal_sections() - print(' Create proposal types') + print(" Create proposal types") self.proposal_types = self.create_proposal_types() # create conferences - print(' Creating sample Conferences') + print(" Creating sample Conferences") for x in range(NUM_CONFERENCES + NUM_EMPTY_CONFERENCES): conference = self.create_conference(x) + self.conferences.append(conference) if x < NUM_CONFERENCES: self.create_moderators(conference) - self.create_propsoal_reviewers(conference) + self.proposal_reviewers = self.create_propsoal_reviewers(conference) # attach all proposal sections for section in self.proposal_sections: @@ -80,86 +89,182 @@ def handle(self, *args, **options): for proposal_type in self.proposal_types: conference.proposal_types.add(proposal_type) + # create proposals + print(" Creating sample proposals") + for x in range(NUM_PUBLIC_PROPOSALS): + self.proposals.append(self.create_proposal(proposal_type="Public")) + + for x in range(NUM_DRAFT_PROPOSALS): + self.proposals.append(self.create_proposal(proposal_type="Draft")) + + for x in range(NUM_CANCELLED_PROPOSALS): + self.proposals.append(self.create_proposal(proposal_type="Cancelled")) + + print(" Create sample Schedule") + for proposal in self.proposals: + if not proposal.get_status_display() == "Public": + continue + self.create_scheduled_item(proposal=proposal) + self.create_scheduled_item(proposal="Break", conference=self.conferences[0]) + + # create comments + print(" Creating sample proposal comments") + for x in range(NUM_PUBLIC_COMMENTS): + self.create_proposal_comment(users=self.users) + + reviewers = [i.reviewer for i in self.proposal_reviewers] + for x in range(NUM_REVIEWER_COMMENTS): + self.create_proposal_comment(users=reviewers) + + print(" Creating default choices for proposal reviewer vote values.") + for vote in constants.ProposalReviewVote.CHOICES: + ProposalSectionReviewerVoteValue.objects.create( + vote_value=vote[0], description=vote[1] + ) + def create_proposal_sections(self): sections = [] for count in range(NUM_PROPOSAL_SECTIONS): - sections.append(ProposalSection.objects.create(**{ - 'name': "Proposal Section %d" % count, - 'description': "Proposal Section %d description" % count, - 'active': True - })) + sections.append( + ProposalSection.objects.create( + **{ + "name": "Proposal Section %d" % count, + "description": "Proposal Section %d description" % count, + "active": True, + } + ) + ) return sections def create_proposal_types(self): types = [] for count in range(NUM_PROPOSAL_TYPES): - types.append(ProposalType.objects.create(**{ - 'name': "Proposal Type %d" % count, - 'description': "Proposal Section %d description" % count, - 'active': True - })) + types.append( + ProposalType.objects.create( + **{ + "name": "Proposal Type %d" % count, + "description": "Proposal Section %d description" % count, + "active": True, + } + ) + ) return types def create_moderators(self, conference): moderators = [] count = self.sd.int(1, len(self.users)) for user in random.sample(self.users, count): - moderators.append(conference.moderators.create( - moderator=user, active=self.sd.boolean())) + moderators.append( + conference.moderators.create(moderator=user, active=self.sd.boolean()) + ) return moderators def create_propsoal_reviewers(self, conference): proposal_reviewers = [] count = self.sd.int(1, len(self.users)) for user in random.sample(self.users, count): - proposal_reviewers.append(conference.proposal_reviewers.create( - reviewer=user, active=self.sd.boolean())) + proposal_reviewers.append( + conference.proposal_reviewers.create( + reviewer=user, active=self.sd.boolean() + ) + ) return proposal_reviewers + def create_scheduled_item(self, proposal, conference=None): + kwargs = {} + kwargs["conference"] = conference if conference else proposal.conference + kwargs["event_date"] = kwargs["conference"].start_date + kwargs["start_time"] = datetime.datetime.time(datetime.datetime.now()) + kwargs["end_time"] = datetime.datetime.time( + datetime.datetime.now() + datetime.timedelta(minutes=45) + ) + if isinstance(proposal, six.string_types): + ScheduleItem.objects.create(alt_name=proposal, **kwargs) + else: + ScheduleItem.objects.create(session=proposal, **kwargs) + def create_conference(self, counter, start_date=None, end_date=None): if counter == 0: min_days_from_creation = 30 - start_date = now() + datetime.timedelta( - days=min_days_from_creation) + start_date = now() + datetime.timedelta(days=min_days_from_creation) # 2 day conference - end_date = now() + datetime.timedelta( - days=min_days_from_creation + 2) + end_date = now() + datetime.timedelta(days=min_days_from_creation + 2) else: start_date = start_date or now() + datetime.timedelta( - random.randrange(-55, 55)) + random.randrange(-55, 55) + ) end_date = end_date or start_date + datetime.timedelta( - random.randrange(1, 4)) + random.randrange(1, 4) + ) conference = Conference.objects.create( - name='%s Conference' % self.sd.words(1, 2).title(), + name="%s Conference" % self.sd.words(1, 2).title(), description=self.sd.paragraph(), - status=self.sd.choices_key(constants.CONFERENCE_STATUS_LIST), + status=self.sd.choices_key(constants.ConferenceStatus.CHOICES), start_date=start_date, end_date=end_date, created_by=self.sd.choice(self.users), - modified_by=self.sd.choice(self.users)) + modified_by=self.sd.choice(self.users), + ) return conference def create_user(self, counter=None, username=None, email=None, **kwargs): counter = counter or self.sd.int() params = { - "username": username or 'user{0}'.format(counter), - "first_name": kwargs.get('first_name', self.sd.name("us", 1)), - "last_name": kwargs.get('last_name', self.sd.surname("us", 1)), + "username": username or "user{0}".format(counter), + "first_name": kwargs.get("first_name", self.sd.name("us", 1)), + "last_name": kwargs.get("last_name", self.sd.surname("us", 1)), "email": email or self.sd.email(), - "is_active": kwargs.get('is_active', self.sd.boolean()), - "is_superuser": kwargs.get('is_superuser', False), - "is_staff": kwargs.get('is_staff', kwargs.get('is_superuser', self.sd.boolean())), + "is_active": kwargs.get("is_active", self.sd.boolean()), + "is_superuser": kwargs.get("is_superuser", False), + "is_staff": kwargs.get( + "is_staff", kwargs.get("is_superuser", self.sd.boolean()) + ), } - user = get_user_model().objects.create(**params) - password = '123123' + user, _ = get_user_model().objects.get_or_create(**params) + password = "123123" user.set_password(password) user.save() - print("User created with username: {username} and password: {password}".format( - username=params.get('username'), password=password)) + print( + "User created with username: {username} and password: {password}".format( + username=params.get("username"), password=password + ) + ) return user + + def create_proposal(self, proposal_type): + + status = next( + (i[0] for i in constants.ProposalStatus.CHOICES if i[1] == proposal_type) + ) + + proposal = Proposal.objects.create( + conference=self.sd.choice(self.conferences), + proposal_section=self.sd.choice(self.proposal_sections), + proposal_type=self.sd.choice(self.proposal_types), + author=self.sd.choice(self.users), + title="%s Proposal" % self.sd.words(1, 2).title(), + description=self.sd.paragraph(), + status=status, + deleted=self.sd.boolean(), + ) + + return proposal + + def create_proposal_comment(self, users): + + commenter = self.sd.choice(users) + + comment = ProposalComment.objects.create( + proposal=self.sd.choice(self.proposals), + private=self.sd.boolean(), + comment="Comment", + commenter=commenter, + ) + + return comment diff --git a/junction/conferences/migrations/0001_initial.py b/junction/conferences/migrations/0001_initial.py index e67a140b..6217f050 100644 --- a/junction/conferences/migrations/0001_initial.py +++ b/junction/conferences/migrations/0001_initial.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff import django_extensions.db.fields from django.conf import settings from django.db import migrations, models @@ -15,61 +14,216 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Conference', + name="Conference", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('name', models.CharField(max_length=255, verbose_name='Conference Name')), - ('slug', django_extensions.db.fields.AutoSlugField(editable=False, populate_from=('name',), max_length=255, blank=True, unique=True)), - ('description', models.TextField(default='')), - ('start_date', models.DateField(verbose_name='Start Date')), - ('end_date', models.DateField(verbose_name='End Date')), - ('status', models.PositiveSmallIntegerField(verbose_name='Current Status', choices=[(1, b'Accepting Call for Proposals'), (2, b'Closed for Proposals'), (3, b'Accepting Votes'), (4, b'Schedule Published')])), - ('deleted', models.BooleanField(default=False, verbose_name='Is Deleted?')), - ('created_by', models.ForeignKey(related_name='created_conference_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_conference_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "name", + models.CharField(max_length=255, verbose_name="Conference Name"), + ), + ( + "slug", + django_extensions.db.fields.AutoSlugField( + editable=False, + populate_from=("name",), + max_length=255, + blank=True, + unique=True, + ), + ), + ("description", models.TextField(default="")), + ("start_date", models.DateField(verbose_name="Start Date")), + ("end_date", models.DateField(verbose_name="End Date")), + ( + "status", + models.PositiveSmallIntegerField( + verbose_name="Current Status", + choices=[ + (1, b"Accepting Call for Proposals"), + (2, b"Closed for Proposals"), + (3, b"Accepting Votes"), + (4, b"Schedule Published"), + ], + ), + ), + ( + "deleted", + models.BooleanField(default=False, verbose_name="Is Deleted?"), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conference_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conference_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ConferenceModerator', + name="ConferenceModerator", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('conference', models.ForeignKey(to='conferences.Conference')), - ('created_by', models.ForeignKey(related_name='created_conferencemoderator_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('moderator', models.ForeignKey(to=settings.AUTH_USER_MODEL)), - ('modified_by', models.ForeignKey(related_name='updated_conferencemoderator_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conferencemoderator_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "moderator", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferencemoderator_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ConferenceProposalReviewer', + name="ConferenceProposalReviewer", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('conference', models.ForeignKey(to='conferences.Conference')), - ('created_by', models.ForeignKey(related_name='created_conferenceproposalreviewer_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_conferenceproposalreviewer_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('reviewer', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conferenceproposalreviewer_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferenceproposalreviewer_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "reviewer", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='conferenceproposalreviewer', - unique_together=set([('conference', 'reviewer')]), + name="conferenceproposalreviewer", + unique_together=set([("conference", "reviewer")]), ), ] diff --git a/junction/conferences/migrations/0002_auto_20150109_1527.py b/junction/conferences/migrations/0002_auto_20150109_1527.py index c59ba0cc..9bfee079 100644 --- a/junction/conferences/migrations/0002_auto_20150109_1527.py +++ b/junction/conferences/migrations/0002_auto_20150109_1527.py @@ -1,39 +1,49 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('conferences', '0001_initial'), + ("conferences", "0001_initial"), ] operations = [ migrations.AlterModelOptions( - name='conferencemoderator', - options={'verbose_name': 'moderator', 'verbose_name_plural': 'moderators'}, + name="conferencemoderator", + options={"verbose_name": "moderator", "verbose_name_plural": "moderators"}, ), migrations.AlterModelOptions( - name='conferenceproposalreviewer', - options={'verbose_name': 'proposals reviewer', 'verbose_name_plural': 'proposals reviewers'}, + name="conferenceproposalreviewer", + options={ + "verbose_name": "proposals reviewer", + "verbose_name_plural": "proposals reviewers", + }, ), migrations.AlterField( - model_name='conferencemoderator', - name='conference', - field=models.ForeignKey(related_name='moderators', to='conferences.Conference'), + model_name="conferencemoderator", + name="conference", + field=models.ForeignKey( + related_name="moderators", + to="conferences.Conference", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AlterField( - model_name='conferenceproposalreviewer', - name='conference', - field=models.ForeignKey(related_name='proposal_reviewers', to='conferences.Conference'), + model_name="conferenceproposalreviewer", + name="conference", + field=models.ForeignKey( + related_name="proposal_reviewers", + to="conferences.Conference", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AlterUniqueTogether( - name='conferencemoderator', - unique_together=set([('conference', 'moderator')]), + name="conferencemoderator", + unique_together=set([("conference", "moderator")]), ), ] diff --git a/junction/conferences/migrations/0003_auto_20150113_1120.py b/junction/conferences/migrations/0003_auto_20150113_1120.py index 5c911f77..8cb85e64 100644 --- a/junction/conferences/migrations/0003_auto_20150113_1120.py +++ b/junction/conferences/migrations/0003_auto_20150113_1120.py @@ -1,19 +1,23 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('conferences', '0002_auto_20150109_1527'), + ("conferences", "0002_auto_20150109_1527"), ] operations = [ migrations.AlterModelOptions( - name='conference', - options={'get_latest_by': 'start_date', 'verbose_name': 'Conference', 'ordering': ('-start_date', 'name'), 'verbose_name_plural': 'Conferences'}, + name="conference", + options={ + "get_latest_by": "start_date", + "verbose_name": "Conference", + "ordering": ("-start_date", "name"), + "verbose_name_plural": "Conferences", + }, ), ] diff --git a/junction/conferences/migrations/0004_conference_logo.py b/junction/conferences/migrations/0004_conference_logo.py index e287fd34..8b949507 100644 --- a/junction/conferences/migrations/0004_conference_logo.py +++ b/junction/conferences/migrations/0004_conference_logo.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff import uuid_upload_path.storage from django.db import migrations, models @@ -9,14 +8,16 @@ class Migration(migrations.Migration): dependencies = [ - ('conferences', '0003_auto_20150113_1120'), + ("conferences", "0003_auto_20150113_1120"), ] operations = [ migrations.AddField( - model_name='conference', - name='logo', - field=models.ImageField(blank=True, null=True, upload_to=uuid_upload_path.storage.upload_to), + model_name="conference", + name="logo", + field=models.ImageField( + blank=True, null=True, upload_to=uuid_upload_path.storage.upload_to + ), preserve_default=True, ), ] diff --git a/junction/conferences/migrations/0005_emailreviewernotificationsetting.py b/junction/conferences/migrations/0005_emailreviewernotificationsetting.py index b07a296f..eabd51a1 100644 --- a/junction/conferences/migrations/0005_emailreviewernotificationsetting.py +++ b/junction/conferences/migrations/0005_emailreviewernotificationsetting.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.conf import settings from django.db import migrations, models @@ -10,28 +9,81 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('proposals', '0003_auto_20150113_1401'), - ('conferences', '0004_conference_logo'), + ("proposals", "0003_auto_20150113_1401"), + ("conferences", "0004_conference_logo"), ] operations = [ migrations.CreateModel( - name='EmailReviewerNotificationSetting', + name="EmailReviewerNotificationSetting", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('action', models.CharField(max_length=15)), - ('status', models.BooleanField(default=True)), - ('conference_reviewer', models.ForeignKey(to='conferences.ConferenceProposalReviewer')), - ('created_by', models.ForeignKey(related_name='created_emailreviewernotificationsetting_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_emailreviewernotificationsetting_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('proposal_section', models.ForeignKey(to='proposals.ProposalSection')), - ('proposal_type', models.ForeignKey(to='proposals.ProposalType')), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("action", models.CharField(max_length=15)), + ("status", models.BooleanField(default=True)), + ( + "conference_reviewer", + models.ForeignKey( + to="conferences.ConferenceProposalReviewer", + on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_emailreviewernotificationsetting_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_emailreviewernotificationsetting_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "proposal_section", + models.ForeignKey( + to="proposals.ProposalSection", + on_delete=models.deletion.CASCADE, + ), + ), + ( + "proposal_type", + models.ForeignKey( + to="proposals.ProposalType", on_delete=models.deletion.CASCADE, + ), + ), ], options={ - 'verbose_name': 'email notification', - 'verbose_name_plural': 'email notifications', + "verbose_name": "email notification", + "verbose_name_plural": "email notifications", }, bases=(models.Model,), ), diff --git a/junction/conferences/migrations/0006_auto_20150216_1929.py b/junction/conferences/migrations/0006_auto_20150216_1929.py index 4f0566d7..55bf3a7f 100644 --- a/junction/conferences/migrations/0006_auto_20150216_1929.py +++ b/junction/conferences/migrations/0006_auto_20150216_1929.py @@ -1,38 +1,30 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('conferences', '0005_emailreviewernotificationsetting'), + ("conferences", "0005_emailreviewernotificationsetting"), ] operations = [ migrations.RemoveField( - model_name='emailreviewernotificationsetting', - name='conference_reviewer', + model_name="emailreviewernotificationsetting", name="conference_reviewer", ), migrations.RemoveField( - model_name='emailreviewernotificationsetting', - name='created_by', + model_name="emailreviewernotificationsetting", name="created_by", ), migrations.RemoveField( - model_name='emailreviewernotificationsetting', - name='modified_by', + model_name="emailreviewernotificationsetting", name="modified_by", ), migrations.RemoveField( - model_name='emailreviewernotificationsetting', - name='proposal_section', + model_name="emailreviewernotificationsetting", name="proposal_section", ), migrations.RemoveField( - model_name='emailreviewernotificationsetting', - name='proposal_type', - ), - migrations.DeleteModel( - name='EmailReviewerNotificationSetting', + model_name="emailreviewernotificationsetting", name="proposal_type", ), + migrations.DeleteModel(name="EmailReviewerNotificationSetting",), ] diff --git a/junction/conferences/migrations/0007_auto_20150522_2358.py b/junction/conferences/migrations/0007_auto_20150522_2358.py new file mode 100644 index 00000000..f944988b --- /dev/null +++ b/junction/conferences/migrations/0007_auto_20150522_2358.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0006_auto_20150216_1929"), + ] + + operations = [ + migrations.AlterField( + model_name="conference", + name="status", + field=models.PositiveSmallIntegerField( + verbose_name="Current Status", + choices=[ + (1, "Accepting Call for Proposals"), + (2, "Closed for Proposals"), + (3, "Accepting Votes"), + (4, "Schedule Published"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0008_auto_20150601_1436.py b/junction/conferences/migrations/0008_auto_20150601_1436.py new file mode 100644 index 00000000..543e25b5 --- /dev/null +++ b/junction/conferences/migrations/0008_auto_20150601_1436.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0007_auto_20150522_2358"), + ] + + operations = [ + migrations.AlterField( + model_name="conference", + name="status", + field=models.PositiveSmallIntegerField( + verbose_name="Current Status", + choices=[ + (1, "Accepting Proposals"), + (2, "Proposal submission closed"), + (3, "Accepting Votes"), + (4, "Schedule Published"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0009_conferenceproposalreviewer_nick.py b/junction/conferences/migrations/0009_conferenceproposalreviewer_nick.py new file mode 100644 index 00000000..6a55eea9 --- /dev/null +++ b/junction/conferences/migrations/0009_conferenceproposalreviewer_nick.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0008_auto_20150601_1436"), + ] + + operations = [ + migrations.AddField( + model_name="conferenceproposalreviewer", + name="nick", + field=models.CharField( + default="Reviewer", max_length=255, verbose_name="Nick Name" + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0010_auto_20150713_2331.py b/junction/conferences/migrations/0010_auto_20150713_2331.py new file mode 100644 index 00000000..a7168cc3 --- /dev/null +++ b/junction/conferences/migrations/0010_auto_20150713_2331.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("conferences", "0009_conferenceproposalreviewer_nick"), + ] + + operations = [ + migrations.CreateModel( + name="ConferenceVenue", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("name", models.CharField(max_length=100)), + ("address", models.TextField()), + ("latitude", models.DecimalField(max_digits=17, decimal_places=15)), + ("longitudes", models.DecimalField(max_digits=19, decimal_places=16)), + ( + "created_by", + models.ForeignKey( + related_name="created_conferencevenue_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferencevenue_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.CreateModel( + name="Room", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("name", models.CharField(max_length=100)), + ("note", models.CharField(max_length=255)), + ( + "created_by", + models.ForeignKey( + related_name="created_room_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_room_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "venue", + models.ForeignKey( + to="conferences.ConferenceVenue", + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.AddField( + model_name="conference", + name="venue", + field=models.ForeignKey( + to="conferences.ConferenceVenue", + null=True, + on_delete=models.deletion.CASCADE, + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0011_auto_20150729_0131.py b/junction/conferences/migrations/0011_auto_20150729_0131.py new file mode 100644 index 00000000..7036e800 --- /dev/null +++ b/junction/conferences/migrations/0011_auto_20150729_0131.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0010_auto_20150713_2331"), + ] + + operations = [ + migrations.AlterField( + model_name="conference", + name="status", + field=models.PositiveSmallIntegerField( + verbose_name="Current Status", + choices=[ + (1, "Accepting Proposals"), + (3, "Accepting Votes"), + (2, "Proposal submission closed"), + (4, "Schedule Published"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0012_historicalconferenceproposalreviewer.py b/junction/conferences/migrations/0012_historicalconferenceproposalreviewer.py new file mode 100644 index 00000000..11e54c91 --- /dev/null +++ b/junction/conferences/migrations/0012_historicalconferenceproposalreviewer.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("conferences", "0011_auto_20150729_0131"), + ] + + operations = [ + migrations.CreateModel( + name="HistoricalConferenceProposalReviewer", + fields=[ + ( + "id", + models.IntegerField( + verbose_name="ID", db_index=True, auto_created=True, blank=True + ), + ), + ( + "created_at", + models.DateTimeField( + verbose_name="Created At", editable=False, blank=True + ), + ), + ( + "modified_at", + models.DateTimeField( + verbose_name="Last Modified At", editable=False, blank=True + ), + ), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "nick", + models.CharField( + default="Reviewer", max_length=255, verbose_name="Nick Name" + ), + ), + ("history_id", models.AutoField(serialize=False, primary_key=True)), + ("history_date", models.DateTimeField()), + ( + "history_type", + models.CharField( + max_length=1, + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + ), + ), + ( + "conference", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="conferences.Conference", + null=True, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "history_user", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "reviewer", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ], + options={ + "ordering": ("-history_date", "-history_id"), + "get_latest_by": "history_date", + "verbose_name": "historical proposals reviewer", + }, + bases=(models.Model,), + ), + ] diff --git a/junction/conferences/migrations/0013_auto_20160131_1954.py b/junction/conferences/migrations/0013_auto_20160131_1954.py new file mode 100644 index 00000000..f613e698 --- /dev/null +++ b/junction/conferences/migrations/0013_auto_20160131_1954.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0012_historicalconferenceproposalreviewer"), + ] + + operations = [ + migrations.AddField( + model_name="conference", + name="hashtags", + field=models.CharField( + max_length=100, + default="", + null=True, + help_text="Used in social sharing, use commas to separate to tags, no '#' required.", + blank=True, + ), + preserve_default=True, + ), + migrations.AddField( + model_name="conference", + name="twitter_id", + field=models.CharField( + max_length=100, + default="", + null=True, + help_text="Used in social share widgets.", + blank=True, + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="conference", + name="venue", + field=models.ForeignKey( + blank=True, + null=True, + to="conferences.ConferenceVenue", + on_delete=models.deletion.CASCADE, + ), + preserve_default=True, + ), + ] diff --git a/junction/conferences/migrations/0014_conferencesettings.py b/junction/conferences/migrations/0014_conferencesettings.py new file mode 100644 index 00000000..ef780f52 --- /dev/null +++ b/junction/conferences/migrations/0014_conferencesettings.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + +from junction.base.constants import ConferenceSettingConstants + + +def add_default_values(apps, schema_editor): + """Add all default values + """ + ConferenceSetting = apps.get_model("conferences", "ConferenceSetting") + public_voting = ConferenceSettingConstants.ALLOW_PUBLIC_VOTING_ON_PROPOSALS + display_propsals = ConferenceSettingConstants.DISPLAY_PROPOSALS_IN_PUBLIC + allow_plus_zero_vote = ConferenceSettingConstants.ALLOW_PLUS_ZERO_REVIEWER_VOTE + + Conference = apps.get_model("conferences", "Conference") + for conf in Conference.objects.all(): + ConferenceSetting.objects.create( + name=public_voting["name"], + value=public_voting["value"], + description=public_voting["description"], + conference=conf, + ) + ConferenceSetting.objects.create( + name=display_propsals["name"], + value=display_propsals["value"], + description=display_propsals["description"], + conference=conf, + ) + ConferenceSetting.objects.create( + name=allow_plus_zero_vote["name"], + value=allow_plus_zero_vote["value"], + description=allow_plus_zero_vote["description"], + conference=conf, + ) + + +def remove_default_values(apps, schema_editor): + ConferenceSetting = apps.get_model("conferences", "ConferenceSetting") + objs = ConferenceSetting.objects.all() + for obj in objs: + obj.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("conferences", "0013_auto_20160131_1954"), + ] + + operations = [ + migrations.CreateModel( + name="ConferenceSetting", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("name", models.CharField(max_length=100, db_index=True)), + ("value", models.BooleanField(default=False)), + ("description", models.CharField(max_length=255)), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conferencesetting_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferencesetting_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.RunPython(add_default_values, remove_default_values), + ] diff --git a/junction/conferences/migrations/0015_auto_20200322_1904.py b/junction/conferences/migrations/0015_auto_20200322_1904.py new file mode 100644 index 00000000..de56da60 --- /dev/null +++ b/junction/conferences/migrations/0015_auto_20200322_1904.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-03-22 13:34 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0014_conferencesettings"), + ] + + operations = [ + migrations.AlterField( + model_name="conference", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_conference_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="conference", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_conference_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="conference", + name="venue", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + to="conferences.ConferenceVenue", + ), + ), + migrations.AlterField( + model_name="conferencemoderator", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_conferencemoderator_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="conferencemoderator", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_conferencemoderator_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="conferenceproposalreviewer", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_conferenceproposalreviewer_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="conferenceproposalreviewer", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_conferenceproposalreviewer_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="conferencesetting", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_conferencesetting_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="conferencesetting", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_conferencesetting_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="conferencevenue", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_conferencevenue_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="conferencevenue", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_conferencevenue_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="room", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_room_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="room", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_room_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + ] diff --git a/junction/conferences/migrations/0016_auto_20230110_1818.py b/junction/conferences/migrations/0016_auto_20230110_1818.py new file mode 100644 index 00000000..e461f2d3 --- /dev/null +++ b/junction/conferences/migrations/0016_auto_20230110_1818.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2 on 2023-01-10 12:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('conferences', '0015_auto_20200322_1904'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalconferenceproposalreviewer', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical proposals reviewer', 'verbose_name_plural': 'historical proposals reviewers'}, + ), + migrations.AddField( + model_name='historicalconferenceproposalreviewer', + name='history_change_reason', + field=models.CharField(max_length=100, null=True), + ), + migrations.AlterField( + model_name='historicalconferenceproposalreviewer', + name='created_by', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Created By'), + ), + migrations.AlterField( + model_name='historicalconferenceproposalreviewer', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalconferenceproposalreviewer', + name='modified_by', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modified By'), + ), + ] diff --git a/junction/conferences/models.py b/junction/conferences/models.py index e8a3822b..365a80bb 100644 --- a/junction/conferences/models.py +++ b/junction/conferences/models.py @@ -1,71 +1,142 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff +from datetime import datetime from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse +from django.urls import reverse from django.db import models -from django.utils.translation import ugettext as _ -from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible +from django.utils.translation import gettext as _ from django_extensions.db.fields import AutoSlugField +from simple_history.models import HistoricalRecords from slugify import slugify from uuid_upload_path import upload_to -# Junction Stuff -from junction.base.constants import CONFERENCE_STATUS_LIST +from junction.base.constants import ConferenceSettingConstants, ConferenceStatus from junction.base.models import AuditModel +from junction.base.utils import get_date_diff_display @python_2_unicode_compatible class Conference(AuditModel): """ Conference/Event master """ + name = models.CharField(max_length=255, verbose_name="Conference Name") - slug = AutoSlugField(max_length=255, unique=True, populate_from=('name',), editable=True) + slug = AutoSlugField( + max_length=255, unique=True, populate_from=("name",), editable=True + ) description = models.TextField(default="") start_date = models.DateField(verbose_name="Start Date") end_date = models.DateField(verbose_name="End Date") logo = models.ImageField(blank=True, null=True, upload_to=upload_to) status = models.PositiveSmallIntegerField( - choices=CONFERENCE_STATUS_LIST, verbose_name="Current Status") + choices=ConferenceStatus.CHOICES, verbose_name="Current Status" + ) + venue = models.ForeignKey( + "ConferenceVenue", null=True, blank=True, on_delete=models.SET_NULL + ) + + twitter_id = models.CharField( + max_length=100, + blank=True, + null=True, + default="", + help_text=_("Used in social share widgets."), + ) + hashtags = models.CharField( + max_length=100, + blank=True, + null=True, + default="", + help_text=_( + "Used in social sharing, use commas to separate to tags, no '#' required." + ), + ) + deleted = models.BooleanField(default=False, verbose_name="Is Deleted?") class Meta: - verbose_name = _('Conference') - verbose_name_plural = _('Conferences') - ordering = ('-start_date', 'name',) - get_latest_by = 'start_date' + verbose_name = _("Conference") + verbose_name_plural = _("Conferences") + ordering = ( + "-start_date", + "name", + ) + get_latest_by = "start_date" def __str__(self): return self.name def get_absolute_url(self): - return reverse("conference-detail", kwargs={'conference_slug': self.slug}) + return reverse("conference-detail", kwargs={"conference_slug": self.slug}) + + def duration_display(self): + return get_date_diff_display(self.start_date, self.end_date) def clean(self): if self.end_date < self.start_date: msg = _("End date should be greater than start date.") - raise ValidationError({'end_date': msg}) + raise ValidationError({"end_date": msg}) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) - return super(Conference, self).save(*args, **kwargs) + if not self.pk: + super(Conference, self).save(*args, **kwargs) + public_voting = ConferenceSettingConstants.ALLOW_PUBLIC_VOTING_ON_PROPOSALS + ConferenceSetting.objects.create( + name=public_voting["name"], + value=public_voting["value"], + description=public_voting["description"], + conference=self, + ) + display_propsals = ConferenceSettingConstants.DISPLAY_PROPOSALS_IN_PUBLIC + ConferenceSetting.objects.create( + name=display_propsals["name"], + value=display_propsals["value"], + description=display_propsals["description"], + conference=self, + ) + allow_plus_zero_vote = ( + ConferenceSettingConstants.ALLOW_PLUS_ZERO_REVIEWER_VOTE + ) + ConferenceSetting.objects.create( + name=allow_plus_zero_vote["name"], + value=allow_plus_zero_vote["value"], + description=allow_plus_zero_vote["description"], + conference=self, + ) + return + super(Conference, self).save(*args, **kwargs) + + def is_accepting_proposals(self): + """Check if any one of the proposal section is accepting proposal. + """ + if ( + self.status == ConferenceStatus.CLOSED_CFP + or self.status == ConferenceStatus.SCHEDULE_PUBLISHED + ): + return False + return self.proposal_types.filter(end_date__gte=datetime.now()).exists() @python_2_unicode_compatible class ConferenceModerator(AuditModel): """ List of Conference Moderators/Administrators """ - conference = models.ForeignKey(Conference, related_name='moderators') - moderator = models.ForeignKey(User) + + conference = models.ForeignKey( + Conference, related_name="moderators", on_delete=models.CASCADE + ) + moderator = models.ForeignKey(User, on_delete=models.CASCADE) active = models.BooleanField(default=True, verbose_name="Is Active?") class Meta: unique_together = ("conference", "moderator") - verbose_name = 'moderator' - verbose_name_plural = 'moderators' + verbose_name = "moderator" + verbose_name_plural = "moderators" def __str__(self): return "{}[{}]".format(self.moderator.get_full_name(), self.conference) @@ -75,14 +146,55 @@ def __str__(self): class ConferenceProposalReviewer(AuditModel): """ List of global proposal reviewers """ - conference = models.ForeignKey(Conference, related_name='proposal_reviewers') - reviewer = models.ForeignKey(User) + + conference = models.ForeignKey( + Conference, related_name="proposal_reviewers", on_delete=models.CASCADE + ) + reviewer = models.ForeignKey(User, on_delete=models.CASCADE) active = models.BooleanField(default=True, verbose_name="Is Active?") + nick = models.CharField( + max_length=255, verbose_name="Nick Name", default="Reviewer" + ) + history = HistoricalRecords() class Meta: - verbose_name = 'proposals reviewer' - verbose_name_plural = 'proposals reviewers' + verbose_name = "proposals reviewer" + verbose_name_plural = "proposals reviewers" unique_together = ("conference", "reviewer") def __str__(self): - return "{}[{}]".format(self.reviewer.get_full_name(), self.conference) + return "{} - {}".format(self.conference, self.reviewer.username) + + +@python_2_unicode_compatible +class ConferenceVenue(AuditModel): + name = models.CharField(max_length=100) + + address = models.TextField() + + latitude = models.DecimalField(max_digits=17, decimal_places=15) + longitudes = models.DecimalField(max_digits=19, decimal_places=16) + + def __str__(self): + return self.name + + +class Room(AuditModel): + name = models.CharField(max_length=100) + venue = models.ForeignKey(ConferenceVenue, on_delete=models.CASCADE) + + note = models.CharField(max_length=255) + + def __str__(self): + return "{}, {}".format(self.name, self.venue) + + +@python_2_unicode_compatible +class ConferenceSetting(AuditModel): + conference = models.ForeignKey(Conference, on_delete=models.CASCADE) + name = models.CharField(max_length=100, db_index=True) + value = models.BooleanField(default=False) + description = models.CharField(max_length=255) + + def __str__(self): + return "{}: {}".format(self.name, self.value) diff --git a/junction/conferences/permissions.py b/junction/conferences/permissions.py new file mode 100644 index 00000000..ab00abce --- /dev/null +++ b/junction/conferences/permissions.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from .models import ConferenceProposalReviewer + + +def is_reviewer(user, conference): + """Returns a boolean indicating if a given user is a conference reviewer. + """ + if not user.is_authenticated: + return False + + qs = ConferenceProposalReviewer.objects.filter( + reviewer=user, conference=conference, active=True + ) + return qs.exists() diff --git a/junction/conferences/serializers.py b/junction/conferences/serializers.py new file mode 100644 index 00000000..45d356fd --- /dev/null +++ b/junction/conferences/serializers.py @@ -0,0 +1,32 @@ +from rest_framework import serializers + +from .models import Conference, ConferenceVenue, Room + + +class ConferenceSerializer(serializers.HyperlinkedModelSerializer): + status = serializers.CharField(source="get_status_display") + + class Meta: + model = Conference + fields = ( + "id", + "name", + "slug", + "description", + "start_date", + "end_date", + "status", + "venue", + ) + + +class VenueSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ConferenceVenue + fields = ("id", "name", "address", "latitude", "longitudes") + + +class RoomSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Room + fields = ("id", "name", "venue", "note") diff --git a/junction/conferences/service.py b/junction/conferences/service.py new file mode 100644 index 00000000..a2f25c7f --- /dev/null +++ b/junction/conferences/service.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + + +from .models import ConferenceModerator + + +def list_conference_moderator(user): + qs = ConferenceModerator.objects.filter(moderator=user) + return qs.all() diff --git a/junction/conferences/tasks.py b/junction/conferences/tasks.py index e905ad11..e13317cb 100644 --- a/junction/conferences/tasks.py +++ b/junction/conferences/tasks.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from celery import shared_task @shared_task def add(x, y): # dummy task - print x+y + print(x + y) diff --git a/junction/conferences/urls.py b/junction/conferences/urls.py new file mode 100644 index 00000000..158ce1fb --- /dev/null +++ b/junction/conferences/urls.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from django.urls import re_path + +from . import views + +urlpatterns = [re_path(r"^$", views.get_conference, name="get-conference")] diff --git a/junction/conferences/views.py b/junction/conferences/views.py index c7ee7577..65da35e8 100644 --- a/junction/conferences/views.py +++ b/junction/conferences/views.py @@ -1,2 +1,40 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals + +from django.urls import reverse +from django.http.response import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.views.decorators.http import require_http_methods +from rest_framework import viewsets +from django_filters import rest_framework as filters + +from .models import Conference, ConferenceVenue, Room +from .serializers import ConferenceSerializer, RoomSerializer, VenueSerializer + + +class ConferenceView(viewsets.ReadOnlyModelViewSet): + queryset = Conference.objects.all() + serializer_class = ConferenceSerializer + + +class VenueView(viewsets.ReadOnlyModelViewSet): + queryset = ConferenceVenue.objects.all() + serializer_class = VenueSerializer + + +class RoomView(viewsets.ReadOnlyModelViewSet): + queryset = Room.objects.all() + serializer_class = RoomSerializer + filter_backends = [filters.DjangoFilterBackend,] + filter_fields = ("venue",) + + +@require_http_methods(["GET"]) +def get_conference(request, conference_slug): + # if the conference does not exist, render 404 + get_object_or_404(Conference, slug=conference_slug) + + # redirect to /proposals else + return HttpResponseRedirect( + reverse("proposals-list", kwargs={"conference_slug": conference_slug}) + ) diff --git a/junction/devices/__init__.py b/junction/devices/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/devices/admin.py b/junction/devices/admin.py new file mode 100644 index 00000000..f7fdc19e --- /dev/null +++ b/junction/devices/admin.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from django.contrib import admin + +from junction.base.admin import TimeAuditAdmin + +from .models import Device + +# Register your models here. + + +class DeviceAdmin(TimeAuditAdmin): + list_display = ( + "uuid", + "verification_code", + "verification_code_sent_at", + ) + TimeAuditAdmin.list_display + + +admin.site.register(Device, DeviceAdmin) diff --git a/junction/devices/migrations/0001_initial.py b/junction/devices/migrations/0001_initial.py new file mode 100644 index 00000000..7796c989 --- /dev/null +++ b/junction/devices/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + +import junction.devices.models + + +class Migration(migrations.Migration): + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Device", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "uuid", + models.UUIDField( + unique=True, max_length=32, db_index=True + ), + ), + ("is_verified", models.BooleanField(default=False)), + ("verification_code", models.IntegerField()), + ( + "verification_code_sent_at", + models.DateTimeField( + auto_now_add=True, verbose_name=b"Verification Code Sent At" + ), + ), + ( + "verification_code_expires_at", + models.DateTimeField( + default=junction.devices.models.expiry_time, + verbose_name=b"Verification Code Expires At", + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + ] diff --git a/junction/devices/migrations/0002_auto_20160623_1448.py b/junction/devices/migrations/0002_auto_20160623_1448.py new file mode 100644 index 00000000..95182be7 --- /dev/null +++ b/junction/devices/migrations/0002_auto_20160623_1448.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + +import junction.devices.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("devices", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="device", + name="verification_code_expires_at", + field=models.DateTimeField( + default=junction.devices.models.expiry_time, + verbose_name="Verification Code Expires At", + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="device", + name="verification_code_sent_at", + field=models.DateTimeField( + auto_now_add=True, verbose_name="Verification Code Sent At" + ), + preserve_default=True, + ), + ] diff --git a/junction/devices/migrations/__init__.py b/junction/devices/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/devices/models.py b/junction/devices/models.py new file mode 100644 index 00000000..eb42cbc8 --- /dev/null +++ b/junction/devices/models.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import datetime + +from django.db import models +from django.utils.timezone import now + +from junction.base.models import TimeAuditModel + + +def expiry_time(expiry_mins=60): + return now() + datetime.timedelta(minutes=expiry_mins) + + +class Device(TimeAuditModel): + uuid = models.UUIDField(unique=True, db_index=True) + # Verification + is_verified = models.BooleanField(default=False) + verification_code = models.IntegerField() + verification_code_sent_at = models.DateTimeField( + auto_now_add=True, verbose_name="Verification Code Sent At" + ) + verification_code_expires_at = models.DateTimeField( + verbose_name="Verification Code Expires At", default=expiry_time + ) + + def __unicode__(self): + return "uuid: {}, is_verified: {}".format(self.uuid, self.is_verified) diff --git a/junction/devices/serializers.py b/junction/devices/serializers.py new file mode 100644 index 00000000..cfe2787f --- /dev/null +++ b/junction/devices/serializers.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from rest_framework import serializers + + +class DeviceRegistrationSerializer(serializers.Serializer): + uuid = serializers.UUIDField() + + +class DeviceConfirmationSerializer(serializers.Serializer): + code = serializers.IntegerField() diff --git a/junction/devices/views.py b/junction/devices/views.py new file mode 100644 index 00000000..c985b48f --- /dev/null +++ b/junction/devices/views.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +import os +import random +import uuid + +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone +from rest_framework import status, views +from rest_framework.response import Response +from six.moves import range + +from .models import Device +from .serializers import DeviceConfirmationSerializer, DeviceRegistrationSerializer + +# Create your views here. + + +class DeviceListApiView(views.APIView): + def _create_device(self, data): + if os.environ.get("TESTING"): + code = settings.DEVICE_VERIFICATION_CODE + else: + code = random.choice(list(range(10000, 99999))) + device = Device.objects.create(uuid=data["uuid"], verification_code=code) + return device + + def post(self, request): + obj = DeviceRegistrationSerializer(data=request.data) + if obj.is_valid(): + if Device.objects.filter(uuid=obj.data["uuid"]).exists(): + return Response( + status=status.HTTP_400_BAD_REQUEST, + data={"uuid": "Already registered"}, + ) + else: + device = self._create_device(data=obj.data) + data = {"uuid": str(device.uuid)} + return Response(status=status.HTTP_201_CREATED, data=data) + return Response(status=status.HTTP_400_BAD_REQUEST, data=obj.errors) + + +# TODO: This is required when verification code dispatch mechansim is decided + + +class DeviceDetailApiView(views.APIView): + def _verify(self, device, code): + if ( + device.verification_code == code + and timezone.now() < device.verification_code_expires_at + ): + device.is_verified = True + device.save() + return Response(status=status.HTTP_200_OK) + return Response( + status=status.HTTP_400_BAD_REQUEST, data={"code": "Incorrect code"} + ) + + def post(self, request, _uuid): + obj = DeviceConfirmationSerializer(data=request.data) + if obj.is_valid(): + try: + device = Device.objects.get(uuid=uuid.UUID(_uuid)) + return self._verify(device=device, code=obj.data["code"]) + except ObjectDoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + return Response(status=status.HTTP_400_BAD_REQUEST, data=obj.errors) diff --git a/junction/feedback/__init__.py b/junction/feedback/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/feedback/admin.py b/junction/feedback/admin.py new file mode 100644 index 00000000..e6eddb70 --- /dev/null +++ b/junction/feedback/admin.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +from django.contrib import admin + +from junction.base.admin import TimeAuditAdmin +from junction.conferences import service + +from .models import ( + ChoiceFeedbackQuestion, + ChoiceFeedbackQuestionValue, + ScheduleItemChoiceFeedback, + ScheduleItemTextFeedback, + TextFeedbackQuestion, +) + +# Register your models here. + + +class TextFeedbackQuestionAdmin(TimeAuditAdmin): + list_display = ( + "title", + "schedule_item_type", + "conference", + ) + TimeAuditAdmin.list_display # noqa + + def get_queryset(self, request): + qs = super(TextFeedbackQuestionAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + + +class ChoiceFeedbackQuestionAdmin(TimeAuditAdmin): + list_display = ( + "title", + "schedule_item_type", + "conference", + ) + TimeAuditAdmin.list_display # noqa + + def get_queryset(self, request): + qs = super(ChoiceFeedbackQuestionAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + + +class ChoiceFeedbackQuestionValueAdmin(TimeAuditAdmin): + list_display = ("question", "title", "value") + TimeAuditAdmin.list_display # noqa + + def get_queryset(self, request): + qs = super(ChoiceFeedbackQuestionValueAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(question__conference__in=[m.conference for m in moderators]) + + +class ScheduleItemTextFeedbackAdmin(TimeAuditAdmin): + list_display = ( + "schedule_item", + "question", + "text", + "device", + ) + TimeAuditAdmin.list_display # noqa + list_filter = ["schedule_item"] # noqa + + def get_queryset(self, request): + qs = super(ScheduleItemTextFeedbackAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(question__conference__in=[m.conference for m in moderators]) + + +class ScheduleItemChoiceFeedbackAdmin(TimeAuditAdmin): + list_display = ( + "schedule_item", + "question", + "value", + "device", + ) + TimeAuditAdmin.list_display # noqa + list_filter = ["schedule_item"] # noqa + + def get_queryset(self, request): + qs = super(ScheduleItemChoiceFeedbackAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(question__conference__in=[m.conference for m in moderators]) + + +admin.site.register(TextFeedbackQuestion, TextFeedbackQuestionAdmin) +admin.site.register(ChoiceFeedbackQuestion, ChoiceFeedbackQuestionAdmin) +admin.site.register(ChoiceFeedbackQuestionValue, ChoiceFeedbackQuestionValueAdmin) +admin.site.register(ScheduleItemTextFeedback, ScheduleItemTextFeedbackAdmin) +admin.site.register(ScheduleItemChoiceFeedback, ScheduleItemChoiceFeedbackAdmin) diff --git a/junction/feedback/migrations/0001_initial.py b/junction/feedback/migrations/0001_initial.py new file mode 100644 index 00000000..4177a10e --- /dev/null +++ b/junction/feedback/migrations/0001_initial.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0012_historicalconferenceproposalreviewer"), + ("schedule", "0003_scheduleitemtype"), + ("devices", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="ChoiceFeedbackQuestion", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("is_required", models.BooleanField(default=False)), + ( + "title", + models.CharField( + max_length=255, verbose_name="Choice Feedback Title" + ), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "schedule_item_type", + models.ForeignKey( + to="schedule.ScheduleItemType", + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.CreateModel( + name="ChoiceFeedbackQuestionValue", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "title", + models.CharField( + max_length=255, verbose_name="Choice Feedback Value Title" + ), + ), + ("value", models.SmallIntegerField()), + ( + "question", + models.ForeignKey( + related_name="allowed_values", + on_delete=models.deletion.CASCADE, + to="feedback.ChoiceFeedbackQuestion", + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.CreateModel( + name="ScheduleItemChoiceFeedback", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("value", models.SmallIntegerField()), + ( + "device", + models.ForeignKey( + blank=True, + to="devices.Device", + null=True, + on_delete=models.deletion.CASCADE, + ), + ), + ( + "question", + models.ForeignKey( + to="feedback.ChoiceFeedbackQuestion", + on_delete=models.deletion.CASCADE, + ), + ), + ( + "schedule_item", + models.ForeignKey( + to="schedule.ScheduleItem", on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.CreateModel( + name="ScheduleItemTextFeedback", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("text", models.TextField()), + ( + "device", + models.ForeignKey( + blank=True, + to="devices.Device", + null=True, + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.CreateModel( + name="TextFeedbackQuestion", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("is_required", models.BooleanField(default=False)), + ( + "title", + models.CharField( + max_length=255, verbose_name="Text Feedback Title" + ), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "schedule_item_type", + models.ForeignKey( + to="schedule.ScheduleItemType", + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.AddField( + model_name="scheduleitemtextfeedback", + name="question", + field=models.ForeignKey( + to="feedback.TextFeedbackQuestion", on_delete=models.deletion.CASCADE, + ), + preserve_default=True, + ), + migrations.AddField( + model_name="scheduleitemtextfeedback", + name="schedule_item", + field=models.ForeignKey( + to="schedule.ScheduleItem", on_delete=models.deletion.CASCADE + ), + preserve_default=True, + ), + ] diff --git a/junction/feedback/migrations/0002_auto_20150913_1904.py b/junction/feedback/migrations/0002_auto_20150913_1904.py new file mode 100644 index 00000000..dcaadf50 --- /dev/null +++ b/junction/feedback/migrations/0002_auto_20150913_1904.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("feedback", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="choicefeedbackquestionvalue", + name="value", + field=models.SmallIntegerField(db_index=True), + preserve_default=True, + ), + migrations.AlterIndexTogether( + name="scheduleitemtextfeedback", + index_together=set([("device", "schedule_item")]), + ), + ] diff --git a/junction/feedback/migrations/0003_auto_20150913_2203.py b/junction/feedback/migrations/0003_auto_20150913_2203.py new file mode 100644 index 00000000..7d7cbbeb --- /dev/null +++ b/junction/feedback/migrations/0003_auto_20150913_2203.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("feedback", "0002_auto_20150913_1904"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitemchoicefeedback", + name="value", + field=models.SmallIntegerField(db_index=True), + preserve_default=True, + ), + migrations.AlterIndexTogether( + name="scheduleitemchoicefeedback", + index_together=set( + [("schedule_item", "value"), ("device", "schedule_item")] + ), + ), + ] diff --git a/junction/feedback/migrations/0004_auto_20150917_1535.py b/junction/feedback/migrations/0004_auto_20150917_1535.py new file mode 100644 index 00000000..2b951c65 --- /dev/null +++ b/junction/feedback/migrations/0004_auto_20150917_1535.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("feedback", "0003_auto_20150913_2203"), + ] + + operations = [ + migrations.AlterField( + model_name="choicefeedbackquestion", + name="is_required", + field=models.BooleanField(default=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="textfeedbackquestion", + name="is_required", + field=models.BooleanField(default=True), + preserve_default=True, + ), + ] diff --git a/junction/feedback/migrations/__init__.py b/junction/feedback/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/feedback/models.py b/junction/feedback/models.py new file mode 100644 index 00000000..42b5e053 --- /dev/null +++ b/junction/feedback/models.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, unicode_literals + +from django.db import models +from six import python_2_unicode_compatible + +from junction.base.models import TimeAuditModel +from junction.conferences.models import Conference +from junction.devices.models import Device +from junction.schedule.models import ScheduleItem, ScheduleItemType + + +class BaseSessionQuestionMixin(models.Model): + schedule_item_type = models.ForeignKey(ScheduleItemType, on_delete=models.CASCADE) + is_required = models.BooleanField(default=True) + conference = models.ForeignKey(Conference, on_delete=models.CASCADE) + + class Meta: + abstract = True + + +@python_2_unicode_compatible +class TextFeedbackQuestion(BaseSessionQuestionMixin, TimeAuditModel): + """Store details about text feedback type information. + """ + + title = models.CharField(max_length=255, verbose_name="Text Feedback Title") + + def __str__(self): + return "title: {}, schedule_item_type: {}, conference: {}".format( + self.title, self.schedule_item_type, self.conference + ) + + def to_response(self): + return { + "title": self.title, + "id": self.id, + "type": "text", + "schedule_item_type": self.schedule_item_type.title, + "is_required": self.is_required, + } + + +@python_2_unicode_compatible +class ChoiceFeedbackQuestion(BaseSessionQuestionMixin, TimeAuditModel): + """Store details about text feedback type information. + """ + + title = models.CharField(max_length=255, verbose_name="Choice Feedback Title") + + def __str__(self): + return "title: {}, schedule_item_type: {}, conference: {}".format( + self.title, self.schedule_item_type, self.conference + ) + + def to_response(self): + allowed_choices = [ + {"title": obj.title, "value": obj.value, "id": obj.id} + for obj in self.allowed_values.all() + ] + return { + "title": self.title, + "id": self.id, + "type": "choice", + "allowed_choices": allowed_choices, + "schedule_item_type": self.schedule_item_type.title, + "is_required": self.is_required, + } + + +@python_2_unicode_compatible +class ChoiceFeedbackQuestionValue(TimeAuditModel): + """Store allowed values for each choice based question + """ + + question = models.ForeignKey( + ChoiceFeedbackQuestion, related_name="allowed_values", on_delete=models.CASCADE + ) + title = models.CharField(max_length=255, verbose_name="Choice Feedback Value Title") + value = models.SmallIntegerField(db_index=True) + + def __str__(self): + return "question: {}, title: {}, value: {}".format( + self.question, self.title, self.value + ) + + +@python_2_unicode_compatible +class ScheduleItemTextFeedback(TimeAuditModel): + schedule_item = models.ForeignKey( + ScheduleItem, db_index=True, on_delete=models.CASCADE + ) + question = models.ForeignKey(TextFeedbackQuestion, on_delete=models.CASCADE) + text = models.TextField() + device = models.ForeignKey( + Device, null=True, blank=True, db_index=True, on_delete=models.CASCADE + ) + + class Meta: + index_together = [["device", "schedule_item"]] + + def __str__(self): + return "schedule_item: {}, question: {}, text: {}, device: {}".format( + self.schedule_item, self.question, self.text[:100], self.device + ) + + +@python_2_unicode_compatible +class ScheduleItemChoiceFeedback(TimeAuditModel): + schedule_item = models.ForeignKey(ScheduleItem, on_delete=models.CASCADE) + question = models.ForeignKey(ChoiceFeedbackQuestion, on_delete=models.CASCADE) + value = models.SmallIntegerField(db_index=True) + device = models.ForeignKey(Device, null=True, blank=True, on_delete=models.CASCADE) + + class Meta: + index_together = [["device", "schedule_item"], ["schedule_item", "value"]] + + def __str__(self): + return "schedule_item: {}, question: {}, value: {}, device: {}".format( + self.schedule_item, self.question, self.value, self.device + ) diff --git a/junction/feedback/permissions.py b/junction/feedback/permissions.py new file mode 100644 index 00000000..b6e62397 --- /dev/null +++ b/junction/feedback/permissions.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +import uuid + +from rest_framework import permissions + +from junction.conferences.permissions import is_reviewer +from junction.devices.models import Device + + +def get_authorization_header(request): + auth = request.META.get("HTTP_AUTHORIZATION") + return auth + + +class CanSubmitFeedBack(permissions.BasePermission): + def has_permission(self, request, view): + token = get_authorization_header(request) + if token: + device_uuid = token.split()[-1] + view.device_uuid = uuid.UUID(device_uuid) + if device_uuid: + return Device.objects.filter(uuid=view.device_uuid).exists() + return False + return False + + +def can_view_feedback(user, schedule_item): + """Given a schedule item object, say a requesting user can view the + feedback. + """ + if user.is_superuser: + return True + + session = schedule_item.session + res = is_reviewer(user=user, conference=schedule_item.conference) + return session and (session.author == user or res) diff --git a/junction/feedback/serializers.py b/junction/feedback/serializers.py new file mode 100644 index 00000000..97c8ecd6 --- /dev/null +++ b/junction/feedback/serializers.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +from rest_framework import serializers + +from .models import ( + ChoiceFeedbackQuestion, + ChoiceFeedbackQuestionValue, + TextFeedbackQuestion, +) + + +def object_exists(model, pk): + if not model.objects.filter(pk=pk): + raise serializers.ValidationError("The question doesn't exist") + return True + + +class FeedbackQueryParamsSerializer(serializers.Serializer): + conference_id = serializers.IntegerField() + + +class TextFeedbackSerializer(serializers.Serializer): + id = serializers.IntegerField() + text = serializers.CharField() + + def validate(self, data): + if object_exists(TextFeedbackQuestion, pk=data["id"]): + return data + + +class ChoiceFeedbackSerializer(serializers.Serializer): + id = serializers.IntegerField() + value_id = serializers.IntegerField() + + def validate(self, data): + if object_exists(ChoiceFeedbackQuestion, pk=data["id"]): + if ChoiceFeedbackQuestionValue.objects.filter( + question_id=data["id"], pk=data["value_id"] + ).exists(): + return data + raise serializers.ValidationError( + "The multiple choice value isn't associated with question" + ) + + +class FeedbackSerializer(serializers.Serializer): + schedule_item_id = serializers.IntegerField() + text = TextFeedbackSerializer(many=True, required=False) + choices = ChoiceFeedbackSerializer(many=True, required=False) diff --git a/junction/feedback/service.py b/junction/feedback/service.py new file mode 100644 index 00000000..0d06d8af --- /dev/null +++ b/junction/feedback/service.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- + +from collections import defaultdict + +from django.core.exceptions import ObjectDoesNotExist +from django.db import IntegrityError, transaction +from django.db.models import Count + +from junction.devices.models import Device +from junction.schedule.models import ScheduleItem, ScheduleItemType + +from .models import ( + ChoiceFeedbackQuestion, + ChoiceFeedbackQuestionValue, + ScheduleItemChoiceFeedback, + ScheduleItemTextFeedback, + TextFeedbackQuestion, +) + +COLORS = ["#46BFBD", "#FDB45C", "#F7464A"] + + +def get_feedback_questions(conference_id): + """Get all feedback questions for the conference. + + {'talk': {'Text': [{'id': 1, 'title': 'How was the speaker ?', + 'is_required': True}], 'Workshop': [{'id': 1, + 'title': 'How was the content ?', 'is_required': True, + allowed_values: [{'title': 'Awesome', 'id': 2}, + {'title': 'Bad', 'id': 3}, {'title': 'Ok', 'id': 4}}]}] + }} + """ + text_questions = get_text_feedback_questions(conference_id=conference_id) + choice_questions = get_choice_feedback_questions(conference_id=conference_id) + return _merge_questions( + text_questions=text_questions, choice_questions=choice_questions + ) + + +def get_text_feedback_questions(conference_id): + """Get all text questions for the conference organized by + schedule item type. + + Return dict contain all questions with schedule item type in dict. + """ + qs = TextFeedbackQuestion.objects.filter(conference_id=conference_id) + return _get_question_oragnized_by_type(qs) + + +def get_choice_feedback_questions(conference_id): + """Get all choice based questions for the conference organized by + schedule item type. + """ + qs = ChoiceFeedbackQuestion.objects.filter( + conference_id=conference_id + ).prefetch_related("allowed_values") + return _get_question_oragnized_by_type(qs) + + +def has_submitted(feedback, device_uuid): + """ + """ + device = Device.objects.get(uuid=device_uuid) + text_feedback = ScheduleItemTextFeedback.objects.filter( + schedule_item_id=feedback.validated_data["schedule_item_id"], device=device + ) + if text_feedback: + return True + choice_feedback = ScheduleItemChoiceFeedback.objects.filter( + schedule_item_id=feedback.validated_data["schedule_item_id"], device=device + ) + return choice_feedback + + +def _has_required_ids(master, submitted): + for item in master: + if item not in submitted: + return False + return True + + +def has_required_fields_data(feedback): + try: + data = feedback.validated_data + sch = ScheduleItem.objects.get(pk=data["schedule_item_id"]) + sch_type = ScheduleItemType.objects.get(title=sch.type) + + t_ids = TextFeedbackQuestion.objects.filter( + schedule_item_type=sch_type, conference=sch.conference, is_required=True + ).values_list("id", flat=True) + + if not data.get("text"): + if t_ids: + return False, "Text Feedback is missing" + else: + submitted_t_ids = {d["id"] for d in data.get("text")} + + if not _has_required_ids(master=t_ids, submitted=submitted_t_ids): + return False, "Required text questions are missing" + + c_ids = ChoiceFeedbackQuestion.objects.filter( + schedule_item_type=sch_type, conference=sch.conference, is_required=True + ).values_list("id", flat=True) + + if not data.get("choices"): + if c_ids: + return False, "Choice feedback is missing" + else: + submitted_c_ids = {d["id"] for d in data.get("choices")} + + if not _has_required_ids(master=c_ids, submitted=submitted_c_ids): + return False, "Choice feedback is missing" + return True, "" + except ObjectDoesNotExist as e: + print(e) + return False + + +def create(feedback, device_uuid): + device = Device.objects.get(uuid=device_uuid) + schedule_item_id = feedback.validated_data["schedule_item_id"] + try: + with transaction.atomic(): + text, choices = [], [] + if feedback.validated_data.get("text"): + text = create_text_feedback( + schedule_item_id=schedule_item_id, + feedbacks=feedback.validated_data.get("text"), + device=device, + ) + if feedback.validated_data.get("choices"): + choices = create_choice_feedback( + schedule_item_id=schedule_item_id, + feedbacks=feedback.validated_data.get("choices"), + device=device, + ) + return {"text": text, "choices": choices} + except (IntegrityError, ObjectDoesNotExist) as e: + print(e) # Replace with log + return False + + +def create_text_feedback(schedule_item_id, feedbacks, device): + text = [] + for feedback in feedbacks: + obj = ScheduleItemTextFeedback.objects.create( + schedule_item_id=schedule_item_id, + question_id=feedback["id"], + text=feedback["text"], + device=device, + ) + d = { + "id": obj.id, + "text": obj.text, + "question_id": feedback["id"], + "schedule_item_id": schedule_item_id, + } + text.append(d) + return text + + +def create_choice_feedback(schedule_item_id, feedbacks, device): + choices = [] + for feedback in feedbacks: + value = ChoiceFeedbackQuestionValue.objects.get( + question_id=feedback["id"], id=feedback["value_id"] + ) + obj = ScheduleItemChoiceFeedback.objects.create( + schedule_item_id=schedule_item_id, + device=device, + question_id=feedback["id"], + value=value.value, + ) + d = { + "id": obj.id, + "value_id": value.id, + "question_id": feedback["id"], + "schedule_item_id": schedule_item_id, + } + choices.append(d) + return choices + + +def get_feedback(schedule_item): + feedback = { + "text": _get_text_feedback(schedule_item=schedule_item), + "choices": _get_choice_feedback(schedule_item=schedule_item), + } + return feedback + + +def _get_text_feedback(schedule_item): + questions = TextFeedbackQuestion.objects.filter( + schedule_item_type__title=schedule_item.type + ) + text = [ + { + "question": question, + "values": ScheduleItemTextFeedback.objects.filter( + question=question, schedule_item=schedule_item + ), + } + for question in questions + ] + return text + + +def _get_choice_feedback(schedule_item): + questions = ChoiceFeedbackQuestion.objects.filter( + schedule_item_type__title=schedule_item.type + ).select_related("allowed_values") + choices = [] + for question in questions: + values = ( + ScheduleItemChoiceFeedback.objects.filter( + schedule_item=schedule_item, question=question + ) + .values("value") + .annotate(Count("value")) + ) + d = { + "question": question, + "values": _get_choice_value_for_chart(question=question, values=values), + } + choices.append(d) + return choices + + +def _get_choice_value_for_chart(question, values): + data = [] + for index, value in enumerate(values): + d = {"label": str(question.allowed_values.get(value=value["value"]).title)} + d["value"] = value["value__count"] + d["color"] = COLORS[index] + data.append(d) + return data + + +def _get_question_oragnized_by_type(qs): + questions = defaultdict(list) + for question in qs: + questions[question.schedule_item_type.title].append(question.to_response()) + return questions + + +def _merge_questions(text_questions, choice_questions): + """Merge the choice and text based questions into schedule type + {'Talk': {'text': [..], 'choice': [...]},} + """ + types = set(text_questions.keys()) + types.union(list(choice_questions.keys())) + questions = {} + for item in types: + questions[item] = { + "text": text_questions.get(item), + "choice": choice_questions.get(item), + } + return questions diff --git a/junction/feedback/tests.py b/junction/feedback/tests.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/feedback/views.py b/junction/feedback/views.py new file mode 100644 index 00000000..ff109898 --- /dev/null +++ b/junction/feedback/views.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +from django.http import HttpResponseForbidden +from django.shortcuts import get_object_or_404, render +from rest_framework import status, views +from rest_framework.response import Response + +from junction.schedule.models import ScheduleItem + +from . import service as feedback_service +from .permissions import CanSubmitFeedBack, can_view_feedback +from .serializers import FeedbackQueryParamsSerializer, FeedbackSerializer + + +class FeedbackQuestionListApiView(views.APIView): + def get(self, request): + data = request.query_params + obj = FeedbackQueryParamsSerializer(data=data) + if obj.is_valid(): + data = feedback_service.get_feedback_questions( + conference_id=obj.data["conference_id"] + ) + return Response(data=data, status=status.HTTP_200_OK) + return Response(data=obj.errors, status=status.HTTP_400_BAD_REQUEST) + + +class FeedbackListApiView(views.APIView): + permission_classes = (CanSubmitFeedBack,) + + def post(self, request): + feedback = FeedbackSerializer(data=request.data) + if feedback.is_valid(): + data = feedback_service.has_required_fields_data(feedback) + if data[0] is False: + return Response( + status=status.HTTP_400_BAD_REQUEST, data={"error": data[1]} + ) + if feedback_service.has_submitted(feedback, device_uuid=self.device_uuid): + return Response( + status=status.HTTP_400_BAD_REQUEST, + data={"error": "Feedback already submitted"}, + ) + data = feedback_service.create( + feedback=feedback, device_uuid=self.device_uuid + ) + return Response(status=status.HTTP_201_CREATED, data=data) + return Response(data=feedback.errors, status=status.HTTP_400_BAD_REQUEST) + + +def view_feedback(request, schedule_item_id): + """Show text/choice feedback for the schedule. + """ + schedule_item = get_object_or_404(ScheduleItem, pk=schedule_item_id) + if not can_view_feedback(user=request.user, schedule_item=schedule_item): + return HttpResponseForbidden("Access Denied") + feedback = feedback_service.get_feedback(schedule_item=schedule_item) + context = {"feedback": feedback, "schedule_item": schedule_item} + return render(request, "feedback/detail.html", context) diff --git a/junction/pages/templates/pages/home_page.html b/junction/pages/templates/pages/home_page.html deleted file mode 100644 index 6c559bfe..00000000 --- a/junction/pages/templates/pages/home_page.html +++ /dev/null @@ -1,74 +0,0 @@ -{% extends 'base.html' %} - -{% load django_markdown %} -{% load webdesign %} -{% load static from staticfiles %} - -{% block head_title %}Welcome to {{ SITE_INFO.site_name }}{% endblock %} -{% block og_title %}Welcome to {{ SITE_INFO.site_name }}{% endblock %} - -{% block page_classes %}{{ block.super }} homepage{% endblock %} -{% block breadcrumbs %}{% endblock %} -{% block header %} -
-
-
-
-

- Welcome to {{ SITE_INFO.site_name }} -

-

- Manage proposals, reviews, schedule, feedbacks during conferences -

-

- - - Conferences - -

-
-
-
-
-{% endblock %} - -{% block content %} -
-

- - Conferences -

- - {% if not conferences %} -

No conferences found.

- {% endif %} - - {% for conference in conferences %} -
-

- {{ conference.name }} -

-

- - {{ conference.start_date }} – - {{ conference.end_date }} - - - {{ conference.get_status_display }} - -

-

{{ conference.description|markdown }}

-
- {% endfor %} -
- -{% endblock %} diff --git a/junction/pages/urls.py b/junction/pages/urls.py deleted file mode 100644 index fbe467b6..00000000 --- a/junction/pages/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- - -# Third Party Stuff -from django.conf.urls import patterns, url - -from . import views - -urlpatterns = patterns( - '', - url(r'^$', views.HomePageView.as_view(), name="homepage"), -) diff --git a/junction/profiles/__init__.py b/junction/profiles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/profiles/admin.py b/junction/profiles/admin.py new file mode 100644 index 00000000..1848f862 --- /dev/null +++ b/junction/profiles/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import Profile + + +class ProfileAdmin(admin.ModelAdmin): + list_display = ("user", "city", "contact_no") + search_fields = ("contact_no", "city") + + +admin.site.register(Profile, ProfileAdmin) diff --git a/junction/profiles/forms.py b/junction/profiles/forms.py new file mode 100644 index 00000000..3023ae13 --- /dev/null +++ b/junction/profiles/forms.py @@ -0,0 +1,15 @@ +from django import forms + +from .models import Profile + + +class ProfileForm(forms.ModelForm): + class Meta: + model = Profile + fields = [ + "city", + "contact_no", + ] + exclude = [ + "user", + ] diff --git a/junction/profiles/migrations/0001_initial.py b/junction/profiles/migrations/0001_initial.py new file mode 100644 index 00000000..c00b6db9 --- /dev/null +++ b/junction/profiles/migrations/0001_initial.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Profile", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("city", models.CharField(max_length=100, null=True, blank=True)), + ("contact_no", models.CharField(max_length=15, null=True, blank=True)), + ( + "created_by", + models.ForeignKey( + related_name="created_profile_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_profile_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + ] diff --git a/junction/profiles/migrations/0002_auto_20200322_1904.py b/junction/profiles/migrations/0002_auto_20200322_1904.py new file mode 100644 index 00000000..309fe1bf --- /dev/null +++ b/junction/profiles/migrations/0002_auto_20200322_1904.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-03-22 13:34 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("profiles", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="profile", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_profile_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="profile", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_profile_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + ] diff --git a/junction/profiles/migrations/__init__.py b/junction/profiles/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/profiles/models.py b/junction/profiles/models.py new file mode 100644 index 00000000..6881a7ba --- /dev/null +++ b/junction/profiles/models.py @@ -0,0 +1,17 @@ +from django.contrib.auth.models import User +from django.db import models + +from junction.base.models import AuditModel + + +class Profile(AuditModel): + """ + It stores the City/Phone Details of the User. + """ + + user = models.OneToOneField(User, on_delete=models.CASCADE) + city = models.CharField(max_length=100, blank=True, null=True) + contact_no = models.CharField(max_length=15, blank=True, null=True) + + def __unicode__(self): + return self.user.username diff --git a/junction/profiles/tests.py b/junction/profiles/tests.py new file mode 100644 index 00000000..bb0758e0 --- /dev/null +++ b/junction/profiles/tests.py @@ -0,0 +1,17 @@ +from django.contrib.auth.models import User +from django.test import TestCase + +from .models import Profile + +# models test + + +class ProfileTest(TestCase): + def setUp(self): + self.user = User.objects.create(username="user1", password="123456") + Profile.objects.create(city="noida", contact_no="1234567890") + + def test_create_profile(self): + user = User.objects.get(username="user1") + profile_details = Profile.objects.get(user=user) + self.assertEqual(profile_details.city, "noida") diff --git a/junction/profiles/urls.py b/junction/profiles/urls.py new file mode 100644 index 00000000..feb0135b --- /dev/null +++ b/junction/profiles/urls.py @@ -0,0 +1,10 @@ +from django.urls import re_path + +from . import views + +app_name = "junction.profiles" + +urlpatterns = [ + re_path(r"^$", views.dashboard, name="dashboard"), + re_path(r"^edit/$", views.profile, name="profile"), +] diff --git a/junction/profiles/views.py b/junction/profiles/views.py new file mode 100644 index 00000000..16ebe697 --- /dev/null +++ b/junction/profiles/views.py @@ -0,0 +1,61 @@ +from collections import OrderedDict + +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.urls import reverse +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.views.decorators.http import require_http_methods + +from junction.conferences.models import Conference + +from .forms import ProfileForm +from .models import Profile + + +@login_required +@require_http_methods(["GET"]) +def dashboard(request): + conf_proposals = OrderedDict() + for conf in Conference.objects.order_by("-end_date"): + for proposal in conf.proposal_set.filter(author=request.user).all(): + if conf.name in conf_proposals: + conf_proposals[conf.name].append(proposal) + else: + conf_proposals[conf.name] = [proposal] + return render( + request, "profiles/dashboard.html", {"conf_proposals": conf_proposals} + ) + + +@login_required +def profile(request): + username = request.user + detail = None + + if request.method == "POST" and username == request.user: + user = User.objects.get(pk=username.id) + detail = Profile.objects.filter(user=user).exists() + if detail: + detail = Profile.objects.get(user=user) + detail_form = ProfileForm(request.POST, instance=detail) + if detail_form.is_valid(): + detail = detail_form.save() + return HttpResponseRedirect(reverse("profiles:dashboard")) + else: + user = User.objects.get(pk=username.id) + detail_form = ProfileForm(request.POST) + if detail_form.is_valid(): + detail_form = detail_form.save(commit=False) + detail_form.user = user + detail_form.save() + return HttpResponseRedirect(reverse("profiles:dashboard")) + + elif request.method == "GET": + user = User.objects.get(pk=username.id) + detail = Profile.objects.filter(user=user).exists() + if detail: + detail = Profile.objects.get(user=user) + return render(request, "profiles/userprofile.html", {"detail": detail}) + else: + return render(request, "profiles/userprofile.html") diff --git a/junction/proposals/admin.py b/junction/proposals/admin.py index 4668f16f..b41f973f 100644 --- a/junction/proposals/admin.py +++ b/junction/proposals/admin.py @@ -1,64 +1,195 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff from django.contrib import admin -from django.db import models +from django.db.models import TextField from pagedown.widgets import AdminPagedownWidget +from simple_history.admin import SimpleHistoryAdmin -# Junction Stuff from junction.base.admin import AuditAdmin, TimeAuditAdmin -from junction.proposals.models import ( - Proposal, - ProposalComment, - ProposalCommentVote, - ProposalSection, - ProposalSectionReviewer, - ProposalType, - ProposalVote -) +from junction.conferences import service +from junction.conferences.models import ConferenceProposalReviewer +from junction.proposals.models import ProposalSection +from junction.base.constants import ProposalStatus, ProposalReviewStatus +from django.http import HttpResponse +from . import models -class ProposalSectionAdmin(AuditAdmin): - list_display = ('name', 'active') + AuditAdmin.list_display +class ExportMixin: + def export_as_csv(self, request, queryset): + import csv -class ProposalSectionReviewerAdmin(AuditAdmin): - list_display = ('conference_reviewer', 'proposal_section') + AuditAdmin.list_display + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="exported_data.csv"' + writer = csv.writer(response) + writer.writerow(['Proposal Info', 'Author Info', 'Author Email', 'Conference', 'Status', 'Review Status']) -class ProposalTypeAdmin(AuditAdmin): - list_display = ('name', 'active') + AuditAdmin.list_display + for item in queryset: + writer.writerow([self.proposal_info(item), self.author_info(item), self.author_email(item), + item.conference.name, dict(ProposalStatus.CHOICES).get(item.status), + dict(ProposalReviewStatus.CHOICES).get(item.review_status)]) + + return response + export_as_csv.short_description = "Export To CSV" -class ProposalAdmin(TimeAuditAdmin): - list_display = ('conference', 'proposal_section', 'proposal_type', 'author', - 'title', 'slug', 'status', 'review_status') + TimeAuditAdmin.list_display +@admin.register(models.ProposalSection) +class ProposalSectionAdmin(AuditAdmin): + list_display = ("name", "active") + AuditAdmin.list_display + + +@admin.register(models.ProposalSectionReviewer) +class ProposalSectionReviewerAdmin(AuditAdmin): + list_display = ("conference_reviewer", "proposal_section") + AuditAdmin.list_display + list_filter = ["proposal_section"] + + def get_queryset(self, request): + qs = super(ProposalSectionReviewerAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter( + conference_reviewer__conference__in=[m.conference for m in moderators] + ) + + def get_form(self, request, obj=None, **kwargs): + form = super(ProposalSectionReviewerAdmin, self).get_form(request, obj, **kwargs) + form.base_fields['conference_reviewer'].queryset = ConferenceProposalReviewer.objects.all().order_by( + '-created_at') + form.base_fields['proposal_section'].queryset = ProposalSection.objects.all().order_by('-created_at') + return form + + +@admin.register(models.ProposalType) +class ProposalTypeAdmin(AuditAdmin): + list_display = ( + "name", + "active", + "start_date", + "end_date", + ) + AuditAdmin.list_display + + +@admin.register(models.Proposal) +class ProposalAdmin(TimeAuditAdmin, SimpleHistoryAdmin, ExportMixin): + list_display = ( + "proposal_info", + "author_info", + "author_email", + "conference", + "status", + "review_status", + ) + list_filter = [ + "proposal_section__name", + "proposal_type", + "target_audience", + "conference", + "status", + "review_status", + ] formfield_overrides = { - models.TextField: {'widget': AdminPagedownWidget}, + TextField: {"widget": AdminPagedownWidget}, } + actions = ["export_as_csv"] -class ProposalVoteAdmin(TimeAuditAdmin): - list_display = ('proposal', 'voter', 'role', 'up_vote') + \ - TimeAuditAdmin.list_display + def proposal_info(self, obj): + return "%s (%s)" % (obj.title, obj.proposal_type) + def author_email(self, obj): + if obj.author: + return obj.author.email -class ProposalCommentAdmin(TimeAuditAdmin): + def author_info(self, obj): + if obj.author: + return "%s (%s)" % (obj.author.get_full_name(), obj.author.username) + + def get_queryset(self, request): + qs = super(ProposalAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + + +@admin.register(models.ProposalVote) +class ProposalVoteAdmin(TimeAuditAdmin): list_display = ( - 'proposal', 'commenter', 'private', 'comment') + TimeAuditAdmin.list_display + "proposal", + "voter", + "role", + "up_vote", + ) + TimeAuditAdmin.list_display + def get_queryset(self, request): + qs = super(ProposalVoteAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(proposal__conference__in=[m.conference for m in moderators]) + + +@admin.register(models.ProposalSectionReviewerVoteValue) +class ProposalSectionReviewerVoteValueAdmin(AuditAdmin): + list_display = ("vote_value", "description") + AuditAdmin.list_display -class ProposalCommentVoteAdmin(TimeAuditAdmin): - list_display = ('proposal_comment', 'voter', 'up_vote') + \ - TimeAuditAdmin.list_display +@admin.register(models.ProposalSectionReviewerVote) +class ProposalSectionReviewerVoteAdmin(TimeAuditAdmin): + list_filter = ["vote_value", "proposal__proposal_type__name"] + list_display = ( + "proposal", + "voter", + "role", + "vote_value", + ) + TimeAuditAdmin.list_display + + def get_queryset(self, request): + qs = super(ProposalSectionReviewerVoteAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(proposal__conference__in=[m.conference for m in moderators]) -admin.site.register(ProposalSection, ProposalSectionAdmin) -admin.site.register(ProposalType, ProposalTypeAdmin) -admin.site.register(Proposal, ProposalAdmin) -admin.site.register(ProposalVote, ProposalVoteAdmin) -admin.site.register(ProposalComment, ProposalCommentAdmin) -admin.site.register(ProposalCommentVote, ProposalCommentVoteAdmin) -admin.site.register(ProposalSectionReviewer, ProposalSectionReviewerAdmin) + +@admin.register(models.ProposalComment) +class ProposalCommentAdmin(TimeAuditAdmin): + list_display = ( + "comment", + "proposal", + "commenter", + "private", + "reviewer", + ) + TimeAuditAdmin.list_display + list_filter = ["private", "reviewer", "commenter"] + + def get_queryset(self, request): + qs = super(ProposalCommentAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(proposal__conference__in=[m.conference for m in moderators]) + + +@admin.register(models.ProposalCommentVote) +class ProposalCommentVoteAdmin(TimeAuditAdmin): + list_display = ( + "proposal_comment", + "voter", + "up_vote", + ) + TimeAuditAdmin.list_display + + def get_queryset(self, request): + qs = super(ProposalCommentVoteAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter( + proposal_comment__proposal__conference__in=[ + m.conference for m in moderators + ] + ) diff --git a/junction/proposals/comment_urls.py b/junction/proposals/comment_urls.py deleted file mode 100644 index ff8eca95..00000000 --- a/junction/proposals/comment_urls.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -# Third Party Stuff -from django.conf.urls import patterns, url - -from . import views - -urlpatterns = patterns( - '', - url(r'^(?P[\w-]+)/create/$', - views.create_proposal_comment, name='proposal-comment-create'), - url(r'^(?P[\w-]+)/comments/(?P\d+)/up-vote/$', - views.proposal_comment_up_vote, name='proposal-comment-up-vote'), - url(r'^(?P[\w-]+)/comments/(?P\d+)/down-vote/$', - views.proposal_comment_down_vote, name='proposal-comment-down-vote'), -) diff --git a/junction/proposals/comments_views.py b/junction/proposals/comments_views.py new file mode 100644 index 00000000..cce370cd --- /dev/null +++ b/junction/proposals/comments_views.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.urls import reverse +from django.http.response import ( + HttpResponse, + HttpResponseForbidden, + HttpResponseRedirect, +) +from django.shortcuts import Http404, get_object_or_404 +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods + +from junction.conferences.models import Conference + +from . import permissions +from .forms import ProposalCommentForm +from .models import Proposal, ProposalComment +from .services import send_mail_for_new_comment, user_action_for_spam + + +@login_required +@require_http_methods(["POST"]) +def create_proposal_comment(request, conference_slug, proposal_slug): + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + form = ProposalCommentForm(request.POST) + + if request.user.is_active is False: + raise PermissionDenied() + + if form.is_valid(): + comment = form.cleaned_data["comment"] + private = form.cleaned_data["private"] + reviewer = form.cleaned_data["reviewer"] + + has_perm = permissions.is_proposal_author_or_proposal_section_reviewer( + user=request.user, conference=conference, proposal=proposal + ) + + if private and not has_perm: + raise Http404() + + proposal_comment = ProposalComment.objects.create( + proposal=proposal, + comment=comment, + private=private, + reviewer=reviewer, + commenter=request.user, + ) + host = "{}://{}".format(settings.SITE_PROTOCOL, request.META.get("HTTP_HOST")) + + if settings.USE_ASYNC_FOR_EMAIL: + send_mail_for_new_comment.delay(proposal_comment.id, host) + else: + send_mail_for_new_comment(proposal_comment.id, host) + + redirect_url = reverse("proposal-detail", args=[conference.slug, proposal.slug]) + + if private: + redirect_url += "#js-reviewers" + elif reviewer: + redirect_url += "#js-only-reviewers" + else: + redirect_url += "#js-comments" + + return HttpResponseRedirect(redirect_url) + + +@login_required +@csrf_exempt +@require_http_methods(["POST"]) +def mark_comment_as_spam(request, conference_slug, proposal_slug, proposal_comment_id): + if not request.is_ajax() or request.user.is_active is False: + return HttpResponseForbidden() + + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + proposal_comment = get_object_or_404( + ProposalComment, proposal=proposal, id=proposal_comment_id + ) + + if proposal_comment.is_spam: + return HttpResponse("Already marked as spam") + + proposal_comment.is_spam = True + proposal_comment.marked_as_spam_by = request.user + proposal_comment.save() + + user_action_for_spam( + proposal_comment.commenter, getattr(settings, "USER_SPAM_THRESHOLD", 2) + ) + + return HttpResponse("Marked as spam") + + +@login_required +@csrf_exempt +@require_http_methods(["POST"]) +def unmark_comment_as_spam( + request, conference_slug, proposal_slug, proposal_comment_id +): + if not request.is_ajax() or request.user.is_active is False: + return HttpResponseForbidden() + + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + proposal_comment = get_object_or_404( + ProposalComment, proposal=proposal, id=proposal_comment_id + ) + + if proposal_comment.is_spam and proposal_comment.marked_as_spam_by == request.user: + proposal_comment.is_spam = False + proposal_comment.marked_as_spam_by = None + proposal_comment.save() + + user_action_for_spam( + proposal_comment.commenter, getattr(settings, "USER_SPAM_THRESHOLD", 2) + ) + + return HttpResponse("Unmarked as spam") + + return HttpResponseForbidden() diff --git a/junction/proposals/dashboard.py b/junction/proposals/dashboard.py new file mode 100644 index 00000000..c9886403 --- /dev/null +++ b/junction/proposals/dashboard.py @@ -0,0 +1,410 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, unicode_literals + +import collections +import io +import uuid + +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.http import HttpResponse +from django.shortcuts import get_object_or_404, render +from django.views.decorators.http import require_http_methods +from xlsxwriter.workbook import Workbook + +from junction.base.constants import ( + ProposalReviewStatus, + ProposalReviewVote, + ProposalStatus, +) +from junction.conferences.models import Conference, ConferenceProposalReviewer + +from . import permissions, services +from .forms import ProposalVotesFilterForm +from .models import ( + Proposal, + ProposalComment, + ProposalSectionReviewer, + ProposalSectionReviewerVoteValue, +) +from .permissions import is_conference_moderator +from .utils import _sort_proposals_for_dashboard + + +@login_required +@require_http_methods(["GET"]) +def proposals_dashboard(request, conference_slug): + conference = get_object_or_404(Conference, slug=conference_slug) + + if not is_conference_moderator(user=request.user, conference=conference): + raise PermissionDenied + + proposals_qs = Proposal.objects.filter( + conference=conference, status=ProposalStatus.PUBLIC + ) + + by_type = {} + by_section = {} + by_reviewer = {} + by_audience = {} + reviewed_count = 0 + unreviewed_count = 0 + for proposal in proposals_qs: + pro_type = proposal.proposal_type + section = proposal.proposal_section + # dict structure {'id':[total, review, unreview, name]} + by_type.setdefault(pro_type.id, [0, 0, 0, pro_type.name]) + by_type[pro_type.id][0] = by_type[pro_type.id][0] + 1 + by_section.setdefault(section.id, [0, 0, 0, section.name]) + by_section[section.id][0] = by_section[section.id][0] + 1 + private_comment_count = ProposalComment.objects.filter( + proposal=proposal, deleted=False, private=True + ).count() + if private_comment_count: + reviewed_count = reviewed_count + 1 + by_type[pro_type.id][1] = by_type[pro_type.id][1] + 1 + by_section[section.id][1] = by_section[section.id][1] + 1 + else: + unreviewed_count = unreviewed_count + 1 + by_type[pro_type.id][2] = by_type[pro_type.id][2] + 1 + by_section[section.id][2] = by_section[section.id][2] + 1 + sections = ProposalSectionReviewer.objects.filter( + conference_reviewer__reviewer=request.user + ).distinct("proposal_section__id") + # Hande case if reviewer is added to section twice' + + for section in sections: + proposal_qs = proposals_qs.filter(proposal_section=section.proposal_section) + # due to space and number issue for key used this + key_id = "%s" % section.proposal_section.id + by_reviewer.setdefault( + key_id, [proposal_qs.count(), 0, 0, section.proposal_section.name] + ) + for proposal in proposal_qs: + private_comment_count = ProposalComment.objects.filter( + proposal=proposal, deleted=False, private=True + ).count() + if private_comment_count: + by_reviewer[key_id][1] = by_reviewer[key_id][1] + 1 + else: + by_reviewer[key_id][2] = by_reviewer[key_id][2] + 1 + + audience_dict = {1: "Beginner", 2: "Intermediate", 3: "Advanced"} + + for proposal in proposals_qs: + audience = audience_dict[proposal.target_audience] + by_audience.setdefault(audience, [0, 0, 0, audience]) + private_comment_count = ProposalComment.objects.filter( + proposal=proposal, deleted=False, private=True + ).count() + if private_comment_count: + by_audience[audience][1] = by_audience[audience][1] + 1 + by_audience[audience][0] = by_audience[audience][0] + 1 + else: + by_audience[audience][2] = by_audience[audience][2] + 1 + by_audience[audience][0] = by_audience[audience][0] + 1 + + ctx = { + "conference": conference, + "total": proposals_qs.count(), + "reviewed": reviewed_count, + "unreviewed": unreviewed_count, + "group_by_type": by_type, + "group_by_section": by_section, + "group_by_reviewer_section": by_reviewer, + "by_target_audience": by_audience, + "is_proposal_reviewer": permissions.is_proposal_reviewer( + user=request.user, conference=conference + ) + } + return render(request, "proposals/dashboard.html", ctx) + + +@login_required +@require_http_methods(["GET"]) +def reviewer_comments_dashboard(request, conference_slug): + conference = get_object_or_404(Conference, slug=conference_slug) + + if not is_conference_moderator(user=request.user, conference=conference): + raise PermissionDenied + conference_reviewers = ConferenceProposalReviewer.objects.filter( + conference=conference, active=True + ) + proposals_qs = Proposal.objects.filter( + conference=conference, status=ProposalStatus.PUBLIC + ) + by_conference = {} + by_section = {} + for reviewers in conference_reviewers: + id = reviewers.reviewer.id + by_conference.setdefault(id, [reviewers.reviewer, 0]) + by_conference[id][1] = ( + ProposalComment.objects.filter( + commenter=reviewers.reviewer, + deleted=False, + private=True, + proposal__status=ProposalStatus.PUBLIC, + proposal__conference=conference, + ) + .order_by("proposal") + .distinct("proposal") + .count() + ) + # by_section is dict with + # find each reviewers section and their comments + # Need to rework on this code section to make it 1-2 loops + by_section.setdefault(id, {"reviewer": reviewers.reviewer, "interaction": []}) + reviewers_section = ProposalSectionReviewer.objects.filter( + conference_reviewer=reviewers + ) + for section in reviewers_section: + proposal_qs = proposals_qs.filter(proposal_section=section.proposal_section) + commented = 0 + uncommented = 0 + for proposal in proposal_qs: + private_comment_count = ProposalComment.objects.filter( + proposal=proposal, + deleted=False, + private=True, + commenter=reviewers.reviewer, + ).count() + if private_comment_count: + commented = commented + 1 + else: + uncommented = uncommented + 1 + by_section[id]["interaction"].append( + [ + proposal_qs.count(), + commented, + uncommented, + section.proposal_section.name, + ] + ) + + ctx = { + "conference": conference, + "conference_reviewers": conference_reviewers, + "by_conference": by_conference, + "by_section": by_section, + "is_proposal_reviewer": permissions.is_proposal_reviewer( + user=request.user, conference=conference + ) + } + + return render(request, "proposals/reviewers_dashboard.html", ctx) + + +@require_http_methods(["GET", "POST"]) +def reviewer_votes_dashboard(request, conference_slug): + conference = get_object_or_404(Conference, slug=conference_slug) + user = request.user + if not is_conference_moderator(user=request.user, conference=conference): + raise PermissionDenied + + proposal_sections = conference.proposal_sections.all() + proposals_qs = Proposal.objects.select_related( + "proposal_type", "proposal_section", "conference", "author", + ).filter(conference=conference, status=ProposalStatus.PUBLIC) + + proposals = [] + s_items = collections.namedtuple("section_items", "section proposals") + form = ProposalVotesFilterForm(conference=conference) + + if request.method == "GET": + for section in proposal_sections: + section_proposals = [ + p for p in proposals_qs if p.proposal_section == section + ] + proposals.append(s_items(section, section_proposals)) + + return render( + request, + "proposals/votes-dashboard.html", + {"conference": conference, "proposals": proposals, "form": form}, + ) + + form = ProposalVotesFilterForm(conference=conference, data=request.POST) + + if not form.is_valid(): + return render( + request, + "proposals/votes-dashboard.html", + {"form": form, "conference": conference, "errors": form.errors}, + ) + + # Valid form + + proposals = _sort_proposals_for_dashboard(conference, proposals_qs, user, form) + + return render( + request, + "proposals/votes-dashboard.html", + {"conference": conference, "proposals": proposals, "form": form}, + ) + + +@require_http_methods(["GET", "POST"]) +def second_phase_voting(request, conference_slug): + conference = get_object_or_404(Conference, slug=conference_slug) + user = request.user + + if not permissions.is_proposal_reviewer(request.user, conference): + raise PermissionDenied + + proposal_sections = conference.proposal_sections.all() + proposals_qs = Proposal.objects.select_related( + "proposal_type", "proposal_section", "conference", "author", + ).filter(conference=conference, review_status=ProposalReviewStatus.SELECTED) + + proposals = [] + s_items = collections.namedtuple("section_items", "section proposals") + form = ProposalVotesFilterForm(conference=conference) + + if request.method == "GET": + for section in proposal_sections: + section_proposals = [ + p for p in proposals_qs if p.proposal_section == section + ] + proposals.append(s_items(section, section_proposals)) + + return render( + request, + "proposals/second_phase_voting.html", + {"conference": conference, "proposals": proposals, "form": form}, + ) + + form = ProposalVotesFilterForm(conference=conference, data=request.POST) + + if not form.is_valid(): + return render( + request, + "proposals/votes-dashboard.html", + {"form": form, "conference": conference, "errors": form.errors}, + ) + + # Valid form + proposals = _sort_proposals_for_dashboard(conference, proposals_qs, user, form) + + return render( + request, + "proposals/second_phase_voting.html", + {"conference": conference, "proposals": proposals, "form": form}, + ) + + +@require_http_methods(["GET", "POST"]) +def export_reviewer_votes(request, conference_slug): + """ + Write reviewer votes to a spreadsheet. + """ + conference = get_object_or_404(Conference, slug=conference_slug) + + if not is_conference_moderator(user=request.user, conference=conference): + raise PermissionDenied + + proposal_sections = conference.proposal_sections.all() + proposals_qs = Proposal.objects.select_related( + "proposal_type", "proposal_section", "conference", "author", + ).filter(conference=conference, status=ProposalStatus.PUBLIC) + proposals_qs = sorted( + proposals_qs, key=lambda x: x.get_reviewer_votes_sum(), reverse=True + ) + vote_values_list = ProposalSectionReviewerVoteValue.objects.order_by("-vote_value") + vote_values_list = [v.vote_value for v in vote_values_list] + vote_values_desc = tuple( + i.description + for i in ProposalSectionReviewerVoteValue.objects.order_by("-vote_value") + ) + header = ( + ("Proposal Type", "Title", "Sum of reviewer votes", "No. of reviewer votes") + + tuple(vote_values_desc) + + ("Public votes count", "Vote Comments") + ) + output = io.BytesIO() + + with Workbook(output) as book: + for section in proposal_sections: + sheet = book.add_worksheet(section.name[:30]) + cell_format = book.add_format({"bold": True}) + sheet.write_row(0, 0, header, cell_format) + + section_proposals = [ + p for p in proposals_qs if p.proposal_section == section + ] + + for index, p in enumerate(section_proposals, 1): + vote_details = tuple( + p.get_reviewer_votes_count_by_value(v) for v in vote_values_list + ) + vote_comment = "\n".join( + [ + comment.comment + for comment in p.proposalcomment_set.filter( + vote=True, deleted=False, + ) + ] + ) + row = ( + ( + p.proposal_type.name, + p.title, + p.get_reviewer_votes_sum(), + p.get_reviewer_votes_count(), + ) + + vote_details + + (p.get_votes_count(), vote_comment,) + ) + if ( + p.get_reviewer_votes_count_by_value( + ProposalSectionReviewerVoteValue.objects.get( + vote_value=ProposalReviewVote.NOT_ALLOWED + ).vote_value + ) + > 0 + ): + cell_format = book.add_format({"bg_color": "red"}) + elif ( + p.get_reviewer_votes_count_by_value( + ProposalSectionReviewerVoteValue.objects.get( + vote_value=ProposalReviewVote.MUST_HAVE + ).vote_value + ) + > 2 + ): + cell_format = book.add_format({"bg_color": "green"}) + elif p.get_reviewer_votes_count() < 2: + cell_format = book.add_format({"bg_color": "yellow"}) + else: + cell_format = None + + sheet.write_row(index, 0, row, cell_format) + + output.seek(0) + response = HttpResponse( + output.read(), + content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + file_name = str(uuid.uuid4())[:8] + response["Content-Disposition"] = "attachment; filename=junction-{}.xlsx".format( + file_name + ) + + return response + + +@login_required +@require_http_methods(["GET"]) +def proposal_state(request, conference_slug): + conf = get_object_or_404(Conference, slug=conference_slug) + + if not is_conference_moderator(user=request.user, conference=conf): + raise PermissionDenied + + state = request.GET.get("q", "unreviewed") + proposals = services.group_proposals_by_reveiew_state(conf=conf, state=state) + return render( + request, + "proposals/review_state.html", + {"conference": conf, "proposals": dict(proposals), "state": state.title()}, + ) diff --git a/junction/proposals/forms.py b/junction/proposals/forms.py index 082b8946..72640dd5 100644 --- a/junction/proposals/forms.py +++ b/junction/proposals/forms.py @@ -1,27 +1,72 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff +from datetime import datetime from django import forms from django.utils.safestring import mark_safe from pagedown.widgets import PagedownWidget -# Junction Stuff -from junction.base.constants import PROPOSAL_REVIEW_STATUS_LIST, PROPOSAL_STATUS_LIST, PROPOSAL_TARGET_AUDIENCES -from junction.proposals.models import ProposalSection, ProposalType +from junction.base.constants import ( + ConferenceSettingConstants, + ProposalReviewerComment, + ProposalReviewStatus, + ProposalStatus, + ProposalTargetAudience, + ProposalVotesFilter, +) +from junction.proposals.models import ( + ProposalSection, + ProposalSectionReviewerVoteValue, + ProposalType, +) -def _get_proposal_section_choices(conference): - return [(str(cps.id), cps.name) - for cps in ProposalSection.objects.filter(conferences=conference)] +def _get_proposal_section_choices(conference, action="edit"): + if action == "create": + return [ + (str(cps.id), cps.name) + for cps in ProposalSection.objects.filter(conferences=conference) + ] + else: + return [ + (str(cps.id), cps.name) + for cps in ProposalSection.objects.filter(conferences=conference) + ] -def _get_proposal_type_choices(conference): - return [(str(cpt.id), cpt.name) - for cpt in ProposalType.objects.filter(conferences=conference)] +def _get_proposal_type_choices(conference, action="edit"): + if action == "create": + return [ + (str(cpt.id), cpt.name) + for cpt in ProposalType.objects.filter( + conferences=conference, end_date__gte=datetime.now() + ) + ] + else: + return [ + (str(cpt.id), cpt.name) + for cpt in ProposalType.objects.filter(conferences=conference) + ] -class HorizRadioRenderer(forms.RadioSelect.renderer): +def _get_proposal_section_reviewer_vote_choices(conference): + allow_plus_zero_vote = ConferenceSettingConstants.ALLOW_PLUS_ZERO_REVIEWER_VOTE + plus_zero_vote_setting = conference.conferencesetting_set.filter( + name=allow_plus_zero_vote["name"] + ).first() + if plus_zero_vote_setting: + plus_zero_vote_setting_value = plus_zero_vote_setting.value + else: + plus_zero_vote_setting_value = True + values = [] + for i in ProposalSectionReviewerVoteValue.objects.all().reverse(): + if i.vote_value == 0 and not plus_zero_vote_setting_value: + continue + values.append((i.vote_value, "{}".format(i.description))) + return values + + +class HorizRadioRenderer(forms.RadioSelect): """ This overrides widget method to put radio buttons horizontally instead of vertically. @@ -29,77 +74,127 @@ class HorizRadioRenderer(forms.RadioSelect.renderer): def render(self): """Outputs radios""" - return mark_safe(u'\n'.join([u'%s\n' % w for w in self])) + return mark_safe("\n".join(["%s\n" % w for w in self])) class ProposalForm(forms.Form): - ''' + """ Used for create/edit - ''' - title = forms.CharField(min_length=10, - help_text="Title of the proposal, no buzz words!", - widget=forms.TextInput(attrs={'class': 'charfield'})) - description = forms.CharField(widget=PagedownWidget(show_preview=True), - help_text=("Describe your proposal with clear objective in simple sentence." - " Keep it short and simple.")) + """ + + title = forms.CharField( + min_length=10, + help_text="Title of the Proposal", + widget=forms.TextInput(attrs={"class": "charfield"}), + ) + description = forms.CharField( + widget=PagedownWidget(attrs={"show_preview":True}), help_text=("Describe your Proposal") + ) target_audience = forms.ChoiceField( - choices=PROPOSAL_TARGET_AUDIENCES, - widget=forms.Select(attrs={'class': 'dropdown'})) + label="Target Audience", + choices=ProposalTargetAudience.CHOICES, + widget=forms.Select(attrs={"class": "dropdown"}), + ) status = forms.ChoiceField( - widget=forms.Select(attrs={'class': 'dropdown'}), - choices=PROPOSAL_STATUS_LIST, - help_text=("If you choose DRAFT people can't the see the session in the list." - " Make the proposal PUBLIC when you're done with editing the session.")) + widget=forms.Select(attrs={"class": "dropdown"}), + choices=ProposalStatus.CHOICES, + help_text=( + "If you choose DRAFT people can't see the session in the list." + " Make the proposal PUBLIC when you're done editing." + ), + ) proposal_type = forms.ChoiceField( - widget=forms.Select(attrs={'class': 'dropdown'})) + label="Proposal Type", widget=forms.Select(attrs={"class": "dropdown"}) + ) proposal_section = forms.ChoiceField( - widget=forms.Select(attrs={'class': 'dropdown'})) + label="Proposal Section", widget=forms.Select(attrs={"class": "dropdown"}) + ) # Additional Content prerequisites = forms.CharField( - widget=PagedownWidget(show_preview=True), required=False, - help_text="What should the participants know before attending your session?") + label="Pre-requisites", + widget=PagedownWidget(attrs={"show_preview":True}), + required=False, + help_text="What should the participants know before attending your session?", + ) + video_url = forms.CharField( + label="Video URL", + required=False, + help_text="Short 1-2 min video describing your talk", + widget=forms.TextInput(attrs={"class": "charfield"}), + ) content_urls = forms.CharField( - widget=PagedownWidget(show_preview=True), required=False, - help_text="Links to your session like GitHub repo, Blog, Slideshare etc ...") + label="Content URLs", + widget=PagedownWidget(attrs={"show_preview":True}), + required=False, + help_text="Links to your session like GitHub repo, Blog, Slideshare etc ...", + ) + private_content_urls = forms.BooleanField( + help_text="Check the box if you want to make your content URLs private", + label="Make the context URLs private", + required=False, + ) speaker_info = forms.CharField( - widget=PagedownWidget(show_preview=True), required=False, - help_text="Say something about yourself, work etc...") + label="Speaker Information", + widget=PagedownWidget(attrs={"show_preview":True}), + required=False, + help_text="Say something about yourself, work etc...", + ) + is_first_time_speaker = forms.BooleanField( + label="First Time Speaker", + required=False, + help_text="Please mark, if you are a first time speaker for any conference or meetup," + "not just for PyCon India", + ) speaker_links = forms.CharField( - widget=PagedownWidget(show_preview=True), required=False, - help_text="Links to your previous work like Blog, Open Source Contributions etc ...") + label="Speaker Links", + widget=PagedownWidget(attrs={"show_preview":True}), + required=False, + help_text="Links to your previous work like Blog, Open Source Contributions etc ...", + ) - def __init__(self, conference, *args, **kwargs): + def __init__(self, conference, action="edit", *args, **kwargs): super(ProposalForm, self).__init__(*args, **kwargs) - self.fields['proposal_section'].choices = _get_proposal_section_choices( - conference) - self.fields['proposal_type'].choices = _get_proposal_type_choices( - conference) + self.fields["proposal_section"].choices = _get_proposal_section_choices( + conference, action=action + ) + self.fields["proposal_type"].choices = _get_proposal_type_choices( + conference, action=action + ) @classmethod def populate_form_for_update(self, proposal): - form = ProposalForm(proposal.conference, - initial={'title': proposal.title, - 'description': proposal.description, - 'target_audience': proposal.target_audience, - 'prerequisites': proposal.prerequisites, - 'content_urls': proposal.content_urls, - 'speaker_info': proposal.speaker_info, - 'speaker_links': proposal.speaker_links, - 'status': proposal.status, - 'proposal_section': proposal.proposal_section.pk, - 'proposal_type': proposal.proposal_type.pk, }) + form = ProposalForm( + proposal.conference, + initial={ + "title": proposal.title, + "description": proposal.description, + "target_audience": proposal.target_audience, + "prerequisites": proposal.prerequisites, + "video_url": proposal.video_url, + "content_urls": proposal.content_urls, + "private_content_urls": proposal.private_content_urls, + "speaker_info": proposal.speaker_info, + "speaker_links": proposal.speaker_links, + "is_first_time_speaker": proposal.is_first_time_speaker, + "status": proposal.status, + "proposal_section": proposal.proposal_section.pk, + "proposal_type": proposal.proposal_type.pk, + }, + ) return form class ProposalCommentForm(forms.Form): - ''' + """ Used to add comments - ''' - comment = forms.CharField(widget=PagedownWidget(show_preview=True)) + """ + + comment = forms.CharField(widget=PagedownWidget(attrs={"show_preview":True})) private = forms.BooleanField(required=False, widget=forms.HiddenInput()) + reviewer = forms.BooleanField(required=False, widget=forms.HiddenInput()) class ProposalReviewForm(forms.Form): @@ -107,4 +202,82 @@ class ProposalReviewForm(forms.Form): """ Used to review the proposal. """ - review_status = forms.ChoiceField(choices=PROPOSAL_REVIEW_STATUS_LIST) + + review_status = forms.ChoiceField( + choices=ProposalReviewStatus.CHOICES, widget=forms.RadioSelect() + ) + + +class ProposalReviewerVoteForm(forms.Form): + """ + Used by ProposalSectionReviewers to vote on proposals. + """ + + vote_value = forms.ChoiceField( + widget=forms.RadioSelect(), + label="Do you think this proposal will make a good addition to PyCon India ?", + ) + comment = forms.CharField( + widget=forms.Textarea(attrs={"minlength": "30"}), + help_text="Leave a comment justifying your vote.", + ) + + def __init__(self, *args, **kwargs): + conference = kwargs.pop("conference", None) + super(ProposalReviewerVoteForm, self).__init__(*args, **kwargs) + choices = _get_proposal_section_reviewer_vote_choices(conference) + self.fields["vote_value"].choices = choices + + +class ProposalTypesChoices(forms.Form): + """ + Base proposal form with proposal sections & types. + """ + + proposal_section = forms.ChoiceField( + widget=forms.Select(attrs={"class": "dropdown"}) + ) + proposal_type = forms.ChoiceField(widget=forms.Select(attrs={"class": "dropdown"})) + + def __init__(self, conference, *args, **kwargs): + super(ProposalTypesChoices, self).__init__(*args, **kwargs) + self.fields["proposal_section"].choices = _get_proposal_section_choices( + conference + ) + self.fields["proposal_type"].choices = _get_proposal_type_choices(conference) + + +class ProposalsToReviewForm(ProposalTypesChoices): + """ + Used to filter proposals + """ + + reviewer_comment = forms.ChoiceField( + widget=forms.Select(attrs={"class": "dropdown"}) + ) + + def __init__(self, conference, proposal_sections, *args, **kwargs): + super(ProposalsToReviewForm, self).__init__(conference, *args, **kwargs) + ps_choices = [(str(ps.id), ps.name) for ps in proposal_sections] + self.fields["reviewer_comment"].choices = ProposalReviewerComment.CHOICES + self.fields["proposal_section"].choices = ps_choices + + for name, field in list(self.fields.items()): + field.choices.insert(0, ("all", "All")) + + +class ProposalVotesFilterForm(ProposalTypesChoices): + """ + Form to filter proposals based on votes and review_status. + """ + + votes = forms.ChoiceField(widget=forms.Select(attrs={"class": "dropdown votes"})) + review_status = forms.ChoiceField(widget=forms.Select(attrs={"class": "dropdown"})) + + def __init__(self, conference, *args, **kwargs): + super(ProposalVotesFilterForm, self).__init__(conference, *args, **kwargs) + self.fields["votes"].choices = ProposalVotesFilter.CHOICES + self.fields["review_status"].choices = ProposalReviewStatus.CHOICES + + for name, field in list(self.fields.items()): + field.choices.insert(0, ("all", "All")) diff --git a/junction/proposals/migrations/0001_initial.py b/junction/proposals/migrations/0001_initial.py index 00649d43..4dce77c9 100644 --- a/junction/proposals/migrations/0001_initial.py +++ b/junction/proposals/migrations/0001_initial.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff import django_extensions.db.fields from django.conf import settings from django.db import migrations, models @@ -11,186 +10,522 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('conferences', '0001_initial'), + ("conferences", "0001_initial"), ] operations = [ migrations.CreateModel( - name='ConferenceProposalSection', + name="ConferenceProposalSection", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('conference', models.ForeignKey(to='conferences.Conference')), - ('created_by', models.ForeignKey(related_name='created_conferenceproposalsection_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_conferenceproposalsection_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conferenceproposalsection_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferenceproposalsection_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='ConferenceProposalType', + name="ConferenceProposalType", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('conference', models.ForeignKey(to='conferences.Conference')), - ('created_by', models.ForeignKey(related_name='created_conferenceproposaltype_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_conferenceproposaltype_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_conferenceproposaltype_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_conferenceproposaltype_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='Proposal', + name="Proposal", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('title', models.CharField(max_length=255)), - ('slug', django_extensions.db.fields.AutoSlugField(populate_from=('title',), max_length=255, editable=False, blank=True)), - ('description', models.TextField(default='')), - ('target_audience', models.PositiveSmallIntegerField(default=1, verbose_name='Target Audience', choices=[(1, b'Beginner'), (2, b'Intermediate'), (3, b'Advanced')])), - ('prerequisites', models.TextField(default='')), - ('content_urls', models.TextField(default='')), - ('speaker_info', models.TextField(default='')), - ('speaker_links', models.TextField(default='')), - ('status', models.PositiveSmallIntegerField(default=1, choices=[(1, b'Draft'), (2, b'Public'), (3, b'Cancelled')])), - ('review_status', models.PositiveSmallIntegerField(default=1, verbose_name='Review Status', choices=[(1, b'Yet to be reviewed'), (2, b'Selected'), (3, b'Rejected'), (4, b' On hold'), (5, b'Wait-listed')])), - ('deleted', models.BooleanField(default=False, verbose_name='Is Deleted?')), - ('author', models.ForeignKey(verbose_name='Primary Speaker', to=settings.AUTH_USER_MODEL)), - ('conference', models.ForeignKey(to='conferences.Conference')), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("title", models.CharField(max_length=255)), + ( + "slug", + django_extensions.db.fields.AutoSlugField( + populate_from=("title",), + max_length=255, + editable=False, + blank=True, + ), + ), + ("description", models.TextField(default="")), + ( + "target_audience", + models.PositiveSmallIntegerField( + default=1, + verbose_name="Target Audience", + choices=[ + (1, b"Beginner"), + (2, b"Intermediate"), + (3, b"Advanced"), + ], + ), + ), + ("prerequisites", models.TextField(default="")), + ("content_urls", models.TextField(default="")), + ("speaker_info", models.TextField(default="")), + ("speaker_links", models.TextField(default="")), + ( + "status", + models.PositiveSmallIntegerField( + default=1, + choices=[(1, b"Draft"), (2, b"Public"), (3, b"Cancelled")], + ), + ), + ( + "review_status", + models.PositiveSmallIntegerField( + default=1, + verbose_name="Review Status", + choices=[ + (1, b"Yet to be reviewed"), + (2, b"Selected"), + (3, b"Rejected"), + (4, b" On hold"), + (5, b"Wait-listed"), + ], + ), + ), + ( + "deleted", + models.BooleanField(default=False, verbose_name="Is Deleted?"), + ), + ( + "author", + models.ForeignKey( + verbose_name="Primary Speaker", + to=settings.AUTH_USER_MODEL, + on_delete=models.deletion.CASCADE, + ), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='ProposalComment', + name="ProposalComment", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('private', models.BooleanField(default=False, verbose_name='Is Private?')), - ('comment', models.TextField()), - ('deleted', models.BooleanField(default=False, verbose_name='Is Deleted?')), - ('commenter', models.ForeignKey(to=settings.AUTH_USER_MODEL)), - ('proposal', models.ForeignKey(to='proposals.Proposal')), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "private", + models.BooleanField(default=False, verbose_name="Is Private?"), + ), + ("comment", models.TextField()), + ( + "deleted", + models.BooleanField(default=False, verbose_name="Is Deleted?"), + ), + ( + "commenter", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), + ( + "proposal", + models.ForeignKey( + to="proposals.Proposal", on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ProposalCommentVote', + name="ProposalCommentVote", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('up_vote', models.BooleanField(default=True)), - ('proposal_comment', models.ForeignKey(to='proposals.ProposalComment')), - ('voter', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("up_vote", models.BooleanField(default=True)), + ( + "proposal_comment", + models.ForeignKey( + to="proposals.ProposalComment", + on_delete=models.deletion.CASCADE, + ), + ), + ( + "voter", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.CreateModel( - name='ProposalSection', + name="ProposalSection", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('name', models.CharField(max_length=255, verbose_name='Proposal Section Name')), - ('description', models.TextField(default='')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('created_by', models.ForeignKey(related_name='created_proposalsection_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_proposalsection_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "name", + models.CharField( + max_length=255, verbose_name="Proposal Section Name" + ), + ), + ("description", models.TextField(default="")), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_proposalsection_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_proposalsection_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ProposalType', + name="ProposalType", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('name', models.CharField(max_length=255, verbose_name='Proposal Type Name')), - ('description', models.TextField(default='')), - ('active', models.BooleanField(default=True, verbose_name='Is Active?')), - ('created_by', models.ForeignKey(related_name='created_proposaltype_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_proposaltype_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "name", + models.CharField(max_length=255, verbose_name="Proposal Type Name"), + ), + ("description", models.TextField(default="")), + ( + "active", + models.BooleanField(default=True, verbose_name="Is Active?"), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_proposaltype_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_proposaltype_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), migrations.CreateModel( - name='ProposalVote', + name="ProposalVote", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('role', models.PositiveSmallIntegerField(default=1, choices=[(1, b'Public'), (2, b'Reviewer')])), - ('up_vote', models.BooleanField(default=True)), - ('proposal', models.ForeignKey(to='proposals.Proposal')), - ('voter', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "role", + models.PositiveSmallIntegerField( + default=1, choices=[(1, b"Public"), (2, b"Reviewer")] + ), + ), + ("up_vote", models.BooleanField(default=True)), + ( + "proposal", + models.ForeignKey( + to="proposals.Proposal", on_delete=models.deletion.CASCADE, + ), + ), + ( + "voter", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - }, + options={}, bases=(models.Model,), ), migrations.AlterUniqueTogether( - name='proposalvote', - unique_together=set([('proposal', 'voter')]), + name="proposalvote", unique_together=set([("proposal", "voter")]), ), migrations.AlterUniqueTogether( - name='proposalcommentvote', - unique_together=set([('proposal_comment', 'voter')]), + name="proposalcommentvote", + unique_together=set([("proposal_comment", "voter")]), ), migrations.AddField( - model_name='proposal', - name='proposal_section', - field=models.ForeignKey(verbose_name='Proposal Section', to='proposals.ProposalSection'), + model_name="proposal", + name="proposal_section", + field=models.ForeignKey( + verbose_name="Proposal Section", + to="proposals.ProposalSection", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AddField( - model_name='proposal', - name='proposal_type', - field=models.ForeignKey(verbose_name='Proposal Type', to='proposals.ProposalType'), + model_name="proposal", + name="proposal_type", + field=models.ForeignKey( + verbose_name="Proposal Type", + to="proposals.ProposalType", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AlterUniqueTogether( - name='proposal', - unique_together=set([('conference', 'slug')]), + name="proposal", unique_together=set([("conference", "slug")]), ), migrations.AddField( - model_name='conferenceproposaltype', - name='proposal_type', - field=models.ForeignKey(verbose_name='Proposal Type', to='proposals.ProposalType'), + model_name="conferenceproposaltype", + name="proposal_type", + field=models.ForeignKey( + verbose_name="Proposal Type", + to="proposals.ProposalType", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AlterUniqueTogether( - name='conferenceproposaltype', - unique_together=set([('conference', 'proposal_type')]), + name="conferenceproposaltype", + unique_together=set([("conference", "proposal_type")]), ), migrations.AddField( - model_name='conferenceproposalsection', - name='proposal_section', - field=models.ForeignKey(verbose_name='Proposal Section', to='proposals.ProposalSection'), + model_name="conferenceproposalsection", + name="proposal_section", + field=models.ForeignKey( + verbose_name="Proposal Section", + to="proposals.ProposalSection", + on_delete=models.deletion.CASCADE, + ), preserve_default=True, ), migrations.AlterUniqueTogether( - name='conferenceproposalsection', - unique_together=set([('conference', 'proposal_section')]), + name="conferenceproposalsection", + unique_together=set([("conference", "proposal_section")]), ), ] diff --git a/junction/proposals/migrations/0002_auto_20150105_2220.py b/junction/proposals/migrations/0002_auto_20150105_2220.py index 2f7b6392..8816f4c4 100644 --- a/junction/proposals/migrations/0002_auto_20150105_2220.py +++ b/junction/proposals/migrations/0002_auto_20150105_2220.py @@ -1,74 +1,55 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('conferences', '0001_initial'), - ('proposals', '0001_initial'), + ("conferences", "0001_initial"), + ("proposals", "0001_initial"), ] operations = [ migrations.AlterUniqueTogether( - name='conferenceproposalsection', - unique_together=None, + name="conferenceproposalsection", unique_together=None, ), migrations.RemoveField( - model_name='conferenceproposalsection', - name='conference', + model_name="conferenceproposalsection", name="conference", ), migrations.RemoveField( - model_name='conferenceproposalsection', - name='created_by', + model_name="conferenceproposalsection", name="created_by", ), migrations.RemoveField( - model_name='conferenceproposalsection', - name='modified_by', + model_name="conferenceproposalsection", name="modified_by", ), migrations.RemoveField( - model_name='conferenceproposalsection', - name='proposal_section', - ), - migrations.DeleteModel( - name='ConferenceProposalSection', + model_name="conferenceproposalsection", name="proposal_section", ), + migrations.DeleteModel(name="ConferenceProposalSection",), migrations.AlterUniqueTogether( - name='conferenceproposaltype', - unique_together=None, - ), - migrations.RemoveField( - model_name='conferenceproposaltype', - name='conference', + name="conferenceproposaltype", unique_together=None, ), + migrations.RemoveField(model_name="conferenceproposaltype", name="conference",), + migrations.RemoveField(model_name="conferenceproposaltype", name="created_by",), migrations.RemoveField( - model_name='conferenceproposaltype', - name='created_by', + model_name="conferenceproposaltype", name="modified_by", ), migrations.RemoveField( - model_name='conferenceproposaltype', - name='modified_by', - ), - migrations.RemoveField( - model_name='conferenceproposaltype', - name='proposal_type', - ), - migrations.DeleteModel( - name='ConferenceProposalType', + model_name="conferenceproposaltype", name="proposal_type", ), + migrations.DeleteModel(name="ConferenceProposalType",), migrations.AddField( - model_name='proposalsection', - name='conferences', - field=models.ManyToManyField(to='conferences.Conference'), + model_name="proposalsection", + name="conferences", + field=models.ManyToManyField(to="conferences.Conference"), preserve_default=True, ), migrations.AddField( - model_name='proposaltype', - name='conferences', - field=models.ManyToManyField(to='conferences.Conference'), + model_name="proposaltype", + name="conferences", + field=models.ManyToManyField(to="conferences.Conference"), preserve_default=True, ), ] diff --git a/junction/proposals/migrations/0003_auto_20150113_1401.py b/junction/proposals/migrations/0003_auto_20150113_1401.py index f51e93a4..a5ac8e99 100644 --- a/junction/proposals/migrations/0003_auto_20150113_1401.py +++ b/junction/proposals/migrations/0003_auto_20150113_1401.py @@ -1,27 +1,30 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('proposals', '0002_auto_20150105_2220'), + ("proposals", "0002_auto_20150105_2220"), ] operations = [ migrations.AlterField( - model_name='proposalsection', - name='conferences', - field=models.ManyToManyField(to='conferences.Conference', related_name='proposal_sections'), + model_name="proposalsection", + name="conferences", + field=models.ManyToManyField( + to="conferences.Conference", related_name="proposal_sections" + ), preserve_default=True, ), migrations.AlterField( - model_name='proposaltype', - name='conferences', - field=models.ManyToManyField(to='conferences.Conference', related_name='proposal_types'), + model_name="proposaltype", + name="conferences", + field=models.ManyToManyField( + to="conferences.Conference", related_name="proposal_types" + ), preserve_default=True, ), ] diff --git a/junction/proposals/migrations/0004_proposalsectionreviewer.py b/junction/proposals/migrations/0004_proposalsectionreviewer.py index 8e1c8bde..d007cbad 100644 --- a/junction/proposals/migrations/0004_proposalsectionreviewer.py +++ b/junction/proposals/migrations/0004_proposalsectionreviewer.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.conf import settings from django.db import migrations, models @@ -10,25 +9,73 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('conferences', '0006_auto_20150216_1929'), - ('proposals', '0003_auto_20150113_1401'), + ("conferences", "0006_auto_20150216_1929"), + ("proposals", "0003_auto_20150113_1401"), ] operations = [ migrations.CreateModel( - name='ProposalSectionReviewer', + name="ProposalSectionReviewer", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('modified_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('conference_reviewer', models.ForeignKey(verbose_name='Conference Proposal Reviewers', to='conferences.ConferenceProposalReviewer')), - ('created_by', models.ForeignKey(related_name='created_proposalsectionreviewer_set', verbose_name='Created By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('modified_by', models.ForeignKey(related_name='updated_proposalsectionreviewer_set', verbose_name='Modified By', blank=True, to=settings.AUTH_USER_MODEL, null=True)), - ('proposal_section', models.ForeignKey(verbose_name='Proposal Section', to='proposals.ProposalSection')), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "conference_reviewer", + models.ForeignKey( + verbose_name="Conference Proposal Reviewers", + to="conferences.ConferenceProposalReviewer", + on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_proposalsectionreviewer_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_proposalsectionreviewer_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "proposal_section", + models.ForeignKey( + verbose_name="Proposal Section", + to="proposals.ProposalSection", + on_delete=models.deletion.CASCADE, + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model,), ), ] diff --git a/junction/proposals/migrations/0005_proposalsectionreviewer_active.py b/junction/proposals/migrations/0005_proposalsectionreviewer_active.py index 6dbd93e5..043bd681 100644 --- a/junction/proposals/migrations/0005_proposalsectionreviewer_active.py +++ b/junction/proposals/migrations/0005_proposalsectionreviewer_active.py @@ -1,21 +1,20 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -# Third Party Stuff from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('proposals', '0004_proposalsectionreviewer'), + ("proposals", "0004_proposalsectionreviewer"), ] operations = [ migrations.AddField( - model_name='proposalsectionreviewer', - name='active', - field=models.BooleanField(default=True, verbose_name='Is Active?'), + model_name="proposalsectionreviewer", + name="active", + field=models.BooleanField(default=True, verbose_name="Is Active?"), preserve_default=True, ), ] diff --git a/junction/proposals/migrations/0006_auto_20150416_1612.py b/junction/proposals/migrations/0006_auto_20150416_1612.py new file mode 100644 index 00000000..c4b8829b --- /dev/null +++ b/junction/proposals/migrations/0006_auto_20150416_1612.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0005_proposalsectionreviewer_active"), + ] + + operations = [ + migrations.AlterField( + model_name="proposal", + name="content_urls", + field=models.TextField(default="", blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="prerequisites", + field=models.TextField(default="", blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="review_status", + field=models.PositiveSmallIntegerField( + default=1, + verbose_name="Review Status", + choices=[ + (1, "Yet to be reviewed"), + (2, "Selected"), + (3, "Rejected"), + (4, "On hold"), + (5, "Wait-listed"), + ], + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="speaker_info", + field=models.TextField(default="", blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="speaker_links", + field=models.TextField(default="", blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="status", + field=models.PositiveSmallIntegerField( + default=1, choices=[(2, "Public"), (1, "Draft"), (3, "Cancelled")] + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0007_auto_20150522_2358.py b/junction/proposals/migrations/0007_auto_20150522_2358.py new file mode 100644 index 00000000..c5b0c472 --- /dev/null +++ b/junction/proposals/migrations/0007_auto_20150522_2358.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0006_auto_20150416_1612"), + ] + + operations = [ + migrations.AddField( + model_name="proposalcomment", + name="reviewer", + field=models.BooleanField(default=False, verbose_name="Is Reviewer?"), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="target_audience", + field=models.PositiveSmallIntegerField( + default=1, + verbose_name="Target Audience", + choices=[(1, "Beginner"), (2, "Intermediate"), (3, "Advanced")], + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposalvote", + name="role", + field=models.PositiveSmallIntegerField( + default=1, choices=[(1, "Public"), (2, "Reviewer")] + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0007_auto_20150525_2150.py b/junction/proposals/migrations/0007_auto_20150525_2150.py new file mode 100644 index 00000000..5ad089f9 --- /dev/null +++ b/junction/proposals/migrations/0007_auto_20150525_2150.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0006_auto_20150416_1612"), + ] + + operations = [ + migrations.CreateModel( + name="ProposalSectionReviewerVote", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "role", + models.PositiveSmallIntegerField( + default=2, choices=[(1, "Public"), (2, "Reviewer")] + ), + ), + ("up_vote", models.BooleanField(default=True)), + ( + "proposal", + models.ForeignKey( + to="proposals.Proposal", on_delete=models.deletion.CASCADE, + ), + ), + ( + "voter", + models.ForeignKey( + to="proposals.ProposalSectionReviewer", + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={}, + bases=(models.Model,), + ), + migrations.AlterUniqueTogether( + name="proposalsectionreviewervote", + unique_together=set([("proposal", "voter")]), + ), + ] diff --git a/junction/proposals/migrations/0008_auto_20150528_2243.py b/junction/proposals/migrations/0008_auto_20150528_2243.py new file mode 100644 index 00000000..1c528b17 --- /dev/null +++ b/junction/proposals/migrations/0008_auto_20150528_2243.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0007_auto_20150525_2150"), + ] + + operations = [ + migrations.RemoveField( + model_name="proposalsectionreviewervote", name="up_vote", + ), + migrations.AddField( + model_name="proposalsectionreviewervote", + name="vote_value", + field=models.SmallIntegerField(default=True), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0009_auto_20150529_2216.py b/junction/proposals/migrations/0009_auto_20150529_2216.py new file mode 100644 index 00000000..3758eec7 --- /dev/null +++ b/junction/proposals/migrations/0009_auto_20150529_2216.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("proposals", "0008_auto_20150528_2243"), + ] + + operations = [ + migrations.CreateModel( + name="ProposalSectionReviewerVoteValue", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("vote_value", models.SmallIntegerField()), + ("description", models.CharField(max_length=255)), + ( + "created_by", + models.ForeignKey( + related_name="created_proposalsectionreviewervotevalue_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_proposalsectionreviewervotevalue_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.AlterField( + model_name="proposalsectionreviewervote", + name="vote_value", + field=models.ForeignKey( + to="proposals.ProposalSectionReviewerVoteValue", + on_delete=models.deletion.CASCADE, + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0010_merge.py b/junction/proposals/migrations/0010_merge.py new file mode 100644 index 00000000..c1858b1b --- /dev/null +++ b/junction/proposals/migrations/0010_merge.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0007_auto_20150522_2358"), + ("proposals", "0009_auto_20150529_2216"), + ] + + operations = [] diff --git a/junction/proposals/migrations/0011_auto_20150530_0224.py b/junction/proposals/migrations/0011_auto_20150530_0224.py new file mode 100644 index 00000000..e8a3029e --- /dev/null +++ b/junction/proposals/migrations/0011_auto_20150530_0224.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0010_merge"), + ] + + operations = [ + migrations.AlterModelOptions( + name="proposalsectionreviewervotevalue", + options={"ordering": ("-vote_value",)}, + ), + ] diff --git a/junction/proposals/migrations/0012_auto_20150709_0842.py b/junction/proposals/migrations/0012_auto_20150709_0842.py new file mode 100644 index 00000000..4f4fbf41 --- /dev/null +++ b/junction/proposals/migrations/0012_auto_20150709_0842.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import datetime + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0011_auto_20150530_0224"), + ] + + operations = [ + migrations.AddField( + model_name="proposalsection", + name="end_date", + field=models.DateField( + default=datetime.datetime.now, verbose_name="End Date" + ), + preserve_default=True, + ), + migrations.AddField( + model_name="proposalsection", + name="start_date", + field=models.DateField( + default=datetime.datetime.now, verbose_name="Start Date" + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0013_proposalcomment_vote.py b/junction/proposals/migrations/0013_proposalcomment_vote.py new file mode 100644 index 00000000..20ed96d6 --- /dev/null +++ b/junction/proposals/migrations/0013_proposalcomment_vote.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0012_auto_20150709_0842"), + ] + + operations = [ + migrations.AddField( + model_name="proposalcomment", + name="vote", + field=models.BooleanField(default=False, verbose_name="Is Justification?"), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0014_auto_20150729_0131.py b/junction/proposals/migrations/0014_auto_20150729_0131.py new file mode 100644 index 00000000..cc4d438d --- /dev/null +++ b/junction/proposals/migrations/0014_auto_20150729_0131.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import datetime + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0013_proposalcomment_vote"), + ] + + operations = [ + migrations.RemoveField(model_name="proposalsection", name="end_date",), + migrations.RemoveField(model_name="proposalsection", name="start_date",), + migrations.AddField( + model_name="proposaltype", + name="end_date", + field=models.DateField( + default=datetime.datetime.now, verbose_name="End Date" + ), + preserve_default=True, + ), + migrations.AddField( + model_name="proposaltype", + name="start_date", + field=models.DateField( + default=datetime.datetime.now, verbose_name="Start Date" + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="review_status", + field=models.PositiveSmallIntegerField( + default=1, + verbose_name="Review Status", + choices=[ + (4, "On hold"), + (3, "Rejected"), + (2, "Selected"), + (5, "Wait-listed"), + (1, "Yet to be reviewed"), + ], + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="status", + field=models.PositiveSmallIntegerField( + default=1, choices=[(3, "Cancelled"), (1, "Draft"), (2, "Public")] + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposal", + name="target_audience", + field=models.PositiveSmallIntegerField( + default=1, + verbose_name="Target Audience", + choices=[(3, "Advanced"), (1, "Beginner"), (2, "Intermediate")], + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposalcomment", + name="vote", + field=models.BooleanField( + default=False, verbose_name="What is the reason?" + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0015_auto_20150806_2312.py b/junction/proposals/migrations/0015_auto_20150806_2312.py new file mode 100644 index 00000000..618657ec --- /dev/null +++ b/junction/proposals/migrations/0015_auto_20150806_2312.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import django_extensions.db.fields +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("conferences", "0012_historicalconferenceproposalreviewer"), + ("proposals", "0014_auto_20150729_0131"), + ] + + operations = [ + migrations.CreateModel( + name="HistoricalProposal", + fields=[ + ( + "id", + models.IntegerField( + verbose_name="ID", db_index=True, auto_created=True, blank=True + ), + ), + ( + "created_at", + models.DateTimeField( + verbose_name="Created At", editable=False, blank=True + ), + ), + ( + "modified_at", + models.DateTimeField( + verbose_name="Last Modified At", editable=False, blank=True + ), + ), + ("title", models.CharField(max_length=255)), + ( + "slug", + django_extensions.db.fields.AutoSlugField( + populate_from=("title",), + max_length=255, + editable=False, + blank=True, + ), + ), + ("description", models.TextField(default="")), + ( + "target_audience", + models.PositiveSmallIntegerField( + default=1, + verbose_name="Target Audience", + choices=[(3, "Advanced"), (1, "Beginner"), (2, "Intermediate")], + ), + ), + ("prerequisites", models.TextField(default="", blank=True)), + ("content_urls", models.TextField(default="", blank=True)), + ("speaker_info", models.TextField(default="", blank=True)), + ("speaker_links", models.TextField(default="", blank=True)), + ( + "status", + models.PositiveSmallIntegerField( + default=1, + choices=[(3, "Cancelled"), (1, "Draft"), (2, "Public")], + ), + ), + ( + "review_status", + models.PositiveSmallIntegerField( + default=1, + verbose_name="Review Status", + choices=[ + (4, "On hold"), + (3, "Rejected"), + (2, "Selected"), + (5, "Wait-listed"), + (1, "Yet to be reviewed"), + ], + ), + ), + ( + "deleted", + models.BooleanField(default=False, verbose_name="Is Deleted?"), + ), + ("history_id", models.AutoField(serialize=False, primary_key=True)), + ("history_date", models.DateTimeField()), + ( + "history_type", + models.CharField( + max_length=1, + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + ), + ), + ( + "author", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "conference", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="conferences.Conference", + null=True, + ), + ), + ( + "history_user", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "proposal_section", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="proposals.ProposalSection", + null=True, + ), + ), + ( + "proposal_type", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="proposals.ProposalType", + null=True, + ), + ), + ], + options={ + "ordering": ("-history_date", "-history_id"), + "get_latest_by": "history_date", + "verbose_name": "historical proposal", + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name="HistoricalProposalSectionReviewerVote", + fields=[ + ( + "id", + models.IntegerField( + verbose_name="ID", db_index=True, auto_created=True, blank=True + ), + ), + ( + "created_at", + models.DateTimeField( + verbose_name="Created At", editable=False, blank=True + ), + ), + ( + "modified_at", + models.DateTimeField( + verbose_name="Last Modified At", editable=False, blank=True + ), + ), + ( + "role", + models.PositiveSmallIntegerField( + default=2, choices=[(1, "Public"), (2, "Reviewer")] + ), + ), + ("history_id", models.AutoField(serialize=False, primary_key=True)), + ("history_date", models.DateTimeField()), + ( + "history_type", + models.CharField( + max_length=1, + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + ), + ), + ( + "history_user", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "proposal", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="proposals.Proposal", + null=True, + ), + ), + ( + "vote_value", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="proposals.ProposalSectionReviewerVoteValue", + null=True, + ), + ), + ( + "voter", + models.ForeignKey( + related_name="+", + on_delete=models.deletion.DO_NOTHING, + db_constraint=False, + blank=True, + to="proposals.ProposalSectionReviewer", + null=True, + ), + ), + ], + options={ + "ordering": ("-history_date", "-history_id"), + "get_latest_by": "history_date", + "verbose_name": "historical ProposalSectionReviewerVote", + }, + bases=(models.Model,), + ), + migrations.AlterModelOptions( + name="proposalsectionreviewervote", + options={"verbose_name": "ProposalSectionReviewerVote"}, + ), + ] diff --git a/junction/proposals/migrations/0016_auto_20160221_0240.py b/junction/proposals/migrations/0016_auto_20160221_0240.py new file mode 100644 index 00000000..436eccd9 --- /dev/null +++ b/junction/proposals/migrations/0016_auto_20160221_0240.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0015_auto_20150806_2312"), + ] + + operations = [ + migrations.AlterField( + model_name="proposalsection", + name="description", + field=models.TextField(blank=True, default=""), + preserve_default=True, + ), + migrations.AlterField( + model_name="proposaltype", + name="description", + field=models.TextField(blank=True, default=""), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0017_proposalcomment_type.py b/junction/proposals/migrations/0017_proposalcomment_type.py new file mode 100644 index 00000000..082d0de5 --- /dev/null +++ b/junction/proposals/migrations/0017_proposalcomment_type.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0016_auto_20160221_0240"), + ] + + operations = [ + migrations.AddField( + model_name="proposalcomment", + name="type", + field=models.PositiveSmallIntegerField( + default=0, choices=[(0, "Unclassified"), (1, "Second phase voting")] + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0018_auto_20160806_1727.py b/junction/proposals/migrations/0018_auto_20160806_1727.py new file mode 100644 index 00000000..64b74058 --- /dev/null +++ b/junction/proposals/migrations/0018_auto_20160806_1727.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0017_proposalcomment_type"), + ] + + operations = [ + migrations.RemoveField(model_name="proposalcomment", name="type",), + migrations.AddField( + model_name="historicalproposalsectionreviewervote", + name="phase", + field=models.PositiveSmallIntegerField( + default=0, choices=[(0, "Initial voting"), (1, "Second phase voting")] + ), + preserve_default=True, + ), + migrations.AddField( + model_name="proposalsectionreviewervote", + name="phase", + field=models.PositiveSmallIntegerField( + default=0, choices=[(0, "Initial voting"), (1, "Second phase voting")] + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0019_proposalcomment_comment_type.py b/junction/proposals/migrations/0019_proposalcomment_comment_type.py new file mode 100644 index 00000000..7d0e966c --- /dev/null +++ b/junction/proposals/migrations/0019_proposalcomment_comment_type.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0018_auto_20160806_1727"), + ] + + operations = [ + migrations.AddField( + model_name="proposalcomment", + name="comment_type", + field=models.PositiveSmallIntegerField( + default=0, + choices=[(0, "All general comments"), (1, "Second phase voting")], + ), + preserve_default=True, + ), + ] diff --git a/junction/proposals/migrations/0020_auto_20160806_2023.py b/junction/proposals/migrations/0020_auto_20160806_2023.py new file mode 100644 index 00000000..2af1c830 --- /dev/null +++ b/junction/proposals/migrations/0020_auto_20160806_2023.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0019_proposalcomment_comment_type"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="proposalsectionreviewervote", unique_together=set([]), + ), + ] diff --git a/junction/proposals/migrations/0021_auto_20160905_0044.py b/junction/proposals/migrations/0021_auto_20160905_0044.py new file mode 100644 index 00000000..57b97898 --- /dev/null +++ b/junction/proposals/migrations/0021_auto_20160905_0044.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0020_auto_20160806_2023"), + ] + + operations = [ + migrations.AlterModelOptions( + name="proposalcomment", options={"ordering": ("created_at",)}, + ), + ] diff --git a/junction/proposals/migrations/0022_auto_20170610_1600.py b/junction/proposals/migrations/0022_auto_20170610_1600.py new file mode 100644 index 00000000..b9d1c053 --- /dev/null +++ b/junction/proposals/migrations/0022_auto_20170610_1600.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-06-10 10:30 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("proposals", "0021_auto_20160905_0044"), + ] + + operations = [ + migrations.CreateModel( + name="SpamComment", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ], + ), + migrations.AddField( + model_name="proposalcomment", + name="is_spam", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="spamcomment", + name="comment", + field=models.ForeignKey( + on_delete=models.deletion.CASCADE, to="proposals.ProposalComment", + ), + ), + migrations.AddField( + model_name="spamcomment", + name="marked_by", + field=models.ForeignKey( + on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + migrations.AlterUniqueTogether( + name="spamcomment", unique_together=set([("comment", "marked_by")]), + ), + ] diff --git a/junction/proposals/migrations/0023_auto_20170610_1633.py b/junction/proposals/migrations/0023_auto_20170610_1633.py new file mode 100644 index 00000000..398c8708 --- /dev/null +++ b/junction/proposals/migrations/0023_auto_20170610_1633.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-06-10 11:03 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0022_auto_20170610_1600"), + ] + + operations = [ + migrations.AlterIndexTogether( + name="spamcomment", index_together=set([("comment", "marked_by")]), + ), + ] diff --git a/junction/proposals/migrations/0024_auto_20170610_1857.py b/junction/proposals/migrations/0024_auto_20170610_1857.py new file mode 100644 index 00000000..a8e5cdd2 --- /dev/null +++ b/junction/proposals/migrations/0024_auto_20170610_1857.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-06-10 13:27 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("proposals", "0023_auto_20170610_1633"), + ] + + operations = [ + migrations.AlterUniqueTogether(name="spamcomment", unique_together=set([]),), + migrations.AlterIndexTogether(name="spamcomment", index_together=set([]),), + migrations.RemoveField(model_name="spamcomment", name="comment",), + migrations.RemoveField(model_name="spamcomment", name="marked_by",), + migrations.AddField( + model_name="proposalcomment", + name="marked_as_spam_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.deletion.CASCADE, + related_name="marked_as_spam_by", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterIndexTogether( + name="proposalcomment", + index_together=set([("is_spam", "marked_as_spam_by")]), + ), + migrations.DeleteModel(name="SpamComment",), + ] diff --git a/junction/proposals/migrations/0025_auto_20200321_0049.py b/junction/proposals/migrations/0025_auto_20200321_0049.py new file mode 100644 index 00000000..9c15d5ae --- /dev/null +++ b/junction/proposals/migrations/0025_auto_20200321_0049.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-03-20 19:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0024_auto_20170610_1857"), + ] + + operations = [ + migrations.AlterIndexTogether( + name="proposalcomment", + index_together=set( + [("is_spam", "marked_as_spam_by"), ("commenter", "is_spam")] + ), + ), + ] diff --git a/junction/proposals/migrations/0026_auto_20200323_2010.py b/junction/proposals/migrations/0026_auto_20200323_2010.py new file mode 100644 index 00000000..12d98cf2 --- /dev/null +++ b/junction/proposals/migrations/0026_auto_20200323_2010.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-03-23 14:40 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0025_auto_20200321_0049"), + ] + + operations = [ + migrations.AlterField( + model_name="proposalcomment", + name="marked_as_spam_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="marked_as_spam_by", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="proposalsection", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_proposalsection_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="proposalsection", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_proposalsection_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="proposalsectionreviewer", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_proposalsectionreviewer_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="proposalsectionreviewer", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_proposalsectionreviewer_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="proposalsectionreviewervotevalue", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_proposalsectionreviewervotevalue_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="proposalsectionreviewervotevalue", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_proposalsectionreviewervotevalue_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="proposaltype", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_proposaltype_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="proposaltype", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_proposaltype_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + ] diff --git a/junction/proposals/migrations/0027_auto_20200502_0540.py b/junction/proposals/migrations/0027_auto_20200502_0540.py new file mode 100644 index 00000000..2f2b36aa --- /dev/null +++ b/junction/proposals/migrations/0027_auto_20200502_0540.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-05-02 00:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0026_auto_20200323_2010"), + ] + + operations = [ + migrations.AddField( + model_name="historicalproposal", + name="video_url", + field=models.URLField( + blank=True, + default="", + help_text="A short 1-2 mins video link about your talk", + ), + ), + migrations.AddField( + model_name="proposal", + name="video_url", + field=models.URLField( + blank=True, + default="", + help_text="A short 1-2 mins video link about your talk", + ), + ), + ] diff --git a/junction/proposals/migrations/0028_auto_20200617_2337.py b/junction/proposals/migrations/0028_auto_20200617_2337.py new file mode 100644 index 00000000..2fea0f83 --- /dev/null +++ b/junction/proposals/migrations/0028_auto_20200617_2337.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-06-17 18:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0027_auto_20200502_0540"), + ] + + operations = [ + migrations.AlterField( + model_name="historicalproposal", + name="video_url", + field=models.URLField( + blank=True, + default="", + help_text="Short 1-2 min video describing your talk", + ), + ), + migrations.AlterField( + model_name="proposal", + name="video_url", + field=models.URLField( + blank=True, + default="", + help_text="Short 1-2 min video describing your talk", + ), + ), + ] diff --git a/junction/proposals/migrations/0029_auto_20200707_0844.py b/junction/proposals/migrations/0029_auto_20200707_0844.py new file mode 100644 index 00000000..fda66ed2 --- /dev/null +++ b/junction/proposals/migrations/0029_auto_20200707_0844.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-07-07 03:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0028_auto_20200617_2337"), + ] + + operations = [ + migrations.AddField( + model_name="historicalproposal", + name="is_first_time_speaker", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="proposal", + name="is_first_time_speaker", + field=models.BooleanField(default=False), + ), + ] diff --git a/junction/proposals/migrations/0030_auto_20200805_2137.py b/junction/proposals/migrations/0030_auto_20200805_2137.py new file mode 100644 index 00000000..46d442d3 --- /dev/null +++ b/junction/proposals/migrations/0030_auto_20200805_2137.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-08-05 16:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0029_auto_20200707_0844"), + ] + + operations = [ + migrations.AddField( + model_name="historicalproposal", + name="private_content_urls", + field=models.BooleanField( + default=False, + help_text="Check it if you want to make your content URLs private", + ), + ), + migrations.AddField( + model_name="proposal", + name="private_content_urls", + field=models.BooleanField( + default=False, + help_text="Check it if you want to make your content URLs private", + ), + ), + ] diff --git a/junction/proposals/migrations/0031_auto_20230110_1818.py b/junction/proposals/migrations/0031_auto_20230110_1818.py new file mode 100644 index 00000000..be9a81a7 --- /dev/null +++ b/junction/proposals/migrations/0031_auto_20230110_1818.py @@ -0,0 +1,74 @@ +# Generated by Django 3.2 on 2023-01-10 12:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('proposals', '0030_auto_20200805_2137'), + ] + + operations = [ + migrations.AlterModelOptions( + name='historicalproposal', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical proposal', 'verbose_name_plural': 'historical proposals'}, + ), + migrations.AlterModelOptions( + name='historicalproposalsectionreviewervote', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical ProposalSectionReviewerVote', 'verbose_name_plural': 'historical ProposalSectionReviewerVotes'}, + ), + migrations.AddField( + model_name='historicalproposal', + name='history_change_reason', + field=models.CharField(max_length=100, null=True), + ), + migrations.AddField( + model_name='historicalproposalsectionreviewervote', + name='history_change_reason', + field=models.CharField(max_length=100, null=True), + ), + migrations.AlterField( + model_name='historicalproposal', + name='author', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Primary Speaker'), + ), + migrations.AlterField( + model_name='historicalproposal', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='historicalproposal', + name='is_first_time_speaker', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AlterField( + model_name='historicalproposal', + name='proposal_section', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='proposals.proposalsection', verbose_name='Proposal Section'), + ), + migrations.AlterField( + model_name='historicalproposal', + name='proposal_type', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='proposals.proposaltype', verbose_name='Proposal Type'), + ), + migrations.AlterField( + model_name='historicalproposalsectionreviewervote', + name='history_date', + field=models.DateTimeField(db_index=True), + ), + migrations.AlterField( + model_name='proposal', + name='is_first_time_speaker', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AlterField( + model_name='proposalcomment', + name='is_spam', + field=models.BooleanField(blank=True, default=False), + ), + ] diff --git a/junction/proposals/models.py b/junction/proposals/models.py index d39ed0b9..d88da0ea 100644 --- a/junction/proposals/models.py +++ b/junction/proposals/models.py @@ -1,32 +1,42 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -# Third Party Stuff +from datetime import datetime + from django.contrib.auth.models import User -from django.core.urlresolvers import reverse +from django.urls import reverse from django.db import models -from django.utils.encoding import python_2_unicode_compatible +from django.template.defaultfilters import slugify +from six import python_2_unicode_compatible from django_extensions.db.fields import AutoSlugField +from hashids import Hashids +from rest_framework.reverse import reverse as rf_reverse +from simple_history.models import HistoricalRecords -# Junction Stuff from junction.base.constants import ( - PROPOSAL_REVIEW_STATUS_LIST, - PROPOSAL_STATUS_LIST, - PROPOSAL_TARGET_AUDIENCES, - PROPOSAL_USER_VOTE_ROLES + ProposalCommentType, + ProposalReviewStatus, + ProposalReviewVote, + ProposalStatus, + ProposalTargetAudience, + ProposalUserVoteRole, + PSRVotePhase, ) from junction.base.models import AuditModel, TimeAuditModel -from junction.conferences.models import Conference +from junction.conferences.models import Conference, ConferenceProposalReviewer @python_2_unicode_compatible class ProposalSection(AuditModel): """ List of Proposal Sections""" + name = models.CharField(max_length=255, verbose_name="Proposal Section Name") - description = models.TextField(default="") + description = models.TextField(default="", blank=True) active = models.BooleanField(default=True, verbose_name="Is Active?") - conferences = models.ManyToManyField(to=Conference, related_name='proposal_sections') + conferences = models.ManyToManyField( + to=Conference, related_name="proposal_sections" + ) def __str__(self): return self.name @@ -36,9 +46,15 @@ def __str__(self): class ProposalSectionReviewer(AuditModel): """ List of Proposal Section Reviewers""" - conference_reviewer = models.ForeignKey('conferences.ConferenceProposalReviewer', - verbose_name="Conference Proposal Reviewers") - proposal_section = models.ForeignKey(ProposalSection, verbose_name="Proposal Section") + + conference_reviewer = models.ForeignKey( + "conferences.ConferenceProposalReviewer", + verbose_name="Conference Proposal Reviewers", + on_delete=models.CASCADE, + ) + proposal_section = models.ForeignKey( + ProposalSection, verbose_name="Proposal Section", on_delete=models.CASCADE + ) active = models.BooleanField(default=True, verbose_name="Is Active?") def __str__(self): @@ -49,10 +65,13 @@ def __str__(self): class ProposalType(AuditModel): """ List of Proposal Types """ + name = models.CharField(max_length=255, verbose_name="Proposal Type Name") - description = models.TextField(default="") + description = models.TextField(default="", blank=True) active = models.BooleanField(default=True, verbose_name="Is Active?") - conferences = models.ManyToManyField(to=Conference, related_name='proposal_types') + conferences = models.ManyToManyField(to=Conference, related_name="proposal_types") + start_date = models.DateField(default=datetime.now, verbose_name="Start Date") + end_date = models.DateField(default=datetime.now, verbose_name="End Date") def __str__(self): return self.name @@ -62,61 +81,189 @@ def __str__(self): class Proposal(TimeAuditModel): """ The proposals master """ - conference = models.ForeignKey(Conference) - proposal_section = models.ForeignKey(ProposalSection, verbose_name="Proposal Section") - proposal_type = models.ForeignKey(ProposalType, verbose_name="Proposal Type") - author = models.ForeignKey(User, verbose_name="Primary Speaker") + + conference = models.ForeignKey(Conference, on_delete=models.CASCADE) + proposal_section = models.ForeignKey( + ProposalSection, verbose_name="Proposal Section", on_delete=models.CASCADE + ) + proposal_type = models.ForeignKey( + ProposalType, verbose_name="Proposal Type", on_delete=models.CASCADE + ) + author = models.ForeignKey( + User, verbose_name="Primary Speaker", on_delete=models.CASCADE + ) title = models.CharField(max_length=255) - slug = AutoSlugField(max_length=255, populate_from=('title',)) + slug = AutoSlugField(max_length=255, populate_from=("title",)) description = models.TextField(default="") target_audience = models.PositiveSmallIntegerField( - choices=PROPOSAL_TARGET_AUDIENCES, default=1, verbose_name="Target Audience") - prerequisites = models.TextField(default="") - content_urls = models.TextField(default="") - speaker_info = models.TextField(default="") - speaker_links = models.TextField(default="") + choices=ProposalTargetAudience.CHOICES, + default=ProposalTargetAudience.BEGINNER, + verbose_name="Target Audience", + ) + video_url = models.URLField( + blank=True, default="", help_text="Short 1-2 min video describing your talk", + ) + prerequisites = models.TextField(blank=True, default="") + content_urls = models.TextField(blank=True, default="") + private_content_urls = models.BooleanField( + default=False, + help_text="Check it if you want to make your content URLs private", + ) + speaker_info = models.TextField(blank=True, default="") + speaker_links = models.TextField(blank=True, default="") + is_first_time_speaker = models.BooleanField(blank=True, default=False) status = models.PositiveSmallIntegerField( - choices=PROPOSAL_STATUS_LIST, default=1) + choices=ProposalStatus.CHOICES, default=ProposalStatus.DRAFT + ) review_status = models.PositiveSmallIntegerField( - choices=PROPOSAL_REVIEW_STATUS_LIST, default=1, verbose_name="Review Status") + choices=ProposalReviewStatus.CHOICES, + default=ProposalReviewStatus.YET_TO_BE_REVIEWED, + verbose_name="Review Status", + ) deleted = models.BooleanField(default=False, verbose_name="Is Deleted?") + history = HistoricalRecords() def __str__(self): - return self.title + return "{}, {}".format(self.title, self.proposal_type) + + def is_public(self): + # TODO: Fix with proper enum + return self.status == 2 + + def get_slug(self): + return slugify(self.title) + + def get_hashid(self): + hashids = Hashids(min_length=5) + return hashids.encode(self.id) def get_absolute_url(self): - return reverse('proposal-detail', args=[self.conference.slug, self.slug]) + return reverse( + "proposal-detail", + args=[self.conference.slug, self.get_slug(), self.get_hashid()], + ) def get_update_url(self): - return reverse('proposal-update', args=[self.conference.slug, self.slug]) + return reverse("proposal-update", args=[self.conference.slug, self.slug]) def get_review_url(self): - return reverse('proposal-review', args=[self.conference.slug, self.slug]) + return reverse("proposal-review", args=[self.conference.slug, self.slug]) + + def get_vote_url(self): + return reverse("proposal-reviewer-vote", args=[self.conference.slug, self.slug]) + + def get_secondary_vote_url(self): + return reverse( + "proposal-reviewer-secondary-vote", args=[self.conference.slug, self.slug] + ) def get_delete_url(self): - return reverse('proposal-delete', args=[self.conference.slug, self.slug]) + return reverse("proposal-delete", args=[self.conference.slug, self.slug]) def get_up_vote_url(self): - return reverse('proposal-vote-up', args=[self.conference.slug, self.slug]) + return reverse("proposal-vote-up", args=[self.conference.slug, self.slug]) def get_down_vote_url(self): - return reverse('proposal-vote-down', args=[self.conference.slug, self.slug]) + return reverse("proposal-vote-down", args=[self.conference.slug, self.slug]) + + def get_remove_vote_url(self): + return reverse("proposal-vote-remove", args=[self.conference.slug, self.slug]) def get_comments_count(self): - """ Show only the public comment count """ - return ProposalComment.objects.filter(proposal=self, deleted=False, private=False).count() + """ Show only public comments count """ + return ProposalComment.objects.filter( + proposal=self, deleted=False, private=False, vote=False, reviewer=False + ).count() + + def get_reviews_comments_count(self): + """ Show only private comments count """ + return ProposalComment.objects.filter( + proposal=self, deleted=False, private=True, vote=False + ).count() + + def get_reviewer_comments_count(self, reviewer): + """ Number of private comments by a reviewer """ + return ProposalComment.objects.filter( + proposal=self, deleted=False, private=True, commenter=reviewer, vote=False + ).count() def get_votes_count(self): """ Show only the public comment count """ - up_vote_count = ProposalVote.objects.filter(proposal=self, up_vote=True).count() - down_vote_count = ProposalVote.objects.filter(proposal=self, up_vote=False).count() + votes = ( + ProposalVote.objects.filter(proposal=self) + .values("up_vote") + .annotate(counts=models.Count("up_vote")) + ) + votes = {item["up_vote"]: item["counts"] for item in votes} + up_vote_count = votes.get(True, 0) + down_vote_count = votes.get(False, 0) return up_vote_count - down_vote_count - def status_text(self): - """ Text representation of status values """ - for value, text in PROPOSAL_STATUS_LIST: - if self.status == value: - return text + def get_reviewer_votes_count(self): + """ Show sum of reviewer vote value. """ + return ProposalSectionReviewerVote.objects.filter(proposal=self).count() + + def get_reviewer_votes_count_by_value(self, vote_value): + """ Show sum of reviewer votes for given vote value. """ + return ProposalSectionReviewerVote.objects.filter( + proposal=self, vote_value__vote_value=vote_value + ).count() + + def get_reviewer_votes_sum(self): + votes = ProposalSectionReviewerVote.objects.filter(proposal=self,) + sum_of_votes = sum((v.vote_value.vote_value for v in votes)) + return sum_of_votes + + def get_reviewer_vote_value(self, reviewer): + try: + vote = ProposalSectionReviewerVote.objects.get( + proposal=self, voter__conference_reviewer__reviewer=reviewer, + ) + return vote.vote_value.vote_value + except ProposalSectionReviewerVote.DoesNotExist: + return 0 + + def get_reviewers_count(self): + """ Count of reviewers for given proposal section """ + return ProposalSectionReviewer.objects.filter( + proposal_section=self.proposal_section + ).count() + + def has_negative_votes(self): + """ Show sum of reviewer votes for given vote value. """ + return ( + ProposalSectionReviewerVote.objects.filter( + proposal=self, vote_value__vote_value=ProposalReviewVote.NOT_ALLOWED, + ).count() + > 0 + ) + + def to_response(self, request): + """method will return dict which can be passed to response + """ + author = "{} {}".format(self.author.first_name, self.author.last_name) + data = { + "id": self.id, + "author": author, + "title": self.title, + "description": self.description, + "target_audience": dict(ProposalTargetAudience.CHOICES)[ + self.target_audience + ], + "status": dict(ProposalStatus.CHOICES)[self.status], + "review_status": dict(ProposalReviewStatus.CHOICES)[self.review_status], + "proposal_type": self.proposal_type.name, + "proposal_section": self.proposal_section.name, + "votes_count": self.get_votes_count(), + "speaker_info": self.speaker_info, + "speaker_links": self.speaker_links, + "content_urls": self.content_urls, + "private_content_urls": self.private_content_urls, + "conference": rf_reverse( + "conference-detail", kwargs={"pk": self.conference_id}, request=request + ), + } + return data class Meta: unique_together = ("conference", "slug") @@ -126,10 +273,12 @@ class Meta: class ProposalVote(TimeAuditModel): """ User vote for a specific proposal """ - proposal = models.ForeignKey(Proposal) - voter = models.ForeignKey(User) + + proposal = models.ForeignKey(Proposal, on_delete=models.CASCADE) + voter = models.ForeignKey(User, on_delete=models.CASCADE) role = models.PositiveSmallIntegerField( - choices=PROPOSAL_USER_VOTE_ROLES, default=1) + choices=ProposalUserVoteRole.CHOICES, default=ProposalUserVoteRole.PUBLIC + ) up_vote = models.BooleanField(default=True) def __str__(self): @@ -139,39 +288,152 @@ class Meta: unique_together = ("proposal", "voter") +class ProposalCommentQuerySet(models.QuerySet): + def get_public_comments(self): + return self.filter(private=False, reviewer=False, vote=False) + + def get_reviewers_comments(self): + return self.filter(private=True, vote=False) + + def get_reviewers_only_comments(self): + return self.filter(reviewer=True, vote=False) + + +@python_2_unicode_compatible +class ProposalSectionReviewerVoteValue(AuditModel): + """ Proposal reviewer vote choices. """ + + vote_value = models.SmallIntegerField() + description = models.CharField(max_length=255) + + def __str__(self): + return "{} ({})".format(self.description, self.vote_value) + + class Meta: + ordering = ("-vote_value",) + + +@python_2_unicode_compatible +class ProposalSectionReviewerVote(TimeAuditModel): + + """ Reviewer vote for a specific proposal """ + + proposal = models.ForeignKey(Proposal, on_delete=models.CASCADE) + voter = models.ForeignKey(ProposalSectionReviewer, on_delete=models.CASCADE) + role = models.PositiveSmallIntegerField( + choices=ProposalUserVoteRole.CHOICES, default=ProposalUserVoteRole.REVIEWER + ) + vote_value = models.ForeignKey( + ProposalSectionReviewerVoteValue, on_delete=models.CASCADE + ) + phase = models.PositiveSmallIntegerField( + choices=PSRVotePhase.CHOICES, default=PSRVotePhase.PRIMARY + ) + + history = HistoricalRecords() + + def __str__(self): + return "[{}] {}".format(self.vote_value, self.proposal) + + class Meta: + verbose_name = "ProposalSectionReviewerVote" + + +# FIXME: Need to move private, reviewer, vote to type @python_2_unicode_compatible class ProposalComment(TimeAuditModel): """ User comments for a specific proposal """ - proposal = models.ForeignKey(Proposal) - commenter = models.ForeignKey(User) + + proposal = models.ForeignKey(Proposal, on_delete=models.CASCADE) + commenter = models.ForeignKey(User, on_delete=models.CASCADE) private = models.BooleanField(default=False, verbose_name="Is Private?") + reviewer = models.BooleanField(default=False, verbose_name="Is Reviewer?") + vote = models.BooleanField(default=False, verbose_name="What is the reason?") comment = models.TextField() deleted = models.BooleanField(default=False, verbose_name="Is Deleted?") + comment_type = models.PositiveSmallIntegerField( + choices=ProposalCommentType.CHOICES, default=ProposalCommentType.GENERAL + ) + objects = ProposalCommentQuerySet.as_manager() + is_spam = models.BooleanField(default=False, blank=True) + marked_as_spam_by = models.ForeignKey( + User, + related_name="marked_as_spam_by", + default=None, + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + + class Meta: + ordering = ("created_at",) + index_together = [["is_spam", "marked_as_spam_by"], ["commenter", "is_spam"]] def __str__(self): - return "[{} by {}] {}".format(self.comment, - self.commenter.get_full_name(), - self.proposal) + return "[{} by {}] {}".format( + self.comment, self.commenter.get_full_name(), self.proposal + ) def get_up_vote_url(self): - return reverse('proposal-comment-up-vote', args=[self.proposal.conference.slug, self.proposal.slug, self.id]) + return reverse( + "proposal-comment-up-vote", + args=[self.proposal.conference.slug, self.proposal.slug, self.id], + ) def get_down_vote_url(self): - return reverse('proposal-comment-down-vote', args=[self.proposal.conference.slug, self.proposal.slug, self.id]) + return reverse( + "proposal-comment-down-vote", + args=[self.proposal.conference.slug, self.proposal.slug, self.id], + ) + + def get_mark_spam_url(self): + return reverse( + "comment_mark_spam", + args=[self.proposal.conference.slug, self.proposal.slug, self.id], + ) + + def get_unmark_spam_url(self): + return reverse( + "comment_unmark_spam", + args=[self.proposal.conference.slug, self.proposal.slug, self.id], + ) def get_votes_count(self): - up_vote_count = ProposalCommentVote.objects.filter(proposal_comment=self, up_vote=True).count() - down_vote_count = ProposalCommentVote.objects.filter(proposal_comment=self, up_vote=False).count() + up_vote_count = ProposalCommentVote.objects.filter( + proposal_comment=self, up_vote=True + ).count() + down_vote_count = ProposalCommentVote.objects.filter( + proposal_comment=self, up_vote=False + ).count() return up_vote_count - down_vote_count + def get_reviewer_nick(self): + reviewer = ConferenceProposalReviewer.objects.get( + conference_id=self.proposal.conference_id, reviewer_id=self.commenter_id + ) + return reviewer.nick + + def get_comment_type(self): + if self.deleted: + return "Deleted" + elif self.vote: + return "Vote" + elif self.reviewer: + return "Reviewer Only" + elif self.private: + return "Review" + else: + return "Public" + @python_2_unicode_compatible class ProposalCommentVote(TimeAuditModel): """ User vote for a specific proposal's comment """ - proposal_comment = models.ForeignKey(ProposalComment) - voter = models.ForeignKey(User) + + proposal_comment = models.ForeignKey(ProposalComment, on_delete=models.CASCADE) + voter = models.ForeignKey(User, on_delete=models.CASCADE) up_vote = models.BooleanField(default=True) def __str__(self): diff --git a/junction/proposals/permissions.py b/junction/proposals/permissions.py new file mode 100644 index 00000000..8d678809 --- /dev/null +++ b/junction/proposals/permissions.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +from django.core.exceptions import PermissionDenied + +from junction.base.constants import ConferenceStatus +from junction.conferences.models import ConferenceProposalReviewer + +from .models import ProposalSectionReviewer + + +def is_proposal_voting_allowed(proposal): + return proposal.conference.status != ConferenceStatus.SCHEDULE_PUBLISHED + + +def is_proposal_author(user, proposal): + return user.is_authenticated and proposal.author == user + + +def is_proposal_reviewer(user, conference): + authenticated = user.is_authenticated + is_reviewer = ConferenceProposalReviewer.objects.filter( + reviewer=user.id, conference=conference, active=True + ).exists() + return authenticated and is_reviewer + + +def is_proposal_section_reviewer(user, conference, proposal): + return ( + user.is_authenticated + and ProposalSectionReviewer.objects.filter( + conference_reviewer__reviewer=user, + conference_reviewer__conference=conference, + proposal_section=proposal.proposal_section, + active=True, + ).exists() + ) + + +def is_proposal_author_or_proposal_reviewer(user, conference, proposal): + reviewer = is_proposal_reviewer(user, conference) + author = is_proposal_author(user, proposal) + return reviewer or author + + +def is_proposal_author_or_proposal_section_reviewer(user, conference, proposal): + return is_proposal_author(user, proposal) or is_proposal_section_reviewer( + user, conference, proposal + ) + + +def is_proposal_author_or_permisson_denied(user, proposal): + if is_proposal_author(user, proposal): + return True + raise PermissionDenied + + +def is_conference_moderator(user, conference): + if user.is_superuser: + return True + + users = [mod.moderator for mod in conference.moderators.all()] + return user in users diff --git a/junction/proposals/proposal_urls.py b/junction/proposals/proposal_urls.py deleted file mode 100644 index 1f0f127e..00000000 --- a/junction/proposals/proposal_urls.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -# Third Party Stuff -from django.conf.urls import patterns, url - -from . import views - -urlpatterns = patterns( - '', - - url(r'^$', views.list_proposals, name='proposals-list'), - url(r'^create/$', views.create_proposal, name='proposal-create'), - url(r'^(?P[\w-]+)/update/$', - views.update_proposal, name='proposal-update'), - url(r'^(?P[\w-]+)/$', views.detail_proposal, name='proposal-detail'), - url(r'^(?P[\w-]+)/delete/$', - views.delete_proposal, name='proposal-delete'), - url(r'^(?P[\w-]+)/review/$', - views.review_proposal, name='proposal-review'), - - # Voting - url(r'^(?P[\w-]+)/up-vote/$', - views.proposal_vote_up, name='proposal-vote-up'), - url(r'^(?P[\w-]+)/down-vote/$', - views.proposal_vote_down, name='proposal-vote-down'), -) diff --git a/junction/proposals/serializers.py b/junction/proposals/serializers.py new file mode 100644 index 00000000..e13b0a85 --- /dev/null +++ b/junction/proposals/serializers.py @@ -0,0 +1,47 @@ +from rest_framework import serializers + +from .models import Proposal, ProposalSection, ProposalType + + +class ProposalSerializer(serializers.HyperlinkedModelSerializer): + section = serializers.SerializerMethodField() + type = serializers.SerializerMethodField() + author = serializers.SerializerMethodField() + + def get_section(self, proposal): + return proposal.proposal_section.name + + def get_type(self, proposal): + return proposal.proposal_type.name + + def get_author(self, proposal): + author = proposal.author + return ( + "{} {}".format(author.first_name, author.last_name).strip() + or author.username + ) + + class Meta: + model = Proposal + fields = ( + "title", + "section", + "type", + "author", + "slug", + "description", + "target_audience", + "prerequisites", + "content_urls", + "speaker_info", + "speaker_links", + ) + + +class ProposalFilterSerializer(serializers.Serializer): + proposal_section = serializers.PrimaryKeyRelatedField( + queryset=ProposalSection.objects.all(), required=False + ) + proposal_type = serializers.PrimaryKeyRelatedField( + queryset=ProposalType.objects.all(), required=False + ) diff --git a/junction/proposals/services.py b/junction/proposals/services.py index e20dbbc9..13987c15 100644 --- a/junction/proposals/services.py +++ b/junction/proposals/services.py @@ -1,68 +1,184 @@ # -*- coding: utf-8 -*- + from __future__ import absolute_import, unicode_literals -# Standard Library +import collections import logging -# Third Party Stuff +from celery import shared_task from django.conf import settings +from django.contrib.auth.models import User +from markdown2 import markdown -# Junction Stuff +from junction.base.constants import ProposalStatus from junction.base.emailer import send_email +from junction.conferences.models import Conference -from .models import ProposalSection, ProposalSectionReviewer +from .models import Proposal, ProposalComment, ProposalSection, ProposalSectionReviewer logger = logging.getLogger(__name__) -def send_mail_for_new_comment(proposal_comment, host, login_url): - proposal = proposal_comment.proposal - send_to = comment_recipients(proposal_comment) - for to in send_to: - if to == proposal_comment.commenter: - continue - send_email(to=to, - template_dir='proposals/email/comment', - context={'to': to, - 'proposal': proposal, - 'comment': proposal_comment, - 'host': host, - 'login_url': login_url}) +def _get_proposal_section_reviewers(proposal): + proposal_reviewers = set( + ProposalSectionReviewer.objects.filter( + proposal_section=proposal.proposal_section + ) + ) + recipients = { + proposal_reviewer.conference_reviewer.reviewer + for proposal_reviewer in proposal_reviewers + } + return recipients + + +def _arrange_proposals_by_section(proposal_qs): + res = collections.defaultdict(list) + for proposal in proposal_qs: + res[proposal.proposal_section.name].append(proposal) + return res + + +def group_proposals_by_reveiew_state(conf, state="reviewed"): + reviewed_qs = ( + conf.proposal_set.filter(status=ProposalStatus.PUBLIC) + .select_related("proposal_type", "proposal_section", "proposalsection") + .filter(proposalcomment__private=True, proposalcomment__deleted=False) + ) + if state == "reviewed": + proposal_qs = reviewed_qs.distinct() + return _arrange_proposals_by_section(proposal_qs) + else: + ids = reviewed_qs.values_list("id").distinct() + qs = ( + conf.proposal_set.filter(status=ProposalStatus.PUBLIC) + .select_related("proposal_type", "proposal_section", "proposalsection") + .exclude(pk__in=ids) + ) + return _arrange_proposals_by_section(qs) + + +def markdown_to_html(md): + """ + Convert given markdown to html. + :param md: string + :return: string - converted html + """ + return markdown(md) def comment_recipients(proposal_comment): proposal = proposal_comment.proposal - if proposal_comment.private: - proposal_reviewers = set(ProposalSectionReviewer.objects.filter( - proposal_section=proposal.proposal_section)) - recipients = {proposal_reviewer.conference_reviewer.reviewer - for proposal_reviewer in proposal_reviewers} + if proposal_comment.reviewer: + recipients = _get_proposal_section_reviewers(proposal=proposal) + elif proposal_comment.private: + recipients = _get_proposal_section_reviewers(proposal=proposal) + recipients.add(proposal.author) else: recipients = { comment.commenter - for comment in proposal.proposalcomment_set - .all().select_related('commenter')} - recipients.add(proposal.author) + for comment in proposal.proposalcomment_set.all().select_related( + "commenter" + ) + } + recipients.add(proposal.author) + ADMINS = getattr(settings, "SPAM_MODERATION_ADMINS", []) + if ADMINS: + for admin in ADMINS: + recipients.add(User.objects.get(email=admin)) + return recipients -def send_mail_for_new_proposal(proposal, host): - proposal_section = ProposalSection.objects.get( - pk=proposal.proposal_section_id) - send_to = [p.conference_reviewer.reviewer for p in - ProposalSectionReviewer.objects.filter( - proposal_section=proposal_section, - active=True)] +@shared_task(ignore_result=True) +def send_mail_for_new_comment(proposal_comment_id, host): + proposal_comment = ProposalComment.objects.get(id=proposal_comment_id) + proposal = proposal_comment.proposal + login_url = "{}?next={}".format(settings.LOGIN_URL, proposal.get_absolute_url()) + send_to = comment_recipients(proposal_comment) + commenter = proposal_comment.commenter + comment_type = proposal_comment.get_comment_type() + comment_html = markdown_to_html(proposal_comment.comment) + for to in send_to: + if to == proposal_comment.commenter: + continue + send_email( + to=to, + template_dir="proposals/email/comment", + context={ + "to": to, + "host": host, + "login_url": login_url, + "proposal": proposal, + "comment": proposal_comment, + "comment_html": comment_html, + "commenter": commenter, + "by_author": commenter == proposal.author, + "comment_type": comment_type, + }, + ) + + +@shared_task(ignore_result=True) +def send_mail_for_new_proposal(proposal_id, host): + proposal = Proposal.objects.get(id=proposal_id) + proposal_section = ProposalSection.objects.get(pk=proposal.proposal_section_id) + send_to = [ + p.conference_reviewer.reviewer + for p in ProposalSectionReviewer.objects.filter( + proposal_section=proposal_section, active=True + ) + ] proposal_url = proposal.get_absolute_url() login_url = settings.LOGIN_URL for to in send_to: if to == proposal.author: continue - send_email(to=to, - template_dir='proposals/email/proposal', - context={'to': to, - 'proposal': proposal, - 'proposal_section': proposal_section, - 'host': host, - 'proposal_url': proposal_url, - 'login_url': login_url}) + send_email( + to=to, + template_dir="proposals/email/proposal", + context={ + "to": to, + "proposal": proposal, + "proposal_section": proposal_section, + "host": host, + "proposal_url": proposal_url, + "login_url": login_url, + }, + ) + + +@shared_task(ignore_result=True) +def send_mail_for_proposal_content(conference_id, proposal_id, host): + """ + Send mail to proposal author to upload content for proposal. + """ + conference = Conference.objects.get(id=conference_id) + proposal = Proposal.objects.get(id=proposal_id) + login_url = "{}?next={}".format(settings.LOGIN_URL, proposal.get_absolute_url()) + author = proposal.author + author_name = author.get_full_name() or author.username + context = { + "host": host, + "login_url": login_url, + "conference": conference, + "proposal": proposal, + "author_name": author_name, + } + return send_email( + to=author, template_dir="proposals/email/upload_content", context=context + ) + + +def user_action_for_spam(user, threshold): + """When a comment is marked as spam, make appropriate status update to user model + """ + total_spam = ProposalComment.objects.filter(commenter=user, is_spam=True).count() + if total_spam >= threshold: + if user.is_active is True: + user.is_active = False + user.save() + else: + if user.is_active is False: + user.is_active = True + user.save() diff --git a/junction/proposals/templatetags/__init__.py b/junction/proposals/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/proposals/templatetags/proposal_filters.py b/junction/proposals/templatetags/proposal_filters.py new file mode 100644 index 00000000..3e3232a7 --- /dev/null +++ b/junction/proposals/templatetags/proposal_filters.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + +import collections +import re + +from django import template + +from junction.base.constants import PSRVotePhase +from junction.proposals.models import ( + ProposalComment, + ProposalSectionReviewer, + ProposalSectionReviewerVote, +) +from junction.proposals.permissions import is_proposal_section_reviewer + +register = template.Library() + + +@register.filter(name="reviewer_comments") +def reviewer_comments(proposal, user): + return proposal.get_reviewer_comments_count(user) > 0 + + +@register.filter(name="is_reviewer_voted") +def is_reviewer_voted(proposal, user, phase=None): + if not phase: + phase = PSRVotePhase.PRIMARY + + try: + voter = ProposalSectionReviewer.objects.get( + conference_reviewer__reviewer=user, + conference_reviewer__conference=proposal.conference, + proposal_section=proposal.proposal_section, + ) + vote = ProposalSectionReviewerVote.objects.get( + proposal=proposal, voter=voter, phase=phase + ) + except ( + ProposalSectionReviewer.DoesNotExist, + ProposalSectionReviewerVote.DoesNotExist, + ): + vote = None + + return vote + + +@register.filter(name="get_content_urls") +def get_content_urls(proposal): + if proposal.content_urls: + url_re = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" + urls = re.findall(url_re, proposal.content_urls) + return urls + else: + return [] + + +@register.filter(name="has_upvoted_comment") +def has_upvoted_comment(comment, user): + vote = comment.proposalcommentvote_set.filter(voter=user) + if vote: + return vote[0].up_vote + + +@register.filter(name="proposal_section_reviewer") +def proposal_section_reviewer(proposal, user): + """ + Checks if the logged in user is a section reviewer + """ + return is_proposal_section_reviewer(user, proposal.conference, proposal) + + +@register.filter(name="get_reviewers_vote_details") +def get_reviewers_vote_details(proposal, user): + """ + Get voter name & details for given proposals. + """ + v_detail = collections.namedtuple("v_detail", "voter_nick vote_value vote_comment") + reviewers = ProposalSectionReviewer.objects.filter( + proposal_section=proposal.proposal_section, + conference_reviewer__conference=proposal.conference, + ) + + vote_details = [] + for reviewer in reviewers: + voter_nick = reviewer.conference_reviewer.nick + rv_qs = ProposalSectionReviewerVote.objects.filter( + proposal=proposal, voter=reviewer + ) + if rv_qs: + vote_value = rv_qs[0].vote_value.description + else: + vote_value = None + + vc_qs = ProposalComment.objects.filter( + proposal=proposal, + commenter=reviewer.conference_reviewer.reviewer, + vote=True, + ) + if vc_qs: + vote_comment = vc_qs[0].comment + else: + vote_comment = None + + vote_details.append(v_detail(voter_nick, vote_value, vote_comment)) + + return vote_details diff --git a/junction/proposals/urls.py b/junction/proposals/urls.py new file mode 100644 index 00000000..80004598 --- /dev/null +++ b/junction/proposals/urls.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + +from django.conf.urls import include, url +from django.urls import re_path + +from . import comments_views, dashboard, views, votes_views + +comment_urls = [ + re_path( + r"^(?P[\w-]+)/create/$", + comments_views.create_proposal_comment, + name="proposal-comment-create", + ), + re_path( + r"^(?P[\w-]+)/comments/(?P\d+)/up-vote/$", + votes_views.proposal_comment_up_vote, + name="proposal-comment-up-vote", + ), + re_path( + r"^(?P[\w-]+)/comments/(?P\d+)/down-vote/$", + votes_views.proposal_comment_down_vote, + name="proposal-comment-down-vote", + ), + re_path( + r"^(?P[\w-]+)/comments/(?P\d+)/mark_spam/$", + comments_views.mark_comment_as_spam, + name="comment_mark_spam", + ), + re_path( + r"^(?P[\w-]+)/comments/(?P\d+)/unmark_spam/$", + comments_views.unmark_comment_as_spam, + name="comment_unmark_spam", + ), +] + +urlpatterns = [ + # proposal urls + re_path(r"^$", views.list_proposals, name="proposals-list"), + re_path(r"^create/$", views.create_proposal, name="proposal-create"), + re_path(r"^to_review/$", views.proposals_to_review, name="proposals-to-review"), + re_path( + r"^second_phase_voting/$", + dashboard.second_phase_voting, + name="second-phase-voting", + ), + re_path(r"^(?P[\w-]+)/$", views.detail_proposal, name="proposal-detail"), + re_path( + r"^(?P[\w-]+)~(?P.*)/$", + views.detail_proposal, + name="proposal-detail", + ), + re_path(r"^(?P[\w-]+)/delete/$", views.delete_proposal, name="proposal-delete"), + re_path(r"^(?P[\w-]+)/update/$", views.update_proposal, name="proposal-update"), + re_path( + r"^(?P[\w-]+)/upload-content/$", + views.proposal_upload_content, + name="proposal-upload-content", + ), + re_path( + r"^(?P[\w-]+)/change-proposal-review-state/$", + views.review_proposal, + name="proposal-review", + ), + # comment urls + re_path(r"^comment/", include(comment_urls)), + # Voting + re_path( + r"^(?P[\w-]+)/down-vote/$", + votes_views.proposal_vote_down, + name="proposal-vote-down", + ), + re_path( + r"^(?P[\w-]+)/up-vote/$", + votes_views.proposal_vote_up, + name="proposal-vote-up", + ), + re_path( + r"^(?P[\w-]+)/remove-vote/$", + votes_views.proposal_vote_remove, + name="proposal-vote-remove", + ), + re_path( + r"^(?P[\w-]+)/vote/$", + votes_views.proposal_reviewer_vote, + name="proposal-reviewer-vote", + ), + re_path( + r"^(?P[\w-]+)/second-vote/$", + votes_views.proposal_reviewer_secondary_vote, + name="proposal-reviewer-secondary-vote", + ), +] diff --git a/junction/proposals/utils.py b/junction/proposals/utils.py new file mode 100644 index 00000000..d57c92b8 --- /dev/null +++ b/junction/proposals/utils.py @@ -0,0 +1,193 @@ +import collections + +from django.core.exceptions import PermissionDenied + +from junction.base.constants import ( + ProposalCommentType, + ProposalReviewVote, + ProposalVotesFilter, + PSRVotePhase, +) +from junction.proposals import permissions +from junction.proposals.models import ( + ProposalComment, + ProposalSection, + ProposalSectionReviewer, + ProposalSectionReviewerVote, + ProposalSectionReviewerVoteValue, +) + + +def get_reviewer_vote_info(user, conference, proposal, vote_phase): + + if vote_phase == PSRVotePhase.PRIMARY: + comment_type = ProposalCommentType.GENERAL + elif vote_phase == PSRVotePhase.SECONDARY: + comment_type = ProposalCommentType.SECONDARY_VOTING + + if not ( + permissions.is_proposal_section_reviewer(user, conference, proposal) + and permissions.is_proposal_voting_allowed(proposal) + ): + raise PermissionDenied + + voter = ProposalSectionReviewer.objects.get( + conference_reviewer__reviewer=user, + conference_reviewer__conference=conference, + proposal_section=proposal.proposal_section, + ) + + try: + psr_vote = ProposalSectionReviewerVote.objects.get( + proposal=proposal, voter=voter, phase=vote_phase + ) + except ProposalSectionReviewerVote.DoesNotExist: + psr_vote = None + + try: + vote_comment = ProposalComment.objects.get( + proposal=proposal, + commenter=user, + vote=True, + deleted=False, + comment_type=comment_type, + ) + except Exception: + vote_comment = None + + return psr_vote, vote_comment + + +def update_reviewer_vote_info( + user, psr_vote, vote_value, comment, phase, proposal, conference +): + + if phase == PSRVotePhase.PRIMARY: + comment_type = ProposalCommentType.GENERAL + elif phase == PSRVotePhase.SECONDARY: + comment_type = ProposalCommentType.SECONDARY_VOTING + + voter = ProposalSectionReviewer.objects.filter( + conference_reviewer__reviewer=user, + conference_reviewer__conference=conference, + proposal_section=proposal.proposal_section, + )[0] + + vote_value = ProposalSectionReviewerVoteValue.objects.filter(vote_value=vote_value)[ + 0 + ] + + psr_vote, _ = ProposalSectionReviewerVote.objects.update_or_create( + proposal=proposal, voter=voter, phase=phase, defaults={"vote_value": vote_value} + ) + + p_comment, _ = ProposalComment.objects.update_or_create( + proposal=proposal, + commenter=user, + vote=True, + comment_type=comment_type, + defaults={"comment": comment}, + ) + + return psr_vote, p_comment + + +def _sort_proposals_for_dashboard(conference, proposals_qs, user, form): + cps = form.cleaned_data["proposal_section"] + cpt = form.cleaned_data["proposal_type"] + votes = form.cleaned_data["votes"] + review_status = form.cleaned_data["review_status"] + + proposal_sections = conference.proposal_sections.all() + s_items = collections.namedtuple("section_items", "section proposals") + proposals = [] + + if cps != "all": + proposal_sections = ProposalSection.objects.filter(pk=cps) + if cpt != "all": + proposals_qs = proposals_qs.filter(proposal_type__id__in=cpt) + if votes != "all": + votes = int(votes) + if review_status != "all": + proposals_qs = proposals_qs.filter(review_status=review_status) + + if votes == ProposalVotesFilter.NO_VOTES: + proposals_qs = [ + p for p in proposals_qs if p.get_reviewer_votes_count() == votes + ] + elif votes == ProposalVotesFilter.MIN_ONE_VOTE: + proposals_qs = [ + p for p in proposals_qs if p.get_reviewer_votes_count() >= votes + ] + elif votes == ProposalVotesFilter.SORT_BY_REVIEWER: + proposals_qs = sorted( + proposals_qs, + key=lambda x: x.get_reviewer_vote_value(reviewer=user), + reverse=True, + ) + elif votes == ProposalVotesFilter.SORT_BY_SUM: + proposals_qs = sorted( + proposals_qs, key=lambda x: x.get_reviewer_votes_sum(), reverse=True + ) + proposals = [s_items("", proposals_qs)] + + elif votes == ProposalVotesFilter.SORT_BY_SELECTION: + # Selection of proposal is based on conference guidelines. + # More info is available at http://tiny.cc/qzo5cy + + proposals_qs = [p for p in proposals_qs if not p.has_negative_votes()] + proposals_qs = sorted( + proposals_qs, key=lambda x: x.get_reviewer_votes_sum(), reverse=True + ) + + selected = [ + p + for p in proposals_qs + if p.get_reviewer_votes_count_by_value(ProposalReviewVote.MUST_HAVE) >= 2 + ] + proposals.append(s_items("Selected", selected)) + + batch1 = [ + p + for p in proposals_qs + if p.get_reviewer_votes_count_by_value(ProposalReviewVote.MUST_HAVE) == 1 + and p.get_reviewer_votes_count_by_value(ProposalReviewVote.GOOD) > 2 + ] + proposals.append(s_items("1 Must Have & 2+ Good Votes", batch1)) + + batch2 = [ + p + for p in proposals_qs + if p.get_reviewer_votes_count_by_value(ProposalReviewVote.MUST_HAVE) == 1 + and p.get_reviewer_votes_count_by_value(ProposalReviewVote.GOOD) == 1 + ] + proposals.append(s_items("1 Must Have & 1 Good Vote", batch2)) + + batch3 = [ + p + for p in proposals_qs + if p.get_reviewer_votes_count_by_value(ProposalReviewVote.GOOD) > 2 + and p not in batch1 + ] + proposals.append(s_items("2+ Good Votes", batch3)) + + batch4 = [ + p + for p in proposals_qs + if p.get_reviewer_votes_count_by_value(ProposalReviewVote.GOOD) == 1 + and p.get_reviewer_votes_count_by_value(ProposalReviewVote.NOT_BAD) > 2 + and p not in batch2 + ] + proposals.append(s_items("1 Good & 2+ Not Bad votes", batch4)) + + if votes not in ( + ProposalVotesFilter.SORT_BY_SUM, + ProposalVotesFilter.SORT_BY_SELECTION, + ): + for section in proposal_sections: + section_proposals = [ + p for p in proposals_qs if p.proposal_section == section + ] + proposals.append(s_items(section, section_proposals)) + + return proposals diff --git a/junction/proposals/views.py b/junction/proposals/views.py index fce7c9a6..450b379b 100644 --- a/junction/proposals/views.py +++ b/junction/proposals/views.py @@ -1,369 +1,523 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals + +import collections -# Third Party Stuff from django.conf import settings from django.contrib.auth.decorators import login_required -from django.core.urlresolvers import reverse -from django.http.response import HttpResponse, HttpResponseForbidden, HttpResponseRedirect -from django.shortcuts import Http404, get_object_or_404, render +from django.core.exceptions import PermissionDenied +from django.urls import reverse +from django.http import Http404 +from django.http.response import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render from django.views.decorators.http import require_http_methods - -# Junction Stuff -from junction.base.constants import PROPOSAL_REVIEW_STATUS_SELECTED, PROPOSAL_STATUS_PUBLIC -from junction.conferences.models import Conference, ConferenceProposalReviewer - -from .forms import ProposalCommentForm, ProposalForm, ProposalReviewForm -from .models import ( - Proposal, - ProposalComment, - ProposalCommentVote, - ProposalSection, - ProposalSectionReviewer, - ProposalType, - ProposalVote +from hashids import Hashids +from rest_framework import viewsets +from django_filters import rest_framework as filters +from rest_framework.response import Response + +from junction.base.constants import ( + ConferenceSettingConstants, + ConferenceStatus, + ProposalReviewStatus, + ProposalStatus, ) -from .services import send_mail_for_new_comment, send_mail_for_new_proposal - - -def _is_proposal_author(user, proposal): - if user.is_authenticated() and proposal.author == user: - return True - return False - +from junction.conferences.models import Conference +from junction.feedback import permissions as feedback_permission + +from . import permissions, serializers +from .forms import ( + ProposalCommentForm, + ProposalForm, + ProposalReviewForm, + ProposalsToReviewForm, +) +from .models import Proposal, ProposalComment, ProposalSectionReviewer, ProposalVote +from .services import send_mail_for_new_proposal, send_mail_for_proposal_content + + +class ProposalView(viewsets.ReadOnlyModelViewSet): + queryset = Proposal.objects.filter(status=2) + serializer_class = serializers.ProposalSerializer + filter_backend = (filters.DjangoFilterBackend,) + filter_fields = ("conference", "review_status", "proposal_type", "proposal_section") + + def get_queryset(self): + data = super(ProposalView, self).get_queryset() + return self.filter_queryset(data) + + def list(self, request): + data = self.get_queryset() + response = {"proposals": []} + for datum in data: + d = datum.to_response(request=request) + response["proposals"].append(d) + return Response(response) + + +# Filtering +def _filter_proposals(request, proposals_qs): + """Filters a proposal queryset based on the values present in the request's + query strings. + + Supported query strings are `proposal_section` and `proposal_type`. + """ + serializer = serializers.ProposalFilterSerializer(data=request.GET) + if not serializer.is_valid(): + raise Http404() + + proposal_section_filter = serializer.validated_data.get("proposal_section", None) + proposal_type_filter = serializer.validated_data.get("proposal_type", None) + is_filtered = False + filter_name = False -def _is_proposal_reviewer(user, conference): - if user.is_authenticated() and ConferenceProposalReviewer.objects.filter( - reviewer=user, conference=conference, active=True): - return True - return False + if proposal_section_filter: + proposals_qs = proposals_qs.filter(proposal_section=proposal_section_filter) + is_filtered = True + filter_name = proposal_section_filter + if proposal_type_filter: + proposals_qs = proposals_qs.filter(proposal_type=proposal_type_filter) + is_filtered = True + filter_name = proposal_type_filter -def _is_proposal_author_or_reviewer(user, conference, proposal): - return _is_proposal_author(user, proposal) or _is_proposal_reviewer( - user, conference) + return is_filtered, filter_name, proposals_qs -@require_http_methods(['GET']) +@require_http_methods(["GET"]) def list_proposals(request, conference_slug): conference = get_object_or_404(Conference, slug=conference_slug) + public_voting = ConferenceSettingConstants.ALLOW_PUBLIC_VOTING_ON_PROPOSALS + public_voting_setting = conference.conferencesetting_set.filter( + name=public_voting["name"] + ).first() + public_voting_setting_value = ( + public_voting_setting.value if public_voting_setting else True + ) - proposals_qs = Proposal.objects.filter(conference=conference) - - user_proposals_list = [] - if request.user.is_authenticated(): # Display the proposals by this user - user_proposals_list = proposals_qs.filter(author=request.user) - - selected_proposals_list = proposals_qs.filter(review_status=PROPOSAL_REVIEW_STATUS_SELECTED) - - proposals_to_review = [] - if _is_proposal_reviewer(request.user, conference): - proposal_reviewer_sections = [p.proposal_section for p in - ProposalSectionReviewer.objects.filter( - conference_reviewer__reviewer=request.user)] - proposals_to_review = [p for p in proposals_qs - if p.proposal_section in proposal_reviewer_sections] - - # Filtering - proposal_section_filter = request.GET.getlist('proposal_section') - proposal_type_filter = request.GET.getlist('proposal_type') - is_filtered = False - - if proposal_section_filter: - proposals_qs = proposals_qs.filter(proposal_section__id__in=proposal_section_filter) - is_filtered = True + proposals_qs = Proposal.objects.filter(conference=conference).select_related( + "proposal_type", "proposal_section", "conference", "author", + ) - if proposal_type_filter: - proposals_qs = proposals_qs.filter(proposal_type__id__in=proposal_type_filter) - is_filtered = True + is_filtered, filter_name, proposals_qs = _filter_proposals(request, proposals_qs) - # Display proposals which are public & exclude logged in user proposals - if request.user.is_authenticated(): - proposals_qs = proposals_qs.exclude(author=request.user.id) + # make sure it's after the tag filtering is applied + selected_proposals_list = proposals_qs.filter( + review_status=ProposalReviewStatus.SELECTED + ) - public_proposals_list = proposals_qs.exclude( - review_status=PROPOSAL_REVIEW_STATUS_SELECTED).filter( - status=PROPOSAL_STATUS_PUBLIC).order_by('-created_at') + selected_proposals = collections.defaultdict(list) + for proposal in selected_proposals_list: + name = proposal.proposal_type.name + selected_proposals[name].append(proposal) - proposal_sections = ProposalSection.objects.filter(conferences=conference) - proposal_types = ProposalType.objects.filter(conferences=conference) + # Display proposals which are public + public_proposals_list = ( + proposals_qs.exclude(review_status=ProposalReviewStatus.SELECTED) + .filter(status=ProposalStatus.PUBLIC) + .order_by("created_at") + ) - return render(request, 'proposals/list.html', - {'public_proposals_list': public_proposals_list, - 'user_proposals_list': user_proposals_list, - 'selected_proposals_list': selected_proposals_list, - 'proposals_to_review': proposals_to_review, - 'proposal_sections': proposal_sections, - 'proposal_types': proposal_types, - 'is_filtered': is_filtered, - 'conference': conference}) + return render( + request, + "proposals/list.html", + { + "public_proposals_list": public_proposals_list, + "selected_proposals": dict(selected_proposals), + "proposal_sections": conference.proposal_sections.all(), + "proposal_types": conference.proposal_types.all(), + "is_filtered": is_filtered, + "filter_name": filter_name, + "is_reviewer": permissions.is_proposal_reviewer( + user=request.user, conference=conference + ), + "conference": conference, + "ConferenceStatus": ConferenceStatus, + "public_voting_setting": public_voting_setting_value, + }, + ) @login_required -@require_http_methods(['GET', 'POST']) +@require_http_methods(["GET", "POST"]) def create_proposal(request, conference_slug): conference = get_object_or_404(Conference, slug=conference_slug) - if request.method == 'GET': - if conference.status != 1: - return render(request, 'proposals/closed.html', - {'conference': conference}) - form = ProposalForm(conference) - return render(request, 'proposals/create.html', - {'form': form, - 'conference': conference, }) + if request.method == "GET": + if not conference.is_accepting_proposals(): + return render(request, "proposals/closed.html", {"conference": conference}) + form = ProposalForm( + conference, action="create", initial={"status": ProposalStatus.PUBLIC} + ) + return render( + request, "proposals/create.html", {"form": form, "conference": conference} + ) # POST Workflow - form = ProposalForm(conference, request.POST) + form = ProposalForm(conference, data=request.POST, action="create") if not form.is_valid(): - return render(request, 'proposals/create.html', - {'form': form, - 'conference': conference, - 'errors': form.errors}) + return render( + request, + "proposals/create.html", + {"form": form, "conference": conference, "errors": form.errors}, + ) # Valid Form proposal = Proposal.objects.create( author=request.user, conference=conference, - title=form.cleaned_data['title'], - description=form.cleaned_data['description'], - target_audience=form.cleaned_data['target_audience'], - prerequisites=form.cleaned_data['prerequisites'], - content_urls=form.cleaned_data['content_urls'], - speaker_info=form.cleaned_data['speaker_info'], - speaker_links=form.cleaned_data['speaker_links'], - status=form.cleaned_data['status'], - proposal_type_id=form.cleaned_data['proposal_type'], - proposal_section_id=form.cleaned_data['proposal_section']) - host = '{}://{}'.format(settings.SITE_PROTOCOL, request.META['HTTP_HOST']) - send_mail_for_new_proposal(proposal, host) - - return HttpResponseRedirect(reverse('proposals-list', - args=[conference.slug])) - - -@require_http_methods(['GET']) -def detail_proposal(request, conference_slug, slug): - conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404(Proposal, slug=slug, conference=conference) - allow_private_comment = _is_proposal_author_or_reviewer(request.user, conference, proposal) + title=form.cleaned_data["title"], + description=form.cleaned_data["description"], + target_audience=form.cleaned_data["target_audience"], + prerequisites=form.cleaned_data["prerequisites"], + video_url=form.cleaned_data["video_url"], + content_urls=form.cleaned_data["content_urls"], + private_content_urls=form.cleaned_data["private_content_urls"], + speaker_info=form.cleaned_data["speaker_info"], + speaker_links=form.cleaned_data["speaker_links"], + is_first_time_speaker=form.cleaned_data["is_first_time_speaker"], + status=form.cleaned_data["status"], + proposal_type_id=form.cleaned_data["proposal_type"], + proposal_section_id=form.cleaned_data["proposal_section"], + ) + host = "{}://{}".format(settings.SITE_PROTOCOL, request.META.get("HTTP_HOST")) + + if settings.USE_ASYNC_FOR_EMAIL: + send_mail_for_new_proposal.delay(proposal.id, host) + else: + send_mail_for_new_proposal(proposal.id, host) + + return HttpResponseRedirect( + reverse("proposal-detail", args=[conference.slug, proposal.slug]) + ) - vote_value = 0 - try: - if request.user.is_authenticated(): - proposal_vote = ProposalVote.objects.get(proposal=proposal, voter=request.user) - vote_value = 1 if proposal_vote.up_vote else -1 - except ProposalVote.DoesNotExist: - pass +@require_http_methods(["GET"]) +def detail_proposal(request, conference_slug, slug, hashid=None): + """Display a proposal detail page. + """ + # Here try to get a proposal by it's hashid. If the slug didn't match because + # the title might have changed, redirect to the correct slug. + # hashid is optional due to backward compatibility. If the hashid is not present + # we still try to get the proposal by old method i.e. using just the slug, but + # redirect to the correct url containing hashid. + hashids = Hashids(min_length=5) + id = hashids.decode(hashid) + if id: + proposal = get_object_or_404(Proposal, id=id[0]) + if slug != proposal.get_slug(): + return HttpResponseRedirect(proposal.get_absolute_url()) + else: + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=slug, conference=conference) + return HttpResponseRedirect(proposal.get_absolute_url()) + + if proposal.deleted or ( + not proposal.is_public() and request.user != proposal.author + ): + raise Http404("404") + + # Here we have obtained the proposal that we want to display. + conference = proposal.conference + read_private_comment = permissions.is_proposal_author_or_proposal_reviewer( + request.user, conference, proposal + ) + write_private_comment = permissions.is_proposal_author_or_proposal_section_reviewer( + request.user, conference, proposal + ) + is_reviewer = permissions.is_proposal_reviewer(request.user, conference) + is_section_reviewer = permissions.is_proposal_section_reviewer( + request.user, conference, proposal + ) + public_voting = ConferenceSettingConstants.ALLOW_PUBLIC_VOTING_ON_PROPOSALS + public_voting_setting = conference.conferencesetting_set.filter( + name=public_voting["name"] + ).first() + vote_value, public_voting_setting_value = 0, True + + if public_voting_setting: + public_voting_setting_value = public_voting_setting.value + try: + if request.user.is_authenticated: + proposal_vote = ProposalVote.objects.get( + proposal=proposal, voter=request.user + ) + vote_value = 1 if proposal_vote.up_vote else -1 + except ProposalVote.DoesNotExist: + pass ctx = { - 'login_url': settings.LOGIN_URL, - 'proposal': proposal, - 'allow_private_comment': allow_private_comment, - 'vote_value': vote_value, - 'can_delete': request.user == proposal.author, - 'is_author': request.user == proposal.author, + "login_url": settings.LOGIN_URL, + "proposal": proposal, + "read_private_comment": read_private_comment, + "write_private_comment": write_private_comment, + "vote_value": vote_value, + "is_author": request.user == proposal.author, + "is_reviewer": is_reviewer, + "is_section_reviewer": is_section_reviewer, + "can_view_feedback": False, + "schedule_item": False, + "reviewers_comments": False, + "reviewers_proposal_comment_form": False, + "reviewers_only_proposal_comment_form": False, + "reviewers_only_comments": False, + "can_vote": permissions.is_proposal_voting_allowed(proposal), + "public_voting_setting": public_voting_setting_value, + "review_comments": False } + if proposal.scheduleitem_set.all(): + schedule_item = proposal.scheduleitem_set.all()[0] + ctx["can_view_feedback"] = feedback_permission.can_view_feedback( + user=request.user, schedule_item=schedule_item + ) + ctx["schedule_item"] = schedule_item + comments = ProposalComment.objects.filter( - proposal=proposal, deleted=False, + proposal=proposal, deleted=False, vote=False + ) + + if read_private_comment: + ctx["reviewers_comments"] = comments.get_reviewers_comments() + if write_private_comment: + ctx["reviewers_proposal_comment_form"] = ProposalCommentForm( + initial={"private": True} + ) + if is_reviewer: + ctx["reviewers_only_proposal_comment_form"] = ProposalCommentForm( + initial={"reviewer": True} + ) + ctx["reviewers_only_comments"] = comments.get_reviewers_only_comments() + + ctx.update( + { + "comments": comments.get_public_comments(), + "proposal_comment_form": ProposalCommentForm(), + } ) - if allow_private_comment: - ctx.update({ - 'reviewers_comments': comments.filter(private=True), - 'reviewers_proposal_comment_form': ProposalCommentForm( - initial={'private': True}) - }) - ctx.update({'comments': comments.filter(private=False), - 'proposal_comment_form': ProposalCommentForm()}) - return render(request, 'proposals/detail/base.html', ctx) + ctx["enable_upload_content"] = settings.ENABLE_UPLOAD_CONTENT + ctx["enable_second_phase_voting"] = settings.ENABLE_SECOND_PHASE_VOTING + + return render(request, "proposals/detail/base.html", ctx) @login_required -@require_http_methods(['GET', 'POST']) +@require_http_methods(["GET", "POST"]) def update_proposal(request, conference_slug, slug): conference = get_object_or_404(Conference, slug=conference_slug) proposal = get_object_or_404(Proposal, slug=slug, conference=conference) - if not proposal.author == request.user: - return HttpResponseForbidden() + if not permissions.is_proposal_author(user=request.user, proposal=proposal): + raise PermissionDenied - if request.method == 'GET': + if request.method == "GET": form = ProposalForm.populate_form_for_update(proposal) - return render(request, 'proposals/update.html', {'form': form, - 'proposal': proposal}) + return render( + request, "proposals/update.html", {"form": form, "proposal": proposal} + ) # POST Workflow - form = ProposalForm(conference, request.POST) + form = ProposalForm(conference, data=request.POST) if not form.is_valid(): - return render(request, 'proposals/update.html', {'form': form, - 'proposal': proposal, - 'errors': form.errors}) + return render( + request, + "proposals/update.html", + {"form": form, "proposal": proposal, "errors": form.errors}, + ) # Valid Form - proposal.title = form.cleaned_data['title'] - proposal.description = form.cleaned_data['description'] - proposal.target_audience = form.cleaned_data['target_audience'] - proposal.prerequisites = form.cleaned_data['prerequisites'] - proposal.content_urls = form.cleaned_data['content_urls'] - proposal.speaker_info = form.cleaned_data['speaker_info'] - proposal.speaker_links = form.cleaned_data['speaker_links'] - proposal.status = form.cleaned_data['status'] - proposal.proposal_type_id = form.cleaned_data['proposal_type'] - proposal.proposal_section_id = form.cleaned_data['proposal_section'] + proposal.title = form.cleaned_data["title"] + proposal.description = form.cleaned_data["description"] + proposal.target_audience = form.cleaned_data["target_audience"] + proposal.prerequisites = form.cleaned_data["prerequisites"] + proposal.video_url = form.cleaned_data["video_url"] + proposal.content_urls = form.cleaned_data["content_urls"] + proposal.private_content_urls = form.cleaned_data["private_content_urls"] + proposal.speaker_info = form.cleaned_data["speaker_info"] + proposal.speaker_links = form.cleaned_data["speaker_links"] + proposal.is_first_time_speaker = form.cleaned_data["is_first_time_speaker"] + proposal.status = form.cleaned_data["status"] + proposal.proposal_type_id = form.cleaned_data["proposal_type"] + proposal.proposal_section_id = form.cleaned_data["proposal_section"] proposal.save() - return HttpResponseRedirect(reverse('proposals-list', - args=[conference.slug])) + return HttpResponseRedirect( + reverse("proposal-detail", args=[conference.slug, proposal.slug]) + ) @login_required -@require_http_methods(['GET', 'POST']) -def review_proposal(request, conference_slug, slug): +@require_http_methods(["GET", "POST"]) +def proposals_to_review(request, conference_slug): conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404(Proposal, slug=slug, conference=conference) - - if not _is_proposal_reviewer(request.user, conference): - return HttpResponseForbidden() - if request.method == 'GET': + if not permissions.is_proposal_reviewer(request.user, conference): + raise PermissionDenied - comments = ProposalComment.objects.filter( - proposal=proposal, deleted=False, + proposals_qs = ( + Proposal.objects.select_related( + "proposal_type", "proposal_section", "conference", "author", + ) + .filter(conference=conference) + .filter(status=ProposalStatus.PUBLIC) + .order_by("created_at") + ) + psr = ProposalSectionReviewer.objects.filter( + conference_reviewer__reviewer=request.user, + conference_reviewer__conference=conference, + ) + proposal_reviewer_sections = [p.proposal_section for p in psr] + proposal_sections = conference.proposal_sections.all() + proposal_types = conference.proposal_types.all() + + s_items = collections.namedtuple("section_items", "section proposals") + + if request.method == "GET": + proposals_to_review = [] + for section in proposal_reviewer_sections: + section_proposals = [ + p for p in proposals_qs if p.proposal_section == section + ] + proposals_to_review.append(s_items(section, section_proposals)) + + form = ProposalsToReviewForm( + conference=conference, proposal_sections=proposal_reviewer_sections ) - proposal_review_form = ProposalReviewForm( - initial={'review_status': proposal.review_status}) - - ctx = { - 'proposal': proposal, - 'proposal_review_form': proposal_review_form, - 'reviewers_comments': comments.filter(private=True), - 'reviewers_proposal_comment_form': ProposalCommentForm( - initial={'private': True}), + context = { + "proposals_to_review": proposals_to_review, + "proposal_reviewer_sections": proposal_reviewer_sections, + "proposal_sections": proposal_sections, + "proposal_types": proposal_types, + "conference": conference, + "form": form, } - return render(request, 'proposals/review.html', ctx) + return render(request, "proposals/to_review.html", context) # POST Workflow - form = ProposalReviewForm(request.POST) + form = ProposalsToReviewForm( + data=request.POST, + conference=conference, + proposal_sections=proposal_reviewer_sections, + ) if not form.is_valid(): - return render(request, 'proposals/review.html', {'form': form, - 'proposal': proposal, - 'errors': form.errors}) + context["errors"] = form.errors + return render(request, "proposals/to_review.html", context) # Valid Form - proposal.review_status = form.cleaned_data['review_status'] - proposal.save() - return HttpResponseRedirect(reverse('proposals-list', - args=[conference.slug])) - + p_section = form.cleaned_data["proposal_section"] + p_type = form.cleaned_data["proposal_type"] + r_comment = form.cleaned_data["reviewer_comment"] -@login_required -@require_http_methods(['GET', 'POST']) -def delete_proposal(request, conference_slug, slug): - conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404(Proposal, slug=slug, conference=conference) + if p_section != "all": + proposals_qs = proposals_qs.filter(proposal_section__id__in=p_section) + if p_type != "all": + proposals_qs = proposals_qs.filter(proposal_type__id__in=p_type) + if r_comment == "True": + proposals_qs = [p for p in proposals_qs if p.get_reviews_comments_count() > 0] - if not proposal.author == request.user: - return HttpResponseForbidden() + proposals_to_review = [] + for section in proposal_reviewer_sections: + section_proposals = [p for p in proposals_qs if p.proposal_section == section] + proposals_to_review.append(s_items(section, section_proposals)) + + context = { + "proposals_to_review": proposals_to_review, + "proposal_sections": proposal_sections, + "proposal_types": proposal_types, + "conference": conference, + "form": form, + } - if request.method == 'GET': - return render(request, 'proposals/delete.html', {'proposal': proposal}) - elif request.method == 'POST': - proposal.delete() - return HttpResponseRedirect(reverse('proposals-list', - args=[conference.slug])) + return render(request, "proposals/to_review.html", context) @login_required -@require_http_methods(['POST']) -def create_proposal_comment(request, conference_slug, proposal_slug): +@require_http_methods(["GET", "POST"]) +def review_proposal(request, conference_slug, slug): conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404( - Proposal, slug=proposal_slug, conference=conference) - form = ProposalCommentForm(request.POST) - if form.is_valid(): - comment = form.cleaned_data['comment'] - private = form.cleaned_data['private'] - - if private and not _is_proposal_author_or_reviewer( - request.user, conference, proposal): - raise Http404() - - proposal_comment = ProposalComment.objects.create( - proposal=proposal, comment=comment, - private=private, commenter=request.user) - send_mail_for_new_comment( - proposal_comment, login_url=settings.LOGIN_URL, - host='{}://{}'.format(settings.SITE_PROTOCOL, - request.META['HTTP_HOST'])) - - redirect_url = reverse('proposal-detail', - args=[conference.slug, proposal.slug]) + proposal = get_object_or_404(Proposal, slug=slug, conference=conference) - redirect_url += "#js-reviewers" if private else "#js-comments" + if not permissions.is_proposal_section_reviewer(request.user, conference, proposal): + raise PermissionDenied - return HttpResponseRedirect(redirect_url) + if request.method == "GET": + comments = ProposalComment.objects.filter(proposal=proposal, deleted=False) + proposal_review_form = ProposalReviewForm( + initial={"review_status": proposal.review_status} + ) -@login_required -def proposal_vote(request, conference_slug, proposal_slug, up_vote): - conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + ctx = { + "proposal": proposal, + "proposal_review_form": proposal_review_form, + "reviewers_comments": comments.get_reviewers_comments(), + "reviewers_only_comments": comments.get_reviewers_only_comments(), + "reviewers_proposal_comment_form": ProposalCommentForm( + initial={"private": True} + ), + "reviewers_only_proposal_comment_form": ProposalCommentForm( + initial={"review": True} + ), + } - proposal_vote, created = ProposalVote.objects.get_or_create( - proposal=proposal, voter=request.user) # @UnusedVariable + return render(request, "proposals/review.html", ctx) - role = 2 if _is_proposal_reviewer(request.user, conference) else 1 + # POST Workflow + form = ProposalReviewForm(data=request.POST) + if not form.is_valid(): + context = {"form": form, "proposal": proposal, "form_errors": form.errors} + return render(request, "proposals/review.html", context) - proposal_vote.role = role - proposal_vote.up_vote = up_vote - proposal_vote.save() + # Valid Form + proposal.review_status = form.cleaned_data["review_status"] + proposal.save() - return HttpResponse(proposal.get_votes_count()) + return HttpResponseRedirect(reverse("proposals-list", args=[conference.slug])) @login_required -@require_http_methods(['POST']) -def proposal_vote_up(request, conference_slug, proposal_slug): - return proposal_vote(request, conference_slug, proposal_slug, True) - +@require_http_methods(["POST"]) +def proposal_upload_content(request, conference_slug, slug): + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=slug, conference=conference) -@login_required -@require_http_methods(['POST']) -def proposal_vote_down(request, conference_slug, proposal_slug): - return proposal_vote(request, conference_slug, proposal_slug, False) + if not ( + permissions.is_proposal_section_reviewer(request.user, conference, proposal) + or request.user.is_superuser + ): + raise PermissionDenied + host = "{}://{}".format(settings.SITE_PROTOCOL, request.META["HTTP_HOST"]) -def proposal_comment_vote(request, conference_slug, proposal_slug, comment_id, - up_vote): - conference = get_object_or_404(Conference, slug=conference_slug) - proposal = get_object_or_404(Proposal, slug=proposal_slug, - conference=conference) - proposal_comment = get_object_or_404(ProposalComment, proposal=proposal, - id=comment_id) - proposal_comment_vote, created = ProposalCommentVote.objects.get_or_create( - proposal_comment=proposal_comment, voter=request.user) - proposal_comment_vote.up_vote = up_vote - proposal_comment_vote.save() + if settings.USE_ASYNC_FOR_EMAIL: + send_mail_for_proposal_content.delay(conference.id, proposal.id, host) + message = "Email sent successfully." + else: + response = send_mail_for_proposal_content(conference.id, proposal.id, host) + if response == 1: + message = "Email sent successfully." + else: + message = ( + "There is problem in sending mail. Please contact conference chair." + ) - return HttpResponseRedirect(reverse('proposal-detail', - args=[conference.slug, proposal.slug])) + return HttpResponse(message) @login_required -@require_http_methods(['POST']) -def proposal_comment_up_vote(request, conference_slug, proposal_slug, - proposal_comment_id): - return proposal_comment_vote(request, conference_slug, proposal_slug, - proposal_comment_id, True) +@require_http_methods(["GET", "POST"]) +def delete_proposal(request, conference_slug, slug): + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=slug, conference=conference) + permissions.is_proposal_author_or_permisson_denied( + user=request.user, proposal=proposal + ) -@login_required -@require_http_methods(['POST']) -def proposal_comment_down_vote(request, conference_slug, proposal_slug, - proposal_comment_id): - return proposal_comment_vote(request, conference_slug, proposal_slug, - proposal_comment_id, False) + if request.method == "GET": + return render(request, "proposals/delete.html", {"proposal": proposal}) + elif request.method == "POST": + proposal.delete() + return HttpResponseRedirect(reverse("proposals-list", args=[conference.slug])) diff --git a/junction/proposals/votes_views.py b/junction/proposals/votes_views.py new file mode 100644 index 00000000..a8f181f3 --- /dev/null +++ b/junction/proposals/votes_views.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth.decorators import login_required +from django.urls import reverse +from django.http import HttpResponseForbidden +from django.http.response import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.views.decorators.http import require_http_methods + +from junction.base.constants import ConferenceSettingConstants, ProposalUserVoteRole +from junction.conferences.models import Conference + +from . import permissions, utils +from .forms import ProposalReviewerVoteForm +from .models import ( + Proposal, + ProposalComment, + ProposalCommentVote, + ProposalVote, + PSRVotePhase, +) + + +@login_required +def proposal_vote(request, conference_slug, proposal_slug, up_vote): + conference = get_object_or_404(Conference, slug=conference_slug) + + public_voting = ConferenceSettingConstants.ALLOW_PUBLIC_VOTING_ON_PROPOSALS + public_voting_setting = conference.conferencesetting_set.filter( + name=public_voting["name"] + ).first() + if public_voting_setting and not public_voting_setting.value: + return HttpResponseForbidden() + + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + + if not permissions.is_proposal_voting_allowed(proposal): + return HttpResponseForbidden() + + if up_vote is None: + # Remove any vote casted and return + ProposalVote.objects.filter(proposal=proposal, voter=request.user).delete() + return HttpResponse(proposal.get_votes_count()) + + proposal_vote, created = ProposalVote.objects.get_or_create( + proposal=proposal, voter=request.user + ) # @UnusedVariable + + if permissions.is_proposal_reviewer(request.user, conference): + role = ProposalUserVoteRole.REVIEWER + else: + role = ProposalUserVoteRole.PUBLIC + + proposal_vote.role = role + proposal_vote.up_vote = up_vote + proposal_vote.save() + + return HttpResponse(proposal.get_votes_count()) + + +@login_required +@require_http_methods(["POST"]) +def proposal_vote_up(request, conference_slug, proposal_slug): + return proposal_vote(request, conference_slug, proposal_slug, True) + + +@login_required +@require_http_methods(["POST"]) +def proposal_vote_down(request, conference_slug, proposal_slug): + return proposal_vote(request, conference_slug, proposal_slug, False) + + +@login_required +@require_http_methods(["POST"]) +def proposal_vote_remove(request, conference_slug, proposal_slug): + return proposal_vote(request, conference_slug, proposal_slug, None) + + +def proposal_comment_vote(request, conference_slug, proposal_slug, comment_id, up_vote): + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + proposal_comment = get_object_or_404( + ProposalComment, proposal=proposal, id=comment_id + ) + proposal_comment_vote, created = ProposalCommentVote.objects.get_or_create( + proposal_comment=proposal_comment, voter=request.user + ) + proposal_comment_vote.up_vote = up_vote + proposal_comment_vote.save() + + return HttpResponse(proposal_comment.get_votes_count()) + + +@login_required +@require_http_methods(["POST"]) +def proposal_comment_up_vote( + request, conference_slug, proposal_slug, proposal_comment_id +): + return proposal_comment_vote( + request, conference_slug, proposal_slug, proposal_comment_id, True + ) + + +@login_required +@require_http_methods(["POST"]) +def proposal_comment_down_vote( + request, conference_slug, proposal_slug, proposal_comment_id +): + return proposal_comment_vote( + request, conference_slug, proposal_slug, proposal_comment_id, False + ) + + +@login_required +@require_http_methods(["GET", "POST"]) +def proposal_reviewer_vote(request, conference_slug, proposal_slug): + user = request.user + vote_phase = PSRVotePhase.PRIMARY + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + + psr_vote, p_comment = utils.get_reviewer_vote_info( + user, conference, proposal, vote_phase + ) + + if request.method == "GET": + if psr_vote and p_comment: + proposal_vote_form = ProposalReviewerVoteForm( + conference=conference, + initial={ + "vote_value": psr_vote.vote_value.vote_value, + "comment": p_comment.comment, + }, + ) + else: + proposal_vote_form = ProposalReviewerVoteForm(conference=conference) + ctx = { + "proposal": proposal, + "form": proposal_vote_form, + "vote": psr_vote, + } + + return render(request, "proposals/vote.html", ctx) + + # POST Workflow + form = ProposalReviewerVoteForm(data=request.POST, conference=conference) + if not form.is_valid(): + ctx = {"form": form, "proposal": proposal, "form_errors": form.errors} + return render(request, "proposals/vote.html", ctx) + + # Valid Form + vote_value = form.cleaned_data["vote_value"] + comment = form.cleaned_data["comment"] + + utils.update_reviewer_vote_info( + user, psr_vote, vote_value, comment, vote_phase, proposal, conference + ) + return HttpResponseRedirect(reverse("proposals-to-review", args=[conference.slug])) + + +@login_required +@require_http_methods(["GET", "POST"]) +def proposal_reviewer_secondary_vote(request, conference_slug, proposal_slug): + vote_phase = PSRVotePhase.SECONDARY + user = request.user + conference = get_object_or_404(Conference, slug=conference_slug) + proposal = get_object_or_404(Proposal, slug=proposal_slug, conference=conference) + + psr_vote, p_comment = utils.get_reviewer_vote_info( + user, conference, proposal, vote_phase + ) + + if request.method == "GET": + if psr_vote and p_comment: + proposal_vote_form = ProposalReviewerVoteForm( + conference=conference, + initial={ + "vote_value": psr_vote.vote_value.vote_value, + "comment": p_comment.comment, + }, + ) + else: + proposal_vote_form = ProposalReviewerVoteForm(conference=conference) + ctx = { + "proposal": proposal, + "form": proposal_vote_form, + "vote": psr_vote, + } + + return render(request, "proposals/vote.html", ctx) + + # POST Workflow + form = ProposalReviewerVoteForm(data=request.POST, conference=conference) + if not form.is_valid(): + ctx = {"form": form, "proposal": proposal, "form_errors": form.errors} + return render(request, "proposals/vote.html", ctx) + + # Valid Form + vote_value = form.cleaned_data["vote_value"] + comment = form.cleaned_data["comment"] + + utils.update_reviewer_vote_info( + user, psr_vote, vote_value, comment, vote_phase, proposal, conference + ) + return HttpResponseRedirect(reverse("proposals-to-review", args=[conference.slug])) diff --git a/junction/schedule/__init__.py b/junction/schedule/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/schedule/admin.py b/junction/schedule/admin.py new file mode 100644 index 00000000..ba29a64d --- /dev/null +++ b/junction/schedule/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin # noqa + +from junction.conferences import service + +from .models import ScheduleItem, ScheduleItemType + + +@admin.register(ScheduleItem) +class SchduleItemAdmin(admin.ModelAdmin): + list_filter = ("type", "room") + + def get_queryset(self, request): + qs = super(SchduleItemAdmin, self).get_queryset(request) + if request.user.is_superuser: + return qs + moderators = service.list_conference_moderator(user=request.user) + return qs.filter(conference__in=[m.conference for m in moderators]) + + +@admin.register(ScheduleItemType) +class SchduleItemTypeAdmin(admin.ModelAdmin): + pass diff --git a/junction/schedule/migrations/0001_initial.py b/junction/schedule/migrations/0001_initial.py new file mode 100644 index 00000000..18354136 --- /dev/null +++ b/junction/schedule/migrations/0001_initial.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("conferences", "0010_auto_20150713_2331"), + ("proposals", "0012_auto_20150709_0842"), + ] + + operations = [ + migrations.CreateModel( + name="ScheduleItem", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("event_date", models.DateField(db_index=True)), + ("start_time", models.TimeField(db_index=True)), + ("end_time", models.TimeField()), + ("alt_name", models.CharField(max_length=100, blank=True)), + ( + "type", + models.CharField( + default=b"TALK", + max_length=20, + choices=[ + (b"TALK", b"Talk"), + (b"LUNCH", b"Lunch"), + (b"BREAK", b"Break"), + ], + ), + ), + ( + "conference", + models.ForeignKey( + to="conferences.Conference", on_delete=models.deletion.CASCADE, + ), + ), + ( + "created_by", + models.ForeignKey( + related_name="created_scheduleitem_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_scheduleitem_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "room", + models.ForeignKey( + to="conferences.Room", + null=True, + on_delete=models.deletion.CASCADE, + ), + ), + ( + "session", + models.ForeignKey( + to="proposals.Proposal", + null=True, + on_delete=models.deletion.CASCADE, + ), + ), + ], + options={}, + bases=(models.Model,), + ), + migrations.AlterIndexTogether( + name="scheduleitem", index_together=set([("event_date", "start_time")]), + ), + ] diff --git a/junction/schedule/migrations/0002_auto_20150831_0043.py b/junction/schedule/migrations/0002_auto_20150831_0043.py new file mode 100644 index 00000000..f23191ee --- /dev/null +++ b/junction/schedule/migrations/0002_auto_20150831_0043.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitem", + name="type", + field=models.CharField( + default=b"TALK", + max_length=20, + choices=[ + (b"TALK", b"Talk"), + (b"LUNCH", b"Lunch"), + (b"BREAK", b"Break"), + (b"WORKSHOP", b"Workshop"), + (b"POSTER", b"Poster"), + (b"OPEN_SPACE", b"Open Space"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/schedule/migrations/0003_scheduleitemtype.py b/junction/schedule/migrations/0003_scheduleitemtype.py new file mode 100644 index 00000000..24780862 --- /dev/null +++ b/junction/schedule/migrations/0003_scheduleitemtype.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + +SCHEDULE_ITEM_TYPES = ["Talk", "Lunch", "Break", "Workshop", "Poster", "Open Space"] + + +def load_fixture(apps, schema_editor): + Model = apps.get_model("schedule", "ScheduleItemType") + for item_type in SCHEDULE_ITEM_TYPES: + Model.objects.create(title=item_type) + + +def unload_fixture(apps, schema_editor): + Model = apps.get_model("schedule", "ScheduleItemType") + for item_type in SCHEDULE_ITEM_TYPES: + for obj in Model.objects.filter(title=item_type): + obj.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("schedule", "0002_auto_20150831_0043"), + ] + + operations = [ + migrations.CreateModel( + name="ScheduleItemType", + fields=[ + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="Created At"), + ), + ( + "modified_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ("title", models.CharField(max_length=100)), + ( + "created_by", + models.ForeignKey( + related_name="created_scheduleitemtype_set", + verbose_name="Created By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ( + "modified_by", + models.ForeignKey( + related_name="updated_scheduleitemtype_set", + verbose_name="Modified By", + blank=True, + on_delete=models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + null=True, + ), + ), + ], + options={"abstract": False}, + bases=(models.Model,), + ), + migrations.RunPython(load_fixture, reverse_code=unload_fixture), + ] diff --git a/junction/schedule/migrations/0004_auto_20150917_2017.py b/junction/schedule/migrations/0004_auto_20150917_2017.py new file mode 100644 index 00000000..61afaf53 --- /dev/null +++ b/junction/schedule/migrations/0004_auto_20150917_2017.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0003_scheduleitemtype"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitem", + name="type", + field=models.CharField( + default=b"Talk", + max_length=20, + choices=[ + (b"Talk", b"Talk"), + (b"Lunch", b"Lunch"), + (b"Break", b"Break"), + (b"Workshop", b"Workshop"), + (b"Poster", b"Poster"), + (b"Open Space", b"Open Space"), + (b"Introduction", b"Introduction"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/schedule/migrations/0005_auto_20150917_2112.py b/junction/schedule/migrations/0005_auto_20150917_2112.py new file mode 100644 index 00000000..73fe7062 --- /dev/null +++ b/junction/schedule/migrations/0005_auto_20150917_2112.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0004_auto_20150917_2017"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitem", + name="session", + field=models.ForeignKey( + blank=True, + to="proposals.Proposal", + null=True, + on_delete=models.deletion.CASCADE, + ), + preserve_default=True, + ), + migrations.AlterField( + model_name="scheduleitem", + name="type", + field=models.CharField( + default=b"Talk", + max_length=20, + choices=[ + (b"Talk", b"Talk"), + (b"Lunch", b"Lunch"), + (b"Break", b"Break"), + (b"Workshop", b"Workshop"), + (b"Poster", b"Poster"), + (b"Open Space", b"Open Space"), + (b"Introduction", b"Introduction"), + (b"Lightning Talk", b"Lightning Talk"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/schedule/migrations/0006_auto_20150918_0042.py b/junction/schedule/migrations/0006_auto_20150918_0042.py new file mode 100644 index 00000000..c4022ba9 --- /dev/null +++ b/junction/schedule/migrations/0006_auto_20150918_0042.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0005_auto_20150917_2112"), + ] + + operations = [ + migrations.AddField( + model_name="scheduleitem", + name="alt_description", + field=models.TextField(blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name="scheduleitem", + name="alt_name", + field=models.CharField(max_length=255, blank=True), + preserve_default=True, + ), + ] diff --git a/junction/schedule/migrations/0007_auto_20160623_1448.py b/junction/schedule/migrations/0007_auto_20160623_1448.py new file mode 100644 index 00000000..64c4085b --- /dev/null +++ b/junction/schedule/migrations/0007_auto_20160623_1448.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0006_auto_20150918_0042"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitem", + name="type", + field=models.CharField( + max_length=20, + default="Talk", + choices=[ + ("Talk", "Talk"), + ("Lunch", "Lunch"), + ("Break", "Break"), + ("Workshop", "Workshop"), + ("Poster", "Poster"), + ("Open Space", "Open Space"), + ("Introduction", "Introduction"), + ("Lightning Talk", "Lightning Talk"), + ], + ), + preserve_default=True, + ), + ] diff --git a/junction/schedule/migrations/0008_auto_20200322_1904.py b/junction/schedule/migrations/0008_auto_20200322_1904.py new file mode 100644 index 00000000..41950dd5 --- /dev/null +++ b/junction/schedule/migrations/0008_auto_20200322_1904.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2020-03-22 13:34 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("schedule", "0007_auto_20160623_1448"), + ] + + operations = [ + migrations.AlterField( + model_name="scheduleitem", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_scheduleitem_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="scheduleitem", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_scheduleitem_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + migrations.AlterField( + model_name="scheduleitem", + name="room", + field=models.ForeignKey( + null=True, on_delete=models.deletion.SET_NULL, to="conferences.Room", + ), + ), + migrations.AlterField( + model_name="scheduleitem", + name="session", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + to="proposals.Proposal", + ), + ), + migrations.AlterField( + model_name="scheduleitemtype", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="created_scheduleitemtype_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + migrations.AlterField( + model_name="scheduleitemtype", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=models.deletion.SET_NULL, + related_name="updated_scheduleitemtype_set", + to=settings.AUTH_USER_MODEL, + verbose_name="Modified By", + ), + ), + ] diff --git a/junction/schedule/migrations/0009_alter_scheduleitem_session.py b/junction/schedule/migrations/0009_alter_scheduleitem_session.py new file mode 100644 index 00000000..3e6608f1 --- /dev/null +++ b/junction/schedule/migrations/0009_alter_scheduleitem_session.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2023-01-10 12:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('proposals', '0031_auto_20230110_1818'), + ('schedule', '0008_auto_20200322_1904'), + ] + + operations = [ + migrations.AlterField( + model_name='scheduleitem', + name='session', + field=models.ForeignKey(blank=True, limit_choices_to={'review_status': 2}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='proposals.proposal'), + ), + ] diff --git a/junction/schedule/migrations/__init__.py b/junction/schedule/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/junction/schedule/models.py b/junction/schedule/models.py new file mode 100644 index 00000000..cd8013ef --- /dev/null +++ b/junction/schedule/models.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from six import python_2_unicode_compatible +from rest_framework.reverse import reverse + +from junction.base.constants import ProposalReviewStatus +from junction.base.models import AuditModel +from junction.conferences.models import Conference, Room +from junction.proposals.models import Proposal + + +@python_2_unicode_compatible +class ScheduleItemType(AuditModel): + title = models.CharField(max_length=100) + + def __str__(self): + return self.title + + +class ScheduleItem(AuditModel): + INTROUDCTION = "Introduction" + TALK = "Talk" + LUNCH = "Lunch" + BREAK = "Break" + WORKSHOP = "Workshop" + POSTER = "Poster" + OPEN_SPACE = "Open Space" + LIGHTNING_TALK = "Lightning Talk" + SCHEDULE_ITEM_TYPE = ( + (TALK, "Talk"), + (LUNCH, "Lunch"), + (BREAK, "Break"), + (WORKSHOP, "Workshop"), + (POSTER, "Poster"), + (OPEN_SPACE, "Open Space"), + (INTROUDCTION, "Introduction"), + (LIGHTNING_TALK, "Lightning Talk"), + ) + room = models.ForeignKey(Room, null=True, on_delete=models.SET_NULL) + # if a session is not present, venue can be null Ex: break + event_date = models.DateField(db_index=True) + start_time = models.TimeField(db_index=True) + end_time = models.TimeField() + alt_name = models.CharField(max_length=255, blank=True) + alt_description = models.TextField(blank=True) + limit_choices = {"review_status": ProposalReviewStatus.SELECTED} + session = models.ForeignKey( + Proposal, + null=True, + blank=True, + limit_choices_to=limit_choices, + on_delete=models.SET_NULL, + ) + type = models.CharField(max_length=20, choices=SCHEDULE_ITEM_TYPE, default=TALK) + + conference = models.ForeignKey(Conference, on_delete=models.CASCADE) + + def __unicode__(self): + return "{} - {} on {} from {} to {} in {}".format( + self.conference, + self.name, + self.event_date, + self.start_time, + self.end_time, + self.room, + ) + + @property + def name(self): + return self.alt_name or self.session.title + + class Meta: + index_together = [("event_date", "start_time")] + + def to_response(self, request): + """method will return dict which can be passed to response + """ + data = { + "id": self.id, + "room_id": getattr(self.room, "id", None), + "event_date": self.event_date.strftime("%Y-%m-%d"), + "start_time": self.start_time.strftime("%H:%M:%S"), + "end_time": self.end_time.strftime("%H:%M:%S"), + "name": self.name, + "type": self.type, + "conference": reverse( + "conference-detail", kwargs={"pk": self.conference_id}, request=request + ), + } + if self.session: + session = self.session + author = "{} {}".format(session.author.first_name, session.author.last_name) + data["session"] = { + "id": session.id, + "title": session.title, + "section": session.proposal_section.name, + "author": author, + "description": session.description, + "target_audience": session.target_audience, + "prerequisites": session.prerequisites, + "content_urls": session.content_urls, + "speaker_links": session.speaker_links, + "speaker_info": session.speaker_info, + } + else: + data["session"] = {"description": self.alt_description} + return data diff --git a/junction/schedule/serializers.py b/junction/schedule/serializers.py new file mode 100644 index 00000000..f9a2160e --- /dev/null +++ b/junction/schedule/serializers.py @@ -0,0 +1,24 @@ +from rest_framework import serializers + +from junction.proposals.serializers import ProposalSerializer + +from .models import ScheduleItem + + +class ScheduleSerializer(serializers.HyperlinkedModelSerializer): + session = ProposalSerializer() + room_id = serializers.PrimaryKeyRelatedField(source="room", read_only=True) + + class Meta: + model = ScheduleItem + fields = ( + "room_id", + "event_date", + "start_time", + "end_time", + "name", + "session", + "type", + "conference", + "id", + ) diff --git a/junction/schedule/tests.py b/junction/schedule/tests.py new file mode 100644 index 00000000..9a30df3b --- /dev/null +++ b/junction/schedule/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase # noqa + +# Create your tests here. diff --git a/junction/schedule/urls.py b/junction/schedule/urls.py new file mode 100644 index 00000000..34589b78 --- /dev/null +++ b/junction/schedule/urls.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + +from django.urls import re_path + +from . import views + +urlpatterns = [ + # schedule urls + re_path(r"^dummy_schedule/$", views.dummy_schedule, name="dummy_schedule"), +] diff --git a/junction/schedule/views.py b/junction/schedule/views.py new file mode 100644 index 00000000..cee1ae3a --- /dev/null +++ b/junction/schedule/views.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from collections import OrderedDict, defaultdict + +from django.core.exceptions import ObjectDoesNotExist +from django.http import HttpResponse +from django.shortcuts import Http404, render +from django.template.loader import render_to_string +from rest_framework import viewsets +from django_filters import rest_framework as filters +from rest_framework.response import Response + +from .models import ScheduleItem +from .serializers import ScheduleSerializer + + +class ScheduleView(viewsets.ReadOnlyModelViewSet): + queryset = ScheduleItem.objects.all() + serializer_class = ScheduleSerializer + filter_backend = (filters.DjangoFilterBackend,) + filter_fields = ("room", "conference", "event_date") + + def get_queryset(self): + data = ( + super(ScheduleView, self) + .get_queryset() + .prefetch_related( + "session", + "session__proposal_type", + "session__proposal_section", + "session__author", + ) + .order_by("event_date", "start_time") + ) + return self.filter_queryset(data) + + def list(self, request): + data = self.get_queryset() + schedule = defaultdict(OrderedDict) + for datum in data: + d = datum.to_response(request=request) + key = "{} - {}".format(d["start_time"], d["end_time"]) + event_date = d["event_date"] + try: + schedule[event_date][key].append(d) + except KeyError: + schedule[event_date][key] = [d] + return Response(schedule) + + +def dummy_schedule(request, conference_slug): + data = render_to_string("dummy_schedule.json") + return HttpResponse(data, content_type="application/json") + + +def non_proposal_schedule_item_view(request, sch_item_id): + try: + sch_item = ScheduleItem.objects.get(pk=sch_item_id) + return render( + request, "proposals/detail/schedule-item.html", {"sch_item": sch_item} + ) + except ObjectDoesNotExist: + raise Http404() diff --git a/junction/static/bower.json b/junction/static/bower.json deleted file mode 100644 index f5d81222..00000000 --- a/junction/static/bower.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "junction", - "homepage": "https://github.com/pythonindia/junction", - "private": true, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "bootstrap": "~3.3.2", - "font-awesome": "~4.2.0" - } -} diff --git a/junction/static/css/app.css b/junction/static/css/app.css index d4127f71..d6504897 100644 --- a/junction/static/css/app.css +++ b/junction/static/css/app.css @@ -1,4 +1,9 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ html { font-family: sans-serif; -ms-text-size-adjust: 100%; @@ -88,7 +93,6 @@ figure { margin: 1em 40px; } hr { - -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } @@ -148,8 +152,6 @@ input[type="number"]::-webkit-outer-spin-button { } input[type="search"] { -webkit-appearance: textfield; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; box-sizing: content-box; } input[type="search"]::-webkit-search-cancel-button, @@ -228,9 +230,6 @@ th { h3 { page-break-after: avoid; } - select { - background: #fff !important; - } .navbar { display: none; } @@ -270,10 +269,10 @@ th { -moz-osx-font-smoothing: grayscale; } .glyphicon-asterisk:before { - content: "\2a"; + content: "\002a"; } .glyphicon-plus:before { - content: "\2b"; + content: "\002b"; } .glyphicon-euro:before, .glyphicon-eur:before { @@ -945,12 +944,24 @@ th { .glyphicon-bitcoin:before { content: "\e227"; } +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} .glyphicon-yen:before { content: "\00a5"; } +.glyphicon-jpy:before { + content: "\00a5"; +} .glyphicon-ruble:before { content: "\20bd"; } +.glyphicon-rub:before { + content: "\20bd"; +} .glyphicon-scale:before { content: "\e230"; } @@ -1064,7 +1075,7 @@ body { font-size: 14px; line-height: 1.42857143; color: #333333; - background-color: #ffffff; + background-color: #fff; } input, button, @@ -1075,7 +1086,7 @@ textarea { line-height: inherit; } a { - color: #2066c5; + color: #2066C5; text-decoration: none; } a:hover, @@ -1084,7 +1095,6 @@ a:focus { text-decoration: underline; } a:focus { - outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } @@ -1109,8 +1119,8 @@ img { .img-thumbnail { padding: 4px; line-height: 1.42857143; - background-color: #ffffff; - border: 1px solid #dddddd; + background-color: #fff; + border: 1px solid #ddd; border-radius: 4px; -webkit-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; @@ -1147,6 +1157,9 @@ hr { overflow: visible; clip: auto; } +[role="button"] { + cursor: pointer; +} h1, h2, h3, @@ -1310,67 +1323,77 @@ mark, text-transform: capitalize; } .text-muted { - color: #777777; + color: #5e5e5e; } .text-primary { - color: #f85441; + color: #e41f08; } -a.text-primary:hover { - color: #f7270f; +a.text-primary:hover, +a.text-primary:focus { + color: #b31806; } .text-success { color: #3c763d; } -a.text-success:hover { +a.text-success:hover, +a.text-success:focus { color: #2b542c; } .text-info { color: #31708f; } -a.text-info:hover { +a.text-info:hover, +a.text-info:focus { color: #245269; } .text-warning { color: #8a6d3b; } -a.text-warning:hover { +a.text-warning:hover, +a.text-warning:focus { color: #66512c; } .text-danger { color: #a94442; } -a.text-danger:hover { +a.text-danger:hover, +a.text-danger:focus { color: #843534; } .bg-primary { color: #fff; - background-color: #f85441; + background-color: #e41f08; } -a.bg-primary:hover { - background-color: #f7270f; +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #b31806; } .bg-success { background-color: #dff0d8; } -a.bg-success:hover { +a.bg-success:hover, +a.bg-success:focus { background-color: #c1e2b3; } .bg-info { background-color: #d9edf7; } -a.bg-info:hover { +a.bg-info:hover, +a.bg-info:focus { background-color: #afd9ee; } .bg-warning { background-color: #fcf8e3; } -a.bg-warning:hover { +a.bg-warning:hover, +a.bg-warning:focus { background-color: #f7ecb5; } .bg-danger { background-color: #f2dede; } -a.bg-danger:hover { +a.bg-danger:hover, +a.bg-danger:focus { background-color: #e4b9b9; } .page-header { @@ -1509,8 +1532,8 @@ code { kbd { padding: 2px 4px; font-size: 90%; - color: #ffffff; - background-color: #333333; + color: #fff; + background-color: #333; border-radius: 3px; box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); } @@ -1530,7 +1553,7 @@ pre { word-wrap: break-word; color: #333333; background-color: #f5f5f5; - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 4px; } pre code { @@ -2218,7 +2241,7 @@ table { caption { padding-top: 8px; padding-bottom: 8px; - color: #777777; + color: #5e5e5e; text-align: left; } th { @@ -2238,11 +2261,11 @@ th { padding: 8px; line-height: 1.42857143; vertical-align: top; - border-top: 1px solid #dddddd; + border-top: 1px solid #ddd; } .table > thead > tr > th { vertical-align: bottom; - border-bottom: 2px solid #dddddd; + border-bottom: 2px solid #ddd; } .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > th, @@ -2253,10 +2276,10 @@ th { border-top: 0; } .table > tbody + tbody { - border-top: 2px solid #dddddd; + border-top: 2px solid #ddd; } .table .table { - background-color: #ffffff; + background-color: #fff; } .table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, @@ -2267,7 +2290,7 @@ th { padding: 5px; } .table-bordered { - border: 1px solid #dddddd; + border: 1px solid #ddd; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, @@ -2275,7 +2298,7 @@ th { .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { - border: 1px solid #dddddd; + border: 1px solid #ddd; } .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { @@ -2413,7 +2436,7 @@ table th[class*="col-"] { margin-bottom: 15px; overflow-y: hidden; -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #dddddd; + border: 1px solid #ddd; } .table-responsive > .table { margin-bottom: 0; @@ -2500,7 +2523,6 @@ select[size] { input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { - outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } @@ -2519,9 +2541,9 @@ output { font-size: 14px; line-height: 1.42857143; color: #555555; - background-color: #ffffff; + background-color: #fff; background-image: none; - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); @@ -2536,22 +2558,29 @@ output { box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); } .form-control::-moz-placeholder { - color: #999999; + color: #999; opacity: 1; } .form-control:-ms-input-placeholder { - color: #999999; + color: #999; } .form-control::-webkit-input-placeholder { - color: #999999; + color: #999; +} +.form-control::-ms-expand { + border: 0; + background-color: transparent; } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - cursor: not-allowed; background-color: #eeeeee; opacity: 1; } +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} textarea.form-control { height: auto; } @@ -2559,10 +2588,10 @@ input[type="search"] { -webkit-appearance: none; } @media screen and (-webkit-min-device-pixel-ratio: 0) { - input[type="date"], - input[type="time"], - input[type="datetime-local"], - input[type="month"] { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { line-height: 34px; } input[type="date"].input-sm, @@ -2618,6 +2647,7 @@ input[type="search"] { } .radio-inline, .checkbox-inline { + position: relative; display: inline-block; padding-left: 20px; margin-bottom: 0; @@ -2654,6 +2684,7 @@ fieldset[disabled] .checkbox label { padding-top: 7px; padding-bottom: 7px; margin-bottom: 0; + min-height: 34px; } .form-control-static.input-lg, .form-control-static.input-sm { @@ -2682,17 +2713,18 @@ select[multiple].input-sm { line-height: 1.5; border-radius: 3px; } -select.form-group-sm .form-control { +.form-group-sm select.form-control { height: 30px; line-height: 30px; } -textarea.form-group-sm .form-control, -select[multiple].form-group-sm .form-control { +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { height: auto; } .form-group-sm .form-control-static { height: 30px; - padding: 5px 10px; + min-height: 32px; + padding: 6px 10px; font-size: 12px; line-height: 1.5; } @@ -2718,17 +2750,18 @@ select[multiple].input-lg { line-height: 1.3333333; border-radius: 6px; } -select.form-group-lg .form-control { +.form-group-lg select.form-control { height: 46px; line-height: 46px; } -textarea.form-group-lg .form-control, -select[multiple].form-group-lg .form-control { +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { height: auto; } .form-group-lg .form-control-static { height: 46px; - padding: 10px 16px; + min-height: 38px; + padding: 11px 16px; font-size: 18px; line-height: 1.3333333; } @@ -2750,12 +2783,16 @@ select[multiple].form-group-lg .form-control { text-align: center; pointer-events: none; } -.input-lg + .form-control-feedback { +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { width: 46px; height: 46px; line-height: 46px; } -.input-sm + .form-control-feedback { +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { width: 30px; height: 30px; line-height: 30px; @@ -2940,12 +2977,14 @@ select[multiple].form-group-lg .form-control { } @media (min-width: 768px) { .form-horizontal .form-group-lg .control-label { - padding-top: 14.333333px; + padding-top: 11px; + font-size: 18px; } } @media (min-width: 768px) { .form-horizontal .form-group-sm .control-label { padding-top: 6px; + font-size: 12px; } } .btn { @@ -2974,14 +3013,13 @@ select[multiple].form-group-lg .form-control { .btn.focus, .btn:active.focus, .btn.active.focus { - outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } .btn:hover, .btn:focus, .btn.focus { - color: #333333; + color: #333; text-decoration: none; } .btn:active, @@ -2995,35 +3033,56 @@ select[multiple].form-group-lg .form-control { .btn[disabled], fieldset[disabled] .btn { cursor: not-allowed; - pointer-events: none; opacity: 0.65; filter: alpha(opacity=65); -webkit-box-shadow: none; box-shadow: none; } +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} .btn-default { - color: #333333; - background-color: #ffffff; - border-color: #cccccc; + color: #333; + background-color: #fff; + border-color: #ccc; } -.btn-default:hover, .btn-default:focus, -.btn-default.focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { - color: #333333; + color: #333; background-color: #e6e6e6; border-color: #adadad; } +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { background-image: none; } -.btn-default.disabled, -.btn-default[disabled], -fieldset[disabled] .btn-default, .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, @@ -3032,43 +3091,55 @@ fieldset[disabled] .btn-default:hover, 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: #ffffff; - border-color: #cccccc; +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; } .btn-default .badge { - color: #ffffff; - background-color: #333333; + color: #fff; + background-color: #333; } .btn-primary { - color: #ffffff; - background-color: #f85441; - border-color: #f73d28; + color: #fff; + background-color: #e41f08; + border-color: #cc1b07; } -.btn-primary:hover, .btn-primary:focus, -.btn-primary.focus, +.btn-primary.focus { + color: #fff; + background-color: #b31806; + border-color: #510b03; +} +.btn-primary:hover { + color: #fff; + background-color: #b31806; + border-color: #911305; +} .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { - color: #ffffff; - background-color: #f7270f; - border-color: #db1d08; + color: #fff; + background-color: #b31806; + border-color: #911305; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #911305; + border-color: #510b03; } .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { background-image: none; } -.btn-primary.disabled, -.btn-primary[disabled], -fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, @@ -3077,43 +3148,55 @@ fieldset[disabled] .btn-primary:hover, 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: #f85441; - border-color: #f73d28; +fieldset[disabled] .btn-primary.focus { + background-color: #e41f08; + border-color: #cc1b07; } .btn-primary .badge { - color: #f85441; - background-color: #ffffff; + color: #e41f08; + background-color: #fff; } .btn-success { - color: #ffffff; - background-color: #5cb85c; - border-color: #4cae4c; + color: #fff; + background-color: #2d672d; + border-color: #255625; } -.btn-success:hover, .btn-success:focus, -.btn-success.focus, +.btn-success.focus { + color: #fff; + background-color: #1e441e; + border-color: #000000; +} +.btn-success:hover { + color: #fff; + background-color: #1e441e; + border-color: #132b13; +} .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { - color: #ffffff; - background-color: #449d44; - border-color: #398439; + color: #fff; + background-color: #1e441e; + border-color: #132b13; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #132b13; + border-color: #000000; } .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { background-image: none; } -.btn-success.disabled, -.btn-success[disabled], -fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, @@ -3122,43 +3205,55 @@ fieldset[disabled] .btn-success:hover, 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: #5cb85c; - border-color: #4cae4c; +fieldset[disabled] .btn-success.focus { + background-color: #2d672d; + border-color: #255625; } .btn-success .badge { - color: #5cb85c; - background-color: #ffffff; + color: #2d672d; + background-color: #fff; } .btn-info { - color: #ffffff; - background-color: #5bc0de; - border-color: #46b8da; + color: #fff; + background-color: #1f7e9a; + border-color: #1b6d85; } -.btn-info:hover, .btn-info:focus, -.btn-info.focus, +.btn-info.focus { + color: #fff; + background-color: #175b70; + border-color: #05161b; +} +.btn-info:hover { + color: #fff; + background-color: #175b70; + border-color: #114352; +} .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { - color: #ffffff; - background-color: #31b0d5; - border-color: #269abc; + color: #fff; + background-color: #175b70; + border-color: #114352; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #114352; + border-color: #05161b; } .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { background-image: none; } -.btn-info.disabled, -.btn-info[disabled], -fieldset[disabled] .btn-info, .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, @@ -3167,43 +3262,55 @@ fieldset[disabled] .btn-info:hover, 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: #5bc0de; - border-color: #46b8da; +fieldset[disabled] .btn-info.focus { + background-color: #1f7e9a; + border-color: #1b6d85; } .btn-info .badge { - color: #5bc0de; - background-color: #ffffff; + color: #1f7e9a; + background-color: #fff; } .btn-warning { - color: #ffffff; - background-color: #f0ad4e; - border-color: #eea236; + color: #fff; + background-color: #b06d0f; + border-color: #985f0d; } -.btn-warning:hover, .btn-warning:focus, -.btn-warning.focus, +.btn-warning.focus { + color: #fff; + background-color: #81500b; + border-color: #231503; +} +.btn-warning:hover { + color: #fff; + background-color: #81500b; + border-color: #603b08; +} .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { - color: #ffffff; - background-color: #ec971f; - border-color: #d58512; + color: #fff; + background-color: #81500b; + border-color: #603b08; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #603b08; + border-color: #231503; } .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { background-image: none; } -.btn-warning.disabled, -.btn-warning[disabled], -fieldset[disabled] .btn-warning, .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, @@ -3212,43 +3319,55 @@ fieldset[disabled] .btn-warning:hover, 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: #f0ad4e; - border-color: #eea236; +fieldset[disabled] .btn-warning.focus { + background-color: #b06d0f; + border-color: #985f0d; } .btn-warning .badge { - color: #f0ad4e; - background-color: #ffffff; + color: #b06d0f; + background-color: #fff; } .btn-danger { - color: #ffffff; - background-color: #d9534f; - border-color: #d43f3a; + color: #fff; + background-color: #8b211e; + border-color: #761c19; } -.btn-danger:hover, .btn-danger:focus, -.btn-danger.focus, +.btn-danger.focus { + color: #fff; + background-color: #611715; + border-color: #0d0303; +} +.btn-danger:hover { + color: #fff; + background-color: #611715; + border-color: #43100f; +} .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { - color: #ffffff; - background-color: #c9302c; - border-color: #ac2925; + color: #fff; + background-color: #611715; + border-color: #43100f; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #43100f; + border-color: #0d0303; } .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { background-image: none; } -.btn-danger.disabled, -.btn-danger[disabled], -fieldset[disabled] .btn-danger, .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, @@ -3257,22 +3376,16 @@ fieldset[disabled] .btn-danger:hover, 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: #d9534f; - border-color: #d43f3a; +fieldset[disabled] .btn-danger.focus { + background-color: #8b211e; + border-color: #761c19; } .btn-danger .badge { - color: #d9534f; - background-color: #ffffff; + color: #8b211e; + background-color: #fff; } .btn-link { - color: #2066c5; + color: #2066C5; font-weight: normal; border-radius: 0; } @@ -3348,11 +3461,9 @@ input[type="button"].btn-block { } .collapse { display: none; - visibility: hidden; } .collapse.in { display: block; - visibility: visible; } tr.collapse.in { display: table-row; @@ -3377,7 +3488,8 @@ tbody.collapse.in { height: 0; margin-left: 2px; vertical-align: middle; - border-top: 4px solid; + border-top: 4px dashed; + border-top: 4px solid \9; border-right: 4px solid transparent; border-left: 4px solid transparent; } @@ -3401,8 +3513,8 @@ tbody.collapse.in { list-style: none; font-size: 14px; text-align: left; - background-color: #ffffff; - border: 1px solid #cccccc; + background-color: #fff; + border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 4px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); @@ -3437,10 +3549,10 @@ tbody.collapse.in { .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { - color: #ffffff; + color: #fff; text-decoration: none; outline: 0; - background-color: #f85441; + background-color: #e41f08; } .dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:hover, @@ -3492,7 +3604,8 @@ tbody.collapse.in { .dropup .caret, .navbar-fixed-bottom .dropdown .caret { border-top: 0; - border-bottom: 4px solid; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; content: ""; } .dropup .dropdown-menu, @@ -3541,6 +3654,7 @@ tbody.collapse.in { .btn-toolbar { margin-left: -5px; } +.btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group { float: left; @@ -3633,13 +3747,15 @@ tbody.collapse.in { } .btn-group-vertical > .btn:first-child:not(:last-child) { border-top-right-radius: 4px; + border-top-left-radius: 4px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:last-child:not(:first-child) { - border-bottom-left-radius: 4px; border-top-right-radius: 0; border-top-left-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; @@ -3696,6 +3812,9 @@ tbody.collapse.in { width: 100%; margin-bottom: 0; } +.input-group .form-control:focus { + z-index: 3; +} .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { @@ -3766,7 +3885,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { color: #555555; text-align: center; background-color: #eeeeee; - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 4px; } .input-group-addon.input-sm { @@ -3831,6 +3950,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group { + z-index: 2; margin-left: -1px; } .nav { @@ -3866,7 +3986,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .nav .open > a:hover, .nav .open > a:focus { background-color: #eeeeee; - border-color: #2066c5; + border-color: #2066C5; } .nav .nav-divider { height: 1px; @@ -3878,7 +3998,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { max-width: none; } .nav-tabs { - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid #ddd; } .nav-tabs > li { float: left; @@ -3891,14 +4011,14 @@ select[multiple].input-group-sm > .input-group-btn > .btn { border-radius: 4px 4px 0 0; } .nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #dddddd; + border-color: #eeeeee #eeeeee #ddd; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { color: #555555; - background-color: #ffffff; - border: 1px solid #dddddd; + background-color: #fff; + border: 1px solid #ddd; border-bottom-color: transparent; cursor: default; } @@ -3933,17 +4053,17 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #dddddd; + border: 1px solid #ddd; } @media (min-width: 768px) { .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid #ddd; border-radius: 4px 4px 0 0; } .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #ffffff; + border-bottom-color: #fff; } } .nav-pills > li { @@ -3958,8 +4078,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - color: #ffffff; - background-color: #f85441; + color: #fff; + background-color: #e41f08; } .nav-stacked > li { float: none; @@ -4001,26 +4121,24 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { - border: 1px solid #dddddd; + border: 1px solid #ddd; } @media (min-width: 768px) { .nav-tabs-justified > li > a { - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid #ddd; border-radius: 4px 4px 0 0; } .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { - border-bottom-color: #ffffff; + border-bottom-color: #fff; } } .tab-content > .tab-pane { display: none; - visibility: hidden; } .tab-content > .active { display: block; - visibility: visible; } .nav-tabs .dropdown-menu { margin-top: -1px; @@ -4062,7 +4180,6 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-collapse.collapse { display: block !important; - visibility: visible !important; height: auto !important; padding-bottom: 0; overflow: visible !important; @@ -4358,89 +4475,89 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-default { background-color: rgba(0, 0, 0, 0.8); - border-color: #f73721; + border-color: #c41a07; } .navbar-default .navbar-brand { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { - color: #fff7f8; + color: #FFF7F8; background-color: transparent; } .navbar-default .navbar-text { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .navbar-nav > li > a { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { color: #ffdee2; - background-color: #f73721; + background-color: #c41a07; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { color: #ffdee2; - background-color: #f73721; + background-color: #c41a07; } .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, .navbar-default .navbar-nav > .disabled > a:focus { - color: #cccccc; + color: #ccc; background-color: transparent; } .navbar-default .navbar-toggle { - border-color: #dddddd; + border-color: #ddd; } .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { - background-color: #dddddd; + background-color: #ddd; } .navbar-default .navbar-toggle .icon-bar { - background-color: #888888; + background-color: #888; } .navbar-default .navbar-collapse, .navbar-default .navbar-form { - border-color: #f73721; + border-color: #c41a07; } .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { - background-color: #f73721; + background-color: #c41a07; color: #ffdee2; } @media (max-width: 767px) { .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { color: #ffdee2; - background-color: #f73721; + background-color: #c41a07; } .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { color: #ffdee2; - background-color: #f73721; + background-color: #c41a07; } .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #cccccc; + color: #ccc; background-color: transparent; } } .navbar-default .navbar-link { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .navbar-link:hover { color: #ffdee2; } .navbar-default .btn-link { - color: #fff7f8; + color: #FFF7F8; } .navbar-default .btn-link:hover, .navbar-default .btn-link:focus { @@ -4450,10 +4567,10 @@ select[multiple].input-group-sm > .input-group-btn > .btn { fieldset[disabled] .navbar-default .btn-link:hover, .navbar-default .btn-link[disabled]:focus, fieldset[disabled] .navbar-default .btn-link:focus { - color: #cccccc; + color: #ccc; } .navbar-inverse { - background-color: #222222; + background-color: #222; border-color: #080808; } .navbar-inverse .navbar-brand { @@ -4461,7 +4578,7 @@ fieldset[disabled] .navbar-default .btn-link:focus { } .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { - color: #ffffff; + color: #fff; background-color: transparent; } .navbar-inverse .navbar-text { @@ -4472,30 +4589,30 @@ fieldset[disabled] .navbar-default .btn-link:focus { } .navbar-inverse .navbar-nav > li > a:hover, .navbar-inverse .navbar-nav > li > a:focus { - color: #ffffff; + color: #fff; background-color: transparent; } .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { - color: #ffffff; + color: #fff; background-color: #080808; } .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:hover, .navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444444; + color: #444; background-color: transparent; } .navbar-inverse .navbar-toggle { - border-color: #333333; + border-color: #333; } .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { - background-color: #333333; + background-color: #333; } .navbar-inverse .navbar-toggle .icon-bar { - background-color: #ffffff; + background-color: #fff; } .navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { @@ -4505,7 +4622,7 @@ fieldset[disabled] .navbar-default .btn-link:focus { .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { background-color: #080808; - color: #ffffff; + color: #fff; } @media (max-width: 767px) { .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { @@ -4519,19 +4636,19 @@ fieldset[disabled] .navbar-default .btn-link:focus { } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #ffffff; + color: #fff; background-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #ffffff; + color: #fff; background-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444444; + color: #444; background-color: transparent; } } @@ -4539,20 +4656,20 @@ fieldset[disabled] .navbar-default .btn-link:focus { color: #9d9d9d; } .navbar-inverse .navbar-link:hover { - color: #ffffff; + color: #fff; } .navbar-inverse .btn-link { color: #9d9d9d; } .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link:focus { - color: #ffffff; + color: #fff; } .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link[disabled]:focus, fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444444; + color: #444; } .breadcrumb { padding: 8px 15px; @@ -4567,10 +4684,10 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .breadcrumb > li + li:before { content: "/\00a0"; padding: 0 5px; - color: #cccccc; + color: #ccc; } .breadcrumb > .active { - color: #777777; + color: #4F4F4F; } .pagination { display: inline-block; @@ -4588,9 +4705,9 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { padding: 6px 12px; line-height: 1.42857143; text-decoration: none; - color: #2066c5; - background-color: #ffffff; - border: 1px solid #dddddd; + color: #2066C5; + background-color: #fff; + border: 1px solid #ddd; margin-left: -1px; } .pagination > li:first-child > a, @@ -4608,9 +4725,10 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { + z-index: 2; color: #154483; background-color: #eeeeee; - border-color: #dddddd; + border-color: #ddd; } .pagination > .active > a, .pagination > .active > span, @@ -4618,10 +4736,10 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pagination > .active > span:hover, .pagination > .active > a:focus, .pagination > .active > span:focus { - z-index: 2; - color: #ffffff; - background-color: #f85441; - border-color: #f85441; + z-index: 3; + color: #fff; + background-color: #e41f08; + border-color: #e41f08; cursor: default; } .pagination > .disabled > span, @@ -4631,14 +4749,15 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { color: #777777; - background-color: #ffffff; - border-color: #dddddd; + background-color: #fff; + border-color: #ddd; cursor: not-allowed; } .pagination-lg > li > a, .pagination-lg > li > span { padding: 10px 16px; font-size: 18px; + line-height: 1.3333333; } .pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { @@ -4654,6 +4773,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pagination-sm > li > span { padding: 5px 10px; font-size: 12px; + line-height: 1.5; } .pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { @@ -4678,8 +4798,8 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pager li > span { display: inline-block; padding: 5px 14px; - background-color: #ffffff; - border: 1px solid #dddddd; + background-color: #fff; + border: 1px solid #ddd; border-radius: 15px; } .pager li > a:hover, @@ -4700,7 +4820,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { .pager .disabled > a:focus, .pager .disabled > span { color: #777777; - background-color: #ffffff; + background-color: #fff; cursor: not-allowed; } .label { @@ -4709,7 +4829,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { font-size: 75%; font-weight: bold; line-height: 1; - color: #ffffff; + color: #fff; text-align: center; white-space: nowrap; vertical-align: baseline; @@ -4717,7 +4837,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus { } a.label:hover, a.label:focus { - color: #ffffff; + color: #fff; text-decoration: none; cursor: pointer; } @@ -4729,46 +4849,46 @@ a.label:focus { top: -1px; } .label-default { - background-color: #919191; + background-color: #6a6a6a; } .label-default[href]:hover, .label-default[href]:focus { - background-color: #777777; + background-color: #515151; } .label-primary { - background-color: #f85441; + background-color: #e41f08; } .label-primary[href]:hover, .label-primary[href]:focus { - background-color: #f7270f; + background-color: #b31806; } .label-success { - background-color: #5cb85c; + background-color: #2d672d; } .label-success[href]:hover, .label-success[href]:focus { - background-color: #449d44; + background-color: #1e441e; } .label-info { - background-color: #5bc0de; + background-color: #1f7e9a; } .label-info[href]:hover, .label-info[href]:focus { - background-color: #31b0d5; + background-color: #175b70; } .label-warning { - background-color: #f0ad4e; + background-color: #b06d0f; } .label-warning[href]:hover, .label-warning[href]:focus { - background-color: #ec971f; + background-color: #81500b; } .label-danger { - background-color: #d9534f; + background-color: #8b211e; } .label-danger[href]:hover, .label-danger[href]:focus { - background-color: #c9302c; + background-color: #611715; } .badge { display: inline-block; @@ -4776,9 +4896,9 @@ a.label:focus { padding: 3px 7px; font-size: 12px; font-weight: bold; - color: #ffffff; + color: #fff; line-height: 1; - vertical-align: baseline; + vertical-align: middle; white-space: nowrap; text-align: center; background-color: #777777; @@ -4791,20 +4911,21 @@ a.label:focus { position: relative; top: -1px; } -.btn-xs .badge { +.btn-xs .badge, +.btn-group-xs > .btn .badge { top: 0; padding: 1px 5px; } a.badge:hover, a.badge:focus { - color: #ffffff; + color: #fff; text-decoration: none; cursor: pointer; } .list-group-item.active > .badge, .nav-pills > .active > a > .badge { - color: #2066c5; - background-color: #ffffff; + color: #2066C5; + background-color: #fff; } .list-group-item > .badge { float: right; @@ -4816,7 +4937,8 @@ a.badge:focus { margin-left: 3px; } .jumbotron { - padding: 30px 15px; + padding-top: 30px; + padding-bottom: 30px; margin-bottom: 30px; color: inherit; background-color: #eeeeee; @@ -4836,13 +4958,16 @@ a.badge:focus { .container .jumbotron, .container-fluid .jumbotron { border-radius: 6px; + padding-left: 15px; + padding-right: 15px; } .jumbotron .container { max-width: 100%; } @media screen and (min-width: 768px) { .jumbotron { - padding: 48px 0; + padding-top: 48px; + padding-bottom: 48px; } .container .jumbotron, .container-fluid .jumbotron { @@ -4859,8 +4984,8 @@ a.badge:focus { padding: 4px; margin-bottom: 20px; line-height: 1.42857143; - background-color: #ffffff; - border: 1px solid #dddddd; + background-color: #fff; + border: 1px solid #ddd; border-radius: 4px; -webkit-transition: border 0.2s ease-in-out; -o-transition: border 0.2s ease-in-out; @@ -4874,7 +4999,7 @@ a.badge:focus { a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active { - border-color: #2066c5; + border-color: #2066C5; } .thumbnail .caption { padding: 9px; @@ -4986,9 +5111,9 @@ a.thumbnail.active { height: 100%; font-size: 12px; line-height: 20px; - color: #ffffff; + color: #fff; text-align: center; - background-color: #f85441; + background-color: #e41f08; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); -webkit-transition: width 0.6s ease; @@ -5009,7 +5134,7 @@ a.thumbnail.active { animation: progress-bar-stripes 2s linear infinite; } .progress-bar-success { - background-color: #5cb85c; + background-color: #2d672d; } .progress-striped .progress-bar-success { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); @@ -5017,7 +5142,7 @@ a.thumbnail.active { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-info { - background-color: #5bc0de; + background-color: #1f7e9a; } .progress-striped .progress-bar-info { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); @@ -5025,7 +5150,7 @@ a.thumbnail.active { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-warning { - background-color: #f0ad4e; + background-color: #b06d0f; } .progress-striped .progress-bar-warning { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); @@ -5033,7 +5158,7 @@ a.thumbnail.active { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-danger { - background-color: #d9534f; + background-color: #8b211e; } .progress-striped .progress-bar-danger { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); @@ -5057,6 +5182,9 @@ a.thumbnail.active { .media-object { display: block; } +.media-object.img-thumbnail { + max-width: none; +} .media-right, .media > .pull-right { padding-left: 10px; @@ -5094,8 +5222,8 @@ a.thumbnail.active { display: block; padding: 10px 15px; margin-bottom: -1px; - background-color: #ffffff; - border: 1px solid #dddddd; + background-color: #fff; + border: 1px solid #ddd; } .list-group-item:first-child { border-top-right-radius: 4px; @@ -5106,18 +5234,26 @@ a.thumbnail.active { border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } -a.list-group-item { - color: #555555; +a.list-group-item, +button.list-group-item { + color: #555; } -a.list-group-item .list-group-item-heading { - color: #333333; +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; } a.list-group-item:hover, -a.list-group-item:focus { +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { text-decoration: none; - color: #555555; + color: #555; background-color: #f5f5f5; } +button.list-group-item { + width: 100%; + text-align: left; +} .list-group-item.disabled, .list-group-item.disabled:hover, .list-group-item.disabled:focus { @@ -5139,9 +5275,9 @@ a.list-group-item:focus { .list-group-item.active:hover, .list-group-item.active:focus { z-index: 2; - color: #ffffff; - background-color: #f85441; - border-color: #f85441; + color: #fff; + background-color: #e41f08; + border-color: #e41f08; } .list-group-item.active .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading, @@ -5157,26 +5293,33 @@ a.list-group-item:focus { .list-group-item.active .list-group-item-text, .list-group-item.active:hover .list-group-item-text, .list-group-item.active:focus .list-group-item-text { - color: #ffffff; + color: #fdc3bc; } .list-group-item-success { color: #3c763d; background-color: #dff0d8; } -a.list-group-item-success { +a.list-group-item-success, +button.list-group-item-success { color: #3c763d; } -a.list-group-item-success .list-group-item-heading { +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { color: inherit; } a.list-group-item-success:hover, -a.list-group-item-success:focus { +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { color: #3c763d; background-color: #d0e9c6; } a.list-group-item-success.active, +button.list-group-item-success.active, a.list-group-item-success.active:hover, -a.list-group-item-success.active:focus { +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { color: #fff; background-color: #3c763d; border-color: #3c763d; @@ -5185,20 +5328,27 @@ a.list-group-item-success.active:focus { color: #31708f; background-color: #d9edf7; } -a.list-group-item-info { +a.list-group-item-info, +button.list-group-item-info { color: #31708f; } -a.list-group-item-info .list-group-item-heading { +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { color: inherit; } a.list-group-item-info:hover, -a.list-group-item-info:focus { +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { color: #31708f; background-color: #c4e3f3; } a.list-group-item-info.active, +button.list-group-item-info.active, a.list-group-item-info.active:hover, -a.list-group-item-info.active:focus { +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { color: #fff; background-color: #31708f; border-color: #31708f; @@ -5207,20 +5357,27 @@ a.list-group-item-info.active:focus { color: #8a6d3b; background-color: #fcf8e3; } -a.list-group-item-warning { +a.list-group-item-warning, +button.list-group-item-warning { color: #8a6d3b; } -a.list-group-item-warning .list-group-item-heading { +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { color: inherit; } a.list-group-item-warning:hover, -a.list-group-item-warning:focus { +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { color: #8a6d3b; background-color: #faf2cc; } a.list-group-item-warning.active, +button.list-group-item-warning.active, a.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus { +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { color: #fff; background-color: #8a6d3b; border-color: #8a6d3b; @@ -5229,20 +5386,27 @@ a.list-group-item-warning.active:focus { color: #a94442; background-color: #f2dede; } -a.list-group-item-danger { +a.list-group-item-danger, +button.list-group-item-danger { color: #a94442; } -a.list-group-item-danger .list-group-item-heading { +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { color: inherit; } a.list-group-item-danger:hover, -a.list-group-item-danger:focus { +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { color: #a94442; background-color: #ebcccc; } a.list-group-item-danger.active, +button.list-group-item-danger.active, a.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus { +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { color: #fff; background-color: #a94442; border-color: #a94442; @@ -5257,7 +5421,7 @@ a.list-group-item-danger.active:focus { } .panel { margin-bottom: 20px; - background-color: #ffffff; + background-color: #fff; border: 1px solid transparent; border-radius: 4px; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); @@ -5291,7 +5455,7 @@ a.list-group-item-danger.active:focus { .panel-footer { padding: 10px 15px; background-color: #f5f5f5; - border-top: 1px solid #dddddd; + border-top: 1px solid #ddd; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } @@ -5316,6 +5480,10 @@ a.list-group-item-danger.active:focus { border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} .panel-heading + .list-group .list-group-item:first-child { border-top-width: 0; } @@ -5401,7 +5569,7 @@ a.list-group-item-danger.active:focus { .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body { - border-top: 1px solid #dddddd; + border-top: 1px solid #ddd; } .panel > .table > tbody:first-child > tr:first-child th, .panel > .table > tbody:first-child > tr:first-child td { @@ -5478,49 +5646,49 @@ a.list-group-item-danger.active:focus { } .panel-group .panel-heading + .panel-collapse > .panel-body, .panel-group .panel-heading + .panel-collapse > .list-group { - border-top: 1px solid #dddddd; + border-top: 1px solid #ddd; } .panel-group .panel-footer { border-top: 0; } .panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #dddddd; + border-bottom: 1px solid #ddd; } .panel-default { - border-color: #dddddd; + border-color: #ddd; } .panel-default > .panel-heading { color: #333333; background-color: #f5f5f5; - border-color: #dddddd; + border-color: #ddd; } .panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #dddddd; + border-top-color: #ddd; } .panel-default > .panel-heading .badge { color: #f5f5f5; background-color: #333333; } .panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #dddddd; + border-bottom-color: #ddd; } .panel-primary { - border-color: #f85441; + border-color: #e41f08; } .panel-primary > .panel-heading { - color: #ffffff; - background-color: #f85441; - border-color: #f85441; + color: #fff; + background-color: #e41f08; + border-color: #e41f08; } .panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #f85441; + border-top-color: #e41f08; } .panel-primary > .panel-heading .badge { - color: #f85441; - background-color: #ffffff; + color: #e41f08; + background-color: #fff; } .panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #f85441; + border-bottom-color: #e41f08; } .panel-success { border-color: #d6e9c6; @@ -5614,10 +5782,10 @@ a.list-group-item-danger.active:focus { width: 100%; border: 0; } -.embed-responsive.embed-responsive-16by9 { +.embed-responsive-16by9 { padding-bottom: 56.25%; } -.embed-responsive.embed-responsive-4by3 { +.embed-responsive-4by3 { padding-bottom: 75%; } .well { @@ -5647,14 +5815,14 @@ a.list-group-item-danger.active:focus { font-size: 21px; font-weight: bold; line-height: 1; - color: #000000; - text-shadow: 0 1px 0 #ffffff; + color: #000; + text-shadow: 0 1px 0 #fff; opacity: 0.2; filter: alpha(opacity=20); } .close:hover, .close:focus { - color: #000000; + color: #000; text-decoration: none; cursor: pointer; opacity: 0.5; @@ -5678,7 +5846,7 @@ button.close { right: 0; bottom: 0; left: 0; - z-index: 1040; + z-index: 1050; -webkit-overflow-scrolling: touch; outline: 0; } @@ -5709,8 +5877,8 @@ button.close { } .modal-content { position: relative; - background-color: #ffffff; - border: 1px solid #999999; + background-color: #fff; + border: 1px solid #999; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); @@ -5719,11 +5887,13 @@ button.close { outline: 0; } .modal-backdrop { - position: absolute; + position: fixed; top: 0; right: 0; + bottom: 0; left: 0; - background-color: #000000; + z-index: 1040; + background-color: #000; } .modal-backdrop.fade { opacity: 0; @@ -5736,7 +5906,6 @@ button.close { .modal-header { padding: 15px; border-bottom: 1px solid #e5e5e5; - min-height: 16.42857143px; } .modal-header .close { margin-top: -2px; @@ -5793,11 +5962,22 @@ button.close { position: absolute; z-index: 1070; display: block; - visibility: visible; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 12px; + font-style: normal; font-weight: normal; - line-height: 1.4; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 12px; opacity: 0; filter: alpha(opacity=0); } @@ -5824,10 +6004,9 @@ button.close { .tooltip-inner { max-width: 200px; padding: 3px 8px; - color: #ffffff; + color: #fff; text-align: center; - text-decoration: none; - background-color: #000000; + background-color: #000; border-radius: 4px; } .tooltip-arrow { @@ -5842,56 +6021,56 @@ button.close { left: 50%; margin-left: -5px; border-width: 5px 5px 0; - border-top-color: #000000; + border-top-color: #000; } .tooltip.top-left .tooltip-arrow { bottom: 0; right: 5px; margin-bottom: -5px; border-width: 5px 5px 0; - border-top-color: #000000; + border-top-color: #000; } .tooltip.top-right .tooltip-arrow { bottom: 0; left: 5px; margin-bottom: -5px; border-width: 5px 5px 0; - border-top-color: #000000; + border-top-color: #000; } .tooltip.right .tooltip-arrow { top: 50%; left: 0; margin-top: -5px; border-width: 5px 5px 5px 0; - border-right-color: #000000; + border-right-color: #000; } .tooltip.left .tooltip-arrow { top: 50%; right: 0; margin-top: -5px; border-width: 5px 0 5px 5px; - border-left-color: #000000; + border-left-color: #000; } .tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; - border-bottom-color: #000000; + border-bottom-color: #000; } .tooltip.bottom-left .tooltip-arrow { top: 0; right: 5px; margin-top: -5px; border-width: 0 5px 5px; - border-bottom-color: #000000; + border-bottom-color: #000; } .tooltip.bottom-right .tooltip-arrow { top: 0; left: 5px; margin-top: -5px; border-width: 0 5px 5px; - border-bottom-color: #000000; + border-bottom-color: #000; } .popover { position: absolute; @@ -5902,18 +6081,28 @@ button.close { max-width: 276px; padding: 1px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; + font-style: normal; font-weight: normal; + letter-spacing: normal; + line-break: auto; line-height: 1.42857143; text-align: left; - background-color: #ffffff; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 14px; + background-color: #fff; background-clip: padding-box; - border: 1px solid #cccccc; + border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 6px; -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - white-space: normal; } .popover.top { margin-top: -10px; @@ -5967,7 +6156,7 @@ button.close { bottom: 1px; margin-left: -10px; border-bottom-width: 0; - border-top-color: #ffffff; + border-top-color: #fff; } .popover.right > .arrow { top: 50%; @@ -5982,7 +6171,7 @@ button.close { left: 1px; bottom: -10px; border-left-width: 0; - border-right-color: #ffffff; + border-right-color: #fff; } .popover.bottom > .arrow { left: 50%; @@ -5997,7 +6186,7 @@ button.close { top: 1px; margin-left: -10px; border-top-width: 0; - border-bottom-color: #ffffff; + border-bottom-color: #fff; } .popover.left > .arrow { top: 50%; @@ -6011,7 +6200,7 @@ button.close { content: " "; right: 1px; border-right-width: 0; - border-left-color: #ffffff; + border-left-color: #fff; bottom: -10px; } .carousel { @@ -6042,9 +6231,9 @@ button.close { -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; backface-visibility: hidden; - -webkit-perspective: 1000; - -moz-perspective: 1000; - perspective: 1000; + -webkit-perspective: 1000px; + -moz-perspective: 1000px; + perspective: 1000px; } .carousel-inner > .item.next, .carousel-inner > .item.active.right { @@ -6105,9 +6294,10 @@ button.close { opacity: 0.5; filter: alpha(opacity=50); font-size: 20px; - color: #ffffff; + color: #fff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + background-color: rgba(0, 0, 0, 0); } .carousel-control.left { background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); @@ -6128,7 +6318,7 @@ button.close { .carousel-control:hover, .carousel-control:focus { outline: 0; - color: #ffffff; + color: #fff; text-decoration: none; opacity: 0.9; filter: alpha(opacity=90); @@ -6139,6 +6329,7 @@ button.close { .carousel-control .glyphicon-chevron-right { position: absolute; top: 50%; + margin-top: -10px; z-index: 5; display: inline-block; } @@ -6156,7 +6347,6 @@ button.close { .carousel-control .icon-next { width: 20px; height: 20px; - margin-top: -10px; line-height: 1; font-family: serif; } @@ -6183,7 +6373,7 @@ button.close { height: 10px; margin: 1px; text-indent: -999px; - border: 1px solid #ffffff; + border: 1px solid #fff; border-radius: 10px; cursor: pointer; background-color: #000 \9; @@ -6193,7 +6383,7 @@ button.close { margin: 0; width: 12px; height: 12px; - background-color: #ffffff; + background-color: #fff; } .carousel-caption { position: absolute; @@ -6203,7 +6393,7 @@ button.close { z-index: 10; padding-top: 20px; padding-bottom: 20px; - color: #ffffff; + color: #fff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } @@ -6217,16 +6407,16 @@ button.close { .carousel-control .icon-next { width: 30px; height: 30px; - margin-top: -15px; + margin-top: -10px; font-size: 30px; } .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { - margin-left: -15px; + margin-left: -10px; } .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { - margin-right: -15px; + margin-right: -10px; } .carousel-caption { left: 20%; @@ -6265,6 +6455,8 @@ button.close { .pager:after, .panel-body:before, .panel-body:after, +.modal-header:before, +.modal-header:after, .modal-footer:before, .modal-footer:after { content: " "; @@ -6284,6 +6476,7 @@ button.close { .navbar-collapse:after, .pager:after, .panel-body:after, +.modal-header:after, .modal-footer:after { clear: both; } @@ -6316,7 +6509,6 @@ button.close { } .hidden { display: none !important; - visibility: hidden !important; } .affix { position: fixed; @@ -6349,7 +6541,7 @@ button.close { display: block !important; } table.visible-xs { - display: table; + display: table !important; } tr.visible-xs { display: table-row !important; @@ -6379,7 +6571,7 @@ button.close { display: block !important; } table.visible-sm { - display: table; + display: table !important; } tr.visible-sm { display: table-row !important; @@ -6409,7 +6601,7 @@ button.close { display: block !important; } table.visible-md { - display: table; + display: table !important; } tr.visible-md { display: table-row !important; @@ -6439,7 +6631,7 @@ button.close { display: block !important; } table.visible-lg { - display: table; + display: table !important; } tr.visible-lg { display: table-row !important; @@ -6492,7 +6684,7 @@ button.close { display: block !important; } table.visible-print { - display: table; + display: table !important; } tr.visible-print { display: table-row !important; @@ -6594,7 +6786,7 @@ button.close { } .fa-border { padding: .2em .25em .15em; - border: solid 0.08em #eeeeee; + border: solid 0.08em #eee; border-radius: .1em; } .pull-right { @@ -6692,7 +6884,7 @@ button.close { font-size: 2em; } .fa-inverse { - color: #ffffff; + color: #fff; } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ @@ -8213,6 +8405,13 @@ button.close { letter-spacing: 2px; font-weight: bold; } +.navbar { + margin-bottom: 0; + border-bottom: 0; +} +.navbar-default { + border-color: transparent; +} a.tag { color: #fff; } @@ -8225,9 +8424,65 @@ input#id_remember { cursor: pointer; } .btn-vote.active { - color: #f85441; + color: #e41f08; +} +.btn-vote.active:not(.can-remove-vote) { cursor: default; } +.status { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + margin-left: 5px; +} +.status-2 { + background-color: #6a6a6a; +} +.status-2[href]:hover, +.status-2[href]:focus { + background-color: #515151; +} +.status-3, +.status-1 { + background-color: #2d672d; +} +.status-3[href]:hover, +.status-1[href]:hover, +.status-3[href]:focus, +.status-1[href]:focus { + background-color: #1e441e; +} +.status-4 { + background-color: #1f7e9a; +} +.status-4[href]:hover, +.status-4[href]:focus { + background-color: #175b70; +} +.breadcrumb--fixed .container-fluid { + margin-top: 27px; +} +.breadcrumb--fixed .breadcrumb { + position: fixed; + left: 0; + right: 0; + top: 0; + z-index: 90000; + border-radius: 0; +} +.green { + color: green; +} +.spam { + background-color: #b3a6a6; +} .align-bottom { vertical-align: bottom; } @@ -8238,7 +8493,7 @@ input#id_remember { display: inline-block !important; } .color-secondary { - color: #fff7f8; + color: #FFF7F8; } .transparent-black { background: rgba(0, 0, 0, 0.5); @@ -8288,9 +8543,17 @@ input#id_remember { background-color: #4585f3; border-color: #4585f3; } -.btn-google:hover, .btn-google:focus, -.btn-google.focus, +.btn-google.focus { + color: white; + background-color: #1566f0; + border-color: #0b47ad; +} +.btn-google:hover { + color: white; + background-color: #1566f0; + border-color: #0f60ec; +} .btn-google:active, .btn-google.active, .open > .dropdown-toggle.btn-google { @@ -8298,14 +8561,24 @@ input#id_remember { background-color: #1566f0; border-color: #0f60ec; } +.btn-google:active:hover, +.btn-google.active:hover, +.open > .dropdown-toggle.btn-google:hover, +.btn-google:active:focus, +.btn-google.active:focus, +.open > .dropdown-toggle.btn-google:focus, +.btn-google:active.focus, +.btn-google.active.focus, +.open > .dropdown-toggle.btn-google.focus { + color: white; + background-color: #0e56d4; + border-color: #0b47ad; +} .btn-google:active, .btn-google.active, .open > .dropdown-toggle.btn-google { background-image: none; } -.btn-google.disabled, -.btn-google[disabled], -fieldset[disabled] .btn-google, .btn-google.disabled:hover, .btn-google[disabled]:hover, fieldset[disabled] .btn-google:hover, @@ -8314,13 +8587,7 @@ fieldset[disabled] .btn-google:hover, fieldset[disabled] .btn-google:focus, .btn-google.disabled.focus, .btn-google[disabled].focus, -fieldset[disabled] .btn-google.focus, -.btn-google.disabled:active, -.btn-google[disabled]:active, -fieldset[disabled] .btn-google:active, -.btn-google.disabled.active, -.btn-google[disabled].active, -fieldset[disabled] .btn-google.active { +fieldset[disabled] .btn-google.focus { background-color: #4585f3; border-color: #4585f3; } @@ -8330,12 +8597,20 @@ fieldset[disabled] .btn-google.active { } .btn-github { color: #454545; - background-color: #eeeeee; + background-color: #eee; border-color: #bbbbbb; } -.btn-github:hover, .btn-github:focus, -.btn-github.focus, +.btn-github.focus { + color: #454545; + background-color: #d5d5d5; + border-color: #7b7b7b; +} +.btn-github:hover { + color: #454545; + background-color: #d5d5d5; + border-color: #9c9c9c; +} .btn-github:active, .btn-github.active, .open > .dropdown-toggle.btn-github { @@ -8343,14 +8618,24 @@ fieldset[disabled] .btn-google.active { background-color: #d5d5d5; border-color: #9c9c9c; } +.btn-github:active:hover, +.btn-github.active:hover, +.open > .dropdown-toggle.btn-github:hover, +.btn-github:active:focus, +.btn-github.active:focus, +.open > .dropdown-toggle.btn-github:focus, +.btn-github:active.focus, +.btn-github.active.focus, +.open > .dropdown-toggle.btn-github.focus { + color: #454545; + background-color: #c3c3c3; + border-color: #7b7b7b; +} .btn-github:active, .btn-github.active, .open > .dropdown-toggle.btn-github { background-image: none; } -.btn-github.disabled, -.btn-github[disabled], -fieldset[disabled] .btn-github, .btn-github.disabled:hover, .btn-github[disabled]:hover, fieldset[disabled] .btn-github:hover, @@ -8359,18 +8644,12 @@ fieldset[disabled] .btn-github:hover, fieldset[disabled] .btn-github:focus, .btn-github.disabled.focus, .btn-github[disabled].focus, -fieldset[disabled] .btn-github.focus, -.btn-github.disabled:active, -.btn-github[disabled]:active, -fieldset[disabled] .btn-github:active, -.btn-github.disabled.active, -.btn-github[disabled].active, -fieldset[disabled] .btn-github.active { - background-color: #eeeeee; +fieldset[disabled] .btn-github.focus { + background-color: #eee; border-color: #bbbbbb; } .btn-github .badge { - color: #eeeeee; + color: #eee; background-color: #454545; } .hr-mini { @@ -8401,7 +8680,7 @@ body { } footer { padding: 20px 0; - background-color: #fff7f8; + background-color: #FFF7F8; border-top: 1px solid #ffdee2; border-bottom: 1px solid #ffdee2; } @@ -8421,9 +8700,9 @@ footer { font-size: 14px; line-height: 1.42857143; color: #555555; - background-color: #ffffff; + background-color: #fff; background-image: none; - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); @@ -8442,18 +8721,24 @@ footer { .form-container input[type=text]::-moz-placeholder, .form-container input[type=email]::-moz-placeholder, .form-container input[type=password]::-moz-placeholder { - color: #999999; + color: #999; opacity: 1; } .form-container input[type=text]:-ms-input-placeholder, .form-container input[type=email]:-ms-input-placeholder, .form-container input[type=password]:-ms-input-placeholder { - color: #999999; + color: #999; } .form-container input[type=text]::-webkit-input-placeholder, .form-container input[type=email]::-webkit-input-placeholder, .form-container input[type=password]::-webkit-input-placeholder { - color: #999999; + color: #999; +} +.form-container input[type=text]::-ms-expand, +.form-container input[type=email]::-ms-expand, +.form-container input[type=password]::-ms-expand { + border: 0; + background-color: transparent; } .form-container input[type=text][disabled], .form-container input[type=email][disabled], @@ -8464,10 +8749,17 @@ footer { fieldset[disabled] .form-container input[type=text], fieldset[disabled] .form-container input[type=email], fieldset[disabled] .form-container input[type=password] { - cursor: not-allowed; background-color: #eeeeee; opacity: 1; } +.form-container input[type=text][disabled], +.form-container input[type=email][disabled], +.form-container input[type=password][disabled], +fieldset[disabled] .form-container input[type=text], +fieldset[disabled] .form-container input[type=email], +fieldset[disabled] .form-container input[type=password] { + cursor: not-allowed; +} textarea.form-container input[type=text], textarea.form-container input[type=email], textarea.form-container input[type=password] { @@ -8475,14 +8767,14 @@ textarea.form-container input[type=password] { } .form-container { padding: 20px; - border: 1px solid #5bc0de; + border: 1px solid #1f7e9a; border-radius: 6px; margin-bottom: 20px; - box-shadow: 0 0 10px #b0e1ef; + box-shadow: 0 0 10px #46b8da; } .form-container.has-error { - border-color: #d9534f; - box-shadow: 0 0 10px #eba5a3; + border-color: #8b211e; + box-shadow: 0 0 10px #d43f3a; } .form-container ul.errorlist { padding-left: 18px; @@ -8510,11 +8802,11 @@ textarea.form-container input[type=password] { display: none; } .homepage .jumbotron { - height: 100%; - padding-bottom: 0; + height: 50%; + padding-bottom: 0em; margin-top: -80px; background-size: cover; - background-position: top center; + background-position: center center; } .homepage nav.navbar { position: fixed; @@ -8540,71 +8832,16 @@ textarea.form-container input[type=password] { .homepage .navbar-wrapper { margin-bottom: 80px; } -.message-wrapper { +.homepage .message-wrapper { display: table; width: 100%; height: 100%; } -.message { +.homepage .message { vertical-align: bottom; + padding-bottom: 3em; display: table-cell; } -.status { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #ffffff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; - margin-left: 5px; -} -a.status:hover, -a.status:focus { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} -.status:empty { - display: none; -} -.btn .status { - position: relative; - top: -1px; -} -.status.label-proposal-type { - padding: 5px; - border-radius: 4px; - border: 1px solid #2066c5; - color: #333; -} -.status-2 { - background-color: #919191; -} -.status-2[href]:hover, -.status-2[href]:focus { - background-color: #777777; -} -.status-3, -.status-1 { - background-color: #5cb85c; -} -.status-3[href]:hover, -.status-1[href]:hover, -.status-3[href]:focus, -.status-1[href]:focus { - background-color: #449d44; -} -.status-4 { - background-color: #5bc0de; -} -.status-4[href]:hover, -.status-4[href]:focus { - background-color: #31b0d5; -} .page-proposals .proposal--title { font-size: 20px; margin-top: 0; @@ -8636,7 +8873,11 @@ a.status:focus { padding: 0; } .proposal-meta table td { - padding: 3px; + padding: 3px !important; +} +.proposal-writeup .heading { + border-bottom: 1px solid #eee; + padding-bottom: 5px; } .comments-and-reviews-panel { margin-bottom: 20px; @@ -8681,7 +8922,7 @@ a.status:focus { .tag.label-proposal-type { padding: 5px; border-radius: 4px; - border: 1px solid #2066c5; + border: 1px solid #eee; color: #333; } .tag.label-proposal-type { @@ -8690,7 +8931,7 @@ a.status:focus { transition: background-color 0.3s ease; } .tag.label-proposal-type:hover { - background-color: #2066c5; + background-color: #a2a2a2; } .review-form { margin-top: 20px; @@ -8703,9 +8944,9 @@ a.status:focus { font-size: 14px; line-height: 1.42857143; color: #555555; - background-color: #ffffff; + background-color: #fff; background-image: none; - border: 1px solid #cccccc; + border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); @@ -8720,22 +8961,29 @@ a.status:focus { box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); } .review-form select::-moz-placeholder { - color: #999999; + color: #999; opacity: 1; } .review-form select:-ms-input-placeholder { - color: #999999; + color: #999; } .review-form select::-webkit-input-placeholder { - color: #999999; + color: #999; +} +.review-form select::-ms-expand { + border: 0; + background-color: transparent; } .review-form select[disabled], .review-form select[readonly], fieldset[disabled] .review-form select { - cursor: not-allowed; background-color: #eeeeee; opacity: 1; } +.review-form select[disabled], +fieldset[disabled] .review-form select { + cursor: not-allowed; +} textarea.review-form select { height: auto; } @@ -8745,3 +8993,93 @@ textarea.review-form select { .dropdown { margin-left: 10px; } +.conference-list { + padding: 40px 0; +} + +.conference-list .heading { + font-size: 24px; + margin-bottom: 30px; + text-align: center; +} + +.no-conferences { + text-align: center; + font-style: italic; + color: #666; +} + +#conference-grid { + display: grid; + gap: 20px; + max-width: 1200px; + margin: 0 auto; + padding: 0 15px; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +} + +.conference-item { + border: 1px solid #ddd; + border-radius: 5px; + padding: 15px; + cursor: pointer; + transition: all 0.3s ease; + background-color: #ffffff; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + display: flex; + flex-direction: column; + justify-content: space-between; + text-decoration: none; + color: inherit; +} + +.conference-item:hover { + box-shadow: 0 5px 15px rgba(0,0,0,0.2); + transform: translateY(-5px); + text-decoration: none; +} + +.conference-date { + font-size: 14px; + color: #666; + margin-bottom: 10px; + text-align: left; +} + +.conference-item h3 { + margin: 0 0 10px 0; + font-size: 18px; +} + +.conference-item .meta { + font-size: 13px;; + color: #666; + margin-top: auto; +} + +.conference-item .status{ + display: inline-block; + padding: 2px 5px; + border-radius: 3px; + font-size:13px;; +} + +@media (max-width: 1024px) { + #conference-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + #conference-grid { + grid-template-columns: 1fr; + } +} + +.conference-item a, +.conference-item a:hover, +.conference-item a:focus, +.conference-item a:active { + text-decoration: none; + color: inherit; +} diff --git a/junction/static/css/bootstrap-markdown.min.css b/junction/static/css/bootstrap-markdown.min.css deleted file mode 100644 index 388b2f5f..00000000 --- a/junction/static/css/bootstrap-markdown.min.css +++ /dev/null @@ -1 +0,0 @@ -.md-editor{display:block;border:1px solid #ddd}.md-editor .md-footer,.md-editor>.md-header{display:block;padding:6px 4px;background:#f5f5f5}.md-editor>.md-header{margin:0}.md-editor>.md-preview{background:#fff;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;min-height:10px;overflow:auto}.md-editor>textarea{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;outline:0;margin:0;display:block;padding:0;width:100%;border:0;border-top:1px dashed #ddd;border-bottom:1px dashed #ddd;border-radius:0;box-shadow:none;background:#eee}.md-editor>textarea:focus{box-shadow:none;background:#fff}.md-editor.active{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.md-editor .md-controls{float:right;padding:3px}.md-editor .md-controls .md-control{right:5px;color:#bebebe;padding:3px 3px 3px 10px}.md-editor .md-controls .md-control:hover{color:#333}.md-editor.md-fullscreen-mode{width:100%;height:100%;position:fixed;top:0;left:0;z-index:99999;padding:60px 30px 15px;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-footer{display:none}.md-editor.md-fullscreen-mode .md-input,.md-editor.md-fullscreen-mode .md-preview{margin:0 auto!important;height:100%!important;font-size:20px!important;padding:20px!important;color:#999;line-height:1.6em!important;resize:none!important;box-shadow:none!important;background:#fff!important;border:0!important}.md-editor.md-fullscreen-mode .md-preview{color:#333;overflow:auto}.md-editor.md-fullscreen-mode .md-input:focus,.md-editor.md-fullscreen-mode .md-input:hover{color:#333;background:#fff!important}.md-editor.md-fullscreen-mode .md-header{background:0 0;text-align:center;position:fixed;width:100%;top:20px}.md-editor.md-fullscreen-mode .btn-group{float:none}.md-editor.md-fullscreen-mode .btn{border:0;background:0 0;color:#b3b3b3}.md-editor.md-fullscreen-mode .btn.active,.md-editor.md-fullscreen-mode .btn:active,.md-editor.md-fullscreen-mode .btn:focus,.md-editor.md-fullscreen-mode .btn:hover{box-shadow:none;color:#333}.md-editor.md-fullscreen-mode .md-fullscreen-controls{position:absolute;top:20px;right:20px;text-align:right;z-index:1002;display:block}.md-editor.md-fullscreen-mode .md-fullscreen-controls a{color:#b3b3b3;clear:right;margin:10px;width:30px;height:30px;text-align:center}.md-editor.md-fullscreen-mode .md-fullscreen-controls a:hover{color:#333;text-decoration:none}.md-editor.md-fullscreen-mode .md-editor{height:100%!important;position:relative}.md-editor .md-fullscreen-controls{display:none}.md-nooverflow{overflow:hidden;position:fixed;width:100%} \ No newline at end of file diff --git a/junction/static/css/bootstrap.css b/junction/static/css/bootstrap.css deleted file mode 100644 index 037dd056..00000000 --- a/junction/static/css/bootstrap.css +++ /dev/null @@ -1,6203 +0,0 @@ -/*! - * Bootstrap v3.2.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/*! normalize.css v3.0.1 | MIT License | git.io/normalize */ -html { - font-family: sans-serif; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} -body { - margin: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section, -summary { - display: block; -} -audio, -canvas, -progress, -video { - display: inline-block; - vertical-align: baseline; -} -audio:not([controls]) { - display: none; - height: 0; -} -[hidden], -template { - display: none; -} -a { - background: transparent; -} -a:active, -a:hover { - outline: 0; -} -abbr[title] { - border-bottom: 1px dotted; -} -b, -strong { - font-weight: bold; -} -dfn { - font-style: italic; -} -h1 { - margin: .67em 0; - font-size: 2em; -} -mark { - color: #000; - background: #ff0; -} -small { - font-size: 80%; -} -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} -sup { - top: -.5em; -} -sub { - bottom: -.25em; -} -img { - border: 0; -} -svg:not(:root) { - overflow: hidden; -} -figure { - margin: 1em 40px; -} -hr { - height: 0; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -pre { - overflow: auto; -} -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -button, -input, -optgroup, -select, -textarea { - margin: 0; - font: inherit; - color: inherit; -} -button { - overflow: visible; -} -button, -select { - text-transform: none; -} -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} -button[disabled], -html input[disabled] { - cursor: default; -} -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} -input { - line-height: normal; -} -input[type="checkbox"], -input[type="radio"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 0; -} -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} -legend { - padding: 0; - border: 0; -} -textarea { - overflow: auto; -} -optgroup { - font-weight: bold; -} -table { - border-spacing: 0; - border-collapse: collapse; -} -td, -th { - padding: 0; -} -@media print { - * { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - a[href^="javascript:"]:after, - a[href^="#"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } - select { - background: #fff !important; - } - .navbar { - display: none; - } - .table td, - .table th { - background-color: #fff !important; - } - .btn > .caret, - .dropup > .btn > .caret { - border-top-color: #000 !important; - } - .label { - border: 1px solid #000; - } - .table { - border-collapse: collapse !important; - } - .table-bordered th, - .table-bordered td { - border: 1px solid #ddd !important; - } -} -@font-face { - font-family: 'Glyphicons Halflings'; - - src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); -} -.glyphicon { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -.glyphicon-asterisk:before { - content: "\2a"; -} -.glyphicon-plus:before { - content: "\2b"; -} -.glyphicon-euro:before { - content: "\20ac"; -} -.glyphicon-minus:before { - content: "\2212"; -} -.glyphicon-cloud:before { - content: "\2601"; -} -.glyphicon-envelope:before { - content: "\2709"; -} -.glyphicon-pencil:before { - content: "\270f"; -} -.glyphicon-glass:before { - content: "\e001"; -} -.glyphicon-music:before { - content: "\e002"; -} -.glyphicon-search:before { - content: "\e003"; -} -.glyphicon-heart:before { - content: "\e005"; -} -.glyphicon-star:before { - content: "\e006"; -} -.glyphicon-star-empty:before { - content: "\e007"; -} -.glyphicon-user:before { - content: "\e008"; -} -.glyphicon-film:before { - content: "\e009"; -} -.glyphicon-th-large:before { - content: "\e010"; -} -.glyphicon-th:before { - content: "\e011"; -} -.glyphicon-th-list:before { - content: "\e012"; -} -.glyphicon-ok:before { - content: "\e013"; -} -.glyphicon-remove:before { - content: "\e014"; -} -.glyphicon-zoom-in:before { - content: "\e015"; -} -.glyphicon-zoom-out:before { - content: "\e016"; -} -.glyphicon-off:before { - content: "\e017"; -} -.glyphicon-signal:before { - content: "\e018"; -} -.glyphicon-cog:before { - content: "\e019"; -} -.glyphicon-trash:before { - content: "\e020"; -} -.glyphicon-home:before { - content: "\e021"; -} -.glyphicon-file:before { - content: "\e022"; -} -.glyphicon-time:before { - content: "\e023"; -} -.glyphicon-road:before { - content: "\e024"; -} -.glyphicon-download-alt:before { - content: "\e025"; -} -.glyphicon-download:before { - content: "\e026"; -} -.glyphicon-upload:before { - content: "\e027"; -} -.glyphicon-inbox:before { - content: "\e028"; -} -.glyphicon-play-circle:before { - content: "\e029"; -} -.glyphicon-repeat:before { - content: "\e030"; -} -.glyphicon-refresh:before { - content: "\e031"; -} -.glyphicon-list-alt:before { - content: "\e032"; -} -.glyphicon-lock:before { - content: "\e033"; -} -.glyphicon-flag:before { - content: "\e034"; -} -.glyphicon-headphones:before { - content: "\e035"; -} -.glyphicon-volume-off:before { - content: "\e036"; -} -.glyphicon-volume-down:before { - content: "\e037"; -} -.glyphicon-volume-up:before { - content: "\e038"; -} -.glyphicon-qrcode:before { - content: "\e039"; -} -.glyphicon-barcode:before { - content: "\e040"; -} -.glyphicon-tag:before { - content: "\e041"; -} -.glyphicon-tags:before { - content: "\e042"; -} -.glyphicon-book:before { - content: "\e043"; -} -.glyphicon-bookmark:before { - content: "\e044"; -} -.glyphicon-print:before { - content: "\e045"; -} -.glyphicon-camera:before { - content: "\e046"; -} -.glyphicon-font:before { - content: "\e047"; -} -.glyphicon-bold:before { - content: "\e048"; -} -.glyphicon-italic:before { - content: "\e049"; -} -.glyphicon-text-height:before { - content: "\e050"; -} -.glyphicon-text-width:before { - content: "\e051"; -} -.glyphicon-align-left:before { - content: "\e052"; -} -.glyphicon-align-center:before { - content: "\e053"; -} -.glyphicon-align-right:before { - content: "\e054"; -} -.glyphicon-align-justify:before { - content: "\e055"; -} -.glyphicon-list:before { - content: "\e056"; -} -.glyphicon-indent-left:before { - content: "\e057"; -} -.glyphicon-indent-right:before { - content: "\e058"; -} -.glyphicon-facetime-video:before { - content: "\e059"; -} -.glyphicon-picture:before { - content: "\e060"; -} -.glyphicon-map-marker:before { - content: "\e062"; -} -.glyphicon-adjust:before { - content: "\e063"; -} -.glyphicon-tint:before { - content: "\e064"; -} -.glyphicon-edit:before { - content: "\e065"; -} -.glyphicon-share:before { - content: "\e066"; -} -.glyphicon-check:before { - content: "\e067"; -} -.glyphicon-move:before { - content: "\e068"; -} -.glyphicon-step-backward:before { - content: "\e069"; -} -.glyphicon-fast-backward:before { - content: "\e070"; -} -.glyphicon-backward:before { - content: "\e071"; -} -.glyphicon-play:before { - content: "\e072"; -} -.glyphicon-pause:before { - content: "\e073"; -} -.glyphicon-stop:before { - content: "\e074"; -} -.glyphicon-forward:before { - content: "\e075"; -} -.glyphicon-fast-forward:before { - content: "\e076"; -} -.glyphicon-step-forward:before { - content: "\e077"; -} -.glyphicon-eject:before { - content: "\e078"; -} -.glyphicon-chevron-left:before { - content: "\e079"; -} -.glyphicon-chevron-right:before { - content: "\e080"; -} -.glyphicon-plus-sign:before { - content: "\e081"; -} -.glyphicon-minus-sign:before { - content: "\e082"; -} -.glyphicon-remove-sign:before { - content: "\e083"; -} -.glyphicon-ok-sign:before { - content: "\e084"; -} -.glyphicon-question-sign:before { - content: "\e085"; -} -.glyphicon-info-sign:before { - content: "\e086"; -} -.glyphicon-screenshot:before { - content: "\e087"; -} -.glyphicon-remove-circle:before { - content: "\e088"; -} -.glyphicon-ok-circle:before { - content: "\e089"; -} -.glyphicon-ban-circle:before { - content: "\e090"; -} -.glyphicon-arrow-left:before { - content: "\e091"; -} -.glyphicon-arrow-right:before { - content: "\e092"; -} -.glyphicon-arrow-up:before { - content: "\e093"; -} -.glyphicon-arrow-down:before { - content: "\e094"; -} -.glyphicon-share-alt:before { - content: "\e095"; -} -.glyphicon-resize-full:before { - content: "\e096"; -} -.glyphicon-resize-small:before { - content: "\e097"; -} -.glyphicon-exclamation-sign:before { - content: "\e101"; -} -.glyphicon-gift:before { - content: "\e102"; -} -.glyphicon-leaf:before { - content: "\e103"; -} -.glyphicon-fire:before { - content: "\e104"; -} -.glyphicon-eye-open:before { - content: "\e105"; -} -.glyphicon-eye-close:before { - content: "\e106"; -} -.glyphicon-warning-sign:before { - content: "\e107"; -} -.glyphicon-plane:before { - content: "\e108"; -} -.glyphicon-calendar:before { - content: "\e109"; -} -.glyphicon-random:before { - content: "\e110"; -} -.glyphicon-comment:before { - content: "\e111"; -} -.glyphicon-magnet:before { - content: "\e112"; -} -.glyphicon-chevron-up:before { - content: "\e113"; -} -.glyphicon-chevron-down:before { - content: "\e114"; -} -.glyphicon-retweet:before { - content: "\e115"; -} -.glyphicon-shopping-cart:before { - content: "\e116"; -} -.glyphicon-folder-close:before { - content: "\e117"; -} -.glyphicon-folder-open:before { - content: "\e118"; -} -.glyphicon-resize-vertical:before { - content: "\e119"; -} -.glyphicon-resize-horizontal:before { - content: "\e120"; -} -.glyphicon-hdd:before { - content: "\e121"; -} -.glyphicon-bullhorn:before { - content: "\e122"; -} -.glyphicon-bell:before { - content: "\e123"; -} -.glyphicon-certificate:before { - content: "\e124"; -} -.glyphicon-thumbs-up:before { - content: "\e125"; -} -.glyphicon-thumbs-down:before { - content: "\e126"; -} -.glyphicon-hand-right:before { - content: "\e127"; -} -.glyphicon-hand-left:before { - content: "\e128"; -} -.glyphicon-hand-up:before { - content: "\e129"; -} -.glyphicon-hand-down:before { - content: "\e130"; -} -.glyphicon-circle-arrow-right:before { - content: "\e131"; -} -.glyphicon-circle-arrow-left:before { - content: "\e132"; -} -.glyphicon-circle-arrow-up:before { - content: "\e133"; -} -.glyphicon-circle-arrow-down:before { - content: "\e134"; -} -.glyphicon-globe:before { - content: "\e135"; -} -.glyphicon-wrench:before { - content: "\e136"; -} -.glyphicon-tasks:before { - content: "\e137"; -} -.glyphicon-filter:before { - content: "\e138"; -} -.glyphicon-briefcase:before { - content: "\e139"; -} -.glyphicon-fullscreen:before { - content: "\e140"; -} -.glyphicon-dashboard:before { - content: "\e141"; -} -.glyphicon-paperclip:before { - content: "\e142"; -} -.glyphicon-heart-empty:before { - content: "\e143"; -} -.glyphicon-link:before { - content: "\e144"; -} -.glyphicon-phone:before { - content: "\e145"; -} -.glyphicon-pushpin:before { - content: "\e146"; -} -.glyphicon-usd:before { - content: "\e148"; -} -.glyphicon-gbp:before { - content: "\e149"; -} -.glyphicon-sort:before { - content: "\e150"; -} -.glyphicon-sort-by-alphabet:before { - content: "\e151"; -} -.glyphicon-sort-by-alphabet-alt:before { - content: "\e152"; -} -.glyphicon-sort-by-order:before { - content: "\e153"; -} -.glyphicon-sort-by-order-alt:before { - content: "\e154"; -} -.glyphicon-sort-by-attributes:before { - content: "\e155"; -} -.glyphicon-sort-by-attributes-alt:before { - content: "\e156"; -} -.glyphicon-unchecked:before { - content: "\e157"; -} -.glyphicon-expand:before { - content: "\e158"; -} -.glyphicon-collapse-down:before { - content: "\e159"; -} -.glyphicon-collapse-up:before { - content: "\e160"; -} -.glyphicon-log-in:before { - content: "\e161"; -} -.glyphicon-flash:before { - content: "\e162"; -} -.glyphicon-log-out:before { - content: "\e163"; -} -.glyphicon-new-window:before { - content: "\e164"; -} -.glyphicon-record:before { - content: "\e165"; -} -.glyphicon-save:before { - content: "\e166"; -} -.glyphicon-open:before { - content: "\e167"; -} -.glyphicon-saved:before { - content: "\e168"; -} -.glyphicon-import:before { - content: "\e169"; -} -.glyphicon-export:before { - content: "\e170"; -} -.glyphicon-send:before { - content: "\e171"; -} -.glyphicon-floppy-disk:before { - content: "\e172"; -} -.glyphicon-floppy-saved:before { - content: "\e173"; -} -.glyphicon-floppy-remove:before { - content: "\e174"; -} -.glyphicon-floppy-save:before { - content: "\e175"; -} -.glyphicon-floppy-open:before { - content: "\e176"; -} -.glyphicon-credit-card:before { - content: "\e177"; -} -.glyphicon-transfer:before { - content: "\e178"; -} -.glyphicon-cutlery:before { - content: "\e179"; -} -.glyphicon-header:before { - content: "\e180"; -} -.glyphicon-compressed:before { - content: "\e181"; -} -.glyphicon-earphone:before { - content: "\e182"; -} -.glyphicon-phone-alt:before { - content: "\e183"; -} -.glyphicon-tower:before { - content: "\e184"; -} -.glyphicon-stats:before { - content: "\e185"; -} -.glyphicon-sd-video:before { - content: "\e186"; -} -.glyphicon-hd-video:before { - content: "\e187"; -} -.glyphicon-subtitles:before { - content: "\e188"; -} -.glyphicon-sound-stereo:before { - content: "\e189"; -} -.glyphicon-sound-dolby:before { - content: "\e190"; -} -.glyphicon-sound-5-1:before { - content: "\e191"; -} -.glyphicon-sound-6-1:before { - content: "\e192"; -} -.glyphicon-sound-7-1:before { - content: "\e193"; -} -.glyphicon-copyright-mark:before { - content: "\e194"; -} -.glyphicon-registration-mark:before { - content: "\e195"; -} -.glyphicon-cloud-download:before { - content: "\e197"; -} -.glyphicon-cloud-upload:before { - content: "\e198"; -} -.glyphicon-tree-conifer:before { - content: "\e199"; -} -.glyphicon-tree-deciduous:before { - content: "\e200"; -} -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -*:before, -*:after { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -html { - font-size: 10px; - - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.42857143; - color: #333; - background-color: #fff; -} -input, -button, -select, -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} -a { - color: #428bca; - text-decoration: none; -} -a:hover, -a:focus { - color: #2a6496; - text-decoration: underline; -} -a:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -figure { - margin: 0; -} -img { - vertical-align: middle; -} -.img-responsive, -.thumbnail > img, -.thumbnail a > img, -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - display: block; - width: 100% \9; - max-width: 100%; - height: auto; -} -.img-rounded { - border-radius: 6px; -} -.img-thumbnail { - display: inline-block; - width: 100% \9; - max-width: 100%; - height: auto; - padding: 4px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.img-circle { - border-radius: 50%; -} -hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; -} -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-family: inherit; - font-weight: 500; - line-height: 1.1; - color: inherit; -} -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small, -.h1 small, -.h2 small, -.h3 small, -.h4 small, -.h5 small, -.h6 small, -h1 .small, -h2 .small, -h3 .small, -h4 .small, -h5 .small, -h6 .small, -.h1 .small, -.h2 .small, -.h3 .small, -.h4 .small, -.h5 .small, -.h6 .small { - font-weight: normal; - line-height: 1; - color: #777; -} -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 20px; - margin-bottom: 10px; -} -h1 small, -.h1 small, -h2 small, -.h2 small, -h3 small, -.h3 small, -h1 .small, -.h1 .small, -h2 .small, -.h2 .small, -h3 .small, -.h3 .small { - font-size: 65%; -} -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 10px; -} -h4 small, -.h4 small, -h5 small, -.h5 small, -h6 small, -.h6 small, -h4 .small, -.h4 .small, -h5 .small, -.h5 .small, -h6 .small, -.h6 .small { - font-size: 75%; -} -h1, -.h1 { - font-size: 36px; -} -h2, -.h2 { - font-size: 30px; -} -h3, -.h3 { - font-size: 24px; -} -h4, -.h4 { - font-size: 18px; -} -h5, -.h5 { - font-size: 14px; -} -h6, -.h6 { - font-size: 12px; -} -p { - margin: 0 0 10px; -} -.lead { - margin-bottom: 20px; - font-size: 16px; - font-weight: 300; - line-height: 1.4; -} -@media (min-width: 768px) { - .lead { - font-size: 21px; - } -} -small, -.small { - font-size: 85%; -} -cite { - font-style: normal; -} -mark, -.mark { - padding: .2em; - background-color: #fcf8e3; -} -.text-left { - text-align: left; -} -.text-right { - text-align: right; -} -.text-center { - text-align: center; -} -.text-justify { - text-align: justify; -} -.text-nowrap { - white-space: nowrap; -} -.text-lowercase { - text-transform: lowercase; -} -.text-uppercase { - text-transform: uppercase; -} -.text-capitalize { - text-transform: capitalize; -} -.text-muted { - color: #777; -} -.text-primary { - color: #428bca; -} -a.text-primary:hover { - color: #3071a9; -} -.text-success { - color: #3c763d; -} -a.text-success:hover { - color: #2b542c; -} -.text-info { - color: #31708f; -} -a.text-info:hover { - color: #245269; -} -.text-warning { - color: #8a6d3b; -} -a.text-warning:hover { - color: #66512c; -} -.text-danger { - color: #a94442; -} -a.text-danger:hover { - color: #843534; -} -.bg-primary { - color: #fff; - background-color: #428bca; -} -a.bg-primary:hover { - background-color: #3071a9; -} -.bg-success { - background-color: #dff0d8; -} -a.bg-success:hover { - background-color: #c1e2b3; -} -.bg-info { - background-color: #d9edf7; -} -a.bg-info:hover { - background-color: #afd9ee; -} -.bg-warning { - background-color: #fcf8e3; -} -a.bg-warning:hover { - background-color: #f7ecb5; -} -.bg-danger { - background-color: #f2dede; -} -a.bg-danger:hover { - background-color: #e4b9b9; -} -.page-header { - padding-bottom: 9px; - margin: 40px 0 20px; - border-bottom: 1px solid #eee; -} -ul, -ol { - margin-top: 0; - margin-bottom: 10px; -} -ul ul, -ol ul, -ul ol, -ol ol { - margin-bottom: 0; -} -.list-unstyled { - padding-left: 0; - list-style: none; -} -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} -.list-inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} -dl { - margin-top: 0; - margin-bottom: 20px; -} -dt, -dd { - line-height: 1.42857143; -} -dt { - font-weight: bold; -} -dd { - margin-left: 0; -} -@media (min-width: 768px) { - .dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; - } - .dl-horizontal dd { - margin-left: 180px; - } -} -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #777; -} -.initialism { - font-size: 90%; - text-transform: uppercase; -} -blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; -} -blockquote p:last-child, -blockquote ul:last-child, -blockquote ol:last-child { - margin-bottom: 0; -} -blockquote footer, -blockquote small, -blockquote .small { - display: block; - font-size: 80%; - line-height: 1.42857143; - color: #777; -} -blockquote footer:before, -blockquote small:before, -blockquote .small:before { - content: '\2014 \00A0'; -} -.blockquote-reverse, -blockquote.pull-right { - padding-right: 15px; - padding-left: 0; - text-align: right; - border-right: 5px solid #eee; - border-left: 0; -} -.blockquote-reverse footer:before, -blockquote.pull-right footer:before, -.blockquote-reverse small:before, -blockquote.pull-right small:before, -.blockquote-reverse .small:before, -blockquote.pull-right .small:before { - content: ''; -} -.blockquote-reverse footer:after, -blockquote.pull-right footer:after, -.blockquote-reverse small:after, -blockquote.pull-right small:after, -.blockquote-reverse .small:after, -blockquote.pull-right .small:after { - content: '\00A0 \2014'; -} -blockquote:before, -blockquote:after { - content: ""; -} -address { - margin-bottom: 20px; - font-style: normal; - line-height: 1.42857143; -} -code, -kbd, -pre, -samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} -code { - padding: 2px 4px; - font-size: 90%; - color: #c7254e; - background-color: #f9f2f4; - border-radius: 4px; -} -kbd { - padding: 2px 4px; - font-size: 90%; - color: #fff; - background-color: #333; - border-radius: 3px; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); -} -kbd kbd { - padding: 0; - font-size: 100%; - -webkit-box-shadow: none; - box-shadow: none; -} -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 1.42857143; - color: #333; - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - border: 1px solid #ccc; - border-radius: 4px; -} -pre code { - padding: 0; - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0; -} -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -@media (min-width: 768px) { - .container { - width: 750px; - } -} -@media (min-width: 992px) { - .container { - width: 970px; - } -} -@media (min-width: 1200px) { - .container { - width: 1170px; - } -} -.container-fluid { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} -.row { - margin-right: -15px; - margin-left: -15px; -} -.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} -.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { - float: left; -} -.col-xs-12 { - width: 100%; -} -.col-xs-11 { - width: 91.66666667%; -} -.col-xs-10 { - width: 83.33333333%; -} -.col-xs-9 { - width: 75%; -} -.col-xs-8 { - width: 66.66666667%; -} -.col-xs-7 { - width: 58.33333333%; -} -.col-xs-6 { - width: 50%; -} -.col-xs-5 { - width: 41.66666667%; -} -.col-xs-4 { - width: 33.33333333%; -} -.col-xs-3 { - width: 25%; -} -.col-xs-2 { - width: 16.66666667%; -} -.col-xs-1 { - width: 8.33333333%; -} -.col-xs-pull-12 { - right: 100%; -} -.col-xs-pull-11 { - right: 91.66666667%; -} -.col-xs-pull-10 { - right: 83.33333333%; -} -.col-xs-pull-9 { - right: 75%; -} -.col-xs-pull-8 { - right: 66.66666667%; -} -.col-xs-pull-7 { - right: 58.33333333%; -} -.col-xs-pull-6 { - right: 50%; -} -.col-xs-pull-5 { - right: 41.66666667%; -} -.col-xs-pull-4 { - right: 33.33333333%; -} -.col-xs-pull-3 { - right: 25%; -} -.col-xs-pull-2 { - right: 16.66666667%; -} -.col-xs-pull-1 { - right: 8.33333333%; -} -.col-xs-pull-0 { - right: auto; -} -.col-xs-push-12 { - left: 100%; -} -.col-xs-push-11 { - left: 91.66666667%; -} -.col-xs-push-10 { - left: 83.33333333%; -} -.col-xs-push-9 { - left: 75%; -} -.col-xs-push-8 { - left: 66.66666667%; -} -.col-xs-push-7 { - left: 58.33333333%; -} -.col-xs-push-6 { - left: 50%; -} -.col-xs-push-5 { - left: 41.66666667%; -} -.col-xs-push-4 { - left: 33.33333333%; -} -.col-xs-push-3 { - left: 25%; -} -.col-xs-push-2 { - left: 16.66666667%; -} -.col-xs-push-1 { - left: 8.33333333%; -} -.col-xs-push-0 { - left: auto; -} -.col-xs-offset-12 { - margin-left: 100%; -} -.col-xs-offset-11 { - margin-left: 91.66666667%; -} -.col-xs-offset-10 { - margin-left: 83.33333333%; -} -.col-xs-offset-9 { - margin-left: 75%; -} -.col-xs-offset-8 { - margin-left: 66.66666667%; -} -.col-xs-offset-7 { - margin-left: 58.33333333%; -} -.col-xs-offset-6 { - margin-left: 50%; -} -.col-xs-offset-5 { - margin-left: 41.66666667%; -} -.col-xs-offset-4 { - margin-left: 33.33333333%; -} -.col-xs-offset-3 { - margin-left: 25%; -} -.col-xs-offset-2 { - margin-left: 16.66666667%; -} -.col-xs-offset-1 { - margin-left: 8.33333333%; -} -.col-xs-offset-0 { - margin-left: 0; -} -@media (min-width: 768px) { - .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { - float: left; - } - .col-sm-12 { - width: 100%; - } - .col-sm-11 { - width: 91.66666667%; - } - .col-sm-10 { - width: 83.33333333%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-8 { - width: 66.66666667%; - } - .col-sm-7 { - width: 58.33333333%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-5 { - width: 41.66666667%; - } - .col-sm-4 { - width: 33.33333333%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-2 { - width: 16.66666667%; - } - .col-sm-1 { - width: 8.33333333%; - } - .col-sm-pull-12 { - right: 100%; - } - .col-sm-pull-11 { - right: 91.66666667%; - } - .col-sm-pull-10 { - right: 83.33333333%; - } - .col-sm-pull-9 { - right: 75%; - } - .col-sm-pull-8 { - right: 66.66666667%; - } - .col-sm-pull-7 { - right: 58.33333333%; - } - .col-sm-pull-6 { - right: 50%; - } - .col-sm-pull-5 { - right: 41.66666667%; - } - .col-sm-pull-4 { - right: 33.33333333%; - } - .col-sm-pull-3 { - right: 25%; - } - .col-sm-pull-2 { - right: 16.66666667%; - } - .col-sm-pull-1 { - right: 8.33333333%; - } - .col-sm-pull-0 { - right: auto; - } - .col-sm-push-12 { - left: 100%; - } - .col-sm-push-11 { - left: 91.66666667%; - } - .col-sm-push-10 { - left: 83.33333333%; - } - .col-sm-push-9 { - left: 75%; - } - .col-sm-push-8 { - left: 66.66666667%; - } - .col-sm-push-7 { - left: 58.33333333%; - } - .col-sm-push-6 { - left: 50%; - } - .col-sm-push-5 { - left: 41.66666667%; - } - .col-sm-push-4 { - left: 33.33333333%; - } - .col-sm-push-3 { - left: 25%; - } - .col-sm-push-2 { - left: 16.66666667%; - } - .col-sm-push-1 { - left: 8.33333333%; - } - .col-sm-push-0 { - left: auto; - } - .col-sm-offset-12 { - margin-left: 100%; - } - .col-sm-offset-11 { - margin-left: 91.66666667%; - } - .col-sm-offset-10 { - margin-left: 83.33333333%; - } - .col-sm-offset-9 { - margin-left: 75%; - } - .col-sm-offset-8 { - margin-left: 66.66666667%; - } - .col-sm-offset-7 { - margin-left: 58.33333333%; - } - .col-sm-offset-6 { - margin-left: 50%; - } - .col-sm-offset-5 { - margin-left: 41.66666667%; - } - .col-sm-offset-4 { - margin-left: 33.33333333%; - } - .col-sm-offset-3 { - margin-left: 25%; - } - .col-sm-offset-2 { - margin-left: 16.66666667%; - } - .col-sm-offset-1 { - margin-left: 8.33333333%; - } - .col-sm-offset-0 { - margin-left: 0; - } -} -@media (min-width: 992px) { - .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { - float: left; - } - .col-md-12 { - width: 100%; - } - .col-md-11 { - width: 91.66666667%; - } - .col-md-10 { - width: 83.33333333%; - } - .col-md-9 { - width: 75%; - } - .col-md-8 { - width: 66.66666667%; - } - .col-md-7 { - width: 58.33333333%; - } - .col-md-6 { - width: 50%; - } - .col-md-5 { - width: 41.66666667%; - } - .col-md-4 { - width: 33.33333333%; - } - .col-md-3 { - width: 25%; - } - .col-md-2 { - width: 16.66666667%; - } - .col-md-1 { - width: 8.33333333%; - } - .col-md-pull-12 { - right: 100%; - } - .col-md-pull-11 { - right: 91.66666667%; - } - .col-md-pull-10 { - right: 83.33333333%; - } - .col-md-pull-9 { - right: 75%; - } - .col-md-pull-8 { - right: 66.66666667%; - } - .col-md-pull-7 { - right: 58.33333333%; - } - .col-md-pull-6 { - right: 50%; - } - .col-md-pull-5 { - right: 41.66666667%; - } - .col-md-pull-4 { - right: 33.33333333%; - } - .col-md-pull-3 { - right: 25%; - } - .col-md-pull-2 { - right: 16.66666667%; - } - .col-md-pull-1 { - right: 8.33333333%; - } - .col-md-pull-0 { - right: auto; - } - .col-md-push-12 { - left: 100%; - } - .col-md-push-11 { - left: 91.66666667%; - } - .col-md-push-10 { - left: 83.33333333%; - } - .col-md-push-9 { - left: 75%; - } - .col-md-push-8 { - left: 66.66666667%; - } - .col-md-push-7 { - left: 58.33333333%; - } - .col-md-push-6 { - left: 50%; - } - .col-md-push-5 { - left: 41.66666667%; - } - .col-md-push-4 { - left: 33.33333333%; - } - .col-md-push-3 { - left: 25%; - } - .col-md-push-2 { - left: 16.66666667%; - } - .col-md-push-1 { - left: 8.33333333%; - } - .col-md-push-0 { - left: auto; - } - .col-md-offset-12 { - margin-left: 100%; - } - .col-md-offset-11 { - margin-left: 91.66666667%; - } - .col-md-offset-10 { - margin-left: 83.33333333%; - } - .col-md-offset-9 { - margin-left: 75%; - } - .col-md-offset-8 { - margin-left: 66.66666667%; - } - .col-md-offset-7 { - margin-left: 58.33333333%; - } - .col-md-offset-6 { - margin-left: 50%; - } - .col-md-offset-5 { - margin-left: 41.66666667%; - } - .col-md-offset-4 { - margin-left: 33.33333333%; - } - .col-md-offset-3 { - margin-left: 25%; - } - .col-md-offset-2 { - margin-left: 16.66666667%; - } - .col-md-offset-1 { - margin-left: 8.33333333%; - } - .col-md-offset-0 { - margin-left: 0; - } -} -@media (min-width: 1200px) { - .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { - float: left; - } - .col-lg-12 { - width: 100%; - } - .col-lg-11 { - width: 91.66666667%; - } - .col-lg-10 { - width: 83.33333333%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-8 { - width: 66.66666667%; - } - .col-lg-7 { - width: 58.33333333%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-5 { - width: 41.66666667%; - } - .col-lg-4 { - width: 33.33333333%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-2 { - width: 16.66666667%; - } - .col-lg-1 { - width: 8.33333333%; - } - .col-lg-pull-12 { - right: 100%; - } - .col-lg-pull-11 { - right: 91.66666667%; - } - .col-lg-pull-10 { - right: 83.33333333%; - } - .col-lg-pull-9 { - right: 75%; - } - .col-lg-pull-8 { - right: 66.66666667%; - } - .col-lg-pull-7 { - right: 58.33333333%; - } - .col-lg-pull-6 { - right: 50%; - } - .col-lg-pull-5 { - right: 41.66666667%; - } - .col-lg-pull-4 { - right: 33.33333333%; - } - .col-lg-pull-3 { - right: 25%; - } - .col-lg-pull-2 { - right: 16.66666667%; - } - .col-lg-pull-1 { - right: 8.33333333%; - } - .col-lg-pull-0 { - right: auto; - } - .col-lg-push-12 { - left: 100%; - } - .col-lg-push-11 { - left: 91.66666667%; - } - .col-lg-push-10 { - left: 83.33333333%; - } - .col-lg-push-9 { - left: 75%; - } - .col-lg-push-8 { - left: 66.66666667%; - } - .col-lg-push-7 { - left: 58.33333333%; - } - .col-lg-push-6 { - left: 50%; - } - .col-lg-push-5 { - left: 41.66666667%; - } - .col-lg-push-4 { - left: 33.33333333%; - } - .col-lg-push-3 { - left: 25%; - } - .col-lg-push-2 { - left: 16.66666667%; - } - .col-lg-push-1 { - left: 8.33333333%; - } - .col-lg-push-0 { - left: auto; - } - .col-lg-offset-12 { - margin-left: 100%; - } - .col-lg-offset-11 { - margin-left: 91.66666667%; - } - .col-lg-offset-10 { - margin-left: 83.33333333%; - } - .col-lg-offset-9 { - margin-left: 75%; - } - .col-lg-offset-8 { - margin-left: 66.66666667%; - } - .col-lg-offset-7 { - margin-left: 58.33333333%; - } - .col-lg-offset-6 { - margin-left: 50%; - } - .col-lg-offset-5 { - margin-left: 41.66666667%; - } - .col-lg-offset-4 { - margin-left: 33.33333333%; - } - .col-lg-offset-3 { - margin-left: 25%; - } - .col-lg-offset-2 { - margin-left: 16.66666667%; - } - .col-lg-offset-1 { - margin-left: 8.33333333%; - } - .col-lg-offset-0 { - margin-left: 0; - } -} -table { - background-color: transparent; -} -th { - text-align: left; -} -.table { - width: 100%; - max-width: 100%; - margin-bottom: 20px; -} -.table > thead > tr > th, -.table > tbody > tr > th, -.table > tfoot > tr > th, -.table > thead > tr > td, -.table > tbody > tr > td, -.table > tfoot > tr > td { - padding: 8px; - line-height: 1.42857143; - vertical-align: top; - border-top: 1px solid #ddd; -} -.table > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid #ddd; -} -.table > caption + thead > tr:first-child > th, -.table > colgroup + thead > tr:first-child > th, -.table > thead:first-child > tr:first-child > th, -.table > caption + thead > tr:first-child > td, -.table > colgroup + thead > tr:first-child > td, -.table > thead:first-child > tr:first-child > td { - border-top: 0; -} -.table > tbody + tbody { - border-top: 2px solid #ddd; -} -.table .table { - background-color: #fff; -} -.table-condensed > thead > tr > th, -.table-condensed > tbody > tr > th, -.table-condensed > tfoot > tr > th, -.table-condensed > thead > tr > td, -.table-condensed > tbody > tr > td, -.table-condensed > tfoot > tr > td { - padding: 5px; -} -.table-bordered { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > tbody > tr > td, -.table-bordered > tfoot > tr > td { - border: 1px solid #ddd; -} -.table-bordered > thead > tr > th, -.table-bordered > thead > tr > td { - border-bottom-width: 2px; -} -.table-striped > tbody > tr:nth-child(odd) > td, -.table-striped > tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} -.table-hover > tbody > tr:hover > td, -.table-hover > tbody > tr:hover > th { - background-color: #f5f5f5; -} -table col[class*="col-"] { - position: static; - display: table-column; - float: none; -} -table td[class*="col-"], -table th[class*="col-"] { - position: static; - display: table-cell; - float: none; -} -.table > thead > tr > td.active, -.table > tbody > tr > td.active, -.table > tfoot > tr > td.active, -.table > thead > tr > th.active, -.table > tbody > tr > th.active, -.table > tfoot > tr > th.active, -.table > thead > tr.active > td, -.table > tbody > tr.active > td, -.table > tfoot > tr.active > td, -.table > thead > tr.active > th, -.table > tbody > tr.active > th, -.table > tfoot > tr.active > th { - background-color: #f5f5f5; -} -.table-hover > tbody > tr > td.active:hover, -.table-hover > tbody > tr > th.active:hover, -.table-hover > tbody > tr.active:hover > td, -.table-hover > tbody > tr:hover > .active, -.table-hover > tbody > tr.active:hover > th { - background-color: #e8e8e8; -} -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: #dff0d8; -} -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr:hover > .success, -.table-hover > tbody > tr.success:hover > th { - background-color: #d0e9c6; -} -.table > thead > tr > td.info, -.table > tbody > tr > td.info, -.table > tfoot > tr > td.info, -.table > thead > tr > th.info, -.table > tbody > tr > th.info, -.table > tfoot > tr > th.info, -.table > thead > tr.info > td, -.table > tbody > tr.info > td, -.table > tfoot > tr.info > td, -.table > thead > tr.info > th, -.table > tbody > tr.info > th, -.table > tfoot > tr.info > th { - background-color: #d9edf7; -} -.table-hover > tbody > tr > td.info:hover, -.table-hover > tbody > tr > th.info:hover, -.table-hover > tbody > tr.info:hover > td, -.table-hover > tbody > tr:hover > .info, -.table-hover > tbody > tr.info:hover > th { - background-color: #c4e3f3; -} -.table > thead > tr > td.warning, -.table > tbody > tr > td.warning, -.table > tfoot > tr > td.warning, -.table > thead > tr > th.warning, -.table > tbody > tr > th.warning, -.table > tfoot > tr > th.warning, -.table > thead > tr.warning > td, -.table > tbody > tr.warning > td, -.table > tfoot > tr.warning > td, -.table > thead > tr.warning > th, -.table > tbody > tr.warning > th, -.table > tfoot > tr.warning > th { - background-color: #fcf8e3; -} -.table-hover > tbody > tr > td.warning:hover, -.table-hover > tbody > tr > th.warning:hover, -.table-hover > tbody > tr.warning:hover > td, -.table-hover > tbody > tr:hover > .warning, -.table-hover > tbody > tr.warning:hover > th { - background-color: #faf2cc; -} -.table > thead > tr > td.danger, -.table > tbody > tr > td.danger, -.table > tfoot > tr > td.danger, -.table > thead > tr > th.danger, -.table > tbody > tr > th.danger, -.table > tfoot > tr > th.danger, -.table > thead > tr.danger > td, -.table > tbody > tr.danger > td, -.table > tfoot > tr.danger > td, -.table > thead > tr.danger > th, -.table > tbody > tr.danger > th, -.table > tfoot > tr.danger > th { - background-color: #f2dede; -} -.table-hover > tbody > tr > td.danger:hover, -.table-hover > tbody > tr > th.danger:hover, -.table-hover > tbody > tr.danger:hover > td, -.table-hover > tbody > tr:hover > .danger, -.table-hover > tbody > tr.danger:hover > th { - background-color: #ebcccc; -} -@media screen and (max-width: 767px) { - .table-responsive { - width: 100%; - margin-bottom: 15px; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - border: 1px solid #ddd; - } - .table-responsive > .table { - margin-bottom: 0; - } - .table-responsive > .table > thead > tr > th, - .table-responsive > .table > tbody > tr > th, - .table-responsive > .table > tfoot > tr > th, - .table-responsive > .table > thead > tr > td, - .table-responsive > .table > tbody > tr > td, - .table-responsive > .table > tfoot > tr > td { - white-space: nowrap; - } - .table-responsive > .table-bordered { - border: 0; - } - .table-responsive > .table-bordered > thead > tr > th:first-child, - .table-responsive > .table-bordered > tbody > tr > th:first-child, - .table-responsive > .table-bordered > tfoot > tr > th:first-child, - .table-responsive > .table-bordered > thead > tr > td:first-child, - .table-responsive > .table-bordered > tbody > tr > td:first-child, - .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; - } - .table-responsive > .table-bordered > thead > tr > th:last-child, - .table-responsive > .table-bordered > tbody > tr > th:last-child, - .table-responsive > .table-bordered > tfoot > tr > th:last-child, - .table-responsive > .table-bordered > thead > tr > td:last-child, - .table-responsive > .table-bordered > tbody > tr > td:last-child, - .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; - } - .table-responsive > .table-bordered > tbody > tr:last-child > th, - .table-responsive > .table-bordered > tfoot > tr:last-child > th, - .table-responsive > .table-bordered > tbody > tr:last-child > td, - .table-responsive > .table-bordered > tfoot > tr:last-child > td { - border-bottom: 0; - } -} -fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; -} -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: inherit; - color: #333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: bold; -} -input[type="search"] { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - line-height: normal; -} -input[type="file"] { - display: block; -} -input[type="range"] { - display: block; - width: 100%; -} -select[multiple], -select[size] { - height: auto; -} -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -output { - display: block; - padding-top: 7px; - font-size: 14px; - line-height: 1.42857143; - color: #555; -} -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; -} -.form-control:focus { - border-color: #66afe9; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); - box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); -} -.form-control::-moz-placeholder { - color: #777; - opacity: 1; -} -.form-control:-ms-input-placeholder { - color: #777; -} -.form-control::-webkit-input-placeholder { - color: #777; -} -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] .form-control { - cursor: not-allowed; - background-color: #eee; - opacity: 1; -} -textarea.form-control { - height: auto; -} -input[type="search"] { - -webkit-appearance: none; -} -input[type="date"], -input[type="time"], -input[type="datetime-local"], -input[type="month"] { - line-height: 34px; - line-height: 1.42857143 \0; -} -input[type="date"].input-sm, -input[type="time"].input-sm, -input[type="datetime-local"].input-sm, -input[type="month"].input-sm { - line-height: 30px; -} -input[type="date"].input-lg, -input[type="time"].input-lg, -input[type="datetime-local"].input-lg, -input[type="month"].input-lg { - line-height: 46px; -} -.form-group { - margin-bottom: 15px; -} -.radio, -.checkbox { - position: relative; - display: block; - min-height: 20px; - margin-top: 10px; - margin-bottom: 10px; -} -.radio label, -.checkbox label { - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; -} -.radio input[type="radio"], -.radio-inline input[type="radio"], -.checkbox input[type="checkbox"], -.checkbox-inline input[type="checkbox"] { - position: absolute; - margin-top: 4px \9; - margin-left: -20px; -} -.radio + .radio, -.checkbox + .checkbox { - margin-top: -5px; -} -.radio-inline, -.checkbox-inline { - display: inline-block; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - vertical-align: middle; - cursor: pointer; -} -.radio-inline + .radio-inline, -.checkbox-inline + .checkbox-inline { - margin-top: 0; - margin-left: 10px; -} -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"].disabled, -input[type="checkbox"].disabled, -fieldset[disabled] input[type="radio"], -fieldset[disabled] input[type="checkbox"] { - cursor: not-allowed; -} -.radio-inline.disabled, -.checkbox-inline.disabled, -fieldset[disabled] .radio-inline, -fieldset[disabled] .checkbox-inline { - cursor: not-allowed; -} -.radio.disabled label, -.checkbox.disabled label, -fieldset[disabled] .radio label, -fieldset[disabled] .checkbox label { - cursor: not-allowed; -} -.form-control-static { - padding-top: 7px; - padding-bottom: 7px; - margin-bottom: 0; -} -.form-control-static.input-lg, -.form-control-static.input-sm { - padding-right: 0; - padding-left: 0; -} -.input-sm, -.form-horizontal .form-group-sm .form-control { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-sm { - height: 30px; - line-height: 30px; -} -textarea.input-sm, -select[multiple].input-sm { - height: auto; -} -.input-lg, -.form-horizontal .form-group-lg .form-control { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -select.input-lg { - height: 46px; - line-height: 46px; -} -textarea.input-lg, -select[multiple].input-lg { - height: auto; -} -.has-feedback { - position: relative; -} -.has-feedback .form-control { - padding-right: 42.5px; -} -.form-control-feedback { - position: absolute; - top: 25px; - right: 0; - z-index: 2; - display: block; - width: 34px; - height: 34px; - line-height: 34px; - text-align: center; -} -.input-lg + .form-control-feedback { - width: 46px; - height: 46px; - line-height: 46px; -} -.input-sm + .form-control-feedback { - width: 30px; - height: 30px; - line-height: 30px; -} -.has-success .help-block, -.has-success .control-label, -.has-success .radio, -.has-success .checkbox, -.has-success .radio-inline, -.has-success .checkbox-inline { - color: #3c763d; -} -.has-success .form-control { - border-color: #3c763d; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-success .form-control:focus { - border-color: #2b542c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; -} -.has-success .input-group-addon { - color: #3c763d; - background-color: #dff0d8; - border-color: #3c763d; -} -.has-success .form-control-feedback { - color: #3c763d; -} -.has-warning .help-block, -.has-warning .control-label, -.has-warning .radio, -.has-warning .checkbox, -.has-warning .radio-inline, -.has-warning .checkbox-inline { - color: #8a6d3b; -} -.has-warning .form-control { - border-color: #8a6d3b; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-warning .form-control:focus { - border-color: #66512c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; -} -.has-warning .input-group-addon { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #8a6d3b; -} -.has-warning .form-control-feedback { - color: #8a6d3b; -} -.has-error .help-block, -.has-error .control-label, -.has-error .radio, -.has-error .checkbox, -.has-error .radio-inline, -.has-error .checkbox-inline { - color: #a94442; -} -.has-error .form-control { - border-color: #a94442; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -} -.has-error .form-control:focus { - border-color: #843534; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; -} -.has-error .input-group-addon { - color: #a94442; - background-color: #f2dede; - border-color: #a94442; -} -.has-error .form-control-feedback { - color: #a94442; -} -.has-feedback label.sr-only ~ .form-control-feedback { - top: 0; -} -.help-block { - display: block; - margin-top: 5px; - margin-bottom: 10px; - color: #737373; -} -@media (min-width: 768px) { - .form-inline .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .input-group { - display: inline-table; - vertical-align: middle; - } - .form-inline .input-group .input-group-addon, - .form-inline .input-group .input-group-btn, - .form-inline .input-group .form-control { - width: auto; - } - .form-inline .input-group > .form-control { - width: 100%; - } - .form-inline .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio, - .form-inline .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .form-inline .radio label, - .form-inline .checkbox label { - padding-left: 0; - } - .form-inline .radio input[type="radio"], - .form-inline .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .form-inline .has-feedback .form-control-feedback { - top: 0; - } -} -.form-horizontal .radio, -.form-horizontal .checkbox, -.form-horizontal .radio-inline, -.form-horizontal .checkbox-inline { - padding-top: 7px; - margin-top: 0; - margin-bottom: 0; -} -.form-horizontal .radio, -.form-horizontal .checkbox { - min-height: 27px; -} -.form-horizontal .form-group { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .form-horizontal .control-label { - padding-top: 7px; - margin-bottom: 0; - text-align: right; - } -} -.form-horizontal .has-feedback .form-control-feedback { - top: 0; - right: 15px; -} -@media (min-width: 768px) { - .form-horizontal .form-group-lg .control-label { - padding-top: 14.3px; - } -} -@media (min-width: 768px) { - .form-horizontal .form-group-sm .control-label { - padding-top: 6px; - } -} -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.btn:focus, -.btn:active:focus, -.btn.active:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} -.btn:hover, -.btn:focus { - color: #333; - text-decoration: none; -} -.btn:active, -.btn.active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn.disabled, -.btn[disabled], -fieldset[disabled] .btn { - pointer-events: none; - cursor: not-allowed; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} -.btn-default:hover, -.btn-default:focus, -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} -.btn-default:active, -.btn-default.active, -.open > .dropdown-toggle.btn-default { - background-image: none; -} -.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: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: #fff; - border-color: #ccc; -} -.btn-default .badge { - color: #fff; - background-color: #333; -} -.btn-primary { - color: #fff; - background-color: #428bca; - border-color: #357ebd; -} -.btn-primary:hover, -.btn-primary:focus, -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - color: #fff; - background-color: #3071a9; - border-color: #285e8e; -} -.btn-primary:active, -.btn-primary.active, -.open > .dropdown-toggle.btn-primary { - background-image: none; -} -.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: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: #428bca; - border-color: #357ebd; -} -.btn-primary .badge { - color: #428bca; - background-color: #fff; -} -.btn-success { - color: #fff; - background-color: #5cb85c; - border-color: #4cae4c; -} -.btn-success:hover, -.btn-success:focus, -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - color: #fff; - background-color: #449d44; - border-color: #398439; -} -.btn-success:active, -.btn-success.active, -.open > .dropdown-toggle.btn-success { - background-image: none; -} -.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: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: #5cb85c; - border-color: #4cae4c; -} -.btn-success .badge { - color: #5cb85c; - background-color: #fff; -} -.btn-info { - color: #fff; - background-color: #5bc0de; - border-color: #46b8da; -} -.btn-info:hover, -.btn-info:focus, -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - color: #fff; - background-color: #31b0d5; - border-color: #269abc; -} -.btn-info:active, -.btn-info.active, -.open > .dropdown-toggle.btn-info { - background-image: none; -} -.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: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: #5bc0de; - border-color: #46b8da; -} -.btn-info .badge { - color: #5bc0de; - background-color: #fff; -} -.btn-warning { - color: #fff; - background-color: #f0ad4e; - border-color: #eea236; -} -.btn-warning:hover, -.btn-warning:focus, -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - color: #fff; - background-color: #ec971f; - border-color: #d58512; -} -.btn-warning:active, -.btn-warning.active, -.open > .dropdown-toggle.btn-warning { - background-image: none; -} -.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: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: #f0ad4e; - border-color: #eea236; -} -.btn-warning .badge { - color: #f0ad4e; - background-color: #fff; -} -.btn-danger { - color: #fff; - background-color: #d9534f; - border-color: #d43f3a; -} -.btn-danger:hover, -.btn-danger:focus, -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - color: #fff; - background-color: #c9302c; - border-color: #ac2925; -} -.btn-danger:active, -.btn-danger.active, -.open > .dropdown-toggle.btn-danger { - background-image: none; -} -.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: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: #d9534f; - border-color: #d43f3a; -} -.btn-danger .badge { - color: #d9534f; - background-color: #fff; -} -.btn-link { - font-weight: normal; - color: #428bca; - cursor: pointer; - border-radius: 0; -} -.btn-link, -.btn-link:active, -.btn-link[disabled], -fieldset[disabled] .btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; -} -.btn-link, -.btn-link:hover, -.btn-link:focus, -.btn-link:active { - border-color: transparent; -} -.btn-link:hover, -.btn-link:focus { - color: #2a6496; - text-decoration: underline; - background-color: transparent; -} -.btn-link[disabled]:hover, -fieldset[disabled] .btn-link:hover, -.btn-link[disabled]:focus, -fieldset[disabled] .btn-link:focus { - color: #777; - text-decoration: none; -} -.btn-lg, -.btn-group-lg > .btn { - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -.btn-sm, -.btn-group-sm > .btn { - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-xs, -.btn-group-xs > .btn { - padding: 1px 5px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -.btn-block { - display: block; - width: 100%; -} -.btn-block + .btn-block { - margin-top: 5px; -} -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} -.fade { - opacity: 0; - -webkit-transition: opacity .15s linear; - -o-transition: opacity .15s linear; - transition: opacity .15s linear; -} -.fade.in { - opacity: 1; -} -.collapse { - display: none; -} -.collapse.in { - display: block; -} -tr.collapse.in { - display: table-row; -} -tbody.collapse.in { - display: table-row-group; -} -.collapsing { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height .35s ease; - -o-transition: height .35s ease; - transition: height .35s ease; -} -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: 4px solid; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} -.dropdown { - position: relative; -} -.dropdown-toggle:focus { - outline: 0; -} -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - font-size: 14px; - text-align: left; - list-style: none; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .15); - border-radius: 4px; - -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); - box-shadow: 0 6px 12px rgba(0, 0, 0, .175); -} -.dropdown-menu.pull-right { - right: 0; - left: auto; -} -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.dropdown-menu > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.42857143; - color: #333; - white-space: nowrap; -} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - color: #262626; - text-decoration: none; - background-color: #f5f5f5; -} -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - color: #fff; - text-decoration: none; - background-color: #428bca; - outline: 0; -} -.dropdown-menu > .disabled > a, -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - color: #777; -} -.dropdown-menu > .disabled > a:hover, -.dropdown-menu > .disabled > a:focus { - text-decoration: none; - cursor: not-allowed; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -} -.open > .dropdown-menu { - display: block; -} -.open > a { - outline: 0; -} -.dropdown-menu-right { - right: 0; - left: auto; -} -.dropdown-menu-left { - right: auto; - left: 0; -} -.dropdown-header { - display: block; - padding: 3px 20px; - font-size: 12px; - line-height: 1.42857143; - color: #777; - white-space: nowrap; -} -.dropdown-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 990; -} -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - content: ""; - border-top: 0; - border-bottom: 4px solid; -} -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} -@media (min-width: 768px) { - .navbar-right .dropdown-menu { - right: 0; - left: auto; - } - .navbar-right .dropdown-menu-left { - right: auto; - left: 0; - } -} -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-block; - vertical-align: middle; -} -.btn-group > .btn, -.btn-group-vertical > .btn { - position: relative; - float: left; -} -.btn-group > .btn:hover, -.btn-group-vertical > .btn:hover, -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus, -.btn-group > .btn:active, -.btn-group-vertical > .btn:active, -.btn-group > .btn.active, -.btn-group-vertical > .btn.active { - z-index: 2; -} -.btn-group > .btn:focus, -.btn-group-vertical > .btn:focus { - outline: 0; -} -.btn-group .btn + .btn, -.btn-group .btn + .btn-group, -.btn-group .btn-group + .btn, -.btn-group .btn-group + .btn-group { - margin-left: -1px; -} -.btn-toolbar { - margin-left: -5px; -} -.btn-toolbar .btn-group, -.btn-toolbar .input-group { - float: left; -} -.btn-toolbar > .btn, -.btn-toolbar > .btn-group, -.btn-toolbar > .input-group { - margin-left: 5px; -} -.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { - border-radius: 0; -} -.btn-group > .btn:first-child { - margin-left: 0; -} -.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn:last-child:not(:first-child), -.btn-group > .dropdown-toggle:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group > .btn-group { - float: left; -} -.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group > .btn-group:first-child > .btn:last-child, -.btn-group > .btn-group:first-child > .dropdown-toggle { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.btn-group > .btn-group:last-child > .btn:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; -} -.btn-group > .btn-lg + .dropdown-toggle { - padding-right: 12px; - padding-left: 12px; -} -.btn-group.open .dropdown-toggle { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn-group.open .dropdown-toggle.btn-link { - -webkit-box-shadow: none; - box-shadow: none; -} -.btn .caret { - margin-left: 0; -} -.btn-lg .caret { - border-width: 5px 5px 0; - border-bottom-width: 0; -} -.dropup .btn-lg .caret { - border-width: 0 5px 5px; -} -.btn-group-vertical > .btn, -.btn-group-vertical > .btn-group, -.btn-group-vertical > .btn-group > .btn { - display: block; - float: none; - width: 100%; - max-width: 100%; -} -.btn-group-vertical > .btn-group > .btn { - float: none; -} -.btn-group-vertical > .btn + .btn, -.btn-group-vertical > .btn + .btn-group, -.btn-group-vertical > .btn-group + .btn, -.btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; -} -.btn-group-vertical > .btn:not(:first-child):not(:last-child) { - border-radius: 0; -} -.btn-group-vertical > .btn:first-child:not(:last-child) { - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn:last-child:not(:first-child) { - border-top-left-radius: 0; - border-top-right-radius: 0; - border-bottom-left-radius: 4px; -} -.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { - border-radius: 0; -} -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, -.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.btn-group-justified { - display: table; - width: 100%; - table-layout: fixed; - border-collapse: separate; -} -.btn-group-justified > .btn, -.btn-group-justified > .btn-group { - display: table-cell; - float: none; - width: 1%; -} -.btn-group-justified > .btn-group .btn { - width: 100%; -} -.btn-group-justified > .btn-group .dropdown-menu { - left: auto; -} -[data-toggle="buttons"] > .btn > input[type="radio"], -[data-toggle="buttons"] > .btn > input[type="checkbox"] { - position: absolute; - z-index: -1; - filter: alpha(opacity=0); - opacity: 0; -} -.input-group { - position: relative; - display: table; - border-collapse: separate; -} -.input-group[class*="col-"] { - float: none; - padding-right: 0; - padding-left: 0; -} -.input-group .form-control { - position: relative; - z-index: 2; - float: left; - width: 100%; - margin-bottom: 0; -} -.input-group-lg > .form-control, -.input-group-lg > .input-group-addon, -.input-group-lg > .input-group-btn > .btn { - height: 46px; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; -} -select.input-group-lg > .form-control, -select.input-group-lg > .input-group-addon, -select.input-group-lg > .input-group-btn > .btn { - height: 46px; - line-height: 46px; -} -textarea.input-group-lg > .form-control, -textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn, -select[multiple].input-group-lg > .form-control, -select[multiple].input-group-lg > .input-group-addon, -select[multiple].input-group-lg > .input-group-btn > .btn { - height: auto; -} -.input-group-sm > .form-control, -.input-group-sm > .input-group-addon, -.input-group-sm > .input-group-btn > .btn { - height: 30px; - padding: 5px 10px; - font-size: 12px; - line-height: 1.5; - border-radius: 3px; -} -select.input-group-sm > .form-control, -select.input-group-sm > .input-group-addon, -select.input-group-sm > .input-group-btn > .btn { - height: 30px; - line-height: 30px; -} -textarea.input-group-sm > .form-control, -textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn, -select[multiple].input-group-sm > .form-control, -select[multiple].input-group-sm > .input-group-addon, -select[multiple].input-group-sm > .input-group-btn > .btn { - height: auto; -} -.input-group-addon, -.input-group-btn, -.input-group .form-control { - display: table-cell; -} -.input-group-addon:not(:first-child):not(:last-child), -.input-group-btn:not(:first-child):not(:last-child), -.input-group .form-control:not(:first-child):not(:last-child) { - border-radius: 0; -} -.input-group-addon, -.input-group-btn { - width: 1%; - white-space: nowrap; - vertical-align: middle; -} -.input-group-addon { - padding: 6px 12px; - font-size: 14px; - font-weight: normal; - line-height: 1; - color: #555; - text-align: center; - background-color: #eee; - border: 1px solid #ccc; - border-radius: 4px; -} -.input-group-addon.input-sm { - padding: 5px 10px; - font-size: 12px; - border-radius: 3px; -} -.input-group-addon.input-lg { - padding: 10px 16px; - font-size: 18px; - border-radius: 6px; -} -.input-group-addon input[type="radio"], -.input-group-addon input[type="checkbox"] { - margin-top: 0; -} -.input-group .form-control:first-child, -.input-group-addon:first-child, -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group > .btn, -.input-group-btn:first-child > .dropdown-toggle, -.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.input-group-addon:first-child { - border-right: 0; -} -.input-group .form-control:last-child, -.input-group-addon:last-child, -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group > .btn, -.input-group-btn:last-child > .dropdown-toggle, -.input-group-btn:first-child > .btn:not(:first-child), -.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} -.input-group-addon:last-child { - border-left: 0; -} -.input-group-btn { - position: relative; - font-size: 0; - white-space: nowrap; -} -.input-group-btn > .btn { - position: relative; -} -.input-group-btn > .btn + .btn { - margin-left: -1px; -} -.input-group-btn > .btn:hover, -.input-group-btn > .btn:focus, -.input-group-btn > .btn:active { - z-index: 2; -} -.input-group-btn:first-child > .btn, -.input-group-btn:first-child > .btn-group { - margin-right: -1px; -} -.input-group-btn:last-child > .btn, -.input-group-btn:last-child > .btn-group { - margin-left: -1px; -} -.nav { - padding-left: 0; - margin-bottom: 0; - list-style: none; -} -.nav > li { - position: relative; - display: block; -} -.nav > li > a { - position: relative; - display: block; - padding: 10px 15px; -} -.nav > li > a:hover, -.nav > li > a:focus { - text-decoration: none; - background-color: #eee; -} -.nav > li.disabled > a { - color: #777; -} -.nav > li.disabled > a:hover, -.nav > li.disabled > a:focus { - color: #777; - text-decoration: none; - cursor: not-allowed; - background-color: transparent; -} -.nav .open > a, -.nav .open > a:hover, -.nav .open > a:focus { - background-color: #eee; - border-color: #428bca; -} -.nav .nav-divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} -.nav > li > a > img { - max-width: none; -} -.nav-tabs { - border-bottom: 1px solid #ddd; -} -.nav-tabs > li { - float: left; - margin-bottom: -1px; -} -.nav-tabs > li > a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - border-radius: 4px 4px 0 0; -} -.nav-tabs > li > a:hover { - border-color: #eee #eee #ddd; -} -.nav-tabs > li.active > a, -.nav-tabs > li.active > a:hover, -.nav-tabs > li.active > a:focus { - color: #555; - cursor: default; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} -.nav-tabs.nav-justified { - width: 100%; - border-bottom: 0; -} -.nav-tabs.nav-justified > li { - float: none; -} -.nav-tabs.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-tabs.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-tabs.nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs.nav-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs.nav-justified > .active > a, -.nav-tabs.nav-justified > .active > a:hover, -.nav-tabs.nav-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs.nav-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs.nav-justified > .active > a, - .nav-tabs.nav-justified > .active > a:hover, - .nav-tabs.nav-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.nav-pills > li { - float: left; -} -.nav-pills > li > a { - border-radius: 4px; -} -.nav-pills > li + li { - margin-left: 2px; -} -.nav-pills > li.active > a, -.nav-pills > li.active > a:hover, -.nav-pills > li.active > a:focus { - color: #fff; - background-color: #428bca; -} -.nav-stacked > li { - float: none; -} -.nav-stacked > li + li { - margin-top: 2px; - margin-left: 0; -} -.nav-justified { - width: 100%; -} -.nav-justified > li { - float: none; -} -.nav-justified > li > a { - margin-bottom: 5px; - text-align: center; -} -.nav-justified > .dropdown .dropdown-menu { - top: auto; - left: auto; -} -@media (min-width: 768px) { - .nav-justified > li { - display: table-cell; - width: 1%; - } - .nav-justified > li > a { - margin-bottom: 0; - } -} -.nav-tabs-justified { - border-bottom: 0; -} -.nav-tabs-justified > li > a { - margin-right: 0; - border-radius: 4px; -} -.nav-tabs-justified > .active > a, -.nav-tabs-justified > .active > a:hover, -.nav-tabs-justified > .active > a:focus { - border: 1px solid #ddd; -} -@media (min-width: 768px) { - .nav-tabs-justified > li > a { - border-bottom: 1px solid #ddd; - border-radius: 4px 4px 0 0; - } - .nav-tabs-justified > .active > a, - .nav-tabs-justified > .active > a:hover, - .nav-tabs-justified > .active > a:focus { - border-bottom-color: #fff; - } -} -.tab-content > .tab-pane { - display: none; -} -.tab-content > .active { - display: block; -} -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} -.navbar-collapse { - padding-right: 15px; - padding-left: 15px; - overflow-x: visible; - -webkit-overflow-scrolling: touch; - border-top: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); -} -.navbar-collapse.in { - overflow-y: auto; -} -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } - .navbar-collapse.in { - overflow-y: visible; - } - .navbar-fixed-top .navbar-collapse, - .navbar-static-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - padding-right: 0; - padding-left: 0; - } -} -.navbar-fixed-top .navbar-collapse, -.navbar-fixed-bottom .navbar-collapse { - max-height: 340px; -} -@media (max-width: 480px) and (orientation: landscape) { - .navbar-fixed-top .navbar-collapse, - .navbar-fixed-bottom .navbar-collapse { - max-height: 200px; - } -} -.container > .navbar-header, -.container-fluid > .navbar-header, -.container > .navbar-collapse, -.container-fluid > .navbar-collapse { - margin-right: -15px; - margin-left: -15px; -} -@media (min-width: 768px) { - .container > .navbar-header, - .container-fluid > .navbar-header, - .container > .navbar-collapse, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; - } -} -.navbar-static-top { - z-index: 1000; - border-width: 0 0 1px; -} -@media (min-width: 768px) { - .navbar-static-top { - border-radius: 0; - } -} -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -@media (min-width: 768px) { - .navbar-fixed-top, - .navbar-fixed-bottom { - border-radius: 0; - } -} -.navbar-fixed-top { - top: 0; - border-width: 0 0 1px; -} -.navbar-fixed-bottom { - bottom: 0; - margin-bottom: 0; - border-width: 1px 0 0; -} -.navbar-brand { - float: left; - height: 50px; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; -} -.navbar-brand:hover, -.navbar-brand:focus { - text-decoration: none; -} -@media (min-width: 768px) { - .navbar > .container .navbar-brand, - .navbar > .container-fluid .navbar-brand { - margin-left: -15px; - } -} -.navbar-toggle { - position: relative; - float: right; - padding: 9px 10px; - margin-top: 8px; - margin-right: 15px; - margin-bottom: 8px; - background-color: transparent; - background-image: none; - border: 1px solid transparent; - border-radius: 4px; -} -.navbar-toggle:focus { - outline: 0; -} -.navbar-toggle .icon-bar { - display: block; - width: 22px; - height: 2px; - border-radius: 1px; -} -.navbar-toggle .icon-bar + .icon-bar { - margin-top: 4px; -} -@media (min-width: 768px) { - .navbar-toggle { - display: none; - } -} -.navbar-nav { - margin: 7.5px -15px; -} -.navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; - line-height: 20px; -} -@media (max-width: 767px) { - .navbar-nav .open .dropdown-menu { - position: static; - float: none; - width: auto; - margin-top: 0; - background-color: transparent; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-nav .open .dropdown-menu > li > a, - .navbar-nav .open .dropdown-menu .dropdown-header { - padding: 5px 15px 5px 25px; - } - .navbar-nav .open .dropdown-menu > li > a { - line-height: 20px; - } - .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-nav .open .dropdown-menu > li > a:focus { - background-image: none; - } -} -@media (min-width: 768px) { - .navbar-nav { - float: left; - margin: 0; - } - .navbar-nav > li { - float: left; - } - .navbar-nav > li > a { - padding-top: 15px; - padding-bottom: 15px; - } - .navbar-nav.navbar-right:last-child { - margin-right: -15px; - } -} -@media (min-width: 768px) { - .navbar-left { - float: left !important; - } - .navbar-right { - float: right !important; - } -} -.navbar-form { - padding: 10px 15px; - margin-top: 8px; - margin-right: -15px; - margin-bottom: 8px; - margin-left: -15px; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); -} -@media (min-width: 768px) { - .navbar-form .form-group { - display: inline-block; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .navbar-form .input-group { - display: inline-table; - vertical-align: middle; - } - .navbar-form .input-group .input-group-addon, - .navbar-form .input-group .input-group-btn, - .navbar-form .input-group .form-control { - width: auto; - } - .navbar-form .input-group > .form-control { - width: 100%; - } - .navbar-form .control-label { - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio, - .navbar-form .checkbox { - display: inline-block; - margin-top: 0; - margin-bottom: 0; - vertical-align: middle; - } - .navbar-form .radio label, - .navbar-form .checkbox label { - padding-left: 0; - } - .navbar-form .radio input[type="radio"], - .navbar-form .checkbox input[type="checkbox"] { - position: relative; - margin-left: 0; - } - .navbar-form .has-feedback .form-control-feedback { - top: 0; - } -} -@media (max-width: 767px) { - .navbar-form .form-group { - margin-bottom: 5px; - } -} -@media (min-width: 768px) { - .navbar-form { - width: auto; - padding-top: 0; - padding-bottom: 0; - margin-right: 0; - margin-left: 0; - border: 0; - -webkit-box-shadow: none; - box-shadow: none; - } - .navbar-form.navbar-right:last-child { - margin-right: -15px; - } -} -.navbar-nav > li > .dropdown-menu { - margin-top: 0; - border-top-left-radius: 0; - border-top-right-radius: 0; -} -.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.navbar-btn { - margin-top: 8px; - margin-bottom: 8px; -} -.navbar-btn.btn-sm { - margin-top: 10px; - margin-bottom: 10px; -} -.navbar-btn.btn-xs { - margin-top: 14px; - margin-bottom: 14px; -} -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-right: 15px; - margin-left: 15px; - } - .navbar-text.navbar-right:last-child { - margin-right: 0; - } -} -.navbar-default { - background-color: #f8f8f8; - border-color: #e7e7e7; -} -.navbar-default .navbar-brand { - color: #777; -} -.navbar-default .navbar-brand:hover, -.navbar-default .navbar-brand:focus { - color: #5e5e5e; - background-color: transparent; -} -.navbar-default .navbar-text { - color: #777; -} -.navbar-default .navbar-nav > li > a { - color: #777; -} -.navbar-default .navbar-nav > li > a:hover, -.navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; -} -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:hover, -.navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; -} -.navbar-default .navbar-nav > .disabled > a, -.navbar-default .navbar-nav > .disabled > a:hover, -.navbar-default .navbar-nav > .disabled > a:focus { - color: #ccc; - background-color: transparent; -} -.navbar-default .navbar-toggle { - border-color: #ddd; -} -.navbar-default .navbar-toggle:hover, -.navbar-default .navbar-toggle:focus { - background-color: #ddd; -} -.navbar-default .navbar-toggle .icon-bar { - background-color: #888; -} -.navbar-default .navbar-collapse, -.navbar-default .navbar-form { - border-color: #e7e7e7; -} -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .open > a:hover, -.navbar-default .navbar-nav > .open > a:focus { - color: #555; - background-color: #e7e7e7; -} -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { - color: #333; - background-color: transparent; - } - .navbar-default .navbar-nav .open .dropdown-menu > .active > a, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #555; - background-color: #e7e7e7; - } - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #ccc; - background-color: transparent; - } -} -.navbar-default .navbar-link { - color: #777; -} -.navbar-default .navbar-link:hover { - color: #333; -} -.navbar-default .btn-link { - color: #777; -} -.navbar-default .btn-link:hover, -.navbar-default .btn-link:focus { - color: #333; -} -.navbar-default .btn-link[disabled]:hover, -fieldset[disabled] .navbar-default .btn-link:hover, -.navbar-default .btn-link[disabled]:focus, -fieldset[disabled] .navbar-default .btn-link:focus { - color: #ccc; -} -.navbar-inverse { - background-color: #222; - border-color: #080808; -} -.navbar-inverse .navbar-brand { - color: #777; -} -.navbar-inverse .navbar-brand:hover, -.navbar-inverse .navbar-brand:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-text { - color: #777; -} -.navbar-inverse .navbar-nav > li > a { - color: #777; -} -.navbar-inverse .navbar-nav > li > a:hover, -.navbar-inverse .navbar-nav > li > a:focus { - color: #fff; - background-color: transparent; -} -.navbar-inverse .navbar-nav > .active > a, -.navbar-inverse .navbar-nav > .active > a:hover, -.navbar-inverse .navbar-nav > .active > a:focus { - color: #fff; - background-color: #080808; -} -.navbar-inverse .navbar-nav > .disabled > a, -.navbar-inverse .navbar-nav > .disabled > a:hover, -.navbar-inverse .navbar-nav > .disabled > a:focus { - color: #444; - background-color: transparent; -} -.navbar-inverse .navbar-toggle { - border-color: #333; -} -.navbar-inverse .navbar-toggle:hover, -.navbar-inverse .navbar-toggle:focus { - background-color: #333; -} -.navbar-inverse .navbar-toggle .icon-bar { - background-color: #fff; -} -.navbar-inverse .navbar-collapse, -.navbar-inverse .navbar-form { - border-color: #101010; -} -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .open > a:hover, -.navbar-inverse .navbar-nav > .open > a:focus { - color: #fff; - background-color: #080808; -} -@media (max-width: 767px) { - .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { - border-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu .divider { - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { - color: #777; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { - color: #fff; - background-color: transparent; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-color: #080808; - } - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, - .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { - color: #444; - background-color: transparent; - } -} -.navbar-inverse .navbar-link { - color: #777; -} -.navbar-inverse .navbar-link:hover { - color: #fff; -} -.navbar-inverse .btn-link { - color: #777; -} -.navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link:focus { - color: #fff; -} -.navbar-inverse .btn-link[disabled]:hover, -fieldset[disabled] .navbar-inverse .btn-link:hover, -.navbar-inverse .btn-link[disabled]:focus, -fieldset[disabled] .navbar-inverse .btn-link:focus { - color: #444; -} -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} -.breadcrumb > li { - display: inline-block; -} -.breadcrumb > li + li:before { - padding: 0 5px; - color: #ccc; - content: "/\00a0"; -} -.breadcrumb > .active { - color: #777; -} -.pagination { - display: inline-block; - padding-left: 0; - margin: 20px 0; - border-radius: 4px; -} -.pagination > li { - display: inline; -} -.pagination > li > a, -.pagination > li > span { - position: relative; - float: left; - padding: 6px 12px; - margin-left: -1px; - line-height: 1.42857143; - color: #428bca; - text-decoration: none; - background-color: #fff; - border: 1px solid #ddd; -} -.pagination > li:first-child > a, -.pagination > li:first-child > span { - margin-left: 0; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} -.pagination > li:last-child > a, -.pagination > li:last-child > span { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -.pagination > li > a:hover, -.pagination > li > span:hover, -.pagination > li > a:focus, -.pagination > li > span:focus { - color: #2a6496; - background-color: #eee; - border-color: #ddd; -} -.pagination > .active > a, -.pagination > .active > span, -.pagination > .active > a:hover, -.pagination > .active > span:hover, -.pagination > .active > a:focus, -.pagination > .active > span:focus { - z-index: 2; - color: #fff; - cursor: default; - background-color: #428bca; - border-color: #428bca; -} -.pagination > .disabled > span, -.pagination > .disabled > span:hover, -.pagination > .disabled > span:focus, -.pagination > .disabled > a, -.pagination > .disabled > a:hover, -.pagination > .disabled > a:focus { - color: #777; - cursor: not-allowed; - background-color: #fff; - border-color: #ddd; -} -.pagination-lg > li > a, -.pagination-lg > li > span { - padding: 10px 16px; - font-size: 18px; -} -.pagination-lg > li:first-child > a, -.pagination-lg > li:first-child > span { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} -.pagination-lg > li:last-child > a, -.pagination-lg > li:last-child > span { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} -.pagination-sm > li > a, -.pagination-sm > li > span { - padding: 5px 10px; - font-size: 12px; -} -.pagination-sm > li:first-child > a, -.pagination-sm > li:first-child > span { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.pagination-sm > li:last-child > a, -.pagination-sm > li:last-child > span { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} -.pager { - padding-left: 0; - margin: 20px 0; - text-align: center; - list-style: none; -} -.pager li { - display: inline; -} -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 15px; -} -.pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #eee; -} -.pager .next > a, -.pager .next > span { - float: right; -} -.pager .previous > a, -.pager .previous > span { - float: left; -} -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > a:focus, -.pager .disabled > span { - color: #777; - cursor: not-allowed; - background-color: #fff; -} -.label { - display: inline; - padding: .2em .6em .3em; - font-size: 75%; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: .25em; -} -a.label:hover, -a.label:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -.label:empty { - display: none; -} -.btn .label { - position: relative; - top: -1px; -} -.label-default { - background-color: #777; -} -.label-default[href]:hover, -.label-default[href]:focus { - background-color: #5e5e5e; -} -.label-primary { - background-color: #428bca; -} -.label-primary[href]:hover, -.label-primary[href]:focus { - background-color: #3071a9; -} -.label-success { - background-color: #5cb85c; -} -.label-success[href]:hover, -.label-success[href]:focus { - background-color: #449d44; -} -.label-info { - background-color: #5bc0de; -} -.label-info[href]:hover, -.label-info[href]:focus { - background-color: #31b0d5; -} -.label-warning { - background-color: #f0ad4e; -} -.label-warning[href]:hover, -.label-warning[href]:focus { - background-color: #ec971f; -} -.label-danger { - background-color: #d9534f; -} -.label-danger[href]:hover, -.label-danger[href]:focus { - background-color: #c9302c; -} -.badge { - display: inline-block; - min-width: 10px; - padding: 3px 7px; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: #fff; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - background-color: #777; - border-radius: 10px; -} -.badge:empty { - display: none; -} -.btn .badge { - position: relative; - top: -1px; -} -.btn-xs .badge { - top: 0; - padding: 1px 5px; -} -a.badge:hover, -a.badge:focus { - color: #fff; - text-decoration: none; - cursor: pointer; -} -a.list-group-item.active > .badge, -.nav-pills > .active > a > .badge { - color: #428bca; - background-color: #fff; -} -.nav-pills > li > a > .badge { - margin-left: 3px; -} -.jumbotron { - padding: 30px; - margin-bottom: 30px; - color: inherit; - background-color: #eee; -} -.jumbotron h1, -.jumbotron .h1 { - color: inherit; -} -.jumbotron p { - margin-bottom: 15px; - font-size: 21px; - font-weight: 200; -} -.jumbotron > hr { - border-top-color: #d5d5d5; -} -.container .jumbotron { - border-radius: 6px; -} -.jumbotron .container { - max-width: 100%; -} -@media screen and (min-width: 768px) { - .jumbotron { - padding-top: 48px; - padding-bottom: 48px; - } - .container .jumbotron { - padding-right: 60px; - padding-left: 60px; - } - .jumbotron h1, - .jumbotron .h1 { - font-size: 63px; - } -} -.thumbnail { - display: block; - padding: 4px; - margin-bottom: 20px; - line-height: 1.42857143; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 4px; - -webkit-transition: all .2s ease-in-out; - -o-transition: all .2s ease-in-out; - transition: all .2s ease-in-out; -} -.thumbnail > img, -.thumbnail a > img { - margin-right: auto; - margin-left: auto; -} -a.thumbnail:hover, -a.thumbnail:focus, -a.thumbnail.active { - border-color: #428bca; -} -.thumbnail .caption { - padding: 9px; - color: #333; -} -.alert { - padding: 15px; - margin-bottom: 20px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert .alert-link { - font-weight: bold; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable, -.alert-dismissible { - padding-right: 35px; -} -.alert-dismissable .close, -.alert-dismissible .close { - position: relative; - top: -2px; - right: -21px; - color: inherit; -} -.alert-success { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success hr { - border-top-color: #c9e2b3; -} -.alert-success .alert-link { - color: #2b542c; -} -.alert-info { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.alert-info hr { - border-top-color: #a6e1ec; -} -.alert-info .alert-link { - color: #245269; -} -.alert-warning { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.alert-warning hr { - border-top-color: #f7e1b5; -} -.alert-warning .alert-link { - color: #66512c; -} -.alert-danger { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.alert-danger hr { - border-top-color: #e4b9c0; -} -.alert-danger .alert-link { - color: #843534; -} -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@-o-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f5f5f5; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); -} -.progress-bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - line-height: 20px; - color: #fff; - text-align: center; - background-color: #428bca; - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); - -webkit-transition: width .6s ease; - -o-transition: width .6s ease; - transition: width .6s ease; -} -.progress-striped .progress-bar, -.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); - -webkit-background-size: 40px 40px; - background-size: 40px 40px; -} -.progress.active .progress-bar, -.progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} -.progress-bar[aria-valuenow="1"], -.progress-bar[aria-valuenow="2"] { - min-width: 30px; -} -.progress-bar[aria-valuenow="0"] { - min-width: 30px; - color: #777; - background-color: transparent; - background-image: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.progress-bar-success { - background-color: #5cb85c; -} -.progress-striped .progress-bar-success { - 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); -} -.progress-bar-info { - background-color: #5bc0de; -} -.progress-striped .progress-bar-info { - 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); -} -.progress-bar-warning { - background-color: #f0ad4e; -} -.progress-striped .progress-bar-warning { - 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); -} -.progress-bar-danger { - background-color: #d9534f; -} -.progress-striped .progress-bar-danger { - 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); -} -.media, -.media-body { - overflow: hidden; - zoom: 1; -} -.media, -.media .media { - margin-top: 15px; -} -.media:first-child { - margin-top: 0; -} -.media-object { - display: block; -} -.media-heading { - margin: 0 0 5px; -} -.media > .pull-left { - margin-right: 10px; -} -.media > .pull-right { - margin-left: 10px; -} -.media-list { - padding-left: 0; - list-style: none; -} -.list-group { - padding-left: 0; - margin-bottom: 20px; -} -.list-group-item { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid #ddd; -} -.list-group-item:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; -} -.list-group-item > .badge { - float: right; -} -.list-group-item > .badge + .badge { - margin-right: 5px; -} -a.list-group-item { - color: #555; -} -a.list-group-item .list-group-item-heading { - color: #333; -} -a.list-group-item:hover, -a.list-group-item:focus { - color: #555; - text-decoration: none; - background-color: #f5f5f5; -} -.list-group-item.disabled, -.list-group-item.disabled:hover, -.list-group-item.disabled:focus { - color: #777; - background-color: #eee; -} -.list-group-item.disabled .list-group-item-heading, -.list-group-item.disabled:hover .list-group-item-heading, -.list-group-item.disabled:focus .list-group-item-heading { - color: inherit; -} -.list-group-item.disabled .list-group-item-text, -.list-group-item.disabled:hover .list-group-item-text, -.list-group-item.disabled:focus .list-group-item-text { - color: #777; -} -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: 2; - color: #fff; - background-color: #428bca; - border-color: #428bca; -} -.list-group-item.active .list-group-item-heading, -.list-group-item.active:hover .list-group-item-heading, -.list-group-item.active:focus .list-group-item-heading, -.list-group-item.active .list-group-item-heading > small, -.list-group-item.active:hover .list-group-item-heading > small, -.list-group-item.active:focus .list-group-item-heading > small, -.list-group-item.active .list-group-item-heading > .small, -.list-group-item.active:hover .list-group-item-heading > .small, -.list-group-item.active:focus .list-group-item-heading > .small { - color: inherit; -} -.list-group-item.active .list-group-item-text, -.list-group-item.active:hover .list-group-item-text, -.list-group-item.active:focus .list-group-item-text { - color: #e1edf7; -} -.list-group-item-success { - color: #3c763d; - background-color: #dff0d8; -} -a.list-group-item-success { - color: #3c763d; -} -a.list-group-item-success .list-group-item-heading { - color: inherit; -} -a.list-group-item-success:hover, -a.list-group-item-success:focus { - color: #3c763d; - background-color: #d0e9c6; -} -a.list-group-item-success.active, -a.list-group-item-success.active:hover, -a.list-group-item-success.active:focus { - color: #fff; - background-color: #3c763d; - border-color: #3c763d; -} -.list-group-item-info { - color: #31708f; - background-color: #d9edf7; -} -a.list-group-item-info { - color: #31708f; -} -a.list-group-item-info .list-group-item-heading { - color: inherit; -} -a.list-group-item-info:hover, -a.list-group-item-info:focus { - color: #31708f; - background-color: #c4e3f3; -} -a.list-group-item-info.active, -a.list-group-item-info.active:hover, -a.list-group-item-info.active:focus { - color: #fff; - background-color: #31708f; - border-color: #31708f; -} -.list-group-item-warning { - color: #8a6d3b; - background-color: #fcf8e3; -} -a.list-group-item-warning { - color: #8a6d3b; -} -a.list-group-item-warning .list-group-item-heading { - color: inherit; -} -a.list-group-item-warning:hover, -a.list-group-item-warning:focus { - color: #8a6d3b; - background-color: #faf2cc; -} -a.list-group-item-warning.active, -a.list-group-item-warning.active:hover, -a.list-group-item-warning.active:focus { - color: #fff; - background-color: #8a6d3b; - border-color: #8a6d3b; -} -.list-group-item-danger { - color: #a94442; - background-color: #f2dede; -} -a.list-group-item-danger { - color: #a94442; -} -a.list-group-item-danger .list-group-item-heading { - color: inherit; -} -a.list-group-item-danger:hover, -a.list-group-item-danger:focus { - color: #a94442; - background-color: #ebcccc; -} -a.list-group-item-danger.active, -a.list-group-item-danger.active:hover, -a.list-group-item-danger.active:focus { - color: #fff; - background-color: #a94442; - border-color: #a94442; -} -.list-group-item-heading { - margin-top: 0; - margin-bottom: 5px; -} -.list-group-item-text { - margin-bottom: 0; - line-height: 1.3; -} -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: 0 1px 1px rgba(0, 0, 0, .05); -} -.panel-body { - padding: 15px; -} -.panel-heading { - padding: 10px 15px; - border-bottom: 1px solid transparent; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel-heading > .dropdown .dropdown-toggle { - color: inherit; -} -.panel-title { - margin-top: 0; - margin-bottom: 0; - font-size: 16px; - color: inherit; -} -.panel-title > a { - color: inherit; -} -.panel-footer { - padding: 10px 15px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .list-group { - margin-bottom: 0; -} -.panel > .list-group .list-group-item { - border-width: 1px 0; - border-radius: 0; -} -.panel > .list-group:first-child .list-group-item:first-child { - border-top: 0; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .list-group:last-child .list-group-item:last-child { - border-bottom: 0; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel-heading + .list-group .list-group-item:first-child { - border-top-width: 0; -} -.list-group + .panel-footer { - border-top-width: 0; -} -.panel > .table, -.panel > .table-responsive > .table, -.panel > .panel-collapse > .table { - margin-bottom: 0; -} -.panel > .table:first-child, -.panel > .table-responsive:first-child > .table:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { - border-top-left-radius: 3px; -} -.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, -.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, -.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, -.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { - border-top-right-radius: 3px; -} -.panel > .table:last-child, -.panel > .table-responsive:last-child > .table:last-child { - border-bottom-right-radius: 3px; - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { - border-bottom-left-radius: 3px; -} -.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, -.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, -.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, -.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { - border-bottom-right-radius: 3px; -} -.panel > .panel-body + .table, -.panel > .panel-body + .table-responsive { - border-top: 1px solid #ddd; -} -.panel > .table > tbody:first-child > tr:first-child th, -.panel > .table > tbody:first-child > tr:first-child td { - border-top: 0; -} -.panel > .table-bordered, -.panel > .table-responsive > .table-bordered { - border: 0; -} -.panel > .table-bordered > thead > tr > th:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, -.panel > .table-bordered > tbody > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, -.panel > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, -.panel > .table-bordered > thead > tr > td:first-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, -.panel > .table-bordered > tbody > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, -.panel > .table-bordered > tfoot > tr > td:first-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { - border-left: 0; -} -.panel > .table-bordered > thead > tr > th:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, -.panel > .table-bordered > tbody > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, -.panel > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, -.panel > .table-bordered > thead > tr > td:last-child, -.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, -.panel > .table-bordered > tbody > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, -.panel > .table-bordered > tfoot > tr > td:last-child, -.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { - border-right: 0; -} -.panel > .table-bordered > thead > tr:first-child > td, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, -.panel > .table-bordered > tbody > tr:first-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, -.panel > .table-bordered > thead > tr:first-child > th, -.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, -.panel > .table-bordered > tbody > tr:first-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { - border-bottom: 0; -} -.panel > .table-bordered > tbody > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, -.panel > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, -.panel > .table-bordered > tbody > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, -.panel > .table-bordered > tfoot > tr:last-child > th, -.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { - border-bottom: 0; -} -.panel > .table-responsive { - margin-bottom: 0; - border: 0; -} -.panel-group { - margin-bottom: 20px; -} -.panel-group .panel { - margin-bottom: 0; - border-radius: 4px; -} -.panel-group .panel + .panel { - margin-top: 5px; -} -.panel-group .panel-heading { - border-bottom: 0; -} -.panel-group .panel-heading + .panel-collapse > .panel-body { - border-top: 1px solid #ddd; -} -.panel-group .panel-footer { - border-top: 0; -} -.panel-group .panel-footer + .panel-collapse .panel-body { - border-bottom: 1px solid #ddd; -} -.panel-default { - border-color: #ddd; -} -.panel-default > .panel-heading { - color: #333; - background-color: #f5f5f5; - border-color: #ddd; -} -.panel-default > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ddd; -} -.panel-default > .panel-heading .badge { - color: #f5f5f5; - background-color: #333; -} -.panel-default > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ddd; -} -.panel-primary { - border-color: #428bca; -} -.panel-primary > .panel-heading { - color: #fff; - background-color: #428bca; - border-color: #428bca; -} -.panel-primary > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #428bca; -} -.panel-primary > .panel-heading .badge { - color: #428bca; - background-color: #fff; -} -.panel-primary > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #428bca; -} -.panel-success { - border-color: #d6e9c6; -} -.panel-success > .panel-heading { - color: #3c763d; - background-color: #dff0d8; - border-color: #d6e9c6; -} -.panel-success > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #d6e9c6; -} -.panel-success > .panel-heading .badge { - color: #dff0d8; - background-color: #3c763d; -} -.panel-success > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #d6e9c6; -} -.panel-info { - border-color: #bce8f1; -} -.panel-info > .panel-heading { - color: #31708f; - background-color: #d9edf7; - border-color: #bce8f1; -} -.panel-info > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #bce8f1; -} -.panel-info > .panel-heading .badge { - color: #d9edf7; - background-color: #31708f; -} -.panel-info > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #bce8f1; -} -.panel-warning { - border-color: #faebcc; -} -.panel-warning > .panel-heading { - color: #8a6d3b; - background-color: #fcf8e3; - border-color: #faebcc; -} -.panel-warning > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #faebcc; -} -.panel-warning > .panel-heading .badge { - color: #fcf8e3; - background-color: #8a6d3b; -} -.panel-warning > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #faebcc; -} -.panel-danger { - border-color: #ebccd1; -} -.panel-danger > .panel-heading { - color: #a94442; - background-color: #f2dede; - border-color: #ebccd1; -} -.panel-danger > .panel-heading + .panel-collapse > .panel-body { - border-top-color: #ebccd1; -} -.panel-danger > .panel-heading .badge { - color: #f2dede; - background-color: #a94442; -} -.panel-danger > .panel-footer + .panel-collapse > .panel-body { - border-bottom-color: #ebccd1; -} -.embed-responsive { - position: relative; - display: block; - height: 0; - padding: 0; - overflow: hidden; -} -.embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} -.embed-responsive.embed-responsive-16by9 { - padding-bottom: 56.25%; -} -.embed-responsive.embed-responsive-4by3 { - padding-bottom: 75%; -} -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); -} -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, .15); -} -.well-lg { - padding: 24px; - border-radius: 6px; -} -.well-sm { - padding: 9px; - border-radius: 3px; -} -.close { - float: right; - font-size: 21px; - font-weight: bold; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - filter: alpha(opacity=20); - opacity: .2; -} -.close:hover, -.close:focus { - color: #000; - text-decoration: none; - cursor: pointer; - filter: alpha(opacity=50); - opacity: .5; -} -button.close { - -webkit-appearance: none; - padding: 0; - cursor: pointer; - background: transparent; - border: 0; -} -.modal-open { - overflow: hidden; -} -.modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - -webkit-overflow-scrolling: touch; - outline: 0; -} -.modal.fade .modal-dialog { - -webkit-transition: -webkit-transform .3s ease-out; - -o-transition: -o-transform .3s ease-out; - transition: transform .3s ease-out; - -webkit-transform: translate3d(0, -25%, 0); - -o-transform: translate3d(0, -25%, 0); - transform: translate3d(0, -25%, 0); -} -.modal.in .modal-dialog { - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -.modal-open .modal { - overflow-x: hidden; - overflow-y: auto; -} -.modal-dialog { - position: relative; - width: auto; - margin: 10px; -} -.modal-content { - position: relative; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - outline: 0; - -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); - box-shadow: 0 3px 9px rgba(0, 0, 0, .5); -} -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; -} -.modal-backdrop.fade { - filter: alpha(opacity=0); - opacity: 0; -} -.modal-backdrop.in { - filter: alpha(opacity=50); - opacity: .5; -} -.modal-header { - min-height: 16.42857143px; - padding: 15px; - border-bottom: 1px solid #e5e5e5; -} -.modal-header .close { - margin-top: -2px; -} -.modal-title { - margin: 0; - line-height: 1.42857143; -} -.modal-body { - position: relative; - padding: 15px; -} -.modal-footer { - padding: 15px; - text-align: right; - border-top: 1px solid #e5e5e5; -} -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} -.modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; -} -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - .modal-content { - -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - box-shadow: 0 5px 15px rgba(0, 0, 0, .5); - } - .modal-sm { - width: 300px; - } -} -@media (min-width: 992px) { - .modal-lg { - width: 900px; - } -} -.tooltip { - position: absolute; - z-index: 1070; - display: block; - font-size: 12px; - line-height: 1.4; - visibility: visible; - filter: alpha(opacity=0); - opacity: 0; -} -.tooltip.in { - filter: alpha(opacity=90); - opacity: .9; -} -.tooltip.top { - padding: 5px 0; - margin-top: -3px; -} -.tooltip.right { - padding: 0 5px; - margin-left: 3px; -} -.tooltip.bottom { - padding: 5px 0; - margin-top: 3px; -} -.tooltip.left { - padding: 0 5px; - margin-left: -3px; -} -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #fff; - text-align: center; - text-decoration: none; - background-color: #000; - border-radius: 4px; -} -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-left .tooltip-arrow { - bottom: 0; - left: 5px; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.top-right .tooltip-arrow { - right: 5px; - bottom: 0; - border-width: 5px 5px 0; - border-top-color: #000; -} -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-width: 5px 5px 5px 0; - border-right-color: #000; -} -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-width: 5px 0 5px 5px; - border-left-color: #000; -} -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-left .tooltip-arrow { - top: 0; - left: 5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.tooltip.bottom-right .tooltip-arrow { - top: 0; - right: 5px; - border-width: 0 5px 5px; - border-bottom-color: #000; -} -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: none; - max-width: 276px; - padding: 1px; - text-align: left; - white-space: normal; - background-color: #fff; - -webkit-background-clip: padding-box; - background-clip: padding-box; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, .2); - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); - box-shadow: 0 5px 10px rgba(0, 0, 0, .2); -} -.popover.top { - margin-top: -10px; -} -.popover.right { - margin-left: 10px; -} -.popover.bottom { - margin-top: 10px; -} -.popover.left { - margin-left: -10px; -} -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-radius: 5px 5px 0 0; -} -.popover-content { - padding: 9px 14px; -} -.popover > .arrow, -.popover > .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} -.popover > .arrow { - border-width: 11px; -} -.popover > .arrow:after { - content: ""; - border-width: 10px; -} -.popover.top > .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, .25); - border-bottom-width: 0; -} -.popover.top > .arrow:after { - bottom: 1px; - margin-left: -10px; - content: " "; - border-top-color: #fff; - border-bottom-width: 0; -} -.popover.right > .arrow { - top: 50%; - left: -11px; - margin-top: -11px; - border-right-color: #999; - border-right-color: rgba(0, 0, 0, .25); - border-left-width: 0; -} -.popover.right > .arrow:after { - bottom: -10px; - left: 1px; - content: " "; - border-right-color: #fff; - border-left-width: 0; -} -.popover.bottom > .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-top-width: 0; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, .25); -} -.popover.bottom > .arrow:after { - top: 1px; - margin-left: -10px; - content: " "; - border-top-width: 0; - border-bottom-color: #fff; -} -.popover.left > .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-right-width: 0; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, .25); -} -.popover.left > .arrow:after { - right: 1px; - bottom: -10px; - content: " "; - border-right-width: 0; - border-left-color: #fff; -} -.carousel { - position: relative; -} -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: .6s ease-in-out left; - -o-transition: .6s ease-in-out left; - transition: .6s ease-in-out left; -} -.carousel-inner > .item > img, -.carousel-inner > .item > a > img { - line-height: 1; -} -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} -.carousel-inner > .active { - left: 0; -} -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} -.carousel-inner > .next { - left: 100%; -} -.carousel-inner > .prev { - left: -100%; -} -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} -.carousel-inner > .active.left { - left: -100%; -} -.carousel-inner > .active.right { - left: 100%; -} -.carousel-control { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 15%; - font-size: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); - filter: alpha(opacity=50); - opacity: .5; -} -.carousel-control.left { - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control.right { - right: 0; - left: auto; - background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); - background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); - background-repeat: repeat-x; -} -.carousel-control:hover, -.carousel-control:focus { - color: #fff; - text-decoration: none; - filter: alpha(opacity=90); - outline: 0; - opacity: .9; -} -.carousel-control .icon-prev, -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-left, -.carousel-control .glyphicon-chevron-right { - position: absolute; - top: 50%; - z-index: 5; - display: inline-block; -} -.carousel-control .icon-prev, -.carousel-control .glyphicon-chevron-left { - left: 50%; - margin-left: -10px; -} -.carousel-control .icon-next, -.carousel-control .glyphicon-chevron-right { - right: 50%; - margin-right: -10px; -} -.carousel-control .icon-prev, -.carousel-control .icon-next { - width: 20px; - height: 20px; - margin-top: -10px; - font-family: serif; -} -.carousel-control .icon-prev:before { - content: '\2039'; -} -.carousel-control .icon-next:before { - content: '\203a'; -} -.carousel-indicators { - position: absolute; - bottom: 10px; - left: 50%; - z-index: 15; - width: 60%; - padding-left: 0; - margin-left: -30%; - text-align: center; - list-style: none; -} -.carousel-indicators li { - display: inline-block; - width: 10px; - height: 10px; - margin: 1px; - text-indent: -999px; - cursor: pointer; - background-color: #000 \9; - background-color: rgba(0, 0, 0, 0); - border: 1px solid #fff; - border-radius: 10px; -} -.carousel-indicators .active { - width: 12px; - height: 12px; - margin: 0; - background-color: #fff; -} -.carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, .6); -} -.carousel-caption .btn { - text-shadow: none; -} -@media screen and (min-width: 768px) { - .carousel-control .glyphicon-chevron-left, - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-prev, - .carousel-control .icon-next { - width: 30px; - height: 30px; - margin-top: -15px; - font-size: 30px; - } - .carousel-control .glyphicon-chevron-left, - .carousel-control .icon-prev { - margin-left: -15px; - } - .carousel-control .glyphicon-chevron-right, - .carousel-control .icon-next { - margin-right: -15px; - } - .carousel-caption { - right: 20%; - left: 20%; - padding-bottom: 30px; - } - .carousel-indicators { - bottom: 20px; - } -} -.clearfix:before, -.clearfix:after, -.dl-horizontal dd:before, -.dl-horizontal dd:after, -.container:before, -.container:after, -.container-fluid:before, -.container-fluid:after, -.row:before, -.row:after, -.form-horizontal .form-group:before, -.form-horizontal .form-group:after, -.btn-toolbar:before, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:before, -.btn-group-vertical > .btn-group:after, -.nav:before, -.nav:after, -.navbar:before, -.navbar:after, -.navbar-header:before, -.navbar-header:after, -.navbar-collapse:before, -.navbar-collapse:after, -.pager:before, -.pager:after, -.panel-body:before, -.panel-body:after, -.modal-footer:before, -.modal-footer:after { - display: table; - content: " "; -} -.clearfix:after, -.dl-horizontal dd:after, -.container:after, -.container-fluid:after, -.row:after, -.form-horizontal .form-group:after, -.btn-toolbar:after, -.btn-group-vertical > .btn-group:after, -.nav:after, -.navbar:after, -.navbar-header:after, -.navbar-collapse:after, -.pager:after, -.panel-body:after, -.modal-footer:after { - clear: both; -} -.center-block { - display: block; - margin-right: auto; - margin-left: auto; -} -.pull-right { - float: right !important; -} -.pull-left { - float: left !important; -} -.hide { - display: none !important; -} -.show { - display: block !important; -} -.invisible { - visibility: hidden; -} -.text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} -.hidden { - display: none !important; - visibility: hidden !important; -} -.affix { - position: fixed; - -webkit-transform: translate3d(0, 0, 0); - -o-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} -@-ms-viewport { - width: device-width; -} -.visible-xs, -.visible-sm, -.visible-md, -.visible-lg { - display: none !important; -} -.visible-xs-block, -.visible-xs-inline, -.visible-xs-inline-block, -.visible-sm-block, -.visible-sm-inline, -.visible-sm-inline-block, -.visible-md-block, -.visible-md-inline, -.visible-md-inline-block, -.visible-lg-block, -.visible-lg-inline, -.visible-lg-inline-block { - display: none !important; -} -@media (max-width: 767px) { - .visible-xs { - display: block !important; - } - table.visible-xs { - display: table; - } - tr.visible-xs { - display: table-row !important; - } - th.visible-xs, - td.visible-xs { - display: table-cell !important; - } -} -@media (max-width: 767px) { - .visible-xs-block { - display: block !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline { - display: inline !important; - } -} -@media (max-width: 767px) { - .visible-xs-inline-block { - display: inline-block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm { - display: block !important; - } - table.visible-sm { - display: table; - } - tr.visible-sm { - display: table-row !important; - } - th.visible-sm, - td.visible-sm { - display: table-cell !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-block { - display: block !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline { - display: inline !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .visible-sm-inline-block { - display: inline-block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md { - display: block !important; - } - table.visible-md { - display: table; - } - tr.visible-md { - display: table-row !important; - } - th.visible-md, - td.visible-md { - display: table-cell !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-block { - display: block !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline { - display: inline !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .visible-md-inline-block { - display: inline-block !important; - } -} -@media (min-width: 1200px) { - .visible-lg { - display: block !important; - } - table.visible-lg { - display: table; - } - tr.visible-lg { - display: table-row !important; - } - th.visible-lg, - td.visible-lg { - display: table-cell !important; - } -} -@media (min-width: 1200px) { - .visible-lg-block { - display: block !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline { - display: inline !important; - } -} -@media (min-width: 1200px) { - .visible-lg-inline-block { - display: inline-block !important; - } -} -@media (max-width: 767px) { - .hidden-xs { - display: none !important; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .hidden-sm { - display: none !important; - } -} -@media (min-width: 992px) and (max-width: 1199px) { - .hidden-md { - display: none !important; - } -} -@media (min-width: 1200px) { - .hidden-lg { - display: none !important; - } -} -.visible-print { - display: none !important; -} -@media print { - .visible-print { - display: block !important; - } - table.visible-print { - display: table; - } - tr.visible-print { - display: table-row !important; - } - th.visible-print, - td.visible-print { - display: table-cell !important; - } -} -.visible-print-block { - display: none !important; -} -@media print { - .visible-print-block { - display: block !important; - } -} -.visible-print-inline { - display: none !important; -} -@media print { - .visible-print-inline { - display: inline !important; - } -} -.visible-print-inline-block { - display: none !important; -} -@media print { - .visible-print-inline-block { - display: inline-block !important; - } -} -@media print { - .hidden-print { - display: none !important; - } -} -/*# sourceMappingURL=bootstrap.css.map */ diff --git a/junction/static/css/bootstrap.min.css b/junction/static/css/bootstrap.min.css deleted file mode 100644 index a9f35cee..00000000 --- a/junction/static/css/bootstrap.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.2.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.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: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:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.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: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:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.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: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:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.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: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:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.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: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:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.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: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:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.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);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{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)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{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)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{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)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{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)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/junction/static/css/details.css b/junction/static/css/details.css deleted file mode 100644 index 90d8eeaa..00000000 --- a/junction/static/css/details.css +++ /dev/null @@ -1,73 +0,0 @@ -.proposal-details{ - padding:10px; - margin-top:10px; - border:1px solid #ccc; - border-radius:5px; -} -.proposal-details h1 { - margin:0px; - font-size:1.5em; - padding:6px; -} -.proposal-title{ - padding-bottom: 10px; -} -.icon-holder{ - width: 60px; - height: 54px; - border: 1px solid #3BABEA; - vertical-align: middle; - border-radius: 6px; -} -.icon-holder-inverse{ - background: transparent; - padding: 0px 8px 0px 8px; - color: #000; - height: 40px; -} -.icon-holder h4 { - margin:5px 0px 5px 0px; -} -.icon-holder > i { - margin-left:5px; -} -.icon-holder-bottom{ - background: #3BABEA; - height: 23px; - color:#ffffff; - cursor:pointer; -} -.icon-holder-bottom i { - padding:5px; -} -.porsal-title-section{ - padding:5px; - color:#ffffff; -} -.porsal-title-section .col-sm-12{ - padding-top:10px; - padding-bottom:10px; -} -.porsal-description h4{ - font-weight:600; -} -.proposal-update { - padding-top:15px; -} -.proposal-update a{ - font-size:1.5em; - color:#000; - padding-right:10px; -} -.proposal-comments{ - padding:5px 10px 5px 10px; - margin-left:-10px; - margin-right:-10px; - color:#ffffff; - background-color: #6F6F6F; -} -@media (max-width: 480px) { - .porsal-title-section .col-sm-12{ - height:100px; - } -} \ No newline at end of file diff --git a/junction/static/css/fonts.css b/junction/static/css/fonts.css deleted file mode 100644 index 1d71e20b..00000000 --- a/junction/static/css/fonts.css +++ /dev/null @@ -1,47 +0,0 @@ -@font-face { - font-family: 'icomoon'; - src:url('../fonts/icomoon.eot?21i6q3'); - src:url('../fonts/icomoon.eot?#iefix21i6q3') format('embedded-opentype'), - url('../fonts/icomoon.woff?21i6q3') format('woff'), - url('../fonts/icomoon.ttf?21i6q3') format('truetype'), - url('../fonts/icomoon.svg?21i6q3#icomoon') format('svg'); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"], [class*=" icon-"] { - font-family: 'icomoon'; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-user:before { - content: "\e600"; -} -.icon-checkmark-circle:before { - content: "\e606"; -} -.icon-checkmark:before { - content: "\e601"; -} -.icon-enter:before { - content: "\e603"; -} -.icon-exit:before { - content: "\e604"; -} -.icon-google:before { - content: "\e605"; -} -.icon-github:before { - content: "\e602"; -} - diff --git a/junction/static/css/form.css b/junction/static/css/form.css deleted file mode 100644 index 3a853efd..00000000 --- a/junction/static/css/form.css +++ /dev/null @@ -1,59 +0,0 @@ - .form-container{ - box-shadow:none; - } - .required > label.control-label:after { - content: "*:"; - } - label.control-label:after{ - content :":"; - } - label.control-label{ - text-align:right; - padding-right: 1em; - } - .form-container input[type=text]{ - display: inline; - width: 76%; - } - - .edit-proposal-form label.control-label { - width:24%; - padding-right:2%; - } - - @media (max-width: 480px) { - label.control-label{ - display: block; - width:100%; - text-align:left; - } - .form-container input[type=text]{ - display: block; - width: 100%; - } -} - - -/* Proposals */ - -.dropdown { - display: inline; - width: auto; -} - - -/* Editor */ - -.wmd-preview { - background-color: #fff !important; - border: 2px dashed #ccc; -} -@media (min-width: 800px) { - .wmd-panel { - margin-top: -35px; - margin-bottom: 50px; - } - .wmd-preview { - margin-bottom: 10px; - } -} diff --git a/junction/static/css/layout.css b/junction/static/css/layout.css deleted file mode 100644 index 617786b7..00000000 --- a/junction/static/css/layout.css +++ /dev/null @@ -1,460 +0,0 @@ -.nospace, .nospace-top { - margin-top: 0 !important; -} - -.nospace, .nospace-bottom { - margin-bottom: 0 !important; -} - -.space-1q, .space-1q-top { - margin-top: 0.25em; -} - -.space-1q, .space-1q-bottom { - margin-bottom: 0.25em; -} - -.space-half, .space-half-top { - margin-top: 0.5em; -} - -.space-half, .space-half-bottom { - margin-bottom: 0.5em; -} - -.space-3q, .space-3q-top { - margin-top: 0.75em; -} - -.space-3q, .space-3q-bottom { - margin-bottom: 0.75em; -} - -.space-1, .space-1-top { - margin-top: 1em; -} - -.space-1, .space-1-bottom { - margin-bottom: 1em; -} - -.space-line, .space-line-top { - margin-top: 1.5em; -} - -.space-line, .space-line-bottom { - margin-bottom: 1.5em; -} - -.space-2, .space-2-top { - margin-top: 2em; -} - -.space-2, .space-2-bottom { - margin-bottom: 2em; -} - -.space-2h, .space-2h-top { - margin-top: 2.5em; -} - -.space-2h, .space-2h-bottom { - margin-bottom: 2.5em; -} - -.space-3, .space-3-top { - margin-top: 3em; -} - -.space-3, .space-3-bottom { - margin-bottom: 3em; -} - -.space-4, .space-4-top { - margin-top: 4em; -} - -.space-4, .space-4-bottom { - margin-bottom: 4em; -} - -.space-neg-1q, .space-neg-1q-top { - margin-top: -0.25em; -} - -.space-neg-1q, .space-neg-1q-bottom { - margin-bottom: -0.25em; -} - -.space-neg-half, .space-neg-half-top { - margin-top: -0.5em; -} - -.space-neg-half, .space-neg-half-bottom { - margin-bottom: -0.5em; -} - -.space-neg-3q, .space-neg-3q-top { - margin-top: -0.75em; -} - -.space-neg-3q, .space-neg-3q-bottom { - margin-bottom: -0.75em; -} - -.space-neg-1, .space-neg-1-top { - margin-top: -1em; -} - -.space-ned-1, .space-neg-1-bottom { - margin-bottom: -1em; -} - -.space-neg-2, .space-neg-2-top { - margin-top: -2em; -} - -.space-ned-2, .space-neg-2-bottom { - margin-bottom: -2em; -} - -.space-neg-3, .space-neg-3-top { - margin-top: -3em; -} - -.space-ned-3, .space-neg-3-bottom { - margin-bottom: -3em; -} - -.space-neg-box, .space-neg-box-top { - margin-top: -3.7em; -} - -.space-neg-box, .space-neg-box-bottom { - margin-bottom: -3.7em; -} - -.space-neg-4, .space-neg-4-top { - margin-top: -4em; -} - -.space-ned-4, .space-neg-4-bottom { - margin-bottom: -4em; -} - -.push-1q, .push-1q-top { - padding-top: 0.25em; -} - -.push-1q, .push-1q-bottom { - padding-bottom: 0.25em; -} - -.push-half, .push-half-top { - padding-top: 0.5em; -} - -.push-half, .push-half-bottom { - padding-bottom: 0.5em; -} - -.push-3q, .push-3q-top { - padding-top: 0.75em; -} - -.push-3q, .push-3q-bottom { - padding-bottom: 0.75em; -} - -.push-1, .push-1-top { - padding-top: 1em; -} - -.push-1, .push-1-bottom { - padding-bottom: 1em; -} - -.push-line, .push-line-top { - padding-top: 1.5em; -} - -.push-line, .push-line-bottom { - padding-bottom: 1.5em; -} - -.push-2, .push-2-top { - padding-top: 2em; -} - -.push-2, .push-2-bottom { - padding-bottom: 2em; -} - -.push-2h, .push-2h-top { - padding-top: 2.5em; -} - -.push-2h, .push-2h-bottom { - padding-bottom: 2.5em; -} - -.push-3, .push-3-top { - padding-top: 3em; -} - -.push-3, .push-3-bottom { - padding-bottom: 3em; -} - -.push-4, .push-4-top { - padding-top: 4em; -} - -.push-4, .push-4-bottom { - padding-bottom: 4em; -} - -.push-5, .push-5-top { - padding-top: 5em; -} - -.push-5, .push-5-bottom { - padding-bottom: 5em; -} - -.push-6, .push-6-top { - padding-top: 6em; -} - -.push-6, .push-6-bottom { - padding-bottom: 6em; -} - -.push-8, .push-8-top { - padding-top: 8em; -} - -.push-8, .push-8-bottom { - padding-bottom: 8em; -} - -.no-pad, .no-pad-left { - padding-left: 0; -} - -.no-pad, .no-pad-right { - padding-right: 0; -} - -.pad-half, .pad-half-left { - padding-left: 0.5em; -} - -.pad-half, .pad-half-right { - padding-right: 0.5em; -} - -.pad-3q, .pad-3q-left { - padding-left: 0.75em; -} - -.pad-3q, .pad-3q-right { - padding-right: 0.75em; -} - -.pad-1, .pad-1-left { - padding-left: 1em; -} - -.pad-1, .pad-1-right { - padding-right: 1em; -} - -.pad-line, .pad-line-left { - padding-left: 1.5em; -} - -.pad-line, .pad-line-right { - padding-right: 1.5em; -} - -.pad-2, .pad-2-left { - padding-left: 2em; -} - -.pad-2, .pad-2-right { - padding-right: 2em; -} - -.pad-2h, .pad-2h-left { - padding-left: 2.5em; -} - -.pad-2h, .pad-2h-right { - padding-right: 2.5em; -} - -.pad-3, .pad-3-left { - padding-left: 3em; -} - -.pad-3, .pad-3-right { - padding-right: 3em; -} - -.pad-4, .pad-4-left { - padding-left: 4em; -} - -.pad-4, .pad-4-right { - padding-right: 4em; -} - -.inlineblock { - display: inline-block !important; -} -a.white:hover { - color: white; -} -.white { - color: #FFF; -} -.yellow { - color: #ffdd55 !important; -} -.blue { - color: #2c5aa0; -} -.green { - color: #75c16a; -} -.red { - color: #a94442; -} -.gray{ - color: #CCC; -} -.fill-yellow { - background-color: #ffdd55; -} -.fill-blue { - background-color: #2c5aa0; -} -.fill-blue-alt { - background-color: #214478; -} -.fill-white { - background-color: #FFF; -} -.fill-gray { - background-color: #EEE; -} -.normal { - font-weight: 400; -} -.card { - box-shadow: 1px 1px 3px rgba(0,0,0,0.1); -} - -.center-wrapper { - display: table; -} -.absolute-center { - display: table-cell; - vertical-align: middle; -} -.measure-block { - width: 80%; - margin: auto; -} -.tint { - font-size: 0.6em; -} -html, body { - height: 100%; -} -body { - height: auto; - min-height: 100%; - position: relative; -} -footer { - position: absolute; - left: 0; - right: 0; - bottom: 0; - top: auto; -} - -.form-container { - background: #FFF; - box-shadow: 1px 1px 3px rgba(0,0,0,0.1); - border-radius: 0.15em; -} -.form-container h2 { - text-transform: uppercase; - font-size: 1.1em; - padding: 0.8em 0; - border-top-left-radius: 0.15em; - border-top-right-radius: 0.15em; - color: #FBFBF6; -} -.form-container input[type=text] + label { - visibility: hidden; - width: 0; - height: 0; -} -.form-container input[type=text], .form-container input[type=password], .form-container input[type=email], .form-container input[type=url], .form-container textarea { - border: 2px solid #CCC; - padding: 0.5em 1em; - width: 100%; - border-radius: 0.3em; -} -button, .form-container input[type=submit] { - background: #ffdd55; - border: 1px solid #dabd42; - border-radius: 0.3em; - color: #7C6E0D; - padding: 0.3em 1.5em 0.4em 1.5em; -} -button:hover { - color: #695E13; - background-color: #dabd42; - border-color: #C2A737; -} -.flushright { - float: right; -} -.flushleft { - float: left; -} -.clearboth { - clear: both; -} - -ul.errorlist{ - padding: 0; - margin: 0; -} -ul.errorlist li { - list-style-type: none; - background-color: #f2dede; - border: 1px solid #ebccd1; - border-radius: 4px; - color: #a94442; - padding: 0.5em 1em; - margin-bottom: 1em; -} - -.legible { - font-size: 1.5em; - line-height: 1.4em; -} -.legible-prose { - font-size: 1.2em; - line-height: 1.5em; -} -.regular { - font-size: 1em; - line-height: 1.4em; -} diff --git a/junction/static/css/list.css b/junction/static/css/list.css deleted file mode 100644 index 79685eff..00000000 --- a/junction/static/css/list.css +++ /dev/null @@ -1,43 +0,0 @@ - header.custom-container{ - padding:10px; - } - .proposal-list h1 { - margin:0px; - } - .icon-holder{ - display: block; - width: 40px; - height: 50px; - background-color: #3BABEA; - padding: 8px; - vertical-align: middle; - border-radius: 6px; - color:#ffffff; - } - .icon-holder-inverse{ - background: transparent; - padding: 0px 8px 0px 8px; - color: #000; - height: 40px; - } - .icon-holder > h4 { - margin:0px; - } - .icon-holder > i { - margin-left:5px; - } - .proposal-list-content { - padding-bottom: 10px; - padding-top:10px; - border-bottom:1px dotted #ccc; - display:table; - width:100%; - } - - .proposal-list .title-underline{ - border-bottom:1px solid #ccc; - } - .border-less{ - border-top:none; - border-bottom:none; - } \ No newline at end of file diff --git a/junction/static/css/style.css b/junction/static/css/style.css deleted file mode 100644 index 668c4c79..00000000 --- a/junction/static/css/style.css +++ /dev/null @@ -1,377 +0,0 @@ -/* Common styles */ -html { - font-size: 1em; -} -body { - padding-top: 5em; - padding-bottom:60px; - font-family: 'Open Sans', sans-serif; - background-color: #f5f5f5; - color: #333; - font-size: 0.885em; - line-height: 1.45em; -} - -/* Homepage styles */ -.masthead { - height: 27em; - display: block; - background: url('/static/images/masthead.jpg') center center no-repeat scroll; - -webkit-background-size: cover; - -moz-background-size: cover; - background-size: cover; - -o-background-size: cover; - position: relative; -} -.header-big h2 { - font-size: 1.5em; - line-height: 1.4em; -} -.header-big { - height: 18em; - width: 100%; -} -.masthead:after { - background: rgba(43, 91, 132, 0.1); - content: ''; - display: block; - height: inherit; - width: inherit; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -/* Customize Bootstrap */ -.navbar-default { - background-color: #FFF; - border: 0; - box-shadow: 0 0 3px rgba(0,0,0,0.1); -} -.navbar, .navbar-header { - margin-bottom: 0; - min-height: 5em; -} -.navbar-default .navbar-brand, .navbar-default .navbar-nav > li > a { - color: #333; - font-size: 1.08em; -} -.navbar-brand { - padding: 0.9em 0 0.7em 0.6em; - height: 70px; -} -.navbar-brand img { - height: 100%; -} -.navbar-nav > li > a, .navbar-toggle { - height: 4.7em; - line-height: 4.7em; - vertical-align: middle; -} -.navbar-toggle { - border: 0; -} -.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { - background-color: #0d202b; -} -.navbar-default .navbar-collapse, .navbar-default .navbar-form { - border-color: transparent; -} -.navbar-nav > li > a { - padding-top: 0; - padding-bottom: 0; -} -.navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>li>a:focus, .navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover { - background: #EEE; - color: #D5A952; -} -.navbar-default .navbar-nav>.open>a:focus { - background: #EEE; - color: #2b5b84; -} -.navbar-default .navbar-nav>li>a>.caret { - color: rgba(0,0,0,0.3); -} -.dropdown-menu { - background: #FFF; -} -.dropdown-menu>li>a { - color: #333; -} -.dropdown-menu>li>a:hover { - color: #D5A952; - background: rgba(0,0,0,0.1); -} -@media (max-width: 767px) { - .navbar-default .navbar-nav .open .dropdown-menu>li>a { - color: #CCC; - } - .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover, .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus { - color: #D5A952; - } - .dropdown .visible-xs{ - display: inline-block !important; - } -} -ul.socialaccount_providers { - padding-left: 0; - list-style-type: none; -} -a.socialaccount_provider { - margin: 1em 0; - text-align: center; - padding: 0.5em 0.5em 0.35em 0.5em; - border-bottom: 0.15em solid transparent; - width: 100%; - display: block; - text-decoration: none; - border-radius: 0.3em; -} -a.socialaccount_provider.github { - background-color: #DDD; - color: #444444; -} -a.socialaccount_provider.google { - background-color: #4683ea; - color: #FFF; -} -a.socialaccount_provider.google:hover { - border-bottom: 0.15em solid #4683ea; - background-color: #4387fd; -} -a.socialaccount_provider.github:hover { - border-bottom: 0.15em solid #DBDBDB; - background-color: #E5E5E5; -} -.icon { - padding-right: 0.4em; -} - -.btn-primary { - background: #ffdd55; - color: #7C6E0D; - border-color: #dabd42; -} -.btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open>.dropdown-toggle.btn-primary { - color: #695E13; - background-color: #dabd42; - border-color: #C2A737; -} - -/* Sidebar navigation */ -.nav-sidebar { - margin-right: -21px; /* 20px padding + 1px border */ - margin-bottom: 20px; - margin-left: -20px; -} -.nav-sidebar > li > a { - padding-right: 20px; - padding-left: 20px; -} -.nav-sidebar > .active > a, -.nav-sidebar > .active > a:hover, -.nav-sidebar > .active > a:focus { - color: #fff; - background-color: #428bca; -} -.content { - /* width: 90%; */ - margin: 0 auto; -} -@media (max-width: 768px) { - .sidebar { - margin-top: 1em; - } -} -@media (min-width: 768px) { - .sidebar { - display: block; - height: 100%; - position: fixed; - width: 25%; - } -} - - -.im-checkout { - margin: 0 auto; -} -.custom-container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -.wmd-wrapper>.wmd-panel{ -margin-left:auto; -margin-right:auto; -} -#tabs { - float: right; - font-weight: normal; - margin-top: -42px; -} -#tabs a.youarehere { - background: #fff; - border: 1px solid #ccc; - border-bottom-color: #fff; - color: #000; - font-size: 120%; - height: 30px; - line-height: 28px; - margin-top: 3px; - padding: 0 11px 0 11px; -} - -#tabs a { - background: inherit; - border: 1px solid transparent; - color: #777; - display: block; - float: left; - font-size: 90%; - height: 24px; - line-height: 20px; - margin: 9px 8px 0 0; - padding: 0 11px 0 11px; - text-decoration: none; - border-bottom: 1px solid #ccc; -} -#tabs a:hover { - background: #fff; - border: 1px solid #ccc; - border-bottom-color: #fff; -} -@media (min-width: 320px) { - .custom-container { - padding-right: 0px; - padding-left: 0px; - } -} -@media (max-width: 480px) { - #tabs { - margin-top: 0px; - } - #tabs a { - margin-left:0px; - margin-right:0px; - } -} -@media (min-width: 768px) { - .custom-container { - width: 750px; - } - .wmd-wrapper>.wmd-panel{ - margin-left:24%; - margin-right:auto; - width:76%; - } -} -@media (min-width: 992px){ - .custom-container { - width: 960px; - } -} -@media (min-width: 1200px){ - .custom-container { - width: 960px; - } -} - - -.wmd-panel .wmd-input{ - background-color:#ffffff; -} - - -.header-tab { - width:100%; - margin-top: 10px; - margin-bottom: 10px; - padding-bottom: 20px; -} - - -.header-tab-title { - margin-bottom: -10px; - border-bottom: 1px solid #ccc; - height: 34px; - clear: both; -} -header-tab-title >h2{ - float: left; - font-size: 18px; - line-height: 34px; - margin-bottom: 0; -} - - - -.statscontainer { - float: left; - padding:0px; -} -.stats { - background:#BDD7E6; - margin: 0px; - padding: 4px 7px 6px 7px; - width: 58px; - border-radius: 8px; -} -.proposal-list h3 , p { - margin:0px; - margin-bottom:5px; -} -.margin-less{ - margin:0px; -} -.padding-less{ - padding:0px; -} -.border-less{ - border-top:none; - border-bottom:none; -} - -/* - Header - --------------------------------------------------------------------------- -*/ - -.head_content { - text-align: center; - background: #2c5aa0; - color: #fff; - padding: 2em 1em 2em 1em; -} -.head_content h1 { - color: #ffdd55; -} - -/* Arrow after status - .stats:after, .stats:before { - left: 73px; - top: 50%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -.stats:after { - border-color: rgba(136, 183, 213, 0); - border-left-color: #88b7d5; - border-width: 9px; - margin-top: -10px; -} -.stats:before { - border-color: rgba(194, 225, 245, 0); - border-left-color: #c2e1f5; - border-width: 15px; - margin-top: -16px; -} */ diff --git a/junction/static/css/ui.css b/junction/static/css/ui.css deleted file mode 100644 index 68c384f3..00000000 --- a/junction/static/css/ui.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - Markdown Editor - ============================================================================ */ - -.wmd-preview { - background-color: #fff; -} diff --git a/junction/static/fonts/fontawesome-webfont.svg b/junction/static/fonts/fontawesome-webfont.svg index d907b25a..b6129693 100644 --- a/junction/static/fonts/fontawesome-webfont.svg +++ b/junction/static/fonts/fontawesome-webfont.svg @@ -517,4 +517,4 @@ - \ No newline at end of file + diff --git a/junction/static/fonts/glyphicons-halflings-regular.svg b/junction/static/fonts/glyphicons-halflings-regular.svg index e3e2dc73..265491f5 100644 --- a/junction/static/fonts/glyphicons-halflings-regular.svg +++ b/junction/static/fonts/glyphicons-halflings-regular.svg @@ -226,4 +226,4 @@ - \ No newline at end of file + diff --git a/junction/static/fonts/glyphicons-halflings-regular.woff2 b/junction/static/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 00000000..64539b54 Binary files /dev/null and b/junction/static/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/junction/static/fonts/icomoon.svg b/junction/static/fonts/icomoon.svg index 6653629f..c97c422a 100644 --- a/junction/static/fonts/icomoon.svg +++ b/junction/static/fonts/icomoon.svg @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/junction/static/img/bg/home.jpg b/junction/static/img/bg/home.jpg index 43d519ab..2de1ff94 100644 Binary files a/junction/static/img/bg/home.jpg and b/junction/static/img/bg/home.jpg differ diff --git a/junction/static/js/Chart.min.js b/junction/static/js/Chart.min.js new file mode 100644 index 00000000..fdbb8d9d --- /dev/null +++ b/junction/static/js/Chart.min.js @@ -0,0 +1,11 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 1.0.2 + * + * Copyright 2015 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ +(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),st?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),tthis.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;ip&&(p=t.x+s,n=i),t.x-sp&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)0&&ithis.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.ythis.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'
      <% for (var i=0; i
    • <%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    '};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); diff --git a/junction/static/js/bootstrap-markdown.js b/junction/static/js/bootstrap-markdown.js index a0053515..c778059a 100644 --- a/junction/static/js/bootstrap-markdown.js +++ b/junction/static/js/bootstrap-markdown.js @@ -1037,7 +1037,7 @@ if (link != null && link != '' && link != 'http://' && link.substr(0,4) == 'http') { var sanitizedLink = $('
    '+link+'
    ').text() - + // transform selection and set the cursor into chunked text e.replaceSelection('!['+chunk+']('+sanitizedLink+' "'+e.__localize('enter image title here')+'")') cursor = selected.start+2 diff --git a/junction/static/js/bootstrap.min.js b/junction/static/js/bootstrap.min.js index 7c1561a8..ceb0492b 100644 --- a/junction/static/js/bootstrap.min.js +++ b/junction/static/js/bootstrap.min.js @@ -3,4 +3,4 @@ * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('