From 486c66b198147b3c30efc9ff71a4dbb74b6927b4 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 11:59:21 +0100 Subject: [PATCH 001/275] Update requirements.txt before starting to test for issues --- requirements.txt | 50 +++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/requirements.txt b/requirements.txt index 532cadea..f74f08e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,33 +1,35 @@ +beautifulsoup4==4.4.1 diff-match-patch==20121119 -dj-database-url==0.3.0 +dj-database-url==0.4.0 dj-static==0.0.6 -Django==1.8.2 -django-debug-toolbar==1.3.0 +Django==1.9.4 +django-debug-toolbar==1.4 django-ical==1.3 -django-recaptcha==1.0.4 -django-registration-redux==1.2 -django-reversion==1.8.7 +django-recaptcha==1.0.5 +django-registration-redux==1.4 +django-reversion==1.10.1 django-toolbelt==0.0.1 -django-widget-tweaks==1.3 -gunicorn==19.3.0 -icalendar==3.9.0 -lxml==3.4.4 -Pillow==2.8.1 -psycopg2==2.6 -Pygments==2.0.2 -PyPDF2==1.24 -python-dateutil==2.4.2 -pytz==2015.4 -raven==5.8.1 -reportlab==3.1.44 +django-widget-tweaks==1.4.1 +gunicorn==19.4.5 +icalendar==3.9.2 +lxml==3.6.0 +Markdown==2.6.6 +Pillow==3.1.1 +psycopg2==2.6.1 +Pygments==2.1.3 +PyPDF2==1.25.1 +python-dateutil==2.5.2 +pytz==2016.3 +raven==5.12.0 +reportlab==3.3.0 selenium==2.53.1 -simplejson==3.7.2 -six==1.9.0 -sqlparse==0.1.15 +simplejson==3.8.2 +six==1.10.0 +sqlparse==0.1.19 static3==0.6.1 svg2rlg==0.3 yolk==0.4.3 -z3c.rml==2.8.1 -zope.event==4.0.3 -zope.interface==4.1.2 +z3c.rml==3.0.0 +zope.event==4.2.0 +zope.interface==4.1.3 zope.schema==4.4.2 From 0ee37b1cd31a4af4fb0cd314ac7b97237a3710ee Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 12:07:07 +0100 Subject: [PATCH 002/275] Fix issues with python2 imports --- RIGS/admin.py | 14 +++++++------- RIGS/forms.py | 2 +- RIGS/models.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/RIGS/admin.py b/RIGS/admin.py index 18dc554e..f8107a9e 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -2,15 +2,15 @@ from django.contrib import admin from RIGS import models, forms from django.contrib.auth.admin import UserAdmin from django.utils.translation import ugettext_lazy as _ -import reversion +from reversion.admin import VersionAdmin # Register your models here. -admin.site.register(models.Person, reversion.VersionAdmin) -admin.site.register(models.Organisation, reversion.VersionAdmin) -admin.site.register(models.VatRate, reversion.VersionAdmin) -admin.site.register(models.Venue, reversion.VersionAdmin) -admin.site.register(models.Event, reversion.VersionAdmin) -admin.site.register(models.EventItem, reversion.VersionAdmin) +admin.site.register(models.Person, VersionAdmin) +admin.site.register(models.Organisation, VersionAdmin) +admin.site.register(models.VatRate, VersionAdmin) +admin.site.register(models.Venue, VersionAdmin) +admin.site.register(models.Event, VersionAdmin) +admin.site.register(models.EventItem, VersionAdmin) admin.site.register(models.Invoice) admin.site.register(models.Payment) diff --git a/RIGS/forms.py b/RIGS/forms.py index e1e95012..d1b826c3 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -45,7 +45,7 @@ class ProfileChangeForm(UserChangeForm): # Events Shit class EventForm(forms.ModelForm): - datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + settings.DATETIME_INPUT_FORMATS + datetime_input_formats = formats.get_format_lazy("DATETIME_INPUT_FORMATS") + list(settings.DATETIME_INPUT_FORMATS) meet_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False) access_at = forms.DateTimeField(input_formats=datetime_input_formats, required=False) diff --git a/RIGS/models.py b/RIGS/models.py index fc06911f..05066323 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import AbstractUser from django.conf import settings from django.utils.functional import cached_property from django.utils.encoding import python_2_unicode_compatible -import reversion +from reversion import revisions as reversion import string import random from collections import Counter @@ -552,4 +552,4 @@ class Payment(models.Model): method = models.CharField(max_length=2, choices=METHODS, null=True, blank=True) def __str__(self): - return "%s: %d" % (self.get_method_display(), self.amount) \ No newline at end of file + return "%s: %d" % (self.get_method_display(), self.amount) From cd2aed00d724bc38c21275fda99fb57482b4864a Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 12:09:04 +0100 Subject: [PATCH 003/275] Update login URL in settings so redirects work correctly. --- PyRIGS/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index d85ff887..396e893f 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -144,8 +144,8 @@ RAVEN_CONFIG = { AUTH_USER_MODEL = 'RIGS.Profile' LOGIN_REDIRECT_URL = '/' -LOGIN_URL = '/user/login' -LOGOUT_URL = '/user/logout' +LOGIN_URL = '/user/login/' +LOGOUT_URL = '/user/logout/' ACCOUNT_ACTIVATION_DAYS = 7 From 98ee9bb0db3c5fbac9ee7cca315c11835494ca48 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 12:13:11 +0100 Subject: [PATCH 004/275] Fix imports in tests --- RIGS/test_functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 154fdfb2..ed563be3 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -11,7 +11,7 @@ import re import os from datetime import date, timedelta from django.db import transaction -import reversion +from reversion import revisions as reversion import json From d43e4b2465c1023f12a2f429eb4db9079cef78ae Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 12:33:46 +0100 Subject: [PATCH 005/275] Update settings and urls to fix new deprecations --- PyRIGS/settings.py | 53 +++++---- PyRIGS/urls.py | 8 +- RIGS/urls.py | 272 ++++++++++++++++++++++----------------------- 3 files changed, 169 insertions(+), 164 deletions(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 396e893f..13ca4874 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/1.7/ref/settings/ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os + BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') @@ -18,13 +19,12 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get('SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e' +SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get( + 'SECRET_KEY') else 'gxhy(a#5mhp289_=6xx$7jh=eh$ymxg^ymc+di*0c*geiu3p_e' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True -TEMPLATE_DEBUG = True - ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com'] INTERNAL_IPS = ['127.0.0.1'] @@ -33,7 +33,6 @@ ADMINS = ( ('Tom Price', 'tomtom5152@gmail.com') ) - # Application definition INSTALLED_APPS = ( @@ -69,7 +68,6 @@ ROOT_URLCONF = 'PyRIGS.urls' WSGI_APPLICATION = 'PyRIGS.wsgi.application' - # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases DATABASES = { @@ -81,6 +79,7 @@ DATABASES = { if not DEBUG: import dj_database_url + DATABASES['default'] = dj_database_url.config() # Logging @@ -111,12 +110,12 @@ LOGGING = { 'mail_admins': { 'class': 'django.utils.log.AdminEmailHandler', 'level': 'ERROR', - # But the emails are plain text by default - HTML is nicer + # But the emails are plain text by default - HTML is nicer 'include_html': True, }, }, 'loggers': { - # Again, default Django configuration to email unhandled exceptions + # Again, default Django configuration to email unhandled exceptions 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', @@ -183,19 +182,7 @@ USE_L10N = True USE_TZ = True -DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M','%Y-%m-%dT%H:%M:%S') - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", - "django.core.context_processors.request", - "django.contrib.messages.context_processors.messages", -) - +DATETIME_INPUT_FORMATS = ('%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S') # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ @@ -206,10 +193,28 @@ STATIC_DIRS = ( os.path.join(BASE_DIR, 'static/') ) -TEMPLATE_DIRS = ( - os.path.join(BASE_DIR, 'templates'), -) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.join(BASE_DIR, 'templates'), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", + "django.core.context_processors.request", + "django.contrib.messages.context_processors.messages", + ] + }, + }, +] -USE_GRAVATAR=True +USE_GRAVATAR = True TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" diff --git a/PyRIGS/urls.py b/PyRIGS/urls.py index 9821ae20..f8993d48 100644 --- a/PyRIGS/urls.py +++ b/PyRIGS/urls.py @@ -6,19 +6,19 @@ from registration.backends.default.views import RegistrationView import RIGS from RIGS import regbackend -urlpatterns = patterns('', +urlpatterns = [ # Examples: # url(r'^$', 'PyRIGS.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^', include('RIGS.urls')), - url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail), + url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail), name="registration_register"), url('^user/', include('django.contrib.auth.urls')), url('^user/', include('registration.backends.default.urls')), url(r'^admin/', include(admin.site.urls)), -) +] if settings.DEBUG: - urlpatterns += staticfiles_urlpatterns() \ No newline at end of file + urlpatterns += staticfiles_urlpatterns() diff --git a/RIGS/urls.py b/RIGS/urls.py index 4338df08..fa6f9872 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,4 +1,5 @@ from django.conf.urls import patterns, include, url +from django.contrib.auth.views import password_reset from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView @@ -6,153 +7,152 @@ from django.views.generic import RedirectView from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required -urlpatterns = patterns('', - # Examples: - # url(r'^$', 'PyRIGS.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - url('^$', login_required(views.Index.as_view()), name='index'), - url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), +urlpatterns = [ + # Examples: + # url(r'^$', 'PyRIGS.views.home', name='home'), + # url(r'^blog/', include('blog.urls')), + url('^$', login_required(views.Index.as_view()), name='index'), + url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), - url('^user/login/$', 'RIGS.views.login', name='login'), - url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), + url('^user/login/$', views.login, name='login'), + url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}), - # People - url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), - name='person_list'), - url(r'^people/add/$', - permission_required_with_403('RIGS.add_person')(views.PersonCreate.as_view()), - name='person_create'), - url(r'^people/(?P\d+)/$', - permission_required_with_403('RIGS.view_person')(views.PersonDetail.as_view()), - name='person_detail'), - url(r'^people/(?P\d+)/history/$', - permission_required_with_403('RIGS.view_person')(versioning.VersionHistory.as_view()), - name='person_history', kwargs={'model': models.Person}), - url(r'^people/(?P\d+)/edit/$', - permission_required_with_403('RIGS.change_person')(views.PersonUpdate.as_view()), - name='person_update'), + # People + url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), + name='person_list'), + url(r'^people/add/$', + permission_required_with_403('RIGS.add_person')(views.PersonCreate.as_view()), + name='person_create'), + url(r'^people/(?P\d+)/$', + permission_required_with_403('RIGS.view_person')(views.PersonDetail.as_view()), + name='person_detail'), + url(r'^people/(?P\d+)/history/$', + permission_required_with_403('RIGS.view_person')(versioning.VersionHistory.as_view()), + name='person_history', kwargs={'model': models.Person}), + url(r'^people/(?P\d+)/edit/$', + permission_required_with_403('RIGS.change_person')(views.PersonUpdate.as_view()), + name='person_update'), - # Organisations - url(r'^organisations/$', - permission_required_with_403('RIGS.view_organisation')(views.OrganisationList.as_view()), - name='organisation_list'), - url(r'^organisations/add/$', - permission_required_with_403('RIGS.add_organisation')(views.OrganisationCreate.as_view()), - name='organisation_create'), - url(r'^organisations/(?P\d+)/$', - permission_required_with_403('RIGS.view_organisation')(views.OrganisationDetail.as_view()), - name='organisation_detail'), - url(r'^organisations/(?P\d+)/history/$', - permission_required_with_403('RIGS.view_organisation')(versioning.VersionHistory.as_view()), - name='organisation_history', kwargs={'model': models.Organisation}), - url(r'^organisations/(?P\d+)/edit/$', - permission_required_with_403('RIGS.change_organisation')(views.OrganisationUpdate.as_view()), - name='organisation_update'), + # Organisations + url(r'^organisations/$', + permission_required_with_403('RIGS.view_organisation')(views.OrganisationList.as_view()), + name='organisation_list'), + url(r'^organisations/add/$', + permission_required_with_403('RIGS.add_organisation')(views.OrganisationCreate.as_view()), + name='organisation_create'), + url(r'^organisations/(?P\d+)/$', + permission_required_with_403('RIGS.view_organisation')(views.OrganisationDetail.as_view()), + name='organisation_detail'), + url(r'^organisations/(?P\d+)/history/$', + permission_required_with_403('RIGS.view_organisation')(versioning.VersionHistory.as_view()), + name='organisation_history', kwargs={'model': models.Organisation}), + url(r'^organisations/(?P\d+)/edit/$', + permission_required_with_403('RIGS.change_organisation')(views.OrganisationUpdate.as_view()), + name='organisation_update'), - # Venues - url(r'^venues/$', - permission_required_with_403('RIGS.view_venue')(views.VenueList.as_view()), - name='venue_list'), - url(r'^venues/add/$', - permission_required_with_403('RIGS.add_venue')(views.VenueCreate.as_view()), - name='venue_create'), - url(r'^venues/(?P\d+)/$', - permission_required_with_403('RIGS.view_venue')(views.VenueDetail.as_view()), - name='venue_detail'), - url(r'^venues/(?P\d+)/history/$', - permission_required_with_403('RIGS.view_venue')(versioning.VersionHistory.as_view()), - name='venue_history', kwargs={'model': models.Venue}), - url(r'^venues/(?P\d+)/edit/$', - permission_required_with_403('RIGS.change_venue')(views.VenueUpdate.as_view()), - name='venue_update'), + # Venues + url(r'^venues/$', + permission_required_with_403('RIGS.view_venue')(views.VenueList.as_view()), + name='venue_list'), + url(r'^venues/add/$', + permission_required_with_403('RIGS.add_venue')(views.VenueCreate.as_view()), + name='venue_create'), + url(r'^venues/(?P\d+)/$', + permission_required_with_403('RIGS.view_venue')(views.VenueDetail.as_view()), + name='venue_detail'), + url(r'^venues/(?P\d+)/history/$', + permission_required_with_403('RIGS.view_venue')(versioning.VersionHistory.as_view()), + name='venue_history', kwargs={'model': models.Venue}), + url(r'^venues/(?P\d+)/edit/$', + permission_required_with_403('RIGS.change_venue')(views.VenueUpdate.as_view()), + name='venue_update'), - # Rigboard - url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'), - url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/calendar/(?P(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/calendar/(?P(month|week|day))/(?P(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), - url(r'^rigboard/activity/$', - permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), - name='activity_table'), - url(r'^rigboard/activity/feed/$', - permission_required_with_403('RIGS.view_event')(versioning.ActivityFeed.as_view()), - name='activity_feed'), + # Rigboard + url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'), + url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), + url(r'^rigboard/calendar/(?P(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), + name='web_calendar'), + url(r'^rigboard/calendar/(?P(month|week|day))/(?P(\d{4}-\d{2}-\d{2}))/$', + login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), + url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), + url(r'^rigboard/activity/$', + permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), + name='activity_table'), + url(r'^rigboard/activity/feed/$', + permission_required_with_403('RIGS.view_event')(versioning.ActivityFeed.as_view()), + name='activity_feed'), - url(r'^event/(?P\d+)/$', - permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), - name='event_detail'), - url(r'^event/(?P\d+)/print/$', - permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), - name='event_print'), - url(r'^event/create/$', - permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()), - name='event_create'), - url(r'^event/(?P\d+)/edit/$', - permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()), - name='event_update'), - url(r'^event/(?P\d+)/duplicate/$', - permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()), - name='event_duplicate'), - url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()), - name='event_archive'), + url(r'^event/(?P\d+)/$', + permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), + name='event_detail'), + url(r'^event/(?P\d+)/print/$', + permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), + name='event_print'), + url(r'^event/create/$', + permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()), + name='event_create'), + url(r'^event/(?P\d+)/edit/$', + permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()), + name='event_update'), + url(r'^event/(?P\d+)/duplicate/$', + permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()), + name='event_duplicate'), + url(r'^event/archive/$', login_required()(rigboard.EventArchive.as_view()), + name='event_archive'), - url(r'^event/(?P\d+)/history/$', - permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), - name='event_history', kwargs={'model': models.Event}), + url(r'^event/(?P\d+)/history/$', + permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), + name='event_history', kwargs={'model': models.Event}), - + # Finance + url(r'^invoice/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), + name='invoice_list'), + url(r'^invoice/archive/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoiceArchive.as_view()), + name='invoice_archive'), + url(r'^invoice/waiting/$', + permission_required_with_403('RIGS.add_invoice')(finance.InvoiceWaiting.as_view()), + name='invoice_waiting'), - # Finance - url(r'^invoice/$', - permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), - name='invoice_list'), - url(r'^invoice/archive/$', - permission_required_with_403('RIGS.view_invoice')(finance.InvoiceArchive.as_view()), - name='invoice_archive'), - url(r'^invoice/waiting/$', - permission_required_with_403('RIGS.add_invoice')(finance.InvoiceWaiting.as_view()), - name='invoice_waiting'), + url(r'^event/(?P\d+)/invoice/$', + permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()), + name='invoice_event'), - url(r'^event/(?P\d+)/invoice/$', - permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()), - name='invoice_event'), + url(r'^invoice/(?P\d+)/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()), + name='invoice_detail'), + url(r'^invoice/(?P\d+)/print/$', + permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()), + name='invoice_print'), + url(r'^invoice/(?P\d+)/void/$', + permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), + name='invoice_void'), + url(r'^payment/create/$', + permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), + name='payment_create'), + url(r'^payment/(?P\d+)/delete/$', + permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()), + name='payment_delete'), - url(r'^invoice/(?P\d+)/$', - permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()), - name='invoice_detail'), - url(r'^invoice/(?P\d+)/print/$', - permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()), - name='invoice_print'), - url(r'^invoice/(?P\d+)/void/$', - permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), - name='invoice_void'), - url(r'^payment/create/$', - permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), - name='payment_create'), - url(r'^payment/(?P\d+)/delete/$', - permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()), - name='payment_delete'), + # User editing + url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'), + url(r'^user/(?P\d+)/$', + permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()), + name='profile_detail'), + url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), + name='profile_update_self'), + url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), - # User editing - url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'), - url(r'^user/(?P\d+)/$', - permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()), - name='profile_detail'), - url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), - name='profile_update_self'), - url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), + # ICS Calendar - API key authentication + url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), - # ICS Calendar - API key authentication - url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), - - # API - url(r'^api/(?P\w+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), - url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), - - # Legacy URL's - url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True,pattern_name='event_detail')), - url(r'^bookings/$', RedirectView.as_view(permanent=True,pattern_name='rigboard')), - url(r'^bookings/past/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), -) + # API + url(r'^api/(?P\w+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), + url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), + # Legacy URL's + url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')), + url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')), + url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), +] From c1d164bd7374a305f75ea4c1208b1f7a722180da Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 13:12:35 +0100 Subject: [PATCH 006/275] Remove the db.sqlite3 file This was loooong overdue --- .gitignore | 1 + db.sqlite3 | Bin 84992 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 db.sqlite3 diff --git a/.gitignore b/.gitignore index d31b5aa9..64848594 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ coverage.xml # Django stuff: *.log +db.sqlite3 # Sphinx documentation docs/_build/ diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index 07462b3f640fb99ec6f986f85dfa5d5315cc4d15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84992 zcmeHw3ve9Cb=dUu0!xqtL4X98Jd%e6K#*L50OqrQ9FGEdIEu$VkCenns>x!p0G9{s z;&vD0QI@Ja;FZpa?MtkZ?UPHEQ!1y*QDRxK<%`Q@SL{=Am&7io&QYE9ahyc4FZM|} zt5T_ps!plo^=D>!W@iC#MbRYKxZCDT_j~1*++ zt5+_ZJ`Z`Fn?8T#Xnd$rSS$_2Zx(9B>xJ53UDFaS9;6nQmaK)sOlhGt$YOPNmE^1E zPhYw^4UBm?LADGhkx*Y!e!F-KDdoy+>4p093s65+VYyMI?^Z|=Evr(xW$CR_IzjI5 z!tWg--Ie9LkZxrC(mfXKj9nUS#Wb*g-C8Qu7R&W|xmvOGKtvSSi%fTP#;%#IvIr|( zSdlv~s-3aXQFM)#lZ&rjdVaZ7DV74S*jK(l;$hMVjsj-FzJ{W)i3zk)5M{Smp05=e zP?|bt&5KZd`8uuAOK+o1f`HE2&(QD;gdea^K=`DchcIoYAWYa35SsSW5Dwad5XNsp z5W5J$Cen4`Z`eKz;Zro9u6}sz=^rfC_Pp41I3YiWjQ!b@sQ*T#%&+|cr=Oy1DP z%?$J+J-=PVO*Y{gS{Sa8%|Lv4ib8Un7arGe*ED9?H> zttX9CH{j|{d3Eb%sj>_ma(1>>s@KCv>gkM`OXs(WBsCw&lw}xtE)*9^kxS;c2)J4< zfUD>9bQrFdHqxnVezS<1ZNk;E&A8fl&deG{W}*vlM|e@SjCHfnfF@oFL&#*ZX-&&- z5{adQ#8P2$)y6aFT-r49Q4v>cN4l1@^mLej05_Gc(qg{&C*b{xc` zD6sf=qW=)qH`Pivp%;_I2|Sg_1O92HV^jBGJh+L>brz&o8oT1yWUouL>RfrDWX;#A z%S-j1U3i4Y7jBa=p=XB^QLfyqmW!qCVJ9Oxf%Nv`gpe<+@fPu$N{-bObzr(NL-@~uMSnwtMtN07}75o|eGx*c^C-En616S~MJcB=mKZGyhXYf<_Nu0$7 zK8g>*i18TSiMQcRSXF4foJ+ zHwmLL8t$TDKMi-1uyY3u`)Jrp!|f#O=%L{@8g`Q~vXzEgXb4~02;nXoZlYn7glZ=Z zJ7^e*;4TF7dKXeL?&|6W+4rfhDENQkZ{q)ee;s=HOZXNpK(C&H{yc!Us_#Sp{kPCx zUujWIGFnRDo=ae7Uql($>rTpiZtm}e*!^y-GgA+5k0>K%tu$Y*lMfb{yTxZdQTFsi zl>WVBDqrGL^fo7gPtm>I5HUa?=v-f{m2UNJ^+l0sx_b*m#ayWF&CV;G*L%9)l~Um! zPdhh3#0UWwr}1r3h}}b(p|f^Rr-R11-O&+I_V2C3Cnnip;Q#K8KskrSMZWOdjUkH| zS=-PhNuLU@gXGoQcj-kDCAPmXyI8I)ROexpQoBv&|339|3N~>!j;lY!GwOHYOW>#Q zC42(Ei~k7L0$+wX`#03j;m_UkN|qT)3ABVyJ zy=wp8zCF^v7tt=q`@b8(`~SASNdEwZaQlC^KZ^AKt=l5~F%N2s`%3%&W;avb|GWAl z{Ua1y?Ejm3BK><9GqnFlT{PbRJ9kF*??r+B-?1+;GK_>iUl*Pulo7Z8;~kOyLHeoV z^?$WD65HS9>;Gi__wDMpst*}2B_JiR;Uv(Fo>w}RlZtW@4WO^6C)F45Z{gok&|jk6 zv17;3on!RKW2JMIv$+T1c!VCxdB<@^D&;$|^PlGhQ{fbDpmX+-0Xe^gQFK!8xLpUa^3%K4SR^QsHd?IRFWu>fHp{K={r=Om_ zJbnJ;^s~^^$Y~~p!K1ZyaM;e1mn5jF%kc#**-AzS*Y$$RAbzumD;;z7!Bk1-6S+?mNtJ#P9jydU}Hz+-XMDBvJMOHgUm*@YWaBgjo z^Z)#aVDf8t2IpzbFmhnEo;1J+H$A2qV@8e`t0&F8ksQw@Q;=>R=uq~LOgTEZtJ%}$ z4P!iIW^-xn_;80ZFgfKaE(4ZmZf@vlOHXMbw%?F}kp|oB%za`(I-`nbWeV zTxO8k|B5QmYw?>jl4dR|_x~Tru}(52C2(IPAp8G)5wj#BCGbE>Kz{!}kkulYk`lNt z5|I7>zKB^8krH?yB_R9%16eJSDJg;bA_4OKA5j{L(m>mOIeb7h@Hlhn!X~vve6>7iVrfpSNH%DgLwkj;K z_YzFA5_WzM*d^YxJwNipBakAMn=%ehrQb2PAVvi{CP)+ zck`=*5y$W3gkNVW3(P<*CZ^)Xyqt~5u^mO^1G)kdj46mOJXXi#U=P+rQ>+NQ%^ z(F9vW+G*M{WK78YzqUs^i6|xT&`UtB{~!7)lm(CyXj=ku{%>1!5>ZOvp_hQ{{||i? z$^u9Uv@HR-{%>1!5>ZOvp_hQ{{||i?$^u9Uv@HSI|JxRwM3fSE=p`V({~!7)lm(Cy zXj=lL|6}w~1$`85_}Pg~^>^CNsu?je&>1^3vM#^HK2zP{OnlvlhNxaMva)B8Ue$bu zUDZs}#CunoBKksc^ePmf+~cN#2s%j#_i_&(y2;LooMki>*`c%pyv{b(zaGh;`WVbY%+oD zy`H9(D-kQ1G&9AyLeVe9gItQ-6}&&u#SxUJ+zg3`T~-rz`6gAAy3_Af9KRxn=EGPJ#Wu z`i6QLCE@Xp)YqE3vKLb^;r5B%K&{wi8#>v$2%61m?fh zBPCo-0fk+TZ9-57Wq6ER+|#9E?oR??||DjLg-AUozY5q{yGHK)&| zW^?HbYZB}huAL#BHV7|tS)N1%#xB-nmo|LrldQ|K`)Wi2;`5=9dJuDj?oqBZO7o?f z+x6lXFP}a)b@^KS%=ESR)YU5&PM?QN&P|`c0zwGys)a=+vXGI|>Er8D*7J`hqzf;W z8rQ49rPHhp-Xt@()|@aM(rl*XZ#nt>?~QEoNlM^RlK@%&N6-yLeM@~Ay^n6FQMIOi z8^5uBIe0_QOPUSp@o+iS%cI`BNY^cN45m+J$Oudm?!gqe?p1EcWDi>Ejb%{D4i5ih zkgwggu9s`DmHB-py3r1CxW@Z6A)trcGo|2s5Ma7J2tpAvS0E(sqT-lRgL+ zTbwR^5JbkpWkNmnA;yr|lA%!R3uemEzcxb-_Nokt_7&6i!F%E3DYbIY9$AMaihDH? zE0G@3HXZI5L|cv2$zpwTK8NO%$ei!iSX1MSi%Wj*CMplty-!TwLCi6c;QbuIzZ1f5 z44#j|Zxnt<;5P)n)&Cak$3WsCp9WSjmo{dK=8QK8423iu_I=~P3N6MR9cBvk(yRq* zQV?X~d3!G-7yLf7=8CCoCZ!4FjHFKNNoVWKeno+O6Ft+uS zIh&lB5xz%9Ther3!)Pv+iIV8bYhUmuj@4f}CM zvO7q!kWHqHVu7vNX?e$5l5}9hNU|?IN~~-TFk)MR#OBOGGOL+_Bqv%Db6~@W(aU%# zE%~(uJ-+4Fn!Eo06wLjfRsS4U)tl;f6yhbXAAZ4cp!FEgPXB+jb`9j51mpHmeru@T zu+l##-K$lA{opNd9=JuI+y7hlY?&e@ASJLtB|yIa<^6vfv~p#!qy*NG0O|j|ib4E; z{{k-I!|M0cSJh9bPpaMMP4x5V8X8jGR=%WE6oYQ*Efk~qUcsfZp0Z%yp$eXq^`vfS zSxYla6JAndx;AEHiA&{FCU5BDW=7XEJ-=PV_1rN#xP}&nYh*JJpYIWIO&?vu(lnAs zGhM^T>V}cuCgO??nRP73KuGo%rEotfLFaZH> zDx;_LWGCQGIE*GOaW^BB+a$Q*PUkyBied}WOdVQ53yLOGi8dJl++z+!!&ZJcilto2ZgkJwu^0uxAxZa{r_T@eUu*4zI4?)AmCe* z;+FRS-}1RKb>i%Fd`L`cZwIzeuipYC74O_sH?nYYdf|$r6R=RIH>`!~d>Qm%$i97} zyjXJ5V3)tX3{_0LtTdki%#~|^>Q_ZD{i+Bw^Hm{2w`V-=2nUVA+*~ttNGTK><(nag z6Qmf{jcU14f^vIh5^}AS8|A_R9C|pX$y`T*f1<-jc~FVM^pq%;%GPrw&=YcSlcdZ{ z!GZ{?b#Zy_iHtqA=9#s#ZSB(z%V+HIbz}O}!zNd@*%S1H>dw5^0O^2E+ZcU%ETr+# zgtnTVUkTOes=k}^|)sUBkMP)_6}h9q4=t{-j4lr zz^soS>jix)fb1fFr!FiW0vWNBN7fX4=T=U2tjn=QBD1ijHh@!iKHGP51ue3MB(!3F z#>rGpn1_Ne=MDGOX+YDNtWR~tDO;!nMFT-vCVvl*=YOroIGG|Pu<<28=KlyvDJX^h zQ2p3FO9YQhaxm;*p+%-2Q}(0ra|3?%~1$HB2mpz^L4Wu)MwPM;zRheYxjN$ahDQUuvKR89(0@*;vHewU&EdjoC_Of z@Gh#7h%#dL!8?N7|G&$gEWxD&Hjo79 z`d|5wqI^h=sQ(!6RKCakfBnn#6%xP3^#2!@@CyiBfPyi=o#z{Oj@Vi>Ha3Pn9i@f= zvo{Kr`KnczT?B7BU}h71-^-^wZW<6wZtJ^%*u}u-2-BVqvwx}1+yHfIs>v;0#LS~> zr6p0(TJxLq@XY5H3b5>~!`0Ob;5aP4T&b7m!QMu+ECOBl%6!RM1ciNAsX9(ay>Ae1 zvSksQt<|;4xl}|1E*Rn^%30AhZ)!H}0R_Hi$a++d#*U96Tc_*Mp!lt%o|)5U^kOh! zpMRwiOm^|udL@`lm=_B}KWA*kHW)91;k-Dj+hA_S8oW4ot$A@In6R(gDVTswv~o*q zy&|uix){|F+J;4l_L}k1k_E;;CBYLdSkii*H&FG|7E$>6@h7`JzzS|oE>423|9h0P z3jR0vr|?<4UH!WHiuy4%j^0JTivAwzSN@aok05zne^YU=T0A|)trpX;6Y%^p_|3Dp z45(Ei80u<9v!$dlZf0}2bne6eWO2sH0*pcz8t`>TYsNFtvntCA3*mXBa~b^uk3$}( zoIJqD*vo@tL@ctb>QbW~nnTJoGwGB2A%}BL4qzziM-z_iA`HvtV^<a*J*(@KmNb%<0cO6-~gE=#lDc>*P>YnL!O4Gqc7&aC{&9+nUwQeT!J4Ia2^qSymAgge*?Ub3x z>Slh2h?^103)aVAULS7Vp3G!2xm>H+xm!H@U?AA_Ih188j@;D8|iqy(e{9v%sF z0IMo~k1%`*zmMO;29Bt2tFNhFQ0wXmwHN&@dIS9m`V_i~#!#2?H_F$PUsgV$oDVPI z5!mrTW2sHxgUIKT?PQ@6hq$=t6J-!~c+l`z^k%tqi+fNbVB|G^kvdeXAApSq2hWbCkO zGdzCS`$5=fK?%Ln;m3n=7~*i?B9Xv39C>gKODrZ1?hw)NXeZ^#1ENgvoWSWJy>Ht72h_#h-OoW2Xg zDbx3TQGgjZgb7I(fDofEN5PvQn#ib}3bY5lMJtfK?2R`c?krX6ZIkwg5N-> z7?n`=VhJcA@5LMj7lB=)p!`K!IvkOeDvjE0fdPn$)40Rn8xSBlc@dHmcOi$sDPY&g z1R*Vg!8`Qc^BM%_cB>E~{8ywqU&Mcfzk*-EKZ%#{$MG{biT7ev{l5Bb^>y$J_$%r^ zRBx$s>Sgc^dQ|O)yMq23`uFHxp)aDJh0);#`Y?JLrO`0juKYjcyUN#<-&g*b@)_kP z6vvlXZv+yz5ni|o^4P8j3@P2b#8e9$wD@ivWzoENZuIDm2s(ti3BDV5go17rb0GI+ zCRntX1BpINbJ*(UK;jNR$OpD|jsk&Gc&w&m#n`o7L<4}}VTl2`%$>KSxH%xTULIp;X z$j(UDDB@M@^mI%kSq9L#FSMvrvj5+q{1XNK|7&%x`ccpU{sR6bh+Ok;WsjYow$rQK z*A#L}YUv@7ohuV|hUyNy$gW(acNx)A|M)4;8Py|5U!U-w+S=DCC^qv33#9 z=|hAhob?yM`K;fuWbjDXpBivG>7ED&d4h9cH|s>=n4Rs6T^en5eRDu4mL7;WW=HH? zG9v@)Fv>V7!;e`|Jk2_PjPC9vTn!1Vv# zRFpT7seT79-Aw_(u;R%z3@c{z^o%yArJD^a*0y5+T=+H_Vpc|@_Az?p_NP1e817iw z@P9o5_GD+Qey-K5wbp2D>1#o3yR&{?ICE3M86&`aTT?`n`l2^2xJmG>1wMk^J58Cv z?Oe6CNL&Xy)-r+B!4T+3fm`tMC*bZwP@60?t1S97B!f0p!8L_P{qEN}t~tz_wyj?E zJ2=jADPeE6rHs2@n3dNZ4EL6O5Zw z!1l-C#!Pr;9S(#@NXWu9I zDe3@_FJqtYj)a}IKS-~ZS~(;d-SKlfejdW#ra67TxhPL0S@Xw`8_Jwbh>Mp`pPRaT zEq-SD8k~AK@ZtL%r5Bcn85^-74Y^4zF=?Ofj9o~y+Hvx;?w)^flFxk7ekK~5Od#85 z@8~jAN+stCX~Un%8NdAfiPkK~VY2rp65)u&k>CHTbQ75*C9u&YAlLsJy@qA^qy$<^ zK=%LEvXLoL0vlZdvj1=N8kXgg5@;;}vi~RV|7tBCnIa{yktHC^|I<*;lkhtMzvCOZ z%(86vR00o_H}o;$mVR=QIH4@eER;MYI_Knp-cZvbdQAuQ6q)RyhbklrXA1GDt5+_Z zJ`el!=cdnJffELDcwpUMFZKQd7FXTV?;Ks||M#>}$jqb!qy!#(36S+aA`U*+>EqK^ zqL+@_XV^VpDKgr=q>Z)dLL$jrNu~uFpc3zGW zJJW!#R2!wbKdRm+G?pRG)s^?O>1S)Vt?O_AJQSa)Ru@FyzU8`AD}w{zU_4}2YY6AX zA`VMqUWrK<=NE{TDpcK`pD>HD&9H@H5gY+nM2VlwD`GZ_rBb7HLb)=e&*#L~!0hiL+&9*1QgU1}I=sL+^DUQaAyxSY22E zZ`IWn0u^t6@Y*m4J1$z4HuW~<|93aI^T2pdA_FA8 zi2)Kha)>hE_iW;4fPf7g1hBzT!PyCcq17@CvrVHuGy(u4V?O66LGFeQFc^`sAXEE> z0cPNk=N`q!%fJw2LvWVjvhkr(Wf|6GgOr5Nd5XXWpBNboXM@HGY|!_8;7T6Y@Z767 zY|zw0;3OU;%z(2MkxrozC;nFsfiHLh=#XOWRzzYPa1i{xgFh?LDT_;ygN%VAcQlb} z_R>NCX-!4#TKTwX=o%^(O2h{92DAd7F zs4Jot9*Y7F;(94=&;=4LlHXNQlRKp_pG#rBnJ6DV4^E{2A3)#(@VI&nJpTQ(`pfEX zs&A-&roN;8U%U-Jj&=MLzKYA>73>%BKgX}(Z{qK&N7R_wfxeHvjeZ|}9{mKmjxM7o z&=J%R89iiwROEep9l}kppJ0*Bh|lgdousnsW6rwhO{aOCLTdkXuFHv+X(@Vm{`~viWRW~i9%;~`^^kmc zj8570t>H9^8CbDRwDuv^+V2?c-pzC1Udn|yeeRGE8x{@NDQ>?f{G8+0dvFnvJloI!L>oe?4}ng;P2fEoMj7 zY6+Tye@V%_-F1u1(6uVfp=n)vluA=v5Rb76& z{4dc-hl}R{Zg^B24hawp zA#o0II0q1;W^i~ENIC=I;VgsXG@WRHs1r4m0mzXt-!STsbzqLC0cPsVD&wk;1#j4l z^G(L%DVBlDjt9aae4KfyP6oLoWH341YFzdhZbm(50%}4J4B7(2e0)A=FgVU5j}`dh zF>xK>hDXJr-678*r2fxF=-#pnQUdKsfb{=PG_T;F!9(hAs3*`{@FM^2wFDOIOYBOA zb=?0k=C|(j4sF(d|99EGxb7PyjXBLuK%`7rQQjZUEEJZib$T?QxhJ9h(S7VH} zq*cf!Q%13n38Uqmwk4MA^NiR!uKqaElGyBQIysvzB*Tb_n`5+m^Y%GLavk@YjI<=F qr_9;p%uJ{(`B^61+J2T%TF0#|tuKk0E2grUlr|GqxTbU0#Qz7$U+L-q From 6da688cc9ec34063c0539dd80c2a646d5a33a1da Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 13:15:53 +0100 Subject: [PATCH 007/275] Migrate VAT rate to use a single date field rather than datetime. There is never any need to track the time as VAT rate hardly ever changes and will always do so at midnight. We were already assuming this anyway but it was generating loads of warnings/errors. This will break your local VAT rate database if using sqlite, but it is tested with postgres and works fine. --- RIGS/migrations/0025_auto_20160331_1302.py | 26 ++++++++++++++++++++++ RIGS/models.py | 21 ++++++++--------- 2 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 RIGS/migrations/0025_auto_20160331_1302.py diff --git a/RIGS/migrations/0025_auto_20160331_1302.py b/RIGS/migrations/0025_auto_20160331_1302.py new file mode 100644 index 00000000..eacc7bfd --- /dev/null +++ b/RIGS/migrations/0025_auto_20160331_1302.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-31 12:02 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0024_auto_20160229_2042'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='username', + field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'), + ), + migrations.AlterField( + model_name='vatrate', + name='start_at', + field=models.DateField(), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 05066323..e77a2b93 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -1,9 +1,10 @@ import hashlib import datetime, pytz -from django.db import models, connection +from django.db import models from django.contrib.auth.models import AbstractUser from django.conf import settings +from django.utils import timezone from django.utils.functional import cached_property from django.utils.encoding import python_2_unicode_compatible from reversion import revisions as reversion @@ -172,7 +173,7 @@ class Organisation(models.Model, RevisionMixin): class VatManager(models.Manager): def current_rate(self): - return self.find_rate(datetime.datetime.now()) + return self.find_rate(timezone.now()) def find_rate(self, date): # return self.filter(startAt__lte=date).latest() @@ -187,7 +188,7 @@ class VatManager(models.Manager): @reversion.register @python_2_unicode_compatible class VatRate(models.Model, RevisionMixin): - start_at = models.DateTimeField() + start_at = models.DateField() rate = models.DecimalField(max_digits=6, decimal_places=6) comment = models.CharField(max_length=255) @@ -238,11 +239,11 @@ class Venue(models.Model, RevisionMixin): class EventManager(models.Manager): def current_events(self): events = self.filter( - (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end - (models.Q(end_date__gte=datetime.date.today(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after - (models.Q(dry_hire=True, start_date__gte=datetime.date.today()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire + (models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Starts after with no end + (models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q(status=Event.CANCELLED)) | # Ends after + (models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q(status=Event.CANCELLED)) | # Active dry hire (models.Q(dry_hire=True, checked_in_by__isnull=True) & (models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT - models.Q(status=Event.CANCELLED, start_date__gte=datetime.date.today()) # Canceled but not started + models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') return events @@ -264,12 +265,12 @@ class EventManager(models.Manager): def rig_count(self): event_count = self.filter( - (models.Q(start_date__gte=datetime.date.today(), end_date__isnull=True, dry_hire=False, + (models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False, is_rig=True) & ~models.Q( status=Event.CANCELLED)) | # Starts after with no end - (models.Q(end_date__gte=datetime.date.today(), dry_hire=False, is_rig=True) & ~models.Q( + (models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q( status=Event.CANCELLED)) | # Ends after - (models.Q(dry_hire=True, start_date__gte=datetime.date.today(), is_rig=True) & ~models.Q( + (models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q( status=Event.CANCELLED)) | # Active dry hire (models.Q(dry_hire=True, checked_in_by__isnull=True, is_rig=True) & ( models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) # Active dry hire GT From 463c4d147cd2a8ad934bfa60172f6ee54906887b Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 15:14:54 +0100 Subject: [PATCH 008/275] Update settings for django10 support Allow env.EMAIL_PORT to be None without error Change template context preprocessor in favour of the new syntax. --- PyRIGS/settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 13ca4874..f8747618 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -158,7 +158,7 @@ EMAILER_TEST = False if not DEBUG or EMAILER_TEST: EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = os.environ.get('EMAIL_HOST') - EMAIL_PORT = int(os.environ.get('EMAIL_PORT')) + EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 25)) EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_USE_TLS = bool(int(os.environ.get('EMAIL_USE_TLS', 0))) @@ -203,12 +203,12 @@ TEMPLATES = [ 'OPTIONS': { 'context_processors': [ "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", - "django.core.context_processors.request", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.template.context_processors.request", "django.contrib.messages.context_processors.messages", ] }, From 8cfda6971796dfa6577247bf91c3136c2690b730 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 31 Mar 2016 16:03:07 +0100 Subject: [PATCH 009/275] Set wercker up to use postgres for tests again. Modify the tests to be postgres compatible by using more pointers to things rather than getting them everytime. --- RIGS/test_functional.py | 10 +- RIGS/test_models.py | 199 +++++++++++++++++++++++----------------- wercker.yml | 20 ++-- 3 files changed, 128 insertions(+), 101 deletions(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index ed563be3..1b87cd08 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -430,7 +430,7 @@ class EventTest(LiveServerTestCase): # See redirected to success page successTitle = self.browser.find_element_by_xpath('//h1').text event = models.Event.objects.get(name='Test Event Name') - self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle) + self.assertIn("N%05d | Test Event Name"%event.pk, successTitle) except WebDriverException: # This is a dirty workaround for wercker being a bit funny and not running it correctly. # Waiting for wercker to get back to me about this @@ -487,7 +487,7 @@ class EventTest(LiveServerTestCase): # Attempt to save save.click() - self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) + self.assertNotIn("N%05d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) # Check the new items are visible table = self.browser.find_element_by_id('item-table') # ID number is known, see above @@ -496,7 +496,7 @@ class EventTest(LiveServerTestCase): self.assertIn("Test Item 3", table.text) infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') - self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) @@ -504,7 +504,7 @@ class EventTest(LiveServerTestCase): #Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') - self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above @@ -623,7 +623,7 @@ class EventTest(LiveServerTestCase): # See redirected to success page successTitle = self.browser.find_element_by_xpath('//h1').text event = models.Event.objects.get(name='Test Event Name') - self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle) + self.assertIn("N%05d | Test Event Name"%event.pk, successTitle) def testRigNonRig(self): self.browser.get(self.live_server_url + '/event/create/') diff --git a/RIGS/test_models.py b/RIGS/test_models.py index a7896cbe..b84fc7a6 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -15,71 +15,82 @@ class ProfileTestCase(TestCase): class VatRateTestCase(TestCase): - def setUp(self): - models.VatRate.objects.create(start_at='2014-03-01', rate=0.20, comment='test1') - models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2') + @classmethod + def setUpTestData(cls): + cls.rates = { + 0: models.VatRate.objects.create(start_at='2014-03-01', rate=0.20, comment='test1'), + 1: models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2'), + } def test_find_correct(self): r = models.VatRate.objects.find_rate('2015-03-01') - self.assertEqual(r.comment, 'test1') + self.assertEqual(r, self.rates[0]) r = models.VatRate.objects.find_rate('2016-03-01') - self.assertEqual(r.comment, 'test2') + self.assertEqual(r, self.rates[1]) def test_percent_correct(self): - r = models.VatRate.objects.get(rate=0.20) - self.assertEqual(r.as_percent, 20) + self.assertEqual(self.rates[0].as_percent, 20) class EventTestCase(TestCase): - def setUp(self): - self.all_events = set(range(1, 18)) - self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18) - self.not_current_events = set(self.all_events) - set(self.current_events) + @classmethod + def setUpTestData(cls): + cls.all_events = set(range(1, 18)) + cls.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18) + cls.not_current_events = set(cls.all_events) - set(cls.current_events) - self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') - self.profile = models.Profile.objects.create(username="testuser1", email="1@test.com") + cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com") - # produce 7 normal events - 5 current - models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6), - description="start future no end") - models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end") - models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(), - description="start today with end today") - models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end") - models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21', - description="start past with end past") - models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2), - end_date=date.today() + timedelta(days=2), description="start past, end future") - models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2), - end_date=date.today() + timedelta(days=2), description="start + end in future") + cls.events = { + # produce 7 normal events - 5 current + 1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6), + description="start future no end"), + 2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"), + 3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(), + description="start today with end today"), + 4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"), + 5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21', + description="start past with end past"), + 6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2), + end_date=date.today() + timedelta(days=2), + description="start past, end future"), + 7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2), + end_date=date.today() + timedelta(days=2), + description="start + end in future"), - # 2 cancelled - 1 current - models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2), - end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, - description="cancelled in future") - models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1), - end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, - description="cancelled and started") + # 2 cancelled - 1 current + 8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2), + end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, + description="cancelled in future"), + 9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1), + end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, + description="cancelled and started"), - # 5 dry hire - 3 current - models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today") - models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, - description="dryhire today, checked in") - models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True, - status=models.Event.BOOKED, description="dryhire past") - models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True, - checked_in_by=self.profile, description="dryhire past checked in") - models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, - status=models.Event.CANCELLED, description="dryhire today cancelled") + # 5 dry hire - 3 current + 10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, + description="dryhire today"), + 11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, + checked_in_by=cls.profile, + description="dryhire today, checked in"), + 12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True, + status=models.Event.BOOKED, description="dryhire past"), + 13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True, + checked_in_by=cls.profile, description="dryhire past checked in"), + 14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, + status=models.Event.CANCELLED, description="dryhire today cancelled"), - # 4 non rig - 3 current - models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today") - models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False, - description="non rig tomorrow") - models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False, - description="non rig yesterday") - models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, - description="non rig today cancelled") + # 4 non rig - 3 current + 15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, + description="non rig today"), + 16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False, + description="non rig tomorrow"), + 17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False, + description="non rig yesterday"), + 18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, + status=models.Event.CANCELLED, + description="non rig today cancelled"), + } def test_count(self): # Santiy check we have the expected events created @@ -101,17 +112,23 @@ class EventTestCase(TestCase): def test_related_venue(self): v1 = models.Venue.objects.create(name="TE V1") v2 = models.Venue.objects.create(name="TE V2") - events = models.Event.objects.all() - for event in events[:2]: - event.venue = v1 - event.save() - for event in events[3:4]: - event.venue = v2 + + e1 = [] + e2 = [] + for (key, event) in self.events.iteritems(): + if event.pk % 2: + event.venue = v1 + e1.append(event) + else: + event.venue = v2 + e2.append(event) event.save() - events = models.Event.objects.all() - self.assertItemsEqual(events[:2], v1.latest_events) - self.assertItemsEqual(events[3:4], v2.latest_events) + self.assertItemsEqual(e1, v1.latest_events) + self.assertItemsEqual(e2, v2.latest_events) + + for (key, event) in self.events.iteritems(): + event.venue = None def test_related_vatrate(self): self.assertEqual(self.vatrate, models.Event.objects.all()[0].vat_rate) @@ -120,33 +137,43 @@ class EventTestCase(TestCase): p1 = models.Person.objects.create(name="TE P1") p2 = models.Person.objects.create(name="TE P2") - events = models.Event.objects.all() - for event in events[:2]: - event.person = p1 - event.save() - for event in events[3:4]: - event.person = p2 + e1 = [] + e2 = [] + for (key, event) in self.events.iteritems(): + if event.pk % 2: + event.person = p1 + e1.append(event) + else: + event.person = p2 + e2.append(event) event.save() - events = models.Event.objects.all() - self.assertItemsEqual(events[:2], p1.latest_events) - self.assertItemsEqual(events[3:4], p2.latest_events) + self.assertItemsEqual(e1, p1.latest_events) + self.assertItemsEqual(e2, p2.latest_events) + + for (key, event) in self.events.iteritems(): + event.person = None def test_related_organisation(self): o1 = models.Organisation.objects.create(name="TE O1") o2 = models.Organisation.objects.create(name="TE O2") - events = models.Event.objects.all() - for event in events[:2]: - event.organisation = o1 - event.save() - for event in events[3:4]: - event.organisation = o2 + e1 = [] + e2 = [] + for (key, event) in self.events.iteritems(): + if event.pk % 2: + event.organisation = o1 + e1.append(event) + else: + event.organisation = o2 + e2.append(event) event.save() - events = models.Event.objects.all() - self.assertItemsEqual(events[:2], o1.latest_events) - self.assertItemsEqual(events[3:4], o2.latest_events) + self.assertItemsEqual(e1, o1.latest_events) + self.assertItemsEqual(e2, o2.latest_events) + + for (key, event) in self.events.iteritems(): + event.organisation = None def test_organisation_person_join(self): p1 = models.Person.objects.create(name="TE P1") @@ -184,20 +211,20 @@ class EventTestCase(TestCase): self.assertEqual(len(o2.persons), 1) def test_cancelled_property(self): - event = models.Event.objects.all()[0] - event.status = models.Event.CANCELLED - event.save() - event = models.Event.objects.all()[0] + edit = self.events[1] + edit.status = models.Event.CANCELLED + edit.save() + event = models.Event.objects.get(pk=edit.pk) self.assertEqual(event.status, models.Event.CANCELLED) self.assertTrue(event.cancelled) event.status = models.Event.PROVISIONAL event.save() def test_confirmed_property(self): - event = models.Event.objects.all()[0] - event.status = models.Event.CONFIRMED - event.save() - event = models.Event.objects.all()[0] + edit = self.events[1] + edit.status = models.Event.CONFIRMED + edit.save() + event = models.Event.objects.get(pk=edit.pk) self.assertEqual(event.status, models.Event.CONFIRMED) self.assertTrue(event.confirmed) event.status = models.Event.PROVISIONAL diff --git a/wercker.yml b/wercker.yml index c2749a3e..0e2efea2 100644 --- a/wercker.yml +++ b/wercker.yml @@ -10,10 +10,10 @@ box: heroku/python # You can also use services such as databases. Read more on our dev center: # http://devcenter.wercker.com/docs/services/index.html services: -# - id: postgres -# env: -# POSTGRES_PASSWORD: pyrigstesting -# POSTGRES_USER: pyrigs + - id: postgres + env: + POSTGRES_PASSWORD: pyrigstesting + POSTGRES_USER: pyrigs # http://devcenter.wercker.com/docs/services/postgresql.html # - mongodb @@ -69,11 +69,11 @@ build: echo "pip version $(pip --version) running" # Django uses this to connect to the database -# - script: -# name: set environment -# code: | -# export DEBUG=0 -# export DATABASE_URL="postgres://pyrigs:pyrigstesting@$POSTGRES_PORT_5432_TCP_ADDR:$POSTGRES_PORT_5432_TCP_PORT$POSTGRES_NAME" + - script: + name: set environment + code: | + export DEBUG=0 + export DATABASE_URL="postgres://pyrigs:pyrigstesting@$POSTGRES_PORT_5432_TCP_ADDR:$POSTGRES_PORT_5432_TCP_PORT$POSTGRES_NAME" # A step that executes `pip install` command. - pip-install @@ -100,4 +100,4 @@ build: name: collect coverage data code: | coverage report -m --include=PyRIGS/*.*,RIGS/*.* --omit=*/migrations/* | tee coverage.txt - coverage html --include=PyRIGS/*.*,RIGS/*.* --omit=*/migrations/* \ No newline at end of file + coverage html --include=PyRIGS/*.*,RIGS/*.* --omit=*/migrations/* From 67624eea6f2bae4f44f5c7ae179182d67aed1949 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 15 Jun 2016 23:18:46 +0100 Subject: [PATCH 010/275] Allow deleting invoices, if there are no payments yet --- RIGS/finance.py | 19 ++++++++++ .../RIGS/invoice_confirm_delete.html | 35 +++++++++++++++++++ RIGS/templates/RIGS/invoice_detail.html | 4 +++ RIGS/urls.py | 3 ++ 4 files changed, 61 insertions(+) create mode 100644 RIGS/templates/RIGS/invoice_confirm_delete.html diff --git a/RIGS/finance.py b/RIGS/finance.py index 75de8bd9..65fc3b07 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -94,6 +94,25 @@ class InvoiceVoid(generic.View): return HttpResponseRedirect(reverse_lazy('invoice_list')) return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': object.pk})) +class InvoiceDelete(generic.DeleteView): + model = models.Invoice + + def get(self, request, pk): + obj = self.get_object() + if obj.payment_set.all().count() > 0: + messages.info(self.request, 'To delete an invoice, delete the payments first.') + return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return super(InvoiceDelete, self).get(pk) + + def post(self, request, pk): + obj = self.get_object() + if obj.payment_set.all().count() > 0: + messages.info(self.request, 'To delete an invoice, delete the payments first.') + return HttpResponseRedirect(reverse_lazy('invoice_detail', kwargs={'pk': obj.pk})) + return super(InvoiceDelete, self).post(pk) + + def get_success_url(self): + return self.request.POST.get('next') class InvoiceArchive(generic.ListView): model = models.Invoice diff --git a/RIGS/templates/RIGS/invoice_confirm_delete.html b/RIGS/templates/RIGS/invoice_confirm_delete.html new file mode 100644 index 00000000..fe295ed6 --- /dev/null +++ b/RIGS/templates/RIGS/invoice_confirm_delete.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %} + +{% block content %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index ad177ba4..27b4c514 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -11,6 +11,10 @@
+ + + diff --git a/RIGS/urls.py b/RIGS/urls.py index 4338df08..ee949ff8 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -127,6 +127,9 @@ urlpatterns = patterns('', url(r'^invoice/(?P\d+)/void/$', permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), name='invoice_void'), + url(r'^invoice/(?P\d+)/delete/$', + permission_required_with_403('RIGS.change_invoice')(finance.InvoiceDelete.as_view()), + name='invoice_delete'), url(r'^payment/create/$', permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), name='payment_create'), From 667b0c80ca7bc7e8fd2eae8839f969d61c46dcd1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 16 Jun 2016 01:44:59 +0100 Subject: [PATCH 011/275] Added tests for invoice deleting --- RIGS/test_unit.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 75fcca5a..c51874f4 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -155,3 +155,57 @@ class TestAdminMergeObjects(TestCase): if event.organisation == self.organisations[3]: # The one we left in place continue self.assertEqual(updatedEvent.organisation, self.organisations[1]) + +class TestInvoiceDelete(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today()), + 2: models.Event.objects.create(name="TE E2", start_date=date.today()) + } + + cls.invoices = { + 1: models.Invoice.objects.create(event=cls.events[1]), + 2: models.Invoice.objects.create(event=cls.events[2]) + } + + cls.payments = { + 1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34, method=models.Payment.CASH) + } + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + def test_invoice_delete_allowed(self): + request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[2].pk}) + + response = self.client.get(request_url, follow=True) + self.assertContains(response, "Are you sure") + + # Check the invoice still exists + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk)) + + # Actually delete it + response = self.client.post(request_url, follow=True) + + # Check the invoice is deleted + self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk) + + def test_invoice_delete_not_allowed(self): + request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[1].pk}) + + response = self.client.get(request_url, follow=True) + self.assertContains(response, "To delete an invoice, delete the payments first.") + + # Check the invoice still exists + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) + + # Try to actually delete it + response = self.client.post(request_url, follow=True) + + # Check this didn't work + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) \ No newline at end of file From 11dd9ad02f8fcebf4692689532a1843e7bb159ba Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 11 Jul 2016 13:08:05 +0100 Subject: [PATCH 012/275] Add tmp directory to gitignore --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index d31b5aa9..1793954a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +tmp/ +db.sqlite3 + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -25,9 +28,6 @@ var/ # Continer extras .vagrant -_builds -_steps -_projects # PyInstaller # Usually these files are written by a python script from a template From 7ecd6212ac62ddf730e157e787279cb355131c84 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 11 Jul 2016 20:15:48 +0100 Subject: [PATCH 013/275] Initial commit of app.json (for heroku review apps) --- app.json | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 app.json diff --git a/app.json b/app.json new file mode 100644 index 00000000..ea2e5c25 --- /dev/null +++ b/app.json @@ -0,0 +1,52 @@ +{ + "name": "PyRIGS", + "description": "", + "scripts": { + "postdeploy": "python manage.py migrate" + }, + "env": { + "DEBUG": { + "required": true + }, + "EMAIL_FROM": { + "required": true + }, + "EMAIL_HOST": { + "required": true + }, + "EMAIL_HOST_PASSWORD": { + "required": true + }, + "EMAIL_HOST_USER": { + "required": true + }, + "EMAIL_PORT": { + "required": true + }, + "EMAIL_USE_SSL": { + "required": true + }, + "RECAPTCHA_PRIVATE_KEY": { + "required": true + }, + "RECAPTCHA_PUBLIC_KEY": { + "required": true + }, + "SECRET_KEY": { + "generator": "secret" + } + }, + "formation": { + "web": { + "quantity": 1 + } + }, + "addons": [ + "heroku-postgresql" + ], + "buildpacks": [ + { + "url": "heroku/python" + } + ] +} \ No newline at end of file From b94cef92d23a4a7c76b10b20cde8c072d6aaa181 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 11 Jul 2016 23:26:12 +0100 Subject: [PATCH 014/275] Update selenium due to OS X based firefox issue --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 532cadea..995d2afd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ python-dateutil==2.4.2 pytz==2015.4 raven==5.8.1 reportlab==3.1.44 -selenium==2.53.1 +selenium==2.53.6 simplejson==3.7.2 six==1.9.0 sqlparse==0.1.15 From 84393e9e4a107ec2f784d088bec854585cfe774b Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 11 Jul 2016 23:26:43 +0100 Subject: [PATCH 015/275] Modify user creation test to replicate special character issue in #251 --- RIGS/test_functional.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 154fdfb2..7e5010f7 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- +import os +import re +from datetime import date, timedelta + +import reversion +from django.core import mail +from django.db import transaction from django.test import LiveServerTestCase from django.test.client import Client -from django.core import mail from selenium import webdriver -from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import StaleElementReferenceException, WebDriverException +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait + from RIGS import models -import re -import os -from datetime import date, timedelta -from django.db import transaction -import reversion -import json class UserRegistrationTest(LiveServerTestCase): @@ -103,7 +104,7 @@ class UserRegistrationTest(LiveServerTestCase): # Check Email self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] - self.assertIn('activation required', email.subject) + self.assertIn('John Smith "JS" activation required', email.subject) urls = re.findall( 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', email.body) self.assertEqual(len(urls), 1) From 4f839d05f9fbe96b62f95b9100d3599342ce12c3 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 11 Jul 2016 23:28:15 +0100 Subject: [PATCH 016/275] Fix issues with special characters in registration email subject. Closes #251 --- templates/registration/activation_email_subject.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/registration/activation_email_subject.txt b/templates/registration/activation_email_subject.txt index 2237d0a8..1f707f9e 100644 --- a/templates/registration/activation_email_subject.txt +++ b/templates/registration/activation_email_subject.txt @@ -1 +1 @@ -{{ user }} activation required \ No newline at end of file +{{ user|safe }} activation required From fc6db5bff20bbe466acf6cc6f4234691798ee116 Mon Sep 17 00:00:00 2001 From: davidtaylorhq Date: Wed, 13 Jul 2016 23:19:31 +0100 Subject: [PATCH 017/275] Heroku Staging Setup (#250) Heroku Staging Setup Includes data generation --- PyRIGS/settings.py | 5 + README.md | 17 ++ RIGS/management/__init__.py | 0 RIGS/management/commands/__init__.py | 0 .../management/commands/generateSampleData.py | 248 ++++++++++++++++++ RIGS/test_unit.py | 31 ++- app.json | 3 +- 7 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 RIGS/management/__init__.py create mode 100644 RIGS/management/commands/__init__.py create mode 100644 RIGS/management/commands/generateSampleData.py diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 3098c3fb..2e82807e 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -21,10 +21,15 @@ SECRET_KEY = os.environ.get('SECRET_KEY') if os.environ.get('SECRET_KEY') else ' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True +STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False + TEMPLATE_DEBUG = True ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com'] +if STAGING: + ALLOWED_HOSTS.append('.herokuapp.com') + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') if not DEBUG: SECURE_SSL_REDIRECT = True # Redirect all http requests to https diff --git a/README.md b/README.md index 809c037c..51bfec16 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,22 @@ python manage.py runserver ``` Please refer to Django documentation for a full list of options available here. +### Sample Data ### +Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty: +``` +python manage.py flush +``` +Then load the sample data using the command: +``` +python manage.py generateSampleData +``` +4 user accounts are created for convenience: +|Username |Password | +|---------|---------| +|superuser|superuser| +|finance |finance | +|keyholder|keyholder| +|basic |basic | + ### Committing, pushing and testing ### Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person. diff --git a/RIGS/management/__init__.py b/RIGS/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/RIGS/management/commands/__init__.py b/RIGS/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py new file mode 100644 index 00000000..bf1ce7d2 --- /dev/null +++ b/RIGS/management/commands/generateSampleData.py @@ -0,0 +1,248 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import Group, Permission +from django.db import transaction +import reversion + +import datetime +import random + +from RIGS import models +class Command(BaseCommand): + help = 'Adds sample data to use for testing' + can_import_settings = True + + people = [] + organisations = [] + venues = [] + profiles = [] + + keyholder_group = None + finance_group = None + + + def handle(self, *args, **options): + from django.conf import settings + + if not (settings.DEBUG or settings.STAGING): + raise CommandError('You cannot run this command in production') + + random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests + + with transaction.atomic(): + models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') + + self.setupGenericProfiles() + + self.setupPeople() + self.setupOrganisations() + self.setupVenues() + + self.setupGroups() + + self.setupEvents() + + self.setupUsefulProfiles() + + def setupPeople(self): + names = ["Regulus Black","Sirius Black","Lavender Brown","Cho Chang","Vincent Crabbe","Vincent Crabbe","Bartemius Crouch","Fleur Delacour","Cedric Diggory","Alberforth Dumbledore","Albus Dumbledore","Dudley Dursley","Petunia Dursley","Vernon Dursley","Argus Filch","Seamus Finnigan","Nicolas Flamel","Cornelius Fudge","Goyle","Gregory Goyle","Hermione Granger","Rubeus Hagrid","Igor Karkaroff","Viktor Krum","Bellatrix Lestrange","Alice Longbottom","Frank Longbottom","Neville Longbottom","Luna Lovegood","Xenophilius Lovegood","Remus Lupin","Draco Malfoy","Lucius Malfoy","Narcissa Malfoy","Olympe Maxime","Minerva McGonagall","Mad-Eye Moody","Peter Pettigrew","Harry Potter","James Potter","Lily Potter","Quirinus Quirrell","Tom Riddle","Mary Riddle","Lord Voldemort","Rita Skeeter","Severus Snape","Nymphadora Tonks","Dolores Janes Umbridge","Arthur Weasley","Bill Weasley","Charlie Weasley","Fred Weasley","George Weasley","Ginny Weasley","Molly Weasley","Percy Weasley","Ron Weasley","Dobby","Fluffy","Hedwig","Moaning Myrtle","Aragog","Grawp"] + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + + newPerson = models.Person.objects.create(name=name) + if i % 3 == 0: + newPerson.email = "address@person.com" + + if i % 5 == 0: + newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newPerson.phone = "01234 567894" + + newPerson.save() + self.people.append(newPerson) + + def setupOrganisations(self): + names = ["Acme, inc.","Widget Corp","123 Warehousing","Demo Company","Smith and Co.","Foo Bars","ABC Telecom","Fake Brothers","QWERTY Logistics","Demo, inc.","Sample Company","Sample, inc","Acme Corp","Allied Biscuit","Ankh-Sto Associates","Extensive Enterprise","Galaxy Corp","Globo-Chem","Mr. Sparkle","Globex Corporation","LexCorp","LuthorCorp","North Central Positronics","Omni Consimer Products","Praxis Corporation","Sombra Corporation","Sto Plains Holdings","Tessier-Ashpool","Wayne Enterprises","Wentworth Industries","ZiffCorp","Bluth Company","Strickland Propane","Thatherton Fuels","Three Waters","Water and Power","Western Gas & Electric","Mammoth Pictures","Mooby Corp","Gringotts","Thrift Bank","Flowers By Irene","The Legitimate Businessmens Club","Osato Chemicals","Transworld Consortium","Universal Export","United Fried Chicken","Virtucon","Kumatsu Motors","Keedsler Motors","Powell Motors","Industrial Automation","Sirius Cybernetics Corporation","U.S. Robotics and Mechanical Men","Colonial Movers","Corellian Engineering Corporation","Incom Corporation","General Products","Leeding Engines Ltd.","Blammo","Input, Inc.","Mainway Toys","Videlectrix","Zevo Toys","Ajax","Axis Chemical Co.","Barrytron","Carrys Candles","Cogswell Cogs","Spacely Sprockets","General Forge and Foundry","Duff Brewing Company","Dunder Mifflin","General Services Corporation","Monarch Playing Card Co.","Krustyco","Initech","Roboto Industries","Primatech","Sonky Rubber Goods","St. Anky Beer","Stay Puft Corporation","Vandelay Industries","Wernham Hogg","Gadgetron","Burleigh and Stronginthearm","BLAND Corporation","Nordyne Defense Dynamics","Petrox Oil Company","Roxxon","McMahon and Tate","Sixty Second Avenue","Charles Townsend Agency","Spade and Archer","Megadodo Publications","Rouster and Sideways","C.H. Lavatory and Sons","Globo Gym American Corp","The New Firm","SpringShield","Compuglobalhypermeganet","Data Systems","Gizmonic Institute","Initrode","Taggart Transcontinental","Atlantic Northern","Niagular","Plow King","Big Kahuna Burger","Big T Burgers and Fries","Chez Quis","Chotchkies","The Frying Dutchman","Klimpys","The Krusty Krab","Monks Diner","Milliways","Minuteman Cafe","Taco Grande","Tip Top Cafe","Moes Tavern","Central Perk","Chasers"] + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + newOrganisation = models.Organisation.objects.create(name=name) + if i % 2 == 0: + newOrganisation.has_su_account = True + + if i % 3 == 0: + newOrganisation.email = "address@organisation.com" + + if i % 5 == 0: + newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newOrganisation.phone = "01234 567894" + + newOrganisation.save() + self.organisations.append(newOrganisation) + + def setupVenues(self): + names = ["Bear Island","Crossroads Inn","Deepwood Motte","The Dreadfort","The Eyrie","Greywater Watch","The Iron Islands","Karhold","Moat Cailin","Oldstones","Raventree Hall","Riverlands","The Ruby Ford","Saltpans","Seagard","Torrhen's Square","The Trident","The Twins","The Vale of Arryn","The Whispering Wood","White Harbor","Winterfell","The Arbor","Ashemark","Brightwater Keep","Casterly Rock","Clegane's Keep","Dragonstone","Dorne","God's Eye","The Golden Tooth","Harrenhal","Highgarden","Horn Hill","Fingers","King's Landing","Lannisport","Oldtown","Rainswood","Storm's End","Summerhall","Sunspear","Tarth","Castle Black","Craster's Keep","Fist of the First Men","The Frostfangs","The Gift","The Skirling Pass","The Wall","Asshai","Astapor","Braavos","The Dothraki Sea","Lys","Meereen","Myr","Norvos","Pentos","Qarth","Qohor","The Red Waste","Tyrosh","Vaes Dothrak","Valyria","Village of the Lhazareen","Volantis","Yunkai"] + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + newVenue = models.Venue.objects.create(name=name) + if i % 2 == 0: + newVenue.three_phase_available = True + + if i % 3 == 0: + newVenue.email = "address@venue.com" + + if i % 5 == 0: + newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newVenue.phone = "01234 567894" + + newVenue.save() + self.venues.append(newVenue) + + def setupGroups(self): + self.keyholder_group = Group.objects.create(name='Keyholders') + self.finance_group = Group.objects.create(name='Finance') + + keyholderPerms = ["add_event","change_event","view_event","add_eventitem","change_eventitem","delete_eventitem","add_organisation","change_organisation","view_organisation","add_person","change_person","view_person","view_profile","add_venue","change_venue","view_venue"] + financePerms = ["change_event","view_event","add_eventitem","change_eventitem","add_invoice","change_invoice","view_invoice","add_organisation","change_organisation","view_organisation","add_payment","change_payment","delete_payment","add_person","change_person","view_person"] + + for permId in keyholderPerms: + self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) + + for permId in financePerms: + self.finance_group.permissions.add(Permission.objects.get(codename=permId)) + + def setupGenericProfiles(self): + names = ["Clara Oswin Oswald","Rory Williams","Amy Pond","River Song","Martha Jones","Donna Noble","Jack Harkness","Mickey Smith","Rose Tyler"] + for i, name in enumerate(names): + newProfile = models.Profile.objects.create(username=name.replace(" ",""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1], + email=name.replace(" ","")+"@example.com", + initials="".join([ j[0].upper() for j in name.split() ])) + if i % 2 == 0: + newProfile.phone = "01234 567894" + + newProfile.save() + self.profiles.append(newProfile) + + def setupUsefulProfiles(self): + superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU", + email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True) + superUser.set_password('superuser') + superUser.save() + + financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU", + email="financeuser@example.com", is_active=True) + financeUser.groups.add(self.finance_group) + financeUser.groups.add(self.keyholder_group) + financeUser.set_password('finance') + financeUser.save() + + keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU", + email="keyholderuser@example.com", is_active=True) + keyholderUser.groups.add(self.keyholder_group) + keyholderUser.set_password('keyholder') + keyholderUser.save() + + basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU", + email="basicuser@example.com", is_active=True) + basicUser.set_password('basic') + basicUser.save() + + def setupEvents(self): + names = ["Outdoor Concert","Hall Open Mic Night","Festival","Weekend Event","Magic Show","Society Ball","Evening Show","Talent Show","Acoustic Evening","Hire of Things","SU Event","End of Term Show","Theatre Show","Outdoor Fun Day","Summer Carnival","Open Days","Magic Show","Awards Ceremony","Debating Event","Club Night","DJ Evening","Building Projection","Choir Concert"] + descriptions = ["A brief desciption of the event","This event is boring","Probably wont happen","Warning: this has lots of kit"] + notes = ["The client came into the office at some point","Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"] + + itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00}, + {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00}, + {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52}, + {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00}, + {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50}, + {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00}, + {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00}, + {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00}, + {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}] + + dayDelta = -120 # start adding events from 4 months ago + + for i in range(150): # Let's add 100 events + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + + name = names[i%len(names)] + + startDate = datetime.date.today() + datetime.timedelta(days=dayDelta) + dayDelta = dayDelta + random.randint(0,3) + + newEvent = models.Event.objects.create(name=name, start_date=startDate) + + if random.randint(0,2) > 1: # 1 in 3 have a start time + newEvent.start_time = datetime.time(random.randint(15,20)) + if random.randint(0,2) > 1: # of those, 1 in 3 have an end time on the same day + newEvent.end_time = datetime.time(random.randint(21,23)) + elif random.randint(0,1)>0: # half of the others finish early the next day + newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1) + newEvent.end_time = datetime.time(random.randint(0,5)) + elif random.randint(0,2)>1: # 1 in 3 of the others finish a few days ahead + newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1,4)) + + + if random.randint(0,6) > 0: # 5 in 6 have MIC + newEvent.mic = random.choice(self.profiles) + + if random.randint(0,6) > 0: # 5 in 6 have organisation + newEvent.organisation = random.choice(self.organisations) + + if random.randint(0,6) > 0: # 5 in 6 have person + newEvent.person = random.choice(self.people) + + if random.randint(0,6) > 0: # 5 in 6 have venue + newEvent.venue = random.choice(self.venues) + + # Could have any status, equally weighted + newEvent.status = random.choice([models.Event.BOOKED,models.Event.CONFIRMED,models.Event.PROVISIONAL, models.Event.CANCELLED]) + + newEvent.dry_hire = (random.randint(0,7)==0) # 1 in 7 are dry hire + + if random.randint(0,1) > 0: # 1 in 2 have description + newEvent.description = random.choice(descriptions) + + if random.randint(0,1) > 0: # 1 in 2 have notes + newEvent.notes = random.choice(notes) + + newEvent.save() + + # Now add some items + for j in range(random.randint(1,5)): + itemData = itemOptions[random.randint(0,len(itemOptions)-1)] + newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) + newItem.save() + + while newEvent.sum_total < 0: + itemData = itemOptions[random.randint(0,len(itemOptions)-1)] + newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) + newItem.save() + + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + if newEvent.start_date < datetime.date.today(): # think about adding an invoice + if random.randint(0,2) > 0: # 2 in 3 have had paperwork sent to treasury + newInvoice = models.Invoice.objects.create(event=newEvent) + if newEvent.status is models.Event.CANCELLED: # void cancelled events + newInvoice.void = True + elif random.randint(0,2)>1: # 1 in 3 have been paid + models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today()) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index c51874f4..f0c778b3 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -1,9 +1,12 @@ -from django.core.urlresolvers import reverse -from django.test import TestCase from datetime import date -from RIGS import models from django.core.exceptions import ObjectDoesNotExist +from django.core.management import call_command +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.test.utils import override_settings + +from RIGS import models class TestAdminMergeObjects(TestCase): @@ -160,7 +163,7 @@ class TestInvoiceDelete(TestCase): @classmethod def setUpTestData(cls): cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) - + cls.events = { 1: models.Event.objects.create(name="TE E1", start_date=date.today()), 2: models.Event.objects.create(name="TE E2", start_date=date.today()) @@ -182,7 +185,7 @@ class TestInvoiceDelete(TestCase): def test_invoice_delete_allowed(self): request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[2].pk}) - + response = self.client.get(request_url, follow=True) self.assertContains(response, "Are you sure") @@ -197,7 +200,7 @@ class TestInvoiceDelete(TestCase): def test_invoice_delete_not_allowed(self): request_url = reverse('invoice_delete', kwargs={'pk':self.invoices[1].pk}) - + response = self.client.get(request_url, follow=True) self.assertContains(response, "To delete an invoice, delete the payments first.") @@ -208,4 +211,18 @@ class TestInvoiceDelete(TestCase): response = self.client.post(request_url, follow=True) # Check this didn't work - self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) \ No newline at end of file + self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) + +class TestSampleDataGenerator(TestCase): + @override_settings(DEBUG=True) + def test_generate_sample_data(self): + # Run the management command and check there are no exceptions + call_command('generateSampleData') + + # Check there are lots of events + self.assertTrue(models.Event.objects.all().count() > 100) + + def test_production_exception(self): + from django.core.management.base import CommandError + + self.assertRaisesRegexp(CommandError, ".*production", call_command, 'generateSampleData') diff --git a/app.json b/app.json index ea2e5c25..737e5eb0 100644 --- a/app.json +++ b/app.json @@ -2,12 +2,13 @@ "name": "PyRIGS", "description": "", "scripts": { - "postdeploy": "python manage.py migrate" + "postdeploy": "python manage.py migrate && python manage.py generateSampleData" }, "env": { "DEBUG": { "required": true }, + "STAGING": "1", "EMAIL_FROM": { "required": true }, From b4246fe170766b9e6906700fc3d5ca4d617761c3 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 14 Jul 2016 00:04:09 +0100 Subject: [PATCH 018/275] Fix for MIC field overflowing the bottom of the panel #218 --- RIGS/templates/RIGS/event_form.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index 28e307ca..b2b4dcd7 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -63,7 +63,7 @@ } else { $('.form-is_rig').slideDown(); } - $('.form-hws').css('overflow', 'visible'); + $('.form-hws, .form-hws .form-is_rig').css('overflow', 'visible'); } else { $('#{{form.is_rig.auto_id}}').prop('checked', false); $('.form-is_rig').slideUp(); @@ -445,4 +445,4 @@ {% include 'RIGS/item_modal.html' %} -{% endblock %} \ No newline at end of file +{% endblock %} From 92f4e268831284a6838da73b9abe0543e60c8b4a Mon Sep 17 00:00:00 2001 From: Sam Osborne Date: Thu, 22 Sep 2016 12:58:43 +0100 Subject: [PATCH 019/275] Added link to subhire form Until such a point that subhire on RIGS actually happens, it's useful to have this link here. --- RIGS/templates/RIGS/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index a99dfa18..d67f82c9 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -26,6 +26,7 @@ TEC Forum TEC Wiki Price List + Subhire Insurance Form
@@ -75,4 +76,4 @@ {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} From 6370679b625f5055ea0dc6d1045910218575b744 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 3 Oct 2016 22:45:57 +0100 Subject: [PATCH 020/275] Initial proof of concept --- RIGS/rigboard.py | 15 +++++++++++++++ RIGS/urls.py | 3 +++ templates/registration/login.html | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index a6ccde09..d1b17a89 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -14,6 +14,7 @@ from django.db.models import Q from django.contrib import messages from z3c.rml import rml2pdf from PyPDF2 import PdfFileMerger, PdfFileReader +import simplejson from RIGS import models, forms import datetime @@ -46,6 +47,20 @@ class WebCalendar(generic.TemplateView): class EventDetail(generic.DetailView): model = models.Event +class EventOembed(generic.View): + model = models.Event + + def get(self, request, pk=None): + + object = get_object_or_404(self.model, pk=pk) + data = { + 'html': 'this is some html', + 'version': '1.0', + 'type': 'rich', + } + json = simplejson.dumps(data) + return HttpResponse(json, content_type="application/json") + class EventCreate(generic.CreateView): model = models.Event diff --git a/RIGS/urls.py b/RIGS/urls.py index ee949ff8..53bba8da 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -82,6 +82,9 @@ urlpatterns = patterns('', url(r'^event/(?P\d+)/$', permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), name='event_detail'), + url(r'^event/(?P\d+)/oembed/$', + rigboard.EventOembed.as_view(), + name='event_oembed'), url(r'^event/(?P\d+)/print/$', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), name='event_print'), diff --git a/templates/registration/login.html b/templates/registration/login.html index ace80d16..2b1f1a72 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -2,6 +2,14 @@ {% block title %}Login{% endblock %} +{% block extra-head %} + {% if next %} + + {% endif %} +{% endblock %} + {% block content %} {% include 'registration/loginform.html' %} {% endblock %} \ No newline at end of file From 64f3842a1317dc72569e6999d2d02734c4b38c44 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 3 Oct 2016 23:02:19 +0100 Subject: [PATCH 021/275] Added iframe to embed --- RIGS/rigboard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index d1b17a89..e61571d1 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -53,8 +53,12 @@ class EventOembed(generic.View): def get(self, request, pk=None): object = get_object_or_404(self.model, pk=pk) + + base_url = "https://rigs.nottinghamtec.co.uk" + full_url = base_url+str(object.get_absolute_url()) + data = { - 'html': 'this is some html', + 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', } From f7ea0cb8341b9a1af7ef3e5df2435a39ae9e4aa6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 3 Oct 2016 23:09:57 +0100 Subject: [PATCH 022/275] Remove security from event detail (for testing in staging) --- RIGS/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index 53bba8da..14b4bd0d 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -80,7 +80,7 @@ urlpatterns = patterns('', name='activity_feed'), url(r'^event/(?P\d+)/$', - permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), + rigboard.EventDetail.as_view(), name='event_detail'), url(r'^event/(?P\d+)/oembed/$', rigboard.EventOembed.as_view(), From eb10c8e21fa50da6c2fb9d3421a7fe4a6ee86f77 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 3 Oct 2016 23:13:25 +0100 Subject: [PATCH 023/275] Add meta to detail page --- RIGS/templates/RIGS/event_detail.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index d4b089a9..3f7d6be5 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -1,6 +1,14 @@ {% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} {% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %} +{% block extra-head %} + + + +{% endblock %} + {% block content %}
{% if not request.is_ajax %} From 585f909d3f4deed26c873134f1af1e9b6c043369 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 4 Oct 2016 21:05:07 +0100 Subject: [PATCH 024/275] Escape JSON --- RIGS/rigboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index e61571d1..3a4e3df6 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -62,7 +62,8 @@ class EventOembed(generic.View): 'version': '1.0', 'type': 'rich', } - json = simplejson.dumps(data) + # need to do this: @xframe_options_exempt + json = simplejson.JSONEncoderForHTML(data) return HttpResponse(json, content_type="application/json") From a02087bf2a2f18fe76752e11429a52d1512aa953 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 4 Oct 2016 21:11:43 +0100 Subject: [PATCH 025/275] Fixed fail --- RIGS/rigboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 3a4e3df6..b56ca7d3 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -63,7 +63,7 @@ class EventOembed(generic.View): 'type': 'rich', } # need to do this: @xframe_options_exempt - json = simplejson.JSONEncoderForHTML(data) + json = simplejson.JSONEncoderForHTML().encode(data) return HttpResponse(json, content_type="application/json") From 17c7a3c52446895a1248c976b81ff314766f3597 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 5 Oct 2016 10:39:50 +0100 Subject: [PATCH 026/275] Made embed tag use absolute URL --- RIGS/templates/RIGS/event_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 3f7d6be5..4dcaf612 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -4,7 +4,7 @@ {% block extra-head %} {% endblock %} From 0244f5cfca0038c12222fade438ca2112c5c521a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 5 Oct 2016 10:42:49 +0100 Subject: [PATCH 027/275] Restored login security to events --- RIGS/templates/RIGS/event_detail.html | 8 -------- RIGS/urls.py | 2 +- templates/registration/login.html | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 4dcaf612..d4b089a9 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -1,14 +1,6 @@ {% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} {% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %} -{% block extra-head %} - - - -{% endblock %} - {% block content %}
{% if not request.is_ajax %} diff --git a/RIGS/urls.py b/RIGS/urls.py index 14b4bd0d..53bba8da 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -80,7 +80,7 @@ urlpatterns = patterns('', name='activity_feed'), url(r'^event/(?P\d+)/$', - rigboard.EventDetail.as_view(), + permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/oembed/$', rigboard.EventOembed.as_view(), diff --git a/templates/registration/login.html b/templates/registration/login.html index 2b1f1a72..f9db2e81 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -5,7 +5,7 @@ {% block extra-head %} {% if next %} {% endif %} {% endblock %} From 4b94ea7ef2514bd5830342c23621370c853ea44a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 12:02:44 +0100 Subject: [PATCH 028/275] Made login redirect JS for event detail --- PyRIGS/decorators.py | 17 +++++++++++++---- RIGS/urls.py | 2 +- templates/login_redirect_embed.html | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 templates/login_redirect_embed.html diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 67d8964c..4af93097 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -2,8 +2,9 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.shortcuts import render_to_response from django.template import RequestContext from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse -def user_passes_test_with_403(test_func, login_url=None): +def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): """ Decorator for views that checks that the user passes the given test. @@ -13,12 +14,20 @@ def user_passes_test_with_403(test_func, login_url=None): if not login_url: from django.conf import settings login_url = settings.LOGIN_URL + def _dec(view_func): def _checklogin(request, *args, **kwargs): if test_func(request.user): return view_func(request, *args, **kwargs) elif not request.user.is_authenticated(): - return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) + if oembed_view is not None: + extra_context = {} + extra_context['oembed_url'] = request.scheme + '://' + request.META['HTTP_HOST'] + reverse(oembed_view, kwargs=kwargs) + extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) + resp = render_to_response('login_redirect_embed.html', extra_context, context_instance=RequestContext(request)) + return resp + else: + return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) else: resp = render_to_response('403.html', context_instance=RequestContext(request)) resp.status_code = 403 @@ -28,12 +37,12 @@ def user_passes_test_with_403(test_func, login_url=None): return _checklogin return _dec -def permission_required_with_403(perm, login_url=None): +def permission_required_with_403(perm, login_url=None, oembed_view=None): """ Decorator for views that checks whether a user has a particular permission enabled, redirecting to the log-in page or rendering a 403 as necessary. """ - return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url) + return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url, oembed_view=oembed_view) from RIGS import models diff --git a/RIGS/urls.py b/RIGS/urls.py index 53bba8da..16da83da 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -80,7 +80,7 @@ urlpatterns = patterns('', name='activity_feed'), url(r'^event/(?P\d+)/$', - permission_required_with_403('RIGS.view_event')(rigboard.EventDetail.as_view()), + permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/oembed/$', rigboard.EventOembed.as_view(), diff --git a/templates/login_redirect_embed.html b/templates/login_redirect_embed.html new file mode 100644 index 00000000..afd3487c --- /dev/null +++ b/templates/login_redirect_embed.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% block title %}Login Required{% endblock %} + +{% block js %} + +{% endblock %} + +{% block extra-head %} + {% if oembed_url %} + + {% endif %} +{% endblock %} + +{% block content %} +
+

Login is required for this page

+ Login +
+{% endblock %} From 69b0ff9fae63a1c2e5a065e59020444ef4fcc971 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 12:52:33 +0100 Subject: [PATCH 029/275] Made embed page, with clickjacking protection turned off --- RIGS/rigboard.py | 8 ++ RIGS/templates/RIGS/event_embed.html | 122 +++++++++++++++++++++++++++ RIGS/urls.py | 5 +- 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 RIGS/templates/RIGS/event_embed.html diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index b56ca7d3..23fd5e6a 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -12,6 +12,7 @@ from django.conf import settings from django.http import HttpResponse from django.db.models import Q from django.contrib import messages +from django.views.decorators.clickjacking import xframe_options_exempt from z3c.rml import rml2pdf from PyPDF2 import PdfFileMerger, PdfFileReader import simplejson @@ -66,6 +67,13 @@ class EventOembed(generic.View): json = simplejson.JSONEncoderForHTML().encode(data) return HttpResponse(json, content_type="application/json") +class EventEmbed(EventDetail): + template_name = 'RIGS/event_embed.html' + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super(EventEmbed, self).get(request, *args, **kwargs) + class EventCreate(generic.CreateView): model = models.Event diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html new file mode 100644 index 00000000..2c13dd39 --- /dev/null +++ b/RIGS/templates/RIGS/event_embed.html @@ -0,0 +1,122 @@ +{% load static from staticfiles %} +{% load raven %} + + + + + + + + + + + + + + + + + + +{% include "analytics.html" %} + +
+
+
+
+

+ + + + {% if object.mic %} + {{ object.mic.initials }} +
+ +
+ + {% elif object.is_rig %} + + {% endif %} +
+
+ + + {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} + | {{ object.name }} + {% if object.venue %} + at {{ object.venue }} + {% endif %} + + + +

+ + {% if object.is_rig %} +
+ {{ object.person.name }} + {% if object.organisation %} + for {{ object.organisation.name }} + {% endif %} + {% if object.dry_hire %}Dry Hire{% endif %} +
+ {% endif %} + +

+ {{ event.start_date|date:"D d/m/Y" }} + {% if event.end_date and event.end_date != event.start_date %} + {{ event.end_date|date:"D d/m/Y" }} + {% endif %} + ({{ event.get_status_display }}) +

+

+ {{ event.description|linebreaksbr }} +

+
+
+ {% if not object.cancelled %} +
+ {% if object.meet_at %} +
Crew meet
+
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
+ {% endif %} + {% if object.has_start_time %} +
Event starts
+
+ {{ object.start_time|date:"H:i" }} + {{ object.start_date|date:"(Y-m-d)" }}
+
+ {% endif %} + {% if object.has_end_time%}{% if object.start_date != object.end_date or object.start_time != object.end_time %} +
Event ends
+
+ {{ object.end_time|date:"H:i" }} + {{ object.end_date|date:"(Y-m-d)" }} +
+ {% endif %}{% endif %} +
+ {% endif %} +
+
+
+
+
+ +{% block js %} +{% endblock %} + + \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index 16da83da..b0eca4c4 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -82,7 +82,10 @@ urlpatterns = patterns('', url(r'^event/(?P\d+)/$', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), - url(r'^event/(?P\d+)/oembed/$', + url(r'^event/(?P\d+)/embed/$', + permission_required_with_403('RIGS.view_event')(rigboard.EventEmbed.as_view()), + name='event_oembed'), + url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), name='event_oembed'), url(r'^event/(?P\d+)/print/$', From 59efc2c4850d4ef425087667aaefddbd7c6dfba4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 12:59:37 +0100 Subject: [PATCH 030/275] Fixed JSON --- RIGS/rigboard.py | 5 ++--- RIGS/urls.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 23fd5e6a..a22f78ae 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -9,6 +9,7 @@ from django.shortcuts import get_object_or_404 from django.template import RequestContext from django.template.loader import get_template from django.conf import settings +from django.core.urlresolvers import reverse from django.http import HttpResponse from django.db.models import Q from django.contrib import messages @@ -52,11 +53,9 @@ class EventOembed(generic.View): model = models.Event def get(self, request, pk=None): - - object = get_object_or_404(self.model, pk=pk) base_url = "https://rigs.nottinghamtec.co.uk" - full_url = base_url+str(object.get_absolute_url()) + full_url = base_url+reverse('event_embed', args=[pk]) data = { 'html': ''.format(full_url), diff --git a/RIGS/urls.py b/RIGS/urls.py index b0eca4c4..a2fbc7c1 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -84,7 +84,7 @@ urlpatterns = patterns('', name='event_detail'), url(r'^event/(?P\d+)/embed/$', permission_required_with_403('RIGS.view_event')(rigboard.EventEmbed.as_view()), - name='event_oembed'), + name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), name='event_oembed'), From 466032296457c6d528bf16fa42c3ecc7153e277a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 13:04:33 +0100 Subject: [PATCH 031/275] Remove hardcoded URL --- RIGS/rigboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index a22f78ae..d42c9fa8 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -54,7 +54,7 @@ class EventOembed(generic.View): def get(self, request, pk=None): - base_url = "https://rigs.nottinghamtec.co.uk" + base_url = request.scheme + '://' + request.META['HTTP_HOST'] full_url = base_url+reverse('event_embed', args=[pk]) data = { From f0bb4c5b02b4304b295f7dc5785d23030972293e Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 13:13:09 +0100 Subject: [PATCH 032/275] Move exemption to urls.py (cleaner) --- RIGS/rigboard.py | 6 ------ RIGS/urls.py | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index d42c9fa8..91265871 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -13,7 +13,6 @@ from django.core.urlresolvers import reverse from django.http import HttpResponse from django.db.models import Q from django.contrib import messages -from django.views.decorators.clickjacking import xframe_options_exempt from z3c.rml import rml2pdf from PyPDF2 import PdfFileMerger, PdfFileReader import simplejson @@ -69,11 +68,6 @@ class EventOembed(generic.View): class EventEmbed(EventDetail): template_name = 'RIGS/event_embed.html' - @xframe_options_exempt - def get(self, request, *args, **kwargs): - return super(EventEmbed, self).get(request, *args, **kwargs) - - class EventCreate(generic.CreateView): model = models.Event form_class = forms.EventForm diff --git a/RIGS/urls.py b/RIGS/urls.py index a2fbc7c1..beae4ec2 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -2,6 +2,7 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView +from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required @@ -83,7 +84,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - permission_required_with_403('RIGS.view_event')(rigboard.EventEmbed.as_view()), + xframe_options_exempt(permission_required_with_403('RIGS.view_event')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From 1bdc4bd293247e484aa7fbf63943cb42197c62eb Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 13:22:47 +0100 Subject: [PATCH 033/275] Fixed description = none in embed --- RIGS/templates/RIGS/event_embed.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 2c13dd39..9670d38e 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -77,15 +77,17 @@ {% endif %}

- {{ event.start_date|date:"D d/m/Y" }} - {% if event.end_date and event.end_date != event.start_date %} - {{ event.end_date|date:"D d/m/Y" }} + {{ object.start_date|date:"D d/m/Y" }} + {% if object.end_date and object.end_date != object.start_date %} + {{ object.end_date|date:"D d/m/Y" }} {% endif %} - ({{ event.get_status_display }}) -

-

- {{ event.description|linebreaksbr }} + ({{ object.get_status_display }})

+ {% if object.description %} +

+ {{ object.description|linebreaksbr }} +

+ {% endif %}
{% if not object.cancelled %} From 441a2be0b8d11a03aeebe9c05f45d3c0fd051f08 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 16:08:01 +0100 Subject: [PATCH 034/275] Added embedded login, and all iframe links open in new tab --- RIGS/templates/RIGS/event_embed.html | 56 ++++--------------------- RIGS/urls.py | 3 +- RIGS/views.py | 9 ++++ templates/base_embed.html | 51 ++++++++++++++++++++++ templates/registration/login_embed.html | 11 +++++ 5 files changed, 82 insertions(+), 48 deletions(-) create mode 100644 templates/base_embed.html create mode 100644 templates/registration/login_embed.html diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 9670d38e..49c8384e 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -1,44 +1,12 @@ +{% extends 'base_embed.html' %} {% load static from staticfiles %} -{% load raven %} +{% block content %} - - - - - - - - - - - - - - - - -{% include "analytics.html" %} - -
-
-
+ +
+

@@ -54,7 +22,7 @@ {% endif %} - + {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{ object.name }} @@ -79,7 +47,7 @@

{{ object.start_date|date:"D d/m/Y" }} {% if object.end_date and object.end_date != object.start_date %} - {{ object.end_date|date:"D d/m/Y" }} + – {{ object.end_date|date:"D d/m/Y" }} {% endif %} ({{ object.get_status_display }})

@@ -88,8 +56,7 @@ {{ object.description|linebreaksbr }}

{% endif %} -

-
+ {% if not object.cancelled %}
{% if object.meet_at %} @@ -115,10 +82,5 @@
-
-
-{% block js %} -{% endblock %} - - \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index beae4ec2..b402a109 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -15,6 +15,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), + url('^user/login/embed/$', 'RIGS.views.login_embed', name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People @@ -84,7 +85,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - xframe_options_exempt(permission_required_with_403('RIGS.view_event')(rigboard.EventEmbed.as_view())), + xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), diff --git a/RIGS/views.py b/RIGS/views.py index c013ce1d..fded9865 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -35,6 +35,15 @@ def login(request, **kwargs): return login(request) +def login_embed(request, **kwargs): + if request.user.is_authenticated(): + next = request.REQUEST.get('next', '/') + return HttpResponseRedirect(request.REQUEST.get('next', '/')) + else: + from django.contrib.auth.views import login + + return login(request, template_name="registration/login_embed.html") + """ Called from a modal window (e.g. when an item is submitted to an event/invoice). May optionally also include some javascript in a success message to cause a load of diff --git a/templates/base_embed.html b/templates/base_embed.html new file mode 100644 index 00000000..2dd1dcc7 --- /dev/null +++ b/templates/base_embed.html @@ -0,0 +1,51 @@ +{% load static from staticfiles %} +{% load raven %} + + + + + + + + + + + + + + + + + + + + + +{% include "analytics.html" %} + +
+
+ {% block content %} + {% endblock %} +
+
+ +{% block js %} +{% endblock %} + + \ No newline at end of file diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html new file mode 100644 index 00000000..1cfdfa5b --- /dev/null +++ b/templates/registration/login_embed.html @@ -0,0 +1,11 @@ +{% extends 'base_embed.html' %} + +{% block title %}Login{% endblock %} + +{% block content %} +
+

Rig Information Gathering System

+
+{% include 'registration/loginform.html' %} + +{% endblock %} \ No newline at end of file From 1b28efb6afd162b805138dc7981d7b2a5e9e02b0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 16:10:51 +0100 Subject: [PATCH 035/275] Allow the embedded login to be embedded (useful feature) --- RIGS/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index b402a109..8767d7b9 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -15,7 +15,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), - url('^user/login/embed/$', 'RIGS.views.login_embed', name='login_embed'), + url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People From 25a3ef3f0c8234895662d04dd113af1d80c1b05b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 16:15:53 +0100 Subject: [PATCH 036/275] Don't login in new window --- templates/registration/loginform.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/registration/loginform.html b/templates/registration/loginform.html index 74a3787b..4ec09d2c 100644 --- a/templates/registration/loginform.html +++ b/templates/registration/loginform.html @@ -3,7 +3,7 @@ {% include 'form_errors.html' %}
-
{% csrf_token %} + {% csrf_token %}
{% render_field form.username class+="form-control" placeholder=form.username.label autofocus="" %} From 3f752cd7b7100bf5bcf367adf96050aad7b18ca5 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 16:48:19 +0100 Subject: [PATCH 037/275] Made embed prettier --- RIGS/templates/RIGS/event_embed.html | 92 ++++++++++++++++------------ templates/base_embed.html | 1 + 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 49c8384e..cd0cf360 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -3,12 +3,23 @@ {% block content %} + +
+
+ Rig Information Gathering System +
- -

- + +

{% if object.mic %} @@ -21,7 +32,6 @@ {% endif %} - {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} @@ -32,7 +42,15 @@ -

+

+ +

+ {{ object.start_date|date:"D d/m/Y" }} + {% if object.end_date and object.end_date != object.start_date %} + – {{ object.end_date|date:"D d/m/Y" }} + {% endif %} + ({{ object.get_status_display }}) +

{% if object.is_rig %}
@@ -44,41 +62,39 @@
{% endif %} -

- {{ object.start_date|date:"D d/m/Y" }} - {% if object.end_date and object.end_date != object.start_date %} - – {{ object.end_date|date:"D d/m/Y" }} - {% endif %} - ({{ object.get_status_display }}) -

- {% if object.description %} -

- {{ object.description|linebreaksbr }} -

- {% endif %} - {% if not object.cancelled %} -
- {% if object.meet_at %} -
Crew meet
-
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
- {% endif %} - {% if object.has_start_time %} -
Event starts
-
- {{ object.start_time|date:"H:i" }} - {{ object.start_date|date:"(Y-m-d)" }}
-
- {% endif %} - {% if object.has_end_time%}{% if object.start_date != object.end_date or object.start_time != object.end_time %} -
Event ends
-
- {{ object.end_time|date:"H:i" }} - {{ object.end_date|date:"(Y-m-d)" }} -
- {% endif %}{% endif %} -
+
+ {% if object.mic %} +
MIC
+
{{object.mic.name}}
{% endif %} + {% if object.description %} +
Description
+
{{ object.description|linebreaksbr }}
+ {% endif %} + {% if object.meet_at %} +
Crew meet
+
{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
+ {% endif %} + {% if object.access_at %} +
Access at
+
{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
+ {% endif %} + {% if object.has_start_time %} +
Event starts
+
+ {{ object.start_time|date:"H:i" }} + {{ object.start_date|date:"(Y-m-d)" }}
+
+ {% endif %} + {% if object.has_end_time%}{% if object.start_date != object.end_date or object.start_time != object.end_time %} +
Event ends
+
+ {{ object.end_time|date:"H:i" }} + {{ object.end_date|date:"(Y-m-d)" }} +
+ {% endif %}{% endif %} +
diff --git a/templates/base_embed.html b/templates/base_embed.html index 2dd1dcc7..68053750 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -29,6 +29,7 @@ } .embed_container{ border:5px solid #e9e9e9; + padding-top:12px; height:100%; width:100%; } From 5e9f7e2c6348d88dbd330dc66874baaa3a32e551 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 6 Oct 2016 17:00:45 +0100 Subject: [PATCH 038/275] More prettying --- RIGS/templates/RIGS/event_embed.html | 55 +++++++++++++++++----------- templates/base_embed.html | 2 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index cd0cf360..71b69217 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -18,20 +18,19 @@
diff --git a/templates/base_embed.html b/templates/base_embed.html index 68053750..da3f3480 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -30,7 +30,7 @@ .embed_container{ border:5px solid #e9e9e9; padding-top:12px; - height:100%; + min-height:100%; width:100%; } From 7e379b33db541b1e7d4118e4b915173d0626e18c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 7 Oct 2016 02:24:24 +0100 Subject: [PATCH 039/275] Fixed login autofocus and error messages --- templates/registration/login_embed.html | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 1cfdfa5b..f14a1d28 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -1,4 +1,5 @@ {% extends 'base_embed.html' %} +{% load widget_tweaks %} {% block title %}Login{% endblock %} @@ -6,6 +7,24 @@

Rig Information Gathering System

-{% include 'registration/loginform.html' %} + + +{% include 'form_errors.html' %} +
+ + {% csrf_token %} +
+ + {% render_field form.username class+="form-control" placeholder=form.username.label %} +
+
+ + {% render_field form.password class+="form-control" placeholder=form.password.label %} +
+ + + + +
{% endblock %} \ No newline at end of file From 8a838aa4bd5e051ac34f2645221c1e4364008db5 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 7 Oct 2016 02:51:08 +0100 Subject: [PATCH 040/275] Added decorator for X-Frame header --- PyRIGS/decorators.py | 11 +++++++++++ RIGS/urls.py | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 4af93097..065ce853 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -4,6 +4,17 @@ from django.template import RequestContext from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse +def allow_embed(): + # using django.views.decorators.clickjacking.xframe_options_exempt removes the header + # Safari has differnet defaults to other browsers, so we have to set it explicitly + def headers_wrapper(fun): + def wrapped_function(*args, **kwargs): + response = fun(*args, **kwargs) + response['X-Frame-Options'] = "ALLOW" + return response + return wrapped_function + return headers_wrapper + def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): """ Decorator for views that checks that the user passes the given test. diff --git a/RIGS/urls.py b/RIGS/urls.py index 8767d7b9..1383558e 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -2,10 +2,10 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView -from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required +from PyRIGS.decorators import allow_embed urlpatterns = patterns('', # Examples: @@ -15,7 +15,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), - url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), + url('^user/login/embed/$', allow_embed()(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People @@ -85,7 +85,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + allow_embed()(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From 3f4c362bfab6b165dadc9b308f196ee1750fd9db Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:01:37 +0100 Subject: [PATCH 041/275] Try allow-from header (limited browser support) --- PyRIGS/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 065ce853..42df533c 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -10,7 +10,7 @@ def allow_embed(): def headers_wrapper(fun): def wrapped_function(*args, **kwargs): response = fun(*args, **kwargs) - response['X-Frame-Options'] = "ALLOW" + response['X-Frame-Options'] = "ALLOW-FROM https://forum.nottinghamtec.co.uk/" return response return wrapped_function return headers_wrapper From 3e224a33a7d08daefb46788595e6ed6e7b83bcb3 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:14:29 +0100 Subject: [PATCH 042/275] Try just removing the header, this should work in all browsers --- RIGS/urls.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index 1383558e..ea2681c5 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -2,6 +2,7 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView +from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required @@ -15,7 +16,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), - url('^user/login/embed/$', allow_embed()(views.login_embed), name='login_embed'), + url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People @@ -85,7 +86,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - allow_embed()(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From 536842971d1f1b77d541a5811d06c6e0689fdfb0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:19:18 +0100 Subject: [PATCH 043/275] Revert "Try just removing the header, this should work in all browsers" This reverts commit 3e224a33a7d08daefb46788595e6ed6e7b83bcb3. --- RIGS/urls.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RIGS/urls.py b/RIGS/urls.py index ea2681c5..1383558e 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -2,7 +2,6 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView -from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required @@ -16,7 +15,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), - url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), + url('^user/login/embed/$', allow_embed()(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People @@ -86,7 +85,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + allow_embed()(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From 511ce554b1fe6b87925296cf3b40be81deccd38b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:19:27 +0100 Subject: [PATCH 044/275] Revert "Try allow-from header (limited browser support)" This reverts commit 3f4c362bfab6b165dadc9b308f196ee1750fd9db. --- PyRIGS/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 42df533c..065ce853 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -10,7 +10,7 @@ def allow_embed(): def headers_wrapper(fun): def wrapped_function(*args, **kwargs): response = fun(*args, **kwargs) - response['X-Frame-Options'] = "ALLOW-FROM https://forum.nottinghamtec.co.uk/" + response['X-Frame-Options'] = "ALLOW" return response return wrapped_function return headers_wrapper From 73b8ce4addb864e887b097c050f77524bd85cf35 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:19:35 +0100 Subject: [PATCH 045/275] Revert "Added decorator for X-Frame header" This reverts commit 8a838aa4bd5e051ac34f2645221c1e4364008db5. --- PyRIGS/decorators.py | 11 ----------- RIGS/urls.py | 6 +++--- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 065ce853..4af93097 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -4,17 +4,6 @@ from django.template import RequestContext from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse -def allow_embed(): - # using django.views.decorators.clickjacking.xframe_options_exempt removes the header - # Safari has differnet defaults to other browsers, so we have to set it explicitly - def headers_wrapper(fun): - def wrapped_function(*args, **kwargs): - response = fun(*args, **kwargs) - response['X-Frame-Options'] = "ALLOW" - return response - return wrapped_function - return headers_wrapper - def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): """ Decorator for views that checks that the user passes the given test. diff --git a/RIGS/urls.py b/RIGS/urls.py index 1383558e..8767d7b9 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -2,10 +2,10 @@ from django.conf.urls import patterns, include, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView +from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required -from PyRIGS.decorators import allow_embed urlpatterns = patterns('', # Examples: @@ -15,7 +15,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), - url('^user/login/embed/$', allow_embed()(views.login_embed), name='login_embed'), + url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), # People @@ -85,7 +85,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - allow_embed()(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From ac7e85c24a7d62445d9dd07064c50599d14141ad Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 17:30:23 +0100 Subject: [PATCH 046/275] PEP8 and comments --- PyRIGS/decorators.py | 13 +++++++---- RIGS/urls.py | 23 +++++++++---------- ...edirect_embed.html => login_redirect.html} | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) rename templates/{login_redirect_embed.html => login_redirect.html} (94%) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 4af93097..0b4a72eb 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -4,12 +4,17 @@ from django.template import RequestContext from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse +from RIGS import models + + def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): """ Decorator for views that checks that the user passes the given test. - Anonymous users will be redirected to login_url, while users that fail the test will be given a 403 error. + If embed_view is set, then a JS redirect will be used, and a application/json+oembed + meta tag set with the url of oembed_view + (oembed_view will be passed the kwargs from the main function) """ if not login_url: from django.conf import settings @@ -24,7 +29,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): extra_context = {} extra_context['oembed_url'] = request.scheme + '://' + request.META['HTTP_HOST'] + reverse(oembed_view, kwargs=kwargs) extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) - resp = render_to_response('login_redirect_embed.html', extra_context, context_instance=RequestContext(request)) + resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request)) return resp else: return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) @@ -37,6 +42,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): return _checklogin return _dec + def permission_required_with_403(perm, login_url=None, oembed_view=None): """ Decorator for views that checks whether a user has a particular permission @@ -44,7 +50,6 @@ def permission_required_with_403(perm, login_url=None, oembed_view=None): """ return user_passes_test_with_403(lambda u: u.has_perm(perm), login_url=login_url, oembed_view=oembed_view) -from RIGS import models def api_key_required(function): """ @@ -73,4 +78,4 @@ def api_key_required(function): if user_object.api_key != key: return error_resp return function(request, *args, **kwargs) - return wrap \ No newline at end of file + return wrap diff --git a/RIGS/urls.py b/RIGS/urls.py index 8767d7b9..cbdef421 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import patterns, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms from django.views.generic import RedirectView @@ -16,7 +16,7 @@ urlpatterns = patterns('', url('^user/login/$', 'RIGS.views.login', name='login'), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), - url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form':forms.PasswordReset}), + url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}), # People url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), @@ -73,7 +73,7 @@ urlpatterns = patterns('', url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/calendar/(?P(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/calendar/(?P(month|week|day))/(?P(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), + url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), url(r'^rigboard/activity/$', permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), name='activity_table'), @@ -109,7 +109,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), name='event_history', kwargs={'model': models.Event}), - + # Finance url(r'^invoice/$', @@ -148,10 +148,10 @@ urlpatterns = patterns('', # User editing url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'), url(r'^user/(?P\d+)/$', - permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()), - name='profile_detail'), + permission_required_with_403('RIGS.view_profile')(views.ProfileDetail.as_view()), + name='profile_detail'), url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), - name='profile_update_self'), + name='profile_update_self'), url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), # ICS Calendar - API key authentication @@ -162,8 +162,7 @@ urlpatterns = patterns('', url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), # Legacy URL's - url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True,pattern_name='event_detail')), - url(r'^bookings/$', RedirectView.as_view(permanent=True,pattern_name='rigboard')), - url(r'^bookings/past/$', RedirectView.as_view(permanent=True,pattern_name='event_archive')), -) - + url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')), + url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')), + url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), + ) diff --git a/templates/login_redirect_embed.html b/templates/login_redirect.html similarity index 94% rename from templates/login_redirect_embed.html rename to templates/login_redirect.html index afd3487c..d2dcb0b2 100644 --- a/templates/login_redirect_embed.html +++ b/templates/login_redirect.html @@ -12,7 +12,7 @@ {% if oembed_url %} + title="RIGS Embed" /> {% endif %} {% endblock %} From 008edd8bee0414a1743e9e1a0f85b27a90e395e7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 19:32:45 +0100 Subject: [PATCH 047/275] Lots of tidying up, moved inline CSS into SCSS --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 22 +++ RIGS/templates/RIGS/event_embed.html | 172 +++++++++++------------- templates/base_embed.html | 17 +-- templates/registration/login.html | 11 +- templates/registration/login_embed.html | 4 +- templates/registration/loginform.html | 10 +- 7 files changed, 116 insertions(+), 122 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index d7ed2f52..de7e6c67 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding-top:12px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index be36ebbb..fef12c60 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -147,3 +147,25 @@ ins { }; } } + +html.embedded{ + min-height:100%; + + body{ + min-height:100%; + padding:0; + } + + .embed_container{ + border:5px solid #e9e9e9; + padding-top:12px; + min-height:100%; + width:100%; + } + .source{ + background: url('/static/imgs/pyrigs-avatar.png') no-repeat; + background-size: 16px 16px; + padding-left: 20px; + } + +} \ No newline at end of file diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 71b69217..3eefd00a 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -3,100 +3,87 @@ {% block content %} - - -
-
- Rig Information Gathering System -
- -
- - - {% if object.mic %} -
- {{ object.mic.initials }}
- -
- {% elif object.is_rig %} - +
+ + {% if object.mic %} +
+ {{ object.mic.initials }}
+ +
+ {% elif object.is_rig %} + + {% endif %} +
+ +

+ + {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} + | {{ object.name }} + {% if object.venue %} + at {{ object.venue }} {% endif %} - -

- - - - {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} - | {{ object.name }} - {% if object.venue %} - at {{ object.venue }} - {% endif %} +

+

+ {{ object.start_date|date:"D d/m/Y" }} + {% if object.end_date and object.end_date != object.start_date %} + – {{ object.end_date|date:"D d/m/Y" }} + {% endif %} + ({{ object.get_status_display }}) +

- - - -

- {{ object.start_date|date:"D d/m/Y" }} - {% if object.end_date and object.end_date != object.start_date %} - – {{ object.end_date|date:"D d/m/Y" }} - {% endif %} - ({{ object.get_status_display }}) -

- - {% if object.is_rig %} -
- {{ object.person.name }} - {% if object.organisation %} - for {{ object.organisation.name }} - {% endif %} - {% if object.dry_hire %}Dry Hire{% endif %} -
+ {% if object.is_rig %} +
+ {{ object.person.name }} + {% if object.organisation %} + for {{ object.organisation.name }} {% endif %} + {% if object.dry_hire %}Dry Hire{% endif %} +
+ {% endif %} - - - {% if object.mic %} - - - - - {% endif %} - {% if object.description %} - - - - - {% endif %} - {% if object.meet_at %} - - - - - {% endif %} - {% if object.access_at %} - - - - - {% endif %} - {% if object.has_start_time %} - - - - - {% endif %} - {% if object.has_end_time%}{% if object.start_date != object.end_date or object.start_time != object.end_time %} + +
MIC{{object.mic.name}}
Description{{ object.description|linebreaksbr }}
Crew meet{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
Access at{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
Event starts - {{ object.start_time|date:"H:i" }} - {{ object.start_date|date:"(Y-m-d)" }}
-
+ {% if object.mic %} + + + + + {% endif %} + {% if object.description %} + + + + + {% endif %} + {% if object.meet_at %} + + + + + {% endif %} + {% if object.access_at %} + + + + + {% endif %} + {% if object.has_start_time %} + + + + + {% endif %} + {% if object.has_end_time%} + {% if object.start_date != object.end_date or object.start_time != object.end_time %} - {% endif %}{% endif %} -
MIC{{object.mic.name}}
Description{{ object.description|linebreaksbr }}
Crew meet{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
Access at{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
Event starts + {{ object.start_time|date:"H:i" }} + {{ object.start_date|date:"(Y-m-d)" }}
+
Event ends @@ -104,10 +91,11 @@ {{ object.end_date|date:"(Y-m-d)" }}
-
-
+ {% endif %} + {% endif %} +
+ + {% endblock %} \ No newline at end of file diff --git a/templates/base_embed.html b/templates/base_embed.html index da3f3480..fe987b90 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -1,12 +1,12 @@ {% load static from staticfiles %} {% load raven %} - + lang="{% firstof LANGUAGE_CODE 'en' %}" + class="embedded"> @@ -14,7 +14,6 @@ - - diff --git a/templates/registration/login.html b/templates/registration/login.html index f9db2e81..5dd85fe7 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -2,14 +2,9 @@ {% block title %}Login{% endblock %} -{% block extra-head %} - {% if next %} - - {% endif %} -{% endblock %} - {% block content %} +
+

Rig Information Gathering System

+
{% include 'registration/loginform.html' %} {% endblock %} \ No newline at end of file diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index f14a1d28..32fbb8dd 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -4,7 +4,7 @@ {% block title %}Login{% endblock %} {% block content %} -
+

Rig Information Gathering System

@@ -22,7 +22,7 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}
- + diff --git a/templates/registration/loginform.html b/templates/registration/loginform.html index 4ec09d2c..94343065 100644 --- a/templates/registration/loginform.html +++ b/templates/registration/loginform.html @@ -12,9 +12,11 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %} - Register - Forgotten Password - - + \ No newline at end of file From fc110a0bff0fae58ec058316bae974102ebf64f8 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 19:55:31 +0100 Subject: [PATCH 048/275] Fixed padding --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index de7e6c67..884979f0 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding-top:12px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index fef12c60..93a835d0 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -158,7 +158,7 @@ html.embedded{ .embed_container{ border:5px solid #e9e9e9; - padding-top:12px; + padding:12px 0px; min-height:100%; width:100%; } From 0d92c3812ac2fe34343ad9663e028f4e8ba1b472 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 19:56:56 +0100 Subject: [PATCH 049/275] Tidied up python --- RIGS/rigboard.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 91265871..9f6747d6 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -48,26 +48,30 @@ class WebCalendar(generic.TemplateView): class EventDetail(generic.DetailView): model = models.Event + class EventOembed(generic.View): model = models.Event def get(self, request, pk=None): - base_url = request.scheme + '://' + request.META['HTTP_HOST'] - full_url = base_url+reverse('event_embed', args=[pk]) + embed_url = reverse('event_embed', args=[pk]) + + full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', } - # need to do this: @xframe_options_exempt + json = simplejson.JSONEncoderForHTML().encode(data) return HttpResponse(json, content_type="application/json") + class EventEmbed(EventDetail): template_name = 'RIGS/event_embed.html' + class EventCreate(generic.CreateView): model = models.Event form_class = forms.EventForm From b93a716a3bf43dc3a24848d46823358a481c564a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 20:37:01 +0100 Subject: [PATCH 050/275] Added unit tests --- RIGS/rigboard.py | 5 ++- RIGS/test_unit.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 9f6747d6..618f6bea 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -54,8 +54,7 @@ class EventOembed(generic.View): def get(self, request, pk=None): - embed_url = reverse('event_embed', args=[pk]) - + embed_url = reverse('event_embed', args=[pk]) full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { @@ -84,7 +83,7 @@ class EventCreate(generic.CreateView): form = context['form'] if re.search('"-\d+"', form['items_json'].value()): messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.") - + # Get some other objects to include in the form. Used when there are errors but also nice and quick. for field, model in form.related_models.iteritems(): diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index f0c778b3..47d01bae 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -213,6 +213,83 @@ class TestInvoiceDelete(TestCase): # Check this didn't work self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) + +class TestEmbeddedViews(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today()), + 2: models.Event.objects.create(name="TE E2", start_date=date.today()) + } + + cls.invoices = { + 1: models.Invoice.objects.create(event=cls.events[1]), + 2: models.Invoice.objects.create(event=cls.events[2]) + } + + cls.payments = { + 1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34, method=models.Payment.CASH) + } + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + + def testLoginRedirect(self): + request_url = reverse('event_embed', kwargs={'pk': 1}) + expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url) + + # Request the page and check it redirects + response = self.client.get(request_url, follow=True) + self.assertRedirects(response, expected_url, status_code=302, target_status_code=200) + + # Now login + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + # And check that it no longer redirects + response = self.client.get(request_url, follow=True) + self.assertEqual(len(response.redirect_chain), 0) + + def testXFrameHeaders(self): + event_url = reverse('event_embed', kwargs={'pk': 1}) + login_url = reverse('login_embed') + + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + response = self.client.get(event_url, follow=True) + with self.assertRaises(KeyError): + response._headers["X-Frame-Options"] + + response = self.client.get(login_url, follow=True) + with self.assertRaises(KeyError): + response._headers["X-Frame-Options"] + + def testOEmbed(self): + event_url = reverse('event_detail', kwargs={'pk': 1}) + event_embed_url = reverse('event_embed', kwargs={'pk': 1}) + oembed_url = reverse('event_oembed', kwargs={'pk': 1}) + + alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999}) + alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999}) + + # Test the meta tag is in place + response = self.client.get(event_url, follow=True, HTTP_HOST='example.com') + self.assertContains(response, ' Date: Sat, 8 Oct 2016 21:38:12 +0100 Subject: [PATCH 051/275] Made pretty, and made embedding accessible to non-keyholders --- PyRIGS/decorators.py | 2 +- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 11 +++ RIGS/templates/RIGS/event_embed.html | 119 ++++++++++++++------------- RIGS/urls.py | 2 +- 5 files changed, 75 insertions(+), 61 deletions(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 0b4a72eb..055901ca 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -27,7 +27,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): elif not request.user.is_authenticated(): if oembed_view is not None: extra_context = {} - extra_context['oembed_url'] = request.scheme + '://' + request.META['HTTP_HOST'] + reverse(oembed_view, kwargs=kwargs) + extra_context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request)) return resp diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 884979f0..10b589b7 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:10px}html.embedded p{margin:2px 0}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 93a835d0..bd3a0ffa 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -168,4 +168,15 @@ html.embedded{ padding-left: 20px; } + h3{ + margin-top:10px; + margin-bottom:10px; + } + p{ + margin:2px 0; + } + + .event-mic-photo{ + max-width: 3em; + } } \ No newline at end of file diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 3eefd00a..b03431ee 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -30,69 +30,72 @@

- {{ object.start_date|date:"D d/m/Y" }} - {% if object.end_date and object.end_date != object.start_date %} - – {{ object.end_date|date:"D d/m/Y" }} + {{ object.start_date|date:"D d/m/Y" }} + {% if object.has_start_time %} + {{ object.start_time|date:"H:i" }} + {% endif %} + {% if object.end_date or object.has_end_time %} + – + {% endif %} + {% if object.end_date and object.end_date != object.start_date %} + {{ object.end_date|date:"D d/m/Y" }} + {% endif %} + {% if object.has_end_time %} + {{ object.end_time|date:"H:i" }} {% endif %} - ({{ object.get_status_display }})

- {% if object.is_rig %} -
- {{ object.person.name }} - {% if object.organisation %} - for {{ object.organisation.name }} +
+
+

+ Status: + {{ object.get_status_display }} +

+

+ {% if object.is_rig %} + Client: {{ object.person.name }} + {% if object.organisation %} + for {{ object.organisation.name }} + {% endif %} + {% if object.dry_hire %}Dry Hire{% endif %} + {% else %} + Non-Rig + {% endif %} +

+

+ MIC: + {% if object.mic %} + {{object.mic.name}} + {% else %} + None + {% endif %} +

+
+
+ + {% if object.meet_at %} +

+ Crew meet: + {{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }} +

{% endif %} - {% if object.dry_hire %}Dry Hire{% endif %} -
+ {% if object.access_at %} +

+ Access at: + {{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }} +

+ {% endif %} +

+ Last updated: + {{ object.last_edited_at }} by "{{ object.last_edited_by.initials }}" +

+ + + {% if object.description %} + Description: + {{ object.description|linebreaksbr }} {% endif %} - - - - {% if object.mic %} - - - - - {% endif %} - {% if object.description %} - - - - - {% endif %} - {% if object.meet_at %} - - - - - {% endif %} - {% if object.access_at %} - - - - - {% endif %} - {% if object.has_start_time %} - - - - - {% endif %} - {% if object.has_end_time%} - {% if object.start_date != object.end_date or object.start_time != object.end_time %} - - - - - {% endif %} - {% endif %} +
MIC{{object.mic.name}}
Description{{ object.description|linebreaksbr }}
Crew meet{{ object.meet_at|date:"H:i" }} {{ object.meet_at|date:"(Y-m-d)" }}
Access at{{ object.access_at|date:"H:i" }} {{ object.access_at|date:"(Y-m-d)" }}
Event starts - {{ object.start_time|date:"H:i" }} - {{ object.start_date|date:"(Y-m-d)" }}
-
Event ends - {{ object.end_time|date:"H:i" }} - {{ object.end_date|date:"(Y-m-d)" }} -
diff --git a/RIGS/urls.py b/RIGS/urls.py index cbdef421..15bb0990 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -85,7 +85,7 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - xframe_options_exempt(permission_required_with_403('RIGS.view_event', login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), From 879ecd1f6d2ef579182d9a29e90e21305f29d44a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 21:49:03 +0100 Subject: [PATCH 052/275] Made font size smaller in embed --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 7 +++++-- RIGS/templates/RIGS/event_embed.html | 16 ++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 10b589b7..523fd217 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:10px}html.embedded p{margin:2px 0}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index bd3a0ffa..464b346e 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -162,6 +162,7 @@ html.embedded{ min-height:100%; width:100%; } + .source{ background: url('/static/imgs/pyrigs-avatar.png') no-repeat; background-size: 16px 16px; @@ -170,10 +171,12 @@ html.embedded{ h3{ margin-top:10px; - margin-bottom:10px; + margin-bottom:5px; } + p{ - margin:2px 0; + margin-bottom:2px; + font-size: 11px; } .event-mic-photo{ diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index b03431ee..bef2e096 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -12,7 +12,6 @@ {% if object.mic %}
- {{ object.mic.initials }}
{% elif object.is_rig %} @@ -27,9 +26,7 @@ {% if object.venue %} at {{ object.venue }} {% endif %} - - -

+
{{ object.start_date|date:"D d/m/Y" }} {% if object.has_start_time %} {{ object.start_time|date:"H:i" }} @@ -43,10 +40,11 @@ {% if object.has_end_time %} {{ object.end_time|date:"H:i" }} {% endif %} -

+ +
-
+

Status: {{ object.get_status_display }} @@ -57,7 +55,7 @@ {% if object.organisation %} for {{ object.organisation.name }} {% endif %} - {% if object.dry_hire %}Dry Hire{% endif %} + {% if object.dry_hire %}(Dry Hire){% endif %} {% else %} Non-Rig {% endif %} @@ -71,7 +69,7 @@ {% endif %}

-
+
{% if object.meet_at %}

@@ -92,8 +90,10 @@

{% if object.description %} +

Description: {{ object.description|linebreaksbr }} +

{% endif %} From 5949ff74ecce0c92289a062e772f6b8f1a2d5be0 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 8 Oct 2016 22:55:27 +0100 Subject: [PATCH 053/275] Added javascript cookie check, if blocked, login in new tab --- RIGS/rigboard.py | 2 +- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 11 +++++++++-- templates/base_embed.html | 2 +- templates/registration/login_embed.html | 25 ++++++++++++++++++++++--- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 618f6bea..fc14a89e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -58,7 +58,7 @@ class EventOembed(generic.View): full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { - 'html': ''.format(full_url), + 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', } diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 523fd217..04123b55 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%}html.embedded body{min-height:100%;padding:0}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;background:#e9e9e9;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;background:none;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%;background:#fff}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 464b346e..e31bb58e 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -150,10 +150,16 @@ ins { html.embedded{ min-height:100%; + display: table; + background: #e9e9e9; + width: 100%; body{ - min-height:100%; padding:0; + display: table-cell; + vertical-align: middle; + background: none; + width:100%; } .embed_container{ @@ -161,8 +167,9 @@ html.embedded{ padding:12px 0px; min-height:100%; width:100%; + background: #fff; } - + .source{ background: url('/static/imgs/pyrigs-avatar.png') no-repeat; background-size: 16px 16px; diff --git a/templates/base_embed.html b/templates/base_embed.html index fe987b90..24259ee5 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -26,7 +26,7 @@ {% include "analytics.html" %} -
+
{% block content %} {% endblock %} diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 32fbb8dd..7f50a7bc 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -3,6 +3,18 @@ {% block title %}Login{% endblock %} +{% block js %} + +{% endblock %} + {% block content %}

Rig Information Gathering System

@@ -10,9 +22,11 @@ {% include 'form_errors.html' %} + +
-
{% csrf_token %} + {% csrf_token %}
{% render_field form.username class+="form-control" placeholder=form.username.label %} @@ -21,9 +35,14 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}
- - +
+ + +
+
From 2d5f76852334be318242477d69ff5ea9d519abaf Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:32:58 +0100 Subject: [PATCH 054/275] Added cookie check with nice error message --- RIGS/views.py | 22 ++++++++++++++++++++-- templates/base_embed.html | 10 ++++++++++ templates/registration/login_embed.html | 15 --------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/RIGS/views.py b/RIGS/views.py index fded9865..c0186bed 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -12,6 +12,8 @@ from django.contrib import messages import datetime, pytz import operator from registration.views import RegistrationView +from django.views.decorators.csrf import csrf_exempt + from RIGS import models, forms @@ -29,21 +31,37 @@ class Index(generic.TemplateView): def login(request, **kwargs): if request.user.is_authenticated(): next = request.REQUEST.get('next', '/') - return HttpResponseRedirect(request.REQUEST.get('next', '/')) + return HttpResponseRedirect(next) else: from django.contrib.auth.views import login return login(request) + +# This view should be exempt from requiring CSRF token. +# Then we can check for it and show a nice error +# Don't worry, django.contrib.auth.views.login will +# check for it before logging the user in +@csrf_exempt def login_embed(request, **kwargs): + print("Running LOGIN") if request.user.is_authenticated(): next = request.REQUEST.get('next', '/') - return HttpResponseRedirect(request.REQUEST.get('next', '/')) + return HttpResponseRedirect(next) else: from django.contrib.auth.views import login + if request.method == "POST": + csrf_cookie = request.COOKIES.get('csrftoken', None) + + if csrf_cookie is None: + messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') + request.method = 'GET' # Render the page without trying to login + return login(request, template_name="registration/login_embed.html") + + """ Called from a modal window (e.g. when an item is submitted to an event/invoice). May optionally also include some javascript in a success message to cause a load of diff --git a/templates/base_embed.html b/templates/base_embed.html index 24259ee5..bc7daa1a 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -28,6 +28,16 @@
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% block content %} {% endblock %}
diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 7f50a7bc..64ef8437 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -3,18 +3,6 @@ {% block title %}Login{% endblock %} -{% block js %} - -{% endblock %} - {% block content %}

Rig Information Gathering System

@@ -36,9 +24,6 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}
-
From 3fc04616b3c0f36977e9375b8d8fbbd1bc981bb7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:36:30 +0100 Subject: [PATCH 055/275] Added test for cookie warning --- RIGS/test_unit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 47d01bae..82a7acca 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -252,6 +252,11 @@ class TestEmbeddedViews(TestCase): response = self.client.get(request_url, follow=True) self.assertEqual(len(response.redirect_chain), 0) + def testLoginCookieWarning(self): + login_url = reverse('login_embed') + response = self.client.post(login_url, follow=True) + self.assertContains(response, "Cookies do not seem to be enabled") + def testXFrameHeaders(self): event_url = reverse('event_embed', kwargs={'pk': 1}) login_url = reverse('login_embed') From 88954eca5c4bbc884c4c2bf95c505b0d721a1718 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 10:40:18 +0100 Subject: [PATCH 056/275] Removed weird background from embed --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 04123b55..682c3e08 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;background:#e9e9e9;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;background:none;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%;background:#fff}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index e31bb58e..be753970 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -151,14 +151,12 @@ ins { html.embedded{ min-height:100%; display: table; - background: #e9e9e9; width: 100%; body{ padding:0; display: table-cell; vertical-align: middle; - background: none; width:100%; } @@ -167,7 +165,6 @@ html.embedded{ padding:12px 0px; min-height:100%; width:100%; - background: #fff; } .source{ From f61158b9c07c0d32bd0faad390a94f2c40555934 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:20:43 +0100 Subject: [PATCH 057/275] Rounded corners, transparent background --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 1 + RIGS/templates/RIGS/event_embed.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 682c3e08..3e1d4545 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index be753970..b0a677ac 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -158,6 +158,7 @@ html.embedded{ display: table-cell; vertical-align: middle; width:100%; + background:none; } .embed_container{ diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index bef2e096..e908e9fd 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -11,7 +11,7 @@
{% if object.mic %} -
+
{% elif object.is_rig %} From 68a46af1a86cc34d54f802542d46f39269216bce Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:22:34 +0100 Subject: [PATCH 058/275] Fixed rounded corner fail --- RIGS/templates/RIGS/event_embed.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index e908e9fd..88d7a95d 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -11,8 +11,8 @@
{% if object.mic %} -
- +
+
{% elif object.is_rig %} From e0cb2f4925b8554fd39f2617f616df290edc2704 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:26:32 +0100 Subject: [PATCH 059/275] Linked RIGS title --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 1 + RIGS/templates/RIGS/event_embed.html | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 3e1d4545..41bd5f39 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -16,4 +16,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index b0a677ac..074b23cc 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -172,6 +172,7 @@ html.embedded{ background: url('/static/imgs/pyrigs-avatar.png') no-repeat; background-size: 16px 16px; padding-left: 20px; + color: #000; } h3{ diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index 88d7a95d..c689fee7 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -5,7 +5,9 @@
- Rig Information Gathering System + + Rig Information Gathering System +
From 0541a70cecda45ceb312e0610c340d1a0b9fddb4 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 9 Oct 2016 11:30:13 +0100 Subject: [PATCH 060/275] Fixed event title link (_blank) --- RIGS/templates/RIGS/event_embed.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index c689fee7..f7c13b93 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -22,7 +22,7 @@

- + {% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{ object.name }} {% if object.venue %} From 92c77c07e0b084e65528b77d8b5a0b748d23a9b3 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Oct 2016 18:47:13 +0100 Subject: [PATCH 061/275] Fix tailing line breaks --- RIGS/static/scss/screen.scss | 2 +- RIGS/templates/RIGS/event_embed.html | 2 +- templates/base_embed.html | 2 +- templates/registration/login_embed.html | 2 +- templates/registration/loginform.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 074b23cc..953027a9 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -188,4 +188,4 @@ html.embedded{ .event-mic-photo{ max-width: 3em; } -} \ No newline at end of file +} diff --git a/RIGS/templates/RIGS/event_embed.html b/RIGS/templates/RIGS/event_embed.html index f7c13b93..a6e3e586 100644 --- a/RIGS/templates/RIGS/event_embed.html +++ b/RIGS/templates/RIGS/event_embed.html @@ -103,4 +103,4 @@

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/base_embed.html b/templates/base_embed.html index bc7daa1a..6a7a8741 100644 --- a/templates/base_embed.html +++ b/templates/base_embed.html @@ -46,4 +46,4 @@ {% block js %} {% endblock %} - \ No newline at end of file + diff --git a/templates/registration/login_embed.html b/templates/registration/login_embed.html index 64ef8437..bddb7333 100644 --- a/templates/registration/login_embed.html +++ b/templates/registration/login_embed.html @@ -31,4 +31,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/registration/loginform.html b/templates/registration/loginform.html index 94343065..ad7e33d3 100644 --- a/templates/registration/loginform.html +++ b/templates/registration/loginform.html @@ -19,4 +19,4 @@
-
\ No newline at end of file +
From 97decf8c52fab5be92e963e41a96cb9edd84af5c Mon Sep 17 00:00:00 2001 From: Sam Osborne Date: Wed, 19 Oct 2016 00:54:42 +0100 Subject: [PATCH 062/275] Add link to pre-event RA #accessible --- RIGS/templates/RIGS/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index d67f82c9..ad224883 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -25,6 +25,7 @@ TEC Forum TEC Wiki + Pre-Event Risk Assessment Price List Subhire Insurance Form From 6fc89727f2ba1b89c67df3cde3fc05a8332a14ba Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Fri, 21 Oct 2016 15:45:29 +0100 Subject: [PATCH 063/275] Stop PO number from duplicating when copying event Would close #256 --- RIGS/rigboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index fc14a89e..2e2869ac 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -54,7 +54,7 @@ class EventOembed(generic.View): def get(self, request, pk=None): - embed_url = reverse('event_embed', args=[pk]) + embed_url = reverse('event_embed', args=[pk]) full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { @@ -121,6 +121,7 @@ class EventDuplicate(EventUpdate): old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) new = copy.copy(old) # Make a copy of the object in memory new.based_on = old # Make the new event based on the old event + new.purchase_order = None if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) new.pk = None # This means a new event will be created on save, and all items will be re-created @@ -216,4 +217,4 @@ class EventArchive(generic.ArchiveIndexView): if len(qs) == 0: messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.") - return qs \ No newline at end of file + return qs From 3ce191aaf20d0a3f0abcc6ab81bcd1eb58ab0941 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Sun, 23 Oct 2016 12:26:35 +0100 Subject: [PATCH 064/275] Update README.md Small adjustment to make GitHub markdown display the table. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 51bfec16..787502ef 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Then load the sample data using the command: python manage.py generateSampleData ``` 4 user accounts are created for convenience: + |Username |Password | |---------|---------| |superuser|superuser| From 90c8b199159375a76580844e6eff8fbd868e9068 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 23 Oct 2016 12:45:27 +0100 Subject: [PATCH 065/275] Added tests for PO non-duplication --- RIGS/test_functional.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 7e5010f7..673dadd1 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -438,7 +438,7 @@ class EventTest(LiveServerTestCase): pass def testEventDuplicate(self): - testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end") + testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end", purchase_order="TESTPO") item1 = models.EventItem( event=testEvent, @@ -498,6 +498,8 @@ class EventTest(LiveServerTestCase): infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO hasn't carried through + self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) @@ -506,6 +508,8 @@ class EventTest(LiveServerTestCase): #Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO remains on the old event + self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above From b939bc5a64a20dc2266586548a47a5b33d111891 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 23 Oct 2016 14:05:25 +0100 Subject: [PATCH 066/275] Do not display "not saved" message when the event has been saved --- RIGS/rigboard.py | 4 ++-- RIGS/test_functional.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 2e2869ac..f85c72fe 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -125,8 +125,8 @@ class EventDuplicate(EventUpdate): if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) new.pk = None # This means a new event will be created on save, and all items will be re-created - - messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') + else: + messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') return new diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 673dadd1..f6c60865 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -471,6 +471,9 @@ class EventTest(LiveServerTestCase): self.assertIn("Test Item 1", table.text) self.assertIn("Test Item 2", table.text) + # Check the info message is visible + self.assertIn("Event data duplicated but not yet saved",self.browser.find_element_by_id('content').text) + # Add item form.find_element_by_xpath('//button[contains(@class, "item-add")]').click() wait.until(animation_is_finished()) @@ -489,6 +492,7 @@ class EventTest(LiveServerTestCase): save.click() self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) + self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible # Check the new items are visible table = self.browser.find_element_by_id('item-table') # ID number is known, see above From caa55fe89a512591a055d484d459c9d892bb97ff Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 4 Nov 2016 13:31:06 +0000 Subject: [PATCH 067/275] Restore pagination to invoice waiting (requested by Emma) --- RIGS/finance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 90de18cd..1e0c91a7 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -122,7 +122,7 @@ class InvoiceArchive(generic.ListView): class InvoiceWaiting(generic.ListView): model = models.Event - # paginate_by = 25 + paginate_by = 25 template_name = 'RIGS/event_invoice.html' def get_context_data(self, **kwargs): From 82aa2785ea5b28926748030b9323e9f42343d5a1 Mon Sep 17 00:00:00 2001 From: Sam Osborne Date: Mon, 6 Feb 2017 15:35:29 +0000 Subject: [PATCH 068/275] Make forum go to actual forum Currently goes to old forum :( --- RIGS/templates/RIGS/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index ad224883..99355dc5 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -23,7 +23,7 @@
- TEC Forum + TEC Forum TEC Wiki Pre-Event Risk Assessment Price List From 0ae7bcaf7cafb08c157533060800143e24cebbad Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 10 Mar 2017 15:26:00 +0000 Subject: [PATCH 069/275] Explicitly define height in oembed JSON --- RIGS/rigboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index f85c72fe..81cf564e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -61,6 +61,7 @@ class EventOembed(generic.View): 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', + 'height': '250' } json = simplejson.JSONEncoderForHTML().encode(data) From 9b7c84cf0890788a08a3dec71e00cbe78748b1fb Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 28 Mar 2017 16:43:15 +0100 Subject: [PATCH 070/275] Change invoice filename format Change the invoice filename format to include the event number. Closes #279 --- RIGS/finance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 1e0c91a7..26ca0df8 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -78,7 +78,7 @@ class InvoicePrint(generic.View): escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', object.name) response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = "filename=Invoice %05d | %s.pdf" % (invoice.pk, escapedEventName) + response['Content-Disposition'] = "filename=Invoice %05d - N%05d | %s.pdf" % (invoice.pk, invoice.event.pk, escapedEventName) response.write(pdfData) return response From c2787d54b05b33c89f339f675a5f4f75e4af295f Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 29 Mar 2017 20:35:25 +0100 Subject: [PATCH 071/275] Add authorisation models. Add EventAuthorisation model + migrations Add authorised property to Event. Add appropriate tests --- RIGS/migrations/0025_eventauthorisation.py | 28 ++++++++++++ RIGS/models.py | 28 ++++++++++++ RIGS/test_models.py | 51 ++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 RIGS/migrations/0025_eventauthorisation.py diff --git a/RIGS/migrations/0025_eventauthorisation.py b/RIGS/migrations/0025_eventauthorisation.py new file mode 100644 index 00000000..88f38ff4 --- /dev/null +++ b/RIGS/migrations/0025_eventauthorisation.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0024_auto_20160229_2042'), + ] + + operations = [ + migrations.CreateModel( + name='EventAuthorisation', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('email', models.EmailField(max_length=254)), + ('name', models.CharField(max_length=255)), + ('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)), + ('account_code', models.CharField(max_length=50, null=True, blank=True)), + ('po', models.CharField(max_length=255, null=True, verbose_name=b'purchase order', blank=True)), + ('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event')), + ], + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 0650d81c..350f406a 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -385,6 +385,10 @@ class Event(models.Model, RevisionMixin): def confirmed(self): return (self.status == self.BOOKED or self.status == self.CONFIRMED) + @property + def authorised(self): + return self.authroisations.latest('created_at').amount >= self.total + @property def has_start_time(self): return self.start_time is not None @@ -501,6 +505,30 @@ class EventCrew(models.Model): notes = models.TextField(blank=True, null=True) +class EventAuthorisation(models.Model): + event = models.ForeignKey('Event', related_name='authroisations') + email = models.EmailField() + name = models.CharField(max_length=255) + uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") + account_code = models.CharField(max_length=50, blank=True, null=True) + po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order") + amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") + created_at = models.DateTimeField(auto_now_add=True) + + def clean(self): + if self.amount != self.event.total: + raise ValidationError("The amount authorised must equal the total for the event") + if self.event.organisation and self.event.organisation.union_account: + # Is a union account, requires username and account number + if self.uni_id is None or self.uni_id == "" or self.account_code is None or self.account_code == "": + raise ValidationError("Internal clients require a University ID number and an account code") + else: + # Is an external client, only requires PO + if self.po is None or self.po == "": + raise ValidationError("External clients require a Purchase Order number") + return super(EventAuthorisation, self).clean() + + @python_2_unicode_compatible class Invoice(models.Model): event = models.OneToOneField('Event') diff --git a/RIGS/test_models.py b/RIGS/test_models.py index a7896cbe..0cbe58f1 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -1,5 +1,6 @@ import pytz from django.conf import settings +from django.core.exceptions import ValidationError from django.test import TestCase from RIGS import models from datetime import date, timedelta, datetime, time @@ -327,3 +328,53 @@ class EventPricingTestCase(TestCase): def test_grand_total(self): self.assertEqual(self.e1.total, Decimal('84.48')) self.assertEqual(self.e2.total, Decimal('419.32')) + + +class EventAuthorisationTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.person = models.Person.objects.create(name='Authorisation Test Person') + cls.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation') + cls.event = models.Event.objects.create(name="AuthorisationTestCase", person=cls.person, + start_date=date.today()) + # Add some items + models.EventItem.objects.create(event=cls.event, name="Authorisation test item", quantity=2, cost=123.45, + order=1) + + def test_validation(self): + auth = models.EventAuthorisation(event=self.event, email="authroisation@model.test.case", name="Test Auth") + + auth.amount = self.event.total - 1 + self.assertRaises(ValidationError, auth.clean) + auth.amount = self.event.total + + # Test for externals first + self.assertRaises(ValidationError, auth.clean) + self.event.organisation = self.organisation + self.assertRaises(ValidationError, auth.clean) + auth.po = "TEST123" + self.assertIsNone(auth.clean()) + + auth.po = None + self.organisation.union_account = True + self.assertRaises(ValidationError, auth.clean) + auth.uni_id = "1234567" + self.assertRaises(ValidationError, auth.clean) + auth.account_code = "TST AUTH 12345" + self.assertIsNone(auth.clean()) + + def test_event_property(self): + auth1 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", + name="Test Auth 1", amount=self.event.total - 1) + self.assertFalse(self.event.authorised) + auth1.amount = self.event.total + auth1.save() + self.assertTrue(self.event.authorised) + + auth2 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", + name="Test Auth 2", amount=self.event.total - 1) + self.assertEqual(auth2.pk, self.event.authroisations.latest('created_at').pk) + self.assertFalse(self.event.authorised) + auth2.amount = self.event.total + 1 + auth2.save() + self.assertTrue(self.event.authorised) From e65e97b1a36e7b6311ceb3828ead79c70e72f48c Mon Sep 17 00:00:00 2001 From: Tom Price Date: Thu, 6 Apr 2017 22:26:05 +0100 Subject: [PATCH 072/275] Client facing authorisation procedures. Add forms, views, templates and URLs. Remove created at in favour of the built in versioning as that's much more accurate. Switch to a OneToOneField with EventAuthorisation -> event as a result of this. Move validation from models to forms where it probably belongs. Provide more descriptive errors. Add success page for authorisation. --- RIGS/forms.py | 34 +++++ ...26_remove_eventauthorisation_created_at.py | 18 +++ .../0027_eventauthorisation_event_singular.py | 19 +++ RIGS/models.py | 19 +-- RIGS/rigboard.py | 96 ++++++++++++-- RIGS/templates/RIGS/client_eventdetails.html | 78 ++++++++++++ .../RIGS/eventauthorisation_form.html | 118 ++++++++++++++++++ .../RIGS/eventauthorisation_success.html | 69 ++++++++++ RIGS/urls.py | 36 ++++-- templates/base_client.html | 109 ++++++++++++++++ 10 files changed, 555 insertions(+), 41 deletions(-) create mode 100644 RIGS/migrations/0026_remove_eventauthorisation_created_at.py create mode 100644 RIGS/migrations/0027_eventauthorisation_event_singular.py create mode 100644 RIGS/templates/RIGS/client_eventdetails.html create mode 100644 RIGS/templates/RIGS/eventauthorisation_form.html create mode 100644 RIGS/templates/RIGS/eventauthorisation_success.html create mode 100644 templates/base_client.html diff --git a/RIGS/forms.py b/RIGS/forms.py index e1e95012..c19ac5b8 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -141,3 +141,37 @@ class EventForm(forms.ModelForm): 'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic', 'person', 'organisation', 'dry_hire', 'checked_in_by', 'status', 'collector', 'purchase_order'] + + +class BaseClientEventAuthorisationForm(forms.ModelForm): + tos = forms.BooleanField(required=True, label="Terms of hire") + name = forms.CharField(label="Your Name") + + def clean(self): + if self.cleaned_data.get('amount') != self.instance.event.total: + self.add_error('amount', 'The amount authorised must equal the total for the event.') + return super(BaseClientEventAuthorisationForm, self).clean() + + class Meta: + abstract = True + + +class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): + def __init__(self, **kwargs): + super(InternalClientEventAuthorisationForm, self).__init__(**kwargs) + self.fields['uni_id'].required = True + self.fields['account_code'].required = True + + class Meta: + model = models.EventAuthorisation + fields = ('tos', 'name', 'amount', 'uni_id', 'account_code') + + +class ExternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): + def __init__(self, **kwargs): + super(ExternalClientEventAuthorisationForm, self).__init__(**kwargs) + self.fields['po'].required = True + + class Meta: + model = models.EventAuthorisation + fields = ('tos', 'name', 'amount', 'po') diff --git a/RIGS/migrations/0026_remove_eventauthorisation_created_at.py b/RIGS/migrations/0026_remove_eventauthorisation_created_at.py new file mode 100644 index 00000000..c5ddc143 --- /dev/null +++ b/RIGS/migrations/0026_remove_eventauthorisation_created_at.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0025_eventauthorisation'), + ] + + operations = [ + migrations.RemoveField( + model_name='eventauthorisation', + name='created_at', + ), + ] diff --git a/RIGS/migrations/0027_eventauthorisation_event_singular.py b/RIGS/migrations/0027_eventauthorisation_event_singular.py new file mode 100644 index 00000000..d7796895 --- /dev/null +++ b/RIGS/migrations/0027_eventauthorisation_event_singular.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0026_remove_eventauthorisation_created_at'), + ] + + operations = [ + migrations.AlterField( + model_name='eventauthorisation', + name='event', + field=models.OneToOneField(related_name='authorisation', to='RIGS.Event'), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 350f406a..37225697 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -387,7 +387,7 @@ class Event(models.Model, RevisionMixin): @property def authorised(self): - return self.authroisations.latest('created_at').amount >= self.total + return self.authorisation.amount == self.total @property def has_start_time(self): @@ -505,28 +505,15 @@ class EventCrew(models.Model): notes = models.TextField(blank=True, null=True) +@reversion.register class EventAuthorisation(models.Model): - event = models.ForeignKey('Event', related_name='authroisations') + event = models.OneToOneField('Event', related_name='authorisation') email = models.EmailField() name = models.CharField(max_length=255) uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") account_code = models.CharField(max_length=50, blank=True, null=True) po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order") amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") - created_at = models.DateTimeField(auto_now_add=True) - - def clean(self): - if self.amount != self.event.total: - raise ValidationError("The amount authorised must equal the total for the event") - if self.event.organisation and self.event.organisation.union_account: - # Is a union account, requires username and account number - if self.uni_id is None or self.uni_id == "" or self.account_code is None or self.account_code == "": - raise ValidationError("Internal clients require a University ID number and an account code") - else: - # Is an external client, only requires PO - if self.po is None or self.po == "": - raise ValidationError("External clients require a Purchase Order number") - return super(EventAuthorisation, self).clean() @python_2_unicode_compatible diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 81cf564e..d602d66b 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -10,7 +10,9 @@ from django.template import RequestContext from django.template.loader import get_template from django.conf import settings from django.core.urlresolvers import reverse +from django.core import signing from django.http import HttpResponse +from django.core.exceptions import SuspiciousOperation from django.db.models import Q from django.contrib import messages from z3c.rml import rml2pdf @@ -36,15 +38,17 @@ class RigboardIndex(generic.TemplateView): context['events'] = models.Event.objects.current_events() return context + class WebCalendar(generic.TemplateView): template_name = 'RIGS/calendar.html' def get_context_data(self, **kwargs): context = super(WebCalendar, self).get_context_data(**kwargs) - context['view'] = kwargs.get('view','') - context['date'] = kwargs.get('date','') + context['view'] = kwargs.get('view', '') + context['date'] = kwargs.get('date', '') return context + class EventDetail(generic.DetailView): model = models.Event @@ -53,7 +57,6 @@ class EventOembed(generic.View): model = models.Event def get(self, request, pk=None): - embed_url = reverse('event_embed', args=[pk]) full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) @@ -85,7 +88,6 @@ class EventCreate(generic.CreateView): if re.search('"-\d+"', form['items_json'].value()): messages.info(self.request, "Your item changes have been saved. Please fix the errors and save the event.") - # Get some other objects to include in the form. Used when there are errors but also nice and quick. for field, model in form.related_models.iteritems(): value = form[field].value() @@ -117,15 +119,17 @@ class EventUpdate(generic.UpdateView): def get_success_url(self): return reverse_lazy('event_detail', kwargs={'pk': self.object.pk}) + class EventDuplicate(EventUpdate): def get_object(self, queryset=None): - old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) - new = copy.copy(old) # Make a copy of the object in memory - new.based_on = old # Make the new event based on the old event + old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) + new = copy.copy(old) # Make a copy of the object in memory + new.based_on = old # Make the new event based on the old event new.purchase_order = None - if self.request.method in ('POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) - new.pk = None # This means a new event will be created on save, and all items will be re-created + if self.request.method in ( + 'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) + new.pk = None # This means a new event will be created on save, and all items will be re-created else: messages.info(self.request, 'Event data duplicated but not yet saved. Click save to complete operation.') @@ -136,6 +140,7 @@ class EventDuplicate(EventUpdate): context["duplicate"] = True return context + class EventPrint(generic.View): def get(self, request, pk): object = get_object_or_404(models.Event, pk=pk) @@ -145,8 +150,7 @@ class EventPrint(generic.View): merger = PdfFileMerger() for copy in copies: - - context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this + context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this 'object': object, 'fonts': { 'opensans': { @@ -154,8 +158,8 @@ class EventPrint(generic.View): 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', } }, - 'copy':copy, - 'current_user':request.user, + 'copy': copy, + 'current_user': request.user, }) # context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3 @@ -183,6 +187,7 @@ class EventPrint(generic.View): response.write(merged.getvalue()) return response + class EventArchive(generic.ArchiveIndexView): model = models.Event date_field = "start_date" @@ -219,3 +224,68 @@ class EventArchive(generic.ArchiveIndexView): messages.add_message(self.request, messages.WARNING, "No events have been found matching those criteria.") return qs + + +class EventAuthorise(generic.UpdateView): + template_name = 'RIGS/eventauthorisation_form.html' + success_template = 'RIGS/eventauthorisation_success.html' + + def form_valid(self, form): + # TODO: send email confirmation + self.template_name = self.success_template + messages.add_message(self.request, messages.SUCCESS, + 'Success! Your event has been authorised. You will also receive email confirmation.') + return self.render_to_response(self.get_context_data()) + + @property + def event(self): + return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk']) + + def get_object(self, queryset=None): + return self.event.authorisation + + def get_form_class(self): + if self.event.organisation is not None and self.event.organisation.union_account: + return forms.InternalClientEventAuthorisationForm + else: + return forms.ExternalClientEventAuthorisationForm + + def get_context_data(self, **kwargs): + context = super(EventAuthorise, self).get_context_data(**kwargs) + context['event'] = self.event + + if self.get_form_class() is forms.InternalClientEventAuthorisationForm: + context['internal'] = True + else: + context['internal'] = False + + context['tos_url'] = settings.TERMS_OF_HIRE_URL + return context + + def get(self, request, *args, **kwargs): + if self.get_object() is not None and self.get_object().pk is not None: + if self.event.authorised: + messages.add_message(self.request, messages.WARNING, + "This event has already been authorised. Please confirm you wish to reauthorise") + else: + messages.add_message(self.request, messages.WARNING, + "This event has already been authorised, but the amount has changed." + + "Please check the amount and reauthorise.") + return super(EventAuthorise, self).get(request, *args, **kwargs) + + def get_form(self, **kwargs): + form = super(EventAuthorise, self).get_form(**kwargs) + form.instance.event = self.event + form.instance.email = self.request.email + return form + + def dispatch(self, request, *args, **kwargs): + # Verify our signature matches up and all is well with the integrity of the URL + try: + data = signing.loads(kwargs.get('hmac')) + assert int(kwargs.get('pk')) == int(data.get('pk')) + request.email = data['email'] + except (signing.BadSignature, AssertionError, KeyError): + raise SuspiciousOperation( + "The security integrity of that URL is invalid. Please contact your event MIC to obtain a new URL") + return super(EventAuthorise, self).dispatch(request, *args, **kwargs) diff --git a/RIGS/templates/RIGS/client_eventdetails.html b/RIGS/templates/RIGS/client_eventdetails.html new file mode 100644 index 00000000..4f3810f8 --- /dev/null +++ b/RIGS/templates/RIGS/client_eventdetails.html @@ -0,0 +1,78 @@ +
+
+
+
Contact Details
+
+
+
Person
+
+ {% if event.person %} + {{ event.person.name }} + {% endif %} +
+ +
Email
+
+ {{ event.person.email }} +
+ +
Phone Number
+
{{ event.person.phone }}
+
+
+
+ {% if event.organisation %} +
+
Organisation
+
+
+
Organisation
+
+ {{ event.organisation.name }} +
+ +
Phone Number
+
{{ object.organisation.phone }}
+
+
+
+ {% endif %} +
+ +
+
+
Event Info
+
+
+
Event Venue
+
+ {% if object.venue %} + + {{ object.venue }} + + {% endif %} +
+ +
Status
+
{{ event.get_status_display }}
+ +
 
+ +
Access From
+
{{ event.access_at|date:"D d M Y H:i"|default:"" }}
+ +
Event Starts
+
{{ event.start_date|date:"D d M Y" }} {{ event.start_time|date:"H:i" }}
+ +
Event Ends
+
{{ event.end_date|date:"D d M Y" }} {{ event.end_time|date:"H:i" }}
+ +
 
+ +
Event Description
+
{{ event.description|linebreaksbr }}
+
+
+
+
+
diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html new file mode 100644 index 00000000..e9999a73 --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -0,0 +1,118 @@ +{% extends 'base_client.html' %} +{% load widget_tweaks %} + +{% block title %} + {% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }} +{% endblock %} + +{% block content %} +
+
+

+ {% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} + | {{ event.name }} {% if event.dry_hire %}Dry Hire{% endif %} +

+
+
+ +
+ {% include 'RIGS/client_eventdetails.html' %} +
+ +
+
+ {% with object=event %} + {% include 'RIGS/item_table.html' %} + {% endwith %} +
+
+ +
+
+
+
Event Authorisation
+ +
+
{% csrf_token %} + {% include 'form_errors.html' %} +
+
+
+ + +
+ {% render_field form.name class+="form-control" %} +
+
+ + {% if internal %} +
+ +
+ {% render_field form.uni_id class+="form-control" %} +
+
+ {% endif %} +
+ +
+ {% if internal %} +
+ +
+ {% render_field form.account_code class+="form-control" %} +
+
+ {% else %} +
+ +
+ {% render_field form.po class+="form-control" %} +
+
+ {% endif %} + +
+ +
+ {% render_field form.amount class+="form-control" %} +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+{% endblock %} diff --git a/RIGS/templates/RIGS/eventauthorisation_success.html b/RIGS/templates/RIGS/eventauthorisation_success.html new file mode 100644 index 00000000..2b899076 --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_success.html @@ -0,0 +1,69 @@ +{% extends 'base_client.html' %} +{% load widget_tweaks %} + +{% block title %} + {% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }} +{% endblock %} + +{% block content %} +
+
+

+ {% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} + | {{ event.name }} {% if event.dry_hire %}Dry Hire{% endif %} +

+
+
+ + {% include 'RIGS/client_eventdetails.html' %} + +
+
+ {% with object=event %} + {% include 'RIGS/item_table.html' %} + {% endwith %} +
+
+ +
+
+
+
Event Authorisation
+ +
+
+
+
+
Name
+
{{ object.name }}
+ +
Email
+
{{ object.email }}
+ + {% if internal %} +
University ID
+
{{ object.uni_id }}
+ {% endif %} +
+
+ +
+
+ {% if internal %} +
Account code
+
{{ object.account_code }}
+ {% else %} +
PO
+
{{ object.po }}
+ {% endif %} + +
Authorised amount
+
£ {{ object.amount|floatformat:2 }}
+
+
+
+
+
+
+
+{% endblock %} diff --git a/RIGS/urls.py b/RIGS/urls.py index 15bb0990..2273fd01 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -16,7 +16,8 @@ urlpatterns = patterns('', url('^user/login/$', 'RIGS.views.login', name='login'), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), - url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}), + url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', + {'password_reset_form': forms.PasswordReset}), # People url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), @@ -70,9 +71,12 @@ urlpatterns = patterns('', # Rigboard url(r'^rigboard/$', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'), - url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/calendar/(?P(month|week|day))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), - url(r'^rigboard/calendar/(?P(month|week|day))/(?P(\d{4}-\d{2}-\d{2}))/$', login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), + url(r'^rigboard/calendar/$', login_required()(rigboard.WebCalendar.as_view()), + name='web_calendar'), + url(r'^rigboard/calendar/(?P(month|week|day))/$', + login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), + url(r'^rigboard/calendar/(?P(month|week|day))/(?P(\d{4}-\d{2}-\d{2}))/$', + login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), url(r'^rigboard/archive/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), url(r'^rigboard/activity/$', permission_required_with_403('RIGS.view_event')(versioning.ActivityTable.as_view()), @@ -82,10 +86,12 @@ urlpatterns = patterns('', name='activity_feed'), url(r'^event/(?P\d+)/$', - permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")(rigboard.EventDetail.as_view()), + permission_required_with_403('RIGS.view_event', oembed_view="event_oembed")( + rigboard.EventDetail.as_view()), name='event_detail'), url(r'^event/(?P\d+)/embed/$', - xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), + xframe_options_exempt( + login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), name='event_embed'), url(r'^event/(?P\d+)/oembed_json/$', rigboard.EventOembed.as_view(), @@ -109,7 +115,8 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), name='event_history', kwargs={'model': models.Event}), - + url(r'^event/(?P\d+)/(?P[-:\w]+)/$', rigboard.EventAuthorise.as_view(), + name='event_authorise'), # Finance url(r'^invoice/$', @@ -152,17 +159,22 @@ urlpatterns = patterns('', name='profile_detail'), url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), name='profile_update_self'), - url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), + url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), + name='reset_api_key'), # ICS Calendar - API key authentication - url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), + url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), + name="ics_calendar"), # API - url(r'^api/(?P\w+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), - url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), + url(r'^api/(?P\w+)/$', login_required(views.SecureAPIRequest.as_view()), + name="api_secure"), + url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), + name="api_secure"), # Legacy URL's - url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')), + url(r'^rig/show/(?P\d+)/$', + RedirectView.as_view(permanent=True, pattern_name='event_detail')), url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')), url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), ) diff --git a/templates/base_client.html b/templates/base_client.html new file mode 100644 index 00000000..b8cd614b --- /dev/null +++ b/templates/base_client.html @@ -0,0 +1,109 @@ +{% load static from staticfiles %} +{% load raven %} + + + + + + {% block title %}{% endblock %} | Rig Information Gathering System + + + + + + + + + + {% block css %} + {% endblock %} + + + + + {% block preload_js %} + {% endblock %} + + {% block extra-head %}{% endblock %} + + + +{% include "analytics.html" %} + + +
+
+ {% block content-header %} + {% if error %} +
{{ error }}
{% endif %} + {% if info %} +
{{ info }}
{% endif %} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% endblock %} + + {% block content %}{% endblock %} +
+ + +
+ + + + + + + + + + + +{% block js %} +{% endblock %} + + From 1670b190c2062ac0e540619baeccba509ca5df51 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 18:11:49 +0100 Subject: [PATCH 073/275] Add tests for the client side authorisation --- RIGS/rigboard.py | 3 +- RIGS/test_functional.py | 346 ++++++++++++++++++++++++++-------------- RIGS/test_models.py | 30 ---- 3 files changed, 231 insertions(+), 148 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index d602d66b..2d98e80e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -231,6 +231,7 @@ class EventAuthorise(generic.UpdateView): success_template = 'RIGS/eventauthorisation_success.html' def form_valid(self, form): + self.object = form.save() # TODO: send email confirmation self.template_name = self.success_template messages.add_message(self.request, messages.SUCCESS, @@ -242,7 +243,7 @@ class EventAuthorise(generic.UpdateView): return models.Event.objects.select_related('organisation', 'person', 'venue').get(pk=self.kwargs['pk']) def get_object(self, queryset=None): - return self.event.authorisation + return getattr(self.event, 'authorisation', None) def get_form_class(self): if self.event.organisation is not None and self.event.organisation.union_account: diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index f6c60865..13d6871b 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -4,9 +4,11 @@ import re from datetime import date, timedelta import reversion -from django.core import mail +from django.core import mail, signing +from django.core.urlresolvers import reverse from django.db import transaction -from django.test import LiveServerTestCase +from django.http import HttpResponseBadRequest +from django.test import LiveServerTestCase, TestCase from django.test.client import Client from selenium import webdriver from selenium.common.exceptions import StaleElementReferenceException, WebDriverException @@ -17,10 +19,9 @@ from RIGS import models class UserRegistrationTest(LiveServerTestCase): - def setUp(self): self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) # Set implicit wait session wide + self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' def tearDown(self): @@ -149,17 +150,16 @@ class UserRegistrationTest(LiveServerTestCase): class EventTest(LiveServerTestCase): - def setUp(self): self.profile = models.Profile( username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True) self.profile.set_password("EventTestPassword") self.profile.save() - self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') - + self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) # Set implicit wait session wide + self.browser.implicitly_wait(3) # Set implicit wait session wide self.browser.maximize_window() os.environ['RECAPTCHA_TESTING'] = 'True' @@ -203,7 +203,7 @@ class EventTest(LiveServerTestCase): # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 10) # setup WebDriverWait to use later (to wait for animations) wait.until(animation_is_finished()) @@ -370,26 +370,28 @@ class EventTest(LiveServerTestCase): wait.until(animation_is_finished()) modal = self.browser.find_element_by_id("itemModal") modal.find_element_by_id("item_name").send_keys("Test Item 1") - modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines") + modal.find_element_by_id("item_description").send_keys( + "This is an item description\nthat for reasons unkown spans two lines") e = modal.find_element_by_id("item_quantity") e.click() e.send_keys(Keys.UP) e.send_keys(Keys.UP) e = modal.find_element_by_id("item_cost") e.send_keys("23.95") - e.send_keys(Keys.ENTER) # enter submit + e.send_keys(Keys.ENTER) # enter submit # Confirm item has been saved to json field objectitems = self.browser.execute_script("return objectitems;") self.assertEqual(1, len(objectitems)) - testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID + testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID self.assertEqual("Test Item 1", testitem['name']) - self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields + self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields # See new item appear in table - row = self.browser.find_element_by_id('item--1') # ID number is known, see above + row = self.browser.find_element_by_id('item--1') # ID number is known, see above self.assertIn("Test Item 1", row.find_element_by_xpath('//span[@class="name"]').text) - self.assertIn("This is an item description", row.find_element_by_xpath('//div[@class="item-description"]').text) + self.assertIn("This is an item description", + row.find_element_by_xpath('//div[@class="item-description"]').text) self.assertEqual(u'£ 23.95', row.find_element_by_xpath('//tr[@id="item--1"]/td[2]').text) self.assertEqual("2", row.find_element_by_xpath('//td[@class="quantity"]').text) self.assertEqual(u'£ 47.90', row.find_element_by_xpath('//tr[@id="item--1"]/td[4]').text) @@ -431,92 +433,95 @@ class EventTest(LiveServerTestCase): # See redirected to success page successTitle = self.browser.find_element_by_xpath('//h1').text event = models.Event.objects.get(name='Test Event Name') - self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle) + self.assertIn("N0000%d | Test Event Name" % event.pk, successTitle) except WebDriverException: # This is a dirty workaround for wercker being a bit funny and not running it correctly. # Waiting for wercker to get back to me about this pass def testEventDuplicate(self): - testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end", purchase_order="TESTPO") + testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, + start_date=date.today() + timedelta(days=6), + description="start future no end", purchase_order="TESTPO") item1 = models.EventItem( - event=testEvent, - name="Test Item 1", - cost="10.00", - quantity="1", - order=1 - ).save() + event=testEvent, + name="Test Item 1", + cost="10.00", + quantity="1", + order=1 + ).save() item2 = models.EventItem( - event=testEvent, - name="Test Item 2", - description="Foo", - cost="9.72", - quantity="3", - order=2, - ).save() + event=testEvent, + name="Test Item 2", + description="Foo", + cost="9.72", + quantity="3", + order=2, + ).save() self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk) + '/duplicate/') self.authenticate('/event/' + str(testEvent.pk) + '/duplicate/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 10) # setup WebDriverWait to use later (to wait for animations) save = self.browser.find_element_by_xpath( '(//button[@type="submit"])[3]') form = self.browser.find_element_by_tag_name('form') - # Check the items are visible - table = self.browser.find_element_by_id('item-table') # ID number is known, see above + table = self.browser.find_element_by_id('item-table') # ID number is known, see above self.assertIn("Test Item 1", table.text) self.assertIn("Test Item 2", table.text) # Check the info message is visible - self.assertIn("Event data duplicated but not yet saved",self.browser.find_element_by_id('content').text) + self.assertIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Add item form.find_element_by_xpath('//button[contains(@class, "item-add")]').click() wait.until(animation_is_finished()) modal = self.browser.find_element_by_id("itemModal") modal.find_element_by_id("item_name").send_keys("Test Item 3") - modal.find_element_by_id("item_description").send_keys("This is an item description\nthat for reasons unkown spans two lines") + modal.find_element_by_id("item_description").send_keys( + "This is an item description\nthat for reasons unkown spans two lines") e = modal.find_element_by_id("item_quantity") e.click() e.send_keys(Keys.UP) e.send_keys(Keys.UP) e = modal.find_element_by_id("item_cost") e.send_keys("23.95") - e.send_keys(Keys.ENTER) # enter submit + e.send_keys(Keys.ENTER) # enter submit # Attempt to save save.click() - self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) - self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible + self.assertNotIn("N0000%d" % testEvent.pk, self.browser.find_element_by_xpath('//h1').text) + self.assertNotIn("Event data duplicated but not yet saved", + self.browser.find_element_by_id('content').text) # Check info message not visible # Check the new items are visible - table = self.browser.find_element_by_id('item-table') # ID number is known, see above + table = self.browser.find_element_by_id('item-table') # ID number is known, see above self.assertIn("Test Item 1", table.text) self.assertIn("Test Item 2", table.text) self.assertIn("Test Item 3", table.text) infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') - self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + self.assertIn("N0000%d" % testEvent.pk, + infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) # Check the PO hasn't carried through self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) + self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) # Go back to the old event - - self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) #Go back to the old event - - #Check that based-on hasn't crept into the old event + # Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') - self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + self.assertNotIn("N0000%d" % testEvent.pk, + infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) # Check the PO remains on the old event self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) # Check the items are as they were - table = self.browser.find_element_by_id('item-table') # ID number is known, see above + table = self.browser.find_element_by_id('item-table') # ID number is known, see above self.assertIn("Test Item 1", table.text) self.assertIn("Test Item 2", table.text) self.assertNotIn("Test Item 3", table.text) @@ -526,7 +531,7 @@ class EventTest(LiveServerTestCase): # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 10) # setup WebDriverWait to use later (to wait for animations) wait.until(animation_is_finished()) @@ -553,7 +558,6 @@ class EventTest(LiveServerTestCase): self.assertTrue(error.is_displayed()) self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text) - # Same date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') @@ -575,7 +579,6 @@ class EventTest(LiveServerTestCase): self.assertTrue(error.is_displayed()) self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text) - # Same date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') @@ -591,7 +594,6 @@ class EventTest(LiveServerTestCase): form.find_element_by_id('id_end_time').clear() form.find_element_by_id('id_end_time').send_keys('06:00') - # No end date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') @@ -612,7 +614,6 @@ class EventTest(LiveServerTestCase): self.assertTrue(error.is_displayed()) self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text) - # 2 dates, end after start form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') @@ -623,24 +624,24 @@ class EventTest(LiveServerTestCase): form.find_element_by_id('id_end_date').send_keys('3015-04-26') form.find_element_by_id('id_start_time').clear() - + form.find_element_by_id('id_end_time').clear() - + # Attempt to save - should succeed save.click() - + # See redirected to success page successTitle = self.browser.find_element_by_xpath('//h1').text event = models.Event.objects.get(name='Test Event Name') - self.assertIn("N0000%d | Test Event Name"%event.pk, successTitle) - + self.assertIn("N0000%d | Test Event Name" % event.pk, successTitle) + def testRigNonRig(self): self.browser.get(self.live_server_url + '/event/create/') # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) - self.browser.implicitly_wait(3) #Set session-long wait (only works for non-existant DOM objects) + wait = WebDriverWait(self.browser, 10) # setup WebDriverWait to use later (to wait for animations) + self.browser.implicitly_wait(3) # Set session-long wait (only works for non-existant DOM objects) wait.until(animation_is_finished()) @@ -672,7 +673,8 @@ class EventTest(LiveServerTestCase): person = models.Person(name="Event Detail Person", email="eventdetail@person.tests.rigs", phone="123 123") person.save() with transaction.atomic(), reversion.create_revision(): - organisation = models.Organisation(name="Event Detail Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save() + organisation = models.Organisation(name="Event Detail Organisation", + email="eventdetail@organisation.tests.rigs", phone="123 456").save() with transaction.atomic(), reversion.create_revision(): venue = models.Venue(name="Event Detail Venue").save() with transaction.atomic(), reversion.create_revision(): @@ -702,59 +704,84 @@ class EventTest(LiveServerTestCase): order=2, ).save() - - self.browser.get(self.live_server_url + '/event/%d'%event.pk) - self.authenticate('/event/%d/'%event.pk) - self.assertIn("N%05d | %s"%(event.pk, event.name), self.browser.find_element_by_xpath('//h1').text) + self.browser.get(self.live_server_url + '/event/%d' % event.pk) + self.authenticate('/event/%d/' % event.pk) + self.assertIn("N%05d | %s" % (event.pk, event.name), self.browser.find_element_by_xpath('//h1').text) personPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..') - self.assertEqual(person.name, personPanel.find_element_by_xpath('//dt[text()="Person"]/following-sibling::dd[1]').text) - self.assertEqual(person.email, personPanel.find_element_by_xpath('//dt[text()="Email"]/following-sibling::dd[1]').text) - self.assertEqual(person.phone, personPanel.find_element_by_xpath('//dt[text()="Phone Number"]/following-sibling::dd[1]').text) + self.assertEqual(person.name, + personPanel.find_element_by_xpath('//dt[text()="Person"]/following-sibling::dd[1]').text) + self.assertEqual(person.email, + personPanel.find_element_by_xpath('//dt[text()="Email"]/following-sibling::dd[1]').text) + self.assertEqual(person.phone, + personPanel.find_element_by_xpath('//dt[text()="Phone Number"]/following-sibling::dd[1]').text) organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..') -class IcalTest(LiveServerTestCase): +class IcalTest(LiveServerTestCase): def setUp(self): self.all_events = set(range(1, 18)) self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18) self.not_current_events = set(self.all_events) - set(self.current_events) - self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') + self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') self.profile = models.Profile( username="EventTest", first_name="Event", last_name="Test", initials="ETU", is_superuser=True) self.profile.set_password("EventTestPassword") self.profile.save() # produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked - models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end") - models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), description="start today no end") - models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), end_date=date.today(), description="start today with end today") - models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, start_date=date.today()-timedelta(weeks=104), description="start past 2 years no end") - models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=7), end_date=date.today()-timedelta(days=1), description="start past 1 week with end past") - models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, start_date=date.today()-timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start past, end future") - models.Event.objects.create(name="TE E7", status=models.Event.BOOKED, start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), description="start + end in future") + models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, + start_date=date.today() + timedelta(days=6), description="start future no end") + models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(), + description="start today no end") + models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(), + end_date=date.today(), description="start today with end today") + models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED, + start_date=date.today() - timedelta(weeks=104), + description="start past 2 years no end") + models.Event.objects.create(name="TE E5", status=models.Event.BOOKED, + start_date=date.today() - timedelta(days=7), + end_date=date.today() - timedelta(days=1), + description="start past 1 week with end past") + models.Event.objects.create(name="TE E6", status=models.Event.BOOKED, + start_date=date.today() - timedelta(days=2), + end_date=date.today() + timedelta(days=2), description="start past, end future") + models.Event.objects.create(name="TE E7", status=models.Event.BOOKED, + start_date=date.today() + timedelta(days=2), + end_date=date.today() + timedelta(days=2), description="start + end in future") # 2 cancelled - 1 current - models.Event.objects.create(name="TE E8", start_date=date.today()+timedelta(days=2), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled in future") - models.Event.objects.create(name="TE E9", start_date=date.today()-timedelta(days=1), end_date=date.today()+timedelta(days=2), status=models.Event.CANCELLED, description="cancelled and started") + models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2), + end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, + description="cancelled in future") + models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1), + end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED, + description="cancelled and started") # 5 dry hire - 3 current - 1 cancelled models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today") - models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, description="dryhire today, checked in") - models.Event.objects.create(name="TE E12", start_date=date.today()-timedelta(days=1), dry_hire=True, status=models.Event.BOOKED, description="dryhire past") - models.Event.objects.create(name="TE E13", start_date=date.today()-timedelta(days=2), dry_hire=True, checked_in_by=self.profile, description="dryhire past checked in") - models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, status=models.Event.CANCELLED, description="dryhire today cancelled") + models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile, + description="dryhire today, checked in") + models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True, + status=models.Event.BOOKED, description="dryhire past") + models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True, + checked_in_by=self.profile, description="dryhire past checked in") + models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True, + status=models.Event.CANCELLED, description="dryhire today cancelled") # 4 non rig - 3 current models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today") - models.Event.objects.create(name="TE E16", start_date=date.today()+timedelta(days=1), is_rig=False, description="non rig tomorrow") - models.Event.objects.create(name="TE E17", start_date=date.today()-timedelta(days=1), is_rig=False, description="non rig yesterday") - models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, description="non rig today cancelled") + models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False, + description="non rig tomorrow") + models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False, + description="non rig yesterday") + models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, + description="non rig today cancelled") self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) # Set implicit wait session wide + self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' def tearDown(self): @@ -785,14 +812,15 @@ class IcalTest(LiveServerTestCase): # Completes and comes back to /user/ # Checks that no api key is displayed - self.assertEqual("No API Key Generated", self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text) + self.assertEqual("No API Key Generated", + self.browser.find_element_by_xpath("//div[@id='content']/div/div/div[3]/dl[2]/dd").text) self.assertEqual("No API Key Generated", self.browser.find_element_by_css_selector("pre").text) - + # Now creates an API key, and check a URL is displayed one self.browser.find_element_by_link_text("Generate API Key").click() self.assertIn("rigs.ics", self.browser.find_element_by_id("cal-url").text) self.assertNotIn("?", self.browser.find_element_by_id("cal-url").text) - + # Lets change everything so it's not the default value self.browser.find_element_by_xpath("//input[@value='rig']").click() self.browser.find_element_by_xpath("//input[@value='non-rig']").click() @@ -802,7 +830,9 @@ class IcalTest(LiveServerTestCase): self.browser.find_element_by_xpath("//input[@value='confirmed']").click() # and then check the url is correct - self.assertIn("rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false", self.browser.find_element_by_id("cal-url").text) + self.assertIn( + "rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false", + self.browser.find_element_by_id("cal-url").text) # Awesome - all seems to work @@ -815,27 +845,24 @@ class IcalTest(LiveServerTestCase): # Now creates an API key, and check a URL is displayed one self.browser.find_element_by_link_text("Generate API Key").click() - - c = Client() - + # Default settings - should have all non-cancelled events # Get the ical file (can't do this in selanium because reasons) icalUrl = self.browser.find_element_by_id("cal-url").text response = c.get(icalUrl) self.assertEqual(200, response.status_code) - #Check has entire file + # Check has entire file self.assertIn("BEGIN:VCALENDAR", response.content) self.assertIn("END:VCALENDAR", response.content) - expectedIn= [1,2,3,5,6,7,10,11,12,13,15,16,17] - for test in range(1,18): + expectedIn = [1, 2, 3, 5, 6, 7, 10, 11, 12, 13, 15, 16, 17] + for test in range(1, 18): if test in expectedIn: - self.assertIn("TE E"+str(test)+" ", response.content) + self.assertIn("TE E" + str(test) + " ", response.content) else: - self.assertNotIn("TE E"+str(test)+" ", response.content) - + self.assertNotIn("TE E" + str(test) + " ", response.content) # Only dry hires self.browser.find_element_by_xpath("//input[@value='rig']").click() @@ -845,13 +872,12 @@ class IcalTest(LiveServerTestCase): response = c.get(icalUrl) self.assertEqual(200, response.status_code) - expectedIn= [10,11,12,13] - for test in range(1,18): + expectedIn = [10, 11, 12, 13] + for test in range(1, 18): if test in expectedIn: - self.assertIn("TE E"+str(test)+" ", response.content) + self.assertIn("TE E" + str(test) + " ", response.content) else: - self.assertNotIn("TE E"+str(test)+" ", response.content) - + self.assertNotIn("TE E" + str(test) + " ", response.content) # Only provisional rigs self.browser.find_element_by_xpath("//input[@value='rig']").click() @@ -862,12 +888,12 @@ class IcalTest(LiveServerTestCase): response = c.get(icalUrl) self.assertEqual(200, response.status_code) - expectedIn= [1,2] - for test in range(1,18): + expectedIn = [1, 2] + for test in range(1, 18): if test in expectedIn: - self.assertIn("TE E"+str(test)+" ", response.content) + self.assertIn("TE E" + str(test) + " ", response.content) else: - self.assertNotIn("TE E"+str(test)+" ", response.content) + self.assertNotIn("TE E" + str(test) + " ", response.content) # Only cancelled non-rigs self.browser.find_element_by_xpath("//input[@value='rig']").click() @@ -879,12 +905,12 @@ class IcalTest(LiveServerTestCase): response = c.get(icalUrl) self.assertEqual(200, response.status_code) - expectedIn= [18] - for test in range(1,18): + expectedIn = [18] + for test in range(1, 18): if test in expectedIn: - self.assertIn("TE E"+str(test)+" ", response.content) + self.assertIn("TE E" + str(test) + " ", response.content) else: - self.assertNotIn("TE E"+str(test)+" ", response.content) + self.assertNotIn("TE E" + str(test) + " ", response.content) # Nothing selected self.browser.find_element_by_xpath("//input[@value='non-rig']").click() @@ -894,17 +920,19 @@ class IcalTest(LiveServerTestCase): response = c.get(icalUrl) self.assertEqual(200, response.status_code) - expectedIn= [] - for test in range(1,18): + expectedIn = [] + for test in range(1, 18): if test in expectedIn: - self.assertIn("TE E"+str(test)+" ", response.content) + self.assertIn("TE E" + str(test) + " ", response.content) else: - self.assertNotIn("TE E"+str(test)+" ", response.content) - - # Wow - that was a lot of tests + self.assertNotIn("TE E" + str(test) + " ", response.content) + + # Wow - that was a lot of tests + class animation_is_finished(object): """ Checks if animation is done """ + def __init__(self): pass @@ -915,3 +943,87 @@ class animation_is_finished(object): import time time.sleep(0.1) return finished + + +class ClientEventAuthorisationTest(TestCase): + auth_data = { + 'name': 'Test ABC', + 'po': '1234ABCZXY', + 'account_code': 'ABC TEST 12345', + 'uni_id': 1234567890, + 'tos': True + } + + def setUp(self): + venue = models.Venue.objects.create(name='Authorisation Test Venue') + client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') + organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False) + self.event = models.Event.objects.create( + name='Authorisation Test', + start_date=date.today(), + venue=venue, + person=client, + organisation=organisation, + ) + self.hmac = signing.dumps({'pk': self.event.pk, 'email': 'authemail@function.test'}) + self.url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': self.hmac}) + + def test_requires_valid_hmac(self): + bad_hmac = self.hmac[:-1] + url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': bad_hmac}) + response = self.client.get(url) + self.assertIsInstance(response, HttpResponseBadRequest) + # TODO: Add some form of sensbile user facing error + # self.assertIn(response.content, "new URL") # check there is some level of sane instruction + + response = self.client.get(self.url) + self.assertContains(response, self.event.organisation.name) + + def test_generic_validation(self): + response = self.client.get(self.url) + self.assertContains(response, "Terms of Hire") + + response = self.client.post(self.url) + self.assertContains(response, "This field is required.", 4) + + data = self.auth_data + data['amount'] = self.event.total + 1 + + response = self.client.post(self.url, data) + self.assertContains(response, "The amount authorised must equal the total for the event") + self.assertNotContains(response, "This field is required.") + + data['amount'] = self.event.total + response = self.client.post(self.url, data) + self.assertContains(response, "Your event has been authorised") + + self.event.refresh_from_db() + self.assertTrue(self.event.authorised) + self.assertEqual(self.event.authorisation.email, "authemail@function.test") + + def test_internal_validation(self): + self.event.organisation.union_account = True + self.event.organisation.save() + + response = self.client.get(self.url) + self.assertContains(response, "Account code") + self.assertContains(response, "University ID") + + response = self.client.post(self.url) + self.assertContains(response, "This field is required.", 5) + + data = self.auth_data + response = self.client.post(self.url, data) + self.assertContains(response, "Your event has been authorised.") + + def test_duplicate_warning(self): + auth = models.EventAuthorisation.objects.create(event=self.event, name='Test ABC', email='dupe@functional.test', + po='ABC12345', amount=self.event.total) + response = self.client.get(self.url) + self.assertContains(response, 'This event has already been authorised.') + + auth.amount += 1 + auth.save() + + response = self.client.get(self.url) + self.assertContains(response, 'amount has changed') diff --git a/RIGS/test_models.py b/RIGS/test_models.py index 0cbe58f1..a858187d 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -341,28 +341,6 @@ class EventAuthorisationTestCase(TestCase): models.EventItem.objects.create(event=cls.event, name="Authorisation test item", quantity=2, cost=123.45, order=1) - def test_validation(self): - auth = models.EventAuthorisation(event=self.event, email="authroisation@model.test.case", name="Test Auth") - - auth.amount = self.event.total - 1 - self.assertRaises(ValidationError, auth.clean) - auth.amount = self.event.total - - # Test for externals first - self.assertRaises(ValidationError, auth.clean) - self.event.organisation = self.organisation - self.assertRaises(ValidationError, auth.clean) - auth.po = "TEST123" - self.assertIsNone(auth.clean()) - - auth.po = None - self.organisation.union_account = True - self.assertRaises(ValidationError, auth.clean) - auth.uni_id = "1234567" - self.assertRaises(ValidationError, auth.clean) - auth.account_code = "TST AUTH 12345" - self.assertIsNone(auth.clean()) - def test_event_property(self): auth1 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", name="Test Auth 1", amount=self.event.total - 1) @@ -370,11 +348,3 @@ class EventAuthorisationTestCase(TestCase): auth1.amount = self.event.total auth1.save() self.assertTrue(self.event.authorised) - - auth2 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", - name="Test Auth 2", amount=self.event.total - 1) - self.assertEqual(auth2.pk, self.event.authroisations.latest('created_at').pk) - self.assertFalse(self.event.authorised) - auth2.amount = self.event.total + 1 - auth2.save() - self.assertTrue(self.event.authorised) From cf11e8235f1c73600b57d4403c3186de6591b4e1 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 18:15:47 +0100 Subject: [PATCH 074/275] Add ID tagging to auth form to autoscroll on error --- RIGS/templates/RIGS/eventauthorisation_form.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index e9999a73..d989b01e 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -30,10 +30,11 @@
-
Event Authorisation
+
Event Authorisation
-
{% csrf_token %} + + {% csrf_token %} {% include 'form_errors.html' %}
From 3b2aa02ae5dc2737537f99e9c0b2b4ae780b77ae Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 19:16:45 +0100 Subject: [PATCH 075/275] Add success notification emails. Enable RevisionMixin for EventAuthorisation. Add signal receivers for RIGS. Expand RIGS into an explicitly defined app to support signals. --- RIGS/__init__.py | 1 + RIGS/apps.py | 8 +++++ RIGS/models.py | 2 +- RIGS/rigboard.py | 5 ++- RIGS/signals.py | 34 +++++++++++++++++++ .../eventauthorisation_client_success.txt | 11 ++++++ .../RIGS/eventauthorisation_mic_success.txt | 5 +++ RIGS/test_models.py | 18 ++++++---- 8 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 RIGS/apps.py create mode 100644 RIGS/signals.py create mode 100644 RIGS/templates/RIGS/eventauthorisation_client_success.txt create mode 100644 RIGS/templates/RIGS/eventauthorisation_mic_success.txt diff --git a/RIGS/__init__.py b/RIGS/__init__.py index e69de29b..f6f847cc 100644 --- a/RIGS/__init__.py +++ b/RIGS/__init__.py @@ -0,0 +1 @@ +default_app_config = 'RIGS.apps.RIGSAppConfig' diff --git a/RIGS/apps.py b/RIGS/apps.py new file mode 100644 index 00000000..a0dc3724 --- /dev/null +++ b/RIGS/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class RIGSAppConfig(AppConfig): + name = 'RIGS' + + def ready(self): + import RIGS.signals diff --git a/RIGS/models.py b/RIGS/models.py index 37225697..d1f20446 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -506,7 +506,7 @@ class EventCrew(models.Model): @reversion.register -class EventAuthorisation(models.Model): +class EventAuthorisation(models.Model, RevisionMixin): event = models.OneToOneField('Event', related_name='authorisation') email = models.EmailField() name = models.CharField(max_length=255) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 2d98e80e..5584184d 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -3,6 +3,9 @@ import cStringIO as StringIO from io import BytesIO import urllib2 +import reversion +from django.core.mail import EmailMessage +from django.db import transaction from django.views import generic from django.core.urlresolvers import reverse_lazy from django.shortcuts import get_object_or_404 @@ -232,7 +235,7 @@ class EventAuthorise(generic.UpdateView): def form_valid(self, form): self.object = form.save() - # TODO: send email confirmation + self.template_name = self.success_template messages.add_message(self.request, messages.SUCCESS, 'Success! Your event has been authorised. You will also receive email confirmation.') diff --git a/RIGS/signals.py b/RIGS/signals.py new file mode 100644 index 00000000..869dbdae --- /dev/null +++ b/RIGS/signals.py @@ -0,0 +1,34 @@ +import reversion + +from django.core.mail import EmailMessage +from django.template.loader import get_template + +from RIGS import models + + +def send_eventauthorisation_success_email(instance): + context = { + 'object': instance, + } + client_email = EmailMessage( + "N%05d | %s - Event Authorised".format(instance.event.pk, instance.event.name), + get_template("RIGS/eventauthorisation_client_success.txt").render(context), + to=[instance.email] + ) + mic_email = EmailMessage( + "N%05d | %s - Event Authorised".format(instance.event.pk, instance.event.name), + get_template("RIGS/eventauthorisation_mic_success.txt").render(context), + to=[instance.event.mic.email] + ) + + client_email.send() + mic_email.send() + + +def on_revision_commit(instances, **kwargs): + for instance in instances: + if isinstance(instance, models.EventAuthorisation): + send_eventauthorisation_success_email(instance) + + +reversion.post_revision_commit.connect(on_revision_commit) diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.txt b/RIGS/templates/RIGS/eventauthorisation_client_success.txt new file mode 100644 index 00000000..23e05786 --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.txt @@ -0,0 +1,11 @@ +Hi there, + +Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. + +{% if object.event.organisation and object.event.organisation.union_account %}{# internal #} +Your event is now fully booked and payment will be processed by the finance department automatically. +{% else %}{# external #} +Your event is now fully booked and our finance department will be contact to arrange payment. +{% endif %} + +The TEC Rig Information Gathering System diff --git a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt new file mode 100644 index 00000000..f5577ced --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt @@ -0,0 +1,5 @@ +Hi {{object.event.mic.name}}, + +Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. + +The TEC Rig Information Gathering System diff --git a/RIGS/test_models.py b/RIGS/test_models.py index a858187d..08f1dd69 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -1,4 +1,5 @@ import pytz +import reversion from django.conf import settings from django.core.exceptions import ValidationError from django.test import TestCase @@ -331,14 +332,13 @@ class EventPricingTestCase(TestCase): class EventAuthorisationTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.person = models.Person.objects.create(name='Authorisation Test Person') - cls.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation') - cls.event = models.Event.objects.create(name="AuthorisationTestCase", person=cls.person, + def setUp(self): + self.person = models.Person.objects.create(name='Authorisation Test Person') + self.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation') + self.event = models.Event.objects.create(name="AuthorisationTestCase", person=self.person, start_date=date.today()) # Add some items - models.EventItem.objects.create(event=cls.event, name="Authorisation test item", quantity=2, cost=123.45, + models.EventItem.objects.create(event=self.event, name="Authorisation test item", quantity=2, cost=123.45, order=1) def test_event_property(self): @@ -348,3 +348,9 @@ class EventAuthorisationTestCase(TestCase): auth1.amount = self.event.total auth1.save() self.assertTrue(self.event.authorised) + + def test_last_edited(self): + with reversion.create_revision(): + auth = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", + name="Test Auth", amount=self.event.total) + self.assertIsNotNone(auth.last_edited_at) From 97b11eabbdd649136035c28f3454e28724122f88 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 19:28:35 +0100 Subject: [PATCH 076/275] Add test for sending emails. Add backup email if there isn't an MIC --- PyRIGS/settings.py | 1 + RIGS/signals.py | 9 ++++++++- RIGS/test_functional.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 2e82807e..1cdbcc19 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -221,3 +221,4 @@ TEMPLATE_DIRS = ( USE_GRAVATAR=True TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" +AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' diff --git a/RIGS/signals.py b/RIGS/signals.py index 869dbdae..deac4ce7 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -1,4 +1,5 @@ import reversion +from django.conf import settings from django.core.mail import EmailMessage from django.template.loader import get_template @@ -15,10 +16,16 @@ def send_eventauthorisation_success_email(instance): get_template("RIGS/eventauthorisation_client_success.txt").render(context), to=[instance.email] ) + + if instance.event.mic: + mic_email_address = instance.event.mic.email + else: + mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS + mic_email = EmailMessage( "N%05d | %s - Event Authorised".format(instance.event.pk, instance.event.name), get_template("RIGS/eventauthorisation_mic_success.txt").render(context), - to=[instance.event.mic.email] + to=[mic_email_address] ) client_email.send() diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 13d6871b..87452f11 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -4,6 +4,7 @@ import re from datetime import date, timedelta import reversion +from django.conf import settings from django.core import mail, signing from django.core.urlresolvers import reverse from django.db import transaction @@ -1027,3 +1028,17 @@ class ClientEventAuthorisationTest(TestCase): response = self.client.get(self.url) self.assertContains(response, 'amount has changed') + + def test_email_sent(self): + mail.outbox = [] + + data = self.auth_data + data['amount'] = self.event.total + + response = self.client.post(self.url, data) + self.assertContains(response, "Your event has been authorised.") + self.assertEqual(len(mail.outbox), 2) + + self.assertEqual(mail.outbox[0].to, ['authemail@function.test']) + self.assertEqual(mail.outbox[1].to, [settings.AUTHORISATION_NOTIFICATION_ADDRESS]) + From 7fd0c50146eef0b973209b03e843d60bf42ac77f Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 20:39:19 +0100 Subject: [PATCH 077/275] Add sending of emails to clients. Add email sending methods. Add TEC side sending of emails. --- RIGS/forms.py | 4 + RIGS/rigboard.py | 46 +++++++++- RIGS/templates/RIGS/event_detail.html | 87 +------------------ RIGS/templates/RIGS/event_detail_buttons.html | 33 +++++++ .../eventauthorisation_client_request.txt | 12 +++ .../RIGS/eventauthorisation_request.html | 32 +++++++ RIGS/test_functional.py | 30 +++++++ RIGS/urls.py | 5 ++ 8 files changed, 162 insertions(+), 87 deletions(-) create mode 100644 RIGS/templates/RIGS/event_detail_buttons.html create mode 100644 RIGS/templates/RIGS/eventauthorisation_client_request.txt create mode 100644 RIGS/templates/RIGS/eventauthorisation_request.html diff --git a/RIGS/forms.py b/RIGS/forms.py index c19ac5b8..14df5772 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -175,3 +175,7 @@ class ExternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): class Meta: model = models.EventAuthorisation fields = ('tos', 'name', 'amount', 'po') + + +class EventAuthorisationRequestForm(forms.Form): + email = forms.EmailField(required=True, label='Authoriser Email') diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 5584184d..f4ee7bd0 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -1,11 +1,8 @@ -import os import cStringIO as StringIO from io import BytesIO import urllib2 -import reversion from django.core.mail import EmailMessage -from django.db import transaction from django.views import generic from django.core.urlresolvers import reverse_lazy from django.shortcuts import get_object_or_404 @@ -293,3 +290,46 @@ class EventAuthorise(generic.UpdateView): raise SuspiciousOperation( "The security integrity of that URL is invalid. Please contact your event MIC to obtain a new URL") return super(EventAuthorise, self).dispatch(request, *args, **kwargs) + + +class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin): + model = models.Event + form_class = forms.EventAuthorisationRequestForm + template_name = 'RIGS/eventauthorisation_request.html' + + @property + def object(self): + return self.get_object() + + def get_success_url(self): + if self.request.is_ajax(): + url = reverse_lazy('closemodal') + messages.info(self.request, "$('.event-authorise-request').addClass('btn-success')") + else: + url = reverse_lazy('event_detail', kwargs={ + 'pk': self.object.pk, + }) + messages.add_message(self.request, messages.SUCCESS, "Authorisation request successfully sent.") + return url + + def form_valid(self, form): + email = form.cleaned_data['email'] + + context = { + 'object': self.object, + 'request': self.request, + 'hmac': signing.dumps({ + 'pk': self.object.pk, + 'email': email + }), + } + + msg = EmailMessage( + "N%05d | %s - Event Authorisation Request".format(self.object.pk, self.object.name), + get_template("RIGS/eventauthorisation_client_request.txt").render(context), + to=[email], + ) + + msg.send() + + return super(EventAuthorisationRequest, self).form_valid(form) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index d4b089a9..92d97e46 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -11,34 +11,7 @@
-
- - {% if event.is_rig %} - - {% endif %} - - {% if event.is_rig %} - {% if perms.RIGS.add_invoice %} - - - {% endif %} - {% endif %} -
+ {% include 'RIGS/event_detail_buttons.html' %}
{% endif %} @@ -184,34 +157,7 @@
{% if not request.is_ajax %}
-
- - {% if event.is_rig %} - - {% endif %} - - {% if event.is_rig %} - {% if perms.RIGS.add_invoice %} - - - {% endif %} - {% endif %} -
+ {% include 'RIGS/event_detail_buttons.html' %}
{% endif %} {% if event.is_rig %} @@ -229,34 +175,7 @@
{% if not request.is_ajax %}
-
- - {% if event.is_rig %} - - {% endif %} - - {% if event.is_rig %} - {% if perms.RIGS.add_invoice %} - - - {% endif %} - {% endif %} -
+ {% include 'RIGS/event_detail_buttons.html' %}
{% endif %} {% endif %} diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html new file mode 100644 index 00000000..7f91db83 --- /dev/null +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -0,0 +1,33 @@ +
+ + {% if event.is_rig %} + + {% endif %} + + {% if event.is_rig %} + + + Authorisation Request + + {% if perms.RIGS.add_invoice %} + + + {% endif %} + {% endif %} +
diff --git a/RIGS/templates/RIGS/eventauthorisation_client_request.txt b/RIGS/templates/RIGS/eventauthorisation_client_request.txt new file mode 100644 index 00000000..959da694 --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_client_request.txt @@ -0,0 +1,12 @@ +Hi there, + +{{request.user.get_full_name}} has requested that you authorise N{{object.pk|stringformat:"05d"}} | {{object.name}}. + +Please find the link below to complete the event booking process. +{% if object.event.organisation and object.event.organisation.union_account %}{# internal #} +Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward this email on. +{% endif %} + +{{request.scheme}}://{{request.get_host}}{% url 'event_authorise' object.pk hmac %} + +The TEC Rig Information Gathering System diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html new file mode 100644 index 00000000..87880331 --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_request.html @@ -0,0 +1,32 @@ +{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% load widget_tweaks %} + +{% block title %}Request Authorisation{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} + +
+ {% include 'form_errors.html' %} + +
+ + +
+ {% render_field form.email type="email" class+="form-control" %} +
+
+ +
+
+ +
+
+
+ +
+
+{% endblock %} diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 87452f11..ecaf3ac1 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1042,3 +1042,33 @@ class ClientEventAuthorisationTest(TestCase): self.assertEqual(mail.outbox[0].to, ['authemail@function.test']) self.assertEqual(mail.outbox[1].to, [settings.AUTHORISATION_NOTIFICATION_ADDRESS]) + +class TECEventAuthorisationTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create( + name='Test TEC User', + email='teccie@functional.test', + is_superuser=True # lazily grant all permissions + ) + + def setUp(self): + venue = models.Venue.objects.create(name='Authorisation Test Venue') + client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') + organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False) + self.event = models.Event.objects.create( + name='Authorisation Test', + start_date=date.today(), + venue=venue, + person=client, + organisation=organisation, + ) + self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk}) + + def test_request_send(self): + self.client.force_login(self.profile) + response = self.client.post(self.url) + self.assertContains(response, 'This field is required.') + + response = self.client.post(self.url, {'email': 'client@functional.test'}) + self.assertEqual(response.status_code, 301) diff --git a/RIGS/urls.py b/RIGS/urls.py index 2273fd01..93a95e6d 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -115,6 +115,11 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), name='event_history', kwargs={'model': models.Event}), + url(r'^event/(?P\d+)/auth/$', + permission_required_with_403('RIGS.change_event')( + rigboard.EventAuthorisationRequest.as_view() + ), + name='event_authorise_request'), url(r'^event/(?P\d+)/(?P[-:\w]+)/$', rigboard.EventAuthorise.as_view(), name='event_authorise'), From 3fa9795cde7f22fbcd6faaabfe65488defe116c7 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 20:40:51 +0100 Subject: [PATCH 078/275] Change mic name to use the full name rather than the display name on RIGS --- RIGS/templates/RIGS/eventauthorisation_mic_success.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt index f5577ced..a15391a5 100644 --- a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt @@ -1,4 +1,4 @@ -Hi {{object.event.mic.name}}, +Hi {{object.event.mic.get_full_name}}, Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. From 306c11bb2f384d987398479a055f7499158a6d92 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 20:47:19 +0100 Subject: [PATCH 079/275] Add tooltip JavaScript --- RIGS/templates/RIGS/eventauthorisation_form.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index d989b01e..b4a75d72 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -1,5 +1,15 @@ {% extends 'base_client.html' %} {% load widget_tweaks %} +{% load static %} + +{% block js %} + + +{% endblock %} {% block title %} {% if event.is_rig %}N{{ event.pk|stringformat:"05d" }}{% else %}{{ event.pk }}{% endif %} | {{ event.name }} From 22119a3d085ddd00f3370c9380fe49b44edc5717 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 20:50:28 +0100 Subject: [PATCH 080/275] Add test for sending authorisation email to client --- RIGS/test_functional.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index ecaf3ac1..bda1ffc6 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1070,5 +1070,11 @@ class TECEventAuthorisationTest(TestCase): response = self.client.post(self.url) self.assertContains(response, 'This field is required.') + mail.outbox = [] + response = self.client.post(self.url, {'email': 'client@functional.test'}) self.assertEqual(response.status_code, 301) + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[1] + self.assertIn(email.to, 'client@functional.test') + self.assertIn(email.body, '/event/%d/'.format(self.event.pk)) From 5d17d642ec7c934e6e11fe9101d6c055e98e12ab Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 21:43:18 +0100 Subject: [PATCH 081/275] Update templates to include the new authorisation fields --- RIGS/templates/RIGS/event_detail.html | 31 ++++++++++++++++-- RIGS/templates/RIGS/event_form.html | 8 ----- RIGS/templates/RIGS/event_invoice.html | 15 +++++++-- RIGS/templates/RIGS/event_table.html | 9 ++++-- RIGS/templates/RIGS/invoice_detail.html | 43 +++++++++++++++++++++++-- RIGS/templates/RIGS/invoice_list.html | 8 +++-- RIGS/urls.py | 17 +++++----- 7 files changed, 102 insertions(+), 29 deletions(-) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 92d97e46..22f8aa4a 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -13,7 +13,7 @@
{% include 'RIGS/event_detail_buttons.html' %}
- + {% endif %} {% if object.is_rig %} {# only need contact details for a rig #} @@ -148,8 +148,33 @@ {% endif %} {% if event.is_rig %} -
PO
-
{{ object.purchase_order }}
+ {% if object.purchase_order %} +
PO
+
{{ object.purchase_order }}
+ {% endif %} + +
 
+ +
Authorised
+
{{ object.authorised|yesno:"Yes,No" }}
+ +
Authorised by
+
+ {% if object.authorised %} + {{ object.authorisation.name }} + ({{ object.authorisation.email }}) + {% endif %} +
+ +
Authorised at
+
{{ object.authorisation.last_edited_at }}
+ +
Authorised amount
+
+ {% if object.authorised %} + £ {{ object.authorisation.amount|floatformat:"2" }} + {% endif %} +
{% endif %}
diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index b2b4dcd7..71e91fe6 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -398,14 +398,6 @@ {% render_field form.collector class+="form-control" %}
-
- - -
- {% render_field form.purchase_order class+="form-control" %} -
-
diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index fcbe5e87..71136b35 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -54,7 +54,12 @@ N{{ object.pk|stringformat:"05d" }}
{{ object.get_status_display }} {{ object.start_date }} - {{ object.name }} + + {{ object.name }} + {% if object.is_rig and perms.RIGS.view_event and object.authorised %} + + {% endif %} + {% if object.organisation %} {{ object.organisation.name }} @@ -67,7 +72,11 @@ {% endif %} - {{ object.sum_total|floatformat:2 }} + + {{ object.sum_total|floatformat:2 }} +
+ {{ object.authorisation.po }} + {% if object.mic %} {{ object.mic.initials }}
@@ -92,4 +101,4 @@
{% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/RIGS/templates/RIGS/event_table.html b/RIGS/templates/RIGS/event_table.html index 949c710a..c84b6624 100644 --- a/RIGS/templates/RIGS/event_table.html +++ b/RIGS/templates/RIGS/event_table.html @@ -33,13 +33,18 @@

- {{ event.name }} + + {{ event.name }} + {% if event.venue %} at {{ event.venue }} {% endif %} {% if event.dry_hire %} Dry Hire {% endif %} + {% if event.is_rig and perms.RIGS.view_event and event.authorised %} + + {% endif %}

{% if event.is_rig and not event.cancelled %}
@@ -99,4 +104,4 @@ {% endfor %} -
\ No newline at end of file +
diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index cfcfcfe9..d45deeda 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -76,8 +76,45 @@
{{ object.checked_in_by.name }}
{% endif %} -
PO
-
{{ object.event.purchase_order }}
+ {% if object.event.purchase_order %} +
PO
+
{{ object.event.purchase_order }}
+ {% endif %} + +
 
+ +
Authorised
+
{{ object.event.authorised|yesno:"Yes,No" }}
+ +
Authorised by
+
+ {% if object.event.authorised %} + {{ object.event.authorisation.name }} + ({{ object.event.authorisation.email }}) + {% endif %} +
+ + {% if object.event.organisation.union_account %} + {# internal #} +
Uni ID
+
{{ object.event.authorisation.uni_id }}
+ +
Account code
+
{{ object.event.authorisation.account_code }}
+ {% else %} +
PO
+
{{ object.event.authorisation.po }}
+ {% endif %} + +
Authorised at
+
{{ object.event.authorisation.last_edited_at }}
+ +
Authorised amount
+
+ {% if object.event.authorised %} + £ {{ object.event.authorisation.amount|floatformat:"2" }} + {% endif %} +
@@ -139,4 +176,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 2f34b6c9..62012238 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -59,7 +59,11 @@ {{ object.event.start_date }} {{ object.invoice_date }} - {{ object.balance|floatformat:2 }} + + {{ object.balance|floatformat:2 }} +
+ {{ object.event.authorisation.po }} + @@ -76,4 +80,4 @@ {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/RIGS/urls.py b/RIGS/urls.py index 93a95e6d..2b4c716d 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -115,14 +115,6 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.view_event')(versioning.VersionHistory.as_view()), name='event_history', kwargs={'model': models.Event}), - url(r'^event/(?P\d+)/auth/$', - permission_required_with_403('RIGS.change_event')( - rigboard.EventAuthorisationRequest.as_view() - ), - name='event_authorise_request'), - url(r'^event/(?P\d+)/(?P[-:\w]+)/$', rigboard.EventAuthorise.as_view(), - name='event_authorise'), - # Finance url(r'^invoice/$', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), @@ -157,6 +149,15 @@ urlpatterns = patterns('', permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()), name='payment_delete'), + # Client event authorisation + url(r'^event/(?P\d+)/auth/$', + permission_required_with_403('RIGS.change_event')( + rigboard.EventAuthorisationRequest.as_view() + ), + name='event_authorise_request'), + url(r'^event/(?P\d+)/(?P[-:\w]+)/$', rigboard.EventAuthorise.as_view(), + name='event_authorise'), + # User editing url(r'^user/$', login_required(views.ProfileDetail.as_view()), name='profile_detail'), url(r'^user/(?P\d+)/$', From 391d9ef28f8ec9b2bd0855a0fa788431b16dcf12 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 22:45:27 +0100 Subject: [PATCH 082/275] Update PDF templates and enable sending of PDF via email. PDFs now state QUOTE, INVOICE or RECEIPT. Single copy and all but INVOICE includes terms of hire. --- RIGS/rigboard.py | 37 ++- RIGS/signals.py | 54 +++- RIGS/templates/RIGS/event_print.xml | 19 +- RIGS/templates/RIGS/event_print_page.xml | 239 ++++++++---------- .../RIGS/eventauthorisation_mic_success.txt | 2 +- 5 files changed, 186 insertions(+), 165 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index f4ee7bd0..1c6d6b5e 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -145,33 +145,26 @@ class EventPrint(generic.View): def get(self, request, pk): object = get_object_or_404(models.Event, pk=pk) template = get_template('RIGS/event_print.xml') - copies = ('TEC', 'Client') merger = PdfFileMerger() - for copy in copies: - context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this - 'object': object, - 'fonts': { - 'opensans': { - 'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF', - 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', - } - }, - 'copy': copy, - 'current_user': request.user, - }) + context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this + 'object': object, + 'fonts': { + 'opensans': { + 'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF', + 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', + } + }, + 'quote': True, + 'current_user': request.user, + }) - # context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3 + rml = template.render(context) - rml = template.render(context) - buffer = StringIO.StringIO() - - buffer = rml2pdf.parseString(rml) - - merger.append(PdfFileReader(buffer)) - - buffer.close() + buffer = rml2pdf.parseString(rml) + merger.append(PdfFileReader(buffer)) + buffer.close() terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL) merger.append(StringIO.StringIO(terms.read())) diff --git a/RIGS/signals.py b/RIGS/signals.py index deac4ce7..42bb6504 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -1,33 +1,79 @@ -import reversion -from django.conf import settings +import cStringIO as StringIO +import re +import urllib2 +from io import BytesIO +import reversion +from PyPDF2 import PdfFileReader, PdfFileMerger +from django.conf import settings from django.core.mail import EmailMessage from django.template.loader import get_template +from z3c.rml import rml2pdf from RIGS import models def send_eventauthorisation_success_email(instance): + # Generate PDF first to prevent context conflicts + context = { + 'object': instance.event, + 'fonts': { + 'opensans': { + 'regular': 'RIGS/static/fonts/OPENSANS-REGULAR.TTF', + 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', + } + }, + 'receipt': True, + 'current_user': False, + } + + template = get_template('RIGS/event_print.xml') + merger = PdfFileMerger() + + rml = template.render(context) + + buffer = rml2pdf.parseString(rml) + merger.append(PdfFileReader(buffer)) + buffer.close() + + terms = urllib2.urlopen(settings.TERMS_OF_HIRE_URL) + merger.append(StringIO.StringIO(terms.read())) + + merged = BytesIO() + merger.write(merged) + + # Produce email content context = { 'object': instance, } + + subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name) + client_email = EmailMessage( - "N%05d | %s - Event Authorised".format(instance.event.pk, instance.event.name), + subject, get_template("RIGS/eventauthorisation_client_success.txt").render(context), to=[instance.email] ) + escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name) + + client_email.attach('N%05d - %s - RECEIPT.pdf' % (instance.event.pk, escapedEventName), + merged.getvalue(), + 'application/pdf' + ) + if instance.event.mic: mic_email_address = instance.event.mic.email else: mic_email_address = settings.AUTHORISATION_NOTIFICATION_ADDRESS mic_email = EmailMessage( - "N%05d | %s - Event Authorised".format(instance.event.pk, instance.event.name), + subject, get_template("RIGS/eventauthorisation_mic_success.txt").render(context), to=[mic_email_address] ) + # Now we have both emails successfully generated, send them out client_email.send() mic_email.send() diff --git a/RIGS/templates/RIGS/event_print.xml b/RIGS/templates/RIGS/event_print.xml index 6d693117..ad441a21 100644 --- a/RIGS/templates/RIGS/event_print.xml +++ b/RIGS/templates/RIGS/event_print.xml @@ -22,21 +22,21 @@ - + - - + + - + @@ -100,10 +100,11 @@ - {% if not invoice %}[{{ copy }} Copy]{% endif %} [Page of ] - [Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + + [Paperwork generated{% if current_user %}by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + @@ -118,7 +119,9 @@ {% if not invoice %}[{{ copy }} Copy]{% endif %} [Page of ] - [Paperwork generated by {{current_user.name}} | {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + + [Paperwork generated{% if current_user %}by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + @@ -128,4 +131,4 @@ {% include "RIGS/event_print_page.xml" %} - \ No newline at end of file + diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index a7049459..c0a2afa1 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -1,59 +1,71 @@ -{% if invoice %} - + - {% endif %} +

N{{ object.pk|stringformat:"05d" }}: '{{ object.name }}'

-

N{{ object.pk|stringformat:"05d" }}: '{{ object.name }}'

- - -{{object.start_date|date:"D jS N Y"}} - - - - - {{ object.description|default_if_none:""|linebreaksbr }} + + {{object.start_date|date:"D jS N Y"}} - - -{% if invoice %} + + + {{ object.description|default_if_none:""|linebreaksbr }} + + - INVOICE - - - - Invoice Number - - {{ invoice.pk|stringformat:"05d" }} - - - - Invoice Date - - {{ invoice.invoice_date|date:"d/m/Y" }} - - - - PO Number - - {{ object.purchase_order|default_if_none:"" }} - - + {% if invoice %} + INVOICE + + + + Invoice Number + + {{ invoice.pk|stringformat:"05d" }} + + + + Invoice Date + + {{ invoice.invoice_date|date:"d/m/Y" }} + + + {% if object.purchase_order %} + + PO Number + + {{ object.purchase_order|default_if_none:"" }} + + + {% endif %} + - + {% elif quote %} + + QUOTE + + + + Quote Date + + {% now "d/m/Y" %} + + + + + {% elif receipt %} + + RECEIPT + + {% endif %}
- -{% endif %} - @@ -205,17 +217,7 @@ £ {{ object.vat|floatformat:2 }} - - - - {% if invoice %} - VAT Registration Number: 170734807 - {% else %} - This contract is not an invoice. - {% endif %} - - - + Total @@ -229,90 +231,67 @@ -{% if not invoice %} - + + {% if not invoice %} + + + Bookings will + not + be confirmed until the event is authorised online. + + + + + 24 Hour Emergency Contacts: 07825 065681 and 07825 065678 + + {% else %} + + + VAT Registration Number: 170734807 + + + {% endif %} + + + - Bookings will - not - be confirmed until payment is received and the contract is signed. + {% if object.authorised %} + + Event authorised online by {{ object.authorisation.name }} ({{ object.authorisation.email }}) at + {{ object.authorisation.last_edited_at }}. + + {% if object.organisation.union_account %} + + + University ID + Account Code + Authorised Amount + + + {{ object.authorisation.uni_id }} + {{ object.authorisation.account_code }} + £ {{ object.authorisation.amount|floatformat:2 }} + + + {% else %} + + + Purchase Order + Authorised Amount + + + {{ object.authorisation.po }} + £ {{ object.authorisation.amount|floatformat:2 }} + + + {% endif %} + {% endif %} - - 24 Hour Emergency Contacts: 07825 065681 and 07825 065678 - - - - + - - To be signed on booking: - - {% if object.organisation.union_account %} - - - I agree that am authorised to sign this invoice. I agree that I am the President/Treasurer of the hirer, or - that I have provided written permission from either the President or Treasurer of the hirer stating that I can - sign for this invoice. - - - - - I have read, understood and fully accepted the current conditions of hire. I agree to return any dry hire - items to TEC PA & Lighting in the same condition at the end of the hire period. - - - - - - Conditions of hire attached and available on the TEC PA & Lighting website. E&OE - - - - - Please return this form directly to TEC PA & Lighting and not the Students' Union Finance Department. - - - - - Account Code - - - - - - {% else %} - - - I, the hirer, have read, understand and fully accept the current conditions of hire. This document forms a - binding contract between TEC PA & Lighting and the hirer, the aforementioned conditions of hire forming - an integral part of it. - - - - - - Conditions of hire attached and available on the TEC PA & Lighting website. E&OE - - - - {% include "RIGS/event_print_signature.xml" %} - - - To be signed on the day of the event/hire: - - - - I, the hirer, have received the goods/services as requested and in good order. I agree to return any dry hire - items to TEC PA & Lighting in a similar condition at the end of the hire period. - - - {% endif %} - - {% include "RIGS/event_print_signature.xml" %} - - {% endif %} - \ No newline at end of file + diff --git a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt index a15391a5..98e1cdb8 100644 --- a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt @@ -1,4 +1,4 @@ -Hi {{object.event.mic.get_full_name}}, +Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}}, Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. From 067e03b7572c6b932591c83ac03320025220fa50 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 23:14:09 +0100 Subject: [PATCH 083/275] Remove Event.purchase_order in favour of a simple EventAuthorisation object. --- RIGS/forms.py | 2 +- .../migrations/0028_migrate_purchase_order.py | 47 +++++++++++++++++++ RIGS/models.py | 1 - RIGS/rigboard.py | 1 - RIGS/templates/RIGS/event_detail.html | 5 -- RIGS/templates/RIGS/event_print_page.xml | 8 ---- RIGS/templates/RIGS/invoice_detail.html | 5 -- RIGS/test_functional.py | 6 +-- 8 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 RIGS/migrations/0028_migrate_purchase_order.py diff --git a/RIGS/forms.py b/RIGS/forms.py index 14df5772..77e504eb 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -140,7 +140,7 @@ class EventForm(forms.ModelForm): fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date', 'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic', 'person', 'organisation', 'dry_hire', 'checked_in_by', 'status', - 'collector', 'purchase_order'] + 'collector'] class BaseClientEventAuthorisationForm(forms.ModelForm): diff --git a/RIGS/migrations/0028_migrate_purchase_order.py b/RIGS/migrations/0028_migrate_purchase_order.py new file mode 100644 index 00000000..05275d03 --- /dev/null +++ b/RIGS/migrations/0028_migrate_purchase_order.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.db.models import F, Sum, DecimalField + + +def POs_forward(apps, schema_editor): + VatRate = apps.get_model('RIGS', 'VatRate') + Event = apps.get_model('RIGS', 'Event') + EventItem = apps.get_model('RIGS', 'EventItem') + EventAuthorisation = apps.get_model('RIGS', 'EventAuthorisation') + db_alias = schema_editor.connection.alias + for event in Event.objects.using(db_alias).filter(purchase_order__isnull=False): + sum_total = EventItem.objects.filter(event=event).aggregate( + sum_total=Sum(models.F('cost') * F('quantity'), + output_field=DecimalField( + max_digits=10, + decimal_places=2) + ) + )['sum_total'] + + vat = VatRate.objects.using(db_alias).filter(start_at__lte=event.start_date).latest() + total = sum_total + sum_total * vat.rate + + EventAuthorisation.objects.using(db_alias).create(event=event, name='LEGACY', + email='treasurer@nottinghamtec.co.uk', + amount=total) + + +def POs_reverse(apps, schema_editor): + EventAuthorisation = apps.get_model('RIGS', 'EventAuthorisation') + db_alias = schema_editor.connection.alias + for auth in EventAuthorisation.objects.using(db_alias).filter(po__isnull=False): + auth.event.purchase_order = auth.po + auth.delete() + + +class Migration(migrations.Migration): + dependencies = [ + ('RIGS', '0027_eventauthorisation_event_singular'), + ] + + operations = [ + migrations.RunPython(POs_forward, POs_reverse), + migrations.RemoveField(model_name='event', name='purchase_order') + ] diff --git a/RIGS/models.py b/RIGS/models.py index d1f20446..9b73a1ab 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -332,7 +332,6 @@ class Event(models.Model, RevisionMixin): # Monies payment_method = models.CharField(max_length=255, blank=True, null=True) payment_received = models.CharField(max_length=255, blank=True, null=True) - purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO') collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') # Calculated values diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 1c6d6b5e..f6565b3f 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -125,7 +125,6 @@ class EventDuplicate(EventUpdate): old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) new = copy.copy(old) # Make a copy of the object in memory new.based_on = old # Make the new event based on the old event - new.purchase_order = None if self.request.method in ( 'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 22f8aa4a..3493a421 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -148,11 +148,6 @@ {% endif %} {% if event.is_rig %} - {% if object.purchase_order %} -
PO
-
{{ object.purchase_order }}
- {% endif %} -
 
Authorised
diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index c0a2afa1..56f75e68 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -34,14 +34,6 @@ {{ invoice.invoice_date|date:"d/m/Y" }} - {% if object.purchase_order %} - - PO Number - - {{ object.purchase_order|default_if_none:"" }} - - - {% endif %}
{% elif quote %} diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index d45deeda..d8da9182 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -76,11 +76,6 @@
{{ object.checked_in_by.name }}
{% endif %} - {% if object.event.purchase_order %} -
PO
-
{{ object.event.purchase_order }}
- {% endif %} -
 
Authorised
diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index bda1ffc6..f12bb7c7 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -443,7 +443,7 @@ class EventTest(LiveServerTestCase): def testEventDuplicate(self): testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), - description="start future no end", purchase_order="TESTPO") + description="start future no end") item1 = models.EventItem( event=testEvent, @@ -509,8 +509,6 @@ class EventTest(LiveServerTestCase): infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertIn("N0000%d" % testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) - # Check the PO hasn't carried through - self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) # Go back to the old event @@ -518,8 +516,6 @@ class EventTest(LiveServerTestCase): infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') self.assertNotIn("N0000%d" % testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) - # Check the PO remains on the old event - self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above From 5be3842aea8f2e01da7d31a8cf3aa9a7b25dd186 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 23:28:00 +0100 Subject: [PATCH 084/275] Fix test client login because we are still using Django 1.8 --- RIGS/test_functional.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index f12bb7c7..5d3bf245 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1042,11 +1042,15 @@ class ClientEventAuthorisationTest(TestCase): class TECEventAuthorisationTest(TestCase): @classmethod def setUpTestData(cls): - cls.profile = models.Profile.objects.create( - name='Test TEC User', + cls.profile = models.Profile.objects.get_or_create( + first_name='Test', + last_name='TEC User', + username='eventauthtest', email='teccie@functional.test', is_superuser=True # lazily grant all permissions - ) + )[0] + cls.profile.set_password('eventauthtest123') + cls.profile.save() def setUp(self): venue = models.Venue.objects.create(name='Authorisation Test Venue') @@ -1062,15 +1066,15 @@ class TECEventAuthorisationTest(TestCase): self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk}) def test_request_send(self): - self.client.force_login(self.profile) + self.assertTrue(self.client.login(username=self.profile.username, password='eventauthtest123')) response = self.client.post(self.url) self.assertContains(response, 'This field is required.') mail.outbox = [] response = self.client.post(self.url, {'email': 'client@functional.test'}) - self.assertEqual(response.status_code, 301) + self.assertEqual(response.status_code, 302) self.assertEqual(len(mail.outbox), 1) - email = mail.outbox[1] - self.assertIn(email.to, 'client@functional.test') - self.assertIn(email.body, '/event/%d/'.format(self.event.pk)) + email = mail.outbox[0] + self.assertIn('client@functional.test', email.to) + self.assertIn('/event/%d/' % (self.event.pk), email.body) From 82b6f1cbf81b91ce8a1446ac7ae4f0b9d1eb1a90 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Mon, 10 Apr 2017 23:43:42 +0100 Subject: [PATCH 085/275] Fix string formatting issue. I used python 3 syntax, we aren't yet using python 3... --- RIGS/rigboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index f6565b3f..61ebc3a7 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -317,7 +317,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix } msg = EmailMessage( - "N%05d | %s - Event Authorisation Request".format(self.object.pk, self.object.name), + "N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name), get_template("RIGS/eventauthorisation_client_request.txt").render(context), to=[email], ) From 6e78f16c33f06bc2375061b56e592dfb41c00056 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 11:45:08 +0100 Subject: [PATCH 086/275] Add changes suggested by DT --- RIGS/forms.py | 2 +- RIGS/rigboard.py | 13 ++++++++----- RIGS/signals.py | 3 ++- RIGS/templates/RIGS/eventauthorisation_request.html | 12 +++++++++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/RIGS/forms.py b/RIGS/forms.py index 77e504eb..752b7fb8 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -149,7 +149,7 @@ class BaseClientEventAuthorisationForm(forms.ModelForm): def clean(self): if self.cleaned_data.get('amount') != self.instance.event.total: - self.add_error('amount', 'The amount authorised must equal the total for the event.') + self.add_error('amount', 'The amount authorised must equal the total for the event (inc VAT).') return super(BaseClientEventAuthorisationForm, self).clean() class Meta: diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 61ebc3a7..ecd15842 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -147,7 +147,7 @@ class EventPrint(generic.View): merger = PdfFileMerger() - context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this + context = RequestContext(request, { 'object': object, 'fonts': { 'opensans': { @@ -227,7 +227,8 @@ class EventAuthorise(generic.UpdateView): self.template_name = self.success_template messages.add_message(self.request, messages.SUCCESS, - 'Success! Your event has been authorised. You will also receive email confirmation.') + 'Success! Your event has been authorised. ' + + 'You will also receive email confirmation to %s.' % (self.object.email)) return self.render_to_response(self.get_context_data()) @property @@ -259,10 +260,11 @@ class EventAuthorise(generic.UpdateView): if self.get_object() is not None and self.get_object().pk is not None: if self.event.authorised: messages.add_message(self.request, messages.WARNING, - "This event has already been authorised. Please confirm you wish to reauthorise") + "This event has already been authorised. " + "Reauthorising is not necessary at this time.") else: messages.add_message(self.request, messages.WARNING, - "This event has already been authorised, but the amount has changed." + + "This event has already been authorised, but the amount has changed. " + "Please check the amount and reauthorise.") return super(EventAuthorise, self).get(request, *args, **kwargs) @@ -280,7 +282,7 @@ class EventAuthorise(generic.UpdateView): request.email = data['email'] except (signing.BadSignature, AssertionError, KeyError): raise SuspiciousOperation( - "The security integrity of that URL is invalid. Please contact your event MIC to obtain a new URL") + "This URL is invalid. Please ask your TEC contact for a new URL") return super(EventAuthorise, self).dispatch(request, *args, **kwargs) @@ -320,6 +322,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix "N%05d | %s - Event Authorisation Request" % (self.object.pk, self.object.name), get_template("RIGS/eventauthorisation_client_request.txt").render(context), to=[email], + reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS], ) msg.send() diff --git a/RIGS/signals.py b/RIGS/signals.py index 42bb6504..6aac2388 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -52,7 +52,8 @@ def send_eventauthorisation_success_email(instance): client_email = EmailMessage( subject, get_template("RIGS/eventauthorisation_client_success.txt").render(context), - to=[instance.email] + to=[instance.email], + reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS], ) escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name) diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html index 87880331..10a443bf 100644 --- a/RIGS/templates/RIGS/eventauthorisation_request.html +++ b/RIGS/templates/RIGS/eventauthorisation_request.html @@ -5,17 +5,23 @@ {% block content %}
-
+
+
+

Send authorisation request email.

+

Pressing send will email the address provided. Please triple check everything before continuing.

+
+
+
{% csrf_token %}
{% include 'form_errors.html' %}
- -
+
{% render_field form.email type="email" class+="form-control" %}
From a0440e158702b9028d69830fd4f016368c5f2dda Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 13:48:51 +0100 Subject: [PATCH 087/275] Add useful email addresses for reference. --- RIGS/templates/RIGS/eventauthorisation_request.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html index 10a443bf..9c31f1fe 100644 --- a/RIGS/templates/RIGS/eventauthorisation_request.html +++ b/RIGS/templates/RIGS/eventauthorisation_request.html @@ -10,6 +10,16 @@

Send authorisation request email.

Pressing send will email the address provided. Please triple check everything before continuing.

+ +
+
+
Person Email
+
{{ object.person.email }}
+ +
Organisation Email
+
{{ object.organisation.email }}
+
+
{% csrf_token %} From 36638e4df6c7fb43a85096f8d9f8373232da5bd2 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 14:02:58 +0100 Subject: [PATCH 088/275] Add some more disclaimers explaining things better to internal clients. --- .../RIGS/eventauthorisation_form.html | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index b4a75d72..0809b29a 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -47,6 +47,18 @@ {% csrf_token %} {% include 'form_errors.html' %}
+ {% if internal %} +
+

+ I agree that I am authorised to approve this event. I agree that I am the + President/Treasurer or account holder of the hirer, or that I + have the written permission of the + President/Treasurer or account holder of the hirer stating that + I can authorise this event. +

+
+ {% endif %} +
@@ -60,7 +72,7 @@ {% if internal %}
+ title="Your Student ID or Staff username as the person authorising the event.">
@@ -102,19 +114,19 @@
-
- -
+
From c0f48842426d8c0a4cd419cc8bcf137f20b8e244 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 14:10:00 +0100 Subject: [PATCH 089/275] Add missing PO field. Noticed in testing, that could have gone badly. --- RIGS/migrations/0028_migrate_purchase_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RIGS/migrations/0028_migrate_purchase_order.py b/RIGS/migrations/0028_migrate_purchase_order.py index 05275d03..8a268208 100644 --- a/RIGS/migrations/0028_migrate_purchase_order.py +++ b/RIGS/migrations/0028_migrate_purchase_order.py @@ -25,7 +25,8 @@ def POs_forward(apps, schema_editor): EventAuthorisation.objects.using(db_alias).create(event=event, name='LEGACY', email='treasurer@nottinghamtec.co.uk', - amount=total) + amount=total, + po=event.purchase_order) def POs_reverse(apps, schema_editor): From e12367bde71a694a782b297a56ec8192e54ce60c Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 14:32:06 +0100 Subject: [PATCH 090/275] Few tweaks to printed documents following a conversation with Marilyn from treasury --- RIGS/templates/RIGS/event_print_page.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 56f75e68..422c3c5e 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -51,7 +51,7 @@ {% elif receipt %} - RECEIPT + CONFIRMATION {% endif %} @@ -199,7 +199,7 @@ - {% if not invoice %} + {% if quote %} The full hire fee is payable at least 10 days before the event. @@ -225,7 +225,7 @@ - {% if not invoice %} + {% if quote %} Bookings will From 430862b24d14800805ca50d3a2059213cdc48528 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 15:52:38 +0100 Subject: [PATCH 091/275] Add tracking of who sent the link --- .../0029_eventauthorisation_sent_by.py | 21 +++++++++++++++++++ RIGS/models.py | 1 + RIGS/rigboard.py | 7 +++++-- RIGS/templates/RIGS/event_detail.html | 3 +++ RIGS/templates/RIGS/invoice_detail.html | 3 +++ RIGS/test_functional.py | 12 +++++++++-- 6 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 RIGS/migrations/0029_eventauthorisation_sent_by.py diff --git a/RIGS/migrations/0029_eventauthorisation_sent_by.py b/RIGS/migrations/0029_eventauthorisation_sent_by.py new file mode 100644 index 00000000..80c86299 --- /dev/null +++ b/RIGS/migrations/0029_eventauthorisation_sent_by.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0028_migrate_purchase_order'), + ] + + operations = [ + migrations.AddField( + model_name='eventauthorisation', + name='sent_by', + field=models.ForeignKey(default=1, to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 9b73a1ab..31496ed0 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -513,6 +513,7 @@ class EventAuthorisation(models.Model, RevisionMixin): account_code = models.CharField(max_length=50, blank=True, null=True) po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order") amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") + sent_by = models.ForeignKey('RIGS.Profile') @python_2_unicode_compatible diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index ecd15842..9213e900 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -272,6 +272,7 @@ class EventAuthorise(generic.UpdateView): form = super(EventAuthorise, self).get_form(**kwargs) form.instance.event = self.event form.instance.email = self.request.email + form.instance.sent_by = self.request.sent_by return form def dispatch(self, request, *args, **kwargs): @@ -280,7 +281,8 @@ class EventAuthorise(generic.UpdateView): data = signing.loads(kwargs.get('hmac')) assert int(kwargs.get('pk')) == int(data.get('pk')) request.email = data['email'] - except (signing.BadSignature, AssertionError, KeyError): + request.sent_by = models.Profile.objects.get(pk=data['sent_by']) + except (signing.BadSignature, AssertionError, KeyError, models.Profile.DoesNotExist): raise SuspiciousOperation( "This URL is invalid. Please ask your TEC contact for a new URL") return super(EventAuthorise, self).dispatch(request, *args, **kwargs) @@ -314,7 +316,8 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix 'request': self.request, 'hmac': signing.dumps({ 'pk': self.object.pk, - 'email': email + 'email': email, + 'sent_by': self.request.user.pk, }), } diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 3493a421..4a803e1a 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -170,6 +170,9 @@ £ {{ object.authorisation.amount|floatformat:"2" }} {% endif %} + +
Authorsation request sent by
+
{{ object.authorisation.sent_by }}
{% endif %}
diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index d8da9182..a55927f8 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -110,6 +110,9 @@ £ {{ object.event.authorisation.amount|floatformat:"2" }} {% endif %} + +
Authorsation request sent by
+
{{ object.authorisation.sent_by }}
diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 5d3bf245..c854a91b 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -952,6 +952,13 @@ class ClientEventAuthorisationTest(TestCase): } def setUp(self): + self.profile = models.Profile.objects.get_or_create( + first_name='Test', + last_name='TEC User', + username='eventauthtest', + email='teccie@functional.test', + is_superuser=True # lazily grant all permissions + )[0] venue = models.Venue.objects.create(name='Authorisation Test Venue') client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False) @@ -962,7 +969,8 @@ class ClientEventAuthorisationTest(TestCase): person=client, organisation=organisation, ) - self.hmac = signing.dumps({'pk': self.event.pk, 'email': 'authemail@function.test'}) + self.hmac = signing.dumps({'pk': self.event.pk, 'email': 'authemail@function.test', + 'sent_by': self.profile.pk}) self.url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': self.hmac}) def test_requires_valid_hmac(self): @@ -1015,7 +1023,7 @@ class ClientEventAuthorisationTest(TestCase): def test_duplicate_warning(self): auth = models.EventAuthorisation.objects.create(event=self.event, name='Test ABC', email='dupe@functional.test', - po='ABC12345', amount=self.event.total) + po='ABC12345', amount=self.event.total, sent_by=self.profile) response = self.client.get(self.url) self.assertContains(response, 'This event has already been authorised.') From 56d4e438b6ad98818975c6e67389e501bcc56711 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 11 Apr 2017 16:22:54 +0100 Subject: [PATCH 092/275] Fix test missing EventAuthorisation.sent_by --- RIGS/test_models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/RIGS/test_models.py b/RIGS/test_models.py index 08f1dd69..1f36be43 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -333,6 +333,13 @@ class EventPricingTestCase(TestCase): class EventAuthorisationTestCase(TestCase): def setUp(self): + self.profile = models.Profile.objects.get_or_create( + first_name='Test', + last_name='TEC User', + username='eventauthtest', + email='teccie@functional.test', + is_superuser=True # lazily grant all permissions + )[0] self.person = models.Person.objects.create(name='Authorisation Test Person') self.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation') self.event = models.Event.objects.create(name="AuthorisationTestCase", person=self.person, @@ -343,7 +350,8 @@ class EventAuthorisationTestCase(TestCase): def test_event_property(self): auth1 = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", - name="Test Auth 1", amount=self.event.total - 1) + name="Test Auth 1", amount=self.event.total - 1, + sent_by=self.profile) self.assertFalse(self.event.authorised) auth1.amount = self.event.total auth1.save() @@ -352,5 +360,5 @@ class EventAuthorisationTestCase(TestCase): def test_last_edited(self): with reversion.create_revision(): auth = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", - name="Test Auth", amount=self.event.total) + name="Test Auth", amount=self.event.total, sent_by=self.profile) self.assertIsNotNone(auth.last_edited_at) From d9076a4f5f5bbe280821facccf953b226227c149 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 19 Apr 2017 15:27:12 +0100 Subject: [PATCH 093/275] Quantize event totals to prevent issues with mixed precision on client authorisation form. --- RIGS/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index 31496ed0..4a5f6988 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -366,7 +366,7 @@ class Event(models.Model, RevisionMixin): @property def vat(self): - return self.sum_total * self.vat_rate.rate + return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01')) """ Inc VAT @@ -374,7 +374,7 @@ class Event(models.Model, RevisionMixin): @property def total(self): - return self.sum_total + self.vat + return Decimal(self.sum_total + self.vat).quantize(Decimal('.01')) @property def cancelled(self): From 331dab20f74f4b827a3dc50d89ed1bd36f9c67cc Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 19 Apr 2017 18:14:36 +0100 Subject: [PATCH 094/275] Add basic tracking of when an event authorisation request was sent. Designed and requested by Ross because he can't remember if he's push a button... --- RIGS/migrations/0030_auth_request_sending.py | 30 +++++++++ RIGS/models.py | 5 ++ RIGS/rigboard.py | 7 ++- RIGS/templates/RIGS/event_detail.html | 61 +++++++++++++------ RIGS/templates/RIGS/event_detail_buttons.html | 14 ++++- RIGS/test_functional.py | 6 ++ 6 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 RIGS/migrations/0030_auth_request_sending.py diff --git a/RIGS/migrations/0030_auth_request_sending.py b/RIGS/migrations/0030_auth_request_sending.py new file mode 100644 index 00000000..7243e9a7 --- /dev/null +++ b/RIGS/migrations/0030_auth_request_sending.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0029_eventauthorisation_sent_by'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='auth_request_at', + field=models.DateTimeField(null=True, blank=True), + ), + migrations.AddField( + model_name='event', + name='auth_request_by', + field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True), + ), + migrations.AddField( + model_name='event', + name='auth_request_to', + field=models.EmailField(max_length=254, null=True, blank=True), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index 4a5f6988..89ec852b 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -334,6 +334,11 @@ class Event(models.Model, RevisionMixin): payment_received = models.CharField(max_length=255, blank=True, null=True) collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') + # Authorisation request details + auth_request_by = models.ForeignKey('Profile', null=True, blank=True) + auth_request_at = models.DateTimeField(null=True, blank=True) + auth_request_to = models.EmailField(null=True, blank=True) + # Calculated values """ EX Vat diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 9213e900..06ab03d1 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -300,7 +300,7 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix def get_success_url(self): if self.request.is_ajax(): url = reverse_lazy('closemodal') - messages.info(self.request, "$('.event-authorise-request').addClass('btn-success')") + messages.info(self.request, "location.reload()") else: url = reverse_lazy('event_detail', kwargs={ 'pk': self.object.pk, @@ -310,6 +310,11 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix def form_valid(self, form): email = form.cleaned_data['email'] + event = self.object + event.auth_request_by = self.request.user + event.auth_request_at = datetime.datetime.now() + event.auth_request_to = email + event.save() context = { 'object': self.object, diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 4a803e1a..051aec4f 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -70,6 +70,39 @@
{% endif %} + + {% if event.is_rig %} +
+
Client Authorisation
+
+
+
Authorised
+
{{ object.authorised|yesno:"Yes,No" }}
+ +
Authorised by
+
+ {% if object.authorisation %} + {{ object.authorisation.name }} + ({{ object.authorisation.email }}) + {% endif %} +
+ +
Authorised at
+
{{ object.authorisation.last_edited_at }}
+ +
Authorised amount
+
+ {% if object.authorisation %} + £ {{ object.authorisation.amount|floatformat:"2" }} + {% endif %} +
+ +
Requested by
+
{{ object.authorisation.sent_by }}
+
+
+
+ {% endif %}
{% endif %}
@@ -150,29 +183,17 @@ {% if event.is_rig %}
 
-
Authorised
-
{{ object.authorised|yesno:"Yes,No" }}
+
Authorisation Request
+
{{ object.auth_request_to|yesno:"Yes,No" }}
-
Authorised by
-
- {% if object.authorised %} - {{ object.authorisation.name }} - ({{ object.authorisation.email }}) - {% endif %} -
+
By
+
{{ object.auth_request_by }}
-
Authorised at
-
{{ object.authorisation.last_edited_at }}
+
At
+
{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}
-
Authorised amount
-
- {% if object.authorised %} - £ {{ object.authorisation.amount|floatformat:"2" }} - {% endif %} -
- -
Authorsation request sent by
-
{{ object.authorisation.sent_by }}
+
To
+
{{ object.auth_request_to }}
{% endif %}
diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html index 7f91db83..0c1adae3 100644 --- a/RIGS/templates/RIGS/event_detail_buttons.html +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -11,10 +11,18 @@ class="glyphicon glyphicon-duplicate"> {% if event.is_rig %} - + - Authorisation Request + {% if perms.RIGS.add_invoice %} +{% endblock %} diff --git a/RIGS/templates/RIGS/eventauthorisation_client_request.txt b/RIGS/templates/RIGS/eventauthorisation_client_request.txt index 959da694..7b1297b1 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_request.txt +++ b/RIGS/templates/RIGS/eventauthorisation_client_request.txt @@ -1,12 +1,16 @@ -Hi there, +Hi {{ to_name|default:"there" }}, -{{request.user.get_full_name}} has requested that you authorise N{{object.pk|stringformat:"05d"}} | {{object.name}}. +{{ request.user.get_full_name }} has requested that you authorise N{{ object.pk|stringformat:"05d" }}| {{ object.name }}{% if not to_name %} on behalf of {{ object.person.name }}{% endif %}. -Please find the link below to complete the event booking process. -{% if object.event.organisation and object.event.organisation.union_account %}{# internal #} -Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward this email on. -{% endif %} + Please find the link below to complete the event booking process. + {% if object.event.organisation and object.event.organisation.union_account %}{# internal #} + Remember that only Presidents or Treasurers are allowed to sign off payments. You may need to forward + this + email on. + {% endif %} -{{request.scheme}}://{{request.get_host}}{% url 'event_authorise' object.pk hmac %} +{{ request.scheme }}://{{ request.get_host }}{% url 'event_authorise' object.pk hmac %} -The TEC Rig Information Gathering System +Please note you event will not be booked until you complete this form. + +TEC PA & Lighting diff --git a/RIGS/urls.py b/RIGS/urls.py index 2b4c716d..9c9f1628 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -155,6 +155,11 @@ urlpatterns = patterns('', rigboard.EventAuthorisationRequest.as_view() ), name='event_authorise_request'), + url(r'^event/(?P\d+)/auth/preview/$', + permission_required_with_403('RIGS.change_event')( + rigboard.EventAuthoriseRequestEmailPreview.as_view() + ), + name='event_authorise_preview'), url(r'^event/(?P\d+)/(?P[-:\w]+)/$', rigboard.EventAuthorise.as_view(), name='event_authorise'), diff --git a/requirements.txt b/requirements.txt index 995d2afd..4bfce216 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ gunicorn==19.3.0 icalendar==3.9.0 lxml==3.4.4 Pillow==2.8.1 +premailer==3.0.1 psycopg2==2.6 Pygments==2.0.2 PyPDF2==1.24 diff --git a/templates/base_client_email.html b/templates/base_client_email.html new file mode 100644 index 00000000..a60bc257 --- /dev/null +++ b/templates/base_client_email.html @@ -0,0 +1,49 @@ +{% load static from staticfiles %} +{% load raven %} + + + + + + + + {% block css %} + {% endblock %} + + + + + {% block preload_js %} + {% endblock %} + + {% block extra-head %}{% endblock %} + + + +
+ + + + + +
+ + + +
+
+ +
+
+ {% block content %}{% endblock %} +
+
+ +{% block js %} +{% endblock %} + + From 1710c3f01f614ac725435baec0ad3c09a418b3f9 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 9 May 2017 18:43:27 +0100 Subject: [PATCH 096/275] Send HTML confirmation emails. Also tidy up the PDF and some of the source. --- RIGS/rigboard.py | 1 - RIGS/signals.py | 16 ++++++++++--- RIGS/templates/RIGS/event_print_page.xml | 2 +- .../eventauthorisation_client_success.html | 23 +++++++++++++++++++ .../eventauthorisation_client_success.txt | 6 ++--- 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 RIGS/templates/RIGS/eventauthorisation_client_success.html diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index db95b34f..49fea2cb 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -341,7 +341,6 @@ class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMix external_styles=css).transform() msg.attach_alternative(html, 'text/html') - msg.send() return super(EventAuthorisationRequest, self).form_valid(form) diff --git a/RIGS/signals.py b/RIGS/signals.py index 6aac2388..df5dc8fe 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -6,8 +6,10 @@ from io import BytesIO import reversion from PyPDF2 import PdfFileReader, PdfFileMerger from django.conf import settings -from django.core.mail import EmailMessage +from django.contrib.staticfiles.storage import staticfiles_storage +from django.core.mail import EmailMessage, EmailMultiAlternatives from django.template.loader import get_template +from premailer import Premailer from z3c.rml import rml2pdf from RIGS import models @@ -47,18 +49,26 @@ def send_eventauthorisation_success_email(instance): 'object': instance, } + if instance.email == instance.event.person.email: + context['to_name'] = instance.event.person.name + subject = "N%05d | %s - Event Authorised" % (instance.event.pk, instance.event.name) - client_email = EmailMessage( + client_email = EmailMultiAlternatives( subject, get_template("RIGS/eventauthorisation_client_success.txt").render(context), to=[instance.email], reply_to=[settings.AUTHORISATION_NOTIFICATION_ADDRESS], ) + css = staticfiles_storage.path('css/email.css') + html = Premailer(get_template("RIGS/eventauthorisation_client_success.html").render(context), + external_styles=css).transform() + client_email.attach_alternative(html, 'text/html') + escapedEventName = re.sub('[^a-zA-Z0-9 \n\.]', '', instance.event.name) - client_email.attach('N%05d - %s - RECEIPT.pdf' % (instance.event.pk, escapedEventName), + client_email.attach('N%05d - %s - CONFIRMATION.pdf' % (instance.event.pk, escapedEventName), merged.getvalue(), 'application/pdf' ) diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 422c3c5e..27317c57 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -193,7 +193,7 @@ - {% if not invoice %}VAT Registration Number: 170734807{% endif %} + {% if quote %}VAT Registration Number: 170734807{% endif %} Total (ex. VAT) £ {{ object.sum_total|floatformat:2 }} diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.html b/RIGS/templates/RIGS/eventauthorisation_client_success.html new file mode 100644 index 00000000..cccf8b4a --- /dev/null +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.html @@ -0,0 +1,23 @@ +{% extends 'base_client_email.html' %} + +{% block content %} +
+

Hi {{ to_name|default:"there" }},

+ +

+ Your event N{{ object.event.pk|stringformat:"05d" }} has been successfully authorised + for {{ object.amount }} + by {{ object.name }} as of {{ object.last_edited_at }}. +

+ +

+ {% if object.event.organisation and object.event.organisation.union_account %}{# internal #} + Your event is now fully booked and payment will be processed by the finance department automatically. + {% else %}{# external #} + Your event is now fully booked and our finance department will be contact to arrange payment. + {% endif %} +

+ +

TEC PA & Lighting

+
+{% endblock %} diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.txt b/RIGS/templates/RIGS/eventauthorisation_client_success.txt index 23e05786..693ba7ac 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.txt @@ -1,6 +1,6 @@ -Hi there, +Hi {{ to_name|default:"there" }}, -Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. +Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. {% if object.event.organisation and object.event.organisation.union_account %}{# internal #} Your event is now fully booked and payment will be processed by the finance department automatically. @@ -8,4 +8,4 @@ Your event is now fully booked and payment will be processed by the finance depa Your event is now fully booked and our finance department will be contact to arrange payment. {% endif %} -The TEC Rig Information Gathering System +TEC PA & Lighting From 602ba1d051f8b5098ed1d229db093df23396bfad Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 9 May 2017 18:48:03 +0100 Subject: [PATCH 097/275] Fix order of importing bootstrap variables. --- RIGS/static/css/email.css | 2 +- RIGS/static/scss/email.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/email.css b/RIGS/static/css/email.css index 006f196f..5a7e091e 100644 --- a/RIGS/static/css/email.css +++ b/RIGS/static/css/email.css @@ -1 +1 @@ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url(/static/fonts/bootstrap/glyphicons-halflings-regular.eot);src:url(/static/fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"),url(/static/fonts/bootstrap/glyphicons-halflings-regular.woff2) format("woff2"),url(/static/fonts/bootstrap/glyphicons-halflings-regular.woff) format("woff"),url(/static/fonts/bootstrap/glyphicons-halflings-regular.ttf) format("truetype"),url(/static/fonts/bootstrap/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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}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:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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;margin:-1px;padding:0;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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}mark,.mark{background-color:#fcf8e3;padding:.2em}.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,.initialism{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover,a.text-primary:focus{color:#286090}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#337ab7}a.bg-primary:hover,a.bg-primary:focus{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;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%}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;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;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{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.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-left:15px;padding-right: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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width: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: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio: 0){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,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;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-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{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}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.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-group-sm+.form-control-feedback,.form-group-sm .form-control+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.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:#333;background-color:#fff;border-color:#ccc}.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>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active:hover,.btn-default:active:focus,.btn-default:active.focus,.btn-default.active:hover,.btn-default.active:focus,.btn-default.active.focus,.open>.btn-default.dropdown-toggle:hover,.open>.btn-default.dropdown-toggle:focus,.open>.btn-default.dropdown-toggle.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary:active:hover,.btn-primary:active:focus,.btn-primary:active.focus,.btn-primary.active:hover,.btn-primary.active:focus,.btn-primary.active.focus,.open>.btn-primary.dropdown-toggle:hover,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle.focus{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active:hover,.btn-success:active:focus,.btn-success:active.focus,.btn-success.active:hover,.btn-success.active:focus,.btn-success.active.focus,.open>.btn-success.dropdown-toggle:hover,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus{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:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active:hover,.btn-info:active:focus,.btn-info:active.focus,.btn-info.active:hover,.btn-info.active:focus,.btn-info.active.focus,.open>.btn-info.dropdown-toggle:hover,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus{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:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active:hover,.btn-warning:active:focus,.btn-warning:active.focus,.btn-warning.active:hover,.btn-warning.active:focus,.btn-warning.active.focus,.open>.btn-warning.dropdown-toggle:hover,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus{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:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active:hover,.btn-danger:active:focus,.btn-danger:active.focus,.btn-danger.active:hover,.btn-danger.active:focus,.btn-danger.active.focus,.open>.btn-danger.dropdown-toggle:hover,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.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:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{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%}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;background-size:cover;margin-bottom:2em}.client-header img{height:8em;margin:2em} +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url(/static/fonts/glyphicons-halflings-regular.eot?1485195091);src:url(/static/fonts/glyphicons-halflings-regular.eot?&1485195091#iefix) format("embedded-opentype"),url(/static/fonts/glyphicons-halflings-regular.woff2?1485195091) format("woff2"),url(/static/fonts/glyphicons-halflings-regular.woff?1485195091) format("woff"),url(/static/fonts/glyphicons-halflings-regular.ttf?1485195091) format("truetype"),url(/static/fonts/glyphicons-halflings-regular.svg?1485195091#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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}body{font-family:"Open Sans","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:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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;margin:-1px;padding:0;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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}mark,.mark{background-color:#fcf8e3;padding:.2em}.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,.initialism{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover,a.text-primary:focus{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#428bca}a.bg-primary:hover,a.bg-primary:focus{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;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%}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;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;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{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.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-left:15px;padding-right: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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width: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: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.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::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio: 0){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,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;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-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{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}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{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}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.33}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.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-group-sm+.form-control-feedback,.form-group-sm .form-control+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.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:#333;background-color:#fff;border-color:#ccc}.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>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active:hover,.btn-default:active:focus,.btn-default:active.focus,.btn-default.active:hover,.btn-default.active:focus,.btn-default.active.focus,.open>.btn-default.dropdown-toggle:hover,.open>.btn-default.dropdown-toggle:focus,.open>.btn-default.dropdown-toggle.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus{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:focus,.btn-primary.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.btn-primary:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active:hover,.btn-primary:active:focus,.btn-primary:active.focus,.btn-primary.active:hover,.btn-primary.active:focus,.btn-primary.active.focus,.open>.btn-primary.dropdown-toggle:hover,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus{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:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active:hover,.btn-success:active:focus,.btn-success:active.focus,.btn-success.active:hover,.btn-success.active:focus,.btn-success.active.focus,.open>.btn-success.dropdown-toggle:hover,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus{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:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active:hover,.btn-info:active:focus,.btn-info:active.focus,.btn-info.active:hover,.btn-info.active:focus,.btn-info.active.focus,.open>.btn-info.dropdown-toggle:hover,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus{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:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active:hover,.btn-warning:active:focus,.btn-warning:active.focus,.btn-warning.active:hover,.btn-warning.active:focus,.btn-warning.active.focus,.open>.btn-warning.dropdown-toggle:hover,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus{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:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active:hover,.btn-danger:active:focus,.btn-danger:active.focus,.btn-danger.active:hover,.btn-danger.active:focus,.btn-danger.active.focus,.open>.btn-danger.dropdown-toggle:hover,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.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,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{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%}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;background-size:cover;margin-bottom:2em}.client-header img{height:8em;margin:2em} diff --git a/RIGS/static/scss/email.scss b/RIGS/static/scss/email.scss index 9b627911..7c0b5740 100644 --- a/RIGS/static/scss/email.scss +++ b/RIGS/static/scss/email.scss @@ -1,7 +1,7 @@ @import "bootstrap-compass"; // Core variables and mixins -@import "bootstrap/variables"; @import "bootstrap-variables"; +@import "bootstrap/variables"; @import "bootstrap/mixins"; // Reset and dependencies From 6b0593895339db9cbad7c91ef153b3f57e010c79 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 9 May 2017 19:21:35 +0100 Subject: [PATCH 098/275] Set authorisation button text to be more verbose --- RIGS/templates/RIGS/event_detail_buttons.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html index 0c1adae3..9ad4b189 100644 --- a/RIGS/templates/RIGS/event_detail_buttons.html +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -22,7 +22,17 @@ " href="{% url 'event_authorise_request' object.pk %}"> - + {% if perms.RIGS.add_invoice %} +
+
+

An error occured.

+

Your RIGS account must have an @nottinghamtec.co.uk email address before you can send emails to clients.

+
+
+
+{% endblock %} diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index bbee077c..96d074ca 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1054,7 +1054,7 @@ class TECEventAuthorisationTest(TestCase): first_name='Test', last_name='TEC User', username='eventauthtest', - email='teccie@functional.test', + email='teccie@nottinghamtec.co.uk', is_superuser=True # lazily grant all permissions )[0] cls.profile.set_password('eventauthtest123') @@ -1073,6 +1073,16 @@ class TECEventAuthorisationTest(TestCase): ) self.url = reverse('event_authorise_request', kwargs={'pk': self.event.pk}) + def test_email_check(self): + self.profile.email = 'teccie@someotherdomain.com' + self.profile.save() + + self.assertTrue(self.client.login(username=self.profile.username, password='eventauthtest123')) + + response = self.client.post(self.url) + + self.assertContains(response, 'must have an @nottinghamtec.co.uk email address') + def test_request_send(self): self.assertTrue(self.client.login(username=self.profile.username, password='eventauthtest123')) response = self.client.post(self.url) From e573088c5e9102b956bd4c6bd50380335c39dc4f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 17:51:55 +0100 Subject: [PATCH 100/275] Fix some issues caused by changes made over the last year --- RIGS/admin.py | 7 +++---- RIGS/management/commands/generateSampleData.py | 2 +- RIGS/models.py | 7 ------- RIGS/test_functional.py | 18 ++++++------------ RIGS/views.py | 5 ++--- 5 files changed, 12 insertions(+), 27 deletions(-) diff --git a/RIGS/admin.py b/RIGS/admin.py index 89779738..58b27e4c 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -12,11 +12,10 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count from django.forms import ModelForm +from reversion import revisions as reversion + # Register your models here. -admin.site.register(models.Person, VersionAdmin) -admin.site.register(models.Organisation, VersionAdmin) admin.site.register(models.VatRate, VersionAdmin) -admin.site.register(models.Venue, VersionAdmin) admin.site.register(models.Event, VersionAdmin) admin.site.register(models.EventItem, VersionAdmin) admin.site.register(models.Invoice) @@ -44,7 +43,7 @@ class ProfileAdmin(UserAdmin): add_form = forms.ProfileCreationForm -class AssociateAdmin(reversion.VersionAdmin): +class AssociateAdmin(VersionAdmin): list_display = ('id', 'name', 'number_of_events') search_fields = ['id', 'name'] list_display_links = ['id', 'name'] diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py index bf1ce7d2..59c39c97 100644 --- a/RIGS/management/commands/generateSampleData.py +++ b/RIGS/management/commands/generateSampleData.py @@ -1,7 +1,7 @@ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import Group, Permission from django.db import transaction -import reversion +from reversion import revisions as reversion import datetime import random diff --git a/RIGS/models.py b/RIGS/models.py index cbdc3e74..b245f543 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -12,18 +12,11 @@ from reversion import revisions as reversion import string import random -import string from collections import Counter from decimal import Decimal -import reversion -from django.conf import settings -from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse_lazy -from django.db import models -from django.utils.encoding import python_2_unicode_compatible -from django.utils.functional import cached_property # Create your models here. diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 2cd3af8d..3b867652 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -499,12 +499,8 @@ class EventTest(LiveServerTestCase): # Attempt to save save.click() -<<<<<<< HEAD self.assertNotIn("N%05d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) -======= - self.assertNotIn("N0000%d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible ->>>>>>> 9b7c84cf0890788a08a3dec71e00cbe78748b1fb # Check the new items are visible table = self.browser.find_element_by_id('item-table') # ID number is known, see above @@ -513,13 +509,12 @@ class EventTest(LiveServerTestCase): self.assertIn("Test Item 3", table.text) infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') -<<<<<<< HEAD + self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) -======= - self.assertIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO hasn't carried through self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) ->>>>>>> 9b7c84cf0890788a08a3dec71e00cbe78748b1fb + @@ -527,13 +522,12 @@ class EventTest(LiveServerTestCase): #Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') -<<<<<<< HEAD + self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) -======= - self.assertNotIn("N0000%d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO remains on the old event self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) ->>>>>>> 9b7c84cf0890788a08a3dec71e00cbe78748b1fb + # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above diff --git a/RIGS/views.py b/RIGS/views.py index c0186bed..b681c1bb 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -30,7 +30,7 @@ class Index(generic.TemplateView): def login(request, **kwargs): if request.user.is_authenticated(): - next = request.REQUEST.get('next', '/') + next = request.GET.get('next', '/') return HttpResponseRedirect(next) else: from django.contrib.auth.views import login @@ -44,9 +44,8 @@ def login(request, **kwargs): # check for it before logging the user in @csrf_exempt def login_embed(request, **kwargs): - print("Running LOGIN") if request.user.is_authenticated(): - next = request.REQUEST.get('next', '/') + next = request.GET.get('next', '/') return HttpResponseRedirect(next) else: from django.contrib.auth.views import login From 7f680dcffb425398f19ad0c41a11043f9e75634d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 18:06:17 +0100 Subject: [PATCH 101/275] Fix activity feed --- RIGS/versioning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 6d524fc0..d22f425c 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -158,7 +158,7 @@ def get_previous_version(version): thisId = version.object_id thisVersionId = version.pk - versions = reversion.get_for_object_reference(version.content_type.model_class(), thisId) + versions = reversion.revisions.get_for_object_reference(version.content_type.model_class(), thisId) try: previousVersions = versions.filter(revision_id__lt=version.revision_id).latest( @@ -207,7 +207,7 @@ class VersionHistory(generic.ListView): thisModel = self.kwargs['model'] # thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk']) - versions = reversion.get_for_object_reference(thisModel, self.kwargs['pk']) + versions = reversion.revisions.get_for_object_reference(thisModel, self.kwargs['pk']) return versions From 83028418fe0bfa99e6479392614c591fba1befde Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 18:30:17 +0100 Subject: [PATCH 102/275] Fix deprecation warnings for django 1.10 --- PyRIGS/settings.py | 5 ++--- RIGS/admin.py | 3 +-- RIGS/test_models.py | 6 +++--- RIGS/urls.py | 8 ++++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index fc593be4..70d41064 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -26,8 +26,6 @@ DEBUG = bool(int(os.environ.get('DEBUG'))) if os.environ.get('DEBUG') else True STAGING = bool(int(os.environ.get('STAGING'))) if os.environ.get('STAGING') else False -TEMPLATE_DEBUG = True - ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyrigs.herokuapp.com'] if STAGING: @@ -221,7 +219,8 @@ TEMPLATES = [ "django.template.context_processors.tz", "django.template.context_processors.request", "django.contrib.messages.context_processors.messages", - ] + ], + 'debug': DEBUG }, }, ] diff --git a/RIGS/admin.py b/RIGS/admin.py index 58b27e4c..49b8aa1e 100644 --- a/RIGS/admin.py +++ b/RIGS/admin.py @@ -95,8 +95,7 @@ class AssociateAdmin(VersionAdmin): 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, 'forms': forms } - return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context, - current_app=self.admin_site.name) + return TemplateResponse(request, 'RIGS/admin_associate_merge.html', context) @admin.register(models.Person) diff --git a/RIGS/test_models.py b/RIGS/test_models.py index b84fc7a6..1809c73b 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -275,14 +275,14 @@ class EventTestCase(TestCase): # basic checks manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'), manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'), - manager.create(name='TE IB4', start_date='2016-01-04', access_at='2016-01-03'), - manager.create(name='TE IB5', start_date='2016-01-04', meet_at='2016-01-02'), + manager.create(name='TE IB4', start_date='2016-01-04', access_at=self.create_datetime(2016, 01, 03, 00, 00)), + manager.create(name='TE IB5', start_date='2016-01-04', meet_at=self.create_datetime(2016, 01, 02, 00, 00)), # negative check manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'), ] - in_bounds = manager.events_in_bounds(datetime(2016, 1, 2), datetime(2016, 1, 3)) + in_bounds = manager.events_in_bounds(self.create_datetime(2016, 1, 2, 0, 0), self.create_datetime(2016, 1, 3, 0, 0)) self.assertIn(events[0], in_bounds) self.assertIn(events[1], in_bounds) self.assertIn(events[2], in_bounds) diff --git a/RIGS/urls.py b/RIGS/urls.py index f6b3fe14..30eb7051 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -9,16 +9,16 @@ from django.views.decorators.clickjacking import xframe_options_exempt from PyRIGS.decorators import permission_required_with_403 from PyRIGS.decorators import api_key_required -urlpatterns = patterns('', +urlpatterns = [ # Examples: # url(r'^$', 'PyRIGS.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url('^$', login_required(views.Index.as_view()), name='index'), url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), - url('^user/login/$', 'RIGS.views.login', name='login'), + url('^user/login/$', views.login, name='login'), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), - url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}), + url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}), # People url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), @@ -167,4 +167,4 @@ urlpatterns = patterns('', url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')), url(r'^bookings/$', RedirectView.as_view(permanent=True, pattern_name='rigboard')), url(r'^bookings/past/$', RedirectView.as_view(permanent=True, pattern_name='event_archive')), - ) + ] From 9b1cc965c7e6260c0f16d1040029d28b2945d202 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 18:41:17 +0100 Subject: [PATCH 103/275] Update to Django 1.10 --- PyRIGS/decorators.py | 12 ++++++------ PyRIGS/settings.py | 3 +++ PyRIGS/urls.py | 2 +- RIGS/urls.py | 2 +- requirements.txt | 3 ++- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 055901ca..1ae73e7d 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -26,15 +26,15 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): return view_func(request, *args, **kwargs) elif not request.user.is_authenticated(): if oembed_view is not None: - extra_context = {} - extra_context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) - extra_context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) - resp = render_to_response('login_redirect.html', extra_context, context_instance=RequestContext(request)) + context = RequestContext(request) + context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) + context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) + resp = render_to_response('login_redirect.html', context=context) return resp else: return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) else: - resp = render_to_response('403.html', context_instance=RequestContext(request)) + resp = render_to_response('403.html', context=RequestContext(request)) resp.status_code = 403 return resp _checklogin.__doc__ = view_func.__doc__ @@ -62,7 +62,7 @@ def api_key_required(function): userid = kwargs.get('api_pk') key = kwargs.get('api_key') - error_resp = render_to_response('403.html', context_instance=RequestContext(request)) + error_resp = render_to_response('403.html', context=RequestContext(request)) error_resp.status_code = 403 if key is None: diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 70d41064..d29c0e6b 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -31,6 +31,9 @@ ALLOWED_HOSTS = ['pyrigs.nottinghamtec.co.uk', 'rigs.nottinghamtec.co.uk', 'pyri if STAGING: ALLOWED_HOSTS.append('.herokuapp.com') +if DEBUG: + ALLOWED_HOSTS.append('localhost') + SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') if not DEBUG: SECURE_SSL_REDIRECT = True # Redirect all http requests to https diff --git a/PyRIGS/urls.py b/PyRIGS/urls.py index f8993d48..cbfcbfa0 100644 --- a/PyRIGS/urls.py +++ b/PyRIGS/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.conf import settings diff --git a/RIGS/urls.py b/RIGS/urls.py index 30eb7051..5af3fd9a 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import url from django.contrib.auth.views import password_reset from django.contrib.auth.decorators import login_required diff --git a/requirements.txt b/requirements.txt index f74f08e3..d9fbe723 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ beautifulsoup4==4.4.1 +contextlib2==0.5.5 diff-match-patch==20121119 dj-database-url==0.4.0 dj-static==0.0.6 -Django==1.9.4 +Django==1.10.7 django-debug-toolbar==1.4 django-ical==1.3 django-recaptcha==1.0.5 From d916c1ca19554b65c16ec740761aef65244b8ada Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 20:05:36 +0100 Subject: [PATCH 104/275] Update all the things, and fix some upgrade issues --- PyRIGS/decorators.py | 10 +++++----- PyRIGS/settings.py | 2 ++ PyRIGS/urls.py | 5 +++++ RIGS/migrations/0026_auto_20170510_1846.py | 21 +++++++++++++++++++++ RIGS/test_unit.py | 2 ++ RIGS/versioning.py | 6 +++--- 6 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 RIGS/migrations/0026_auto_20170510_1846.py diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 1ae73e7d..68549449 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -1,5 +1,5 @@ from django.contrib.auth import REDIRECT_FIELD_NAME -from django.shortcuts import render_to_response +from django.shortcuts import render from django.template import RequestContext from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse @@ -26,15 +26,15 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): return view_func(request, *args, **kwargs) elif not request.user.is_authenticated(): if oembed_view is not None: - context = RequestContext(request) + context = {} context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) - resp = render_to_response('login_redirect.html', context=context) + resp = render(request, 'login_redirect.html', context=context) return resp else: return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) else: - resp = render_to_response('403.html', context=RequestContext(request)) + resp = render(request, '403.html') resp.status_code = 403 return resp _checklogin.__doc__ = view_func.__doc__ @@ -62,7 +62,7 @@ def api_key_required(function): userid = kwargs.get('api_pk') key = kwargs.get('api_key') - error_resp = render_to_response('403.html', context=RequestContext(request)) + error_resp = render(request, '403.html') error_resp.status_code = 403 if key is None: diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index d29c0e6b..5c010a23 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -33,6 +33,7 @@ if STAGING: if DEBUG: ALLOWED_HOSTS.append('localhost') + ALLOWED_HOSTS.append('example.com') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') if not DEBUG: @@ -66,6 +67,7 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', 'django.middleware.security.SecurityMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', 'reversion.middleware.RevisionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/PyRIGS/urls.py b/PyRIGS/urls.py index cbfcbfa0..65bf2e63 100644 --- a/PyRIGS/urls.py +++ b/PyRIGS/urls.py @@ -22,3 +22,8 @@ urlpatterns = [ if settings.DEBUG: urlpatterns += staticfiles_urlpatterns() + + import debug_toolbar + urlpatterns = [ + url(r'^__debug__/', include(debug_toolbar.urls)), + ] + urlpatterns diff --git a/RIGS/migrations/0026_auto_20170510_1846.py b/RIGS/migrations/0026_auto_20170510_1846.py new file mode 100644 index 00000000..0a350f10 --- /dev/null +++ b/RIGS/migrations/0026_auto_20170510_1846.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-10 17:46 +from __future__ import unicode_literals + +import django.contrib.auth.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0025_auto_20160331_1302'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='username', + field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'), + ), + ] diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 82a7acca..0b22d84e 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -164,6 +164,8 @@ class TestInvoiceDelete(TestCase): def setUpTestData(cls): cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + cls.events = { 1: models.Event.objects.create(name="TE E1", start_date=date.today()), 2: models.Event.objects.create(name="TE E2", start_date=date.today()) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index d22f425c..65d4328a 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -199,7 +199,7 @@ def get_changes_for_version(newVersion, oldVersion=None): class VersionHistory(generic.ListView): - model = reversion.revisions.Version + model = Version template_name = "RIGS/version_history.html" paginate_by = 25 @@ -236,7 +236,7 @@ class VersionHistory(generic.ListView): class ActivityTable(generic.ListView): - model = reversion.revisions.Version + model = Version template_name = "RIGS/activity_table.html" paginate_by = 25 @@ -260,7 +260,7 @@ class ActivityTable(generic.ListView): class ActivityFeed(generic.ListView): - model = reversion.revisions.Version + model = Version template_name = "RIGS/activity_feed_data.html" paginate_by = 25 From 872e5e72f3a0cb7f81c25e6eb91b6eaa5fcdc605 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 20:11:12 +0100 Subject: [PATCH 105/275] Update the requirements.txt file, always a useful thing to do --- requirements.txt | 50 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/requirements.txt b/requirements.txt index d9fbe723..5e6106d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,36 @@ -beautifulsoup4==4.4.1 +beautifulsoup4==4.6.0 contextlib2==0.5.5 diff-match-patch==20121119 -dj-database-url==0.4.0 +dj-database-url==0.4.2 dj-static==0.0.6 -Django==1.10.7 -django-debug-toolbar==1.4 -django-ical==1.3 -django-recaptcha==1.0.5 -django-registration-redux==1.4 -django-reversion==1.10.1 +Django==1.11.1 +django-debug-toolbar==1.8 +django-ical==1.4 +django-recaptcha==1.3.0 +django-registration-redux==1.6 +django-reversion==1.10.2 django-toolbelt==0.0.1 django-widget-tweaks==1.4.1 -gunicorn==19.4.5 -icalendar==3.9.2 -lxml==3.6.0 -Markdown==2.6.6 -Pillow==3.1.1 -psycopg2==2.6.1 -Pygments==2.1.3 -PyPDF2==1.25.1 -python-dateutil==2.5.2 -pytz==2016.3 -raven==5.12.0 -reportlab==3.3.0 +gunicorn==19.7.1 +icalendar==3.11.4 +lxml==3.7.3 +Markdown==2.6.8 +Pillow==4.1.1 +psycopg2==2.7.1 +Pygments==2.2.0 +PyPDF2==1.26.0 +python-dateutil==2.6.0 +pytz==2017.2 +raven==6.0.0 +reportlab==3.4.0 selenium==2.53.1 -simplejson==3.8.2 +simplejson==3.10.0 six==1.10.0 -sqlparse==0.1.19 -static3==0.6.1 +sqlparse==0.2.3 +static3==0.7.0 svg2rlg==0.3 yolk==0.4.3 -z3c.rml==3.0.0 +z3c.rml==3.2.0 zope.event==4.2.0 -zope.interface==4.1.3 +zope.interface==4.4.0 zope.schema==4.4.2 From 38a8ac1eb477b69c5a3fdef8541cc158b2c71aea Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 20:25:14 +0100 Subject: [PATCH 106/275] Add failing tests for event paperwork printing --- RIGS/test_unit.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 0b22d84e..5b531636 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -216,6 +216,39 @@ class TestInvoiceDelete(TestCase): self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk)) +class TestPrintPaperwork(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + + cls.events = { + 1: models.Event.objects.create(name="TE E1", start_date=date.today()), + } + + cls.invoices = { + 1: models.Invoice.objects.create(event=cls.events[1]), + } + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + def test_print_paperwork_success(self): + request_url = reverse('event_print', kwargs={'pk': self.events[1].pk}) + + response = self.client.get(request_url, follow=True) + self.assertEqual(response.status_code, 200) + + def test_print_invoice_success(self): + request_url = reverse('invoice_print', kwargs={'pk': self.invoices[1].pk}) + + response = self.client.get(request_url, follow=True) + self.assertEqual(response.status_code, 200) + + class TestEmbeddedViews(TestCase): @classmethod def setUpTestData(cls): From 0d726b2b60626a8d88e6794d2305ba08b444ab7a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 20:25:41 +0100 Subject: [PATCH 107/275] Fix paperwork printing --- RIGS/finance.py | 6 +++--- RIGS/rigboard.py | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 26ca0df8..87764189 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -55,8 +55,8 @@ class InvoicePrint(generic.View): invoice = get_object_or_404(models.Invoice, pk=pk) object = invoice.event template = get_template('RIGS/event_print.xml') - copies = ('TEC', 'Client') - context = RequestContext(request, { + + context = { 'object': object, 'fonts': { 'opensans': { @@ -66,7 +66,7 @@ class InvoicePrint(generic.View): }, 'invoice': invoice, 'current_user': request.user, - }) + } rml = template.render(context) buffer = StringIO.StringIO() diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 81cf564e..0d2c6ec8 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -136,6 +136,7 @@ class EventDuplicate(EventUpdate): context["duplicate"] = True return context + class EventPrint(generic.View): def get(self, request, pk): object = get_object_or_404(models.Event, pk=pk) @@ -144,9 +145,9 @@ class EventPrint(generic.View): merger = PdfFileMerger() - for copy in copies: + for thisCopy in copies: - context = RequestContext(request, { # this should be outside the loop, but bug in 1.8.2 prevents this + context = { # this should be outside the loop, but bug in 1.8.2 prevents this 'object': object, 'fonts': { 'opensans': { @@ -154,9 +155,9 @@ class EventPrint(generic.View): 'bold': 'RIGS/static/fonts/OPENSANS-BOLD.TTF', } }, - 'copy':copy, - 'current_user':request.user, - }) + 'copy': thisCopy, + 'current_user': request.user, + } # context['copy'] = copy # this is the way to do it once we upgrade to Django 1.8.3 From 374c31e8b4f54a89e6659f9726d74ad9fbdd97e7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 23:03:35 +0100 Subject: [PATCH 108/275] Fix date/time/datetime field types --- RIGS/finance.py | 3 +++ RIGS/forms.py | 4 ++++ RIGS/templates/RIGS/event_form.html | 12 ++++++------ RIGS/templates/RIGS/payment_form.html | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/RIGS/finance.py b/RIGS/finance.py index 87764189..2c9f21c0 100644 --- a/RIGS/finance.py +++ b/RIGS/finance.py @@ -15,6 +15,9 @@ from z3c.rml import rml2pdf from RIGS import models +from django import forms +forms.DateField.widget = forms.DateInput(attrs={'type': 'date'}) + class InvoiceIndex(generic.ListView): model = models.Invoice diff --git a/RIGS/forms.py b/RIGS/forms.py index d1b826c3..e0d57f70 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -10,6 +10,10 @@ import simplejson from RIGS import models +# Override the django form defaults to use the HTML date/time/datetime UI elements +forms.DateField.widget = forms.DateInput(attrs={'type': 'date'}) +forms.TimeField.widget = forms.DateInput(attrs={'type': 'time'}) +forms.DateTimeField.widget = forms.DateInput(attrs={'type': 'datetime-local'}) # Registration class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index b2b4dcd7..6832d571 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -289,10 +289,10 @@
- {% render_field form.start_date type="date" class+="form-control" %} + {% render_field form.start_date class+="form-control" %}
- {% render_field form.start_time type="time" class+="form-control" %} + {% render_field form.start_time class+="form-control" %}
@@ -304,10 +304,10 @@
- {% render_field form.end_date type="date" class+="form-control" %} + {% render_field form.end_date class+="form-control" %}
- {% render_field form.end_time type="time" class+="form-control" %} + {% render_field form.end_time class+="form-control" %}
@@ -329,7 +329,7 @@ class="col-sm-4 control-label">{{ form.access_at.label }}
- {% render_field form.access_at type="datetime-local" class+="form-control" %} + {% render_field form.access_at class+="form-control" %}
@@ -337,7 +337,7 @@ class="col-sm-4 control-label">{{ form.meet_at.label }}
- {% render_field form.meet_at type="datetime-local" class+="form-control" %} + {% render_field form.meet_at class+="form-control" %}
diff --git a/RIGS/templates/RIGS/payment_form.html b/RIGS/templates/RIGS/payment_form.html index 41bbf15b..13f4a694 100644 --- a/RIGS/templates/RIGS/payment_form.html +++ b/RIGS/templates/RIGS/payment_form.html @@ -16,7 +16,7 @@ for="{{ form.date.id_for_label }}">{{ form.date.label }}
- {% render_field form.date type="date" class+="form-control" %} + {% render_field form.date class+="form-control" %}
From 1f4e53ad27108cc024364fb6348f0b1c93aaad08 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 23:40:48 +0100 Subject: [PATCH 109/275] Add dependency badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 787502ef..625bac90 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # TEC PA & Lighting - PyRIGS # [![Build Status](https://travis-ci.org/nottinghamtec/PyRIGS.svg?branch=develop)](https://travis-ci.org/nottinghamtec/PyRIGS) [![Coverage Status](https://coveralls.io/repos/github/nottinghamtec/PyRIGS/badge.svg?branch=develop)](https://coveralls.io/github/nottinghamtec/PyRIGS?branch=develop) +[![Dependency Status](https://gemnasium.com/badges/github.com/nottinghamtec/PyRIGS.svg)](https://gemnasium.com/github.com/nottinghamtec/PyRIGS) + Welcome to TEC PA & Lightings PyRIGS program. This is a reimplementation of the existing Rig Information Gathering System (RIGS) that was developed using Ruby on Rails. From e547e3e858ebda4f7941bf55ee8ae6851ca33307 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 May 2017 23:54:23 +0100 Subject: [PATCH 110/275] Delete unused wercker config --- wercker.yml | 103 ---------------------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 wercker.yml diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 0e2efea2..00000000 --- a/wercker.yml +++ /dev/null @@ -1,103 +0,0 @@ -# This references the default Python container from -# the Docker Hub with the 2.7 tag: -# https://registry.hub.docker.com/_/python/ -# If you want to use a slim Python container with -# version 3.4.3 you would use: python:3.4-slim -# If you want Google's container you would reference google/python -# Read more about containers on our dev center -# http://devcenter.wercker.com/docs/containers/index.html -box: heroku/python -# You can also use services such as databases. Read more on our dev center: -# http://devcenter.wercker.com/docs/services/index.html -services: - - id: postgres - env: - POSTGRES_PASSWORD: pyrigstesting - POSTGRES_USER: pyrigs - # http://devcenter.wercker.com/docs/services/postgresql.html - - # - mongodb - # http://devcenter.wercker.com/docs/services/mongodb.html - -# This is the build pipeline. Pipelines are the core of wercker -# Read more about pipelines on our dev center -# http://devcenter.wercker.com/docs/pipelines/index.html -build: - # The steps that will be executed on build - # Steps make up the actions in your pipeline - # Read more about steps on our dev center: - # http://devcenter.wercker.com/docs/steps/index.html - steps: - - install-packages: - packages: firefox xvfb - - - script: - name: Enable virtual display - code: |- - # Start xvfb which gives the context an virtual display - # which is required for tests that require an GUI - export DISPLAY=:99.0 - start-stop-daemon --start --quiet --pidfile /tmp/xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset - # Give xvfb time to start. 3 seconds is the default for all xvfb-run commands. - # sleep 3 - - - script: - name: virtualenv install - code: | - pip install virtualenv - - # A step that sets up the python virtual environment - - virtualenv: - name: setup virtual environment - install_wheel: false # Enable wheel to speed up builds (experimental) - - # # Use this virtualenv step for python 3.2 - # - virtualenv - # name: setup virtual environment - # python_location: /usr/bin/python3.2 - - # # This pip-install clears the local wheel cache - # - pip-install: - # clean_wheel_dir: true - - # A custom script step, name value is used in the UI - # and the code value contains the command that get executed - - script: - name: Python Version - code: | - echo "python version $(python --version) running" - echo "pip version $(pip --version) running" - - # Django uses this to connect to the database - - script: - name: set environment - code: | - export DEBUG=0 - export DATABASE_URL="postgres://pyrigs:pyrigstesting@$POSTGRES_PORT_5432_TCP_ADDR:$POSTGRES_PORT_5432_TCP_PORT$POSTGRES_NAME" - - # A step that executes `pip install` command. - - pip-install - - # Install coverage - - script: - name: install coverage - code: | - pip install coverage - - # Collect static files so the manifest storage knows where to look - - script: - name: collect static - code: | - python manage.py collectstatic --noinput - - # Run python tests - - script: - name: run tests - code: | - coverage run manage.py test RIGS - - - script: - name: collect coverage data - code: | - coverage report -m --include=PyRIGS/*.*,RIGS/*.* --omit=*/migrations/* | tee coverage.txt - coverage html --include=PyRIGS/*.*,RIGS/*.* --omit=*/migrations/* From eb1e8935f4689c93182562d3d5bd1cecdc220e52 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 May 2017 20:56:01 +0100 Subject: [PATCH 111/275] Fix reversion in signals.py --- RIGS/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/signals.py b/RIGS/signals.py index df5dc8fe..71263681 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -95,4 +95,4 @@ def on_revision_commit(instances, **kwargs): send_eventauthorisation_success_email(instance) -reversion.post_revision_commit.connect(on_revision_commit) +reversion.revisions.post_revision_commit.connect(on_revision_commit) From 865bb131a517b7e38671c600ad358165b65f5a12 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 May 2017 21:02:48 +0100 Subject: [PATCH 112/275] Add merge migration --- RIGS/migrations/0031_merge_20170512_2102.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 RIGS/migrations/0031_merge_20170512_2102.py diff --git a/RIGS/migrations/0031_merge_20170512_2102.py b/RIGS/migrations/0031_merge_20170512_2102.py new file mode 100644 index 00000000..7056c46b --- /dev/null +++ b/RIGS/migrations/0031_merge_20170512_2102.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-12 20:02 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0030_auth_request_sending'), + ('RIGS', '0026_auto_20170510_1846'), + ] + + operations = [ + ] From 36d258253fd3283acaa4839863cccca2bdf35730 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 12 May 2017 21:32:17 +0100 Subject: [PATCH 113/275] Fix issues caused by dependency upgrades --- PyRIGS/decorators.py | 3 +-- RIGS/test_functional.py | 8 ++------ RIGS/test_models.py | 3 ++- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 4b897923..f9bb4c20 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -1,6 +1,5 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.shortcuts import render -from django.template import RequestContext from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse @@ -88,7 +87,7 @@ def nottinghamtec_address_required(function): def wrap(request, *args, **kwargs): # Fail if current user's email address isn't @nottinghamtec.co.uk if not request.user.email.endswith('@nottinghamtec.co.uk'): - error_resp = render_to_response('RIGS/eventauthorisation_request_error.html', context_instance=RequestContext(request)) + error_resp = render(request, 'RIGS/eventauthorisation_request_error.html') return error_resp return function(request, *args, **kwargs) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 231ade20..38c7173a 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -520,9 +520,6 @@ class EventTest(LiveServerTestCase): self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) - # Check the PO hasn't carried through - self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) - self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk)) #Go back to the old event #Check that based-on hasn't crept into the old event @@ -530,9 +527,6 @@ class EventTest(LiveServerTestCase): self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) - # Check the PO remains on the old event - self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) - # Check the items are as they were table = self.browser.find_element_by_id('item-table') # ID number is known, see above self.assertIn("Test Item 1", table.text) @@ -976,6 +970,7 @@ class ClientEventAuthorisationTest(TestCase): email='teccie@functional.test', is_superuser=True # lazily grant all permissions )[0] + self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') venue = models.Venue.objects.create(name='Authorisation Test Venue') client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False) @@ -1078,6 +1073,7 @@ class TECEventAuthorisationTest(TestCase): cls.profile.save() def setUp(self): + self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') venue = models.Venue.objects.create(name='Authorisation Test Venue') client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=False) diff --git a/RIGS/test_models.py b/RIGS/test_models.py index 1fd8b4a0..b5dfd1e3 100644 --- a/RIGS/test_models.py +++ b/RIGS/test_models.py @@ -360,6 +360,7 @@ class EventPricingTestCase(TestCase): class EventAuthorisationTestCase(TestCase): def setUp(self): + models.VatRate.objects.create(rate=0.20, comment="TP V1", start_at='2013-01-01') self.profile = models.Profile.objects.get_or_create( first_name='Test', last_name='TEC User', @@ -385,7 +386,7 @@ class EventAuthorisationTestCase(TestCase): self.assertTrue(self.event.authorised) def test_last_edited(self): - with reversion.create_revision(): + with reversion.revisions.create_revision(): auth = models.EventAuthorisation.objects.create(event=self.event, email="authroisation@model.test.case", name="Test Auth", amount=self.event.total, sent_by=self.profile) self.assertIsNotNone(auth.last_edited_at) From 55d24e96cb2c815b3d10910225b2cbc67dc61314 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 15 May 2017 20:40:03 +0100 Subject: [PATCH 114/275] Adds basic tests to check that versioning views load successfully More comprehensive tests should be added when versioning.py is updated for the new version of django-reversion --- RIGS/test_unit.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 5b531636..c264b9c1 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -7,7 +7,7 @@ from django.test import TestCase from django.test.utils import override_settings from RIGS import models - +from reversion import revisions as reversion class TestAdminMergeObjects(TestCase): @classmethod @@ -249,6 +249,52 @@ class TestPrintPaperwork(TestCase): self.assertEqual(response.status_code, 200) +class TestVersioningViews(TestCase): + @classmethod + def setUpTestData(cls): + cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True, is_active=True, is_staff=True) + + cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + + cls.events = {} + + with reversion.create_revision(): + reversion.set_user(cls.profile) + cls.events[1] = models.Event.objects.create(name="TE E1", start_date=date.today()) + + with reversion.create_revision(): + reversion.set_user(cls.profile) + cls.events[2] = models.Event.objects.create(name="TE E2", start_date='2014-03-05') + + with reversion.create_revision(): + reversion.set_user(cls.profile) + cls.events[1].description = "A test description" + cls.events[1].save() + + def setUp(self): + self.profile.set_password('testuser') + self.profile.save() + self.assertTrue(self.client.login(username=self.profile.username, password='testuser')) + + def test_event_history_loads_successfully(self): + request_url = reverse('event_history', kwargs={'pk': self.events[1].pk}) + + response = self.client.get(request_url, follow=True) + self.assertEqual(response.status_code, 200) + + def test_activity_feed_loads_successfully(self): + request_url = reverse('activity_feed') + + response = self.client.get(request_url, follow=True) + self.assertEqual(response.status_code, 200) + + def test_activity_table_loads_successfully(self): + request_url = reverse('activity_table') + + response = self.client.get(request_url, follow=True) + self.assertEqual(response.status_code, 200) + + class TestEmbeddedViews(TestCase): @classmethod def setUpTestData(cls): From fdce2fa53d24de5452d201a1018b5c180b7c0e2d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 15 May 2017 21:41:33 +0100 Subject: [PATCH 115/275] Update selenium, use chrome for tests, and use sauce-labs for CI integration tests --- .travis.yml | 15 +++-- README.md | 13 +++++ RIGS/test_functional.py | 124 +++++++++++++++++++++++----------------- requirements.txt | 2 +- 4 files changed, 97 insertions(+), 57 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1efb7729..0e93cb50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,27 @@ +sudo: false +dist: trusty + language: python python: "2.7" +cache: pip -before_install: - - "export DISPLAY=:99.0" - - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" +addons: + sauce_connect: + username: davidtaylorhq + access_key: + secure: "ibpjQ19LfjwfQruiJmX0j6NzyNwsS3PvRFdfXUYcfCEa9Eh20QQ/S8pOdFhRh70KIEuwN5oGuPqDkJPPTjkdY3/NCjuA7/NMTp14jAIX4XjpeNcsPFupp31vEy7KBuX4iAGpenrHJssFCwurpvrlWfwSOrk7bVZKaGUowVOXmyth1FSNQvr5c3YnlxmGvNzNBMMBDcJ3ixSlS9pBRLnHIJ1w3/f9Lx2uONkVMeGM6rVyuHholWvanIyNVYtO9JkXkoie6n1R3gNbXCyJdxSRn2OLppdryUaA0wUPJSu3hqEM3R5EsRDiFJszkJLTwSBG8x4k/dbqim7stjsu1qpUhCIG5mT6e+UI9auPi/5nlwlVmPhSq58qBP53vH3hs++02wjDlgvTGB1p4PqFblHhVaslaQ166bo9skGMZb0fXLlM1aCmmwFTpC5ofiPTSRTdJcljHG/d3JabKX03ME+nX2LFPIMnSLXgrjrfh2ppI6LFESiX3Z8jYUdsgTFeN3nQZ8U0kyb5X9Ay9YFnAaYD9OuxaqweTmqAJQj093GK38+79WMN2jnvEUzM1ZjI8Y4L/f3rHvhNIwYvZjQ+gJRhUqJh2Qruk7ke7uQ1oecxIqRHj8hIFEkuBcM3e86MkRiYQXXI9jOX3JrhI/jivAjFuw0flU2tjLNgM7tUYzjMyqk=" install: - pip install -r requirements.txt - pip install coveralls codeclimate-test-reporter before_script: + - export PATH=$PATH:/usr/lib/chromium-browser/ - python manage.py collectstatic --noinput script: - - coverage run manage.py test RIGS + - coverage run manage.py test --verbosity=2 after_success: - coveralls diff --git a/README.md b/README.md index 625bac90..3401782f 100644 --- a/README.md +++ b/README.md @@ -95,5 +95,18 @@ python manage.py generateSampleData |keyholder|keyholder| |basic |basic | +### Testing ### +Tests are contained in 3 files. `RIGS/test_models.py` contains tests for logic within the data models. `RIGS/test_unit.py` contains "Live server" tests, using raw web requests. `RIGS/test_integration.py` contains user interface tests which take control of a web browser. For automated Travis tests, we use [Sauce Labs](https://saucelabs.com). When debugging locally, ensure that you have the latest version of Google Chrome installed, then install [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) and ensure it is on the `PATH`. + +You can run the entire test suite, or you can run specific sections individually. For example, in order of specificity: + +``` +python manage.py test +python manage.py test RIGS.test_models +python manage.py test RIGS.test_models.EventTestCase +python manage.py test RIGS.test_models.EventTestCase.test_current_events + +``` + ### Committing, pushing and testing ### Feel free to commit as you wish, on your own branch. On my branch (master for development) do not commit code that you either know doesn't work or don't know works. If you must commit this code, please make sure you say in the commit message that it isn't working, and if you can why it isn't working. If and only if you absolutely must push, then please don't leave it as the HEAD for too long, it's not much to ask but when you are done just make sure you haven't broken the HEAD for the next person. diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 3b867652..9452e41c 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -3,7 +3,6 @@ import os import re from datetime import date, timedelta -import reversion from django.core import mail from django.db import transaction from django.test import LiveServerTestCase @@ -15,20 +14,52 @@ from selenium.webdriver.support.ui import WebDriverWait from RIGS import models -import re -import os -from datetime import date, timedelta -from django.db import transaction from reversion import revisions as reversion -import json + +import time +import sys + +browsers = [{"platform": "macOS 10.12", + "browserName": "chrome", + "version": "latest"}, + ] +def on_platforms(platforms): + if os.environ.get("TRAVIS"): + def decorator(base_class): + module = sys.modules[base_class.__module__].__dict__ + for i, platform in enumerate(platforms): + d = dict(base_class.__dict__) + d['desired_capabilities'] = platform + name = "%s_%s" % (base_class.__name__, i + 1) + module[name] = type(name, (base_class,), d) + return decorator + +def create_browser(test_name, desired_capabilities): + # return webdriver.Chrome() + if os.environ.get("TRAVIS"): + username = os.environ["SAUCE_USERNAME"] + access_key = os.environ["SAUCE_ACCESS_KEY"] + caps = {'browserName': desired_capabilities['browserName']} + caps['platform'] = desired_capabilities['platform'] + caps['version'] = desired_capabilities['version'] + caps["tunnel-identifier"] = os.environ["TRAVIS_JOB_NUMBER"] + caps["name"] = '#' + os.environ["TRAVIS_JOB_NUMBER"] + ": " + test_name + hub_url = "%s:%s@localhost:4445" % (username, access_key) + driver = webdriver.Remote(desired_capabilities=caps, command_executor="http://%s/wd/hub" % hub_url) + return driver + else: + return webdriver.Chrome() + + +@on_platforms(browsers) class UserRegistrationTest(LiveServerTestCase): def setUp(self): - self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) # Set implicit wait session wide + self.browser = create_browser(self.id(), self.desired_capabilities) + self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' def tearDown(self): @@ -155,7 +186,7 @@ class UserRegistrationTest(LiveServerTestCase): # All is well - +@on_platforms(browsers) class EventTest(LiveServerTestCase): def setUp(self): @@ -166,9 +197,9 @@ class EventTest(LiveServerTestCase): self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') - self.browser = webdriver.Firefox() - self.browser.implicitly_wait(3) # Set implicit wait session wide - self.browser.maximize_window() + self.browser = create_browser(self.id(), self.desired_capabilities) + self.browser.implicitly_wait(10) # Set implicit wait session wide + # self.browser.maximize_window() os.environ['RECAPTCHA_TESTING'] = 'True' def tearDown(self): @@ -211,7 +242,7 @@ class EventTest(LiveServerTestCase): # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations) wait.until(animation_is_finished()) @@ -366,11 +397,11 @@ class EventTest(LiveServerTestCase): self.assertEqual(obj.pk, int(option.get_attribute("value"))) # Set start date/time - form.find_element_by_id('id_start_date').send_keys('3015-05-25') + form.find_element_by_id('id_start_date').send_keys('25/05/3015') form.find_element_by_id('id_start_time').send_keys('06:59') # Set end date/time - form.find_element_by_id('id_end_date').send_keys('4000-06-27') + form.find_element_by_id('id_end_date').send_keys('27/06/4000') form.find_element_by_id('id_end_time').send_keys('07:00') # Add item @@ -467,7 +498,7 @@ class EventTest(LiveServerTestCase): self.browser.get(self.live_server_url + '/event/' + str(testEvent.pk) + '/duplicate/') self.authenticate('/event/' + str(testEvent.pk) + '/duplicate/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations) save = self.browser.find_element_by_xpath( '(//button[@type="submit"])[3]') @@ -540,7 +571,7 @@ class EventTest(LiveServerTestCase): # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations) wait.until(animation_is_finished()) @@ -555,14 +586,13 @@ class EventTest(LiveServerTestCase): e.send_keys('Test Event Name') # Both dates, no times, end before start - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") - form.find_element_by_id('id_end_date').clear() - form.find_element_by_id('id_end_date').send_keys('3015-04-23') + self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-23'") # Attempt to save - should fail save.click() + error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]') self.assertTrue(error.is_displayed()) self.assertIn("can't finish before it has started", error.find_element_by_xpath('//dd[1]/ul/li').text) @@ -571,16 +601,14 @@ class EventTest(LiveServerTestCase): # Same date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') - form.find_element_by_id('id_end_date').clear() - form.find_element_by_id('id_end_date').send_keys('3015-04-23') + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") + self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-23'") - form.find_element_by_id('id_start_time').clear() + form.find_element_by_id('id_start_time').send_keys(Keys.DELETE) form.find_element_by_id('id_start_time').send_keys('06:59') - form.find_element_by_id('id_end_time').clear() + form.find_element_by_id('id_end_time').send_keys(Keys.DELETE) form.find_element_by_id('id_end_time').send_keys('06:00') # Attempt to save - should fail @@ -593,31 +621,28 @@ class EventTest(LiveServerTestCase): # Same date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') - form.find_element_by_id('id_end_date').clear() - form.find_element_by_id('id_end_date').send_keys('3015-04-23') + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") + self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-24'") - form.find_element_by_id('id_start_time').clear() + form.find_element_by_id('id_start_time').send_keys(Keys.DELETE) form.find_element_by_id('id_start_time').send_keys('06:59') - form.find_element_by_id('id_end_time').clear() + form.find_element_by_id('id_end_time').send_keys(Keys.DELETE) form.find_element_by_id('id_end_time').send_keys('06:00') # No end date, end time before start time form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') + + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") + self.browser.execute_script("document.getElementById('id_end_date').value=''") - form.find_element_by_id('id_end_date').clear() - - form.find_element_by_id('id_start_time').clear() + form.find_element_by_id('id_start_time').send_keys(Keys.DELETE) form.find_element_by_id('id_start_time').send_keys('06:59') - form.find_element_by_id('id_end_time').clear() + form.find_element_by_id('id_end_time').send_keys(Keys.DELETE) form.find_element_by_id('id_end_time').send_keys('06:00') # Attempt to save - should fail @@ -630,15 +655,11 @@ class EventTest(LiveServerTestCase): # 2 dates, end after start form = self.browser.find_element_by_tag_name('form') save = self.browser.find_element_by_xpath('(//button[@type="submit"])[3]') - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") + self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-26'") - form.find_element_by_id('id_end_date').clear() - form.find_element_by_id('id_end_date').send_keys('3015-04-26') - - form.find_element_by_id('id_start_time').clear() - - form.find_element_by_id('id_end_time').clear() + self.browser.execute_script("document.getElementById('id_start_time').value=''") + self.browser.execute_script("document.getElementById('id_end_time').value=''") # Attempt to save - should succeed save.click() @@ -653,7 +674,7 @@ class EventTest(LiveServerTestCase): # Gets redirected to login and back self.authenticate('/event/create/') - wait = WebDriverWait(self.browser, 10) #setup WebDriverWait to use later (to wait for animations) + wait = WebDriverWait(self.browser, 3) #setup WebDriverWait to use later (to wait for animations) self.browser.implicitly_wait(3) #Set session-long wait (only works for non-existant DOM objects) wait.until(animation_is_finished()) @@ -672,8 +693,7 @@ class EventTest(LiveServerTestCase): e.send_keys('Test Event Name') # Set an arbitrary date - form.find_element_by_id('id_start_date').clear() - form.find_element_by_id('id_start_date').send_keys('3015-04-24') + self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") # Save the rig save.click() @@ -728,6 +748,7 @@ class EventTest(LiveServerTestCase): organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..') +@on_platforms(browsers) class IcalTest(LiveServerTestCase): def setUp(self): @@ -767,7 +788,7 @@ class IcalTest(LiveServerTestCase): models.Event.objects.create(name="TE E17", start_date=date.today()-timedelta(days=1), is_rig=False, description="non rig yesterday") models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED, description="non rig today cancelled") - self.browser = webdriver.Firefox() + self.browser = create_browser(self.id(), self.desired_capabilities) self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' @@ -926,6 +947,5 @@ class animation_is_finished(object): numberAnimating = driver.execute_script('return $(":animated").length') finished = numberAnimating == 0 if finished: - import time time.sleep(0.1) return finished diff --git a/requirements.txt b/requirements.txt index 5e6106d3..3e7d8d17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ python-dateutil==2.6.0 pytz==2017.2 raven==6.0.0 reportlab==3.4.0 -selenium==2.53.1 +selenium==3.4.1 simplejson==3.10.0 six==1.10.0 sqlparse==0.2.3 From fbc039c27422428ed5889adda449f69d65076255 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 16 May 2017 13:58:05 +0100 Subject: [PATCH 116/275] Fix tests so they can actually run locally (I failed) --- RIGS/test_functional.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 9452e41c..a804ddd5 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -26,15 +26,20 @@ browsers = [{"platform": "macOS 10.12", def on_platforms(platforms): - if os.environ.get("TRAVIS"): - def decorator(base_class): - module = sys.modules[base_class.__module__].__dict__ - for i, platform in enumerate(platforms): - d = dict(base_class.__dict__) - d['desired_capabilities'] = platform - name = "%s_%s" % (base_class.__name__, i + 1) - module[name] = type(name, (base_class,), d) - return decorator + if not os.environ.get("TRAVIS"): + platforms = {'local'} + + def decorator(base_class): + module = sys.modules[base_class.__module__].__dict__ + for i, platform in enumerate(platforms): + d = dict(base_class.__dict__) + d['desired_capabilities'] = platform + name = "%s_%s" % (base_class.__name__, i + 1) + module[name] = type(name, (base_class,), d) + + return decorator + + def create_browser(test_name, desired_capabilities): From cb23fd183e7ed8e23a336a280e66fc8c507b8c04 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 16 May 2017 14:50:18 +0100 Subject: [PATCH 117/275] Add failing test --- RIGS/test_functional.py | 66 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index a804ddd5..7e9595cb 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- import os import re -from datetime import date, timedelta +import pytz +from datetime import date, time, datetime, timedelta from django.core import mail from django.db import transaction @@ -16,7 +17,8 @@ from RIGS import models from reversion import revisions as reversion -import time +from django.conf import settings + import sys browsers = [{"platform": "macOS 10.12", @@ -753,6 +755,65 @@ class EventTest(LiveServerTestCase): organisationPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Contact Details")]/..') + def testEventEdit(self): + person = models.Person(name="Event Edit Person", email="eventdetail@person.tests.rigs", phone="123 123").save() + organisation = models.Organisation(name="Event Edit Organisation", email="eventdetail@organisation.tests.rigs", phone="123 456").save() + venue = models.Venue(name="Event Detail Venue").save() + + eventData = { + 'name': "Detail Test", + 'description': "This is an event to test the detail view", + 'notes': "It is going to be awful", + 'person': person, + 'organisation': organisation, + 'venue': venue, + 'mic': self.profile, + 'start_date': date(2015, 06, 04), + 'end_date': date(2015, 06, 05), + 'start_time': time(10, 00), + 'end_time': time(15, 00), + 'meet_at': self.create_datetime(2015, 06, 04, 10, 00), + 'access_at': self.create_datetime(2015, 06, 04, 10, 00), + 'collector': 'A Person' + } + + event = models.Event(**eventData) + event.save() + + item1Data = { + 'event': event, + 'name': "Detail Item 1", + 'cost': "10.00", + 'quantity': "1", + 'order': 1 + } + + models.EventItem(**item1Data).save() + + self.browser.get(self.live_server_url + '/event/%d/edit/' % event.pk) + self.authenticate('/event/%d/edit/' % event.pk) + + save = self.browser.find_element_by_xpath('(//button[@type="submit"])[1]') + save.click() + + successTitle = self.browser.find_element_by_xpath('//h1').text + self.assertIn("N%05d | Detail Test" % event.pk, successTitle) + + reloadedEvent = models.Event.objects.get(name='Detail Test') + reloadedItem = models.EventItem.objects.get(name='Detail Item 1') + + # Check the event + for key, value in eventData.iteritems(): + self.assertEqual(str(getattr(reloadedEvent, key)), str(value)) + + # Check the item + for key, value in item1Data.iteritems(): + self.assertEqual(str(getattr(reloadedItem, key)), str(value)) + + def create_datetime(self, year, month, day, hour, min): + tz = pytz.timezone(settings.TIME_ZONE) + return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc) + @on_platforms(browsers) class IcalTest(LiveServerTestCase): @@ -952,5 +1013,6 @@ class animation_is_finished(object): numberAnimating = driver.execute_script('return $(":animated").length') finished = numberAnimating == 0 if finished: + import time time.sleep(0.1) return finished From 4b032944ac567ced90271562e623183b473b70f7 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 16 May 2017 14:50:33 +0100 Subject: [PATCH 118/275] Fix the time formatting --- RIGS/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/forms.py b/RIGS/forms.py index e0d57f70..85fd0394 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -12,8 +12,8 @@ from RIGS import models # Override the django form defaults to use the HTML date/time/datetime UI elements forms.DateField.widget = forms.DateInput(attrs={'type': 'date'}) -forms.TimeField.widget = forms.DateInput(attrs={'type': 'time'}) -forms.DateTimeField.widget = forms.DateInput(attrs={'type': 'datetime-local'}) +forms.TimeField.widget = forms.TextInput(attrs={'type': 'time'}) +forms.DateTimeField.widget = forms.DateTimeInput(attrs={'type': 'datetime-local'}) # Registration class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): From 4b87b0a19696e95fdf115be09dfbd7f4c1bedc43 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 17 May 2017 18:59:06 +0100 Subject: [PATCH 119/275] Add some visual indicators that authorisations have been submitted. This will show teccies and clients that RIGS is processing emails which can take a short while. Should prevent duplicate sending. --- RIGS/static/js/interaction.js | 2 +- RIGS/templates/RIGS/eventauthorisation_form.html | 9 ++++++++- .../RIGS/eventauthorisation_request.html | 15 +++++++++++++-- templates/base_ajax.html | 2 +- templates/base_client.html | 16 ++++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/RIGS/static/js/interaction.js b/RIGS/static/js/interaction.js index e7d023e1..56f04dec 100644 --- a/RIGS/static/js/interaction.js +++ b/RIGS/static/js/interaction.js @@ -136,4 +136,4 @@ $("#item-table tbody").sortable({ }); } -}); \ No newline at end of file +}); diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index 0809b29a..9182df41 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -7,7 +7,14 @@ {% endblock %} diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html index 9c31f1fe..6067b3fe 100644 --- a/RIGS/templates/RIGS/eventauthorisation_request.html +++ b/RIGS/templates/RIGS/eventauthorisation_request.html @@ -22,7 +22,9 @@
- {% csrf_token %} + + {% csrf_token %}
{% include 'form_errors.html' %} @@ -38,11 +40,20 @@
- +
+ + {% endblock %} diff --git a/templates/base_ajax.html b/templates/base_ajax.html index 298ca314..79b934d3 100644 --- a/templates/base_ajax.html +++ b/templates/base_ajax.html @@ -22,4 +22,4 @@ });
-
\ No newline at end of file + diff --git a/templates/base_client.html b/templates/base_client.html index b8cd614b..824e9f88 100644 --- a/templates/base_client.html +++ b/templates/base_client.html @@ -71,6 +71,22 @@ + + - - - {% block preload_js %} - {% endblock %} + - {% block extra-head %}{% endblock %} - - - -
- +
+ + + - -
+ + + + + + +
+ + + +
+ +
- - - + + +
+ {% block content %}{% endblock %} +
+
-
-
-
- {% block content %}{% endblock %} -
-
+ -{% block js %} -{% endblock %} - + From 703fb8561a9673eb0ec88c54d98039951dafe0d1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 15:32:54 +0100 Subject: [PATCH 121/275] =?UTF-8?q?Move=20font=20definition=20into=20div,?= =?UTF-8?q?=20doesn=E2=80=99t=20seem=20to=20be=20picked=20up=20in=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RIGS/static/css/email.css | 2 +- RIGS/static/scss/email.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RIGS/static/css/email.css b/RIGS/static/css/email.css index 937d6b42..915b52f3 100644 --- a/RIGS/static/css/email.css +++ b/RIGS/static/css/email.css @@ -1 +1 @@ -body{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none} +body{margin:0px}.main-table{width:100%;border-collapse:collapse}.client-header{background-image:url("https://www.nottinghamtec.co.uk/imgs/wof2014-1.jpg");background-color:#222;background-repeat:no-repeat;background-position:center;width:100%;margin-bottom:28px}.client-header .logos{width:100%;max-width:640px}.client-header img{height:110px}.content-container{width:100%}.content-container .content{font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;width:100%;max-width:600px;padding:10px;text-align:left}.content-container .content .button-container{width:100%}.content-container .content .button-container .button{padding:6px 12px;background-color:#357ebf;border-radius:4px}.content-container .content .button-container .button a{color:#fff;text-decoration:none} diff --git a/RIGS/static/scss/email.scss b/RIGS/static/scss/email.scss index e94d09c5..73ffe478 100644 --- a/RIGS/static/scss/email.scss +++ b/RIGS/static/scss/email.scss @@ -1,9 +1,7 @@ $button_color: #357ebf; body{ - font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; margin: 0px; - } .main-table{ @@ -36,6 +34,8 @@ body{ width: 100%; .content { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + width: 100%; max-width: 600px; padding: 10px; From b4ab29393eca1065d9f0b966433603d82c45b091 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 16:21:44 +0100 Subject: [PATCH 122/275] Allow confirmation emails to fail without blocking the interface --- RIGS/signals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/signals.py b/RIGS/signals.py index 71263681..faf0f873 100644 --- a/RIGS/signals.py +++ b/RIGS/signals.py @@ -85,8 +85,8 @@ def send_eventauthorisation_success_email(instance): ) # Now we have both emails successfully generated, send them out - client_email.send() - mic_email.send() + client_email.send(fail_silently=True) + mic_email.send(fail_silently=True) def on_revision_commit(instances, **kwargs): From 4e79f00551138c191f2e3653b63e36a5e93fed3e Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 17:22:59 +0100 Subject: [PATCH 123/275] Add pound signs to confirmation emails --- RIGS/templates/RIGS/eventauthorisation_client_success.html | 2 +- RIGS/templates/RIGS/eventauthorisation_client_success.txt | 2 +- RIGS/templates/RIGS/eventauthorisation_mic_success.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.html b/RIGS/templates/RIGS/eventauthorisation_client_success.html index 6226c469..dc6d0485 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_success.html +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.html @@ -5,7 +5,7 @@

Your event N{{ object.event.pk|stringformat:"05d" }} has been successfully authorised - for {{ object.amount }} + for £{{ object.amount }} by {{ object.name }} as of {{ object.last_edited_at }}.

diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.txt b/RIGS/templates/RIGS/eventauthorisation_client_success.txt index 693ba7ac..ff934c4d 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.txt @@ -1,6 +1,6 @@ Hi {{ to_name|default:"there" }}, -Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. +Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. {% if object.event.organisation and object.event.organisation.union_account %}{# internal #} Your event is now fully booked and payment will be processed by the finance department automatically. diff --git a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt index 98e1cdb8..7548cad4 100644 --- a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt @@ -1,5 +1,5 @@ Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}}, -Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for {{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. +Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. The TEC Rig Information Gathering System From 0a45b047a29d709d7f42f2f81d8b35aaf685cffe Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 17:34:49 +0100 Subject: [PATCH 124/275] Add warnings when editing an event that has already been sent to a client --- RIGS/rigboard.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index dbe7e6c7..e9597ab5 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -118,6 +118,14 @@ class EventUpdate(generic.UpdateView): value = form[field].value() if value is not None and value != '': context[field] = model.objects.get(pk=value) + + # If this event has already been emailed to a client, show a warning + if self.object.auth_request_at is not None: + messages.info(self.request, 'This event has already been sent to the client for authorisation, any changes you make will be visible to them immediately.') + + if hasattr(self.object, 'authorised'): + messages.warning(self.request, 'This event has already been authorised by client, any changes to price will require reauthorisation.') + return context def get_success_url(self): From 75a3059c88c0e599ea187035d53bff9093ebc964 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 18:02:29 +0100 Subject: [PATCH 125/275] Add failing duplicate test --- RIGS/test_functional.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index d8ea81f6..4cd23526 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -495,7 +495,10 @@ class EventTest(LiveServerTestCase): def testEventDuplicate(self): testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), - description="start future no end") + description="start future no end", + auth_request_by=self.profile, + auth_request_at=self.create_datetime(2015, 06, 04, 10, 00), + auth_request_to="some@email.address") item1 = models.EventItem( event=testEvent, @@ -549,6 +552,13 @@ class EventTest(LiveServerTestCase): # Attempt to save save.click() + newEvent = models.Event.objects.latest('pk') + + self.assertEqual(newEvent.auth_request_to, None) + self.assertEqual(newEvent.auth_request_by, None) + self.assertEqual(newEvent.auth_request_at, None) + + self.assertFalse(hasattr(newEvent, 'authorised')) self.assertNotIn("N%05d"%testEvent.pk, self.browser.find_element_by_xpath('//h1').text) self.assertNotIn("Event data duplicated but not yet saved", self.browser.find_element_by_id('content').text) # Check info message not visible From 4d316c7a4a9857409fde0320d3142ac39ecfd0e5 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 18 May 2017 18:02:44 +0100 Subject: [PATCH 126/275] Stop authorisation information being duplicated with an event --- RIGS/rigboard.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index e9597ab5..e4c124c2 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -138,6 +138,11 @@ class EventDuplicate(EventUpdate): new = copy.copy(old) # Make a copy of the object in memory new.based_on = old # Make the new event based on the old event + # Remove all the authorisation information from the new event + new.auth_request_to = None + new.auth_request_by = None + new.auth_request_at = None + if self.request.method in ( 'POST', 'PUT'): # This only happens on save (otherwise items won't display in editor) new.pk = None # This means a new event will be created on save, and all items will be re-created From c6b7bbc219da9fab3600e20bbdce921c5d16e4f6 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 23 May 2017 18:19:02 +0100 Subject: [PATCH 127/275] Change to just using online auth for internal clients. This effectively reverts 067e03b. --- RIGS/forms.py | 12 +--- RIGS/migrations/0025_eventauthorisation.py | 1 - .../migrations/0028_migrate_purchase_order.py | 48 ------------- .../0029_eventauthorisation_sent_by.py | 2 +- RIGS/models.py | 8 ++- RIGS/rigboard.py | 12 +--- RIGS/templates/RIGS/event_detail.html | 24 ++++--- RIGS/templates/RIGS/event_detail_buttons.html | 49 +++++++------ RIGS/templates/RIGS/event_form.html | 9 +++ RIGS/templates/RIGS/event_invoice.html | 20 +++--- RIGS/templates/RIGS/event_print_page.xml | 20 +++--- .../RIGS/eventauthorisation_form.html | 71 ++++++++----------- .../RIGS/eventauthorisation_success.html | 9 +-- RIGS/templates/RIGS/invoice_detail.html | 4 +- RIGS/templates/RIGS/invoice_list.html | 4 +- RIGS/test_functional.py | 13 +++- 16 files changed, 123 insertions(+), 183 deletions(-) delete mode 100644 RIGS/migrations/0028_migrate_purchase_order.py diff --git a/RIGS/forms.py b/RIGS/forms.py index c49f2433..95576ca3 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -144,7 +144,7 @@ class EventForm(forms.ModelForm): fields = ['is_rig', 'name', 'venue', 'start_time', 'end_date', 'start_date', 'end_time', 'meet_at', 'access_at', 'description', 'notes', 'mic', 'person', 'organisation', 'dry_hire', 'checked_in_by', 'status', - 'collector'] + 'purchase_order', 'collector'] class BaseClientEventAuthorisationForm(forms.ModelForm): @@ -171,15 +171,5 @@ class InternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): fields = ('tos', 'name', 'amount', 'uni_id', 'account_code') -class ExternalClientEventAuthorisationForm(BaseClientEventAuthorisationForm): - def __init__(self, **kwargs): - super(ExternalClientEventAuthorisationForm, self).__init__(**kwargs) - self.fields['po'].required = True - - class Meta: - model = models.EventAuthorisation - fields = ('tos', 'name', 'amount', 'po') - - class EventAuthorisationRequestForm(forms.Form): email = forms.EmailField(required=True, label='Authoriser Email') diff --git a/RIGS/migrations/0025_eventauthorisation.py b/RIGS/migrations/0025_eventauthorisation.py index 88f38ff4..2065c11d 100644 --- a/RIGS/migrations/0025_eventauthorisation.py +++ b/RIGS/migrations/0025_eventauthorisation.py @@ -19,7 +19,6 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=255)), ('uni_id', models.CharField(max_length=10, null=True, verbose_name=b'University ID', blank=True)), ('account_code', models.CharField(max_length=50, null=True, blank=True)), - ('po', models.CharField(max_length=255, null=True, verbose_name=b'purchase order', blank=True)), ('amount', models.DecimalField(verbose_name=b'authorisation amount', max_digits=10, decimal_places=2)), ('created_at', models.DateTimeField(auto_now_add=True)), ('event', models.ForeignKey(related_name='authroisations', to='RIGS.Event')), diff --git a/RIGS/migrations/0028_migrate_purchase_order.py b/RIGS/migrations/0028_migrate_purchase_order.py deleted file mode 100644 index 8a268208..00000000 --- a/RIGS/migrations/0028_migrate_purchase_order.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -from django.db.models import F, Sum, DecimalField - - -def POs_forward(apps, schema_editor): - VatRate = apps.get_model('RIGS', 'VatRate') - Event = apps.get_model('RIGS', 'Event') - EventItem = apps.get_model('RIGS', 'EventItem') - EventAuthorisation = apps.get_model('RIGS', 'EventAuthorisation') - db_alias = schema_editor.connection.alias - for event in Event.objects.using(db_alias).filter(purchase_order__isnull=False): - sum_total = EventItem.objects.filter(event=event).aggregate( - sum_total=Sum(models.F('cost') * F('quantity'), - output_field=DecimalField( - max_digits=10, - decimal_places=2) - ) - )['sum_total'] - - vat = VatRate.objects.using(db_alias).filter(start_at__lte=event.start_date).latest() - total = sum_total + sum_total * vat.rate - - EventAuthorisation.objects.using(db_alias).create(event=event, name='LEGACY', - email='treasurer@nottinghamtec.co.uk', - amount=total, - po=event.purchase_order) - - -def POs_reverse(apps, schema_editor): - EventAuthorisation = apps.get_model('RIGS', 'EventAuthorisation') - db_alias = schema_editor.connection.alias - for auth in EventAuthorisation.objects.using(db_alias).filter(po__isnull=False): - auth.event.purchase_order = auth.po - auth.delete() - - -class Migration(migrations.Migration): - dependencies = [ - ('RIGS', '0027_eventauthorisation_event_singular'), - ] - - operations = [ - migrations.RunPython(POs_forward, POs_reverse), - migrations.RemoveField(model_name='event', name='purchase_order') - ] diff --git a/RIGS/migrations/0029_eventauthorisation_sent_by.py b/RIGS/migrations/0029_eventauthorisation_sent_by.py index 80c86299..592bc968 100644 --- a/RIGS/migrations/0029_eventauthorisation_sent_by.py +++ b/RIGS/migrations/0029_eventauthorisation_sent_by.py @@ -8,7 +8,7 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('RIGS', '0028_migrate_purchase_order'), + ('RIGS', '0027_eventauthorisation_event_singular'), ] operations = [ diff --git a/RIGS/models.py b/RIGS/models.py index ebb31ca6..c1a33eb8 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -329,6 +329,7 @@ class Event(models.Model, RevisionMixin): # Monies payment_method = models.CharField(max_length=255, blank=True, null=True) payment_received = models.CharField(max_length=255, blank=True, null=True) + purchase_order = models.CharField(max_length=255, blank=True, null=True, verbose_name='PO') collector = models.CharField(max_length=255, blank=True, null=True, verbose_name='collected by') # Authorisation request details @@ -388,7 +389,7 @@ class Event(models.Model, RevisionMixin): @property def authorised(self): - return self.authorisation.amount == self.total + return not self.internal and self.purchase_order or self.authorisation.amount == self.total @property def has_start_time(self): @@ -450,6 +451,10 @@ class Event(models.Model, RevisionMixin): else: return endDate + @property + def internal(self): + return self.organisation and self.organisation.union_account + objects = EventManager() def get_absolute_url(self): @@ -513,7 +518,6 @@ class EventAuthorisation(models.Model, RevisionMixin): name = models.CharField(max_length=255) uni_id = models.CharField(max_length=10, blank=True, null=True, verbose_name="University ID") account_code = models.CharField(max_length=50, blank=True, null=True) - po = models.CharField(max_length=255, blank=True, null=True, verbose_name="purchase order") amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") sent_by = models.ForeignKey('RIGS.Profile') diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index e4c124c2..4546b502 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -137,6 +137,7 @@ class EventDuplicate(EventUpdate): old = super(EventDuplicate, self).get_object(queryset) # Get the object (the event you're duplicating) new = copy.copy(old) # Make a copy of the object in memory new.based_on = old # Make the new event based on the old event + new.purchase_order = None # Remove all the authorisation information from the new event new.auth_request_to = None @@ -256,20 +257,12 @@ class EventAuthorise(generic.UpdateView): return getattr(self.event, 'authorisation', None) def get_form_class(self): - if self.event.organisation is not None and self.event.organisation.union_account: - return forms.InternalClientEventAuthorisationForm - else: - return forms.ExternalClientEventAuthorisationForm + return forms.InternalClientEventAuthorisationForm def get_context_data(self, **kwargs): context = super(EventAuthorise, self).get_context_data(**kwargs) context['event'] = self.event - if self.get_form_class() is forms.InternalClientEventAuthorisationForm: - context['internal'] = True - else: - context['internal'] = False - context['tos_url'] = settings.TERMS_OF_HIRE_URL return context @@ -304,6 +297,7 @@ class EventAuthorise(generic.UpdateView): "This URL is invalid. Please ask your TEC contact for a new URL") return super(EventAuthorise, self).dispatch(request, *args, **kwargs) + class EventAuthorisationRequest(generic.FormView, generic.detail.SingleObjectMixin): model = models.Event form_class = forms.EventAuthorisationRequestForm diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 051aec4f..5f2885b5 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -71,7 +71,7 @@ {% endif %} - {% if event.is_rig %} + {% if event.is_rig and event.internal %}
Client Authorisation
@@ -183,17 +183,23 @@ {% if event.is_rig %}
 
-
Authorisation Request
-
{{ object.auth_request_to|yesno:"Yes,No" }}
+ {% if object.internal %} +
Authorisation Request
+
{{ object.auth_request_to|yesno:"Yes,No" }}
-
By
-
{{ object.auth_request_by }}
+
By
+
{{ object.auth_request_by }}
-
At
-
{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}
+
At
+
{{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}
-
To
-
{{ object.auth_request_to }}
+
To
+
{{ object.auth_request_to }}
+ + {% else %} +
PO
+
{{ object.purchase_order }}
+ {% endif %} {% endif %}
diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html index 9ad4b189..878684cc 100644 --- a/RIGS/templates/RIGS/event_detail_buttons.html +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -11,29 +11,32 @@ class="glyphicon glyphicon-duplicate">
{% if event.is_rig %} - - - - + {% if event.internal %} + + + + + {% endif %} + {% if perms.RIGS.add_invoice %}
+ +
+ + +
+ {% render_field form.purchase_order class+="form-control" %} +
+
diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index 71136b35..ec9755c0 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -61,21 +61,14 @@ {% endif %} - {% if object.organisation %} - {{ object.organisation.name }} -
- {{ object.organisation.union_account|yesno:'Internal,External' }} - {% else %} - {{ object.person.name }} -
- External - {% endif %} - + {{ object.organisation.name }} +
+ {{ object.internal|yesno:'Internal,External' }} {{ object.sum_total|floatformat:2 }}
- {{ object.authorisation.po }} + {% if not object.internal %}{{ object.purchase_order }}{% endif %} {% if object.mic %} @@ -86,7 +79,10 @@ {% endif %} -
+ diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 27317c57..73510b68 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -249,13 +249,12 @@ - {% if object.authorised %} - - Event authorised online by {{ object.authorisation.name }} ({{ object.authorisation.email }}) at - {{ object.authorisation.last_edited_at }}. - + {% if object.internal and object.authorised %} + + Event authorised online by {{ object.authorisation.name }} ({{ object.authorisation.email }}) at + {{ object.authorisation.last_edited_at }}. + - {% if object.organisation.union_account %} University ID @@ -268,19 +267,18 @@ £ {{ object.authorisation.amount|floatformat:2 }} - {% else %} + {% elif not object.internal and object.purchase_order %} + Purchase Order - Authorised Amount - {{ object.authorisation.po }} - £ {{ object.authorisation.amount|floatformat:2 }} + + {{ object.purchase_order }} {% endif %} - {% endif %} diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index 9182df41..06df386d 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -9,11 +9,11 @@ $('[data-toggle="tooltip"]').tooltip(); }); - $('form').on('submit', function() { - $('#loading-modal').modal({ - backdrop: 'static', - show: true - }); + $('form').on('submit', function () { + $('#loading-modal').modal({ + backdrop: 'static', + show: true + }); }); {% endblock %} @@ -54,17 +54,15 @@ {% csrf_token %} {% include 'form_errors.html' %}
- {% if internal %} -
-

- I agree that I am authorised to approve this event. I agree that I am the - President/Treasurer or account holder of the hirer, or that I - have the written permission of the - President/Treasurer or account holder of the hirer stating that - I can authorise this event. -

-
- {% endif %} +
+

+ I agree that I am authorised to approve this event. I agree that I am the + President/Treasurer or account holder of the hirer, or that I + have the written permission of the + President/Treasurer or account holder of the hirer stating that + I can authorise this event. +

+
- {% if internal %} -
- -
- {% render_field form.uni_id class+="form-control" %} -
+
+ +
+ {% render_field form.uni_id class+="form-control" %}
- {% endif %} +
- {% if internal %} -
- -
- {% render_field form.account_code class+="form-control" %} -
+
+ +
+ {% render_field form.account_code class+="form-control" %}
- {% else %} -
- -
- {% render_field form.po class+="form-control" %} -
-
- {% endif %} +
diff --git a/RIGS/templates/RIGS/eventauthorisation_success.html b/RIGS/templates/RIGS/eventauthorisation_success.html index 2b899076..cb24738a 100644 --- a/RIGS/templates/RIGS/eventauthorisation_success.html +++ b/RIGS/templates/RIGS/eventauthorisation_success.html @@ -49,13 +49,8 @@
- {% if internal %} -
Account code
-
{{ object.account_code }}
- {% else %} -
PO
-
{{ object.po }}
- {% endif %} +
Account code
+
{{ object.account_code }}
Authorised amount
£ {{ object.amount|floatformat:2 }}
diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index a55927f8..b305a04f 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -89,7 +89,7 @@ {% endif %} - {% if object.event.organisation.union_account %} + {% if object.event.internal %} {# internal #}
Uni ID
{{ object.event.authorisation.uni_id }}
@@ -98,7 +98,7 @@
{{ object.event.authorisation.account_code }}
{% else %}
PO
-
{{ object.event.authorisation.po }}
+
{{ object.event.purchase_order }}
{% endif %}
Authorised at
diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 62012238..63e61e11 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -50,7 +50,7 @@ {% if object.event.organisation %} {{ object.event.organisation.name }}
- {{ object.event.organisation.union_account|yesno:'Internal,External' }} + {{ object.event.internal|yesno:'Internal,External' }} {% else %} {{ object.event.person.name }}
@@ -62,7 +62,7 @@ {{ object.balance|floatformat:2 }}
- {{ object.event.authorisation.po }} + {{ object.event.purchase_order }} diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 4cd23526..27675442 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -496,6 +496,7 @@ class EventTest(LiveServerTestCase): testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL, start_date=date.today() + timedelta(days=6), description="start future no end", + purchase_order='TESTPO', auth_request_by=self.profile, auth_request_at=self.create_datetime(2015, 06, 04, 10, 00), auth_request_to="some@email.address") @@ -570,6 +571,10 @@ class EventTest(LiveServerTestCase): self.assertIn("Test Item 3", table.text) infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') + self.assertIn("N0000%d" % testEvent.pk, + infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO hasn't carried through + self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) self.assertIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) @@ -578,6 +583,10 @@ class EventTest(LiveServerTestCase): #Check that based-on hasn't crept into the old event infoPanel = self.browser.find_element_by_xpath('//div[contains(text(), "Event Info")]/..') + self.assertNotIn("N0000%d" % testEvent.pk, + infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) + # Check the PO remains on the old event + self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text) self.assertNotIn("N%05d"%testEvent.pk, infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text) @@ -1108,7 +1117,7 @@ class ClientEventAuthorisationTest(TestCase): self.assertContains(response, "Terms of Hire") response = self.client.post(self.url) - self.assertContains(response, "This field is required.", 4) + self.assertContains(response, "This field is required.", 5) data = self.auth_data data['amount'] = self.event.total + 1 @@ -1142,7 +1151,7 @@ class ClientEventAuthorisationTest(TestCase): def test_duplicate_warning(self): auth = models.EventAuthorisation.objects.create(event=self.event, name='Test ABC', email='dupe@functional.test', - po='ABC12345', amount=self.event.total, sent_by=self.profile) + amount=self.event.total, sent_by=self.profile) response = self.client.get(self.url) self.assertContains(response, 'This event has already been authorised.') From d85ebb63a1ec90da2215cd7a90d608f4abd8c6c0 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 23 May 2017 18:41:06 +0100 Subject: [PATCH 128/275] Minor PDF styling fixes --- RIGS/templates/RIGS/event_print.xml | 5 ++--- RIGS/templates/RIGS/event_print_page.xml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/RIGS/templates/RIGS/event_print.xml b/RIGS/templates/RIGS/event_print.xml index ad441a21..0b5a44b1 100644 --- a/RIGS/templates/RIGS/event_print.xml +++ b/RIGS/templates/RIGS/event_print.xml @@ -103,7 +103,7 @@ [Page of ] - [Paperwork generated{% if current_user %}by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + [Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] @@ -116,11 +116,10 @@ - {% if not invoice %}[{{ copy }} Copy]{% endif %} [Page of ] - [Paperwork generated{% if current_user %}by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + [Paperwork generated {% if current_user %}by{{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 73510b68..4e2d194f 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -268,7 +268,7 @@ {% elif not object.internal and object.purchase_order %} - + Purchase Order From 7fdafd854e77b9611fbb9e211ca329f154ca4527 Mon Sep 17 00:00:00 2001 From: Tom Price Date: Tue, 23 May 2017 19:18:19 +0100 Subject: [PATCH 129/275] Add payments to invoice PDF --- RIGS/templates/RIGS/event_print_page.xml | 97 +++++++++++++++++++----- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 4e2d194f..9b8362eb 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -34,6 +34,13 @@ {{ invoice.invoice_date|date:"d/m/Y" }} + + {% if not object.internal %} + + PO + {{ object.purchase_order }} + + {% endif %} {% elif quote %} @@ -210,19 +217,78 @@ - - - Total - - - - - £ {{ object.total|floatformat:2 }} - - + {% if invoice %} + Total + £ {{ object.total|floatformat:2 }} + {% else %} + + + Total + + + + + £ {{ object.total|floatformat:2 }} + + + {% endif %} + +{% if invoice %} + + +

Payments

+ + + + + Method + + + + + Date + + + + + Amount + + + + {% for payment in object.invoice.payment_set.all %} + + {{ payment.get_method_display }} + {{ payment.date }} + £ {{ payment.amount|floatformat:2 }} + + {% endfor %} + + + + + Payment Total + £ {{ object.invoice.payment_total|floatformat:2 }} + + + + + + Balance (ex. VAT) + + + + + £ {{ object.invoice.balance|floatformat:2 }} + + + + +
+{% endif %} + {% if quote %} @@ -267,17 +333,6 @@ £ {{ object.authorisation.amount|floatformat:2 }} - {% elif not object.internal and object.purchase_order %} - - - - Purchase Order - - - - {{ object.purchase_order }} - - {% endif %} From 7cb9e97ecb776d4610dd3ef43e113b901632cbec Mon Sep 17 00:00:00 2001 From: Tom Price Date: Wed, 24 May 2017 16:27:31 +0100 Subject: [PATCH 130/275] Fix text on quote paperwork for external clients Actually finish fixing PDF footer formatting. --- RIGS/templates/RIGS/event_print.xml | 2 +- RIGS/templates/RIGS/event_print_page.xml | 32 +++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/RIGS/templates/RIGS/event_print.xml b/RIGS/templates/RIGS/event_print.xml index 0b5a44b1..f91409ac 100644 --- a/RIGS/templates/RIGS/event_print.xml +++ b/RIGS/templates/RIGS/event_print.xml @@ -119,7 +119,7 @@ [Page of ] - [Paperwork generated {% if current_user %}by{{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] + [Paperwork generated{% if current_user %} by {{current_user.name}} |{% endif %} {% now "d/m/Y H:i" %} | {{object.current_version_id}}] diff --git a/RIGS/templates/RIGS/event_print_page.xml b/RIGS/templates/RIGS/event_print_page.xml index 9b8362eb..4b1ddfdf 100644 --- a/RIGS/templates/RIGS/event_print_page.xml +++ b/RIGS/templates/RIGS/event_print_page.xml @@ -207,16 +207,22 @@ {% if quote %} - - The full hire fee is payable at least 10 days before the event. - + + This quote is valid for 30 days unless otherwise arranged. + {% endif %} VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}% £ {{ object.vat|floatformat:2 }} - + + {% if quote %} + + The full hire fee is payable at least 10 days before the event. + + {% endif %} + {% if invoice %} Total £ {{ object.total|floatformat:2 }} @@ -290,14 +296,22 @@ {% endif %} - + > {% if quote %} + - Bookings will - not - be confirmed until the event is authorised online. - + {% if object.internal %} + Bookings will + not + be confirmed until the event is authorised online. + + {% else %} + Bookings will + not + be confirmed until we have received written confirmation and a Purchase Order. + + {% endif %} From bdd7f02fe2058fcc6d861f8ec47e9908a0e5556c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 25 May 2017 13:09:52 +0100 Subject: [PATCH 131/275] Add EventAuthorisation to the activity feed --- RIGS/models.py | 7 +++++++ RIGS/templates/RIGS/object_button.html | 2 +- RIGS/versioning.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index c1a33eb8..84b8ba39 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -521,6 +521,13 @@ class EventAuthorisation(models.Model, RevisionMixin): amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="authorisation amount") sent_by = models.ForeignKey('RIGS.Profile') + def get_absolute_url(self): + return reverse_lazy('event_detail', kwargs={'pk': self.event.pk}) + + @property + def activity_feed_string(self): + return unicode("N%05d" % self.event.pk + ' (requested by ' + self.sent_by.initials + ')') + @python_2_unicode_compatible class Invoice(models.Model): diff --git a/RIGS/templates/RIGS/object_button.html b/RIGS/templates/RIGS/object_button.html index 92d4ed5a..be907795 100644 --- a/RIGS/templates/RIGS/object_button.html +++ b/RIGS/templates/RIGS/object_button.html @@ -1,4 +1,4 @@ {% load to_class_name from filters %} {# pass in variable "object" to this template #} -
{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{object.name}}' \ No newline at end of file +{% if object.is_rig == False %}Non-rig{% elif object.dry_hire %}Dry Hire{% elif object.is_rig %}Rig{%else%}{{object|to_class_name}}{% endif %} | '{{ object.activity_feed_string|default:object.name }}' \ No newline at end of file diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 65d4328a..958b3a82 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -241,7 +241,7 @@ class ActivityTable(generic.ListView): paginate_by = 25 def get_queryset(self): - versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation]) + versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) return versions def get_context_data(self, **kwargs): @@ -265,7 +265,7 @@ class ActivityFeed(generic.ListView): paginate_by = 25 def get_queryset(self): - versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation]) + versions = get_versions_for_model([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) return versions def get_context_data(self, **kwargs): From 7ec09fb77469c877cc6d3d473bd5d082c7898581 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 25 May 2017 13:48:08 +0100 Subject: [PATCH 132/275] Use chrome natively on Travis, hopefully faster --- .travis.yml | 17 ++++++++++--- RIGS/test_functional.py | 54 +++++++---------------------------------- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e93cb50..1d2e7744 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,21 @@ python: cache: pip addons: - sauce_connect: - username: davidtaylorhq - access_key: - secure: "ibpjQ19LfjwfQruiJmX0j6NzyNwsS3PvRFdfXUYcfCEa9Eh20QQ/S8pOdFhRh70KIEuwN5oGuPqDkJPPTjkdY3/NCjuA7/NMTp14jAIX4XjpeNcsPFupp31vEy7KBuX4iAGpenrHJssFCwurpvrlWfwSOrk7bVZKaGUowVOXmyth1FSNQvr5c3YnlxmGvNzNBMMBDcJ3ixSlS9pBRLnHIJ1w3/f9Lx2uONkVMeGM6rVyuHholWvanIyNVYtO9JkXkoie6n1R3gNbXCyJdxSRn2OLppdryUaA0wUPJSu3hqEM3R5EsRDiFJszkJLTwSBG8x4k/dbqim7stjsu1qpUhCIG5mT6e+UI9auPi/5nlwlVmPhSq58qBP53vH3hs++02wjDlgvTGB1p4PqFblHhVaslaQ166bo9skGMZb0fXLlM1aCmmwFTpC5ofiPTSRTdJcljHG/d3JabKX03ME+nX2LFPIMnSLXgrjrfh2ppI6LFESiX3Z8jYUdsgTFeN3nQZ8U0kyb5X9Ay9YFnAaYD9OuxaqweTmqAJQj093GK38+79WMN2jnvEUzM1ZjI8Y4L/f3rHvhNIwYvZjQ+gJRhUqJh2Qruk7ke7uQ1oecxIqRHj8hIFEkuBcM3e86MkRiYQXXI9jOX3JrhI/jivAjFuw0flU2tjLNgM7tUYzjMyqk=" + apt: + sources: + - google-chrome + packages: + - google-chrome-stable + +before_install: + - "export DISPLAY=:99.0" + - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" install: + - wget http://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip + - unzip chromedriver_linux64.zip + - export PATH=$PATH:$(pwd) + - chmod +x chromedriver - pip install -r requirements.txt - pip install coveralls codeclimate-test-reporter diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 27675442..87e031a2 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -26,50 +26,14 @@ from django.conf import settings import sys -browsers = [{"platform": "macOS 10.12", - "browserName": "chrome", - "version": "latest"}, - ] +def create_browser(): + driver = webdriver.Chrome() + driver.maximize_window() + return driver - -def on_platforms(platforms): - if not os.environ.get("TRAVIS"): - platforms = {'local'} - - def decorator(base_class): - module = sys.modules[base_class.__module__].__dict__ - for i, platform in enumerate(platforms): - d = dict(base_class.__dict__) - d['desired_capabilities'] = platform - name = "%s_%s" % (base_class.__name__, i + 1) - module[name] = type(name, (base_class,), d) - - return decorator - - - - -def create_browser(test_name, desired_capabilities): - # return webdriver.Chrome() - if os.environ.get("TRAVIS"): - username = os.environ["SAUCE_USERNAME"] - access_key = os.environ["SAUCE_ACCESS_KEY"] - caps = {'browserName': desired_capabilities['browserName']} - caps['platform'] = desired_capabilities['platform'] - caps['version'] = desired_capabilities['version'] - caps["tunnel-identifier"] = os.environ["TRAVIS_JOB_NUMBER"] - caps["name"] = '#' + os.environ["TRAVIS_JOB_NUMBER"] + ": " + test_name - hub_url = "%s:%s@localhost:4445" % (username, access_key) - driver = webdriver.Remote(desired_capabilities=caps, command_executor="http://%s/wd/hub" % hub_url) - return driver - else: - return webdriver.Chrome() - - -@on_platforms(browsers) class UserRegistrationTest(LiveServerTestCase): def setUp(self): - self.browser = create_browser(self.id(), self.desired_capabilities) + self.browser = create_browser() self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' @@ -198,7 +162,6 @@ class UserRegistrationTest(LiveServerTestCase): # All is well -@on_platforms(browsers) class EventTest(LiveServerTestCase): def setUp(self): self.profile = models.Profile( @@ -209,7 +172,7 @@ class EventTest(LiveServerTestCase): self.vatrate = models.VatRate.objects.create(start_at='2014-03-05',rate=0.20,comment='test1') - self.browser = create_browser(self.id(), self.desired_capabilities) + self.browser = create_browser() self.browser.implicitly_wait(10) # Set implicit wait session wide # self.browser.maximize_window() @@ -622,6 +585,7 @@ class EventTest(LiveServerTestCase): self.browser.execute_script("document.getElementById('id_end_date').value='3015-04-23'") # Attempt to save - should fail + wait.until(animation_is_finished()) save.click() error = self.browser.find_element_by_xpath('//div[contains(@class, "alert-danger")]') @@ -726,6 +690,7 @@ class EventTest(LiveServerTestCase): self.browser.execute_script("document.getElementById('id_start_date').value='3015-04-24'") # Save the rig + wait.until(animation_is_finished()) save.click() detail_panel = self.browser.find_element_by_xpath("//div[@id='content']/div/div[6]/div/div") self.assertTrue(detail_panel.is_displayed()) @@ -841,7 +806,6 @@ class EventTest(LiveServerTestCase): tz = pytz.timezone(settings.TIME_ZONE) return tz.localize(datetime(year, month, day, hour, min)).astimezone(pytz.utc) -@on_platforms(browsers) class IcalTest(LiveServerTestCase): def setUp(self): self.all_events = set(range(1, 18)) @@ -904,7 +868,7 @@ class IcalTest(LiveServerTestCase): description="non rig today cancelled") - self.browser = create_browser(self.id(), self.desired_capabilities) + self.browser = create_browser() self.browser.implicitly_wait(3) # Set implicit wait session wide os.environ['RECAPTCHA_TESTING'] = 'True' From abb56af222405e57d6577b73085f6309d7ad22a0 Mon Sep 17 00:00:00 2001 From: Johnathan Graydon Date: Fri, 2 Jun 2017 12:52:01 +0100 Subject: [PATCH 133/275] Fix spelling in rig authorisation --- RIGS/templates/RIGS/eventauthorisation_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_form.html b/RIGS/templates/RIGS/eventauthorisation_form.html index 06df386d..e2ae78ce 100644 --- a/RIGS/templates/RIGS/eventauthorisation_form.html +++ b/RIGS/templates/RIGS/eventauthorisation_form.html @@ -108,7 +108,7 @@
+ title="In order to book an event you must agree to the TEC Terms of Hire.">
' + : ''; + var donebutton = this.multiple && this.options.doneButton ? + '
' + + '
' + + '' + + '
' + + '
' + : ''; var drop = - '
' + - '' + - ''; @@ -226,111 +519,198 @@ }, createView: function () { - var $drop = this.createDropdown(); - var $li = this.createLi(); - $drop.find('ul').append($li); + var $drop = this.createDropdown(), + li = this.createLi(); + + $drop.find('ul')[0].innerHTML = li; return $drop; }, reloadLi: function () { - //Remove all children. - this.destroyLi(); - //Re build - var $li = this.createLi(); - this.$menu.find('ul').append($li); - }, - - destroyLi: function () { - this.$menu.find('li').remove(); + // rebuild + var li = this.createLi(); + this.$menuInner[0].innerHTML = li; }, createLi: function () { var that = this, _li = [], - optID = 0; + optID = 0, + titleOption = document.createElement('option'), + liIndex = -1; // increment liIndex whenever a new
  • element is created to ensure liObj is correct // Helper functions /** * @param content * @param [index] * @param [classes] + * @param [optgroup] * @returns {string} */ - var generateLI = function (content, index, classes) { + var generateLI = function (content, index, classes, optgroup) { return '' + content + '
  • '; + ((typeof classes !== 'undefined' && '' !== classes) ? ' class="' + classes + '"' : '') + + ((typeof index !== 'undefined' && null !== index) ? ' data-original-index="' + index + '"' : '') + + ((typeof optgroup !== 'undefined' && null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') + + '>' + content + ''; }; /** * @param text * @param [classes] * @param [inline] - * @param [optgroup] + * @param [tokens] * @returns {string} */ - var generateA = function (text, classes, inline, optgroup) { - var normText = normalizeToBase(htmlEscape(text)); + var generateA = function (text, classes, inline, tokens) { return '' + text + - '' + - ''; + (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + + (inline ? ' style="' + inline + '"' : '') + + (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape($(text).html())) + '"' : '') + + (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') + + ' role="option">' + text + + '' + + ''; }; - this.$element.find('option').each(function () { + if (this.options.title && !this.multiple) { + // this option doesn't create a new
  • element, but does add a new option, so liIndex is decreased + // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended + liIndex--; + + if (!this.$element.find('.bs-title-option').length) { + // Use native JS to prepend option (faster) + var element = this.$element[0]; + titleOption.className = 'bs-title-option'; + titleOption.innerHTML = this.options.title; + titleOption.value = ''; + element.insertBefore(titleOption, element.firstChild); + // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. + // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, + // if so, the select will have the data-selected attribute + var $opt = $(element.options[element.selectedIndex]); + if ($opt.attr('selected') === undefined && this.$element.data('selected') === undefined) { + titleOption.selected = true; + } + } + } + + var $selectOptions = this.$element.find('option'); + + $selectOptions.each(function (index) { var $this = $(this); + liIndex++; + + if ($this.hasClass('bs-title-option')) return; + // Get the class and text for the option - var optionClass = $this.attr('class') || '', - inline = $this.attr('style'), + var optionClass = this.className || '', + inline = htmlEscape(this.style.cssText), text = $this.data('content') ? $this.data('content') : $this.html(), - subtext = typeof $this.data('subtext') !== 'undefined' ? '' + $this.data('subtext') + '' : '', + tokens = $this.data('tokens') ? $this.data('tokens') : null, + subtext = typeof $this.data('subtext') !== 'undefined' ? '' + $this.data('subtext') + '' : '', icon = typeof $this.data('icon') !== 'undefined' ? ' ' : '', - isDisabled = $this.is(':disabled') || $this.parent().is(':disabled'), - index = $this[0].index; + $parent = $this.parent(), + isOptgroup = $parent[0].tagName === 'OPTGROUP', + isOptgroupDisabled = isOptgroup && $parent[0].disabled, + isDisabled = this.disabled || isOptgroupDisabled, + prevHiddenIndex; + if (icon !== '' && isDisabled) { icon = '' + icon + ''; } + if (that.options.hideDisabled && (isDisabled && !isOptgroup || isOptgroupDisabled)) { + // set prevHiddenIndex - the index of the first hidden option in a group of hidden options + // used to determine whether or not a divider should be placed after an optgroup if there are + // hidden options between the optgroup and the first visible option + prevHiddenIndex = $this.data('prevHiddenIndex'); + $this.next().data('prevHiddenIndex', (prevHiddenIndex !== undefined ? prevHiddenIndex : index)); + + liIndex--; + return; + } + if (!$this.data('content')) { // Prepend any icon and append any subtext to the main text. text = icon + '' + text + subtext + ''; } - if (that.options.hideDisabled && isDisabled) { - return; - } + if (isOptgroup && $this.data('divider') !== true) { + if (that.options.hideDisabled && isDisabled) { + if ($parent.data('allOptionsDisabled') === undefined) { + var $options = $parent.children(); + $parent.data('allOptionsDisabled', $options.filter(':disabled').length === $options.length); + } + + if ($parent.data('allOptionsDisabled')) { + liIndex--; + return; + } + } + + var optGroupClass = ' ' + $parent[0].className || ''; - if ($this.parent().is('optgroup') && $this.data('divider') !== true) { if ($this.index() === 0) { // Is it the first option of the optgroup? optID += 1; // Get the opt group label - var label = $this.parent().attr('label'); - var labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '' + $this.parent().data('subtext') + '' : ''; - var labelIcon = $this.parent().data('icon') ? ' ' : ''; - label = labelIcon + '' + label + labelSubtext + ''; + var label = $parent[0].label, + labelSubtext = typeof $parent.data('subtext') !== 'undefined' ? '' + $parent.data('subtext') + '' : '', + labelIcon = $parent.data('icon') ? ' ' : ''; + + label = labelIcon + '' + htmlEscape(label) + labelSubtext + ''; if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? - _li.push(generateLI('', null, 'divider')); + liIndex++; + _li.push(generateLI('', null, 'divider', optID + 'div')); } - - _li.push(generateLI(label, null, 'dropdown-header')); + liIndex++; + _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID)); } - _li.push(generateLI(generateA(text, 'opt ' + optionClass, inline, optID), index)); + if (that.options.hideDisabled && isDisabled) { + liIndex--; + return; + } + + _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID)); } else if ($this.data('divider') === true) { _li.push(generateLI('', index, 'divider')); } else if ($this.data('hidden') === true) { - _li.push(generateLI(generateA(text, optionClass, inline), index, 'hide is-hidden')); + // set prevHiddenIndex - the index of the first hidden option in a group of hidden options + // used to determine whether or not a divider should be placed after an optgroup if there are + // hidden options between the optgroup and the first visible option + prevHiddenIndex = $this.data('prevHiddenIndex'); + $this.next().data('prevHiddenIndex', (prevHiddenIndex !== undefined ? prevHiddenIndex : index)); + + _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden')); } else { - _li.push(generateLI(generateA(text, optionClass, inline), index)); + var showDivider = this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP'; + + // if previous element is not an optgroup and hideDisabled is true + if (!showDivider && that.options.hideDisabled) { + prevHiddenIndex = $this.data('prevHiddenIndex'); + + if (prevHiddenIndex !== undefined) { + // select the element **before** the first hidden element in the group + var prevHidden = $selectOptions.eq(prevHiddenIndex)[0].previousElementSibling; + + if (prevHidden && prevHidden.tagName === 'OPTGROUP' && !prevHidden.disabled) { + showDivider = true; + } + } + } + + if (showDivider) { + liIndex++; + _li.push(generateLI('', null, 'divider', optID + 'div')); + } + _li.push(generateLI(generateA(text, optionClass, inline, tokens), index)); } + + that.liObj[index] = liIndex; }); //If we are not multiple, we don't have a selected item, and we don't have a title, select the first element so something is set in the button @@ -338,7 +718,7 @@ this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); } - return $(_li.join('')); + return _li.join(''); }, findLis: function () { @@ -350,33 +730,44 @@ * @param [updateLi] defaults to true */ render: function (updateLi) { - var that = this; + var that = this, + notDisabled, + $selectOptions = this.$element.find('option'); //Update the LI to match the SELECT if (updateLi !== false) { - this.$element.find('option').each(function (index) { - that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled')); - that.setSelected(index, $(this).is(':selected')); + $selectOptions.each(function (index) { + var $lis = that.findLis().eq(that.liObj[index]); + + that.setDisabled(index, this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled, $lis); + that.setSelected(index, this.selected, $lis); }); } + this.togglePlaceholder(); + this.tabIndex(); - var notDisabled = this.options.hideDisabled ? ':not([disabled])' : ''; - var selectedItems = this.$element.find('option:selected' + notDisabled).map(function () { - var $this = $(this); - var icon = $this.data('icon') && that.options.showIcon ? ' ' : ''; - var subtext; - if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) { - subtext = ' ' + $this.data('subtext') + ''; - } else { - subtext = ''; - } - if ($this.data('content') && that.options.showContent) { - return $this.data('content'); - } else if (typeof $this.attr('title') !== 'undefined') { - return $this.attr('title'); - } else { - return icon + $this.html() + subtext; + + var selectedItems = $selectOptions.map(function () { + if (this.selected) { + if (that.options.hideDisabled && (this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled)) return; + + var $this = $(this), + icon = $this.data('icon') && that.options.showIcon ? ' ' : '', + subtext; + + if (that.options.showSubtext && $this.data('subtext') && !that.multiple) { + subtext = ' ' + $this.data('subtext') + ''; + } else { + subtext = ''; + } + if (typeof $this.attr('title') !== 'undefined') { + return $this.attr('title'); + } else if ($this.data('content') && that.options.showContent) { + return $this.data('content').toString(); + } else { + return icon + $this.html() + subtext; + } } }).toArray(); @@ -389,13 +780,15 @@ var max = this.options.selectedTextFormat.split('>'); if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) { notDisabled = this.options.hideDisabled ? ', [disabled]' : ''; - var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, + var totalCount = $selectOptions.not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText; title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString()); } } - this.options.title = this.$element.attr('title'); + if (this.options.title == undefined) { + this.options.title = this.$element.attr('title'); + } if (this.options.selectedTextFormat == 'static') { title = this.options.title; @@ -406,8 +799,11 @@ title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; } - this.$button.attr('title', htmlEscape(title)); - this.$newElement.find('.filter-option').html(title); + //strip all HTML tags and trim the result, then unescape any escaped tags + this.$button.attr('title', htmlUnescape($.trim(title.replace(/<[^>]*>?/g, '')))); + this.$button.children('.filter-option').html(title); + + this.$element.trigger('rendered.bs.select'); }, /** @@ -416,7 +812,7 @@ */ setStyle: function (style, status) { if (this.$element.attr('class')) { - this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, '')); + this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); } var buttonClass = style ? style : this.options.style; @@ -431,117 +827,251 @@ } }, - liHeight: function () { - if (this.options.size === false) return; + liHeight: function (refresh) { + if (!refresh && (this.options.size === false || this.sizeInfo)) return; - var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'), - $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'), - liHeight = $menuClone.find('li').not('.divider').not('.dropdown-header').filter(':visible').children('a').outerHeight(), - headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0, - searchHeight = this.options.liveSearch ? $menuClone.find('.bs-searchbox').outerHeight() : 0, - actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0; + var newElement = document.createElement('div'), + menu = document.createElement('div'), + menuInner = document.createElement('ul'), + divider = document.createElement('li'), + li = document.createElement('li'), + a = document.createElement('a'), + text = document.createElement('span'), + header = this.options.header && this.$menu.find('.popover-title').length > 0 ? this.$menu.find('.popover-title')[0].cloneNode(true) : null, + search = this.options.liveSearch ? document.createElement('div') : null, + actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, + doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null; - $selectClone.remove(); + text.className = 'text'; + newElement.className = this.$menu[0].parentNode.className + ' open'; + menu.className = 'dropdown-menu open'; + menuInner.className = 'dropdown-menu inner'; + divider.className = 'divider'; - this.$newElement - .data('liHeight', liHeight) - .data('headerHeight', headerHeight) - .data('searchHeight', searchHeight) - .data('actionsHeight', actionsHeight); + text.appendChild(document.createTextNode('Inner text')); + a.appendChild(text); + li.appendChild(a); + menuInner.appendChild(li); + menuInner.appendChild(divider); + if (header) menu.appendChild(header); + if (search) { + var input = document.createElement('input'); + search.className = 'bs-searchbox'; + input.className = 'form-control'; + search.appendChild(input); + menu.appendChild(search); + } + if (actions) menu.appendChild(actions); + menu.appendChild(menuInner); + if (doneButton) menu.appendChild(doneButton); + newElement.appendChild(menu); + + document.body.appendChild(newElement); + + var liHeight = a.offsetHeight, + headerHeight = header ? header.offsetHeight : 0, + searchHeight = search ? search.offsetHeight : 0, + actionsHeight = actions ? actions.offsetHeight : 0, + doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, + dividerHeight = $(divider).outerHeight(true), + // fall back to jQuery if getComputedStyle is not supported + menuStyle = typeof getComputedStyle === 'function' ? getComputedStyle(menu) : false, + $menu = menuStyle ? null : $(menu), + menuPadding = { + vert: parseInt(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + + parseInt(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + + parseInt(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + + parseInt(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), + horiz: parseInt(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + + parseInt(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + + parseInt(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + + parseInt(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) + }, + menuExtras = { + vert: menuPadding.vert + + parseInt(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + + parseInt(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, + horiz: menuPadding.horiz + + parseInt(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + + parseInt(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 + } + + document.body.removeChild(newElement); + + this.sizeInfo = { + liHeight: liHeight, + headerHeight: headerHeight, + searchHeight: searchHeight, + actionsHeight: actionsHeight, + doneButtonHeight: doneButtonHeight, + dividerHeight: dividerHeight, + menuPadding: menuPadding, + menuExtras: menuExtras + }; }, setSize: function () { this.findLis(); + this.liHeight(); + + if (this.options.header) this.$menu.css('padding-top', 0); + if (this.options.size === false) return; + var that = this, - menu = this.$menu, - menuInner = menu.find('.inner'), - selectHeight = this.$newElement.outerHeight(), - liHeight = this.$newElement.data('liHeight'), - headerHeight = this.$newElement.data('headerHeight'), - searchHeight = this.$newElement.data('searchHeight'), - actionsHeight = this.$newElement.data('actionsHeight'), - divHeight = this.$lis.filter('.divider').outerHeight(true), - menuPadding = parseInt(menu.css('padding-top')) + - parseInt(menu.css('padding-bottom')) + - parseInt(menu.css('border-top-width')) + - parseInt(menu.css('border-bottom-width')), - notDisabled = this.options.hideDisabled ? ', .disabled' : '', + $menu = this.$menu, + $menuInner = this.$menuInner, $window = $(window), - menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2, + selectHeight = this.$newElement[0].offsetHeight, + selectWidth = this.$newElement[0].offsetWidth, + liHeight = this.sizeInfo['liHeight'], + headerHeight = this.sizeInfo['headerHeight'], + searchHeight = this.sizeInfo['searchHeight'], + actionsHeight = this.sizeInfo['actionsHeight'], + doneButtonHeight = this.sizeInfo['doneButtonHeight'], + divHeight = this.sizeInfo['dividerHeight'], + menuPadding = this.sizeInfo['menuPadding'], + menuExtras = this.sizeInfo['menuExtras'], + notDisabled = this.options.hideDisabled ? '.disabled' : '', menuHeight, + menuWidth, + getHeight, + getWidth, selectOffsetTop, selectOffsetBot, - posVert = function () { - // JQuery defines a scrollTop function, but in pure JS it's a property - //noinspection JSValidateTypes - selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); - selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; - }; - posVert(); - if (this.options.header) menu.css('padding-top', 0); + selectOffsetLeft, + selectOffsetRight, + getPos = function() { + var pos = that.$newElement.offset(), + $container = $(that.options.container), + containerPos; - if (this.options.size == 'auto') { + if (that.options.container && !$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')); + containerPos.left += parseInt($container.css('borderLeftWidth')); + } else { + containerPos = { top: 0, left: 0 }; + } + + var winPad = that.options.windowPadding; + selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); + selectOffsetBot = $window.height() - selectOffsetTop - selectHeight - containerPos.top - winPad[2]; + selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); + selectOffsetRight = $window.width() - selectOffsetLeft - selectWidth - containerPos.left - winPad[1]; + selectOffsetTop -= winPad[0]; + selectOffsetLeft -= winPad[3]; + }; + + getPos(); + + if (this.options.size === 'auto') { var getSize = function () { var minHeight, - lisVis = that.$lis.not('.hide'); + hasClass = function (className, include) { + return function (element) { + if (include) { + return (element.classList ? element.classList.contains(className) : $(element).hasClass(className)); + } else { + return !(element.classList ? element.classList.contains(className) : $(element).hasClass(className)); + } + }; + }, + lis = that.$menuInner[0].getElementsByTagName('li'), + lisVisible = Array.prototype.filter ? Array.prototype.filter.call(lis, hasClass('hidden', false)) : that.$lis.not('.hidden'), + optGroup = Array.prototype.filter ? Array.prototype.filter.call(lisVisible, hasClass('dropdown-header', true)) : lisVisible.filter('.dropdown-header'); - posVert(); - menuHeight = selectOffsetBot - menuExtras; + getPos(); + menuHeight = selectOffsetBot - menuExtras.vert; + menuWidth = selectOffsetRight - menuExtras.horiz; + + if (that.options.container) { + if (!$menu.data('height')) $menu.data('height', $menu.height()); + getHeight = $menu.data('height'); + + if (!$menu.data('width')) $menu.data('width', $menu.width()); + getWidth = $menu.data('width'); + } else { + getHeight = $menu.height(); + getWidth = $menu.width(); + } if (that.options.dropupAuto) { - that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height())); - } - if (that.$newElement.hasClass('dropup')) { - menuHeight = selectOffsetTop - menuExtras; + that.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras.vert) < getHeight); } - if ((lisVis.length + lisVis.filter('.dropdown-header').length) > 3) { - minHeight = liHeight * 3 + menuExtras - 2; + if (that.$newElement.hasClass('dropup')) { + menuHeight = selectOffsetTop - menuExtras.vert; + } + + if (that.options.dropdownAlignRight === 'auto') { + $menu.toggleClass('dropdown-menu-right', selectOffsetLeft > selectOffsetRight && (menuWidth - menuExtras.horiz) < (getWidth - selectWidth)); + } + + if ((lisVisible.length + optGroup.length) > 3) { + minHeight = liHeight * 3 + menuExtras.vert - 2; } else { minHeight = 0; } - menu.css({ + $menu.css({ 'max-height': menuHeight + 'px', 'overflow': 'hidden', - 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + 'px' + 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px' }); - menuInner.css({ - 'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', + $menuInner.css({ + 'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert + 'px', 'overflow-y': 'auto', - 'min-height': Math.max(minHeight - menuPadding, 0) + 'px' + 'min-height': Math.max(minHeight - menuPadding.vert, 0) + 'px' }); }; getSize(); this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); - $(window).off('resize.getSize').on('resize.getSize', getSize); - $(window).off('scroll.getSize').on('scroll.getSize', getSize); - } else if (this.options.size && this.options.size != 'auto' && menu.find('li' + notDisabled).length > this.options.size) { - var optIndex = this.$lis.not('.divider' + notDisabled).find(' > *').slice(0, this.options.size).last().parent().index(); - var divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; - menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding; + $window.off('resize.getSize scroll.getSize').on('resize.getSize scroll.getSize', getSize); + } else if (this.options.size && this.options.size != 'auto' && this.$lis.not(notDisabled).length > this.options.size) { + var optIndex = this.$lis.not('.divider').not(notDisabled).children().slice(0, this.options.size).last().parent().index(), + divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; + menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; + + if (that.options.container) { + if (!$menu.data('height')) $menu.data('height', $menu.height()); + getHeight = $menu.data('height'); + } else { + getHeight = $menu.height(); + } + if (that.options.dropupAuto) { //noinspection JSUnusedAssignment - this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height())); + this.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras.vert) < getHeight); } - menu.css({'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow': 'hidden'}); - menuInner.css({'max-height': menuHeight - menuPadding + 'px', 'overflow-y': 'auto'}); + $menu.css({ + 'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px', + 'overflow': 'hidden', + 'min-height': '' + }); + $menuInner.css({ + 'max-height': menuHeight - menuPadding.vert + 'px', + 'overflow-y': 'auto', + 'min-height': '' + }); } }, setWidth: function () { - if (this.options.width == 'auto') { + if (this.options.width === 'auto') { this.$menu.css('min-width', '0'); - // Get correct width if element hidden - var selectClone = this.$newElement.clone().appendTo('body'); - var ulWidth = selectClone.find('> .dropdown-menu').css('width'); - var btnWidth = selectClone.css('width', 'auto').find('> button').css('width'); - selectClone.remove(); + // Get correct width if element is hidden + var $selectClone = this.$menu.parent().clone().appendTo('body'), + $selectClone2 = this.options.container ? this.$newElement.clone().appendTo('body') : $selectClone, + ulWidth = $selectClone.children('.dropdown-menu').outerWidth(), + btnWidth = $selectClone2.css('width', 'auto').children('button').outerWidth(); + + $selectClone.remove(); + $selectClone2.remove(); // Set width to whatever's larger, button title or longest option - this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px'); - } else if (this.options.width == 'fit') { + this.$newElement.css('width', Math.max(ulWidth, btnWidth) + 'px'); + } else if (this.options.width === 'fit') { // Remove inline min-width so width can be changed from 'auto' this.$menu.css('min-width', ''); this.$newElement.css('width', '').addClass('fit-width'); @@ -561,74 +1091,108 @@ }, selectPosition: function () { + this.$bsContainer = $('
    '); + var that = this, - drop = '
    ', - $drop = $(drop), + $container = $(this.options.container), pos, + containerPos, actualHeight, getPlacement = function ($element) { - $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); + that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); pos = $element.offset(); + + if (!$container.is('body')) { + containerPos = $container.offset(); + containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); + containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); + } else { + containerPos = { top: 0, left: 0 }; + } + actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; - $drop.css({ - 'top': pos.top + actualHeight, - 'left': pos.left, - 'width': $element[0].offsetWidth, - 'position': 'absolute' + + that.$bsContainer.css({ + 'top': pos.top - containerPos.top + actualHeight, + 'left': pos.left - containerPos.left, + 'width': $element[0].offsetWidth }); }; - this.$newElement.on('click', function () { + + this.$button.on('click', function () { + var $this = $(this); + if (that.isDisabled()) { return; } - getPlacement($(this)); - $drop.appendTo(that.options.container); - $drop.toggleClass('open', !$(this).hasClass('open')); - $drop.append(that.$menu); + + getPlacement(that.$newElement); + + that.$bsContainer + .appendTo(that.options.container) + .toggleClass('open', !$this.hasClass('open')) + .append(that.$menu); }); - $(window).resize(function () { + + $(window).on('resize scroll', function () { getPlacement(that.$newElement); }); - $(window).on('scroll', function () { - getPlacement(that.$newElement); - }); - $('html').on('click', function (e) { - if ($(e.target).closest(that.$newElement).length < 1) { - $drop.removeClass('open'); - } + + this.$element.on('hide.bs.select', function () { + that.$menu.data('height', that.$menu.height()); + that.$bsContainer.detach(); }); }, - setSelected: function (index, selected) { - this.findLis(); - this.$lis.filter('[data-original-index="' + index + '"]').toggleClass('selected', selected); + /** + * @param {number} index - the index of the option that is being changed + * @param {boolean} selected - true if the option is being selected, false if being deselected + * @param {JQuery} $lis - the 'li' element that is being modified + */ + setSelected: function (index, selected, $lis) { + if (!$lis) { + this.togglePlaceholder(); // check if setSelected is being called by changing the value of the select + $lis = this.findLis().eq(this.liObj[index]); + } + + $lis.toggleClass('selected', selected).find('a').attr('aria-selected', selected); }, - setDisabled: function (index, disabled) { - this.findLis(); + /** + * @param {number} index - the index of the option that is being disabled + * @param {boolean} disabled - true if the option is being disabled, false if being enabled + * @param {JQuery} $lis - the 'li' element that is being modified + */ + setDisabled: function (index, disabled, $lis) { + if (!$lis) { + $lis = this.findLis().eq(this.liObj[index]); + } + if (disabled) { - this.$lis.filter('[data-original-index="' + index + '"]').addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1); + $lis.addClass('disabled').children('a').attr('href', '#').attr('tabindex', -1).attr('aria-disabled', true); } else { - this.$lis.filter('[data-original-index="' + index + '"]').removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0); + $lis.removeClass('disabled').children('a').removeAttr('href').attr('tabindex', 0).attr('aria-disabled', false); } }, isDisabled: function () { - return this.$element.is(':disabled'); + return this.$element[0].disabled; }, checkDisabled: function () { var that = this; if (this.isDisabled()) { - this.$button.addClass('disabled').attr('tabindex', -1); + this.$newElement.addClass('disabled'); + this.$button.addClass('disabled').attr('tabindex', -1).attr('aria-disabled', true); } else { if (this.$button.hasClass('disabled')) { - this.$button.removeClass('disabled'); + this.$newElement.removeClass('disabled'); + this.$button.removeClass('disabled').attr('aria-disabled', false); } - if (this.$button.attr('tabindex') == -1) { - if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex'); + if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) { + this.$button.removeAttr('tabindex'); } } @@ -637,37 +1201,62 @@ }); }, + togglePlaceholder: function () { + var value = this.$element.val(); + this.$button.toggleClass('bs-placeholder', value === null || value === '' || (value.constructor === Array && value.length === 0)); + }, + tabIndex: function () { - if (this.$element.is('[tabindex]')) { + if (this.$element.data('tabindex') !== this.$element.attr('tabindex') && + (this.$element.attr('tabindex') !== -98 && this.$element.attr('tabindex') !== '-98')) { this.$element.data('tabindex', this.$element.attr('tabindex')); this.$button.attr('tabindex', this.$element.data('tabindex')); } + + this.$element.attr('tabindex', -98); }, clickListener: function () { - var that = this; + var that = this, + $document = $(document); - this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) { - e.stopPropagation(); - }); + $document.data('spaceSelect', false); - this.$newElement.on('click', function () { - that.setSize(); - if (!that.options.liveSearch && !that.multiple) { - setTimeout(function () { - that.$menu.find('.selected a').focus(); - }, 10); + this.$button.on('keyup', function (e) { + if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { + e.preventDefault(); + $document.data('spaceSelect', false); } }); - this.$menu.on('click', 'li a', function (e) { + this.$button.on('click', function () { + that.setSize(); + }); + + this.$element.on('shown.bs.select', function () { + if (!that.options.liveSearch && !that.multiple) { + that.$menuInner.find('.selected a').focus(); + } else if (!that.multiple) { + var selectedIndex = that.liObj[that.$element[0].selectedIndex]; + + if (typeof selectedIndex !== 'number' || that.options.size === false) return; + + // scroll to selected option + var offset = that.$lis.eq(selectedIndex)[0].offsetTop - that.$menuInner[0].offsetTop; + offset = offset - that.$menuInner[0].offsetHeight/2 + that.sizeInfo.liHeight/2; + that.$menuInner[0].scrollTop = offset; + } + }); + + this.$menuInner.on('click', 'li a', function (e) { var $this = $(this), clickedIndex = $this.parent().data('originalIndex'), prevValue = that.$element.val(), - prevIndex = that.$element.prop('selectedIndex'); + prevIndex = that.$element.prop('selectedIndex'), + triggerChange = true; // Don't close on multi choice menu - if (that.multiple) { + if (that.multiple && that.options.maxOptions !== 1) { e.stopPropagation(); } @@ -685,14 +1274,14 @@ if (!that.multiple) { // Deselect all others if not multi select box $options.prop('selected', false); $option.prop('selected', true); - that.$menu.find('.selected').removeClass('selected'); + that.$menuInner.find('.selected').removeClass('selected').find('a').attr('aria-selected', false); that.setSelected(clickedIndex, true); } else { // Toggle the one we have chosen if we are multi select. $option.prop('selected', !state); that.setSelected(clickedIndex, !state); $this.blur(); - if ((maxOptions !== false) || (maxOptionsGrp !== false)) { + if (maxOptions !== false || maxOptionsGrp !== false) { var maxReached = maxOptions < $options.filter(':selected').length, maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; @@ -700,19 +1289,17 @@ if (maxOptions && maxOptions == 1) { $options.prop('selected', false); $option.prop('selected', true); - that.$menu.find('.selected').removeClass('selected'); + that.$menuInner.find('.selected').removeClass('selected'); that.setSelected(clickedIndex, true); } else if (maxOptionsGrp && maxOptionsGrp == 1) { $optgroup.find('option:selected').prop('selected', false); $option.prop('selected', true); - var optgroupID = $this.data('optgroup'); - - that.$menu.find('.selected').has('a[data-optgroup="' + optgroupID + '"]').removeClass('selected'); - + var optgroupID = $this.parent().data('optgroup'); + that.$menuInner.find('[data-optgroup="' + optgroupID + '"]').removeClass('selected'); that.setSelected(clickedIndex, true); } else { - var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ? - that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText, + var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, + maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), $notify = $('
    '); @@ -729,11 +1316,13 @@ if (maxOptions && maxReached) { $notify.append($('
    ' + maxTxt + '
    ')); + triggerChange = false; that.$element.trigger('maxReached.bs.select'); } if (maxOptionsGrp && maxReachedGrp) { $notify.append($('
    ' + maxTxtGrp + '
    ')); + triggerChange = false; that.$element.trigger('maxReachedGrp.bs.select'); } @@ -749,50 +1338,54 @@ } } - if (!that.multiple) { + if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { that.$button.focus(); } else if (that.options.liveSearch) { that.$searchbox.focus(); } // Trigger select 'change' - if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { - that.$element.change(); + if (triggerChange) { + if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { + // $option.prop('selected') is current option state (selected/unselected). state is previous option state. + changed_arguments = [clickedIndex, $option.prop('selected'), state]; + that.$element + .triggerNative('change'); + } } } }); this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) { - if (e.target == this) { + if (e.currentTarget == this) { e.preventDefault(); e.stopPropagation(); - if (!that.options.liveSearch) { - that.$button.focus(); - } else { + if (that.options.liveSearch && !$(e.target).hasClass('close')) { that.$searchbox.focus(); + } else { + that.$button.focus(); } } }); - this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) { + this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { e.preventDefault(); e.stopPropagation(); - if (!that.options.liveSearch) { - that.$button.focus(); - } else { + if (that.options.liveSearch) { that.$searchbox.focus(); + } else { + that.$button.focus(); } }); this.$menu.on('click', '.popover-title .close', function () { - that.$button.focus(); + that.$button.click(); }); this.$searchbox.on('click', function (e) { e.stopPropagation(); }); - this.$menu.on('click', '.actions-btn', function (e) { if (that.options.liveSearch) { that.$searchbox.focus(); @@ -803,31 +1396,32 @@ e.preventDefault(); e.stopPropagation(); - if ($(this).is('.bs-select-all')) { + if ($(this).hasClass('bs-select-all')) { that.selectAll(); } else { that.deselectAll(); } - that.$element.change(); }); this.$element.change(function () { that.render(false); + that.$element.trigger('changed.bs.select', changed_arguments); + changed_arguments = null; }); }, liveSearchListener: function () { var that = this, - no_results = $('
  • '); + $no_results = $('
  • '); - this.$newElement.on('click.dropdown.data-api touchstart.dropdown.data-api', function () { - that.$menu.find('.active').removeClass('active'); + this.$button.on('click.dropdown.data-api', function () { + that.$menuInner.find('.active').removeClass('active'); if (!!that.$searchbox.val()) { that.$searchbox.val(''); - that.$lis.not('.is-hidden').removeClass('hide'); - if (!!no_results.parent().length) no_results.remove(); + that.$lis.not('.is-hidden').removeClass('hidden'); + if (!!$no_results.parent().length) $no_results.remove(); } - if (!that.multiple) that.$menu.find('.selected').addClass('active'); + if (!that.multiple) that.$menuInner.find('.selected').addClass('active'); setTimeout(function () { that.$searchbox.focus(); }, 10); @@ -838,33 +1432,64 @@ }); this.$searchbox.on('input propertychange', function () { + that.$lis.not('.is-hidden').removeClass('hidden'); + that.$lis.filter('.active').removeClass('active'); + $no_results.remove(); + if (that.$searchbox.val()) { - - if (that.options.searchAccentInsensitive) { - that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':aicontains(' + normalizeToBase(that.$searchbox.val()) + ')').parent().addClass('hide'); + var $searchBase = that.$lis.not('.is-hidden, .divider, .dropdown-header'), + $hideItems; + if (that.options.liveSearchNormalize) { + $hideItems = $searchBase.not(':a' + that._searchStyle() + '("' + normalizeToBase(that.$searchbox.val()) + '")'); } else { - that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide'); + $hideItems = $searchBase.not(':' + that._searchStyle() + '("' + that.$searchbox.val() + '")'); } - if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) { - if (!!no_results.parent().length) no_results.remove(); - no_results.html(that.options.noneResultsText + ' "' + htmlEscape(that.$searchbox.val()) + '"').show(); - that.$menu.find('li').last().after(no_results); - } else if (!!no_results.parent().length) { - no_results.remove(); - } + if ($hideItems.length === $searchBase.length) { + $no_results.html(that.options.noneResultsText.replace('{0}', '"' + htmlEscape(that.$searchbox.val()) + '"')); + that.$menuInner.append($no_results); + that.$lis.addClass('hidden'); + } else { + $hideItems.addClass('hidden'); - } else { - that.$lis.not('.is-hidden').removeClass('hide'); - if (!!no_results.parent().length) no_results.remove(); + var $lisVisible = that.$lis.not('.hidden'), + $foundDiv; + + // hide divider if first or last visible, or if followed by another divider + $lisVisible.each(function (index) { + var $this = $(this); + + if ($this.hasClass('divider')) { + if ($foundDiv === undefined) { + $this.addClass('hidden'); + } else { + if ($foundDiv) $foundDiv.addClass('hidden'); + $foundDiv = $this; + } + } else if ($this.hasClass('dropdown-header') && $lisVisible.eq(index + 1).data('optgroup') !== $this.data('optgroup')) { + $this.addClass('hidden'); + } else { + $foundDiv = null; + } + }); + if ($foundDiv) $foundDiv.addClass('hidden'); + + $searchBase.not('.hidden').first().addClass('active'); + that.$menuInner.scrollTop(0); + } } - - that.$menu.find('li.active').removeClass('active'); - that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus(); - $(this).focus(); }); }, + _searchStyle: function () { + var styles = { + begins: 'ibegins', + startsWith: 'ibegins' + }; + + return styles[this.options.liveSearchStyle] || 'icontains'; + }, + val: function (value) { if (typeof value !== 'undefined') { this.$element.val(value); @@ -876,29 +1501,65 @@ } }, - selectAll: function () { + changeAll: function (status) { + if (!this.multiple) return; + if (typeof status === 'undefined') status = true; + this.findLis(); - this.$lis.not('.divider').not('.disabled').not('.selected').filter(':visible').find('a').click(); + + var $options = this.$element.find('option'), + $lisVisible = this.$lis.not('.divider, .dropdown-header, .disabled, .hidden'), + lisVisLen = $lisVisible.length, + selectedOptions = []; + + if (status) { + if ($lisVisible.filter('.selected').length === $lisVisible.length) return; + } else { + if ($lisVisible.filter('.selected').length === 0) return; + } + + $lisVisible.toggleClass('selected', status); + + for (var i = 0; i < lisVisLen; i++) { + var origIndex = $lisVisible[i].getAttribute('data-original-index'); + selectedOptions[selectedOptions.length] = $options.eq(origIndex)[0]; + } + + $(selectedOptions).prop('selected', status); + + this.render(false); + + this.togglePlaceholder(); + + this.$element + .triggerNative('change'); + }, + + selectAll: function () { + return this.changeAll(true); }, deselectAll: function () { - this.findLis(); - this.$lis.not('.divider').not('.disabled').filter('.selected').filter(':visible').find('a').click(); + return this.changeAll(false); + }, + + toggle: function (e) { + e = e || window.event; + + if (e) e.stopPropagation(); + + this.$button.trigger('click'); }, keydown: function (e) { var $this = $(this), - $parent = ($this.is('input')) ? $this.parent().parent() : $this.parent(), + $parent = $this.is('input') ? $this.parent().parent() : $this.parent(), $items, that = $parent.data('this'), index, - next, - first, - last, - prev, - nextPrev, prevIndex, isActive, + selector = ':not(.disabled, .hidden, .dropdown-header, .divider)', keyCodeMap = { 32: ' ', 48: '0', @@ -950,93 +1611,58 @@ 105: '9' }; - if (that.options.liveSearch) $parent = $this.parent().parent(); - if (that.options.container) $parent = that.$menu; + isActive = that.$newElement.hasClass('open'); - $items = $('[role=menu] li a', $parent); - - isActive = that.$menu.parent().hasClass('open'); - - if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) { + if (!isActive && (e.keyCode >= 48 && e.keyCode <= 57 || e.keyCode >= 96 && e.keyCode <= 105 || e.keyCode >= 65 && e.keyCode <= 90)) { if (!that.options.container) { that.setSize(); that.$menu.parent().addClass('open'); isActive = true; } else { - that.$newElement.trigger('click'); + that.$button.trigger('click'); } that.$searchbox.focus(); + return; } if (that.options.liveSearch) { - if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) { + if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive) { e.preventDefault(); - that.$menu.parent().removeClass('open'); + e.stopPropagation(); + that.$menuInner.click(); that.$button.focus(); } - $items = $('[role=menu] li:not(.divider):not(.dropdown-header):visible', $parent); - if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) { - if ($items.filter('.active').length === 0) { - if (that.options.searchAccentInsensitive) { - $items = that.$newElement.find('li').filter(':aicontains(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')'); - } else { - $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')'); - } - } - } } - if (!$items.length) return; - if (/(38|40)/.test(e.keyCode.toString(10))) { - index = $items.index($items.filter(':focus')); - first = $items.parent(':not(.disabled):visible').first().index(); - last = $items.parent(':not(.disabled):visible').last().index(); - next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index(); - prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index(); - nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index(); - - if (that.options.liveSearch) { - $items.each(function (i) { - if ($(this).is(':not(.disabled)')) { - $(this).data('index', i); - } - }); - index = $items.index($items.filter('.active')); - first = $items.filter(':not(.disabled):visible').first().data('index'); - last = $items.filter(':not(.disabled):visible').last().data('index'); - next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index'); - prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index'); - nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index'); - } - - prevIndex = $this.data('prevIndex'); - - if (e.keyCode == 38) { - if (that.options.liveSearch) index -= 1; - if (index != nextPrev && index > prev) index = prev; - if (index < first) index = first; - if (index == prevIndex) index = last; - } - - if (e.keyCode == 40) { - if (that.options.liveSearch) index += 1; - if (index == -1) index = 0; - if (index != nextPrev && index < next) index = next; - if (index > last) index = last; - if (index == prevIndex) index = first; - } - - $this.data('prevIndex', index); + $items = that.$lis.filter(selector); + if (!$items.length) return; if (!that.options.liveSearch) { - $items.eq(index).focus(); + index = $items.index($items.find('a').filter(':focus').parent()); + } else { + index = $items.index($items.filter('.active')); + } + + prevIndex = that.$menuInner.data('prevIndex'); + + if (e.keyCode == 38) { + if ((that.options.liveSearch || index == prevIndex) && index != -1) index--; + if (index < 0) index += $items.length; + } else if (e.keyCode == 40) { + if (that.options.liveSearch || index == prevIndex) index++; + index = index % $items.length; + } + + that.$menuInner.data('prevIndex', index); + + if (!that.options.liveSearch) { + $items.eq(index).children('a').focus(); } else { e.preventDefault(); - if (!$this.is('.dropdown-toggle')) { - $items.removeClass('active'); - $items.eq(index).addClass('active').find('a').focus(); + if (!$this.hasClass('dropdown-toggle')) { + $items.removeClass('active').eq(index).addClass('active').children('a').focus(); $this.focus(); } } @@ -1046,11 +1672,10 @@ count, prevKey; - $items.each(function () { - if ($(this).parent().is(':not(.disabled)')) { - if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { - keyIndex.push($(this).parent().index()); - } + $items = that.$lis.filter(selector); + $items.each(function (i) { + if ($.trim($(this).children('a').text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { + keyIndex.push(i); } }); @@ -1068,16 +1693,23 @@ if (count > keyIndex.length) count = 1; } - $items.eq(keyIndex[count - 1]).focus(); + $items.eq(keyIndex[count - 1]).children('a').focus(); } // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) { if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault(); if (!that.options.liveSearch) { - $(':focus').click(); + var elem = $(':focus'); + elem.click(); + // Bring back focus for multiselects + elem.focus(); + // Prevent screen from scrolling if the user hit the spacebar + e.preventDefault(); + // Fixes spacebar selection of dropdown items in FF & IE + $(document).data('spaceSelect', true); } else if (!/(32)/.test(e.keyCode.toString(10))) { - that.$menu.find('.active a').click(); + that.$menuInner.find('.active a').click(); $this.focus(); } $(document).data('keycount', 0); @@ -1085,31 +1717,27 @@ if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) { that.$menu.parent().removeClass('open'); + if (that.options.container) that.$newElement.removeClass('open'); that.$button.focus(); } }, mobile: function () { - this.$element.addClass('mobile-device').appendTo(this.$newElement); - if (this.options.container) this.$menu.hide(); + this.$element.addClass('mobile-device'); }, refresh: function () { this.$lis = null; + this.liObj = {}; this.reloadLi(); this.render(); - this.setWidth(); - this.setStyle(); this.checkDisabled(); - this.liHeight(); - }, + this.liHeight(true); + this.setStyle(); + this.setWidth(); + if (this.$lis) this.$searchbox.trigger('propertychange'); - update: function () { - this.reloadLi(); - this.setWidth(); - this.setStyle(); - this.checkDisabled(); - this.liHeight(); + this.$element.trigger('refreshed.bs.select'); }, hide: function () { @@ -1123,37 +1751,46 @@ remove: function () { this.$newElement.remove(); this.$element.remove(); + }, + + destroy: function () { + this.$newElement.before(this.$element).remove(); + + if (this.$bsContainer) { + this.$bsContainer.remove(); + } else { + this.$menu.remove(); + } + + this.$element + .off('.bs.select') + .removeData('selectpicker') + .removeClass('bs-select-hidden selectpicker'); } }; // SELECTPICKER PLUGIN DEFINITION // ============================== - function Plugin(option, event) { + function Plugin(option) { // get the args of the outer function.. var args = arguments; // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them - // to get lost - //noinspection JSDuplicatedDeclaration - var _option = option, - option = args[0], - event = args[1]; - [].shift.apply(args); + // to get lost/corrupted in android 2.3 and IE9 #715 #775 + var _option = option; - // This fixes a bug in the js implementation on android 2.3 #715 - if (typeof option == 'undefined') { - option = _option; - } + [].shift.apply(args); var value; var chain = this.each(function () { var $this = $(this); if ($this.is('select')) { var data = $this.data('selectpicker'), - options = typeof option == 'object' && option; + options = typeof _option == 'object' && _option; if (!data) { var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options); - $this.data('selectpicker', (data = new Selectpicker(this, config, event))); + config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), $this.data().template, options.template); + $this.data('selectpicker', (data = new Selectpicker(this, config))); } else if (options) { for (var i in options) { if (options.hasOwnProperty(i)) { @@ -1162,11 +1799,11 @@ } } - if (typeof option == 'string') { - if (data[option] instanceof Function) { - value = data[option].apply(data, args); + if (typeof _option == 'string') { + if (data[_option] instanceof Function) { + value = data[_option].apply(data, args); } else { - value = data.options[option]; + value = data.options[_option]; } } } @@ -1193,8 +1830,8 @@ $(document) .data('keycount', 0) - .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', Selectpicker.prototype.keydown) - .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', function (e) { + .on('keydown.bs.select', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', Selectpicker.prototype.keydown) + .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', function (e) { e.stopPropagation(); }); diff --git a/RIGS/static/js/button.js b/RIGS/static/js/button.js old mode 100644 new mode 100755 index 4d569017..843b39c9 --- a/RIGS/static/js/button.js +++ b/RIGS/static/js/button.js @@ -1,8 +1,8 @@ /* ======================================================================== - * Bootstrap: button.js v3.3.2 + * Bootstrap: button.js v3.3.7 * http://getbootstrap.com/javascript/#buttons * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ @@ -19,7 +19,7 @@ this.isLoading = false } - Button.VERSION = '3.3.2' + Button.VERSION = '3.3.7' Button.DEFAULTS = { loadingText: 'loading...' @@ -31,7 +31,7 @@ var val = $el.is('input') ? 'val' : 'html' var data = $el.data() - state = state + 'Text' + state += 'Text' if (data.resetText == null) $el.data('resetText', $el[val]()) @@ -41,10 +41,10 @@ if (state == 'loadingText') { this.isLoading = true - $el.addClass(d).attr(d, d) + $el.addClass(d).attr(d, d).prop(d, true) } else if (this.isLoading) { this.isLoading = false - $el.removeClass(d).removeAttr(d) + $el.removeClass(d).removeAttr(d).prop(d, false) } }, this), 0) } @@ -56,15 +56,19 @@ if ($parent.length) { var $input = this.$element.find('input') if ($input.prop('type') == 'radio') { - if ($input.prop('checked') && this.$element.hasClass('active')) changed = false - else $parent.find('.active').removeClass('active') + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') } - if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') } else { this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') } - - if (changed) this.$element.toggleClass('active') } @@ -104,10 +108,15 @@ $(document) .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + var $btn = $(e.target).closest('.btn') Plugin.call($btn, 'toggle') - e.preventDefault() + if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { + // Prevent double click on radios, and the double selections (so cancellation) on checkboxes + e.preventDefault() + // The target component still receive the focus + if ($btn.is('input,button')) $btn.trigger('focus') + else $btn.find('input:visible,button:visible').first().trigger('focus') + } }) .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) diff --git a/RIGS/static/js/carousel.js b/RIGS/static/js/carousel.js old mode 100644 new mode 100755 index 450e1812..6ff954c9 --- a/RIGS/static/js/carousel.js +++ b/RIGS/static/js/carousel.js @@ -1,8 +1,8 @@ /* ======================================================================== - * Bootstrap: carousel.js v3.3.2 + * Bootstrap: carousel.js v3.3.7 * http://getbootstrap.com/javascript/#carousel * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ @@ -17,10 +17,10 @@ this.$element = $(element) this.$indicators = this.$element.find('.carousel-indicators') this.options = options - this.paused = - this.sliding = - this.interval = - this.$active = + this.paused = null + this.sliding = null + this.interval = null + this.$active = null this.$items = null this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) @@ -30,7 +30,7 @@ .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } - Carousel.VERSION = '3.3.2' + Carousel.VERSION = '3.3.7' Carousel.TRANSITION_DURATION = 600 diff --git a/RIGS/static/js/collapse.js b/RIGS/static/js/collapse.js old mode 100644 new mode 100755 index 2bc30e7b..12038693 --- a/RIGS/static/js/collapse.js +++ b/RIGS/static/js/collapse.js @@ -1,11 +1,12 @@ /* ======================================================================== - * Bootstrap: collapse.js v3.3.2 + * Bootstrap: collapse.js v3.3.7 * http://getbootstrap.com/javascript/#collapse * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +/* jshint latedef: false */ +function ($) { 'use strict'; @@ -16,7 +17,8 @@ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, Collapse.DEFAULTS, options) - this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]') + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') this.transitioning = null if (this.options.parent) { @@ -28,13 +30,12 @@ if (this.options.toggle) this.toggle() } - Collapse.VERSION = '3.3.2' + Collapse.VERSION = '3.3.7' Collapse.TRANSITION_DURATION = 350 Collapse.DEFAULTS = { - toggle: true, - trigger: '[data-toggle="collapse"]' + toggle: true } Collapse.prototype.dimension = function () { @@ -172,7 +173,7 @@ var data = $this.data('bs.collapse') var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) - if (!data && options.toggle && option == 'show') options.toggle = false + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) @@ -203,7 +204,7 @@ var $target = getTargetFromTrigger($this) var data = $target.data('bs.collapse') - var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this }) + var option = data ? 'toggle' : $this.data() Plugin.call($target, option) }) diff --git a/RIGS/static/js/dropdown.js b/RIGS/static/js/dropdown.js old mode 100644 new mode 100755 index 200e1c67..04e9c2de --- a/RIGS/static/js/dropdown.js +++ b/RIGS/static/js/dropdown.js @@ -1,8 +1,8 @@ /* ======================================================================== - * Bootstrap: dropdown.js v3.3.2 + * Bootstrap: dropdown.js v3.3.7 * http://getbootstrap.com/javascript/#dropdowns * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ @@ -19,7 +19,41 @@ $(element).on('click.bs.dropdown', this.toggle) } - Dropdown.VERSION = '3.3.2' + Dropdown.VERSION = '3.3.7' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } Dropdown.prototype.toggle = function (e) { var $this = $(this) @@ -34,7 +68,10 @@ if (!isActive) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { // if mobile we use a backdrop because click events don't delegate - $(' From 14836f135c8dac88c5318fa26bf17d3ce0f5d45d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 25 Feb 2019 21:29:14 +0000 Subject: [PATCH 231/275] Update subhire form location Requested by Jonny --- RIGS/templates/RIGS/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index 99355dc5..b5c4c511 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -27,7 +27,7 @@ TEC Wiki Pre-Event Risk Assessment Price List - Subhire Insurance Form + Subhire Insurance Form
    From bd28d2054ed4696848a333b6f1d0999c8161c89d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 20 Jun 2019 00:13:24 +0100 Subject: [PATCH 232/275] Remove autofocus from form --- RIGS/forms.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RIGS/forms.py b/RIGS/forms.py index dcab98aa..9caf3016 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -33,7 +33,12 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): return self.cleaned_data['initials'] -# Login form +# Embedded Login form - remove the autofocus +class EmbeddedAuthenticationForm(AuthenticationForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['username'].widget.attrs.pop('autofocus', None) + class PasswordReset(PasswordResetForm): captcha = ReCaptchaField(label='Captcha') From a18bb07d78a7aac459822d02f30616d3a76fe92a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 20 Jun 2019 00:15:16 +0100 Subject: [PATCH 233/275] Update views.py --- RIGS/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/views.py b/RIGS/views.py index 53a2a20d..aaaac7da 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -62,7 +62,7 @@ def login_embed(request, **kwargs): messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') request.method = 'GET' # Render the page without trying to login - return login(request, template_name="registration/login_embed.html") + return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm) """ From 7e06b5a1629cce9a412b2aab69c4d840d9765105 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 20 Jun 2019 00:29:10 +0100 Subject: [PATCH 234/275] Increase height of forum embeds --- RIGS/rigboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 82bb29dc..b697ad85 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -66,10 +66,10 @@ class EventOembed(generic.View): full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { - 'html': ''.format(full_url), + 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', - 'height': '250' + 'height': '350' } json = simplejson.JSONEncoderForHTML().encode(data) From b269069b6ac4ae660670053c194d3b10a3bbd29a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 20 Jun 2019 00:56:11 +0100 Subject: [PATCH 235/275] Improve embedding style --- RIGS/rigboard.py | 4 ++-- RIGS/static/css/print.css | 6 +++++- RIGS/static/css/screen.css | 8 ++++++-- RIGS/static/scss/screen.scss | 11 +++++++---- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index b697ad85..82bb29dc 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -66,10 +66,10 @@ class EventOembed(generic.View): full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) data = { - 'html': ''.format(full_url), + 'html': ''.format(full_url), 'version': '1.0', 'type': 'rich', - 'height': '350' + 'height': '250' } json = simplejson.JSONEncoderForHTML().encode(data) diff --git a/RIGS/static/css/print.css b/RIGS/static/css/print.css index b89b3fe5..2befadb3 100644 --- a/RIGS/static/css/print.css +++ b/RIGS/static/css/print.css @@ -1 +1,5 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]: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}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url(/static/fonts/glyphicons-halflings-regular.eot?1455294101);src:url(/static/fonts/glyphicons-halflings-regular.eot?&1455294101#iefix) format("embedded-opentype"),url(/static/fonts/glyphicons-halflings-regular.woff2?1455294101) format("woff2"),url(/static/fonts/glyphicons-halflings-regular.woff?1455294101) format("woff"),url(/static/fonts/glyphicons-halflings-regular.ttf?1455294101) format("truetype"),url(/static/fonts/glyphicons-halflings-regular.svg?1455294101#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,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}body{font-family:"Open Sans","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{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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;margin:-1px;padding:0;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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}mark,.mark{background-color:#fcf8e3;padding:.2em}.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,.initialism{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}.bg-primary{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;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%}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;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;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{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.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-left:15px;padding-right: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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.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{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}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"]{line-height:34px}input[type="date"].input-sm,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;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-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.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-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.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;font-size:12px;line-height:1.5;min-height:32px}.input-lg,.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-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.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;font-size:18px;line-height:1.33;min-height:38px}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{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;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.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:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.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}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default: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.focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary: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.focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success: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.focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info: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.focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning: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.focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.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,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,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 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.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-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.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;list-style:none;font-size:14px;text-align:left;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);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.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{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.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;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.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>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.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:before,.btn-toolbar:after{content:" ";display:table}.btn-toolbar:after{clear:both}.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-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,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,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .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:before,.btn-group-vertical>.btn-group:after{content:" ";display:table}.btn-group-vertical>.btn-group:after{clear:both}.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-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.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-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;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"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.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,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{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-bottom-right-radius:0;border-top-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-bottom-left-radius:0;border-top-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{margin-bottom:0;padding-left:0;list-style:none}.nav:before,.nav:after{content:" ";display:table}.nav:after{clear:both}.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;background-color:transparent;cursor:not-allowed}.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;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.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,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-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-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{content:" ";display:table}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{content:" ";display:table}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;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-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.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}@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;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;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;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-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@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 .form-control-static{display:inline-block}.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}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{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{background-color:#e7e7e7;color:#555}@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,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#aaa}.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:#aaa}.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:#090909}.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{background-color:#090909;color:#fff}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#aaa}.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:#090909}.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:#aaa}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#aaa}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,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{content:"/ ";padding:0 5px;color:#ccc}.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;line-height:1.42857143;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.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-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-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-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager:before,.pager:after{content:" ";display:table}.pager:after{clear:both}.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;background-color:#fff;cursor:not-allowed}.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}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.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;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron{padding:30px 15px;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,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right: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:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.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{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.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}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,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,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{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);background-image:-o-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);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);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-success{background-color:#5cb85c}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.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-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}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{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.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 .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .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,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-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,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.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 caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr: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:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 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,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{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-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom: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,.panel-group .panel-heading+.panel-collapse>.list-group{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,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,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;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(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;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);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.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:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.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,0.5);box-shadow:0 5px 15px rgba(0,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-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.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;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-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;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857143;text-align:left;background-color:#fff;background-clip:padding-box;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}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;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{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;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,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;-moz-perspective:1000;perspective:1000}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.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;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.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;line-height:1;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%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:transparent}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,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{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs{display:none !important}.visible-sm{display:none !important}.visible-md{display:none !important}.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}} +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 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%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !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^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url(/static/fonts/glyphicons-halflings-regular.eot?1543879568);src:url(/static/fonts/glyphicons-halflings-regular.eot?&1543879568#iefix) format("embedded-opentype"),url(/static/fonts/glyphicons-halflings-regular.woff2?1543879568) format("woff2"),url(/static/fonts/glyphicons-halflings-regular.woff?1543879568) format("woff"),url(/static/fonts/glyphicons-halflings-regular.ttf?1543879568) format("truetype"),url(/static/fonts/glyphicons-halflings-regular.svg?1543879568#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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}body{font-family:"Open Sans","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:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}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,.initialism{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover,a.text-primary:focus{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#428bca}a.bg-primary:hover,a.bg-primary:focus{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.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}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%}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-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;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}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}@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}.container-fluid:before,.container-fluid:after{display:table;content:" "}.container-fluid:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}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}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.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;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}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: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.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::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio: 0){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,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;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{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}.input-sm,.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-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg,.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-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.33}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.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-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,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 ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.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,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.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>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default:active:hover,.btn-default:active:focus,.btn-default:active.focus,.btn-default.active:hover,.btn-default.active:focus,.btn-default.active.focus,.open>.btn-default.dropdown-toggle:hover,.open>.btn-default.dropdown-toggle:focus,.open>.btn-default.dropdown-toggle.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus{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:focus,.btn-primary.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.btn-primary:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;background-image:none;border-color:#285e8e}.btn-primary:active:hover,.btn-primary:active:focus,.btn-primary:active.focus,.btn-primary.active:hover,.btn-primary.active:focus,.btn-primary.active.focus,.open>.btn-primary.dropdown-toggle:hover,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus{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:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success:active:hover,.btn-success:active:focus,.btn-success:active.focus,.btn-success.active:hover,.btn-success.active:focus,.btn-success.active.focus,.open>.btn-success.dropdown-toggle:hover,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus{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:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info:active:hover,.btn-info:active:focus,.btn-info:active.focus,.btn-info.active:hover,.btn-info.active:focus,.btn-info.active.focus,.open>.btn-info.dropdown-toggle:hover,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus{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:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning:active:hover,.btn-warning:active:focus,.btn-warning:active.focus,.btn-warning.active:hover,.btn-warning.active:focus,.btn-warning.active.focus,.open>.btn-warning.dropdown-toggle:hover,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus{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:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger:active:hover,.btn-danger:active:focus,.btn-danger:active.focus,.btn-danger.active:hover,.btn-danger.active:focus,.btn-danger.active.focus,.open>.btn-danger.dropdown-toggle:hover,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;border-radius:0}.btn-link,.btn-link:active,.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,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,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 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.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-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.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;background-clip:padding-box;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);box-shadow:0 6px 12px rgba(0,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 dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.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>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.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:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn,.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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-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,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,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,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .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:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.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-left-radius:4px;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-right-radius:4px;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"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.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 .form-control:focus{z-index:3}.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,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{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{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.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-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,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.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,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-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}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;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-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@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}.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.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-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@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-right:15px;margin-top:8px;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;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-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@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 .form-control-static{display:inline-block}.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}.navbar-form .form-group:last-child{margin-bottom:0}}@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-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{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{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}}@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{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-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-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-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,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#aaa}.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:#aaa}.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:#090909}.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-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#090909}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#aaa}.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:#090909}.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-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-link{color:#aaa}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#aaa}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,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:"/ "}.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>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{z-index:2;color:#2a6496;background-color:#eee;border-color:#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>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:3;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;line-height:1.33}.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;line-height:1.5}.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:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.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}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.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:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron{padding-top:30px;padding-bottom: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,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .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:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.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}}@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,0.1);box-shadow:inset 0 1px 2px rgba(0,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,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{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);background-image:-o-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);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);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-success{background-color:#5cb85c}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.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.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;cursor:not-allowed;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 .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .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}a.list-group-item,button.list-group-item{color:#555}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,button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}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,button.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,button.list-group-item-success.active,button.list-group-item-success.active:hover,button.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,button.list-group-item-info{color:#31708f}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,button.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,button.list-group-item-info.active,button.list-group-item-info.active:hover,button.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,button.list-group-item-warning{color:#8a6d3b}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,button.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,button.list-group-item-warning.active,button.list-group-item-warning.active:hover,button.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,button.list-group-item-danger{color:#a94442}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,button.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,button.list-group-item-danger.active,button.list-group-item-danger.active:hover,button.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,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.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,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.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,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.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 caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.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,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr: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:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 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,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr: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:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{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-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.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,.panel-group .panel-heading+.panel-collapse>.list-group{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,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,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{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.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-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(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;background-clip:padding-box;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);box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.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{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header:before,.modal-header:after{display:table;content:" "}.modal-header:after{clear:both}.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:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.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,0.5);box-shadow:0 5px 15px rgba(0,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-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;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.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;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)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.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,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,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,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,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.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,0.6);background-color:transparent;filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,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,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,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;outline:0;filter:alpha(opacity=90);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;margin-top:-10px}.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;font-family:serif;line-height:1}.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:transparent;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,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:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs{display:none !important}.visible-sm{display:none !important}.visible-md{display:none !important}.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 !important}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 !important}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 !important}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 !important}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 !important}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}} diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 41bd5f39..5f3902b1 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -1,4 +1,8 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]: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}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url(/static/fonts/glyphicons-halflings-regular.eot?1455294101);src:url(/static/fonts/glyphicons-halflings-regular.eot?&1455294101#iefix) format("embedded-opentype"),url(/static/fonts/glyphicons-halflings-regular.woff2?1455294101) format("woff2"),url(/static/fonts/glyphicons-halflings-regular.woff?1455294101) format("woff"),url(/static/fonts/glyphicons-halflings-regular.ttf?1455294101) format("truetype"),url(/static/fonts/glyphicons-halflings-regular.svg?1455294101#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,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}body{font-family:"Open Sans","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{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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;margin:-1px;padding:0;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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}mark,.mark{background-color:#fcf8e3;padding:.2em}.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,.initialism{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}.bg-primary{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;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%}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;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;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{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media (min-width: 768px){.container{width:750px}}@media (min-width: 992px){.container{width:970px}}@media (min-width: 1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-15px;margin-right:-15px}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.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-left:15px;padding-right: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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.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{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}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"]{line-height:34px}input[type="date"].input-sm,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;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-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.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-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.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;font-size:12px;line-height:1.5;min-height:32px}.input-lg,.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-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.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;font-size:18px;line-height:1.33;min-height:38px}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{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;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.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:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.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}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default.focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default: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.focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary: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.focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success: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.focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info: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.focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning: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.focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.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,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,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 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.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-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.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;list-style:none;font-size:14px;text-align:left;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);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.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{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.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;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.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>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.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:before,.btn-toolbar:after{content:" ";display:table}.btn-toolbar:after{clear:both}.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-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,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,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .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:before,.btn-group-vertical>.btn-group:after{content:" ";display:table}.btn-group-vertical>.btn-group:after{clear:both}.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-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.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-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;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"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.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,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{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-bottom-right-radius:0;border-top-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-bottom-left-radius:0;border-top-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{margin-bottom:0;padding-left:0;list-style:none}.nav:before,.nav:after{content:" ";display:table}.nav:after{clear:both}.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;background-color:transparent;cursor:not-allowed}.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;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.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,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width: 768px){.nav-justified>li,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-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-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{content:" ";display:table}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{content:" ";display:table}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{content:" ";display:table}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;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-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.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}@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;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width: 768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;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;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-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@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 .form-control-static{display:inline-block}.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}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width: 768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width: 768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{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{background-color:#e7e7e7;color:#555}@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,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#aaa}.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:#aaa}.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:#090909}.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{background-color:#090909;color:#fff}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#aaa}.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:#090909}.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:#aaa}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#aaa}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,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{content:"/ ";padding:0 5px;color:#ccc}.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;line-height:1.42857143;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.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-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-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-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager:before,.pager:after{content:" ";display:table}.pager:after{clear:both}.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;background-color:#fff;cursor:not-allowed}.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}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.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;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron{padding:30px 15px;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,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right: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:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.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{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.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}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,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,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{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);background-image:-o-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);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);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-success{background-color:#5cb85c}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.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-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}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{text-decoration:none;color:#555;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#777;cursor:not-allowed}.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 .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .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,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-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,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.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 caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr: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:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 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,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{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-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom: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,.panel-group .panel-heading+.panel-collapse>.list-group{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,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,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;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(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;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);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.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:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.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,0.5);box-shadow:0 5px 15px rgba(0,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-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:normal;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.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;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-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;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:normal;line-height:1.42857143;text-align:left;background-color:#fff;background-clip:padding-box;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}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;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{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;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,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;-moz-perspective:1000;perspective:1000}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.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;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.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;line-height:1;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%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:transparent}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,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{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs{display:none !important}.visible-sm{display:none !important}.visible-md{display:none !important}.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}}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:block;min-height:0}* html .ui-helper-clearfix{height:1%}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0}.ui-front{z-index:100}.ui-state-disabled{cursor:default !important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-accordion{width:100%}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0 !important}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none}.ui-accordion .ui-accordion-content-active{display:block}/*! +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 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%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}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{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{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-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !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^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url(/static/fonts/glyphicons-halflings-regular.eot?1543879568);src:url(/static/fonts/glyphicons-halflings-regular.eot?&1543879568#iefix) format("embedded-opentype"),url(/static/fonts/glyphicons-halflings-regular.woff2?1543879568) format("woff2"),url(/static/fonts/glyphicons-halflings-regular.woff?1543879568) format("woff"),url(/static/fonts/glyphicons-halflings-regular.ttf?1543879568) format("truetype"),url(/static/fonts/glyphicons-halflings-regular.svg?1543879568#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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur: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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.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"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-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:transparent}body{font-family:"Open Sans","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:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;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;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto}.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}[role="button"]{cursor:pointer}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,h1 .small,h2 small,h2 .small,h3 small,h3 .small,h4 small,h4 .small,h5 small,h5 .small,h6 small,h6 .small,.h1 small,.h1 .small,.h2 small,.h2 .small,.h3 small,.h3 .small,.h4 small,.h4 .small,.h5 small,.h5 .small,.h6 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,.h1 small,.h1 .small,h2 small,h2 .small,.h2 small,.h2 .small,h3 small,h3 .small,.h3 small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,h4 .small,.h4 small,.h4 .small,h5 small,h5 .small,.h5 small,.h5 .small,h6 small,h6 .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%}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,.initialism{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover,a.text-primary:focus{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff}.bg-primary{background-color:#428bca}a.bg-primary:hover,a.bg-primary:focus{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{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,ul ol,ol ul,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.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}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}@media (min-width: 768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%}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-reverse small:before,.blockquote-reverse .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,.blockquote-reverse small:after,.blockquote-reverse .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}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;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;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}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}@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}.container-fluid:before,.container-fluid:after{display:table;content:" "}.container-fluid:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.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-1{width:8.33333333%}.col-xs-2{width:16.66666667%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333%}.col-xs-5{width:41.66666667%}.col-xs-6{width:50%}.col-xs-7{width:58.33333333%}.col-xs-8{width:66.66666667%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333%}.col-xs-11{width:91.66666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.33333333%}.col-xs-push-2{left:16.66666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.33333333%}.col-xs-push-5{left:41.66666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.33333333%}.col-xs-push-8{left:66.66666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.33333333%}.col-xs-push-11{left:91.66666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-12{margin-left:100%}@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-1{width:8.33333333%}.col-sm-2{width:16.66666667%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333%}.col-sm-5{width:41.66666667%}.col-sm-6{width:50%}.col-sm-7{width:58.33333333%}.col-sm-8{width:66.66666667%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333%}.col-sm-11{width:91.66666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.33333333%}.col-sm-push-2{left:16.66666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333%}.col-sm-push-5{left:41.66666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.33333333%}.col-sm-push-8{left:66.66666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333%}.col-sm-push-11{left:91.66666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-md-2{width:16.66666667%}.col-md-3{width:25%}.col-md-4{width:33.33333333%}.col-md-5{width:41.66666667%}.col-md-6{width:50%}.col-md-7{width:58.33333333%}.col-md-8{width:66.66666667%}.col-md-9{width:75%}.col-md-10{width:83.33333333%}.col-md-11{width:91.66666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.33333333%}.col-md-pull-2{right:16.66666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333%}.col-md-pull-5{right:41.66666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.33333333%}.col-md-pull-8{right:66.66666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333%}.col-md-pull-11{right:91.66666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.33333333%}.col-md-push-2{left:16.66666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333%}.col-md-push-5{left:41.66666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.33333333%}.col-md-push-8{left:66.66666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333%}.col-md-push-11{left:91.66666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-12{margin-left:100%}}@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-1{width:8.33333333%}.col-lg-2{width:16.66666667%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333%}.col-lg-5{width:41.66666667%}.col-lg-6{width:50%}.col-lg-7{width:58.33333333%}.col-lg-8{width:66.66666667%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333%}.col-lg-11{width:91.66666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.33333333%}.col-lg-push-2{left:16.66666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333%}.col-lg-push-5{left:41.66666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.33333333%}.col-lg-push-8{left:66.66666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333%}.col-lg-push-11{left:91.66666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-12{margin-left:100%}}table{background-color:transparent}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}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>thead>tr>td,.table>tbody>tr>th,.table>tbody>tr>td,.table>tfoot>tr>th,.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>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>th,.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>thead>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>th,.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-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>thead>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>tfoot>tr.active>td,.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>thead>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>tfoot>tr.success>td,.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>thead>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>tfoot>tr.info>td,.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>thead>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>tfoot>tr.warning>td,.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>thead>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>tfoot>tr.danger>td,.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}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width: 767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-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>thead>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>th,.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>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th: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>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th: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>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.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;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}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: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,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s,box-shadow ease-in-out 0.15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.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::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio: 0){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,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,.input-group-sm input[type="date"],input[type="time"].input-sm,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,.input-group-sm input[type="time"],input[type="datetime-local"].input-sm,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,.input-group-sm input[type="datetime-local"],input[type="month"].input-sm,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn,.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,.input-group-lg input[type="date"],input[type="time"].input-lg,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,.input-group-lg input[type="time"],input[type="datetime-local"].input-lg,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,.input-group-lg input[type="datetime-local"],input[type="month"].input-lg,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn,.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;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{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}.input-sm,.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-sm,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn{height:30px;line-height:30px}textarea.input-sm,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,select[multiple].input-sm,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg,.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-lg,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn{height:46px;line-height:46px}textarea.input-lg,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,select[multiple].input-lg,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.33}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.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-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.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,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,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,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,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,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-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)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,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 ~ .form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.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}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media (min-width: 768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width: 768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width: 768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn.focus,.btn:active:focus,.btn:active.focus,.btn.active:focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.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,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.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>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default:active:hover,.btn-default:active:focus,.btn-default:active.focus,.btn-default.active:hover,.btn-default.active:focus,.btn-default.active.focus,.open>.btn-default.dropdown-toggle:hover,.open>.btn-default.dropdown-toggle:focus,.open>.btn-default.dropdown-toggle.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled.focus,.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default.focus{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:focus,.btn-primary.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.btn-primary:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;background-image:none;border-color:#285e8e}.btn-primary:active:hover,.btn-primary:active:focus,.btn-primary:active.focus,.btn-primary.active:hover,.btn-primary.active:focus,.btn-primary.active.focus,.open>.btn-primary.dropdown-toggle:hover,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled.focus,.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary.focus{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:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success:active:hover,.btn-success:active:focus,.btn-success:active.focus,.btn-success.active:hover,.btn-success.active:focus,.btn-success.active.focus,.open>.btn-success.dropdown-toggle:hover,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled.focus,.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success.focus{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:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info:active:hover,.btn-info:active:focus,.btn-info:active.focus,.btn-info.active:hover,.btn-info.active:focus,.btn-info.active.focus,.open>.btn-info.dropdown-toggle:hover,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled.focus,.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info.focus{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:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning:active:hover,.btn-warning:active:focus,.btn-warning:active.focus,.btn-warning.active:hover,.btn-warning.active:focus,.btn-warning.active.focus,.open>.btn-warning.dropdown-toggle:hover,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled.focus,.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning.focus{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:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger:active:hover,.btn-danger:active:focus,.btn-danger:active.focus,.btn-danger.active:hover,.btn-danger.active:focus,.btn-danger.active.focus,.open>.btn-danger.dropdown-toggle:hover,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled.focus,.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;border-radius:0}.btn-link,.btn-link:active,.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,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,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 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.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-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.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;background-clip:padding-box;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);box-shadow:0 6px 12px rgba(0,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 dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width: 768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{left:0;right:auto}}.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>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:2}.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:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn,.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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-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,.btn-group-lg.btn-group>.btn+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,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,.btn-group-lg>.btn .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret,.dropup .btn-group-lg>.btn .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:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.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-left-radius:4px;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-right-radius:4px;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"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.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 .form-control:focus{z-index:3}.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,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{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{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.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-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,.nav-tabs.nav-justified{width:100%}.nav-justified>li,.nav-tabs.nav-justified>li{float:none}.nav-justified>li>a,.nav-tabs.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,.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a,.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified,.nav-tabs.nav-justified{border-bottom:0}.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width: 768px){.nav-tabs-justified>li>a,.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs.nav-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus,.nav-tabs.nav-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}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media (min-width: 768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media (min-width: 768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media (min-width: 768px){.navbar-collapse{width:auto;border-top:0;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-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width: 480px) and (orientation: landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@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}.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width: 768px){.container>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-header,.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-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@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-right:15px;margin-top:8px;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;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-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@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 .form-control-static{display:inline-block}.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}.navbar-form .form-group:last-child{margin-bottom:0}}@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-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{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm,.btn-group-sm>.navbar-btn.btn{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs,.btn-group-xs>.navbar-btn.btn{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}}@media (min-width: 768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right ~ .navbar-right{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-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-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-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,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:hover,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#090909}.navbar-inverse .navbar-brand{color:#aaa}.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:#aaa}.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:#090909}.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-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#090909}@media (max-width: 767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#090909}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#aaa}.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:#090909}.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-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-link{color:#aaa}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#aaa}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:hover,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:"/ "}.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>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{z-index:2;color:#2a6496;background-color:#eee;border-color:#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>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:3;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;line-height:1.33}.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;line-height:1.5}.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:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.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}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.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:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.jumbotron{padding-top:30px;padding-bottom: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,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width: 768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .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:border 0.2s ease-in-out;-o-transition:border 0.2s ease-in-out;transition:border 0.2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.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}}@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,0.1);box-shadow:inset 0 1px 2px rgba(0,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,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{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);background-image:-o-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);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);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-success{background-color:#5cb85c}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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}.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);background-image:-o-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);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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.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.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;cursor:not-allowed;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 .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .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}a.list-group-item,button.list-group-item{color:#555}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,button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}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,button.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,button.list-group-item-success.active,button.list-group-item-success.active:hover,button.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,button.list-group-item-info{color:#31708f}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,button.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,button.list-group-item-info.active,button.list-group-item-info.active:hover,button.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,button.list-group-item-warning{color:#8a6d3b}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,button.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,button.list-group-item-warning.active,button.list-group-item-warning.active:hover,button.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,button.list-group-item-danger{color:#a94442}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,button.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,button.list-group-item-danger.active,button.list-group-item-danger.active:hover,button.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,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.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,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.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,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.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 caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.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,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr: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:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 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,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr: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:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{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-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th: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-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.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,.panel-group .panel-heading+.panel-collapse>.list-group{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,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,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{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.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-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(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;background-clip:padding-box;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);box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.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{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header:before,.modal-header:after{display:table;content:" "}.modal-header:after{clear:both}.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:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.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,0.5);box-shadow:0 5px 15px rgba(0,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-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;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.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;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)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.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,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,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,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,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}@media all and (transform-3d), (-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform 0.6s ease-in-out;-moz-transition:-moz-transform 0.6s ease-in-out;-o-transition:-o-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;-moz-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.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,0.6);background-color:transparent;filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0%, rgba(0,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,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0%, rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0%, rgba(0,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;outline:0;filter:alpha(opacity=90);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;margin-top:-10px}.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;font-family:serif;line-height:1}.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:transparent;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,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:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs{display:none !important}.visible-sm{display:none !important}.visible-md{display:none !important}.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 !important}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 !important}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 !important}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 !important}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 !important}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}}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:block;min-height:0}* html .ui-helper-clearfix{height:1%}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0}.ui-front{z-index:100}.ui-state-disabled{cursor:default !important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-accordion{width:100%}.ui-accordion .ui-accordion-li-fix{display:inline}.ui-accordion .ui-accordion-header-active{border-bottom:0 !important}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none}.ui-accordion .ui-accordion-content-active{display:block}/*! * jQuery UI Bootstrap v1.0 Alpha * * jQuery UI Menu 1.10.3 @@ -16,4 +20,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:0.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{min-height:100%;display:table;width:100%}html.embedded body{padding:0;display:table-cell;vertical-align:middle;width:100%;background:none}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{display:flex;flex-direction:column;width:100%;height:100%;max-height:100%;overflow:hidden;justify-content:center}html.embedded body{padding:0;width:100%;background:none;overflow:scroll}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 953027a9..26e57047 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -149,16 +149,19 @@ ins { } html.embedded{ - min-height:100%; - display: table; + display: flex; + flex-direction: column; width: 100%; + height: 100%; + max-height: 100%; + overflow: hidden; + justify-content: center; body{ padding:0; - display: table-cell; - vertical-align: middle; width:100%; background:none; + overflow: scroll; } .embed_container{ From c0fe1764953552bb358254647442b876dd8337a6 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 14 Jul 2019 23:04:12 +0100 Subject: [PATCH 236/275] Add docker dev instructions --- Dockerfile | 12 ++++++++++++ README.md | 7 +++++++ 2 files changed, 19 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7c01953b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.6 + +WORKDIR /app + +ADD . /app + +RUN pip install -r requirements.txt && \ + python manage.py collectstatic --noinput + +EXPOSE 8000 + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/README.md b/README.md index 3401782f..6e7ddee1 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ python manage.py runserver ``` Please refer to Django documentation for a full list of options available here. +### Development using docker + +``` +docker build . -t pyrigs +docker run -it --rm -p=8000:8000 -v $(pwd):/app pyrigs +``` + ### Sample Data ### Sample data is available to aid local development and user acceptance testing. To load this data into your local database, first ensure the database is empty: ``` From 7babaee44c899cd671530b3a22b43b7cfd5a11ea Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 14 Jul 2019 23:09:44 +0100 Subject: [PATCH 237/275] Add link to pre-filled risk assessment form --- PyRIGS/settings.py | 2 ++ RIGS/rigboard.py | 14 ++++++++++++++ RIGS/templates/RIGS/event_detail_buttons.html | 3 +++ RIGS/urls.py | 3 +++ 4 files changed, 22 insertions(+) diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 393bbbb5..6a40a352 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -233,3 +233,5 @@ USE_GRAVATAR = True TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' +RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get( + 'RISK_ASSESSMENT_URL') else "http://example.com" diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 82bb29dc..a6b3ef80 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -80,6 +80,20 @@ class EventEmbed(EventDetail): template_name = 'RIGS/event_embed.html' +class EventRA(generic.base.RedirectView): + permanent = False + def get_redirect_url(self, *args, **kwargs): + event = get_object_or_404(models.Event, pk=kwargs['pk']) + params = { + 'entry.708610078': f'N{event.pk:05}', + 'entry.905899507': event.name, + 'entry.139491562': event.venue.name if event.venue else '', + 'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''), + 'entry.902421165': event.mic.name if event.mic else '' + } + return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params) + + class EventCreate(generic.CreateView): model = models.Event form_class = forms.EventForm diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html index 878684cc..3d2f06af 100644 --- a/RIGS/templates/RIGS/event_detail_buttons.html +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -2,6 +2,9 @@ + {% if event.is_rig %} \d+)/print/$', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), name='event_print'), + url(r'^event/(?P\d+)/ra/$', + permission_required_with_403('RIGS.change_event')(rigboard.EventRA.as_view()), + name='event_ra'), url(r'^event/create/$', permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()), name='event_create'), From faa4573f6d8154cf660a0ef0e81311bf55193580 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 14 Jul 2019 23:15:13 +0100 Subject: [PATCH 238/275] Add dash to date range --- RIGS/rigboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index a6b3ef80..0bae9940 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -88,7 +88,7 @@ class EventRA(generic.base.RedirectView): 'entry.708610078': f'N{event.pk:05}', 'entry.905899507': event.name, 'entry.139491562': event.venue.name if event.venue else '', - 'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''), + 'entry.1689826056': event.start_date.strftime('%Y-%m-%d') + ((' - ' + event.end_date.strftime('%Y-%m-%d')) if event.end_date else ''), 'entry.902421165': event.mic.name if event.mic else '' } return settings.RISK_ASSESSMENT_URL + "?" + urllib.parse.urlencode(params) From 35997aa882b7b331efafb35572fd501057e5b43e Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 28 Jul 2019 23:08:18 +0100 Subject: [PATCH 239/275] Add API hook for logging risk assessment completion (#341) --- PyRIGS/settings.py | 3 ++ .../0034_event_risk_assessment_edit_url.py | 18 ++++++++++++ RIGS/models.py | 3 ++ RIGS/rigboard.py | 28 +++++++++++++++++++ RIGS/templates/RIGS/event_detail_buttons.html | 14 ++++++++-- RIGS/urls.py | 3 ++ requirements.txt | 2 +- 7 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 RIGS/migrations/0034_event_risk_assessment_edit_url.py diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 6a40a352..a41ad2c9 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/1.7/ref/settings/ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os import raven +import secrets BASE_DIR = os.path.dirname(os.path.dirname(__file__)) @@ -235,3 +236,5 @@ TERMS_OF_HIRE_URL = "http://www.nottinghamtec.co.uk/terms.pdf" AUTHORISATION_NOTIFICATION_ADDRESS = 'productions@nottinghamtec.co.uk' RISK_ASSESSMENT_URL = os.environ.get('RISK_ASSESSMENT_URL') if os.environ.get( 'RISK_ASSESSMENT_URL') else "http://example.com" +RISK_ASSESSMENT_SECRET = os.environ.get('RISK_ASSESSMENT_SECRET') if os.environ.get( + 'RISK_ASSESSMENT_SECRET') else secrets.token_hex(15) diff --git a/RIGS/migrations/0034_event_risk_assessment_edit_url.py b/RIGS/migrations/0034_event_risk_assessment_edit_url.py new file mode 100644 index 00000000..d60cd4bd --- /dev/null +++ b/RIGS/migrations/0034_event_risk_assessment_edit_url.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.5 on 2019-07-28 21:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0033_auto_20180325_0016'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='risk_assessment_edit_url', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/RIGS/models.py b/RIGS/models.py index a4eaffad..2c708851 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -338,6 +338,9 @@ class Event(models.Model, RevisionMixin): auth_request_at = models.DateTimeField(null=True, blank=True) auth_request_to = models.EmailField(null=True, blank=True) + # Risk assessment info + risk_assessment_edit_url = models.CharField(max_length=255, blank=True, null=True) + # Calculated values """ EX Vat diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 0bae9940..e6112b31 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -18,6 +18,7 @@ from django.core.exceptions import SuspiciousOperation from django.db.models import Q from django.contrib import messages from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt from z3c.rml import rml2pdf from PyPDF2 import PdfFileMerger, PdfFileReader import simplejson @@ -84,6 +85,10 @@ class EventRA(generic.base.RedirectView): permanent = False def get_redirect_url(self, *args, **kwargs): event = get_object_or_404(models.Event, pk=kwargs['pk']) + + if event.risk_assessment_edit_url: + return event.risk_assessment_edit_url + params = { 'entry.708610078': f'N{event.pk:05}', 'entry.905899507': event.name, @@ -400,3 +405,26 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView): }) context['to_name'] = self.request.GET.get('to_name', None) return context + +@method_decorator(csrf_exempt, name='dispatch') +class LogRiskAssessment(generic.View): + http_method_names = ["post"] + + def post(self, request, **kwargs): + data = request.POST + shared_secret = data.get("secret") + edit_url = data.get("editUrl") + rig_number = data.get("rigNum") + if shared_secret is None or edit_url is None or rig_number is None: + return HttpResponse(status=422) + + if shared_secret != settings.RISK_ASSESSMENT_SECRET: + return HttpResponse(status=403) + + rig_number = int(re.sub("[^0-9]", "", rig_number)) + + event = get_object_or_404(models.Event, pk=rig_number) + event.risk_assessment_edit_url = edit_url + event.save() + + return HttpResponse(status=200) diff --git a/RIGS/templates/RIGS/event_detail_buttons.html b/RIGS/templates/RIGS/event_detail_buttons.html index 3d2f06af..2f05c7f1 100644 --- a/RIGS/templates/RIGS/event_detail_buttons.html +++ b/RIGS/templates/RIGS/event_detail_buttons.html @@ -2,10 +2,18 @@ - {% if event.is_rig %} + {% if not event.dry_hire %} + + {% endif %} diff --git a/RIGS/urls.py b/RIGS/urls.py index c54e2785..3630f7d0 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -188,6 +188,9 @@ urlpatterns = [ url(r'^api/(?P\w+)/(?P\d+)/$', login_required(views.SecureAPIRequest.as_view()), name="api_secure"), + # Risk assessment API + url(r'^log_risk_assessment/$', rigboard.LogRiskAssessment.as_view(), name='log_risk_assessment'), + # Legacy URL's url(r'^rig/show/(?P\d+)/$', RedirectView.as_view(permanent=True, pattern_name='event_detail')), diff --git a/requirements.txt b/requirements.txt index 62f505d5..ebe42496 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ contextlib2==0.5.5 diff-match-patch==20121119 dj-database-url==0.5.0 dj-static==0.0.6 -Django==2.0.5 +Django==2.0.13 django-debug-toolbar==1.9.1 django-ical==1.4 django-recaptcha==1.4.0 From 86b349f60e29a996d4da3c80096c41aa7be8bd2b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 28 Jul 2019 23:32:54 +0100 Subject: [PATCH 240/275] Tidy up version history for risk assessments --- RIGS/versioning.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 0e9fd54e..c755dde3 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -27,6 +27,8 @@ class FieldComparison(object): def display_value(self, value): if isinstance(self.field, IntegerField) and len(self.field.choices) > 0: return [x[1] for x in self.field.choices if x[0] == value][0] + if self.field.name == "risk_assessment_edit_url" + return "completed" if value else "" return value @property From 1a49bb50e5c1a0625d67f33e0ce7464820441f3f Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sun, 28 Jul 2019 23:40:35 +0100 Subject: [PATCH 241/275] Further version history improvements --- RIGS/models.py | 2 +- RIGS/versioning.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/models.py b/RIGS/models.py index 2c708851..937d5354 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -339,7 +339,7 @@ class Event(models.Model, RevisionMixin): auth_request_to = models.EmailField(null=True, blank=True) # Risk assessment info - risk_assessment_edit_url = models.CharField(max_length=255, blank=True, null=True) + risk_assessment_edit_url = models.CharField(verbose_name="risk assessment", max_length=255, blank=True, null=True) # Calculated values """ diff --git a/RIGS/versioning.py b/RIGS/versioning.py index c755dde3..d7b423c2 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -27,7 +27,7 @@ class FieldComparison(object): def display_value(self, value): if isinstance(self.field, IntegerField) and len(self.field.choices) > 0: return [x[1] for x in self.field.choices if x[0] == value][0] - if self.field.name == "risk_assessment_edit_url" + if self.field.name == "risk_assessment_edit_url": return "completed" if value else "" return value From 4da8040351c81656fc1852781d09d2cdbdde351c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 30 Oct 2019 13:16:14 +0000 Subject: [PATCH 242/275] Only display embedded scrollbars when required --- RIGS/static/css/screen.css | 2 +- RIGS/static/scss/screen.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RIGS/static/css/screen.css b/RIGS/static/css/screen.css index 5f3902b1..3dd93265 100644 --- a/RIGS/static/css/screen.css +++ b/RIGS/static/css/screen.css @@ -20,4 +20,4 @@ * Portions copyright Addy Osmani, jQuery UI & Twitter Bootstrap * Created the LESS version by $dharapvj * Released under MIT - */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{display:flex;flex-direction:column;width:100%;height:100%;max-height:100%;overflow:hidden;justify-content:center}html.embedded body{padding:0;width:100%;background:none;overflow:scroll}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} + */.ui-tooltip{display:block;font-size:11px;filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);opacity:.8;position:absolute;visibility:visible;z-index:1070;max-width:200px;background:#000;border:1px solid #000;color:#fff;padding:3px 8px;text-align:center;text-decoration:none;-moz-box-shadow:inset 0 1px 0 #000;-webkit-box-shadow:inset 0 1px 0 #000;box-shadow:inset 0 1px 0 #000;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border-width:1px}.ui-tooltip .arrow{overflow:hidden;position:absolute;margin-left:0;height:20px;width:20px}.ui-tooltip .arrow.bottom{top:100%;left:38%}.ui-tooltip .arrow.bottom:after{border-top:8px solid #000;border-right:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid transparent}.ui-tooltip .arrow.top{top:-50%;bottom:22px;left:42%}.ui-tooltip .arrow.top:after{border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #000;border-left:6px solid transparent}.ui-tooltip .arrow.left{top:25%;left:-15%;right:0;bottom:-16px}.ui-tooltip .arrow.left:after{width:0;border-top:6px solid transparent;border-right:6px solid #000;border-bottom:6px solid transparent;border-left:6px solid transparent}.ui-tooltip .arrow.right{top:26%;left:100%;right:0;bottom:-16px;margin-left:1px}.ui-tooltip .arrow.right:after{width:0;border-top:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #000}.ui-tooltip .arrow:after{content:" ";position:absolute;height:0;left:0;top:0;width:0;margin-left:0;bottom:12px;box-shadow:6px 5px 9px -9px #000}body,.pad-top{padding-top:50px}#content{padding:40px 15px}#userdropdown>li{padding:0 0.3em}#userdropdown>li .media-object,#activity .media-object{max-width:3em}.table tbody>tr>td.vert-align{vertical-align:middle}.align-right{text-align:right}textarea{width:100%;resize:vertical}.btn-page,.btn-pad{margin:0 0 0.5em}.custom-combobox{display:block}.event-mic-photo{max-width:2em}.item-description{margin-left:1em}.overflow-ellipsis{text-overflow:ellipsis;display:inline-block;max-width:100%;overflow:hidden}.modal-dialog{z-index:inherit}.panel-default .default{background-color:#f5f5f5}del{background-color:#f2dede;border-radius:3px}ins{background-color:#dff0d8;border-radius:3px}.loading-animation{position:relative;margin:30px auto 0}.loading-animation .circle{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;box-shadow:0 0 35px #2187e7;width:50px;height:50px;margin:0 auto;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s infinite ease-in-out;animation:spinPulse 1s infinite ease-in-out}.loading-animation .circle1{background-color:transparent;border:5px solid rgba(0,183,229,0.9);opacity:.9;border-left:5px solid transparent;border-right:5px solid transparent;border-radius:50px;box-shadow:0 0 15px #2187e7;width:30px;height:30px;margin:0 auto;position:relative;top:-40px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s infinite linear;animation:spinoffPulse 1s infinite linear}@-moz-keyframes spinPulse{0%{-moz-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@keyframes spinPulse{0%{-moz-transform:rotate(160deg);-ms-transform:rotate(160deg);-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;box-shadow:0 0 1px #2187e7}50%{-moz-transform:rotate(145deg);-ms-transform:rotate(145deg);-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}100%{-moz-transform:rotate(-320deg);-ms-transform:rotate(-320deg);-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}@-moz-keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinoffPulse{0%{-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)}}html.embedded{display:flex;flex-direction:column;width:100%;height:100%;max-height:100%;overflow:hidden;justify-content:center}html.embedded body{padding:0;width:100%;background:none;overflow:auto}html.embedded .embed_container{border:5px solid #e9e9e9;padding:12px 0px;min-height:100%;width:100%}html.embedded .source{background:url("/static/imgs/pyrigs-avatar.png") no-repeat;background-size:16px 16px;padding-left:20px;color:#000}html.embedded h3{margin-top:10px;margin-bottom:5px}html.embedded p{margin-bottom:2px;font-size:11px}html.embedded .event-mic-photo{max-width:3em} diff --git a/RIGS/static/scss/screen.scss b/RIGS/static/scss/screen.scss index 26e57047..a16fe3a4 100644 --- a/RIGS/static/scss/screen.scss +++ b/RIGS/static/scss/screen.scss @@ -161,7 +161,7 @@ html.embedded{ padding:0; width:100%; background:none; - overflow: scroll; + overflow: auto; } .embed_container{ From 5210afc772cf7a274a496bc02a4b43df6516f47d Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Tue, 26 Nov 2019 17:26:32 +0000 Subject: [PATCH 243/275] Combine client authorisation information in rig detail (#373) * Combine client authorisation information in rig detail * Fix stuff for CI pep8 compliance migration --- RIGS/forms.py | 1 + RIGS/migrations/0035_auto_20191124_1319.py | 18 ++++ RIGS/rigboard.py | 2 + RIGS/templates/RIGS/event_detail.html | 103 ++++++++++----------- RIGS/views.py | 2 +- 5 files changed, 73 insertions(+), 53 deletions(-) create mode 100644 RIGS/migrations/0035_auto_20191124_1319.py diff --git a/RIGS/forms.py b/RIGS/forms.py index 9caf3016..18a81e14 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -39,6 +39,7 @@ class EmbeddedAuthenticationForm(AuthenticationForm): super().__init__(*args, **kwargs) self.fields['username'].widget.attrs.pop('autofocus', None) + class PasswordReset(PasswordResetForm): captcha = ReCaptchaField(label='Captcha') diff --git a/RIGS/migrations/0035_auto_20191124_1319.py b/RIGS/migrations/0035_auto_20191124_1319.py new file mode 100644 index 00000000..dd00494f --- /dev/null +++ b/RIGS/migrations/0035_auto_20191124_1319.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2019-11-24 13:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('RIGS', '0034_event_risk_assessment_edit_url'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='risk_assessment_edit_url', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='risk assessment'), + ), + ] diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index e6112b31..99c80601 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -83,6 +83,7 @@ class EventEmbed(EventDetail): class EventRA(generic.base.RedirectView): permanent = False + def get_redirect_url(self, *args, **kwargs): event = get_object_or_404(models.Event, pk=kwargs['pk']) @@ -406,6 +407,7 @@ class EventAuthoriseRequestEmailPreview(generic.DetailView): context['to_name'] = self.request.GET.get('to_name', None) return context + @method_decorator(csrf_exempt, name='dispatch') class LogRiskAssessment(generic.View): http_method_names = ["post"] diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 5f2885b5..6a18d2f3 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -70,39 +70,6 @@
    {% endif %} - - {% if event.is_rig and event.internal %} -
    -
    Client Authorisation
    -
    -
    -
    Authorised
    -
    {{ object.authorised|yesno:"Yes,No" }}
    - -
    Authorised by
    -
    - {% if object.authorisation %} - {{ object.authorisation.name }} - ({{ object.authorisation.email }}) - {% endif %} -
    - -
    Authorised at
    -
    {{ object.authorisation.last_edited_at }}
    - -
    Authorised amount
    -
    - {% if object.authorisation %} - £ {{ object.authorisation.amount|floatformat:"2" }} - {% endif %} -
    - -
    Requested by
    -
    {{ object.authorisation.sent_by }}
    -
    -
    -
    - {% endif %}
    {% endif %}
    @@ -180,31 +147,63 @@
    {{ object.collector }}
    {% endif %} - {% if event.is_rig %} + {% if event.is_rig and not event.internal %}
     
    - - {% if object.internal %} -
    Authorisation Request
    -
    {{ object.auth_request_to|yesno:"Yes,No" }}
    - -
    By
    -
    {{ object.auth_request_by }}
    - -
    At
    -
    {{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}
    - -
    To
    -
    {{ object.auth_request_to }}
    - - {% else %} -
    PO
    -
    {{ object.purchase_order }}
    - {% endif %} +
    PO
    +
    {{ object.purchase_order }}
    {% endif %}
    + {% if event.is_rig and event.internal %} +
    +
    +
    Client Authorisation
    +
    +
    +
    Authorisation Request
    +
    {{ object.auth_request_to|yesno:"Yes,No" }}
    + +
    By
    +
    {{ object.auth_request_by }}
    + +
    At
    +
    {{ object.auth_request_at|date:"D d M Y H:i"|default:"" }}
    + +
    To
    +
    {{ object.auth_request_to }}
    +
    +
     
    +
    +
    Authorised
    +
    {{ object.authorised|yesno:"Yes,No" }}
    + +
    Authorised by
    +
    + {% if object.authorisation %} + {{ object.authorisation.name }} + ({{ object.authorisation.email }}) + {% endif %} +
    + +
    Authorised at
    +
    {{ object.authorisation.last_edited_at }}
    + +
    Authorised amount
    +
    + {% if object.authorisation %} + £ {{ object.authorisation.amount|floatformat:"2" }} + {% endif %} +
    + +
    Requested by
    +
    {{ object.authorisation.sent_by }}
    +
    +
    +
    +
    + {% endif %} {% if not request.is_ajax %}
    {% include 'RIGS/event_detail_buttons.html' %} diff --git a/RIGS/views.py b/RIGS/views.py index aaaac7da..b197334c 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -62,7 +62,7 @@ def login_embed(request, **kwargs): messages.warning(request, 'Cookies do not seem to be enabled. Try logging in using a new tab.') request.method = 'GET' # Render the page without trying to login - return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm) + return login(request, template_name="registration/login_embed.html", authentication_form=forms.EmbeddedAuthenticationForm) """ From 4f036af85ab81103e34f8f2de146e4f9185a4cfc Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Wed, 4 Dec 2019 23:14:27 +0000 Subject: [PATCH 244/275] Create the Asset Database (#363) --- .gitignore | 2 + PyRIGS/settings.py | 1 + PyRIGS/urls.py | 1 + .../management/commands/generateSampleData.py | 247 +---------------- .../commands/generateSampleRIGSData.py | 260 ++++++++++++++++++ RIGS/templates/RIGS/activity_feed_data.html | 2 +- RIGS/templates/RIGS/activity_table.html | 2 +- RIGS/templates/RIGS/calendar.html | 2 +- RIGS/templates/RIGS/event_archive.html | 2 +- RIGS/templates/RIGS/event_detail.html | 2 +- RIGS/templates/RIGS/event_form.html | 2 +- RIGS/templates/RIGS/event_invoice.html | 2 +- .../RIGS/eventauthorisation_request.html | 2 +- .../eventauthorisation_request_error.html | 2 +- RIGS/templates/RIGS/index.html | 3 +- .../RIGS/invoice_confirm_delete.html | 2 +- RIGS/templates/RIGS/invoice_detail.html | 2 +- RIGS/templates/RIGS/invoice_list.html | 2 +- RIGS/templates/RIGS/organisation_detail.html | 2 +- RIGS/templates/RIGS/organisation_form.html | 2 +- RIGS/templates/RIGS/organisation_list.html | 2 +- .../RIGS/payment_confirm_delete.html | 2 +- RIGS/templates/RIGS/person_detail.html | 2 +- RIGS/templates/RIGS/person_form.html | 2 +- RIGS/templates/RIGS/person_list.html | 2 +- RIGS/templates/RIGS/profile_detail.html | 2 +- RIGS/templates/RIGS/profile_form.html | 2 +- RIGS/templates/RIGS/rigboard.html | 2 +- RIGS/templates/RIGS/venue_detail.html | 2 +- RIGS/templates/RIGS/venue_form.html | 2 +- RIGS/templates/RIGS/venue_list.html | 2 +- RIGS/templates/RIGS/version_history.html | 2 +- RIGS/test_functional.py | 7 +- RIGS/test_unit.py | 4 +- assets/__init__.py | 0 assets/admin.py | 37 +++ assets/apps.py | 5 + assets/filters.py | 9 + assets/forms.py | 30 ++ assets/management/__init__.py | 0 assets/management/commands/__init__.py | 0 .../management/commands/deleteSampleData.py | 23 ++ .../commands/generateSampleAssetsData.py | 114 ++++++++ assets/management/commands/import_old_db.py | 229 +++++++++++++++ .../management/commands/update_old_db_file.py | 110 ++++++++ assets/migrations/0001_initial.py | 86 ++++++ assets/migrations/0002_auto_20180301_1654.py | 18 ++ assets/migrations/0003_auto_20180301_1700.py | 18 ++ assets/migrations/0004_auto_20180301_1711.py | 19 ++ assets/migrations/0005_auto_20180301_1725.py | 18 ++ ...8_1451_squashed_0021_auto_20190105_1156.py | 148 ++++++++++ ...8_0202_squashed_0014_auto_20191017_2052.py | 176 ++++++++++++ assets/migrations/__init__.py | 0 assets/models.py | 141 ++++++++++ assets/static/js/csrf.js | 23 ++ assets/templates/asset_create.html | 62 +++++ assets/templates/asset_list.html | 65 +++++ assets/templates/asset_update.html | 63 +++++ .../asset_update_search_results.html | 16 ++ assets/templates/partials/asset_buttons.html | 25 ++ assets/templates/partials/asset_form.html | 61 ++++ .../partials/asset_list_table_body.html | 33 +++ assets/templates/partials/asset_picker.html | 65 +++++ assets/templates/partials/cable_form.html | 61 ++++ assets/templates/partials/parent_form.html | 41 +++ .../partials/purchasedetails_form.html | 68 +++++ assets/templates/partials/render_field.html | 16 ++ .../templates/partials/supplier_picker.html | 64 +++++ assets/templates/supplier_detail.html | 6 + assets/templates/supplier_list.html | 46 ++++ assets/templates/supplier_update.html | 20 ++ assets/templatetags/__init__.py | 0 assets/templatetags/asset_templatetags.py | 21 ++ assets/urls.py | 22 ++ assets/views.py | 205 ++++++++++++++ pytest.ini | 2 + requirements.txt | 6 +- templates/400.html | 2 +- templates/401.html | 2 +- templates/403.html | 2 +- templates/404.html | 2 +- templates/500.html | 2 +- templates/base.html | 53 +--- templates/base_assets.html | 31 +++ templates/base_rigs.html | 54 ++++ templates/login_redirect.html | 2 +- .../registration/activation_complete.html | 2 +- templates/registration/logged_out.html | 2 +- templates/registration/login.html | 2 +- .../registration/password_change_done.html | 2 +- .../registration/password_change_form.html | 2 +- .../registration/password_reset_complete.html | 2 +- .../registration/password_reset_confirm.html | 2 +- .../registration/password_reset_done.html | 2 +- .../registration/password_reset_form.html | 2 +- .../registration/registration_complete.html | 2 +- templates/registration/registration_form.html | 2 +- 97 files changed, 2577 insertions(+), 344 deletions(-) create mode 100644 RIGS/management/commands/generateSampleRIGSData.py create mode 100644 assets/__init__.py create mode 100644 assets/admin.py create mode 100644 assets/apps.py create mode 100644 assets/filters.py create mode 100644 assets/forms.py create mode 100644 assets/management/__init__.py create mode 100644 assets/management/commands/__init__.py create mode 100644 assets/management/commands/deleteSampleData.py create mode 100644 assets/management/commands/generateSampleAssetsData.py create mode 100644 assets/management/commands/import_old_db.py create mode 100644 assets/management/commands/update_old_db_file.py create mode 100644 assets/migrations/0001_initial.py create mode 100644 assets/migrations/0002_auto_20180301_1654.py create mode 100644 assets/migrations/0003_auto_20180301_1700.py create mode 100644 assets/migrations/0004_auto_20180301_1711.py create mode 100644 assets/migrations/0005_auto_20180301_1725.py create mode 100644 assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py create mode 100644 assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py create mode 100644 assets/migrations/__init__.py create mode 100644 assets/models.py create mode 100644 assets/static/js/csrf.js create mode 100644 assets/templates/asset_create.html create mode 100644 assets/templates/asset_list.html create mode 100644 assets/templates/asset_update.html create mode 100644 assets/templates/asset_update_search_results.html create mode 100644 assets/templates/partials/asset_buttons.html create mode 100644 assets/templates/partials/asset_form.html create mode 100644 assets/templates/partials/asset_list_table_body.html create mode 100644 assets/templates/partials/asset_picker.html create mode 100644 assets/templates/partials/cable_form.html create mode 100644 assets/templates/partials/parent_form.html create mode 100644 assets/templates/partials/purchasedetails_form.html create mode 100644 assets/templates/partials/render_field.html create mode 100644 assets/templates/partials/supplier_picker.html create mode 100644 assets/templates/supplier_detail.html create mode 100644 assets/templates/supplier_list.html create mode 100644 assets/templates/supplier_update.html create mode 100644 assets/templatetags/__init__.py create mode 100644 assets/templatetags/asset_templatetags.py create mode 100644 assets/urls.py create mode 100644 assets/views.py create mode 100644 pytest.ini create mode 100644 templates/base_assets.html create mode 100644 templates/base_rigs.html diff --git a/.gitignore b/.gitignore index b17c3115..041dcbd3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ htmlcov/ .tox/ .coverage .cache +.pytest_cache nosetests.xml coverage.xml @@ -107,3 +108,4 @@ atlassian-ide-plugin.xml com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties +.vscode/ \ No newline at end of file diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index a41ad2c9..5877ad74 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -58,6 +58,7 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'RIGS', + 'assets', 'debug_toolbar', 'registration', diff --git a/PyRIGS/urls.py b/PyRIGS/urls.py index 5cc85548..cb78130c 100644 --- a/PyRIGS/urls.py +++ b/PyRIGS/urls.py @@ -12,6 +12,7 @@ urlpatterns = [ # url(r'^blog/', include('blog.urls')), url(r'^', include('RIGS.urls')), + url('^assets/', include('assets.urls')), url('^user/register/$', RegistrationView.as_view(form_class=RIGS.forms.ProfileRegistrationFormUniqueEmail), name="registration_register"), url('^user/', include('django.contrib.auth.urls')), diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py index 5263a030..e19c569d 100644 --- a/RIGS/management/commands/generateSampleData.py +++ b/RIGS/management/commands/generateSampleData.py @@ -1,252 +1,11 @@ from django.core.management.base import BaseCommand, CommandError -from django.contrib.auth.models import Group, Permission -from django.db import transaction -from reversion import revisions as reversion - -import datetime -import random - -from RIGS import models +from django.core.management import call_command class Command(BaseCommand): help = 'Adds sample data to use for testing' can_import_settings = True - people = [] - organisations = [] - venues = [] - profiles = [] - - keyholder_group = None - finance_group = None - def handle(self, *args, **options): - from django.conf import settings - - if not (settings.DEBUG or settings.STAGING): - raise CommandError('You cannot run this command in production') - - random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests - - with transaction.atomic(): - models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') - - self.setupGenericProfiles() - - self.setupPeople() - self.setupOrganisations() - self.setupVenues() - - self.setupGroups() - - self.setupEvents() - - self.setupUsefulProfiles() - - def setupPeople(self): - names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa - "Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa - for i, name in enumerate(names): - with reversion.create_revision(): - reversion.set_user(random.choice(self.profiles)) - - newPerson = models.Person.objects.create(name=name) - if i % 3 == 0: - newPerson.email = "address@person.com" - - if i % 5 == 0: - newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" - - if i % 7 == 0: - newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567" - - if i % 9 == 0: - newPerson.phone = "01234 567894" - - newPerson.save() - self.people.append(newPerson) - - def setupOrganisations(self): - names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa - "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa - for i, name in enumerate(names): - with reversion.create_revision(): - reversion.set_user(random.choice(self.profiles)) - newOrganisation = models.Organisation.objects.create(name=name) - if i % 2 == 0: - newOrganisation.has_su_account = True - - if i % 3 == 0: - newOrganisation.email = "address@organisation.com" - - if i % 5 == 0: - newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" - - if i % 7 == 0: - newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567" - - if i % 9 == 0: - newOrganisation.phone = "01234 567894" - - newOrganisation.save() - self.organisations.append(newOrganisation) - - def setupVenues(self): - names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch", "The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands", "The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa - "Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa - for i, name in enumerate(names): - with reversion.create_revision(): - reversion.set_user(random.choice(self.profiles)) - newVenue = models.Venue.objects.create(name=name) - if i % 2 == 0: - newVenue.three_phase_available = True - - if i % 3 == 0: - newVenue.email = "address@venue.com" - - if i % 5 == 0: - newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" - - if i % 7 == 0: - newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567" - - if i % 9 == 0: - newVenue.phone = "01234 567894" - - newVenue.save() - self.venues.append(newVenue) - - def setupGroups(self): - self.keyholder_group = Group.objects.create(name='Keyholders') - self.finance_group = Group.objects.create(name='Finance') - - keyholderPerms = ["add_event", "change_event", "view_event", "add_eventitem", "change_eventitem", "delete_eventitem", "add_organisation", "change_organisation", "view_organisation", "add_person", "change_person", "view_person", "view_profile", "add_venue", "change_venue", "view_venue"] - financePerms = ["change_event", "view_event", "add_eventitem", "change_eventitem", "add_invoice", "change_invoice", "view_invoice", "add_organisation", "change_organisation", "view_organisation", "add_payment", "change_payment", "delete_payment", "add_person", "change_person", "view_person"] - - for permId in keyholderPerms: - self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) - - for permId in financePerms: - self.finance_group.permissions.add(Permission.objects.get(codename=permId)) - - def setupGenericProfiles(self): - names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", "Jack Harkness", "Mickey Smith", "Rose Tyler"] - for i, name in enumerate(names): - newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1], - email=name.replace(" ", "") + "@example.com", - initials="".join([j[0].upper() for j in name.split()])) - if i % 2 == 0: - newProfile.phone = "01234 567894" - - newProfile.save() - self.profiles.append(newProfile) - - def setupUsefulProfiles(self): - superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU", - email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True) - superUser.set_password('superuser') - superUser.save() - - financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU", - email="financeuser@example.com", is_active=True) - financeUser.groups.add(self.finance_group) - financeUser.groups.add(self.keyholder_group) - financeUser.set_password('finance') - financeUser.save() - - keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU", - email="keyholderuser@example.com", is_active=True) - keyholderUser.groups.add(self.keyholder_group) - keyholderUser.set_password('keyholder') - keyholderUser.save() - - basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU", - email="basicuser@example.com", is_active=True) - basicUser.set_password('basic') - basicUser.save() - - def setupEvents(self): - names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball", "Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event", - "End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"] - descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"] - notes = ["The client came into the office at some point", "Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"] - - itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00}, - {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00}, - {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52}, - {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00}, - {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50}, - {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00}, - {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00}, - {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00}, - {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}] - - dayDelta = -120 # start adding events from 4 months ago - - for i in range(150): # Let's add 100 events - with reversion.create_revision(): - reversion.set_user(random.choice(self.profiles)) - - name = names[i % len(names)] - - startDate = datetime.date.today() + datetime.timedelta(days=dayDelta) - dayDelta = dayDelta + random.randint(0, 3) - - newEvent = models.Event.objects.create(name=name, start_date=startDate) - - if random.randint(0, 2) > 1: # 1 in 3 have a start time - newEvent.start_time = datetime.time(random.randint(15, 20)) - if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day - newEvent.end_time = datetime.time(random.randint(21, 23)) - elif random.randint(0, 1) > 0: # half of the others finish early the next day - newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1) - newEvent.end_time = datetime.time(random.randint(0, 5)) - elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead - newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4)) - - if random.randint(0, 6) > 0: # 5 in 6 have MIC - newEvent.mic = random.choice(self.profiles) - - if random.randint(0, 6) > 0: # 5 in 6 have organisation - newEvent.organisation = random.choice(self.organisations) - - if random.randint(0, 6) > 0: # 5 in 6 have person - newEvent.person = random.choice(self.people) - - if random.randint(0, 6) > 0: # 5 in 6 have venue - newEvent.venue = random.choice(self.venues) - - # Could have any status, equally weighted - newEvent.status = random.choice([models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED]) - - newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire - - if random.randint(0, 1) > 0: # 1 in 2 have description - newEvent.description = random.choice(descriptions) - - if random.randint(0, 1) > 0: # 1 in 2 have notes - newEvent.notes = random.choice(notes) - - newEvent.save() - - # Now add some items - for j in range(random.randint(1, 5)): - itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] - newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) - newItem.save() - - while newEvent.sum_total < 0: - itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] - newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) - newItem.save() - - with reversion.create_revision(): - reversion.set_user(random.choice(self.profiles)) - if newEvent.start_date < datetime.date.today(): # think about adding an invoice - if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury - newInvoice = models.Invoice.objects.create(event=newEvent) - if newEvent.status is models.Event.CANCELLED: # void cancelled events - newInvoice.void = True - elif random.randint(0, 2) > 1: # 1 in 3 have been paid - models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today()) + call_command('generateSampleRIGSData') + call_command('generateSampleAssetsData') diff --git a/RIGS/management/commands/generateSampleRIGSData.py b/RIGS/management/commands/generateSampleRIGSData.py new file mode 100644 index 00000000..6b543971 --- /dev/null +++ b/RIGS/management/commands/generateSampleRIGSData.py @@ -0,0 +1,260 @@ +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import Group, Permission +from django.db import transaction +from reversion import revisions as reversion + +import datetime +import random + +from RIGS import models + + +class Command(BaseCommand): + help = 'Adds sample data to use for testing' + can_import_settings = True + + people = [] + organisations = [] + venues = [] + profiles = [] + + keyholder_group = None + finance_group = None + + def handle(self, *args, **options): + from django.conf import settings + + if not (settings.DEBUG or settings.STAGING): + raise CommandError('You cannot run this command in production') + + random.seed('Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistant tests + + with transaction.atomic(): + models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + + self.setupGenericProfiles() + + self.setupPeople() + self.setupOrganisations() + self.setupVenues() + + self.setupGroups() + + self.setupEvents() + + self.setupUsefulProfiles() + + def setupPeople(self): + names = ["Regulus Black", "Sirius Black", "Lavender Brown", "Cho Chang", "Vincent Crabbe", "Vincent Crabbe", "Bartemius Crouch", "Fleur Delacour", "Cedric Diggory", "Alberforth Dumbledore", "Albus Dumbledore", "Dudley Dursley", "Petunia Dursley", "Vernon Dursley", "Argus Filch", "Seamus Finnigan", "Nicolas Flamel", "Cornelius Fudge", "Goyle", "Gregory Goyle", "Hermione Granger", "Rubeus Hagrid", "Igor Karkaroff", "Viktor Krum", "Bellatrix Lestrange", "Alice Longbottom", "Frank Longbottom", "Neville Longbottom", "Luna Lovegood", "Xenophilius Lovegood", # noqa + "Remus Lupin", "Draco Malfoy", "Lucius Malfoy", "Narcissa Malfoy", "Olympe Maxime", "Minerva McGonagall", "Mad-Eye Moody", "Peter Pettigrew", "Harry Potter", "James Potter", "Lily Potter", "Quirinus Quirrell", "Tom Riddle", "Mary Riddle", "Lord Voldemort", "Rita Skeeter", "Severus Snape", "Nymphadora Tonks", "Dolores Janes Umbridge", "Arthur Weasley", "Bill Weasley", "Charlie Weasley", "Fred Weasley", "George Weasley", "Ginny Weasley", "Molly Weasley", "Percy Weasley", "Ron Weasley", "Dobby", "Fluffy", "Hedwig", "Moaning Myrtle", "Aragog", "Grawp"] # noqa + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + + newPerson = models.Person.objects.create(name=name) + if i % 3 == 0: + newPerson.email = "address@person.com" + + if i % 5 == 0: + newPerson.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newPerson.address = "1 Person Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newPerson.phone = "01234 567894" + + newPerson.save() + self.people.append(newPerson) + + def setupOrganisations(self): + names = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa + "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + newOrganisation = models.Organisation.objects.create(name=name) + if i % 2 == 0: + newOrganisation.has_su_account = True + + if i % 3 == 0: + newOrganisation.email = "address@organisation.com" + + if i % 5 == 0: + newOrganisation.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newOrganisation.address = "1 Organisation Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newOrganisation.phone = "01234 567894" + + newOrganisation.save() + self.organisations.append(newOrganisation) + + def setupVenues(self): + names = ["Bear Island", "Crossroads Inn", "Deepwood Motte", "The Dreadfort", "The Eyrie", "Greywater Watch", "The Iron Islands", "Karhold", "Moat Cailin", "Oldstones", "Raventree Hall", "Riverlands", "The Ruby Ford", "Saltpans", "Seagard", "Torrhen's Square", "The Trident", "The Twins", "The Vale of Arryn", "The Whispering Wood", "White Harbor", "Winterfell", "The Arbor", "Ashemark", "Brightwater Keep", "Casterly Rock", "Clegane's Keep", "Dragonstone", "Dorne", "God's Eye", "The Golden Tooth", # noqa + "Harrenhal", "Highgarden", "Horn Hill", "Fingers", "King's Landing", "Lannisport", "Oldtown", "Rainswood", "Storm's End", "Summerhall", "Sunspear", "Tarth", "Castle Black", "Craster's Keep", "Fist of the First Men", "The Frostfangs", "The Gift", "The Skirling Pass", "The Wall", "Asshai", "Astapor", "Braavos", "The Dothraki Sea", "Lys", "Meereen", "Myr", "Norvos", "Pentos", "Qarth", "Qohor", "The Red Waste", "Tyrosh", "Vaes Dothrak", "Valyria", "Village of the Lhazareen", "Volantis", "Yunkai"] # noqa + for i, name in enumerate(names): + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + newVenue = models.Venue.objects.create(name=name) + if i % 2 == 0: + newVenue.three_phase_available = True + + if i % 3 == 0: + newVenue.email = "address@venue.com" + + if i % 5 == 0: + newVenue.notes = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" + + if i % 7 == 0: + newVenue.address = "1 Venue Test Street \n Demoton \n United States of TEC \n RMRF 567" + + if i % 9 == 0: + newVenue.phone = "01234 567894" + + newVenue.save() + self.venues.append(newVenue) + + def setupGroups(self): + self.keyholder_group = Group.objects.create(name='Keyholders') + self.finance_group = Group.objects.create(name='Finance') + + keyholderPerms = ["add_event", "change_event", "view_event", + "add_eventitem", "change_eventitem", "delete_eventitem", + "add_organisation", "change_organisation", "view_organisation", + "add_person", "change_person", "view_person", "view_profile", + "add_venue", "change_venue", "view_venue", + "add_asset", "change_asset", "delete_asset", + "asset_finance", "view_asset", "view_supplier", "asset_finance", + "add_supplier"] + financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice", + "add_payment", "change_payment", "delete_payment"] + + for permId in keyholderPerms: + self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) + + for permId in financePerms: + self.finance_group.permissions.add(Permission.objects.get(codename=permId)) + + def setupGenericProfiles(self): + names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", "Jack Harkness", "Mickey Smith", "Rose Tyler"] + for i, name in enumerate(names): + newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], last_name=name.split(" ")[-1], + email=name.replace(" ", "") + "@example.com", + initials="".join([j[0].upper() for j in name.split()])) + if i % 2 == 0: + newProfile.phone = "01234 567894" + + newProfile.save() + self.profiles.append(newProfile) + + def setupUsefulProfiles(self): + superUser = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", initials="SU", + email="superuser@example.com", is_superuser=True, is_active=True, is_staff=True) + superUser.set_password('superuser') + superUser.save() + + financeUser = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", initials="FU", + email="financeuser@example.com", is_active=True) + financeUser.groups.add(self.finance_group) + financeUser.groups.add(self.keyholder_group) + financeUser.set_password('finance') + financeUser.save() + + keyholderUser = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", initials="KU", + email="keyholderuser@example.com", is_active=True) + keyholderUser.groups.add(self.keyholder_group) + keyholderUser.set_password('keyholder') + keyholderUser.save() + + basicUser = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", initials="BU", + email="basicuser@example.com", is_active=True) + basicUser.set_password('basic') + basicUser.save() + + def setupEvents(self): + names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball", "Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event", + "End of Term Show", "Theatre Show", "Outdoor Fun Day", "Summer Carnival", "Open Days", "Magic Show", "Awards Ceremony", "Debating Event", "Club Night", "DJ Evening", "Building Projection", "Choir Concert"] + descriptions = ["A brief desciption of the event", "This event is boring", "Probably wont happen", "Warning: this has lots of kit"] + notes = ["The client came into the office at some point", "Who knows if this will happen", "Probably should check this event", "Maybe not happening", "Run away!"] + + itemOptions = [{'name': 'Speakers', 'description': 'Some really really big speakers \n these are very loud', 'quantity': 2, 'cost': 200.00}, + {'name': 'Projector', 'description': 'Some kind of video thinamejig, probably with unnecessary processing for free', 'quantity': 1, 'cost': 500.00}, + {'name': 'Lighting Desk', 'description': 'Cannot provide guarentee that it will work', 'quantity': 1, 'cost': 200.52}, + {'name': 'Moving lights', 'description': 'Flashy lights, with the copper', 'quantity': 8, 'cost': 50.00}, + {'name': 'Microphones', 'description': 'Make loud noise \n you will want speakers with this', 'quantity': 5, 'cost': 0.50}, + {'name': 'Sound Mixer Thing', 'description': 'Might be analogue, might be digital', 'quantity': 1, 'cost': 100.00}, + {'name': 'Electricity', 'description': 'You need this', 'quantity': 1, 'cost': 200.00}, + {'name': 'Crew', 'description': 'Costs nothing, because reasons', 'quantity': 1, 'cost': 0.00}, + {'name': 'Loyalty Discount', 'description': 'Have some negative moneys', 'quantity': 1, 'cost': -50.00}] + + dayDelta = -120 # start adding events from 4 months ago + + for i in range(150): # Let's add 100 events + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + + name = names[i % len(names)] + + startDate = datetime.date.today() + datetime.timedelta(days=dayDelta) + dayDelta = dayDelta + random.randint(0, 3) + + newEvent = models.Event.objects.create(name=name, start_date=startDate) + + if random.randint(0, 2) > 1: # 1 in 3 have a start time + newEvent.start_time = datetime.time(random.randint(15, 20)) + if random.randint(0, 2) > 1: # of those, 1 in 3 have an end time on the same day + newEvent.end_time = datetime.time(random.randint(21, 23)) + elif random.randint(0, 1) > 0: # half of the others finish early the next day + newEvent.end_date = newEvent.start_date + datetime.timedelta(days=1) + newEvent.end_time = datetime.time(random.randint(0, 5)) + elif random.randint(0, 2) > 1: # 1 in 3 of the others finish a few days ahead + newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4)) + + if random.randint(0, 6) > 0: # 5 in 6 have MIC + newEvent.mic = random.choice(self.profiles) + + if random.randint(0, 6) > 0: # 5 in 6 have organisation + newEvent.organisation = random.choice(self.organisations) + + if random.randint(0, 6) > 0: # 5 in 6 have person + newEvent.person = random.choice(self.people) + + if random.randint(0, 6) > 0: # 5 in 6 have venue + newEvent.venue = random.choice(self.venues) + + # Could have any status, equally weighted + newEvent.status = random.choice([models.Event.BOOKED, models.Event.CONFIRMED, models.Event.PROVISIONAL, models.Event.CANCELLED]) + + newEvent.dry_hire = (random.randint(0, 7) == 0) # 1 in 7 are dry hire + + if random.randint(0, 1) > 0: # 1 in 2 have description + newEvent.description = random.choice(descriptions) + + if random.randint(0, 1) > 0: # 1 in 2 have notes + newEvent.notes = random.choice(notes) + + newEvent.save() + + # Now add some items + for j in range(random.randint(1, 5)): + itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] + newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) + newItem.save() + + while newEvent.sum_total < 0: + itemData = itemOptions[random.randint(0, len(itemOptions) - 1)] + newItem = models.EventItem.objects.create(event=newEvent, order=j, **itemData) + newItem.save() + + with reversion.create_revision(): + reversion.set_user(random.choice(self.profiles)) + if newEvent.start_date < datetime.date.today(): # think about adding an invoice + if random.randint(0, 2) > 0: # 2 in 3 have had paperwork sent to treasury + newInvoice = models.Invoice.objects.create(event=newEvent) + if newEvent.status is models.Event.CANCELLED: # void cancelled events + newInvoice.void = True + elif random.randint(0, 2) > 1: # 1 in 3 have been paid + models.Payment.objects.create(invoice=newInvoice, amount=newInvoice.balance, date=datetime.date.today()) diff --git a/RIGS/templates/RIGS/activity_feed_data.html b/RIGS/templates/RIGS/activity_feed_data.html index 4de8bf7b..cff6c323 100644 --- a/RIGS/templates/RIGS/activity_feed_data.html +++ b/RIGS/templates/RIGS/activity_feed_data.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax_nomodal.html,base_rigs.html" %} {% load static %} {% load paginator from filters %} diff --git a/RIGS/templates/RIGS/activity_table.html b/RIGS/templates/RIGS/activity_table.html index e12dfd7a..f9addd87 100644 --- a/RIGS/templates/RIGS/activity_table.html +++ b/RIGS/templates/RIGS/activity_table.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load static %} {% load paginator from filters %} {% load to_class_name from filters %} diff --git a/RIGS/templates/RIGS/calendar.html b/RIGS/templates/RIGS/calendar.html index f6891d84..51e5e0b7 100644 --- a/RIGS/templates/RIGS/calendar.html +++ b/RIGS/templates/RIGS/calendar.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load static %} diff --git a/RIGS/templates/RIGS/event_archive.html b/RIGS/templates/RIGS/event_archive.html index 00baaad6..4fc5642e 100644 --- a/RIGS/templates/RIGS/event_archive.html +++ b/RIGS/templates/RIGS/event_archive.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load paginator from filters %} {% block title %}Event Archive{% endblock %} diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 6a18d2f3..176756dc 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% block title %}{% if object.is_rig %}N{{ object.pk|stringformat:"05d" }}{% else %}{{ object.pk }}{% endif %} | {{object.name}}{% endblock %} {% block content %} diff --git a/RIGS/templates/RIGS/event_form.html b/RIGS/templates/RIGS/event_form.html index d7ba9591..954ebdbd 100644 --- a/RIGS/templates/RIGS/event_form.html +++ b/RIGS/templates/RIGS/event_form.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load widget_tweaks %} {% load static %} {% load multiply from filters %} diff --git a/RIGS/templates/RIGS/event_invoice.html b/RIGS/templates/RIGS/event_invoice.html index ec9755c0..415293e9 100644 --- a/RIGS/templates/RIGS/event_invoice.html +++ b/RIGS/templates/RIGS/event_invoice.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load paginator from filters %} {% load static %} diff --git a/RIGS/templates/RIGS/eventauthorisation_request.html b/RIGS/templates/RIGS/eventauthorisation_request.html index 6067b3fe..dbb617c3 100644 --- a/RIGS/templates/RIGS/eventauthorisation_request.html +++ b/RIGS/templates/RIGS/eventauthorisation_request.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% load widget_tweaks %} {% block title %}Request Authorisation{% endblock %} diff --git a/RIGS/templates/RIGS/eventauthorisation_request_error.html b/RIGS/templates/RIGS/eventauthorisation_request_error.html index b366622d..aaebe4c0 100644 --- a/RIGS/templates/RIGS/eventauthorisation_request_error.html +++ b/RIGS/templates/RIGS/eventauthorisation_request_error.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% load widget_tweaks %} {% block title %}NottinghamTEC Email Address Required{% endblock %} diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index b5c4c511..826839e6 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% block title %}RIGS{% endblock %} {% block content %} @@ -20,6 +20,7 @@ Rigboard Calendar {% if perms.RIGS.add_event %} New Event{% endif %} + Asset Database
    diff --git a/RIGS/templates/RIGS/invoice_confirm_delete.html b/RIGS/templates/RIGS/invoice_confirm_delete.html index fe295ed6..4b575cb5 100644 --- a/RIGS/templates/RIGS/invoice_confirm_delete.html +++ b/RIGS/templates/RIGS/invoice_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %} diff --git a/RIGS/templates/RIGS/invoice_detail.html b/RIGS/templates/RIGS/invoice_detail.html index e1b77b6c..2e0211da 100644 --- a/RIGS/templates/RIGS/invoice_detail.html +++ b/RIGS/templates/RIGS/invoice_detail.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% block title %}Invoice {{ object.pk }}{% endblock %} diff --git a/RIGS/templates/RIGS/invoice_list.html b/RIGS/templates/RIGS/invoice_list.html index 63e61e11..fca84c27 100644 --- a/RIGS/templates/RIGS/invoice_list.html +++ b/RIGS/templates/RIGS/invoice_list.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load paginator from filters %} {% block title %}Invoices{% endblock %} diff --git a/RIGS/templates/RIGS/organisation_detail.html b/RIGS/templates/RIGS/organisation_detail.html index 7743086c..b12b6391 100644 --- a/RIGS/templates/RIGS/organisation_detail.html +++ b/RIGS/templates/RIGS/organisation_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% block title %}Organisation | {{ object.name }}{% endblock %} diff --git a/RIGS/templates/RIGS/organisation_form.html b/RIGS/templates/RIGS/organisation_form.html index 7ef77e0d..f95e31d7 100644 --- a/RIGS/templates/RIGS/organisation_form.html +++ b/RIGS/templates/RIGS/organisation_form.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% load widget_tweaks %} {% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Organisation{% endif %}{% endblock %} diff --git a/RIGS/templates/RIGS/organisation_list.html b/RIGS/templates/RIGS/organisation_list.html index c8856886..080ca938 100644 --- a/RIGS/templates/RIGS/organisation_list.html +++ b/RIGS/templates/RIGS/organisation_list.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% load paginator from filters %} {% load url_replace from filters %} diff --git a/RIGS/templates/RIGS/payment_confirm_delete.html b/RIGS/templates/RIGS/payment_confirm_delete.html index daa5d004..ab58e243 100644 --- a/RIGS/templates/RIGS/payment_confirm_delete.html +++ b/RIGS/templates/RIGS/payment_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% block title %}Delete payment on invoice {{ object.invoice.pk }}{% endblock %} diff --git a/RIGS/templates/RIGS/person_detail.html b/RIGS/templates/RIGS/person_detail.html index 10c995ae..e8a4f3ad 100644 --- a/RIGS/templates/RIGS/person_detail.html +++ b/RIGS/templates/RIGS/person_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% block title %}Person | {{ object.name }}{% endblock %} diff --git a/RIGS/templates/RIGS/person_form.html b/RIGS/templates/RIGS/person_form.html index a5720b9f..3ebf6409 100644 --- a/RIGS/templates/RIGS/person_form.html +++ b/RIGS/templates/RIGS/person_form.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% load widget_tweaks %} {% block title %}{% if object.pk %}Edit {{ object.name }}{% else %}Add Person{% endif %}{% endblock %} diff --git a/RIGS/templates/RIGS/person_list.html b/RIGS/templates/RIGS/person_list.html index 2cbdff8e..02b9eb2c 100644 --- a/RIGS/templates/RIGS/person_list.html +++ b/RIGS/templates/RIGS/person_list.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% load paginator from filters %} {% load url_replace from filters %} diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index 11904b18..d0a86444 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% block title %}RIGS Profile {{object.pk}}{% endblock %} diff --git a/RIGS/templates/RIGS/profile_form.html b/RIGS/templates/RIGS/profile_form.html index baa33424..b8426875 100644 --- a/RIGS/templates/RIGS/profile_form.html +++ b/RIGS/templates/RIGS/profile_form.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load widget_tweaks %} {% block title %}Update Profile {{object.name}}{% endblock %} diff --git a/RIGS/templates/RIGS/rigboard.html b/RIGS/templates/RIGS/rigboard.html index 9292e304..786f9385 100644 --- a/RIGS/templates/RIGS/rigboard.html +++ b/RIGS/templates/RIGS/rigboard.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% block title %}Rigboard{% endblock %} diff --git a/RIGS/templates/RIGS/venue_detail.html b/RIGS/templates/RIGS/venue_detail.html index ede9d4ec..25d3daef 100644 --- a/RIGS/templates/RIGS/venue_detail.html +++ b/RIGS/templates/RIGS/venue_detail.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% block title %}Venue | {{ object.name }}{% endblock %} diff --git a/RIGS/templates/RIGS/venue_form.html b/RIGS/templates/RIGS/venue_form.html index 4d99af7e..b410a062 100644 --- a/RIGS/templates/RIGS/venue_form.html +++ b/RIGS/templates/RIGS/venue_form.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:'base_ajax.html,base.html' %} +{% extends request.is_ajax|yesno:'base_ajax.html,base_rigs.html' %} {% load widget_tweaks %} {% block title %}{{ object.pk|yesno:"Edit,Add" }} Venue{% endblock %} diff --git a/RIGS/templates/RIGS/venue_list.html b/RIGS/templates/RIGS/venue_list.html index 88ae61ac..80c9d8ac 100644 --- a/RIGS/templates/RIGS/venue_list.html +++ b/RIGS/templates/RIGS/venue_list.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load widget_tweaks %} {% load paginator from filters %} {% load url_replace from filters %} diff --git a/RIGS/templates/RIGS/version_history.html b/RIGS/templates/RIGS/version_history.html index 18ff22d4..676d231e 100644 --- a/RIGS/templates/RIGS/version_history.html +++ b/RIGS/templates/RIGS/version_history.html @@ -1,4 +1,4 @@ -{% extends request.is_ajax|yesno:"base_ajax.html,base.html" %} +{% extends request.is_ajax|yesno:"base_ajax.html,base_rigs.html" %} {% load to_class_name from filters %} {% load paginator from filters %} {% load static %} diff --git a/RIGS/test_functional.py b/RIGS/test_functional.py index 6006e995..aa65158a 100644 --- a/RIGS/test_functional.py +++ b/RIGS/test_functional.py @@ -88,7 +88,7 @@ class UserRegistrationTest(LiveServerTestCase): initials.send_keys('JS') phone.send_keys('0123456789') self.browser.execute_script( - "return jQuery('#g-recaptcha-response').val('PASSED')") + "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()") # Submit incorrect form submit = self.browser.find_element_by_xpath("//input[@type='submit']") @@ -110,8 +110,9 @@ class UserRegistrationTest(LiveServerTestCase): # Correct error password1.send_keys('correcthorsebatterystaple') password2.send_keys('correcthorsebatterystaple') + self.browser.execute_script("console.log('Hello, world!')") self.browser.execute_script( - "return jQuery('#g-recaptcha-response').val('PASSED')") + "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()") # Submit again password2.send_keys(Keys.ENTER) @@ -150,7 +151,7 @@ class UserRegistrationTest(LiveServerTestCase): username.send_keys('TestUsername') password.send_keys('correcthorsebatterystaple') self.browser.execute_script( - "return jQuery('#g-recaptcha-response').val('PASSED')") + "return function() {jQuery('#g-recaptcha-response').val('PASSED'); return 0}()") password.send_keys(Keys.ENTER) # Check we are logged in diff --git a/RIGS/test_unit.py b/RIGS/test_unit.py index 4a020292..0a360a10 100644 --- a/RIGS/test_unit.py +++ b/RIGS/test_unit.py @@ -414,7 +414,7 @@ class TestSampleDataGenerator(TestCase): @override_settings(DEBUG=True) def test_generate_sample_data(self): # Run the management command and check there are no exceptions - call_command('generateSampleData') + call_command('generateSampleRIGSData') # Check there are lots of events self.assertTrue(models.Event.objects.all().count() > 100) @@ -422,4 +422,4 @@ class TestSampleDataGenerator(TestCase): def test_production_exception(self): from django.core.management.base import CommandError - self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleData') + self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleRIGSData') diff --git a/assets/__init__.py b/assets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/admin.py b/assets/admin.py new file mode 100644 index 00000000..7044040f --- /dev/null +++ b/assets/admin.py @@ -0,0 +1,37 @@ +from django.contrib import admin +from assets import models as assets + + +@admin.register(assets.AssetCategory) +class AssetCategoryAdmin(admin.ModelAdmin): + list_display = ['id', 'name'] + ordering = ['id'] + + +@admin.register(assets.AssetStatus) +class AssetStatusAdmin(admin.ModelAdmin): + list_display = ['id', 'name'] + ordering = ['id'] + + +@admin.register(assets.Supplier) +class SupplierAdmin(admin.ModelAdmin): + list_display = ['id', 'name'] + ordering = ['id'] + + +@admin.register(assets.Asset) +class AssetAdmin(admin.ModelAdmin): + list_display = ['id', 'asset_id', 'description', 'category', 'status'] + list_filter = ['is_cable', 'category'] + search_fields = ['id', 'asset_id', 'description'] + + +@admin.register(assets.Connector) +class ConnectorAdmin(admin.ModelAdmin): + list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins'] + + +admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System' +admin.AdminSite.site_title = 'PyAssets Admin' +admin.AdminSite.index_title = 'System Administration' diff --git a/assets/apps.py b/assets/apps.py new file mode 100644 index 00000000..5569d303 --- /dev/null +++ b/assets/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AssetsConfig(AppConfig): + name = 'assets' diff --git a/assets/filters.py b/assets/filters.py new file mode 100644 index 00000000..c7efcd7f --- /dev/null +++ b/assets/filters.py @@ -0,0 +1,9 @@ +import django_filters + +from assets import models + + +class AssetFilter(django_filters.FilterSet): + class Meta: + model = models.Asset + fields = ['asset_id', 'description', 'category', 'status'] diff --git a/assets/forms.py b/assets/forms.py new file mode 100644 index 00000000..430eef20 --- /dev/null +++ b/assets/forms.py @@ -0,0 +1,30 @@ +from django import forms + +from assets import models + + +class AssetForm(forms.ModelForm): + class Meta: + model = models.Asset + fields = '__all__' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['date_sold'].widget.format = '%Y-%m-%d' + self.fields['date_acquired'].widget.format = '%Y-%m-%d' + + +class AssetSearchForm(forms.Form): + query = forms.CharField(required=False) + category = forms.ModelMultipleChoiceField(models.AssetCategory.objects.all(), required=False) + status = forms.ModelMultipleChoiceField(models.AssetStatus.objects.all(), required=False) + + +class SupplierForm(forms.ModelForm): + class Meta: + model = models.Supplier + fields = '__all__' + + +class SupplierSearchForm(forms.Form): + query = forms.CharField(required=False) diff --git a/assets/management/__init__.py b/assets/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/management/commands/__init__.py b/assets/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/management/commands/deleteSampleData.py b/assets/management/commands/deleteSampleData.py new file mode 100644 index 00000000..cdf34ce9 --- /dev/null +++ b/assets/management/commands/deleteSampleData.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand, CommandError + +from assets import models + + +class Command(BaseCommand): + help = 'Deletes testing sample data' + + def handle(self, *args, **kwargs): + from django.conf import settings + + if not (settings.DEBUG): + raise CommandError('You cannot run this command in production') + + self.delete_objects(models.AssetCategory) + self.delete_objects(models.AssetStatus) + self.delete_objects(models.Supplier) + self.delete_objects(models.Connector) + self.delete_objects(models.Asset) + + def delete_objects(self, model): + for object in model.objects.all(): + object.delete() diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py new file mode 100644 index 00000000..15961ab9 --- /dev/null +++ b/assets/management/commands/generateSampleAssetsData.py @@ -0,0 +1,114 @@ +from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone + +from assets import models + + +class Command(BaseCommand): + help = 'Creates some sample data for testing' + + def handle(self, *args, **kwargs): + from django.conf import settings + + if not (settings.DEBUG or settings.STAGING): + raise CommandError('You cannot run this command in production') + + random.seed('Some object to see the random number generator') + + self.create_categories() + self.create_statuses() + self.create_suppliers() + self.create_assets() + self.create_connectors() + self.create_cables() + + def create_categories(self): + categories = ['Case', 'Video', 'General', 'Sound', 'Lighting', 'Rigging'] + + for cat in categories: + models.AssetCategory.objects.create(name=cat) + + def create_statuses(self): + statuses = [('In Service', True), ('Lost', False), ('Binned', False), ('Sold', False), ('Broken', False)] + + for stat in statuses: + models.AssetStatus.objects.create(name=stat[0], should_show=stat[1]) + + def create_suppliers(self): + suppliers = ["Acme, inc.", "Widget Corp", "123 Warehousing", "Demo Company", "Smith and Co.", "Foo Bars", "ABC Telecom", "Fake Brothers", "QWERTY Logistics", "Demo, inc.", "Sample Company", "Sample, inc", "Acme Corp", "Allied Biscuit", "Ankh-Sto Associates", "Extensive Enterprise", "Galaxy Corp", "Globo-Chem", "Mr. Sparkle", "Globex Corporation", "LexCorp", "LuthorCorp", "North Central Positronics", "Omni Consimer Products", "Praxis Corporation", "Sombra Corporation", "Sto Plains Holdings", "Tessier-Ashpool", "Wayne Enterprises", "Wentworth Industries", "ZiffCorp", "Bluth Company", "Strickland Propane", "Thatherton Fuels", "Three Waters", "Water and Power", "Western Gas & Electric", "Mammoth Pictures", "Mooby Corp", "Gringotts", "Thrift Bank", "Flowers By Irene", "The Legitimate Businessmens Club", "Osato Chemicals", "Transworld Consortium", "Universal Export", "United Fried Chicken", "Virtucon", "Kumatsu Motors", "Keedsler Motors", "Powell Motors", "Industrial Automation", "Sirius Cybernetics Corporation", "U.S. Robotics and Mechanical Men", "Colonial Movers", "Corellian Engineering Corporation", "Incom Corporation", "General Products", "Leeding Engines Ltd.", "Blammo", # noqa + "Input, Inc.", "Mainway Toys", "Videlectrix", "Zevo Toys", "Ajax", "Axis Chemical Co.", "Barrytron", "Carrys Candles", "Cogswell Cogs", "Spacely Sprockets", "General Forge and Foundry", "Duff Brewing Company", "Dunder Mifflin", "General Services Corporation", "Monarch Playing Card Co.", "Krustyco", "Initech", "Roboto Industries", "Primatech", "Sonky Rubber Goods", "St. Anky Beer", "Stay Puft Corporation", "Vandelay Industries", "Wernham Hogg", "Gadgetron", "Burleigh and Stronginthearm", "BLAND Corporation", "Nordyne Defense Dynamics", "Petrox Oil Company", "Roxxon", "McMahon and Tate", "Sixty Second Avenue", "Charles Townsend Agency", "Spade and Archer", "Megadodo Publications", "Rouster and Sideways", "C.H. Lavatory and Sons", "Globo Gym American Corp", "The New Firm", "SpringShield", "Compuglobalhypermeganet", "Data Systems", "Gizmonic Institute", "Initrode", "Taggart Transcontinental", "Atlantic Northern", "Niagular", "Plow King", "Big Kahuna Burger", "Big T Burgers and Fries", "Chez Quis", "Chotchkies", "The Frying Dutchman", "Klimpys", "The Krusty Krab", "Monks Diner", "Milliways", "Minuteman Cafe", "Taco Grande", "Tip Top Cafe", "Moes Tavern", "Central Perk", "Chasers"] # noqa + + for supplier in suppliers: + models.Supplier.objects.create(name=supplier) + + def create_assets(self): + asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink'] + + categories = models.AssetCategory.objects.all() + statuses = models.AssetStatus.objects.all() + suppliers = models.Supplier.objects.all() + + for i in range(100): + asset = models.Asset.objects.create( + asset_id='{}'.format(models.Asset.get_available_asset_id()), + description=random.choice(asset_description), + category=random.choice(categories), + status=random.choice(statuses), + date_acquired=timezone.now().date() + ) + + if i % 4 == 0: + asset.parent = models.Asset.objects.order_by('?').first() + + if i % 3 == 0: + asset.purchased_from = random.choice(suppliers) + + asset.save() + + def create_cables(self): + asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep'] + + csas = [0.75, 1.00, 1.25, 2.5, 4] + lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100] + cores = [3, 5] + circuits = [1, 2, 3, 6] + categories = models.AssetCategory.objects.all() + statuses = models.AssetStatus.objects.all() + suppliers = models.Supplier.objects.all() + connectors = models.Connector.objects.all() + + for i in range(100): + asset = models.Asset.objects.create( + asset_id='{}'.format(models.Asset.get_available_asset_id()), + description=random.choice(asset_description), + category=random.choice(categories), + status=random.choice(statuses), + date_acquired=timezone.now().date(), + + is_cable=True, + plug=random.choice(connectors), + socket=random.choice(connectors), + csa=random.choice(csas), + length=random.choice(lengths), + circuits=random.choice(circuits), + cores=random.choice(circuits) + ) + + if i % 4 == 0: + asset.parent = models.Asset.objects.order_by('?').first() + + if i % 3 == 0: + asset.purchased_from = random.choice(suppliers) + + asset.save() + + def create_connectors(self): + connectors = [ + {"description": "13A UK", "current_rating": 13, "voltage_rating": 230, "num_pins": 3}, + {"description": "16A", "current_rating": 16, "voltage_rating": 230, "num_pins": 3}, + {"description": "32/3", "current_rating": 32, "voltage_rating": 400, "num_pins": 5}, + {"description": "Socapex", "current_rating": 23, "voltage_rating": 600, "num_pins": 19}, + ] + for connector in connectors: + conn = models.Connector.objects.create(** connector) + conn.save() diff --git a/assets/management/commands/import_old_db.py b/assets/management/commands/import_old_db.py new file mode 100644 index 00000000..0fcff787 --- /dev/null +++ b/assets/management/commands/import_old_db.py @@ -0,0 +1,229 @@ +import os +import datetime +import xml.etree.ElementTree as ET +from django.core.management.base import BaseCommand +from django.conf import settings + +from assets import models + + +class Command(BaseCommand): + help = 'Imports old db from XML dump' + + epoch = datetime.date(1970, 1, 1) + + def handle(self, *args, **options): + self.import_categories() + self.import_statuses() + self.import_suppliers() + self.import_collections() + self.import_assets() + self.import_cables() + + @staticmethod + def xml_path(file): + return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file)) + + @staticmethod + def parse_xml(file): + tree = ET.parse(file) + + return tree.getroot() + + def import_categories(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Categories.xml')) + + for child in root: + obj, created = models.AssetCategory.objects.update_or_create( + pk=int(child.find('AssetCategoryID').text), + name=child.find('AssetCategory').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Categories - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_statuses(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Status_new.xml')) + + for child in root: + obj, created = models.AssetStatus.objects.update_or_create( + pk=int(child.find('StatusID').text), + name=child.find('Status').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Statuses - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_suppliers(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Asset_Suppliers_new.xml')) + + for child in root: + obj, created = models.Supplier.objects.update_or_create( + pk=int(child.find('Supplier_x0020_Id').text), + name=child.find('Supplier_x0020_Name').text + ) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Suppliers - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_assets(self): + # 0: updated, 1: created + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Assets.xml')) + + for child in root: + defaults = dict() + + # defaults['pk'] = int(child.find('ID').text) + defaults['asset_id'] = child.find('AssetID').text + + try: + defaults['description'] = child.find('AssetDescription').text + except AttributeError: + defaults['description'] = 'None' + + defaults['category'] = models.AssetCategory.objects.get(pk=int(child.find('AssetCategoryID').text)) + defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('StatusID').text)) + + try: + defaults['serial_number'] = child.find('SerialNumber').text + except AttributeError: + pass + + try: + defaults['purchased_from'] = models.Supplier.objects.get(pk=int(child.find('Supplier_x0020_Id').text)) + except AttributeError: + pass + + try: + defaults['date_acquired'] = datetime.datetime.strptime(child.find('DateAcquired').text, '%d/%m/%Y').date() + except AttributeError: + defaults['date_acquired'] = self.epoch + + try: + defaults['date_sold'] = datetime.datetime.strptime(child.find('DateSold').text, '%d/%m/%Y').date() + except AttributeError: + pass + + try: + defaults['purchase_price'] = float(child.find('Replacement_x0020_Value').text) + except AttributeError: + pass + + try: + defaults['salvage_value'] = float(child.find('SalvageValue').text) + except AttributeError: + pass + + try: + defaults['comments'] = child.find('Comments').text + except AttributeError: + pass + + try: + date = child.find('NextSchedMaint').text.split('T')[0] + defaults['next_sched_maint'] = datetime.datetime.strptime(date, '%Y-%m-%d').date() + except AttributeError: + pass + + print(defaults) + + obj, created = models.Asset.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Assets - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_collections(self): + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Cable_Collections.xml')) + + for child in root: + defaults = dict() + + defaults['pk'] = int(child.find('ID').text) + defaults['name'] = child.find('Cable_x0020_Trunk').text + + obj, created = models.Collection.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1])) + + def import_cables(self): + tally = [0, 0] + root = self.parse_xml(self.xml_path('TEC_Cables.xml')) + + for child in root: + defaults = dict() + + defaults['asset_id'] = child.find('Asset_x0020_Number').text + + try: + defaults['description'] = child.find('Type_x0020_of_x0020_Cable').text + except AttributeError: + defaults['description'] = 'None' + + defaults['is_cable'] = True + defaults['category'] = models.AssetCategory.objects.get(pk=9) + + try: + defaults['length'] = child.find('Length_x0020__x0028_m_x0029_').text + except AttributeError: + pass + + defaults['status'] = models.AssetStatus.objects.get(pk=int(child.find('Status').text)) + + try: + defaults['comments'] = child.find('Comments').text + except AttributeError: + pass + + try: + collection_id = int(child.find('Collection').text) + if collection_id != 0: + defaults['collection'] = models.Collection.objects.get(pk=collection_id) + except AttributeError: + pass + + try: + defaults['purchase_price'] = float(child.find('Purchase_x0020_Price').text) + except AttributeError: + pass + + defaults['date_acquired'] = self.epoch + + print(defaults) + + obj, created = models.Asset.objects.update_or_create(**defaults) + + if created: + tally[1] += 1 + else: + tally[0] += 1 + + print('Collections - Updated: {}, Created: {}'.format(tally[0], tally[1])) diff --git a/assets/management/commands/update_old_db_file.py b/assets/management/commands/update_old_db_file.py new file mode 100644 index 00000000..bff0fe22 --- /dev/null +++ b/assets/management/commands/update_old_db_file.py @@ -0,0 +1,110 @@ +import os +import datetime +import xml.etree.ElementTree as ET +from django.core.management.base import BaseCommand +from django.conf import settings + + +class Command(BaseCommand): + help = 'Imports old db from XML dump' + + epoch = datetime.date(1970, 1, 1) + + def handle(self, *args, **options): + # self.update_statuses() + # self.update_suppliers() + self.update_cable_statuses() + + @staticmethod + def xml_path(file): + return os.path.join(settings.BASE_DIR, 'data/DB_Dump/{}'.format(file)) + + @staticmethod + def parse_xml(file): + tree = ET.parse(file) + + return tree.getroot() + + def update_statuses(self): + file = self.xml_path('TEC_Assets.xml') + tree = ET.parse(file) + root = tree.getroot() + + # map old status pk to new status pk + status_map = { + 2: 2, + 3: 4, + 4: 3, + 5: 5, + 6: 1 + } + + for child in root: + status = int(child.find('StatusID').text) + child.find('StatusID').text = str(status_map[status]) + + tree.write(file) + + def update_suppliers(self): + old_file = self.xml_path('TEC_Asset_Suppliers.xml') + old_tree = ET.parse(old_file) + old_root = old_tree.getroot() + + new_file = self.xml_path('TEC_Asset_Suppliers_new.xml') + new_tree = ET.parse(new_file) + new_root = new_tree.getroot() + + # map old supplier pk to new supplier pk + supplier_map = dict() + + def find_in_old(name, root): + for child in root: + found_id = child.find('Supplier_x0020_Id').text + found_name = child.find('Supplier_x0020_Name').text + + if found_name == name: + return found_id + + for new_child in new_root: + new_id = new_child.find('Supplier_x0020_Id').text + new_name = new_child.find('Supplier_x0020_Name').text + + old_id = find_in_old(new_name, old_root) + + supplier_map[int(old_id)] = int(new_id) + + file = self.xml_path('TEC_Assets.xml') + tree = ET.parse(file) + root = tree.getroot() + + for child in root: + try: + supplier = int(child.find('Supplier_x0020_Id').text) + child.find('Supplier_x0020_Id').text = str(supplier_map[supplier]) + except AttributeError: + pass + + tree.write(file) + + def update_cable_statuses(self): + file = self.xml_path('TEC_Cables.xml') + tree = ET.parse(file) + root = tree.getroot() + + # map old status pk to new status pk + status_map = { + 0: 7, + 1: 3, + 3: 2, + 4: 5, + 6: 6, + 7: 1, + 8: 4, + 9: 2, + } + + for child in root: + status = int(child.find('Status').text) + child.find('Status').text = str(status_map[status]) + + tree.write(file) diff --git a/assets/migrations/0001_initial.py b/assets/migrations/0001_initial.py new file mode 100644 index 00000000..c1951b19 --- /dev/null +++ b/assets/migrations/0001_initial.py @@ -0,0 +1,86 @@ +# Generated by Django 2.0.2 on 2018-02-28 16:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Asset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('asset_id', models.IntegerField()), + ('description', models.CharField(max_length=120)), + ('serial_number', models.CharField(blank=True, max_length=150, null=True)), + ('date_acquired', models.DateField()), + ('date_sold', models.DateField(blank=True, null=True)), + ('purchase_price', models.IntegerField()), + ('salvage_value', models.IntegerField(blank=True, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('next_sched_maint', models.DateField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='AssetCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + options={ + 'verbose_name': 'Asset Category', + 'verbose_name_plural': 'Asset Categories', + }, + ), + migrations.CreateModel( + name='AssetStatus', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + options={ + 'verbose_name': 'Asset Status', + 'verbose_name_plural': 'Asset Statuses', + }, + ), + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + ), + migrations.CreateModel( + name='Supplier', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80)), + ], + ), + migrations.AddField( + model_name='asset', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory'), + ), + migrations.AddField( + model_name='asset', + name='collection', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'), + ), + migrations.AddField( + model_name='asset', + name='purchased_from', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'), + ), + migrations.AddField( + model_name='asset', + name='status', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'), + ), + ] diff --git a/assets/migrations/0002_auto_20180301_1654.py b/assets/migrations/0002_auto_20180301_1654.py new file mode 100644 index 00000000..915a7151 --- /dev/null +++ b/assets/migrations/0002_auto_20180301_1654.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 16:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True), + ), + ] diff --git a/assets/migrations/0003_auto_20180301_1700.py b/assets/migrations/0003_auto_20180301_1700.py new file mode 100644 index 00000000..feef9a6c --- /dev/null +++ b/assets/migrations/0003_auto_20180301_1700.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0002_auto_20180301_1654'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='purchase_price', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/assets/migrations/0004_auto_20180301_1711.py b/assets/migrations/0004_auto_20180301_1711.py new file mode 100644 index 00000000..4c884e93 --- /dev/null +++ b/assets/migrations/0004_auto_20180301_1711.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0003_auto_20180301_1700'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='collection', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Collection'), + ), + ] diff --git a/assets/migrations/0005_auto_20180301_1725.py b/assets/migrations/0005_auto_20180301_1725.py new file mode 100644 index 00000000..49fae9c6 --- /dev/null +++ b/assets/migrations/0005_auto_20180301_1725.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.2 on 2018-03-01 17:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0004_auto_20180301_1711'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py b/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py new file mode 100644 index 00000000..f15341e1 --- /dev/null +++ b/assets/migrations/0006_auto_20180728_1451_squashed_0021_auto_20190105_1156.py @@ -0,0 +1,148 @@ +# Generated by Django 2.1.5 on 2019-01-05 19:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + replaces = [('assets', '0006_auto_20180728_1451'), ('assets', '0007_auto_20181215_1447'), ('assets', '0008_auto_20181215_1448'), ('assets', '0009_auto_20181215_1640'), ('assets', '0010_auto_20181215_1640'), ('assets', '0011_auto_20181215_1749'), ('assets', '0012_auto_20181215_1813'), ('assets', '0013_asset_parent'), ('assets', '0014_auto_20190103_1615'), ('assets', '0015_auto_20190103_1617'), ('assets', '0016_remove_asset_collection'), ('assets', '0017_delete_collection'), ('assets', '0018_auto_20190103_1708'), ('assets', '0019_auto_20190103_1723'), ('assets', '0020_auto_20190103_1729'), ('assets', '0021_auto_20190105_1156')] + + dependencies = [ + ('assets', '0005_auto_20180301_1725'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True, unique=True), + ), + migrations.AlterField( + model_name='asset', + name='purchase_price', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='salvage_value', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='is_cable', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='asset', + name='length', + field=models.DecimalField(blank=True, decimal_places=1, max_digits=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(blank=True, max_length=10, null=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(default='', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='asset', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Asset'), + ), + migrations.RemoveField( + model_name='asset', + name='collection', + ), + migrations.DeleteModel( + name='Collection', + ), + migrations.CreateModel( + name='Cable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('asset_id', models.CharField(max_length=10)), + ('description', models.CharField(max_length=120)), + ('serial_number', models.CharField(blank=True, max_length=150, null=True)), + ('date_acquired', models.DateField()), + ('date_sold', models.DateField(blank=True, null=True)), + ('purchase_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('salvage_value', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('comments', models.TextField(blank=True, null=True)), + ('next_sched_maint', models.DateField(blank=True, null=True)), + ('is_cable', models.BooleanField(default=False)), + ('length', models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True)), + ('csa', models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True)), + ('circuits', models.IntegerField(blank=True, null=True)), + ('cores', models.IntegerField(blank=True, null=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetCategory')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=None, related_name='asset_parent', to='assets.Cable')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Connector', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=80)), + ('current_rating', models.DecimalField(decimal_places=2, help_text='Amps', max_digits=10)), + ('voltage_rating', models.IntegerField(default=0, help_text='Volts')), + ('num_pins', models.IntegerField(blank=True, null=True)), + ], + ), + migrations.AddField( + model_name='cable', + name='plug', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'), + ), + migrations.AddField( + model_name='cable', + name='purchased_from', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.Supplier'), + ), + migrations.AddField( + model_name='cable', + name='socket', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'), + ), + migrations.AddField( + model_name='cable', + name='status', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.AssetStatus'), + ), + migrations.AlterField( + model_name='asset', + name='comments', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='asset', + name='serial_number', + field=models.CharField(blank=True, default='', max_length=150), + preserve_default=False, + ), + migrations.AlterField( + model_name='cable', + name='comments', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='cable', + name='serial_number', + field=models.CharField(blank=True, default='', max_length=150), + preserve_default=False, + ), + ] diff --git a/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py b/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py new file mode 100644 index 00000000..116fa1d2 --- /dev/null +++ b/assets/migrations/0007_auto_20190108_0202_squashed_0014_auto_20191017_2052.py @@ -0,0 +1,176 @@ +# Generated by Django 2.0.13 on 2019-12-04 17:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + replaces = [('assets', '0007_auto_20190108_0202'), ('assets', '0008_auto_20191002_1931'), ('assets', '0009_auto_20191008_2148'), ('assets', '0010_auto_20191013_2123'), ('assets', '0011_auto_20191013_2247'), ('assets', '0012_auto_20191014_0012'), ('assets', '0013_auto_20191016_1446'), ('assets', '0014_auto_20191017_2052')] + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('assets', '0006_auto_20180728_1451_squashed_0021_auto_20190105_1156'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Asset'), + ), + migrations.AlterField( + model_name='cable', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_parent', to='assets.Cable'), + ), + migrations.AlterField( + model_name='connector', + name='voltage_rating', + field=models.IntegerField(help_text='Volts'), + ), + migrations.AlterModelOptions( + name='asset', + options={'base_manager_name': 'objects'}, + ), + migrations.AlterModelOptions( + name='cable', + options={'base_manager_name': 'objects'}, + ), + migrations.RemoveField( + model_name='asset', + name='length', + ), + migrations.RemoveField( + model_name='cable', + name='asset_id', + ), + migrations.RemoveField( + model_name='cable', + name='category', + ), + migrations.RemoveField( + model_name='cable', + name='comments', + ), + migrations.RemoveField( + model_name='cable', + name='date_acquired', + ), + migrations.RemoveField( + model_name='cable', + name='date_sold', + ), + migrations.RemoveField( + model_name='cable', + name='description', + ), + migrations.RemoveField( + model_name='cable', + name='id', + ), + migrations.RemoveField( + model_name='cable', + name='is_cable', + ), + migrations.RemoveField( + model_name='cable', + name='next_sched_maint', + ), + migrations.RemoveField( + model_name='cable', + name='parent', + ), + migrations.RemoveField( + model_name='cable', + name='purchase_price', + ), + migrations.RemoveField( + model_name='cable', + name='purchased_from', + ), + migrations.RemoveField( + model_name='cable', + name='salvage_value', + ), + migrations.RemoveField( + model_name='cable', + name='serial_number', + ), + migrations.RemoveField( + model_name='cable', + name='status', + ), + migrations.AddField( + model_name='asset', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_assets.asset_set+', to='contenttypes.ContentType'), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(max_length=10, unique=True), + ), + migrations.RemoveField( + model_name='cable', + name='plug', + ), + migrations.RemoveField( + model_name='cable', + name='socket', + ), + migrations.AlterModelOptions( + name='asset', + options={}, + ), + migrations.RemoveField( + model_name='asset', + name='polymorphic_ctype', + ), + migrations.AddField( + model_name='asset', + name='circuits', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='cores', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='asset', + name='csa', + field=models.DecimalField(blank=True, decimal_places=2, help_text='mm^2', max_digits=10, null=True), + ), + migrations.AddField( + model_name='asset', + name='length', + field=models.DecimalField(blank=True, decimal_places=1, help_text='m', max_digits=10, null=True), + ), + migrations.AddField( + model_name='asset', + name='plug', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plug', to='assets.Connector'), + ), + migrations.AddField( + model_name='asset', + name='socket', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='socket', to='assets.Connector'), + ), + migrations.DeleteModel( + name='Cable', + ), + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['asset_id'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))}, + ), + migrations.AddField( + model_name='assetstatus', + name='should_show', + field=models.BooleanField(default=True, help_text='Should this be shown by default in the asset list.'), + ), + migrations.AlterModelOptions( + name='supplier', + options={'permissions': (('view_supplier', 'Can view a supplier'),)}, + ), + ] diff --git a/assets/migrations/__init__.py b/assets/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/models.py b/assets/models.py new file mode 100644 index 00000000..7abddfd2 --- /dev/null +++ b/assets/models.py @@ -0,0 +1,141 @@ +import re +from django.core.exceptions import ValidationError +from django.db import models, connection +from django.urls import reverse + + +class AssetCategory(models.Model): + class Meta: + verbose_name = 'Asset Category' + verbose_name_plural = 'Asset Categories' + + name = models.CharField(max_length=80) + + def __str__(self): + return self.name + + +class AssetStatus(models.Model): + class Meta: + verbose_name = 'Asset Status' + verbose_name_plural = 'Asset Statuses' + + name = models.CharField(max_length=80) + should_show = models.BooleanField(default=True, help_text="Should this be shown by default in the asset list.") + + def __str__(self): + return self.name + + +class Supplier(models.Model): + name = models.CharField(max_length=80) + + class Meta: + permissions = ( + ('view_supplier', 'Can view a supplier'), + ) + + def get_absolute_url(self): + return reverse('supplier_list') + + def __str__(self): + return self.name + + +class Connector(models.Model): + description = models.CharField(max_length=80) + current_rating = models.DecimalField(decimal_places=2, max_digits=10, help_text='Amps') + voltage_rating = models.IntegerField(help_text='Volts') + num_pins = models.IntegerField(blank=True, null=True) + + def __str__(self): + return self.description + + +class Asset(models.Model): + class Meta: + ordering = ['asset_id'] + permissions = ( + ('asset_finance', 'Can see financial data for assets'), + ('view_asset', 'Can view an asset') + ) + + parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) + asset_id = models.CharField(max_length=10, unique=True) + description = models.CharField(max_length=120) + category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) + status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) + serial_number = models.CharField(max_length=150, blank=True) + purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) + date_acquired = models.DateField() + date_sold = models.DateField(blank=True, null=True) + purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) + salvage_value = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) + comments = models.TextField(blank=True) + next_sched_maint = models.DateField(blank=True, null=True) + + # Cable assets + is_cable = models.BooleanField(default=False) + plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', blank=True, null=True) + socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', blank=True, null=True) + length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True, help_text='m') + csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm^2') + circuits = models.IntegerField(blank=True, null=True) + cores = models.IntegerField(blank=True, null=True) + + def get_available_asset_id(): + sql = """ + SELECT MIN(CAST(a.asset_id AS int))+1 + FROM assets_asset a + LEFT OUTER JOIN assets_asset b ON + (CAST(a.asset_id AS int) + 1 = CAST(b.asset_id AS int)) + WHERE b.asset_id IS NULL AND CAST(a.asset_id AS int) >= %s; + """ + with connection.cursor() as cursor: + cursor.execute(sql, [9000]) + row = cursor.fetchone() + if row[0] is None: + return 9000 + else: + return row[0] + + def get_absolute_url(self): + return reverse('asset_detail', kwargs={'pk': self.asset_id}) + + def __str__(self): + out = str(self.asset_id) + ' - ' + self.description + if self.is_cable: + out += '{} - {}m - {}'.format(self.plug, self.length, self.socket) + return out + + def clean(self): + errdict = {} + if self.date_sold and self.date_acquired > self.date_sold: + errdict["date_sold"] = ["Cannot sell an item before it is acquired"] + + self.asset_id = self.asset_id.upper() + if re.search("^[a-zA-Z0-9]+$", self.asset_id) is None: + errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers"] + + if self.purchase_price and self.purchase_price < 0: + errdict["purchase_price"] = ["A price cannot be negative"] + + if self.salvage_value and self.salvage_value < 0: + errdict["salvage_value"] = ["A price cannot be negative"] + + if self.is_cable: + if not self.length or self.length <= 0: + errdict["length"] = ["The length of a cable must be more than 0"] + if not self.csa or self.csa <= 0: + errdict["csa"] = ["The CSA of a cable must be more than 0"] + if not self.circuits or self.circuits <= 0: + errdict["circuits"] = ["There must be at least one circuit in a cable"] + if not self.cores or self.cores <= 0: + errdict["cores"] = ["There must be at least one core in a cable"] + if self.socket is None: + errdict["socket"] = ["A cable must have a socket"] + if self.plug is None: + errdict["plug"] = ["A cable must have a plug"] + + if errdict != {}: # If there was an error when validation + raise ValidationError(errdict) diff --git a/assets/static/js/csrf.js b/assets/static/js/csrf.js new file mode 100644 index 00000000..895ce31e --- /dev/null +++ b/assets/static/js/csrf.js @@ -0,0 +1,23 @@ +$.ajaxSetup({ + beforeSend: function(xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { + // Only send the token to relative URLs i.e. locally. + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } + } +}); \ No newline at end of file diff --git a/assets/templates/asset_create.html b/assets/templates/asset_create.html new file mode 100644 index 00000000..b0bb8297 --- /dev/null +++ b/assets/templates/asset_create.html @@ -0,0 +1,62 @@ +{% extends 'base_assets.html' %} +{% load widget_tweaks %} +{% load asset_templatetags %} +{% block title %}Asset {{ object.asset_id }}{% endblock %} + + +{% block content %} + + +{% if duplicate %} +
    + {% else %} + + {% endif %} + {% include 'form_errors.html' %} + {% csrf_token %} + +
    +
    + {% include 'partials/asset_form.html' %} +
    +
    +
    +
    + {% include 'partials/purchasedetails_form.html' %} +
    + +
    + {% include 'partials/parent_form.html' %} +
    +
    +
    +
    + {% include 'partials/asset_buttons.html' %} +
    +
    +
    + + {% endblock %} + + {% block js%} + + {%endblock%} diff --git a/assets/templates/asset_list.html b/assets/templates/asset_list.html new file mode 100644 index 00000000..4c86d58d --- /dev/null +++ b/assets/templates/asset_list.html @@ -0,0 +1,65 @@ +{% extends 'base_assets.html' %} +{% block title %}Asset List{% endblock %} +{% load paginator from filters %} +{% load widget_tweaks %} + +{% block content %} + + + +
    +
    + {% render_field form.query|add_class:'form-control' placeholder='Search by Asset ID/Description' style="width: 250px"%} + + +
    +
    +
    +
    + + {% render_field form.category|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Categories" data-header="Categories" data-actions-box="true" %} +
    +
    + + {% render_field form.status|attr:'multiple'|add_class:'form-control selectpicker' data-none-selected-text="Statuses" data-header="Statuses" data-actions-box="true" %} +
    + + +
    +
    + + + + + + + + + + + + + {% include 'partials/asset_list_table_body.html' %} + +
    Asset IDDescriptionCategoryStatus
    + +{% if is_paginated %} +
    + {% paginator %} +
    +{% endif %} + +{% endblock %} + +{% load static %} +{% block css %} + + +{% endblock %} + +{% block preload_js %} + + +{% endblock %} diff --git a/assets/templates/asset_update.html b/assets/templates/asset_update.html new file mode 100644 index 00000000..5a7eec87 --- /dev/null +++ b/assets/templates/asset_update.html @@ -0,0 +1,63 @@ +{% extends 'base_assets.html' %} +{% load widget_tweaks %} +{% load asset_templatetags %} +{% block title %}Asset {{ object.asset_id }}{% endblock %} + + +{% block content %} + + +
    + {% include 'form_errors.html' %} + {% csrf_token %} + +
    +
    + {% include 'partials/asset_form.html' %} +
    +
    +
    + {% if perms.asset.asset_financial %} +
    + {% include 'partials/purchasedetails_form.html' %} +
    + {%endif%} + +
    + {% include 'partials/parent_form.html' %} +
    +
    +
    +
    + {% include 'partials/asset_buttons.html' %} +
    +
    +
    + +{% endblock %} + +{% block js%} +{% if edit %} + +{% endif %} +{% endblock %} diff --git a/assets/templates/asset_update_search_results.html b/assets/templates/asset_update_search_results.html new file mode 100644 index 00000000..edd156f1 --- /dev/null +++ b/assets/templates/asset_update_search_results.html @@ -0,0 +1,16 @@ +{% for asset in object_list %} + + {{ asset.asset_id }} - {{ asset.description }} + +
    + {% empty %} + No assets match given ID +{% endfor %} + + \ No newline at end of file diff --git a/assets/templates/partials/asset_buttons.html b/assets/templates/partials/asset_buttons.html new file mode 100644 index 00000000..39402cbe --- /dev/null +++ b/assets/templates/partials/asset_buttons.html @@ -0,0 +1,25 @@ +{% if edit and object %} + + + Duplicate +{% elif duplicate %} + + +{% elif create %} + + +{% else %} + + +{% endif %} +{% if create or edit or duplicate %} +
    + +{% endif %} diff --git a/assets/templates/partials/asset_form.html b/assets/templates/partials/asset_form.html new file mode 100644 index 00000000..08f82d18 --- /dev/null +++ b/assets/templates/partials/asset_form.html @@ -0,0 +1,61 @@ +{% load widget_tweaks %} +{% load asset_templatetags %} +
    +
    + Asset Details +
    +
    + {% if create or edit or duplicate %} +
    + + {% if duplicate %} + {% render_field form.asset_id|add_class:'form-control' value=object.asset_id %} + {% elif object.asset_id %} + {% render_field form.asset_id|attr:'readonly'|add_class:'disabled_input form-control' value=object.asset_id %} + {% else %} + {% render_field form.asset_id|add_class:'form-control' %} + {% endif %} +
    +
    + + {% render_field form.description|add_class:'form-control' value=object.description %} +
    +
    + + {% render_field form.category|add_class:'form-control'%} +
    + {% render_field form.is_cable|attr:'onchange=checkIfCableHidden()' %} +
    + + {% render_field form.status|add_class:'form-control'%} +
    +
    + + {% render_field form.serial_number|add_class:'form-control' value=object.serial_number %} +
    + +
    + + {% render_field form.comments|add_class:'form-control' %} +
    + {% else %} +
    Asset ID
    +
    {{ object.asset_id }}
    + +
    Description
    +
    {{ object.description }}
    + +
    Category
    +
    {{ object.category }}
    + +
    Status
    +
    {{ object.status }}
    + +
    Serial Number
    +
    {{ object.serial_number|default:'-' }}
    + +
    Comments
    +
    {{ object.comments|default:'-'|linebreaksbr }}
    + {% endif %} +
    +
    diff --git a/assets/templates/partials/asset_list_table_body.html b/assets/templates/partials/asset_list_table_body.html new file mode 100644 index 00000000..11b9650b --- /dev/null +++ b/assets/templates/partials/asset_list_table_body.html @@ -0,0 +1,33 @@ +{% for item in object_list %} + {#
  • {{ item.asset_id }} - {{ item.description }}
  • #} + + {{ item.asset_id }} + {{ item.description }} + {{ item.category }} + {{ item.status }} + + +
    + View + {% if perms.assets.change_asset %} + Edit + Duplicate + {% endif %} +
    + + +{% endfor %} diff --git a/assets/templates/partials/asset_picker.html b/assets/templates/partials/asset_picker.html new file mode 100644 index 00000000..fd63fe86 --- /dev/null +++ b/assets/templates/partials/asset_picker.html @@ -0,0 +1,65 @@ + + +{% load static %} +{% block css %} + + +{% endblock %} + +{% block preload_js %} + + +{% endblock %} + +{% block js %} +{{ js.super }} + +{% endblock js %} \ No newline at end of file diff --git a/assets/templates/partials/cable_form.html b/assets/templates/partials/cable_form.html new file mode 100644 index 00000000..9390a73c --- /dev/null +++ b/assets/templates/partials/cable_form.html @@ -0,0 +1,61 @@ +{% load widget_tweaks %} +{% load asset_templatetags %} +
    +
    + Cable Details +
    +
    + {% if create or edit or duplicate %} +
    + + {% render_field form.plug|add_class:'form-control'%} +
    +
    + + {% render_field form.socket|add_class:'form-control'%} +
    +
    + +
    + {% render_field form.length|add_class:'form-control' %} + {{ form.length.help_text }} +
    +
    +
    + +
    + {% render_field form.csa|add_class:'form-control' value=object.csa %} + {{ form.csa.help_text }} +
    +
    +
    + + {% render_field form.circuits|add_class:'form-control' value=object.circuits %} +
    +
    + + {% render_field form.cores|add_class:'form-control' value=object.cores %} +
    + {% else %} +
    +
    Socket
    +
    {{ object.socket|default_if_none:'-' }}
    + +
    Plug
    +
    {{ object.plug|default_if_none:'-' }}
    + +
    Length
    +
    {{ object.length|default_if_none:'-' }}m
    + +
    Cross Sectional Area
    +
    {{ object.csa|default_if_none:'-' }}m^2
    + +
    Circuits
    +
    {{ object.circuits|default_if_none:'-' }}
    + +
    Cores
    +
    {{ object.cores|default_if_none:'-' }}
    +
    + {% endif %} +
    +
    diff --git a/assets/templates/partials/parent_form.html b/assets/templates/partials/parent_form.html new file mode 100644 index 00000000..5f29240d --- /dev/null +++ b/assets/templates/partials/parent_form.html @@ -0,0 +1,41 @@ +{% load widget_tweaks %} +{% load asset_templatetags %} +
    +
    + Collection Details +
    +
    + {% if create or edit or duplicate %} +
    + + {% include 'partials/asset_picker.html' %} +
    + {% else %} +
    +
    Parent
    +
    + {% if object.parent %} + + {{ object.parent.asset_id }} - {{ object.parent.description }} + + {% else %} + - + {% endif %} +
    + +
    Children
    + {% if object.asset_parent.all %} + {% for child in object.asset_parent.all %} +
    + + {{ child.asset_id }} - {{ child.description }} + +
    + {% endfor %} + {% else %} +
    -
    + {% endif %} +
    + {% endif%} +
    +
    diff --git a/assets/templates/partials/purchasedetails_form.html b/assets/templates/partials/purchasedetails_form.html new file mode 100644 index 00000000..d5979439 --- /dev/null +++ b/assets/templates/partials/purchasedetails_form.html @@ -0,0 +1,68 @@ +{% load widget_tweaks %} +{% load asset_templatetags %} +
    +
    + Purchase Details +
    +
    + {% if create or edit or duplicate %} +
    + + {% include 'partials/supplier_picker.html' %} +
    + +
    + +
    + £ + {% render_field form.purchase_price|add_class:'form-control' value=object.purchase_price %} +
    +
    + +
    + +
    + £ + {% render_field form.salvage_value|add_class:'form-control' value=object.salvage_value %} +
    +
    + +
    + + {% if object.date_acquired%} + {% with date_acq=object.date_acquired|date:"Y-m-d" %} + {% render_field form.date_acquired|add_class:'form-control'|attr:'type="date"' value=date_acq %} + {% endwith %} + {% else %} + + {% endif %} +
    + +
    + + {% with date_sol=object.form.date_sold|date:"Y-m-d" %} + {% render_field form.date_sold|add_class:'form-control'|attr:'type="date"' value=date_sol %} + {% endwith %} +
    + {% else %} +
    +
    Purchased From
    +
    {{ object.purchased_from|default_if_none:'-' }}
    + +
    Purchase Price
    +
    £{{ object.purchase_price|default_if_none:'-' }}
    + +
    Salvage Value
    +
    £{{ object.salvage_value|default_if_none:'-' }}
    + +
    Date Acquired
    +
    {{ object.date_acquired|default_if_none:'-' }}
    + {% if object.date_sold %} +
    Date Sold
    +
    {{ object.date_sold|default_if_none:'-' }}
    + {% endif %} +
    + {% endif %} +
    +
    diff --git a/assets/templates/partials/render_field.html b/assets/templates/partials/render_field.html new file mode 100644 index 00000000..58744530 --- /dev/null +++ b/assets/templates/partials/render_field.html @@ -0,0 +1,16 @@ +{% load widget_tweaks %} + + +
    + + {% if css %} + {% render_field field|add_class:css %} + {% elif disable_if_filled and field.value %} + {% render_field field|attr:'disabled' %} + {% elif css and disable_if_filled %} + {% render_field field|add_class:css|attr:'disabled' %} + {% else %} + {{ field }} + {% endif %} + +
    diff --git a/assets/templates/partials/supplier_picker.html b/assets/templates/partials/supplier_picker.html new file mode 100644 index 00000000..842290b3 --- /dev/null +++ b/assets/templates/partials/supplier_picker.html @@ -0,0 +1,64 @@ + + +{% load static %} +{% block css %} + + +{% endblock %} + +{% block preload_js %} + + +{% endblock %} + +{% block js %} +{{ js.super }} + +{% endblock js %} diff --git a/assets/templates/supplier_detail.html b/assets/templates/supplier_detail.html new file mode 100644 index 00000000..7639cf6b --- /dev/null +++ b/assets/templates/supplier_detail.html @@ -0,0 +1,6 @@ +{% extends 'base_assets.html' %} +{% block title %}Detail{% endblock %} + +{% block content %} +{{ object }} +{% endblock %} \ No newline at end of file diff --git a/assets/templates/supplier_list.html b/assets/templates/supplier_list.html new file mode 100644 index 00000000..71ceb78c --- /dev/null +++ b/assets/templates/supplier_list.html @@ -0,0 +1,46 @@ +{% extends 'base_assets.html' %} +{% block title %}Supplier List{% endblock %} +{% load paginator from filters %} +{% load widget_tweaks %} + +{% block content %} + + + +
    + {% csrf_token %} +
    + {% render_field form.query|add_class:'form-control' placeholder='Search by Name' style="width: 250px"%} + + +
    +
    + + + + + + + + + + {% for item in object_list %} + + + + + {% endfor %} + +
    SupplierQuick Links
    {{ item.name }} + Edit +
    + +{% if is_paginated %} +
    + {% paginator %} +
    +{% endif %} + +{% endblock %} diff --git a/assets/templates/supplier_update.html b/assets/templates/supplier_update.html new file mode 100644 index 00000000..dfc8cc54 --- /dev/null +++ b/assets/templates/supplier_update.html @@ -0,0 +1,20 @@ +{% extends 'base_assets.html' %} +{% block title %}Edit{% endblock %} + +{% block content %} + + +
    + {% csrf_token %} + {{ form }} + + +
    +{% endblock %} diff --git a/assets/templatetags/__init__.py b/assets/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/assets/templatetags/asset_templatetags.py b/assets/templatetags/asset_templatetags.py new file mode 100644 index 00000000..905f9ce2 --- /dev/null +++ b/assets/templatetags/asset_templatetags.py @@ -0,0 +1,21 @@ +from django import template +from django.template.defaultfilters import stringfilter +from django.utils.safestring import SafeData, mark_safe +from django.utils.text import normalize_newlines +from django.utils.html import escape + +register = template.Library() + + +@register.filter(is_safe=True, needs_autoescape=True) +@stringfilter +def linebreaksn(value, autoescape=True): + """ + Convert all newlines in a piece of plain text to jQuery line breaks + (`\n`). + """ + autoescape = autoescape and not isinstance(value, SafeData) + value = normalize_newlines(value) + if autoescape: + value = escape(value) + return mark_safe(value.replace('\n', '\\n')) diff --git a/assets/urls.py b/assets/urls.py new file mode 100644 index 00000000..fa5facd0 --- /dev/null +++ b/assets/urls.py @@ -0,0 +1,22 @@ +from django.urls import path +from assets import views + +from PyRIGS.decorators import permission_required_with_403 + +urlpatterns = [ + path('', views.AssetList.as_view(), name='asset_index'), + path('asset/list/', views.AssetList.as_view(), name='asset_list'), + path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), + path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'), + path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'), + path('asset/id//duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), + + path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'), + + path('supplier/list', views.SupplierList.as_view(), name='supplier_list'), + path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'), + path('supplier/create', permission_required_with_403('assets.create_supplier')(views.SupplierCreate.as_view()), name='supplier_create'), + path('supplier//edit', permission_required_with_403('assets.edit_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'), + + path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'), +] diff --git a/assets/views.py b/assets/views.py new file mode 100644 index 00000000..06449cdb --- /dev/null +++ b/assets/views.py @@ -0,0 +1,205 @@ +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import JsonResponse +from django.views import generic +from django.views.decorators.csrf import csrf_exempt +from django.utils.decorators import method_decorator +from django.urls import reverse +from django.db.models import Q +from assets import models, forms + + +@method_decorator(csrf_exempt, name='dispatch') +class AssetList(LoginRequiredMixin, generic.ListView): + model = models.Asset + template_name = 'asset_list.html' + paginate_by = 40 + ordering = ['-pk'] + hide_hidden_status = True + + def get_initial(self): + initial = {'status': models.AssetStatus.objects.filter(should_show=True)} + return initial + + def get_queryset(self): + if self.request.method == 'POST': + self.form = forms.AssetSearchForm(data=self.request.POST) + elif self.request.method == 'GET' and len(self.request.GET) > 0: + self.form = forms.AssetSearchForm(data=self.request.GET) + else: + self.form = forms.AssetSearchForm(data=self.get_initial()) + form = self.form + if not form.is_valid(): + return self.model.objects.none() + + # TODO Feedback to user when search fails + query_string = form.cleaned_data['query'] or "" + if len(query_string) == 0: + queryset = self.model.objects.all() + elif len(query_string) >= 3: + queryset = self.model.objects.filter(Q(asset_id__exact=query_string) | Q(description__icontains=query_string)) + else: + queryset = self.model.objects.filter(Q(asset_id__exact=query_string)) + + if form.cleaned_data['category']: + queryset = queryset.filter(category__in=form.cleaned_data['category']) + + if len(form.cleaned_data['status']) > 0: + queryset = queryset.filter(status__in=form.cleaned_data['status']) + elif self.hide_hidden_status: + queryset = queryset.filter(status__in=models.AssetStatus.objects.filter(should_show=True)) + + return queryset + + def get_context_data(self, **kwargs): + context = super(AssetList, self).get_context_data(**kwargs) + context["form"] = self.form + + context["categories"] = models.AssetCategory.objects.all() + + context["statuses"] = models.AssetStatus.objects.all() + return context + + +class AssetSearch(AssetList): + hide_hidden_status = False + + def render_to_response(self, context, **response_kwargs): + result = [] + + for asset in context["object_list"]: + result.append({"id": asset.pk, "label": (asset.asset_id + " | " + asset.description)}) + + return JsonResponse(result, safe=False) + + +class AssetIDUrlMixin: + def get_object(self, queryset=None): + pk = self.kwargs.get(self.pk_url_kwarg) + queryset = models.Asset.objects.filter(asset_id=pk) + try: + # Get the single item from the filtered queryset + obj = queryset.get() + except queryset.model.DoesNotExist: + raise Http404(_("No %(verbose_name)s found matching the query") % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj + + +class AssetDetail(LoginRequiredMixin, AssetIDUrlMixin, generic.DetailView): + model = models.Asset + template_name = 'asset_update.html' + + +class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView): + template_name = 'asset_update.html' + model = models.Asset + form_class = forms.AssetForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['edit'] = True + context["connectors"] = models.Connector.objects.all() + + return context + + def get_success_url(self): + return reverse("asset_detail", kwargs={"pk": self.object.asset_id}) + + +class AssetCreate(LoginRequiredMixin, generic.CreateView): + template_name = 'asset_create.html' + model = models.Asset + form_class = forms.AssetForm + + def get_context_data(self, **kwargs): + context = super(AssetCreate, self).get_context_data(**kwargs) + context["create"] = True + context["connectors"] = models.Connector.objects.all() + + return context + + def get_initial(self, *args, **kwargs): + initial = super().get_initial(*args, **kwargs) + initial["asset_id"] = models.Asset.get_available_asset_id() + return initial + + def get_success_url(self): + return reverse("asset_detail", kwargs={"pk": self.object.asset_id}) + + +class DuplicateMixin: + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.object.pk = None + return self.render_to_response(self.get_context_data()) + + +class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate): + model = models.Asset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["create"] = None + context["duplicate"] = True + context['previous_asset_id'] = self.get_object().asset_id + return context + + +class SupplierList(generic.ListView): + model = models.Supplier + template_name = 'supplier_list.html' + paginate_by = 40 + ordering = ['name'] + + def get_queryset(self): + if self.request.method == 'POST': + self.form = forms.SupplierSearchForm(data=self.request.POST) + elif self.request.method == 'GET': + self.form = forms.SupplierSearchForm(data=self.request.GET) + else: + self.form = forms.SupplierSearchForm(data={}) + form = self.form + if not form.is_valid(): + return self.model.objects.none() + + query_string = form.cleaned_data['query'] or "" + if len(query_string) == 0: + queryset = self.model.objects.all() + else: + queryset = self.model.objects.filter(Q(name__icontains=query_string)) + + return queryset + + def get_context_data(self, **kwargs): + context = super(SupplierList, self).get_context_data(**kwargs) + context["form"] = self.form + return context + + +class SupplierSearch(SupplierList): + hide_hidden_status = False + + def render_to_response(self, context, **response_kwargs): + result = [] + + for supplier in context["object_list"]: + result.append({"id": supplier.pk, "name": supplier.name}) + + return JsonResponse(result, safe=False) + + +class SupplierDetail(generic.DetailView): + model = models.Supplier + template_name = 'supplier_detail.html' + + +class SupplierCreate(generic.CreateView): + model = models.Supplier + form_class = forms.SupplierForm + template_name = 'supplier_update.html' + + +class SupplierUpdate(generic.UpdateView): + model = models.Supplier + form_class = forms.SupplierForm + template_name = 'supplier_update.html' diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..95b8798b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE = myproject.settings \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ebe42496..96e43b97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,8 @@ diff-match-patch==20121119 dj-database-url==0.5.0 dj-static==0.0.6 Django==2.0.13 +django-filter==2.0.0 +django-widget-tweaks==1.4.3 django-debug-toolbar==1.9.1 django-ical==1.4 django-recaptcha==1.4.0 @@ -11,7 +13,6 @@ django-registration-redux==2.4 django-reversion==2.0.13 django-toolbelt==0.0.1 premailer==3.2.0 -#django-widget-tweaks==1.4.1 git+git://github.com/jazzband/django-widget-tweaks.git@1.4.2 gunicorn==19.8.1 icalendar==4.0.1 @@ -32,7 +33,8 @@ sqlparse==0.2.4 static3==0.7.0 svg2rlg==0.3 yolk==0.4.3 +whitenoise==4.1.2 z3c.rml==3.5.0 zope.event==4.3.0 zope.interface==4.5.0 -zope.schema==4.5.0 \ No newline at end of file +zope.schema==4.5.0 diff --git a/templates/400.html b/templates/400.html index 5dd0c30b..dda8d561 100644 --- a/templates/400.html +++ b/templates/400.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load staticfiles %} {% block title %}Bad Request{% endblock %} diff --git a/templates/401.html b/templates/401.html index 9b9b019f..7fc7dfe0 100644 --- a/templates/401.html +++ b/templates/401.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load staticfiles %} {% block title %}Unauthorized{% endblock %} diff --git a/templates/403.html b/templates/403.html index 5f59c351..7fdb8f24 100644 --- a/templates/403.html +++ b/templates/403.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load staticfiles %} {% block title %}Forbidden{% endblock %} diff --git a/templates/404.html b/templates/404.html index 5b347c1b..5a4ef0bf 100644 --- a/templates/404.html +++ b/templates/404.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load staticfiles %} {% block title %}Page Not Found{% endblock %} diff --git a/templates/500.html b/templates/500.html index 2fc2db83..26fd115d 100644 --- a/templates/500.html +++ b/templates/500.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'base_rigs.html' %} {% load staticfiles %} {% block title %}Server error{% endblock %} diff --git a/templates/base.html b/templates/base.html index 24cc30bc..53238def 100644 --- a/templates/base.html +++ b/templates/base.html @@ -43,58 +43,13 @@ - RIGS + {% block titleheader %} + {% endblock %}
    - +

    Quick Links

    @@ -20,7 +20,7 @@ Rigboard Calendar {% if perms.RIGS.add_event %} New Event{% endif %} - Asset Database + {% if perms.assets.view_asset %} Asset Database{% endif %}
    @@ -29,7 +29,7 @@ Pre-Event Risk Assessment Price List Subhire Insurance Form - +
    @@ -73,8 +73,8 @@
    {% if perms.RIGS.view_event %}
    - {% include 'RIGS/activity_feed.html' %} - + {% include 'RIGS/activity_feed.html' %} +
    {% endif %}
    diff --git a/assets/urls.py b/assets/urls.py index fa5facd0..1c03a1d9 100644 --- a/assets/urls.py +++ b/assets/urls.py @@ -4,9 +4,9 @@ from assets import views from PyRIGS.decorators import permission_required_with_403 urlpatterns = [ - path('', views.AssetList.as_view(), name='asset_index'), - path('asset/list/', views.AssetList.as_view(), name='asset_list'), - path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), + path('', permission_required_with_403('assets.view_asset')(views.AssetList.as_view()), name='asset_index'), + path('asset/list/', permission_required_with_403('assets.view_asset')(views.AssetList.as_view()), name='asset_list'), + path('asset/id//', permission_required_with_403('assets.view_asset')(views.AssetDetail.as_view()), name='asset_detail'), path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'), path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'), path('asset/id//duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), From c059227d5d3696cb58a212d054b4ce7fd3313915 Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Thu, 5 Dec 2019 12:42:05 +0000 Subject: [PATCH 246/275] Revert "CHANGE: Restrict viewing asset DB to keyholders." This reverts commit 2c334196d5e300019668553c09d4eae7bfd8c975. --- RIGS/templates/RIGS/index.html | 10 +++++----- assets/urls.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index bc1cc659..826839e6 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -11,7 +11,7 @@
    - +

    Quick Links

    @@ -20,7 +20,7 @@ Rigboard Calendar {% if perms.RIGS.add_event %} New Event{% endif %} - {% if perms.assets.view_asset %} Asset Database{% endif %} + Asset Database
    @@ -29,7 +29,7 @@ Pre-Event Risk Assessment Price List Subhire Insurance Form - +
    @@ -73,8 +73,8 @@
    {% if perms.RIGS.view_event %}
    - {% include 'RIGS/activity_feed.html' %} - + {% include 'RIGS/activity_feed.html' %} +
    {% endif %}
    diff --git a/assets/urls.py b/assets/urls.py index 1c03a1d9..fa5facd0 100644 --- a/assets/urls.py +++ b/assets/urls.py @@ -4,9 +4,9 @@ from assets import views from PyRIGS.decorators import permission_required_with_403 urlpatterns = [ - path('', permission_required_with_403('assets.view_asset')(views.AssetList.as_view()), name='asset_index'), - path('asset/list/', permission_required_with_403('assets.view_asset')(views.AssetList.as_view()), name='asset_list'), - path('asset/id//', permission_required_with_403('assets.view_asset')(views.AssetDetail.as_view()), name='asset_detail'), + path('', views.AssetList.as_view(), name='asset_index'), + path('asset/list/', views.AssetList.as_view(), name='asset_list'), + path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'), path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'), path('asset/id//duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), From 9d51a82f319bffdd1dc9b5a45501330764dacd1f Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Thu, 5 Dec 2019 12:56:22 +0000 Subject: [PATCH 247/275] FIX: Fix asset sample data generation --- assets/management/commands/generateSampleAssetsData.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py index 15961ab9..6a656aef 100644 --- a/assets/management/commands/generateSampleAssetsData.py +++ b/assets/management/commands/generateSampleAssetsData.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand, CommandError from django.utils import timezone from assets import models - +import random class Command(BaseCommand): help = 'Creates some sample data for testing' From 0d8fd99d92d9c9ea0cc0398e3cf56e989e1780dc Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Thu, 5 Dec 2019 13:00:47 +0000 Subject: [PATCH 248/275] FIX: Permission errors This fixes keyholders being unable to see financials information or create assets. (Well, the latter needs anyone to be able to create assets before it is fully fixed) --- assets/templates/asset_update.html | 4 ++-- assets/templates/partials/asset_list_table_body.html | 1 - assets/urls.py | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/templates/asset_update.html b/assets/templates/asset_update.html index 5a7eec87..faee7662 100644 --- a/assets/templates/asset_update.html +++ b/assets/templates/asset_update.html @@ -25,12 +25,12 @@
    - {% if perms.asset.asset_financial %} + {% if perms.assets.asset_finance %}
    {% include 'partials/purchasedetails_form.html' %}
    {%endif%} - diff --git a/assets/templates/partials/asset_list_table_body.html b/assets/templates/partials/asset_list_table_body.html index 11b9650b..ab8bcd8d 100644 --- a/assets/templates/partials/asset_list_table_body.html +++ b/assets/templates/partials/asset_list_table_body.html @@ -20,7 +20,6 @@ {{ item.category }} {{ item.status }} -
    View {% if perms.assets.change_asset %} diff --git a/assets/urls.py b/assets/urls.py index fa5facd0..0a8ff599 100644 --- a/assets/urls.py +++ b/assets/urls.py @@ -7,16 +7,16 @@ urlpatterns = [ path('', views.AssetList.as_view(), name='asset_index'), path('asset/list/', views.AssetList.as_view(), name='asset_list'), path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), - path('asset/create/', permission_required_with_403('assets.create_asset')(views.AssetCreate.as_view()), name='asset_create'), + path('asset/create/', permission_required_with_403('assets.add_asset')(views.AssetCreate.as_view()), name='asset_create'), path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'), - path('asset/id//duplicate/', permission_required_with_403('assets.create_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), + path('asset/id//duplicate/', permission_required_with_403('assets.add_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'), path('supplier/list', views.SupplierList.as_view(), name='supplier_list'), path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'), - path('supplier/create', permission_required_with_403('assets.create_supplier')(views.SupplierCreate.as_view()), name='supplier_create'), - path('supplier//edit', permission_required_with_403('assets.edit_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'), + path('supplier/create', permission_required_with_403('assets.add_supplier')(views.SupplierCreate.as_view()), name='supplier_create'), + path('supplier//edit', permission_required_with_403('assets.change_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'), path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'), ] From 62541194eee3f7cbd65c1cacfa86d561e09e9718 Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Thu, 5 Dec 2019 13:10:08 +0000 Subject: [PATCH 249/275] CHORE: Fix pep8 mutter mutter mutter, grumble --- assets/management/commands/generateSampleAssetsData.py | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py index 6a656aef..c41de7fa 100644 --- a/assets/management/commands/generateSampleAssetsData.py +++ b/assets/management/commands/generateSampleAssetsData.py @@ -4,6 +4,7 @@ from django.utils import timezone from assets import models import random + class Command(BaseCommand): help = 'Creates some sample data for testing' From 228d72b7b2e0616f2cef42e71538d0754478d37d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 5 Dec 2019 17:26:02 +0000 Subject: [PATCH 250/275] Automatically run migrations on deploy Because running them via Heroku CLI is easy to forget --- Procfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Procfile b/Procfile index e6088524..91e43543 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ +release: python manage.py migrate web: gunicorn PyRIGS.wsgi --log-file - From b77615b9b940bee255d6acdd309efec7ba20c7e5 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Fri, 6 Dec 2019 00:28:54 +0000 Subject: [PATCH 251/275] Fix handling of prefixed Asset IDs and sorting of the asset list (#382) * FIX: Remove misleading admin site title * Moved across assets_id sorting to use proper numeric values. Also mofifies SQL command to find free asset IDs so that it works on postgres. * Changed generateSampleAssetsData.py to include prefices on some cables. * Fixed pep8 * Fixed missed migration * Ensured hidden asset fields are completed on every database write * CMULTI is a thing, and therefore a max prefix length of 5 cannot be a thing --- assets/admin.py | 5 -- assets/forms.py | 1 + .../commands/generateSampleAssetsData.py | 15 +++-- ...5_1937_squashed_0009_auto_20191205_2041.py | 64 +++++++++++++++++++ assets/models.py | 41 +++++++++--- 5 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py diff --git a/assets/admin.py b/assets/admin.py index 7044040f..3e6c9d58 100644 --- a/assets/admin.py +++ b/assets/admin.py @@ -30,8 +30,3 @@ class AssetAdmin(admin.ModelAdmin): @admin.register(assets.Connector) class ConnectorAdmin(admin.ModelAdmin): list_display = ['id', '__str__', 'current_rating', 'voltage_rating', 'num_pins'] - - -admin.AdminSite.site_header = 'PyAssets - TEC\'s Asset System' -admin.AdminSite.site_title = 'PyAssets Admin' -admin.AdminSite.index_title = 'System Administration' diff --git a/assets/forms.py b/assets/forms.py index 430eef20..7571d28b 100644 --- a/assets/forms.py +++ b/assets/forms.py @@ -7,6 +7,7 @@ class AssetForm(forms.ModelForm): class Meta: model = models.Asset fields = '__all__' + exclude = ['asset_id_prefix', 'asset_id_number'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py index c41de7fa..258551c2 100644 --- a/assets/management/commands/generateSampleAssetsData.py +++ b/assets/management/commands/generateSampleAssetsData.py @@ -1,8 +1,7 @@ +import random from django.core.management.base import BaseCommand, CommandError from django.utils import timezone - from assets import models -import random class Command(BaseCommand): @@ -50,7 +49,7 @@ class Command(BaseCommand): suppliers = models.Supplier.objects.all() for i in range(100): - asset = models.Asset.objects.create( + asset = models.Asset( asset_id='{}'.format(models.Asset.get_available_asset_id()), description=random.choice(asset_description), category=random.choice(categories), @@ -63,11 +62,12 @@ class Command(BaseCommand): if i % 3 == 0: asset.purchased_from = random.choice(suppliers) - + asset.clean() asset.save() def create_cables(self): asset_description = ['The worm', 'Harting without a cap', 'Heavy cable', 'Extension lead', 'IEC cable that we should remember to prep'] + asset_prefixes = ["C", "C4P", "CBNC", "CDMX", "CDV", "CRCD", "CSOCA", "CXLR"] csas = [0.75, 1.00, 1.25, 2.5, 4] lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100] @@ -79,7 +79,7 @@ class Command(BaseCommand): connectors = models.Connector.objects.all() for i in range(100): - asset = models.Asset.objects.create( + asset = models.Asset( asset_id='{}'.format(models.Asset.get_available_asset_id()), description=random.choice(asset_description), category=random.choice(categories), @@ -95,12 +95,17 @@ class Command(BaseCommand): cores=random.choice(circuits) ) + if i % 5 == 0: + prefix = random.choice(asset_prefixes) + asset.asset_id = prefix + str(models.Asset.get_available_asset_id(wanted_prefix=prefix)) + if i % 4 == 0: asset.parent = models.Asset.objects.order_by('?').first() if i % 3 == 0: asset.purchased_from = random.choice(suppliers) + asset.clean() asset.save() def create_connectors(self): diff --git a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py new file mode 100644 index 00000000..15898dc8 --- /dev/null +++ b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py @@ -0,0 +1,64 @@ +# Generated by Django 2.0.13 on 2019-12-05 20:42 + +from django.db import migrations, models +import django.db.migrations.operations.special + +def forwards(apps, schema_editor): + AssetModel = apps.get_model('assets', 'Asset') + + for row in AssetModel.objects.all(): + + row.asset_id = row.asset_id.upper() + asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) + if asset_search is None: # If the asset_id doesn't have a number at the end + row.asset_id += "1" + + asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) + row.asset_id_prefix = asset_search.group(1) + row.asset_id_number = int(asset_search.group(2)) + + row.save(update_fields=['asset_id', 'asset_id_prefix', 'asset_id_number']) + +# Functions from the following migrations need manual copying. +# Move them and any dependencies into this file, then update the +# RunPython operations to refer to the local versions: +# assets.migrations.0008_auto_20191205_1937 + +class Migration(migrations.Migration): + + replaces = [('assets', '0008_auto_20191205_1937'), ('assets', '0009_auto_20191205_2041')] + + dependencies = [ + ('assets', '0007_auto_20190108_0202_squashed_0014_auto_20191017_2052'), + ] + + operations = [ + migrations.AddField( + model_name='asset', + name='asset_id_number', + field=models.IntegerField(default=1), + ), + migrations.AddField( + model_name='asset', + name='asset_id_prefix', + field=models.CharField(default='', max_length=5), + ), + migrations.RunPython( + code=forwards, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['asset_id_prefix', 'asset_id_number'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))}, + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(max_length=15, unique=True), + ), + migrations.AlterField( + model_name='asset', + name='asset_id_prefix', + field=models.CharField(default='', max_length=8), + ), + ] diff --git a/assets/models.py b/assets/models.py index 7abddfd2..993c8c34 100644 --- a/assets/models.py +++ b/assets/models.py @@ -3,6 +3,9 @@ from django.core.exceptions import ValidationError from django.db import models, connection from django.urls import reverse +from django.db.models.signals import pre_save +from django.dispatch.dispatcher import receiver + class AssetCategory(models.Model): class Meta: @@ -54,14 +57,14 @@ class Connector(models.Model): class Asset(models.Model): class Meta: - ordering = ['asset_id'] + ordering = ['asset_id_prefix', 'asset_id_number'] permissions = ( ('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset') ) parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) - asset_id = models.CharField(max_length=10, unique=True) + asset_id = models.CharField(max_length=15, unique=True) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) @@ -83,18 +86,24 @@ class Asset(models.Model): circuits = models.IntegerField(blank=True, null=True) cores = models.IntegerField(blank=True, null=True) - def get_available_asset_id(): + # Hidden asset_id components + # For example, if asset_id was "C1001" then asset_id_prefix would be "C" and number "1001" + asset_id_prefix = models.CharField(max_length=8, default="") + asset_id_number = models.IntegerField(default=1) + + def get_available_asset_id(wanted_prefix=""): sql = """ - SELECT MIN(CAST(a.asset_id AS int))+1 + SELECT a.asset_id_number+1 FROM assets_asset a LEFT OUTER JOIN assets_asset b ON - (CAST(a.asset_id AS int) + 1 = CAST(b.asset_id AS int)) - WHERE b.asset_id IS NULL AND CAST(a.asset_id AS int) >= %s; + (a.asset_id_number + 1 = b.asset_id_number AND + a.asset_id_prefix = b.asset_id_prefix) + WHERE b.asset_id IS NULL AND a.asset_id_number >= %s AND a.asset_id_prefix = %s; """ with connection.cursor() as cursor: - cursor.execute(sql, [9000]) + cursor.execute(sql, [9000, wanted_prefix]) row = cursor.fetchone() - if row[0] is None: + if row is None or row[0] is None: return 9000 else: return row[0] @@ -114,8 +123,9 @@ class Asset(models.Model): errdict["date_sold"] = ["Cannot sell an item before it is acquired"] self.asset_id = self.asset_id.upper() - if re.search("^[a-zA-Z0-9]+$", self.asset_id) is None: - errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers"] + asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", self.asset_id) + if asset_search is None: + errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers, with a final number"] if self.purchase_price and self.purchase_price < 0: errdict["purchase_price"] = ["A price cannot be negative"] @@ -139,3 +149,14 @@ class Asset(models.Model): if errdict != {}: # If there was an error when validation raise ValidationError(errdict) + + +@receiver(pre_save, sender=Asset) +def pre_save_asset(sender, instance, **kwargs): + """Automatically fills in hidden members on database access""" + asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id) + if asset_search is None: + instance.asset_id += "1" + asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", instance.asset_id) + instance.asset_id_prefix = asset_search.group(1) + instance.asset_id_number = int(asset_search.group(2)) From 602ccc15ea62af3444be82a87eaf2a8fb1603999 Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Fri, 6 Dec 2019 00:40:56 +0000 Subject: [PATCH 252/275] FIX: Fix missing import Presumably caused by Matt's IDE being overzealous again. I know I shouldn't be pushing to master but...one line fix... --- ...08_auto_20191205_1937_squashed_0009_auto_20191205_2041.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py index 15898dc8..dd2d89e2 100644 --- a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py +++ b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py @@ -1,18 +1,19 @@ # Generated by Django 2.0.13 on 2019-12-05 20:42 +import re from django.db import migrations, models import django.db.migrations.operations.special def forwards(apps, schema_editor): AssetModel = apps.get_model('assets', 'Asset') - + for row in AssetModel.objects.all(): row.asset_id = row.asset_id.upper() asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) if asset_search is None: # If the asset_id doesn't have a number at the end row.asset_id += "1" - + asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) row.asset_id_prefix = asset_search.group(1) row.asset_id_number = int(asset_search.group(2)) From ddc23ce4e5973a00ed8d17a6f3b89559db8fab22 Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Fri, 6 Dec 2019 00:58:35 +0000 Subject: [PATCH 253/275] FIX: Prefix field still too limited for legacy data Fingers crossed this works I don't have the actual data locally... I know I'm making a mess but needs must. I genuinely hate whoever decided prefixes were a good idea now. --- ...uto_20191205_1937_squashed_0009_auto_20191205_2041.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py index dd2d89e2..edddb178 100644 --- a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py +++ b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py @@ -42,7 +42,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='asset_id_prefix', - field=models.CharField(default='', max_length=5), + field=models.CharField(default='', max_length=8), ), migrations.RunPython( code=forwards, @@ -56,10 +56,5 @@ class Migration(migrations.Migration): model_name='asset', name='asset_id', field=models.CharField(max_length=15, unique=True), - ), - migrations.AlterField( - model_name='asset', - name='asset_id_prefix', - field=models.CharField(default='', max_length=8), - ), + ) ] From 7c876348d7b9500922091d857f11e1494c9a7f1e Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Tue, 10 Dec 2019 22:50:28 +0000 Subject: [PATCH 254/275] Asset fixes (#383) --- ...5_1937_squashed_0009_auto_20191205_2041.py | 60 ------------------- assets/migrations/0008_auto_20191206_2124.py | 51 ++++++++++++++++ 2 files changed, 51 insertions(+), 60 deletions(-) delete mode 100644 assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py create mode 100644 assets/migrations/0008_auto_20191206_2124.py diff --git a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py b/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py deleted file mode 100644 index edddb178..00000000 --- a/assets/migrations/0008_auto_20191205_1937_squashed_0009_auto_20191205_2041.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 2.0.13 on 2019-12-05 20:42 - -import re -from django.db import migrations, models -import django.db.migrations.operations.special - -def forwards(apps, schema_editor): - AssetModel = apps.get_model('assets', 'Asset') - - for row in AssetModel.objects.all(): - - row.asset_id = row.asset_id.upper() - asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) - if asset_search is None: # If the asset_id doesn't have a number at the end - row.asset_id += "1" - - asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) - row.asset_id_prefix = asset_search.group(1) - row.asset_id_number = int(asset_search.group(2)) - - row.save(update_fields=['asset_id', 'asset_id_prefix', 'asset_id_number']) - -# Functions from the following migrations need manual copying. -# Move them and any dependencies into this file, then update the -# RunPython operations to refer to the local versions: -# assets.migrations.0008_auto_20191205_1937 - -class Migration(migrations.Migration): - - replaces = [('assets', '0008_auto_20191205_1937'), ('assets', '0009_auto_20191205_2041')] - - dependencies = [ - ('assets', '0007_auto_20190108_0202_squashed_0014_auto_20191017_2052'), - ] - - operations = [ - migrations.AddField( - model_name='asset', - name='asset_id_number', - field=models.IntegerField(default=1), - ), - migrations.AddField( - model_name='asset', - name='asset_id_prefix', - field=models.CharField(default='', max_length=8), - ), - migrations.RunPython( - code=forwards, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.AlterModelOptions( - name='asset', - options={'ordering': ['asset_id_prefix', 'asset_id_number'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))}, - ), - migrations.AlterField( - model_name='asset', - name='asset_id', - field=models.CharField(max_length=15, unique=True), - ) - ] diff --git a/assets/migrations/0008_auto_20191206_2124.py b/assets/migrations/0008_auto_20191206_2124.py new file mode 100644 index 00000000..f9002182 --- /dev/null +++ b/assets/migrations/0008_auto_20191206_2124.py @@ -0,0 +1,51 @@ +# Generated by Django 2.0.13 on 2019-12-06 21:24 + +from django.db import migrations, models, transaction +import re + +def forwards(apps, schema_editor): + AssetModel = apps.get_model('assets', 'Asset') + with transaction.atomic(): + for row in AssetModel.objects.all(): + + row.asset_id = row.asset_id.upper() + asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) + if asset_search is None: # If the asset_id doesn't have a number at the end + row.asset_id += "1" + + asset_search = re.search("^([A-Z0-9]*?[A-Z]?)([0-9]+)$", row.asset_id) + row.asset_id_prefix = asset_search.group(1) + row.asset_id_number = int(asset_search.group(2)) + + row.save(update_fields=['asset_id', 'asset_id_prefix', 'asset_id_number']) +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0007_auto_20190108_0202_squashed_0014_auto_20191017_2052'), + ] + + operations = [ + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['asset_id_prefix', 'asset_id_number'], 'permissions': (('asset_finance', 'Can see financial data for assets'), ('view_asset', 'Can view an asset'))}, + ), + migrations.AddField( + model_name='asset', + name='asset_id_number', + field=models.IntegerField(default=1), + ), + migrations.AddField( + model_name='asset', + name='asset_id_prefix', + field=models.CharField(default='', max_length=8), + ), + migrations.AlterField( + model_name='asset', + name='asset_id', + field=models.CharField(max_length=15, unique=True), + ), + migrations.RunPython( + code=forwards, + reverse_code=migrations.operations.special.RunPython.noop, + ), + ] From 01a87e0e0be8cf0a139d7f2d6f321f4e10f5851f Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Tue, 31 Dec 2019 12:25:42 +0000 Subject: [PATCH 255/275] FEAT: Add revision history to assets and suppliers (#387) * FEAT: Initial work on revision history for assets The revision history for individual items mostly works, though it shows database ID where it should show asset ID. Recent changes feed isn't yet done. * FEAT: Initial implementation of asset activity stream * CHORE: Fix pep8 * FIX: Asset history table 'branding' * FIX: Individual asset version history is now correctly filtered * FEAT: Make revision history for suppliers accessible * CHORE: *sings* And a pep8 in a broken tree... * Refactored out duplicated code from `AssetVersionHistory * CHORE: pep8 And another random bit of wierd whitespace I found Co-authored-by: Matthew Smith Closes #358 --- RIGS/templates/RIGS/event_detail.html | 2 +- RIGS/templates/RIGS/index.html | 3 +- RIGS/versioning.py | 11 +-- assets/models.py | 32 +++++-- assets/templates/asset_activity_table.html | 92 +++++++++++++++++++++ assets/templates/asset_update.html | 10 +++ assets/templates/asset_version_history.html | 68 +++++++++++++++ assets/templates/supplier_list.html | 1 + assets/urls.py | 25 ++++-- assets/views.py | 29 ++++++- templates/base_assets.html | 7 +- 11 files changed, 251 insertions(+), 29 deletions(-) create mode 100644 assets/templates/asset_activity_table.html create mode 100644 assets/templates/asset_version_history.html diff --git a/RIGS/templates/RIGS/event_detail.html b/RIGS/templates/RIGS/event_detail.html index 176756dc..8668bbac 100644 --- a/RIGS/templates/RIGS/event_detail.html +++ b/RIGS/templates/RIGS/event_detail.html @@ -245,7 +245,7 @@
    diff --git a/RIGS/templates/RIGS/index.html b/RIGS/templates/RIGS/index.html index 826839e6..acd1a707 100644 --- a/RIGS/templates/RIGS/index.html +++ b/RIGS/templates/RIGS/index.html @@ -72,9 +72,8 @@
    {% if perms.RIGS.view_event %} -
    +
    {% include 'RIGS/activity_feed.html' %} -
    {% endif %}
    diff --git a/RIGS/versioning.py b/RIGS/versioning.py index d7b423c2..795ea9fe 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -206,17 +206,14 @@ class VersionHistory(generic.ListView): paginate_by = 25 def get_queryset(self, **kwargs): - thisModel = self.kwargs['model'] + return RIGSVersion.objects.get_for_object(self.get_object()).select_related("revision", "revision__user").all() - versions = RIGSVersion.objects.get_for_object_reference(thisModel, self.kwargs['pk']).select_related("revision", "revision__user").all() - - return versions + def get_object(self, **kwargs): + return get_object_or_404(self.kwargs['model'], pk=self.kwargs['pk']) def get_context_data(self, **kwargs): - thisModel = self.kwargs['model'] context = super(VersionHistory, self).get_context_data(**kwargs) - thisObject = get_object_or_404(thisModel, pk=self.kwargs['pk']) - context['object'] = thisObject + context['object'] = self.get_object() return context diff --git a/assets/models.py b/assets/models.py index 993c8c34..2398a563 100644 --- a/assets/models.py +++ b/assets/models.py @@ -6,6 +6,11 @@ from django.urls import reverse from django.db.models.signals import pre_save from django.dispatch.dispatcher import receiver +from reversion import revisions as reversion +from reversion.models import Version + +from RIGS.models import RevisionMixin + class AssetCategory(models.Model): class Meta: @@ -24,13 +29,15 @@ class AssetStatus(models.Model): verbose_name_plural = 'Asset Statuses' name = models.CharField(max_length=80) - should_show = models.BooleanField(default=True, help_text="Should this be shown by default in the asset list.") + should_show = models.BooleanField( + default=True, help_text="Should this be shown by default in the asset list.") def __str__(self): return self.name -class Supplier(models.Model): +@reversion.register +class Supplier(models.Model, RevisionMixin): name = models.CharField(max_length=80) class Meta: @@ -55,7 +62,8 @@ class Connector(models.Model): return self.description -class Asset(models.Model): +@reversion.register +class Asset(models.Model, RevisionMixin): class Meta: ordering = ['asset_id_prefix', 'asset_id_number'] permissions = ( @@ -63,7 +71,8 @@ class Asset(models.Model): ('view_asset', 'Can view an asset') ) - parent = models.ForeignKey(to='self', related_name='asset_parent', blank=True, null=True, on_delete=models.SET_NULL) + parent = models.ForeignKey(to='self', related_name='asset_parent', + blank=True, null=True, on_delete=models.SET_NULL) asset_id = models.CharField(max_length=15, unique=True) description = models.CharField(max_length=120) category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) @@ -79,10 +88,14 @@ class Asset(models.Model): # Cable assets is_cable = models.BooleanField(default=False) - plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='plug', blank=True, null=True) - socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, related_name='socket', blank=True, null=True) - length = models.DecimalField(decimal_places=1, max_digits=10, blank=True, null=True, help_text='m') - csa = models.DecimalField(decimal_places=2, max_digits=10, blank=True, null=True, help_text='mm^2') + plug = models.ForeignKey(Connector, on_delete=models.SET_NULL, + related_name='plug', blank=True, null=True) + socket = models.ForeignKey(Connector, on_delete=models.SET_NULL, + related_name='socket', blank=True, null=True) + length = models.DecimalField(decimal_places=1, max_digits=10, + blank=True, null=True, help_text='m') + csa = models.DecimalField(decimal_places=2, max_digits=10, + blank=True, null=True, help_text='mm^2') circuits = models.IntegerField(blank=True, null=True) cores = models.IntegerField(blank=True, null=True) @@ -125,7 +138,8 @@ class Asset(models.Model): self.asset_id = self.asset_id.upper() asset_search = re.search("^([a-zA-Z0-9]*?[a-zA-Z]?)([0-9]+)$", self.asset_id) if asset_search is None: - errdict["asset_id"] = ["An Asset ID can only consist of letters and numbers, with a final number"] + errdict["asset_id"] = [ + "An Asset ID can only consist of letters and numbers, with a final number"] if self.purchase_price and self.purchase_price < 0: errdict["purchase_price"] = ["A price cannot be negative"] diff --git a/assets/templates/asset_activity_table.html b/assets/templates/asset_activity_table.html new file mode 100644 index 00000000..aca6142c --- /dev/null +++ b/assets/templates/asset_activity_table.html @@ -0,0 +1,92 @@ +{% extends request.is_ajax|yesno:"base_ajax.html,base_assets.html" %} +{% load static %} +{% load paginator from filters %} +{% load to_class_name from filters %} + +{% block title %}Asset Activity Stream{% endblock %} + +{# TODO: Find a way to reduce code duplication...can't just include the content because of the IDs... #} + +{% block js %} + + + + +{% endblock %} + +{% block content %} +
    +
    +
    +

    Asset Activity Stream

    +
    +
    {% paginator %}
    +
    +
    + + + + + + + + + + + + + + {% for version in object_list %} + + + + + + + + + + + {% endfor %} + + +
    DateObjectVersion IDUserChangesComment
    {{ version.revision.date_created }}{{version.changes.new|to_class_name}} {{ version.changes.new.asset_id|default:version.changes.new.pk }}{{ version.pk }}|{{ version.revision.pk }}{{ version.revision.user.name }} + {% if version.changes.old == None %} + {{version.changes.new|to_class_name}} Created + {% else %} + {% include 'RIGS/version_changes.html' %} + {% endif %} {{ version.changes.revision.comment }}
    + +
    +
    {% paginator %}
    +
    +{% endblock %} diff --git a/assets/templates/asset_update.html b/assets/templates/asset_update.html index faee7662..9987e4fe 100644 --- a/assets/templates/asset_update.html +++ b/assets/templates/asset_update.html @@ -45,6 +45,16 @@
    +{% if not edit %} + +{% endif %} + {% endblock %} {% block js%} diff --git a/assets/templates/asset_version_history.html b/assets/templates/asset_version_history.html new file mode 100644 index 00000000..89223294 --- /dev/null +++ b/assets/templates/asset_version_history.html @@ -0,0 +1,68 @@ +{% extends request.is_ajax|yesno:"base_ajax.html,base_assets.html" %} +{% load to_class_name from filters %} +{% load paginator from filters %} +{% load static %} + +{% block title %}{{object|to_class_name}} {{ object.asset_id }} - Revision History{% endblock %} + +{% block js %} + + + +{% endblock %} + +{% block content %} +
    +
    + +
    {% paginator %}
    +
    +
    + + + + + + + + + + + + {% for version in object_list %} + + + + + + + + + + + {% endfor %} + + +
    DateVersion IDUserChangesComment
    {{ version.revision.date_created }}{{ version.pk }}|{{ version.revision.pk }}{{ version.revision.user.name }} + {% if version.changes.old is None %} + {{object|to_class_name}} Created + {% else %} + {% include 'RIGS/version_changes.html' %} + {% endif %} + + {{ version.revision.comment }} +
    +
    +
    {% paginator %}
    +
    +{% endblock %} diff --git a/assets/templates/supplier_list.html b/assets/templates/supplier_list.html index 71ceb78c..4de91bbc 100644 --- a/assets/templates/supplier_list.html +++ b/assets/templates/supplier_list.html @@ -31,6 +31,7 @@ {{ item.name }} Edit + History {% endfor %} diff --git a/assets/urls.py b/assets/urls.py index 0a8ff599..3acac945 100644 --- a/assets/urls.py +++ b/assets/urls.py @@ -1,5 +1,7 @@ +from django.conf.urls import url from django.urls import path -from assets import views +from assets import views, models +from RIGS import versioning from PyRIGS.decorators import permission_required_with_403 @@ -7,16 +9,27 @@ urlpatterns = [ path('', views.AssetList.as_view(), name='asset_index'), path('asset/list/', views.AssetList.as_view(), name='asset_list'), path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), - path('asset/create/', permission_required_with_403('assets.add_asset')(views.AssetCreate.as_view()), name='asset_create'), - path('asset/id//edit/', permission_required_with_403('assets.change_asset')(views.AssetEdit.as_view()), name='asset_update'), - path('asset/id//duplicate/', permission_required_with_403('assets.add_asset')(views.AssetDuplicate.as_view()), name='asset_duplicate'), + path('asset/create/', permission_required_with_403('assets.add_asset') + (views.AssetCreate.as_view()), name='asset_create'), + path('asset/id//edit/', permission_required_with_403('assets.change_asset') + (views.AssetEdit.as_view()), name='asset_update'), + path('asset/id//duplicate/', permission_required_with_403('assets.add_asset') + (views.AssetDuplicate.as_view()), name='asset_duplicate'), + path('asset/id//history/', views.AssetVersionHistory.as_view(), + name='asset_history', kwargs={'model': models.Asset}), + path('activity', permission_required_with_403('assets.view_asset') + (views.ActivityTable.as_view()), name='asset_activity_table'), path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'), path('supplier/list', views.SupplierList.as_view(), name='supplier_list'), path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'), - path('supplier/create', permission_required_with_403('assets.add_supplier')(views.SupplierCreate.as_view()), name='supplier_create'), - path('supplier//edit', permission_required_with_403('assets.change_supplier')(views.SupplierUpdate.as_view()), name='supplier_update'), + path('supplier/create', permission_required_with_403('assets.add_supplier') + (views.SupplierCreate.as_view()), name='supplier_create'), + path('supplier//edit', permission_required_with_403('assets.change_supplier') + (views.SupplierUpdate.as_view()), name='supplier_update'), + path('supplier//history/', views.SupplierVersionHistory.as_view(), + name='supplier_history', kwargs={'model': models.Supplier}), path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'), ] diff --git a/assets/views.py b/assets/views.py index 06449cdb..1acc28fb 100644 --- a/assets/views.py +++ b/assets/views.py @@ -5,7 +5,9 @@ from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.urls import reverse from django.db.models import Q +from django.shortcuts import get_object_or_404 from assets import models, forms +from RIGS import versioning @method_decorator(csrf_exempt, name='dispatch') @@ -36,7 +38,8 @@ class AssetList(LoginRequiredMixin, generic.ListView): if len(query_string) == 0: queryset = self.model.objects.all() elif len(query_string) >= 3: - queryset = self.model.objects.filter(Q(asset_id__exact=query_string) | Q(description__icontains=query_string)) + queryset = self.model.objects.filter( + Q(asset_id__exact=query_string) | Q(description__icontains=query_string)) else: queryset = self.model.objects.filter(Q(asset_id__exact=query_string)) @@ -46,7 +49,8 @@ class AssetList(LoginRequiredMixin, generic.ListView): if len(form.cleaned_data['status']) > 0: queryset = queryset.filter(status__in=form.cleaned_data['status']) elif self.hide_hidden_status: - queryset = queryset.filter(status__in=models.AssetStatus.objects.filter(should_show=True)) + queryset = queryset.filter( + status__in=models.AssetStatus.objects.filter(should_show=True)) return queryset @@ -203,3 +207,24 @@ class SupplierUpdate(generic.UpdateView): model = models.Supplier form_class = forms.SupplierForm template_name = 'supplier_update.html' + + +class SupplierVersionHistory(versioning.VersionHistory): + template_name = "asset_version_history.html" + + +# TODO: Reduce SQL queries +class AssetVersionHistory(versioning.VersionHistory): + def get_object(self, **kwargs): + return get_object_or_404(models.Asset, asset_id=self.kwargs['pk']) + + +class ActivityTable(versioning.ActivityTable): + model = versioning.RIGSVersion + template_name = "asset_activity_table.html" + paginate_by = 25 + + def get_queryset(self): + versions = versioning.RIGSVersion.objects.get_for_multiple_models( + [models.Asset, models.Supplier]) + return versions diff --git a/templates/base_assets.html b/templates/base_assets.html index 7dd53034..288d7dc9 100644 --- a/templates/base_assets.html +++ b/templates/base_assets.html @@ -5,7 +5,7 @@ {% endblock %} {% block titleelements %} - {% if perms.assets.view_asset%} + {% if perms.assets.view_asset %} {% endif %} - {% if perms.assets.view_supplier%} + {% if perms.assets.view_supplier %} {% endif %} + {% if perms.assets.view_asset %} +
  • Recent Changes
  • + {% endif %} {% endblock %} From ca8253894acb2810d62c5eb8d384e38bfde1732e Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Tue, 31 Dec 2019 12:45:38 +0000 Subject: [PATCH 256/275] FIX #321: Authorisation time shown as 'None' in emails (#378) * FIX #321: Authorisation Success emails dated 'None' * FIX: Additionally fix datestamp on HTML client emails (#321) --- RIGS/templates/RIGS/eventauthorisation_client_success.html | 2 +- RIGS/templates/RIGS/eventauthorisation_client_success.txt | 4 ++-- RIGS/templates/RIGS/eventauthorisation_mic_success.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.html b/RIGS/templates/RIGS/eventauthorisation_client_success.html index dc6d0485..398dee80 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_success.html +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.html @@ -6,7 +6,7 @@

    Your event N{{ object.event.pk|stringformat:"05d" }} has been successfully authorised for £{{ object.amount }} - by {{ object.name }} as of {{ object.last_edited_at }}. + by {{ object.name }} as of {{ object.event.last_edited_at }}.

    diff --git a/RIGS/templates/RIGS/eventauthorisation_client_success.txt b/RIGS/templates/RIGS/eventauthorisation_client_success.txt index ff934c4d..c511fc72 100644 --- a/RIGS/templates/RIGS/eventauthorisation_client_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_client_success.txt @@ -1,6 +1,6 @@ -Hi {{ to_name|default:"there" }}, +Hi {{ to_name|default_if_none:"there" }}, -Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. +Your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}. {% if object.event.organisation and object.event.organisation.union_account %}{# internal #} Your event is now fully booked and payment will be processed by the finance department automatically. diff --git a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt index 7548cad4..43ddd5e1 100644 --- a/RIGS/templates/RIGS/eventauthorisation_mic_success.txt +++ b/RIGS/templates/RIGS/eventauthorisation_mic_success.txt @@ -1,5 +1,5 @@ Hi {{object.event.mic.get_full_name|default_if_none:"somebody"}}, -Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.last_edited_at}}. +Just to let you know your event N{{object.event.pk|stringformat:"05d"}} has been successfully authorised for £{{object.amount}} by {{object.name}} as of {{object.event.last_edited_at}}. The TEC Rig Information Gathering System From 3b28eafc820ef6bf6e3acebacde06daa78d78b02 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 31 Dec 2019 16:33:43 +0000 Subject: [PATCH 257/275] Order RIGSVersions by date --- RIGS/versioning.py | 1 + 1 file changed, 1 insertion(+) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 795ea9fe..102756ab 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -174,6 +174,7 @@ class RIGSVersionManager(VersionQuerySet): class RIGSVersion(Version): class Meta: proxy = True + ordering = ['-date_created'] objects = RIGSVersionManager.as_manager() From 97c0dffbd35856baeaf55baca3416c29cd873db1 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 31 Dec 2019 16:42:56 +0000 Subject: [PATCH 258/275] Order revisions by date created (#389) --- RIGS/versioning.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/RIGS/versioning.py b/RIGS/versioning.py index 102756ab..9dff4a3e 100644 --- a/RIGS/versioning.py +++ b/RIGS/versioning.py @@ -168,13 +168,12 @@ class RIGSVersionManager(VersionQuerySet): for model in model_array: content_types.append(ContentType.objects.get_for_model(model)) - return self.filter(content_type__in=content_types).select_related("revision").order_by("-pk") + return self.filter(content_type__in=content_types).select_related("revision").order_by("-revision__date_created") class RIGSVersion(Version): class Meta: proxy = True - ordering = ['-date_created'] objects = RIGSVersionManager.as_manager() @@ -207,7 +206,7 @@ class VersionHistory(generic.ListView): paginate_by = 25 def get_queryset(self, **kwargs): - return RIGSVersion.objects.get_for_object(self.get_object()).select_related("revision", "revision__user").all() + return RIGSVersion.objects.get_for_object(self.get_object()).select_related("revision", "revision__user").all().order_by("-revision__date_created") def get_object(self, **kwargs): return get_object_or_404(self.kwargs['model'], pk=self.kwargs['pk']) @@ -226,7 +225,7 @@ class ActivityTable(generic.ListView): def get_queryset(self): versions = RIGSVersion.objects.get_for_multiple_models([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) - return versions + return versions.order_by("-revision__date_created") class ActivityFeed(generic.ListView): @@ -236,7 +235,7 @@ class ActivityFeed(generic.ListView): def get_queryset(self): versions = RIGSVersion.objects.get_for_multiple_models([models.Event, models.Venue, models.Person, models.Organisation, models.EventAuthorisation]) - return versions + return versions.order_by("-revision__date_created") def get_context_data(self, **kwargs): # Call the base implementation first to get a context From 82a30ca77d2d3e7e312b39f986734c4c7fcf7019 Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Fri, 3 Jan 2020 21:46:39 +0000 Subject: [PATCH 259/275] Miscellaneous changes to the Asset DB (#390) * FIX #388: Prevent assets losing supplier data on edit * FEAT: Add associated assets to supplier detail view * FIX: Tweak supplier list to make detail view accessible * Potential fix for #380 No idea if it works because I can't reproduce locally. S/O Reckons it should... :P * FEAT #386: Asset search searches serial number. Pending addition of advanced search. * FIX: Order asset categories/statuses alphabetically Instead of by pk because that's silly. * FEAT: Statuses can have a CSS class defined in the admin panel This replaces the hardcoding of colours in the asset list. * FIX: Squash migrations * Fixed supplier not working on all the create asset template * Refactored away "assets" property on "Supplier" by using "related_name" instead Co-authored-by: Matthew Smith --- RIGS/views.py | 3 + assets/filters.py | 2 +- assets/forms.py | 5 ++ assets/migrations/0009_auto_20200102_1933.py | 21 ++++++ ...2_1933_squashed_0011_auto_20200102_2040.py | 28 +++++++ .../0010_assetstatus_display_class.py | 18 +++++ assets/migrations/0010_auto_20200103_2134.py | 19 +++++ assets/migrations/0011_auto_20200102_2040.py | 18 +++++ assets/models.py | 5 +- assets/templates/asset_create.html | 1 - assets/templates/asset_list.html | 4 +- assets/templates/asset_update.html | 1 - .../partials/asset_list_table_body.html | 16 +--- .../partials/purchasedetails_form.html | 25 ++++++- .../templates/partials/supplier_picker.html | 64 ---------------- assets/templates/supplier_detail.html | 73 ++++++++++++++++++- assets/templates/supplier_list.html | 2 +- assets/views.py | 2 +- templates/base_assets.html | 5 ++ 19 files changed, 220 insertions(+), 92 deletions(-) create mode 100644 assets/migrations/0009_auto_20200102_1933.py create mode 100644 assets/migrations/0009_auto_20200102_1933_squashed_0011_auto_20200102_2040.py create mode 100644 assets/migrations/0010_assetstatus_display_class.py create mode 100644 assets/migrations/0010_auto_20200103_2134.py create mode 100644 assets/migrations/0011_auto_20200102_2040.py delete mode 100644 assets/templates/partials/supplier_picker.html diff --git a/RIGS/views.py b/RIGS/views.py index b197334c..023f0089 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -17,6 +17,7 @@ from django.views.decorators.csrf import csrf_exempt from RIGS import models, forms +from assets import models as asset_models from functools import reduce """ @@ -248,6 +249,7 @@ class SecureAPIRequest(generic.View): 'organisation': models.Organisation, 'profile': models.Profile, 'event': models.Event, + 'supplier': asset_models.Supplier } perms = { @@ -256,6 +258,7 @@ class SecureAPIRequest(generic.View): 'organisation': 'RIGS.view_organisation', 'profile': 'RIGS.view_profile', 'event': None, + 'supplier': None } ''' diff --git a/assets/filters.py b/assets/filters.py index c7efcd7f..b7cc8ffa 100644 --- a/assets/filters.py +++ b/assets/filters.py @@ -6,4 +6,4 @@ from assets import models class AssetFilter(django_filters.FilterSet): class Meta: model = models.Asset - fields = ['asset_id', 'description', 'category', 'status'] + fields = ['asset_id', 'description', 'serial_number', 'category', 'status'] diff --git a/assets/forms.py b/assets/forms.py index 7571d28b..ea05efd3 100644 --- a/assets/forms.py +++ b/assets/forms.py @@ -4,6 +4,11 @@ from assets import models class AssetForm(forms.ModelForm): + related_models = { + 'asset': models.Asset, + 'supplier': models.Supplier + } + class Meta: model = models.Asset fields = '__all__' diff --git a/assets/migrations/0009_auto_20200102_1933.py b/assets/migrations/0009_auto_20200102_1933.py new file mode 100644 index 00000000..57f745a1 --- /dev/null +++ b/assets/migrations/0009_auto_20200102_1933.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.13 on 2020-01-02 19:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0008_auto_20191206_2124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='assetcategory', + options={'ordering': ['name'], 'verbose_name': 'Asset Category', 'verbose_name_plural': 'Asset Categories'}, + ), + migrations.AlterModelOptions( + name='assetstatus', + options={'ordering': ['name'], 'verbose_name': 'Asset Status', 'verbose_name_plural': 'Asset Statuses'}, + ), + ] diff --git a/assets/migrations/0009_auto_20200102_1933_squashed_0011_auto_20200102_2040.py b/assets/migrations/0009_auto_20200102_1933_squashed_0011_auto_20200102_2040.py new file mode 100644 index 00000000..649c419c --- /dev/null +++ b/assets/migrations/0009_auto_20200102_1933_squashed_0011_auto_20200102_2040.py @@ -0,0 +1,28 @@ +# Generated by Django 2.0.13 on 2020-01-02 20:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + replaces = [('assets', '0009_auto_20200102_1933'), ('assets', '0010_assetstatus_display_class'), ('assets', '0011_auto_20200102_2040')] + + dependencies = [ + ('assets', '0008_auto_20191206_2124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='assetcategory', + options={'ordering': ['name'], 'verbose_name': 'Asset Category', 'verbose_name_plural': 'Asset Categories'}, + ), + migrations.AlterModelOptions( + name='assetstatus', + options={'ordering': ['name'], 'verbose_name': 'Asset Status', 'verbose_name_plural': 'Asset Statuses'}, + ), + migrations.AddField( + model_name='assetstatus', + name='display_class', + field=models.CharField(help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80), + ), + ] diff --git a/assets/migrations/0010_assetstatus_display_class.py b/assets/migrations/0010_assetstatus_display_class.py new file mode 100644 index 00000000..f2521ea0 --- /dev/null +++ b/assets/migrations/0010_assetstatus_display_class.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2020-01-02 19:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0009_auto_20200102_1933'), + ] + + operations = [ + migrations.AddField( + model_name='assetstatus', + name='display_class', + field=models.TextField(default='', help_text='HTML class to be appended to alter display of assets with this status, such as in the list.'), + ), + ] diff --git a/assets/migrations/0010_auto_20200103_2134.py b/assets/migrations/0010_auto_20200103_2134.py new file mode 100644 index 00000000..91d2a131 --- /dev/null +++ b/assets/migrations/0010_auto_20200103_2134.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.13 on 2020-01-03 21:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0009_auto_20200102_1933_squashed_0011_auto_20200102_2040'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='purchased_from', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Supplier'), + ), + ] diff --git a/assets/migrations/0011_auto_20200102_2040.py b/assets/migrations/0011_auto_20200102_2040.py new file mode 100644 index 00000000..d89aa0ad --- /dev/null +++ b/assets/migrations/0011_auto_20200102_2040.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2020-01-02 20:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0010_assetstatus_display_class'), + ] + + operations = [ + migrations.AlterField( + model_name='assetstatus', + name='display_class', + field=models.CharField(help_text='HTML class to be appended to alter display of assets with this status, such as in the list.', max_length=80), + ), + ] diff --git a/assets/models.py b/assets/models.py index 2398a563..f152a39a 100644 --- a/assets/models.py +++ b/assets/models.py @@ -16,6 +16,7 @@ class AssetCategory(models.Model): class Meta: verbose_name = 'Asset Category' verbose_name_plural = 'Asset Categories' + ordering = ['name'] name = models.CharField(max_length=80) @@ -27,10 +28,12 @@ class AssetStatus(models.Model): class Meta: verbose_name = 'Asset Status' verbose_name_plural = 'Asset Statuses' + ordering = ['name'] name = models.CharField(max_length=80) should_show = models.BooleanField( default=True, help_text="Should this be shown by default in the asset list.") + display_class = models.CharField(max_length=80, help_text="HTML class to be appended to alter display of assets with this status, such as in the list.") def __str__(self): return self.name @@ -78,7 +81,7 @@ class Asset(models.Model, RevisionMixin): category = models.ForeignKey(to=AssetCategory, on_delete=models.CASCADE) status = models.ForeignKey(to=AssetStatus, on_delete=models.CASCADE) serial_number = models.CharField(max_length=150, blank=True) - purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True) + purchased_from = models.ForeignKey(to=Supplier, on_delete=models.CASCADE, blank=True, null=True, related_name="assets") date_acquired = models.DateField() date_sold = models.DateField(blank=True, null=True) purchase_price = models.DecimalField(blank=True, null=True, decimal_places=2, max_digits=10) diff --git a/assets/templates/asset_create.html b/assets/templates/asset_create.html index b0bb8297..bc953d2d 100644 --- a/assets/templates/asset_create.html +++ b/assets/templates/asset_create.html @@ -3,7 +3,6 @@ {% load asset_templatetags %} {% block title %}Asset {{ object.asset_id }}{% endblock %} - {% block content %}