diff --git a/.drone.yml b/.drone.yml index 7b35620..135dddd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ pipeline: - cp -n env.example .env - mkdir -p ./cache/packages ./cache/pip - pip install --user --cache-dir ./cache/pip -r ./requirements/test.txt - - python manage.py test mhackspace --verbosity 2 + - python manage.py test mhackspace --keepdb --verbosity 2 publish-test: pull: True diff --git a/README.org b/README.org index 4531215..3fb38b7 100644 --- a/README.org +++ b/README.org @@ -5,7 +5,6 @@ Repository for the maidstone hackspace website, feel free to fork this site for your own Hackspace. - ** Requirements Before getting started make sure you have git, docker and docker-compose installed on your machine. The simplest way to setup this site is to use docker-compose so please install that from this site @@ -47,7 +46,7 @@ docker-compose -f dev.yml run --rm django python manage.py makemigrations docker-compose -f dev.yml run --rm django python manage.py migrate #+END_SRC *** Create the admin user. -Once created you can login at http://127.0.0.1:8180/admin +Once created you can login at http://127.0.0.1:8180/trustee #+BEGIN_SRC sh docker-compose -f dev.yml run --rm django python manage.py createsuperuser #+END_SRC @@ -74,3 +73,24 @@ docker-compose -fdev.yml run --rm django python manage.py list_subscriptions #+BEGIN_SRC sh docker-compose -fdev.yml run --rm django python manage.py rendervariations 'blog.Post.image' --replace #+END_SRC +** Api +#+BEGIN_SRC python +import requests + +url = 'http://127.0.0.1:8180/api/v1/rfidAuth/' +data = { + 'rfid': '4996', + 'device': '7bff6053-77ef-4250-ac11-8a119fd05a0e' +} + +# client = RequestsClient() +response = requests.post( + 'http://127.0.0.1:8180/api/v1/rfidAuth/', + data={'rfid': '238e', 'device': 'e8f27231-8093-4477-8906-e5ae1b12dbd6'}) +#requests.get(url) +return response.status_code + +#+END_SRC + +#+RESULTS: +: 200 diff --git a/compose/django/start-dev.sh b/compose/django/start-dev.sh index 04e0698..9b8c8ef 100644 --- a/compose/django/start-dev.sh +++ b/compose/django/start-dev.sh @@ -1,3 +1,3 @@ #!/bin/sh python manage.py migrate -python manage.py runserver_plus 0.0.0.0:8000 +while true; do python manage.py runserver_plus 0.0.0.0:8000; sleep 2; done diff --git a/config/settings/common.py b/config/settings/common.py index 35fcc54..a6121a7 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -82,6 +82,7 @@ THIRD_PARTY_APPS = ( 'whitenoise.runserver_nostatic', 'stdimage', 'rest_framework', + 'django_filters', 'draceditor', 'haystack', 'djconfig', @@ -140,7 +141,7 @@ LOCAL_APPS = ( 'mhackspace.blog', 'mhackspace.core', 'mhackspace.requests', - 'mhackspace.register', + 'mhackspace.rfid', ) # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -376,7 +377,12 @@ CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='redis://redis:6379/0') #if CELERY_BROKER_URL == 'django://': # CELERY_RESULT_BACKEND = 'redis://' #else: -CELERY_RESULT_BACKEND = 'django-cache' +CELERY_RESULT_BACKEND = 'redis://redis:6379/0' +CELERY_IGNORE_RESULT = False +CELERY_REDIS_HOST = "redis" +CELERY_REDIS_PORT = 6379 +CELERY_REDIS_DB = 0 + CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' INSTALLED_APPS += ('django_celery_results','django_celery_beat',) CELERY_TIMEZONE = 'UTC' @@ -449,10 +455,12 @@ REST_FRAMEWORK = { 'rest_framework.filters.OrderingFilter' ), 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', + # 'rest_framework.permissions.IsAuthenticated', + # 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.BasicAuthentication', + # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + # 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication', ), @@ -472,3 +480,4 @@ MATRIX_USER=env('MATRIX_USERNAME') MATRIX_PASSWORD=env('MATRIX_PASSWORD') MATRIX_ROOM=env('MATRIX_ROOM') MSG_PREFIX = 'MH' +X_FRAME_OPTIONS = 'SAMEORIGIN' diff --git a/config/settings/local.py b/config/settings/local.py index 8022b67..0af723e 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -86,7 +86,7 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner' ########## CELERY # In development, all tasks will be executed locally by blocking until the task returns -CELERY_ALWAYS_EAGER = True +# CELERY_ALWAYS_EAGER = True ########## END CELERY # Your local stuff: Below this line define 3rd party library settings @@ -99,5 +99,50 @@ CAPTCHA = { WHITENOISE_AUTOREFRESH = True WHITENOISE_USE_FINDERS = True +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s ' + '%(process)d %(thread)d %(message)s' + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'DEBUG', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + }, + 'logfile': { + 'level':'DEBUG', + 'class':'logging.FileHandler', + 'filename': "%s/django.log" % ROOT_DIR, + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins', 'logfile'], + 'level': 'ERROR', + 'propagate': True + }, + 'django.security.DisallowedHost': { + 'level': 'ERROR', + 'handlers': ['logfile', 'console', 'mail_admins'], + 'propagate': True + } + } +} + PAYMENT_PROVIDERS['gocardless']['redirect_url'] = 'http://127.0.0.1:8180' TEMPLATE_DEBUG = False diff --git a/config/settings/production.py b/config/settings/production.py index 6cd0db0..6be8331 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_SECURE = True #disabledd so csrf works with ajax CSRF_COOKIE_HTTPONLY = False -X_FRAME_OPTIONS = 'DENY' +X_FRAME_OPTIONS = 'SAMEORIGIN' # SITE CONFIGURATION # ------------------------------------------------------------------------------ diff --git a/config/settings/stage.py b/config/settings/stage.py index 157cb70..2ca202e 100644 --- a/config/settings/stage.py +++ b/config/settings/stage.py @@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_SECURE = True #disabledd so csrf works with ajax CSRF_COOKIE_HTTPONLY = False -X_FRAME_OPTIONS = 'DENY' +X_FRAME_OPTIONS = 'SAMEORIGIN' # SITE CONFIGURATION # ------------------------------------------------------------------------------ diff --git a/config/settings/test.py b/config/settings/test.py index 98daf9b..f45bef3 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -67,5 +67,8 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [ ] DATABASES = { - 'default': {'ENGINE': 'django.db.backends.sqlite3'} + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(str(ROOT_DIR), 'cache/test_database.db'), + } } diff --git a/config/urls.py b/config/urls.py index ec72a2f..3d9f2ba 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals + from django.conf import settings from django.conf.urls import include, url from django.conf.urls.static import static @@ -9,6 +10,7 @@ from django.views import defaults as default_views from django.contrib.auth import views as auth_views from django.contrib.sitemaps.views import sitemap from rest_framework.routers import DefaultRouter +from rest_framework.documentation import include_docs_urls from mhackspace.contact.views import contact from mhackspace.members.views import MemberListView @@ -20,22 +22,29 @@ from mhackspace.blog.views import PostViewSet, CategoryViewSet, BlogPost, PostLi from mhackspace.blog.sitemaps import PostSitemap, CategorySitemap from mhackspace.feeds.views import FeedViewSet, ArticleViewSet from mhackspace.requests.views import RequestsForm, RequestsList +from mhackspace.rfid.views import DeviceViewSet, AuthUserWithDeviceViewSet + from mhackspace.register.views import RegisterForm from wiki.urls import get_pattern as get_wiki_pattern from django_nyt.urls import get_pattern as get_nyt_pattern +from rest_framework_jwt.views import obtain_jwt_token router = DefaultRouter() -router.register(r'posts', PostViewSet) -router.register(r'categories', CategoryViewSet) -router.register(r'feeds', FeedViewSet) -router.register(r'articles', ArticleViewSet) +router.register(r'posts', PostViewSet, 'posts') +router.register(r'categories', CategoryViewSet, base_name='categories') +router.register(r'feeds', FeedViewSet, 'feeds') +router.register(r'articles', ArticleViewSet, base_name='articles') +router.register(r'rfid', DeviceViewSet, base_name='rfid_device') +router.register(r'rfidAuth', AuthUserWithDeviceViewSet, base_name='device_auth') + sitemaps = { 'posts': PostSitemap, 'category': CategorySitemap, } + urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'), url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'), @@ -69,6 +78,9 @@ urlpatterns = [ # Django Admin, use {% url 'admin:index' %} url(r'{}'.format(settings.ADMIN_URL), admin.site.urls), + url(r'^api-token-auth/', obtain_jwt_token), + url(r'^api/docs/', include_docs_urls(title='Hackspace API')), + # User management url(r'^users/', include('mhackspace.users.urls', namespace='users')), url(r'^accounts/', include('allauth.urls')), diff --git a/mhackspace/base/management/commands/generate_test_data.py b/mhackspace/base/management/commands/generate_test_data.py index a3b8a8d..6665e7d 100644 --- a/mhackspace/base/management/commands/generate_test_data.py +++ b/mhackspace/base/management/commands/generate_test_data.py @@ -1,12 +1,20 @@ +import uuid import random +from django.contrib.auth.hashers import make_password from autofixture import AutoFixture -from autofixture.generators import ImageGenerator +from autofixture.generators import ( + ImageGenerator, + IntegerGenerator, + ChoicesGenerator, + Generator, + LoremWordGenerator) from django.core.management.base import BaseCommand from django.core.management import call_command from mhackspace.base.models import BannerImage from mhackspace.feeds.models import Article, Feed -from mhackspace.users.models import User +from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES from mhackspace.blog.models import Category, Post +from mhackspace.rfid.models import Device, DeviceAuth class ImageFixture(AutoFixture): @@ -14,12 +22,23 @@ class ImageFixture(AutoFixture): scaled_image = ImageGenerator(width=800, height=300, sizes=((1280, 300),)) +def RfidFixture(): + while True: + yield str(uuid.uuid4())[0:4] + + +class RfidGenerator(Generator): + def generate(self): + return str(uuid.uuid4())[0:4] + + class Command(BaseCommand): help = 'Build test data for development environment' def handle(self, *args, **options): - feeds = AutoFixture(Article) + feeds = AutoFixture(Article, generate_fk=True) feeds.create(10) + feed = AutoFixture(Feed) feed.create(10) @@ -32,14 +51,61 @@ class Command(BaseCommand): # load known data call_command('loaddata', 'mhackspace/users/fixtures/groups.json', verbose=0) - # random data + + User.objects.all().delete() + Membership.objects.all().delete() users = AutoFixture(User, field_values={ - 'title': random.choicee(('Mr', 'Mrs', 'Emperor', 'Captain')) + 'title': ChoicesGenerator(values=('Mr', 'Mrs', 'Emperor', 'Captain')), + 'password': make_password('autofixtures'), + 'active': True, + 'username': ChoicesGenerator(values=('Bob', 'Jane', 'Adam', 'Alice', 'Bill', 'Jill', 'Sam', 'Oly')) + }, generate_fk=True) + users.create(8) + users = AutoFixture(User, field_values={ + 'title': 'Mr', + 'username': 'admin', + 'password': make_password('autofixtures'), + 'is_superuser': True, + 'is_staff': True, + 'is_active': True + }, generate_fk=True) + users.create(1) + + user_list = User.objects.all() + members = AutoFixture(Membership, field_values={ + 'status': ChoicesGenerator(MEMBERSHIP_STATUS_CHOICES), + 'user': ChoicesGenerator(values=user_list) }) - users.create(10) + members.create(8) + + Rfid.objects.all().delete() + Device.objects.all().delete() + DeviceAuth.objects.all().delete() + + rfid = AutoFixture( + Rfid, + field_values={ + 'code': RfidGenerator(), + 'description': LoremWordGenerator()}) + rfid.create(20) + + device = AutoFixture(Device, field_values={ + 'name': ChoicesGenerator(values=('Door', 'Printer', 'Laser Cutter', '')) + }) + device.create(5) + + deviceauth = AutoFixture(DeviceAuth) + deviceauth.create(5) + + feed = AutoFixture(Feed) + feed.create(10) + + feeds = AutoFixture(Article) + feeds.create(10) banners = ImageFixture(BannerImage) banners.create(10) self.stdout.write( self.style.SUCCESS( 'Finished creating test data')) + diff --git a/mhackspace/base/tasks.py b/mhackspace/base/tasks.py index 74a998d..8b6a040 100644 --- a/mhackspace/base/tasks.py +++ b/mhackspace/base/tasks.py @@ -6,8 +6,8 @@ from mhackspace.feeds.helper import import_feeds @shared_task def update_homepage_feeds(): - return import_feeds() - + import_feeds() + return {'result': 'Homepage feeds imported'} matrix_url = "https://matrix.org/_matrix/client/r0" matrix_login_url = matrix_url + "/login" @@ -31,9 +31,10 @@ def send_email(email_to, to=[email_to], headers={'Reply-To': 'no-reply@maidstone-hackspace.org.uk'}) email.send() + return {'result', 'Email sent to %s' % email_to} @shared_task -def matrix_message(message): +def matrix_message(message, prefix=''): # we dont rely on theses, so ignore if it goes wrong # TODO at least log that something has gone wrong try: @@ -59,8 +60,8 @@ def matrix_message(message): url = matrix_send_msg_url.format(**url_params) details = { "msgtype": "m.text", - "body": "[%s] %s" % (settings.MSG_PREFIX, message)} + "body": "[%s%s] %s" % (settings.MSG_PREFIX, prefix, message)} r2 = requests.post(url, json=details) except: pass - return True + return {'result', 'Matrix message sent successfully'} diff --git a/mhackspace/base/tests.py b/mhackspace/base/tests.py new file mode 100644 index 0000000..3793b3d --- /dev/null +++ b/mhackspace/base/tests.py @@ -0,0 +1,27 @@ +from test_plus.test import TestCase +from mhackspace.users.models import Membership +from mhackspace.users.models import User +from django.contrib.auth.models import Group + +from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions + +# this needs mocking +class TestTasks(TestCase): + def setUp(self): + self.user1 = self.make_user('u1') + self.user2 = self.make_user('u2') + self.group = Group(name='members') + self.group.save() + + def test_refresh_subscriptions(self): + membership_count = Membership.objects.all().delete() + user_count = User.objects.all().count() + membership_count = Membership.objects.all().count() + self.assertEquals(0, membership_count) + self.assertEquals(2, user_count) + + update_subscriptions(provider_name='gocardless') + + membership_count = Membership.objects.all().count() + self.assertEquals(2, membership_count) + self.assertEquals(2, user_count) diff --git a/mhackspace/feeds/helper.py b/mhackspace/feeds/helper.py index 59a72af..434e3f1 100644 --- a/mhackspace/feeds/helper.py +++ b/mhackspace/feeds/helper.py @@ -53,6 +53,8 @@ def download_remote_images(): render_variations(result[0], image_variations, replace=True) article.save() except: + logger.exception(result) + logger.exception(result[0]) logger.exception('Unable to download remote image for %s' % article.original_image) diff --git a/mhackspace/requests/migrations/0007_auto_20170919_2023.py b/mhackspace/requests/migrations/0007_auto_20170919_2023.py new file mode 100644 index 0000000..eacb3ce --- /dev/null +++ b/mhackspace/requests/migrations/0007_auto_20170919_2023.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-19 20:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('requests', '0006_auto_20170906_0804'), + ] + + operations = [ + migrations.AlterField( + model_name='userrequests', + name='cost', + field=models.DecimalField(decimal_places=2, help_text='Leave blank, if no associated cost, or add estimated cost if not sure.', max_digits=6), + ), + ] diff --git a/mhackspace/requests/models.py b/mhackspace/requests/models.py index 982abe7..fb77b03 100644 --- a/mhackspace/requests/models.py +++ b/mhackspace/requests/models.py @@ -21,7 +21,7 @@ class UserRequests(models.Model): title = models.CharField(max_length=255, help_text='Whats being requested ?') request_type = models.IntegerField(choices=REQUEST_TYPES, null=False) cost = models.DecimalField( - max_digits=4, + max_digits=6, decimal_places=2, help_text='Leave blank, if no associated cost, or add estimated cost if not sure.' ) @@ -45,7 +45,7 @@ class UserRequests(models.Model): def send_topic_update_email(sender, instance, **kwargs): - matrix_message.delay('New Request - %s' % instance.title) + matrix_message.delay(prefix=' - REQUEST', message=instance.title) post_save.connect(send_topic_update_email, sender=UserRequests) diff --git a/mhackspace/requests/tests.py b/mhackspace/requests/tests.py new file mode 100644 index 0000000..ed3f016 --- /dev/null +++ b/mhackspace/requests/tests.py @@ -0,0 +1,86 @@ +from django.test import TestCase +from mhackspace.requests.views import RequestsList, RequestForm +from mhackspace.users.models import User + +# Create your tests here. + +# @pytest.mark.parametrize("version", versions) +# @pytest.mark.parametrize("test_ctx, name", contexts) +# def test_context_renders(name, test_ctx, version): + + # users = AutoFixture(User, field_values={ + # 'title': 'Mr', + # 'username': 'admin', + # 'password': make_password('autofixtures'), + # 'is_superuser': True, + # 'is_staff': True, + # 'is_active': True + # }, generate_fk=True) + +def all_user_types(): + users = AutoFixture(User, field_values={ + 'title': 'Mr', + 'username': 'admin', + 'password': make_password('autofixtures'), + }, generate_fk=True) + yield users.create(1) + + users = AutoFixture(User, field_values={ + 'title': 'Mr', + 'username': 'admin', + 'password': make_password('autofixtures'), + 'is_staff': True, + }, generate_fk=True) + yield users.create(1) + + users = AutoFixture(User, field_values={ + 'title': 'Mr', + 'username': 'admin', + 'password': make_password('autofixtures'), + 'is_superuser': True, + 'is_staff': True, + }, generate_fk=True) + yield users.create(1) + +class BaseUserTestCase(TestCase): + + def setUp(self): + self.user = self.make_user() + self.factory = RequestFactory() + + def testRequestView(self): + for user in all_user_types() + view = RequestsList() + request = self.factory.get('/fake-url') + request.user = user + view.request = request + + +# class TestUserUpdateView(BaseUserTestCase): + +# def setUp(self): +# # call BaseUserTestCase.setUp() +# super(TestUserUpdateView, self).setUp() +# # Instantiate the view directly. Never do this outside a test! +# self.view = UserUpdateView() +# # Generate a fake request +# request = self.factory.get('/fake-url') +# # Attach the user to the request +# request.user = self.user +# # Attach the request to the view +# self.view.request = request + +# def test_get_success_url(self): +# # Expect: '/users/testuser/', as that is the default username for +# # self.make_user() +# self.assertEqual( +# self.view.get_success_url(), +# '/users/testuser/' +# ) + +# def test_get_object(self): +# # Expect: self.user, as that is the request's user object +# self.assertEqual( +# self.view.get_object(), +# self.user +# ) diff --git a/mhackspace/rfid/__init__.py b/mhackspace/rfid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/rfid/admin.py b/mhackspace/rfid/admin.py new file mode 100644 index 0000000..f530735 --- /dev/null +++ b/mhackspace/rfid/admin.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from django.contrib.admin import ModelAdmin +from django.forms.models import ModelChoiceField + +from mhackspace.rfid.models import Device, DeviceAuth + + +@admin.register(Device) +class DeviceAdmin(ModelAdmin): + list_display = ('name', 'identifier') + + +@admin.register(DeviceAuth) +class DeviceAuthAdmin(ModelAdmin): + list_display = ('device', 'rfid_code', 'rfid_user', 'device_id') + + def rfid_code(self, x): + return x.rfid.code + + def rfid_user(self, x): + return x.rfid.user diff --git a/mhackspace/rfid/migrations/0001_initial.py b/mhackspace/rfid/migrations/0001_initial.py new file mode 100644 index 0000000..e00b2b9 --- /dev/null +++ b/mhackspace/rfid/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-04-27 18:29 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('users', '0004_rfid'), + ] + + operations = [ + migrations.CreateModel( + name='Device', + fields=[ + ('identifier', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255, verbose_name='Device name')), + ('description', models.CharField(blank=True, max_length=255, verbose_name='Short description of what the device does')), + ('added_date', models.DateTimeField(default=django.utils.timezone.now, editable=False)), + ], + ), + migrations.CreateModel( + name='DeviceAuth', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rfid.Device')), + ('rfid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Rfid')), + ], + ), + migrations.AddField( + model_name='device', + name='members', + field=models.ManyToManyField(through='rfid.DeviceAuth', to='users.Rfid'), + ), + ] diff --git a/mhackspace/rfid/migrations/__init__.py b/mhackspace/rfid/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/rfid/models.py b/mhackspace/rfid/models.py new file mode 100644 index 0000000..c80c718 --- /dev/null +++ b/mhackspace/rfid/models.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import +import uuid + +from django.utils import timezone +from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from mhackspace.users.models import Rfid +# just brainstorming so we can start playing with this, +# be nice to make this a 3rd party django installable app ? + +# description of a device like door, print, laser cutter +class Device(models.Model): + # user = models.ManyToMany(settings.AUTH_USER_MODEL) + name = models.CharField(_('Device name'), max_length=255) + identifier = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + description = models.CharField(_('Short description of what the device does'), blank=True, max_length=255) + added_date = models.DateTimeField(default=timezone.now, editable=False) + members = models.ManyToManyField(Rfid, through='DeviceAuth') + + def __str__(self): + return self.name + + +# http://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields +# many to many, lookup user from rfid model then get there user_id and the device to check if auth allowed +class DeviceAuth(models.Model): + rfid = models.ForeignKey( + Rfid, + ) + + device = models.ForeignKey( + Device, + ) diff --git a/mhackspace/rfid/serializers.py b/mhackspace/rfid/serializers.py new file mode 100644 index 0000000..9f718c6 --- /dev/null +++ b/mhackspace/rfid/serializers.py @@ -0,0 +1,24 @@ +from rest_framework import serializers +from mhackspace.rfid.models import Device + + +class Task(object): + def __init__(self, **kwargs): + for field in ('id', 'name', 'owner', 'status'): + setattr(self, field, kwargs.get(field, None)) + + +class DeviceSerializer(serializers.ModelSerializer): + added_date = serializers.DateTimeField(format='iso-8601') + + class Meta: + model = Device + fields = ('__all__') + + +class AuthSerializer(serializers.Serializer): + name = serializers.CharField(max_length=255) + rfid = serializers.CharField(max_length=255) + # device = serializers.UUIDField(format='hex_verbose') + device = serializers.CharField(max_length=255) + diff --git a/mhackspace/rfid/tests/__init__.py b/mhackspace/rfid/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/rfid/tests/tests.py b/mhackspace/rfid/tests/tests.py new file mode 100644 index 0000000..2f5b24b --- /dev/null +++ b/mhackspace/rfid/tests/tests.py @@ -0,0 +1,96 @@ +import sys +import requests + +from io import StringIO +from django.core.management import call_command +from test_plus.test import TestCase +from rest_framework.test import APIRequestFactory +from rest_framework.test import RequestsClient + +from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.users.models import User, Rfid + + +# http://www.django-rest-framework.org/api-guide/testing/ + +class MigrationTestCase(TestCase): + + def testRollback(self): + out = StringIO() + sys.stout = out + call_command('migrate', 'rfid', 'zero', stdout=out) + call_command('migrate', 'rfid', stdout=out) + self.assertIn("... OK\n", out.getvalue()) + + +class ApiTests(TestCase): + maxDiff = None + def setUp(self): + self.user = User(name='User01') + self.user.save() + self.device = Device( + name='device01', + identifier='8e274b70-a4b3-4600-9472-f20ea7828cb6') + self.device.save() + self.rfid = Rfid(code='1', user=self.user) + self.rfid.save() + self.auth = DeviceAuth(rfid=self.rfid, device=self.device) + self.auth.save() + + def testAuth(self): + factory = APIRequestFactory() + request = factory.get('/rfid/') + + def testValidAuthCase(self): + "if we have a user rfid and a device identifier" + client = RequestsClient() + response = client.post( + 'http://127.0.0.1:8180/api/v1/rfidAuth/', + data={'rfid': '1', 'device': self.device.identifier}) + assert response.status_code == 200 + expected_result = { + 'rfid': self.rfid.code, + 'name': 'device01', + 'device': str(self.device.identifier)} + self.assertEquals( + response.json(), + expected_result + ) + + def testInValidAuthCase(self): + client = RequestsClient() + response = client.post( + 'http://127.0.0.1:8180/api/v1/rfidAuth/', + data={'rfid': '99', 'device': str(self.device.identifier)}) + assert response.status_code == 404 + + # response = client.post( + # 'http://127.0.0.1:8180/api/v1/rfidAuth/', + # data={'rfid': '1', 'device': 'test%s' % str(self.device.identifier)[3:]}) + # assert response.status_code == 404 + + + def testAuthUserWithDevice(self): + client = RequestsClient() + response = client.get('http://127.0.0.1:8180/api/v1/rfid/?format=json') + assert response.status_code == 200 + self.assertEquals(response.json().get('results'), [{ + 'name': 'device01', + 'identifier': '8e274b70-a4b3-4600-9472-f20ea7828cb6', + 'members': [1], + 'added_date': self.device.added_date.isoformat().replace('+00:00', 'Z'), + 'description': '' + }]) + + def testFetchDeviceList(self): + client = RequestsClient() + response = client.get('http://127.0.0.1:8180/api/v1/rfid/?format=json') + assert response.status_code == 200 + self.assertEquals(response.json().get('results'), [{ + 'name': 'device01', + 'identifier': '8e274b70-a4b3-4600-9472-f20ea7828cb6', + 'members': [1], + 'added_date': self.device.added_date.isoformat().replace('+00:00', 'Z'), + 'description': '' + }]) + diff --git a/mhackspace/rfid/views.py b/mhackspace/rfid/views.py new file mode 100644 index 0000000..30eb8cd --- /dev/null +++ b/mhackspace/rfid/views.py @@ -0,0 +1,41 @@ +import logging +from rest_framework.response import Response +from rest_framework import viewsets +from rest_framework import status +from mhackspace.users.models import Rfid +from mhackspace.rfid.models import Device, DeviceAuth +from mhackspace.rfid.serializers import DeviceSerializer, AuthSerializer +from django.core.exceptions import ObjectDoesNotExist, ValidationError + +logger = logging.getLogger(__name__) + + +class DeviceViewSet(viewsets.ModelViewSet): + queryset = Device.objects.all() + serializer_class = DeviceSerializer + + +# https://medium.com/django-rest-framework/django-rest-framework-viewset-when-you-don-t-have-a-model-335a0490ba6f +class AuthUserWithDeviceViewSet(viewsets.ViewSet): + # http_method_names = ['post'] + serializer_class = AuthSerializer + + def list(self, request): + serializer = DeviceSerializer( + Device.objects.all(), many=True) + return Response(serializer.data) + + def post(self, request, format=None): + try: + rfid = Rfid.objects.get(code=request.data.get('rfid')) + device = Device.objects.get(identifier=request.data.get('device')) + deviceAuth = DeviceAuth.objects.get(device=device.identifier, rfid=rfid.id) + except ObjectDoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + except ValidationError as e: + # except: + # logger.exception("An error occurred") + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR) + serializer = AuthSerializer( + instance={'name': device.name, 'rfid': rfid.code, 'device': device.identifier}) + return Response(serializer.data, status=200) diff --git a/mhackspace/static/sass/components/_wiki.scss b/mhackspace/static/sass/components/_wiki.scss new file mode 100644 index 0000000..e79ec1e --- /dev/null +++ b/mhackspace/static/sass/components/_wiki.scss @@ -0,0 +1,20 @@ +.modal-body iframe { + width: 100%; + height: 100%; +} +.modal-dialog { + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + margin: 50px; + padding: 0; + max-width: None; + position: absolute; +} + +.modal-content { + height: auto; + min-height: 100%; + border-radius: 0; +} diff --git a/mhackspace/static/sass/project.css b/mhackspace/static/sass/project.css new file mode 100644 index 0000000..1353500 --- /dev/null +++ b/mhackspace/static/sass/project.css @@ -0,0 +1,6097 @@ +/*! + * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + line-height: 1.15; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; } + +body { + margin: 0; } + +article, +aside, +footer, +header, +nav, +section { + display: block; } + +h1 { + font-size: 2em; + margin: 0.67em 0; } + +figcaption, +figure, +main { + display: block; } + +figure { + margin: 1em 40px; } + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; } + +pre { + font-family: monospace, monospace; + font-size: 1em; } + +a { + background-color: transparent; + -webkit-text-decoration-skip: objects; } + +a:active, +a:hover { + outline-width: 0; } + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted; } + +b, +strong { + font-weight: inherit; } + +b, +strong { + font-weight: bolder; } + +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; } + +dfn { + font-style: italic; } + +mark { + background-color: #ff0; + color: #000; } + +small { + font-size: 80%; } + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sub { + bottom: -0.25em; } + +sup { + top: -0.5em; } + +audio, +video { + display: inline-block; } + +audio:not([controls]) { + display: none; + height: 0; } + +img { + border-style: none; } + +svg:not(:root) { + overflow: hidden; } + +button, +input, +optgroup, +select, +textarea { + font-family: sans-serif; + font-size: 100%; + line-height: 1.15; + margin: 0; } + +button, +input { + overflow: visible; } + +button, +select { + text-transform: none; } + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; } + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; } + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; } + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal; } + +progress { + display: inline-block; + vertical-align: baseline; } + +textarea { + overflow: auto; } + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + padding: 0; } + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; } + +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; } + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; } + +details, +menu { + display: block; } + +summary { + display: list-item; } + +canvas { + display: inline-block; } + +template { + display: none; } + +[hidden] { + display: none; } + +@media print { + *, + *::before, + *::after, + p::first-letter, + div::first-letter, + blockquote::first-letter, + li::first-letter, + p::first-line, + div::first-line, + blockquote::first-line, + li::first-line { + text-shadow: none !important; + box-shadow: none !important; } + a, + a:visited { + text-decoration: underline; } + abbr[title]::after { + content: " (" attr(title) ")"; } + pre { + white-space: pre-wrap !important; } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; } + thead { + display: table-header-group; } + tr, + img { + page-break-inside: avoid; } + p, + h2, + h3 { + orphans: 3; + widows: 3; } + h2, + h3 { + page-break-after: avoid; } + .navbar { + display: none; } + .badge { + 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; } } + +html { + box-sizing: border-box; } + +*, +*::before, +*::after { + box-sizing: inherit; } + +@-ms-viewport { + width: device-width; } + +html { + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; } + +body { + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 1rem; + font-weight: normal; + line-height: 1.5; + color: #292b2c; + background-color: #fff; } + +[tabindex="-1"]:focus { + outline: none !important; } + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5rem; } + +p { + margin-top: 0; + margin-bottom: 1rem; } + +abbr[title], +abbr[data-original-title] { + cursor: help; } + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; } + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; } + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; } + +dt { + font-weight: bold; } + +dd { + margin-bottom: .5rem; + margin-left: 0; } + +blockquote { + margin: 0 0 1rem; } + +a { + color: #0275d8; + text-decoration: none; } + a:focus, a:hover { + color: #014c8c; + text-decoration: underline; } + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { + color: inherit; + text-decoration: none; } + a:not([href]):not([tabindex]):focus { + outline: 0; } + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; } + +figure { + margin: 0 0 1rem; } + +img { + vertical-align: middle; } + +[role="button"] { + cursor: pointer; } + +a, +area, +button, +[role="button"], +input, +label, +select, +summary, +textarea { + touch-action: manipulation; } + +table { + border-collapse: collapse; + background-color: transparent; } + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #636c72; + text-align: left; + caption-side: bottom; } + +th { + text-align: left; } + +label { + display: inline-block; + margin-bottom: .5rem; } + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; } + +input, +button, +select, +textarea { + line-height: inherit; } + +input[type="radio"]:disabled, +input[type="checkbox"]:disabled { + cursor: not-allowed; } + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; } + +textarea { + resize: vertical; } + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; } + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; } + +input[type="search"] { + -webkit-appearance: none; } + +output { + display: inline-block; } + +[hidden] { + display: none !important; } + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; } + +h1, .h1 { + font-size: 2.5rem; } + +h2, .h2 { + font-size: 2rem; } + +h3, .h3 { + font-size: 1.75rem; } + +h4, .h4 { + font-size: 1.5rem; } + +h5, .h5 { + font-size: 1.25rem; } + +h6, .h6 { + font-size: 1rem; } + +.lead { + font-size: 1.25rem; + font-weight: 300; } + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.1; } + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.1; } + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.1; } + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.1; } + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); } + +small, +.small { + font-size: 80%; + font-weight: normal; } + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; } + +.list-unstyled { + padding-left: 0; + list-style: none; } + +.list-inline { + padding-left: 0; + list-style: none; } + +.list-inline-item { + display: inline-block; } + .list-inline-item:not(:last-child) { + margin-right: 5px; } + +.initialism { + font-size: 90%; + text-transform: uppercase; } + +.blockquote { + padding: 0.5rem 1rem; + margin-bottom: 1rem; + font-size: 1.25rem; + border-left: 0.25rem solid #eceeef; } + +.blockquote-footer { + display: block; + font-size: 80%; + color: #636c72; } + .blockquote-footer::before { + content: "\2014 \00A0"; } + +.blockquote-reverse { + padding-right: 1rem; + padding-left: 0; + text-align: right; + border-right: 0.25rem solid #eceeef; + border-left: 0; } + +.blockquote-reverse .blockquote-footer::before { + content: ""; } + +.blockquote-reverse .blockquote-footer::after { + content: "\00A0 \2014"; } + +.img-fluid { + max-width: 100%; + height: auto; } + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 0.25rem; + transition: all 0.2s ease-in-out; + max-width: 100%; + height: auto; } + +.figure { + display: inline-block; } + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; } + +.figure-caption { + font-size: 90%; + color: #636c72; } + +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } + +code { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #bd4147; + background-color: #f7f7f9; + border-radius: 0.25rem; } + a > code { + padding: 0; + color: inherit; + background-color: inherit; } + +kbd { + padding: 0.2rem 0.4rem; + font-size: 90%; + color: #fff; + background-color: #292b2c; + border-radius: 0.2rem; } + kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; } + +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + font-size: 90%; + color: #292b2c; } + pre code { + padding: 0; + font-size: inherit; + color: inherit; + background-color: transparent; + border-radius: 0; } + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } + +.container { + position: relative; + margin-left: auto; + margin-right: auto; + padding-right: 15px; + padding-left: 15px; } + @media (min-width: 576px) { + .container { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 768px) { + .container { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 992px) { + .container { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 1200px) { + .container { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 576px) { + .container { + width: 540px; + max-width: 100%; } } + @media (min-width: 768px) { + .container { + width: 720px; + max-width: 100%; } } + @media (min-width: 992px) { + .container { + width: 960px; + max-width: 100%; } } + @media (min-width: 1200px) { + .container { + width: 1140px; + max-width: 100%; } } + +.container-fluid { + position: relative; + margin-left: auto; + margin-right: auto; + padding-right: 15px; + padding-left: 15px; } + @media (min-width: 576px) { + .container-fluid { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 768px) { + .container-fluid { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 992px) { + .container-fluid { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 1200px) { + .container-fluid { + padding-right: 15px; + padding-left: 15px; } } + +.row { + display: flex; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; } + @media (min-width: 576px) { + .row { + margin-right: -15px; + margin-left: -15px; } } + @media (min-width: 768px) { + .row { + margin-right: -15px; + margin-left: -15px; } } + @media (min-width: 992px) { + .row { + margin-right: -15px; + margin-left: -15px; } } + @media (min-width: 1200px) { + .row { + margin-right: -15px; + margin-left: -15px; } } + +.no-gutters { + margin-right: 0; + margin-left: 0; } + .no-gutters > .col, + .no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; } + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .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, .col-sm, .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, .col-md, .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, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; } + @media (min-width: 576px) { + .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .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, .col-sm, .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, .col-md, .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, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 768px) { + .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .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, .col-sm, .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, .col-md, .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, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 992px) { + .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .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, .col-sm, .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, .col-md, .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, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl { + padding-right: 15px; + padding-left: 15px; } } + @media (min-width: 1200px) { + .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .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, .col-sm, .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, .col-md, .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, .col-lg, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl { + padding-right: 15px; + padding-left: 15px; } } + +.col { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + +.col-auto { + flex: 0 0 auto; + width: auto; } + +.col-1 { + flex: 0 0 8.33333333%; + max-width: 8.33333333%; } + +.col-2 { + flex: 0 0 16.66666667%; + max-width: 16.66666667%; } + +.col-3 { + flex: 0 0 25%; + max-width: 25%; } + +.col-4 { + flex: 0 0 33.33333333%; + max-width: 33.33333333%; } + +.col-5 { + flex: 0 0 41.66666667%; + max-width: 41.66666667%; } + +.col-6 { + flex: 0 0 50%; + max-width: 50%; } + +.col-7 { + flex: 0 0 58.33333333%; + max-width: 58.33333333%; } + +.col-8 { + flex: 0 0 66.66666667%; + max-width: 66.66666667%; } + +.col-9 { + flex: 0 0 75%; + max-width: 75%; } + +.col-10 { + flex: 0 0 83.33333333%; + max-width: 83.33333333%; } + +.col-11 { + flex: 0 0 91.66666667%; + max-width: 91.66666667%; } + +.col-12 { + flex: 0 0 100%; + max-width: 100%; } + +.pull-0 { + right: auto; } + +.pull-1 { + right: 8.33333333%; } + +.pull-2 { + right: 16.66666667%; } + +.pull-3 { + right: 25%; } + +.pull-4 { + right: 33.33333333%; } + +.pull-5 { + right: 41.66666667%; } + +.pull-6 { + right: 50%; } + +.pull-7 { + right: 58.33333333%; } + +.pull-8 { + right: 66.66666667%; } + +.pull-9 { + right: 75%; } + +.pull-10 { + right: 83.33333333%; } + +.pull-11 { + right: 91.66666667%; } + +.pull-12 { + right: 100%; } + +.push-0 { + left: auto; } + +.push-1 { + left: 8.33333333%; } + +.push-2 { + left: 16.66666667%; } + +.push-3 { + left: 25%; } + +.push-4 { + left: 33.33333333%; } + +.push-5 { + left: 41.66666667%; } + +.push-6 { + left: 50%; } + +.push-7 { + left: 58.33333333%; } + +.push-8 { + left: 66.66666667%; } + +.push-9 { + left: 75%; } + +.push-10 { + left: 83.33333333%; } + +.push-11 { + left: 91.66666667%; } + +.push-12 { + left: 100%; } + +.offset-1 { + margin-left: 8.33333333%; } + +.offset-2 { + margin-left: 16.66666667%; } + +.offset-3 { + margin-left: 25%; } + +.offset-4 { + margin-left: 33.33333333%; } + +.offset-5 { + margin-left: 41.66666667%; } + +.offset-6 { + margin-left: 50%; } + +.offset-7 { + margin-left: 58.33333333%; } + +.offset-8 { + margin-left: 66.66666667%; } + +.offset-9 { + margin-left: 75%; } + +.offset-10 { + margin-left: 83.33333333%; } + +.offset-11 { + margin-left: 91.66666667%; } + +@media (min-width: 576px) { + .col-sm { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-sm-auto { + flex: 0 0 auto; + width: auto; } + .col-sm-1 { + flex: 0 0 8.33333333%; + max-width: 8.33333333%; } + .col-sm-2 { + flex: 0 0 16.66666667%; + max-width: 16.66666667%; } + .col-sm-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-sm-4 { + flex: 0 0 33.33333333%; + max-width: 33.33333333%; } + .col-sm-5 { + flex: 0 0 41.66666667%; + max-width: 41.66666667%; } + .col-sm-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-sm-7 { + flex: 0 0 58.33333333%; + max-width: 58.33333333%; } + .col-sm-8 { + flex: 0 0 66.66666667%; + max-width: 66.66666667%; } + .col-sm-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-sm-10 { + flex: 0 0 83.33333333%; + max-width: 83.33333333%; } + .col-sm-11 { + flex: 0 0 91.66666667%; + max-width: 91.66666667%; } + .col-sm-12 { + flex: 0 0 100%; + max-width: 100%; } + .pull-sm-0 { + right: auto; } + .pull-sm-1 { + right: 8.33333333%; } + .pull-sm-2 { + right: 16.66666667%; } + .pull-sm-3 { + right: 25%; } + .pull-sm-4 { + right: 33.33333333%; } + .pull-sm-5 { + right: 41.66666667%; } + .pull-sm-6 { + right: 50%; } + .pull-sm-7 { + right: 58.33333333%; } + .pull-sm-8 { + right: 66.66666667%; } + .pull-sm-9 { + right: 75%; } + .pull-sm-10 { + right: 83.33333333%; } + .pull-sm-11 { + right: 91.66666667%; } + .pull-sm-12 { + right: 100%; } + .push-sm-0 { + left: auto; } + .push-sm-1 { + left: 8.33333333%; } + .push-sm-2 { + left: 16.66666667%; } + .push-sm-3 { + left: 25%; } + .push-sm-4 { + left: 33.33333333%; } + .push-sm-5 { + left: 41.66666667%; } + .push-sm-6 { + left: 50%; } + .push-sm-7 { + left: 58.33333333%; } + .push-sm-8 { + left: 66.66666667%; } + .push-sm-9 { + left: 75%; } + .push-sm-10 { + left: 83.33333333%; } + .push-sm-11 { + left: 91.66666667%; } + .push-sm-12 { + left: 100%; } + .offset-sm-0 { + margin-left: 0%; } + .offset-sm-1 { + margin-left: 8.33333333%; } + .offset-sm-2 { + margin-left: 16.66666667%; } + .offset-sm-3 { + margin-left: 25%; } + .offset-sm-4 { + margin-left: 33.33333333%; } + .offset-sm-5 { + margin-left: 41.66666667%; } + .offset-sm-6 { + margin-left: 50%; } + .offset-sm-7 { + margin-left: 58.33333333%; } + .offset-sm-8 { + margin-left: 66.66666667%; } + .offset-sm-9 { + margin-left: 75%; } + .offset-sm-10 { + margin-left: 83.33333333%; } + .offset-sm-11 { + margin-left: 91.66666667%; } } + +@media (min-width: 768px) { + .col-md { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-md-auto { + flex: 0 0 auto; + width: auto; } + .col-md-1 { + flex: 0 0 8.33333333%; + max-width: 8.33333333%; } + .col-md-2 { + flex: 0 0 16.66666667%; + max-width: 16.66666667%; } + .col-md-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-md-4 { + flex: 0 0 33.33333333%; + max-width: 33.33333333%; } + .col-md-5 { + flex: 0 0 41.66666667%; + max-width: 41.66666667%; } + .col-md-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-md-7 { + flex: 0 0 58.33333333%; + max-width: 58.33333333%; } + .col-md-8 { + flex: 0 0 66.66666667%; + max-width: 66.66666667%; } + .col-md-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-md-10 { + flex: 0 0 83.33333333%; + max-width: 83.33333333%; } + .col-md-11 { + flex: 0 0 91.66666667%; + max-width: 91.66666667%; } + .col-md-12 { + flex: 0 0 100%; + max-width: 100%; } + .pull-md-0 { + right: auto; } + .pull-md-1 { + right: 8.33333333%; } + .pull-md-2 { + right: 16.66666667%; } + .pull-md-3 { + right: 25%; } + .pull-md-4 { + right: 33.33333333%; } + .pull-md-5 { + right: 41.66666667%; } + .pull-md-6 { + right: 50%; } + .pull-md-7 { + right: 58.33333333%; } + .pull-md-8 { + right: 66.66666667%; } + .pull-md-9 { + right: 75%; } + .pull-md-10 { + right: 83.33333333%; } + .pull-md-11 { + right: 91.66666667%; } + .pull-md-12 { + right: 100%; } + .push-md-0 { + left: auto; } + .push-md-1 { + left: 8.33333333%; } + .push-md-2 { + left: 16.66666667%; } + .push-md-3 { + left: 25%; } + .push-md-4 { + left: 33.33333333%; } + .push-md-5 { + left: 41.66666667%; } + .push-md-6 { + left: 50%; } + .push-md-7 { + left: 58.33333333%; } + .push-md-8 { + left: 66.66666667%; } + .push-md-9 { + left: 75%; } + .push-md-10 { + left: 83.33333333%; } + .push-md-11 { + left: 91.66666667%; } + .push-md-12 { + left: 100%; } + .offset-md-0 { + margin-left: 0%; } + .offset-md-1 { + margin-left: 8.33333333%; } + .offset-md-2 { + margin-left: 16.66666667%; } + .offset-md-3 { + margin-left: 25%; } + .offset-md-4 { + margin-left: 33.33333333%; } + .offset-md-5 { + margin-left: 41.66666667%; } + .offset-md-6 { + margin-left: 50%; } + .offset-md-7 { + margin-left: 58.33333333%; } + .offset-md-8 { + margin-left: 66.66666667%; } + .offset-md-9 { + margin-left: 75%; } + .offset-md-10 { + margin-left: 83.33333333%; } + .offset-md-11 { + margin-left: 91.66666667%; } } + +@media (min-width: 992px) { + .col-lg { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-lg-auto { + flex: 0 0 auto; + width: auto; } + .col-lg-1 { + flex: 0 0 8.33333333%; + max-width: 8.33333333%; } + .col-lg-2 { + flex: 0 0 16.66666667%; + max-width: 16.66666667%; } + .col-lg-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-lg-4 { + flex: 0 0 33.33333333%; + max-width: 33.33333333%; } + .col-lg-5 { + flex: 0 0 41.66666667%; + max-width: 41.66666667%; } + .col-lg-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-lg-7 { + flex: 0 0 58.33333333%; + max-width: 58.33333333%; } + .col-lg-8 { + flex: 0 0 66.66666667%; + max-width: 66.66666667%; } + .col-lg-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-lg-10 { + flex: 0 0 83.33333333%; + max-width: 83.33333333%; } + .col-lg-11 { + flex: 0 0 91.66666667%; + max-width: 91.66666667%; } + .col-lg-12 { + flex: 0 0 100%; + max-width: 100%; } + .pull-lg-0 { + right: auto; } + .pull-lg-1 { + right: 8.33333333%; } + .pull-lg-2 { + right: 16.66666667%; } + .pull-lg-3 { + right: 25%; } + .pull-lg-4 { + right: 33.33333333%; } + .pull-lg-5 { + right: 41.66666667%; } + .pull-lg-6 { + right: 50%; } + .pull-lg-7 { + right: 58.33333333%; } + .pull-lg-8 { + right: 66.66666667%; } + .pull-lg-9 { + right: 75%; } + .pull-lg-10 { + right: 83.33333333%; } + .pull-lg-11 { + right: 91.66666667%; } + .pull-lg-12 { + right: 100%; } + .push-lg-0 { + left: auto; } + .push-lg-1 { + left: 8.33333333%; } + .push-lg-2 { + left: 16.66666667%; } + .push-lg-3 { + left: 25%; } + .push-lg-4 { + left: 33.33333333%; } + .push-lg-5 { + left: 41.66666667%; } + .push-lg-6 { + left: 50%; } + .push-lg-7 { + left: 58.33333333%; } + .push-lg-8 { + left: 66.66666667%; } + .push-lg-9 { + left: 75%; } + .push-lg-10 { + left: 83.33333333%; } + .push-lg-11 { + left: 91.66666667%; } + .push-lg-12 { + left: 100%; } + .offset-lg-0 { + margin-left: 0%; } + .offset-lg-1 { + margin-left: 8.33333333%; } + .offset-lg-2 { + margin-left: 16.66666667%; } + .offset-lg-3 { + margin-left: 25%; } + .offset-lg-4 { + margin-left: 33.33333333%; } + .offset-lg-5 { + margin-left: 41.66666667%; } + .offset-lg-6 { + margin-left: 50%; } + .offset-lg-7 { + margin-left: 58.33333333%; } + .offset-lg-8 { + margin-left: 66.66666667%; } + .offset-lg-9 { + margin-left: 75%; } + .offset-lg-10 { + margin-left: 83.33333333%; } + .offset-lg-11 { + margin-left: 91.66666667%; } } + +@media (min-width: 1200px) { + .col-xl { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; } + .col-xl-auto { + flex: 0 0 auto; + width: auto; } + .col-xl-1 { + flex: 0 0 8.33333333%; + max-width: 8.33333333%; } + .col-xl-2 { + flex: 0 0 16.66666667%; + max-width: 16.66666667%; } + .col-xl-3 { + flex: 0 0 25%; + max-width: 25%; } + .col-xl-4 { + flex: 0 0 33.33333333%; + max-width: 33.33333333%; } + .col-xl-5 { + flex: 0 0 41.66666667%; + max-width: 41.66666667%; } + .col-xl-6 { + flex: 0 0 50%; + max-width: 50%; } + .col-xl-7 { + flex: 0 0 58.33333333%; + max-width: 58.33333333%; } + .col-xl-8 { + flex: 0 0 66.66666667%; + max-width: 66.66666667%; } + .col-xl-9 { + flex: 0 0 75%; + max-width: 75%; } + .col-xl-10 { + flex: 0 0 83.33333333%; + max-width: 83.33333333%; } + .col-xl-11 { + flex: 0 0 91.66666667%; + max-width: 91.66666667%; } + .col-xl-12 { + flex: 0 0 100%; + max-width: 100%; } + .pull-xl-0 { + right: auto; } + .pull-xl-1 { + right: 8.33333333%; } + .pull-xl-2 { + right: 16.66666667%; } + .pull-xl-3 { + right: 25%; } + .pull-xl-4 { + right: 33.33333333%; } + .pull-xl-5 { + right: 41.66666667%; } + .pull-xl-6 { + right: 50%; } + .pull-xl-7 { + right: 58.33333333%; } + .pull-xl-8 { + right: 66.66666667%; } + .pull-xl-9 { + right: 75%; } + .pull-xl-10 { + right: 83.33333333%; } + .pull-xl-11 { + right: 91.66666667%; } + .pull-xl-12 { + right: 100%; } + .push-xl-0 { + left: auto; } + .push-xl-1 { + left: 8.33333333%; } + .push-xl-2 { + left: 16.66666667%; } + .push-xl-3 { + left: 25%; } + .push-xl-4 { + left: 33.33333333%; } + .push-xl-5 { + left: 41.66666667%; } + .push-xl-6 { + left: 50%; } + .push-xl-7 { + left: 58.33333333%; } + .push-xl-8 { + left: 66.66666667%; } + .push-xl-9 { + left: 75%; } + .push-xl-10 { + left: 83.33333333%; } + .push-xl-11 { + left: 91.66666667%; } + .push-xl-12 { + left: 100%; } + .offset-xl-0 { + margin-left: 0%; } + .offset-xl-1 { + margin-left: 8.33333333%; } + .offset-xl-2 { + margin-left: 16.66666667%; } + .offset-xl-3 { + margin-left: 25%; } + .offset-xl-4 { + margin-left: 33.33333333%; } + .offset-xl-5 { + margin-left: 41.66666667%; } + .offset-xl-6 { + margin-left: 50%; } + .offset-xl-7 { + margin-left: 58.33333333%; } + .offset-xl-8 { + margin-left: 66.66666667%; } + .offset-xl-9 { + margin-left: 75%; } + .offset-xl-10 { + margin-left: 83.33333333%; } + .offset-xl-11 { + margin-left: 91.66666667%; } } + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; } + .table th, + .table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #eceeef; } + .table thead th { + vertical-align: bottom; + border-bottom: 2px solid #eceeef; } + .table tbody + tbody { + border-top: 2px solid #eceeef; } + .table .table { + background-color: #fff; } + +.table-sm th, +.table-sm td { + padding: 0.3rem; } + +.table-bordered { + border: 1px solid #eceeef; } + .table-bordered th, + .table-bordered td { + border: 1px solid #eceeef; } + .table-bordered thead th, + .table-bordered thead td { + border-bottom-width: 2px; } + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); } + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); } + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); } + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); } + .table-hover .table-active:hover > td, + .table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); } + +.table-success, +.table-success > th, +.table-success > td { + background-color: #dff0d8; } + +.table-hover .table-success:hover { + background-color: #d0e9c6; } + .table-hover .table-success:hover > td, + .table-hover .table-success:hover > th { + background-color: #d0e9c6; } + +.table-info, +.table-info > th, +.table-info > td { + background-color: #d9edf7; } + +.table-hover .table-info:hover { + background-color: #c4e3f3; } + .table-hover .table-info:hover > td, + .table-hover .table-info:hover > th { + background-color: #c4e3f3; } + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #fcf8e3; } + +.table-hover .table-warning:hover { + background-color: #faf2cc; } + .table-hover .table-warning:hover > td, + .table-hover .table-warning:hover > th { + background-color: #faf2cc; } + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f2dede; } + +.table-hover .table-danger:hover { + background-color: #ebcccc; } + .table-hover .table-danger:hover > td, + .table-hover .table-danger:hover > th { + background-color: #ebcccc; } + +.thead-inverse th { + color: #fff; + background-color: #292b2c; } + +.thead-default th { + color: #464a4c; + background-color: #eceeef; } + +.table-inverse { + color: #fff; + background-color: #292b2c; } + .table-inverse th, + .table-inverse td, + .table-inverse thead th { + border-color: #fff; } + .table-inverse.table-bordered { + border: 0; } + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .table-responsive.table-bordered { + border: 0; } + +.form-control { + display: block; + width: 100%; + padding: 0.5rem 0.75rem; + font-size: 1rem; + line-height: 1.25; + color: #464a4c; + background-color: #fff; + background-image: none; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; } + .form-control::-ms-expand { + background-color: transparent; + border: 0; } + .form-control:focus { + color: #464a4c; + background-color: #fff; + border-color: #5cb3fd; + outline: none; } + .form-control::placeholder { + color: #636c72; + opacity: 1; } + .form-control:disabled, .form-control[readonly] { + background-color: #eceeef; + opacity: 1; } + .form-control:disabled { + cursor: not-allowed; } + +select.form-control:not([size]):not([multiple]) { + height: calc(2.25rem + 2px); } + +select.form-control:focus::-ms-value { + color: #464a4c; + background-color: #fff; } + +.form-control-file, +.form-control-range { + display: block; } + +.col-form-label { + padding-top: calc(0.5rem - 1px * 2); + padding-bottom: calc(0.5rem - 1px * 2); + margin-bottom: 0; } + +.col-form-label-lg { + padding-top: calc(0.75rem - 1px * 2); + padding-bottom: calc(0.75rem - 1px * 2); + font-size: 1.25rem; } + +.col-form-label-sm { + padding-top: calc(0.25rem - 1px * 2); + padding-bottom: calc(0.25rem - 1px * 2); + font-size: 0.875rem; } + +.col-form-legend { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + margin-bottom: 0; + font-size: 1rem; } + +.form-control-static { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + margin-bottom: 0; + line-height: 1.25; + border: solid transparent; + border-width: 1px 0; } + .form-control-static.form-control-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, .form-control-static.form-control-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 { + padding-right: 0; + padding-left: 0; } + +.form-control-sm, .input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; } + +select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > select.input-group-addon:not([size]):not([multiple]), +.input-group-sm > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: 1.8125rem; } + +.form-control-lg, .input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + border-radius: 0.3rem; } + +select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > select.input-group-addon:not([size]):not([multiple]), +.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) { + height: 3.16666667rem; } + +.form-group { + margin-bottom: 1rem; } + +.form-text { + display: block; + margin-top: 0.25rem; } + +.form-check { + position: relative; + display: block; + margin-bottom: 0.5rem; } + .form-check.disabled .form-check-label { + color: #636c72; + cursor: not-allowed; } + +.form-check-label { + padding-left: 1.25rem; + margin-bottom: 0; + cursor: pointer; } + +.form-check-input { + position: absolute; + margin-top: 0.25rem; + margin-left: -1.25rem; } + .form-check-input:only-child { + position: static; } + +.form-check-inline { + display: inline-block; } + .form-check-inline .form-check-label { + vertical-align: middle; } + .form-check-inline + .form-check-inline { + margin-left: 0.75rem; } + +.form-control-feedback { + margin-top: 0.25rem; } + +.form-control-success, +.form-control-warning, +.form-control-danger { + padding-right: 2.25rem; + background-repeat: no-repeat; + background-position: center right 0.5625rem; + background-size: 1.125rem 1.125rem; } + +.has-success .form-control-feedback, +.has-success .form-control-label, +.has-success .col-form-label, +.has-success .form-check-label, +.has-success .custom-control { + color: #5cb85c; } + +.has-success .form-control { + border-color: #5cb85c; } + +.has-success .input-group-addon { + color: #5cb85c; + border-color: #5cb85c; + background-color: #eaf6ea; } + +.has-success .form-control-success { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E"); } + +.has-warning .form-control-feedback, +.has-warning .form-control-label, +.has-warning .col-form-label, +.has-warning .form-check-label, +.has-warning .custom-control { + color: #f0ad4e; } + +.has-warning .form-control { + border-color: #f0ad4e; } + +.has-warning .input-group-addon { + color: #f0ad4e; + border-color: #f0ad4e; + background-color: white; } + +.has-warning .form-control-warning { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23f0ad4e' d='M4.4 5.324h-.8v-2.46h.8zm0 1.42h-.8V5.89h.8zM3.76.63L.04 7.075c-.115.2.016.425.26.426h7.397c.242 0 .372-.226.258-.426C6.726 4.924 5.47 2.79 4.253.63c-.113-.174-.39-.174-.494 0z'/%3E%3C/svg%3E"); } + +.has-danger .form-control-feedback, +.has-danger .form-control-label, +.has-danger .col-form-label, +.has-danger .form-check-label, +.has-danger .custom-control { + color: #d9534f; } + +.has-danger .form-control { + border-color: #d9534f; } + +.has-danger .input-group-addon { + color: #d9534f; + border-color: #d9534f; + background-color: #fdf7f7; } + +.has-danger .form-control-danger { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E"); } + +.form-inline { + display: flex; + flex-flow: row wrap; + align-items: center; } + .form-inline .form-check { + width: 100%; } + @media (min-width: 576px) { + .form-inline label { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0; } + .form-inline .form-group { + display: flex; + flex: 0 0 auto; + flex-flow: row wrap; + align-items: center; + margin-bottom: 0; } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; } + .form-inline .form-control-static { + display: inline-block; } + .form-inline .input-group { + width: auto; } + .form-inline .form-control-label { + margin-bottom: 0; + vertical-align: middle; } + .form-inline .form-check { + display: flex; + align-items: center; + justify-content: center; + width: auto; + margin-top: 0; + margin-bottom: 0; } + .form-inline .form-check-label { + padding-left: 0; } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; } + .form-inline .custom-control { + display: flex; + align-items: center; + justify-content: center; + padding-left: 0; } + .form-inline .custom-control-indicator { + position: static; + display: inline-block; + margin-right: 0.25rem; + vertical-align: text-bottom; } + .form-inline .has-feedback .form-control-feedback { + top: 0; } } + +.btn { + display: inline-block; + font-weight: normal; + line-height: 1.25; + text-align: center; + white-space: nowrap; + vertical-align: middle; + user-select: none; + border: 1px solid transparent; + padding: 0.5rem 1rem; + font-size: 1rem; + border-radius: 0.25rem; + transition: all 0.2s ease-in-out; } + .btn:focus, .btn:hover { + text-decoration: none; } + .btn:focus, .btn.focus { + outline: 0; + box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.25); } + .btn.disabled, .btn:disabled { + cursor: not-allowed; + opacity: .65; } + .btn:active, .btn.active { + background-image: none; } + +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; } + +.btn-primary { + color: #fff; + background-color: #0275d8; + border-color: #0275d8; } + .btn-primary:hover { + color: #fff; + background-color: #025aa5; + border-color: #01549b; } + .btn-primary:focus, .btn-primary.focus { + box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5); } + .btn-primary.disabled, .btn-primary:disabled { + background-color: #0275d8; + border-color: #0275d8; } + .btn-primary:active, .btn-primary.active, + .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #025aa5; + background-image: none; + border-color: #01549b; } + +.btn-secondary { + color: #292b2c; + background-color: #fff; + border-color: #ccc; } + .btn-secondary:hover { + color: #292b2c; + background-color: #e6e5e5; + border-color: #adadad; } + .btn-secondary:focus, .btn-secondary.focus { + box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5); } + .btn-secondary.disabled, .btn-secondary:disabled { + background-color: #fff; + border-color: #ccc; } + .btn-secondary:active, .btn-secondary.active, + .show > .btn-secondary.dropdown-toggle { + color: #292b2c; + background-color: #e6e5e5; + background-image: none; + border-color: #adadad; } + +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #5bc0de; } + .btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #2aabd2; } + .btn-info:focus, .btn-info.focus { + box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5); } + .btn-info.disabled, .btn-info:disabled { + background-color: #5bc0de; + border-color: #5bc0de; } + .btn-info:active, .btn-info.active, + .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #31b0d5; + background-image: none; + border-color: #2aabd2; } + +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #5cb85c; } + .btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #419641; } + .btn-success:focus, .btn-success.focus { + box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5); } + .btn-success.disabled, .btn-success:disabled { + background-color: #5cb85c; + border-color: #5cb85c; } + .btn-success:active, .btn-success.active, + .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #449d44; + background-image: none; + border-color: #419641; } + +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #f0ad4e; } + .btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #eb9316; } + .btn-warning:focus, .btn-warning.focus { + box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5); } + .btn-warning.disabled, .btn-warning:disabled { + background-color: #f0ad4e; + border-color: #f0ad4e; } + .btn-warning:active, .btn-warning.active, + .show > .btn-warning.dropdown-toggle { + color: #fff; + background-color: #ec971f; + background-image: none; + border-color: #eb9316; } + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d9534f; } + .btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #c12e2a; } + .btn-danger:focus, .btn-danger.focus { + box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5); } + .btn-danger.disabled, .btn-danger:disabled { + background-color: #d9534f; + border-color: #d9534f; } + .btn-danger:active, .btn-danger.active, + .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #c9302c; + background-image: none; + border-color: #c12e2a; } + +.btn-outline-primary { + color: #0275d8; + background-image: none; + background-color: transparent; + border-color: #0275d8; } + .btn-outline-primary:hover { + color: #fff; + background-color: #0275d8; + border-color: #0275d8; } + .btn-outline-primary:focus, .btn-outline-primary.focus { + box-shadow: 0 0 0 2px rgba(2, 117, 216, 0.5); } + .btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #0275d8; + background-color: transparent; } + .btn-outline-primary:active, .btn-outline-primary.active, + .show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #0275d8; + border-color: #0275d8; } + +.btn-outline-secondary { + color: #ccc; + background-image: none; + background-color: transparent; + border-color: #ccc; } + .btn-outline-secondary:hover { + color: #fff; + background-color: #ccc; + border-color: #ccc; } + .btn-outline-secondary:focus, .btn-outline-secondary.focus { + box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.5); } + .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #ccc; + background-color: transparent; } + .btn-outline-secondary:active, .btn-outline-secondary.active, + .show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #ccc; + border-color: #ccc; } + +.btn-outline-info { + color: #5bc0de; + background-image: none; + background-color: transparent; + border-color: #5bc0de; } + .btn-outline-info:hover { + color: #fff; + background-color: #5bc0de; + border-color: #5bc0de; } + .btn-outline-info:focus, .btn-outline-info.focus { + box-shadow: 0 0 0 2px rgba(91, 192, 222, 0.5); } + .btn-outline-info.disabled, .btn-outline-info:disabled { + color: #5bc0de; + background-color: transparent; } + .btn-outline-info:active, .btn-outline-info.active, + .show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #5bc0de; + border-color: #5bc0de; } + +.btn-outline-success { + color: #5cb85c; + background-image: none; + background-color: transparent; + border-color: #5cb85c; } + .btn-outline-success:hover { + color: #fff; + background-color: #5cb85c; + border-color: #5cb85c; } + .btn-outline-success:focus, .btn-outline-success.focus { + box-shadow: 0 0 0 2px rgba(92, 184, 92, 0.5); } + .btn-outline-success.disabled, .btn-outline-success:disabled { + color: #5cb85c; + background-color: transparent; } + .btn-outline-success:active, .btn-outline-success.active, + .show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #5cb85c; + border-color: #5cb85c; } + +.btn-outline-warning { + color: #f0ad4e; + background-image: none; + background-color: transparent; + border-color: #f0ad4e; } + .btn-outline-warning:hover { + color: #fff; + background-color: #f0ad4e; + border-color: #f0ad4e; } + .btn-outline-warning:focus, .btn-outline-warning.focus { + box-shadow: 0 0 0 2px rgba(240, 173, 78, 0.5); } + .btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #f0ad4e; + background-color: transparent; } + .btn-outline-warning:active, .btn-outline-warning.active, + .show > .btn-outline-warning.dropdown-toggle { + color: #fff; + background-color: #f0ad4e; + border-color: #f0ad4e; } + +.btn-outline-danger { + color: #d9534f; + background-image: none; + background-color: transparent; + border-color: #d9534f; } + .btn-outline-danger:hover { + color: #fff; + background-color: #d9534f; + border-color: #d9534f; } + .btn-outline-danger:focus, .btn-outline-danger.focus { + box-shadow: 0 0 0 2px rgba(217, 83, 79, 0.5); } + .btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #d9534f; + background-color: transparent; } + .btn-outline-danger:active, .btn-outline-danger.active, + .show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #d9534f; + border-color: #d9534f; } + +.btn-link { + font-weight: normal; + color: #0275d8; + border-radius: 0; } + .btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled { + background-color: transparent; } + .btn-link, .btn-link:focus, .btn-link:active { + border-color: transparent; } + .btn-link:hover { + border-color: transparent; } + .btn-link:focus, .btn-link:hover { + color: #014c8c; + text-decoration: underline; + background-color: transparent; } + .btn-link:disabled { + color: #636c72; } + .btn-link:disabled:focus, .btn-link:disabled:hover { + text-decoration: none; } + +.btn-lg, .btn-group-lg > .btn { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + border-radius: 0.3rem; } + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; } + +.btn-block { + display: block; + width: 100%; } + +.btn-block + .btn-block { + margin-top: 0.5rem; } + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; } + +.fade { + opacity: 0; + transition: opacity 0.15s linear; } + .fade.show { + opacity: 1; } + +.collapse { + display: none; } + .collapse.show { + display: block; } + +tr.collapse.show { + display: table-row; } + +tbody.collapse.show { + display: table-row-group; } + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; } + +.dropup, +.dropdown { + position: relative; } + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.3em; + vertical-align: middle; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-left: 0.3em solid transparent; } + +.dropdown-toggle:focus { + outline: 0; } + +.dropup .dropdown-toggle::after { + border-top: 0; + border-bottom: 0.3em solid; } + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #292b2c; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; } + +.dropdown-divider { + height: 1px; + margin: 0.5rem 0; + overflow: hidden; + background-color: #eceeef; } + +.dropdown-item { + display: block; + width: 100%; + padding: 3px 1.5rem; + clear: both; + font-weight: normal; + color: #292b2c; + text-align: inherit; + white-space: nowrap; + background: none; + border: 0; } + .dropdown-item:focus, .dropdown-item:hover { + color: #1d1e1f; + text-decoration: none; + background-color: #f7f7f9; } + .dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #0275d8; } + .dropdown-item.disabled, .dropdown-item:disabled { + color: #636c72; + cursor: not-allowed; + background-color: transparent; } + +.show > .dropdown-menu { + display: block; } + +.show > a { + outline: 0; } + +.dropdown-menu-right { + right: 0; + left: auto; } + +.dropdown-menu-left { + right: auto; + left: 0; } + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #636c72; + white-space: nowrap; } + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; } + +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 0.125rem; } + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; } + .btn-group > .btn, + .btn-group-vertical > .btn { + position: relative; + flex: 0 1 auto; } + .btn-group > .btn:hover, + .btn-group-vertical > .btn:hover { + z-index: 2; } + .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, + .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, + .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-left: -1px; } + +.btn-toolbar { + display: flex; + justify-content: flex-start; } + .btn-toolbar .input-group { + width: auto; } + +.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 + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; } + .btn + .dropdown-toggle-split::after { + margin-left: 0; } + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; } + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 1.125rem; + padding-left: 1.125rem; } + +.btn-group-vertical { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; } + .btn-group-vertical .btn, + .btn-group-vertical .btn-group { + width: 100%; } + .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-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + +.btn-group-vertical > .btn:last-child:not(:first-child) { + 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; } + +[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: flex; + width: 100%; } + .input-group .form-control { + position: relative; + z-index: 2; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; } + .input-group .form-control:focus, .input-group .form-control:active, .input-group .form-control:hover { + z-index: 3; } + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: flex; + flex-direction: column; + justify-content: center; } + .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 { + white-space: nowrap; + vertical-align: middle; } + +.input-group-addon { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: normal; + line-height: 1.25; + color: #464a4c; + text-align: center; + background-color: #eceeef; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; } + .input-group-addon.form-control-sm, + .input-group-sm > .input-group-addon, + .input-group-sm > .input-group-btn > .input-group-addon.btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; } + .input-group-addon.form-control-lg, + .input-group-lg > .input-group-addon, + .input-group-lg > .input-group-btn > .input-group-addon.btn { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + border-radius: 0.3rem; } + .input-group-addon input[type="radio"], + .input-group-addon input[type="checkbox"] { + margin-top: 0; } + +.input-group .form-control:not(:last-child), +.input-group-addon:not(:last-child), +.input-group-btn:not(:last-child) > .btn, +.input-group-btn:not(:last-child) > .btn-group > .btn, +.input-group-btn:not(:last-child) > .dropdown-toggle, +.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } + +.input-group-addon:not(:last-child) { + border-right: 0; } + +.input-group .form-control:not(:first-child), +.input-group-addon:not(:first-child), +.input-group-btn:not(:first-child) > .btn, +.input-group-btn:not(:first-child) > .btn-group > .btn, +.input-group-btn:not(:first-child) > .dropdown-toggle, +.input-group-btn:not(:last-child) > .btn:not(:first-child), +.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + +.form-control + .input-group-addon:not(:first-child) { + border-left: 0; } + +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; } + .input-group-btn > .btn { + position: relative; + flex: 1; } + .input-group-btn > .btn + .btn { + margin-left: -1px; } + .input-group-btn > .btn:focus, .input-group-btn > .btn:active, .input-group-btn > .btn:hover { + z-index: 3; } + .input-group-btn:not(:last-child) > .btn, + .input-group-btn:not(:last-child) > .btn-group { + margin-right: -1px; } + .input-group-btn:not(:first-child) > .btn, + .input-group-btn:not(:first-child) > .btn-group { + z-index: 2; + margin-left: -1px; } + .input-group-btn:not(:first-child) > .btn:focus, .input-group-btn:not(:first-child) > .btn:active, .input-group-btn:not(:first-child) > .btn:hover, + .input-group-btn:not(:first-child) > .btn-group:focus, + .input-group-btn:not(:first-child) > .btn-group:active, + .input-group-btn:not(:first-child) > .btn-group:hover { + z-index: 3; } + +.custom-control { + position: relative; + display: inline-flex; + min-height: 1.5rem; + padding-left: 1.5rem; + margin-right: 1rem; + cursor: pointer; } + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; } + .custom-control-input:checked ~ .custom-control-indicator { + color: #fff; + background-color: #0275d8; } + .custom-control-input:focus ~ .custom-control-indicator { + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #0275d8; } + .custom-control-input:active ~ .custom-control-indicator { + color: #fff; + background-color: #8fcafe; } + .custom-control-input:disabled ~ .custom-control-indicator { + cursor: not-allowed; + background-color: #eceeef; } + .custom-control-input:disabled ~ .custom-control-description { + color: #636c72; + cursor: not-allowed; } + +.custom-control-indicator { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + user-select: none; + background-color: #ddd; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; } + +.custom-checkbox .custom-control-indicator { + border-radius: 0.25rem; } + +.custom-checkbox .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); } + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-indicator { + background-color: #0275d8; + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); } + +.custom-radio .custom-control-indicator { + border-radius: 50%; } + +.custom-radio .custom-control-input:checked ~ .custom-control-indicator { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); } + +.custom-controls-stacked { + display: flex; + flex-direction: column; } + .custom-controls-stacked .custom-control { + margin-bottom: 0.25rem; } + .custom-controls-stacked .custom-control + .custom-control { + margin-left: 0; } + +.custom-select { + display: inline-block; + max-width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.25; + color: #464a4c; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; + -moz-appearance: none; + -webkit-appearance: none; } + .custom-select:focus { + border-color: #5cb3fd; + outline: none; } + .custom-select:focus::-ms-value { + color: #464a4c; + background-color: #fff; } + .custom-select:disabled { + color: #636c72; + cursor: not-allowed; + background-color: #eceeef; } + .custom-select::-ms-expand { + opacity: 0; } + +.custom-select-sm { + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; } + +.custom-file { + position: relative; + display: inline-block; + max-width: 100%; + height: 2.5rem; + margin-bottom: 0; + cursor: pointer; } + +.custom-file-input { + min-width: 14rem; + max-width: 100%; + height: 2.5rem; + margin: 0; + filter: alpha(opacity=0); + opacity: 0; } + +.custom-file-control { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 5; + height: 2.5rem; + padding: 0.5rem 1rem; + line-height: 1.5; + color: #464a4c; + pointer-events: none; + user-select: none; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; } + .custom-file-control:lang(en)::after { + content: "Choose file..."; } + .custom-file-control::before { + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + z-index: 6; + display: block; + height: 2.5rem; + padding: 0.5rem 1rem; + line-height: 1.5; + color: #464a4c; + background-color: #eceeef; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0 0.25rem 0.25rem 0; } + .custom-file-control:lang(en)::before { + content: "Browse"; } + +.nav { + display: flex; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + +.nav-link { + display: block; + padding: 0.5em 1em; } + .nav-link:focus, .nav-link:hover { + text-decoration: none; } + .nav-link.disabled { + color: #636c72; + cursor: not-allowed; } + +.nav-tabs { + border-bottom: 1px solid #ddd; } + .nav-tabs .nav-item { + margin-bottom: -1px; } + .nav-tabs .nav-link { + border: 1px solid transparent; + border-top-right-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + .nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #eceeef #eceeef #ddd; } + .nav-tabs .nav-link.disabled { + color: #636c72; + background-color: transparent; + border-color: transparent; } + .nav-tabs .nav-link.active, + .nav-tabs .nav-item.show .nav-link { + color: #464a4c; + background-color: #fff; + border-color: #ddd #ddd #fff; } + .nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; } + +.nav-pills .nav-link { + border-radius: 0.25rem; } + +.nav-pills .nav-link.active, +.nav-pills .nav-item.show .nav-link { + color: #fff; + cursor: default; + background-color: #0275d8; } + +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; } + +.nav-justified .nav-item { + flex: 1 1 100%; + text-align: center; } + +.tab-content > .tab-pane { + display: none; } + +.tab-content > .active { + display: block; } + +.navbar { + position: relative; + display: flex; + flex-direction: column; + padding: 0.5rem 1rem; } + +.navbar-brand { + display: inline-block; + padding-top: .25rem; + padding-bottom: .25rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; } + .navbar-brand:focus, .navbar-brand:hover { + text-decoration: none; } + +.navbar-nav { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; } + .navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; } + +.navbar-text { + display: inline-block; + padding-top: .425rem; + padding-bottom: .425rem; } + +.navbar-toggler { + align-self: flex-start; + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; } + .navbar-toggler:focus, .navbar-toggler:hover { + text-decoration: none; } + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; } + +.navbar-toggler-left { + position: absolute; + left: 1rem; } + +.navbar-toggler-right { + position: absolute; + right: 1rem; } + +@media (max-width: 575px) { + .navbar-toggleable .navbar-nav .dropdown-menu { + position: static; + float: none; } + .navbar-toggleable > .container { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 576px) { + .navbar-toggleable { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable .navbar-nav { + flex-direction: row; } + .navbar-toggleable .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; } + .navbar-toggleable > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable .navbar-collapse { + display: flex !important; + width: 100%; } + .navbar-toggleable .navbar-toggler { + display: none; } } + +@media (max-width: 767px) { + .navbar-toggleable-sm .navbar-nav .dropdown-menu { + position: static; + float: none; } + .navbar-toggleable-sm > .container { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 768px) { + .navbar-toggleable-sm { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-sm .navbar-nav { + flex-direction: row; } + .navbar-toggleable-sm .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; } + .navbar-toggleable-sm > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-sm .navbar-collapse { + display: flex !important; + width: 100%; } + .navbar-toggleable-sm .navbar-toggler { + display: none; } } + +@media (max-width: 991px) { + .navbar-toggleable-md .navbar-nav .dropdown-menu { + position: static; + float: none; } + .navbar-toggleable-md > .container { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 992px) { + .navbar-toggleable-md { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-md .navbar-nav { + flex-direction: row; } + .navbar-toggleable-md .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; } + .navbar-toggleable-md > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-md .navbar-collapse { + display: flex !important; + width: 100%; } + .navbar-toggleable-md .navbar-toggler { + display: none; } } + +@media (max-width: 1199px) { + .navbar-toggleable-lg .navbar-nav .dropdown-menu { + position: static; + float: none; } + .navbar-toggleable-lg > .container { + padding-right: 0; + padding-left: 0; } } + +@media (min-width: 1200px) { + .navbar-toggleable-lg { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-lg .navbar-nav { + flex-direction: row; } + .navbar-toggleable-lg .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; } + .navbar-toggleable-lg > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-lg .navbar-collapse { + display: flex !important; + width: 100%; } + .navbar-toggleable-lg .navbar-toggler { + display: none; } } + +.navbar-toggleable-xl { + flex-direction: row; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-xl .navbar-nav .dropdown-menu { + position: static; + float: none; } + .navbar-toggleable-xl > .container { + padding-right: 0; + padding-left: 0; } + .navbar-toggleable-xl .navbar-nav { + flex-direction: row; } + .navbar-toggleable-xl .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; } + .navbar-toggleable-xl > .container { + display: flex; + flex-wrap: nowrap; + align-items: center; } + .navbar-toggleable-xl .navbar-collapse { + display: flex !important; + width: 100%; } + .navbar-toggleable-xl .navbar-toggler { + display: none; } + +.navbar-light .navbar-brand, +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.9); } + .navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover, + .navbar-light .navbar-toggler:focus, + .navbar-light .navbar-toggler:hover { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); } + .navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); } + .navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); } + +.navbar-light .navbar-nav .open > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.open, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); } + +.navbar-light .navbar-toggler { + border-color: rgba(0, 0, 0, 0.1); } + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); } + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); } + +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-toggler { + color: white; } + .navbar-inverse .navbar-brand:focus, .navbar-inverse .navbar-brand:hover, + .navbar-inverse .navbar-toggler:focus, + .navbar-inverse .navbar-toggler:hover { + color: white; } + +.navbar-inverse .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); } + .navbar-inverse .navbar-nav .nav-link:focus, .navbar-inverse .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, 0.75); } + .navbar-inverse .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); } + +.navbar-inverse .navbar-nav .open > .nav-link, +.navbar-inverse .navbar-nav .active > .nav-link, +.navbar-inverse .navbar-nav .nav-link.open, +.navbar-inverse .navbar-nav .nav-link.active { + color: white; } + +.navbar-inverse .navbar-toggler { + border-color: rgba(255, 255, 255, 0.1); } + +.navbar-inverse .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); } + +.navbar-inverse .navbar-text { + color: rgba(255, 255, 255, 0.5); } + +.card { + position: relative; + display: flex; + flex-direction: column; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; } + +.card-block { + flex: 1 1 auto; + padding: 1.25rem; } + +.card-title { + margin-bottom: 0.75rem; } + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; } + +.card-text:last-child { + margin-bottom: 0; } + +.card-link:hover { + text-decoration: none; } + +.card-link + .card-link { + margin-left: 1.25rem; } + +.card > .list-group:first-child .list-group-item:first-child { + border-top-right-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: #f7f7f9; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); } + .card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: #f7f7f9; + border-top: 1px solid rgba(0, 0, 0, 0.125); } + .card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; } + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; } + +.card-primary { + background-color: #0275d8; + border-color: #0275d8; } + .card-primary .card-header, + .card-primary .card-footer { + background-color: transparent; } + +.card-success { + background-color: #5cb85c; + border-color: #5cb85c; } + .card-success .card-header, + .card-success .card-footer { + background-color: transparent; } + +.card-info { + background-color: #5bc0de; + border-color: #5bc0de; } + .card-info .card-header, + .card-info .card-footer { + background-color: transparent; } + +.card-warning { + background-color: #f0ad4e; + border-color: #f0ad4e; } + .card-warning .card-header, + .card-warning .card-footer { + background-color: transparent; } + +.card-danger { + background-color: #d9534f; + border-color: #d9534f; } + .card-danger .card-header, + .card-danger .card-footer { + background-color: transparent; } + +.card-outline-primary { + background-color: transparent; + border-color: #0275d8; } + +.card-outline-secondary { + background-color: transparent; + border-color: #ccc; } + +.card-outline-info { + background-color: transparent; + border-color: #5bc0de; } + +.card-outline-success { + background-color: transparent; + border-color: #5cb85c; } + +.card-outline-warning { + background-color: transparent; + border-color: #f0ad4e; } + +.card-outline-danger { + background-color: transparent; + border-color: #d9534f; } + +.card-inverse { + color: rgba(255, 255, 255, 0.65); } + .card-inverse .card-header, + .card-inverse .card-footer { + background-color: transparent; + border-color: rgba(255, 255, 255, 0.2); } + .card-inverse .card-header, + .card-inverse .card-footer, + .card-inverse .card-title, + .card-inverse .card-blockquote { + color: #fff; } + .card-inverse .card-link, + .card-inverse .card-text, + .card-inverse .card-subtitle, + .card-inverse .card-blockquote .blockquote-footer { + color: rgba(255, 255, 255, 0.65); } + .card-inverse .card-link:focus, .card-inverse .card-link:hover { + color: #fff; } + +.card-blockquote { + padding: 0; + margin-bottom: 0; + border-left: 0; } + +.card-img { + border-radius: calc(0.25rem - 1px); } + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; } + +.card-img-top { + border-top-right-radius: calc(0.25rem - 1px); + border-top-left-radius: calc(0.25rem - 1px); } + +.card-img-bottom { + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); } + +@media (min-width: 576px) { + .card-deck { + display: flex; + flex-flow: row wrap; } + .card-deck .card { + display: flex; + flex: 1 0 0; + flex-direction: column; } + .card-deck .card:not(:first-child) { + margin-left: 15px; } + .card-deck .card:not(:last-child) { + margin-right: 15px; } } + +@media (min-width: 576px) { + .card-group { + display: flex; + flex-flow: row wrap; } + .card-group .card { + flex: 1 0 0; } + .card-group .card + .card { + margin-left: 0; + border-left: 0; } + .card-group .card:first-child { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } + .card-group .card:first-child .card-img-top { + border-top-right-radius: 0; } + .card-group .card:first-child .card-img-bottom { + border-bottom-right-radius: 0; } + .card-group .card:last-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + .card-group .card:last-child .card-img-top { + border-top-left-radius: 0; } + .card-group .card:last-child .card-img-bottom { + border-bottom-left-radius: 0; } + .card-group .card:not(:first-child):not(:last-child) { + border-radius: 0; } + .card-group .card:not(:first-child):not(:last-child) .card-img-top, + .card-group .card:not(:first-child):not(:last-child) .card-img-bottom { + border-radius: 0; } } + +@media (min-width: 576px) { + .card-columns { + column-count: 3; + column-gap: 1.25rem; } + .card-columns .card { + display: inline-block; + width: 100%; + margin-bottom: 0.75rem; } } + +.breadcrumb { + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #eceeef; + border-radius: 0.25rem; } + .breadcrumb::after { + display: block; + content: ""; + clear: both; } + +.breadcrumb-item { + float: left; } + .breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #636c72; + content: "/"; } + .breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; } + .breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; } + .breadcrumb-item.active { + color: #636c72; } + +.pagination { + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; } + +.page-item:first-child .page-link { + margin-left: 0; + border-bottom-left-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + +.page-item:last-child .page-link { + border-bottom-right-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + +.page-item.active .page-link { + z-index: 2; + color: #fff; + background-color: #0275d8; + border-color: #0275d8; } + +.page-item.disabled .page-link { + color: #636c72; + pointer-events: none; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; } + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #0275d8; + background-color: #fff; + border: 1px solid #ddd; } + .page-link:focus, .page-link:hover { + color: #014c8c; + text-decoration: none; + background-color: #eceeef; + border-color: #ddd; } + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; } + +.pagination-lg .page-item:first-child .page-link { + border-bottom-left-radius: 0.3rem; + border-top-left-radius: 0.3rem; } + +.pagination-lg .page-item:last-child .page-link { + border-bottom-right-radius: 0.3rem; + border-top-right-radius: 0.3rem; } + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; } + +.pagination-sm .page-item:first-child .page-link { + border-bottom-left-radius: 0.2rem; + border-top-left-radius: 0.2rem; } + +.pagination-sm .page-item:last-child .page-link { + border-bottom-right-radius: 0.2rem; + border-top-right-radius: 0.2rem; } + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; } + .badge:empty { + display: none; } + +.btn .badge { + position: relative; + top: -1px; } + +a.badge:focus, a.badge:hover { + color: #fff; + text-decoration: none; + cursor: pointer; } + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; } + +.badge-default { + background-color: #636c72; } + .badge-default[href]:focus, .badge-default[href]:hover { + background-color: #4b5257; } + +.badge-primary { + background-color: #0275d8; } + .badge-primary[href]:focus, .badge-primary[href]:hover { + background-color: #025aa5; } + +.badge-success { + background-color: #5cb85c; } + .badge-success[href]:focus, .badge-success[href]:hover { + background-color: #449d44; } + +.badge-info { + background-color: #5bc0de; } + .badge-info[href]:focus, .badge-info[href]:hover { + background-color: #31b0d5; } + +.badge-warning { + background-color: #f0ad4e; } + .badge-warning[href]:focus, .badge-warning[href]:hover { + background-color: #ec971f; } + +.badge-danger { + background-color: #d9534f; } + .badge-danger[href]:focus, .badge-danger[href]:hover { + background-color: #c9302c; } + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #eceeef; + border-radius: 0.3rem; } + @media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; } } + +.jumbotron-hr { + border-top-color: #d0d5d8; } + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; } + +.alert { + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; } + +.alert-heading { + color: inherit; } + +.alert-link { + font-weight: bold; } + +.alert-dismissible .close { + position: relative; + top: -0.75rem; + right: -1.25rem; + padding: 0.75rem 1.25rem; + color: inherit; } + +.alert-success { + background-color: #dff0d8; + border-color: #d0e9c6; + color: #3c763d; } + .alert-success hr { + border-top-color: #c1e2b3; } + .alert-success .alert-link { + color: #2b542c; } + +.alert-info { + background-color: #d9edf7; + border-color: #bcdff1; + color: #31708f; } + .alert-info hr { + border-top-color: #a6d5ec; } + .alert-info .alert-link { + color: #245269; } + +.alert-warning { + background-color: #fcf8e3; + border-color: #faf2cc; + color: #8a6d3b; } + .alert-warning hr { + border-top-color: #f7ecb5; } + .alert-warning .alert-link { + color: #66512c; } + +.alert-danger { + background-color: #f2dede; + border-color: #ebcccc; + color: #a94442; } + .alert-danger hr { + border-top-color: #e4b9b9; } + .alert-danger .alert-link { + color: #843534; } + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; } + to { + background-position: 0 0; } } + +.progress { + display: flex; + overflow: hidden; + font-size: 0.75rem; + line-height: 1rem; + text-align: center; + background-color: #eceeef; + border-radius: 0.25rem; } + +.progress-bar { + height: 1rem; + color: #fff; + background-color: #0275d8; } + +.progress-bar-striped { + 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: 1rem 1rem; } + +.progress-bar-animated { + animation: progress-bar-stripes 1s linear infinite; } + +.media { + display: flex; + align-items: flex-start; } + +.media-body { + flex: 1; } + +.list-group { + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; } + +.list-group-item-action { + width: 100%; + color: #464a4c; + text-align: inherit; } + .list-group-item-action .list-group-item-heading { + color: #292b2c; } + .list-group-item-action:focus, .list-group-item-action:hover { + color: #464a4c; + text-decoration: none; + background-color: #f7f7f9; } + .list-group-item-action:active { + color: #292b2c; + background-color: #eceeef; } + +.list-group-item { + position: relative; + display: flex; + flex-flow: row wrap; + align-items: center; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); } + .list-group-item:first-child { + border-top-right-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + .list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + .list-group-item:focus, .list-group-item:hover { + text-decoration: none; } + .list-group-item.disabled, .list-group-item:disabled { + color: #636c72; + cursor: not-allowed; + background-color: #fff; } + .list-group-item.disabled .list-group-item-heading, .list-group-item:disabled .list-group-item-heading { + color: inherit; } + .list-group-item.disabled .list-group-item-text, .list-group-item:disabled .list-group-item-text { + color: #636c72; } + .list-group-item.active { + z-index: 2; + color: #fff; + background-color: #0275d8; + border-color: #0275d8; } + .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 { + color: inherit; } + .list-group-item.active .list-group-item-text { + color: #daeeff; } + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; } + +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; } + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; } + +.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:focus, a.list-group-item-success:hover, + button.list-group-item-success:focus, + button.list-group-item-success:hover { + color: #3c763d; + background-color: #d0e9c6; } + a.list-group-item-success.active, + button.list-group-item-success.active { + 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:focus, a.list-group-item-info:hover, + button.list-group-item-info:focus, + button.list-group-item-info:hover { + color: #31708f; + background-color: #c4e3f3; } + a.list-group-item-info.active, + button.list-group-item-info.active { + 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:focus, a.list-group-item-warning:hover, + button.list-group-item-warning:focus, + button.list-group-item-warning:hover { + color: #8a6d3b; + background-color: #faf2cc; } + a.list-group-item-warning.active, + button.list-group-item-warning.active { + 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:focus, a.list-group-item-danger:hover, + button.list-group-item-danger:focus, + button.list-group-item-danger:hover { + color: #a94442; + background-color: #ebcccc; } + a.list-group-item-danger.active, + button.list-group-item-danger.active { + color: #fff; + background-color: #a94442; + border-color: #a94442; } + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; } + .embed-responsive::before { + display: block; + content: ""; } + .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-21by9::before { + padding-top: 42.85714286%; } + +.embed-responsive-16by9::before { + padding-top: 56.25%; } + +.embed-responsive-4by3::before { + padding-top: 75%; } + +.embed-responsive-1by1::before { + padding-top: 100%; } + +.close { + float: right; + font-size: 1.5rem; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; } + .close:focus, .close:hover { + color: #000; + text-decoration: none; + cursor: pointer; + opacity: .75; } + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; } + +.modal-open { + overflow: hidden; } + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; } + .modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -25%); } + .modal.show .modal-dialog { + 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; + display: flex; + flex-direction: column; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + 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; } + .modal-backdrop.show { + opacity: 0.5; } + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px; + border-bottom: 1px solid #eceeef; } + +.modal-title { + margin-bottom: 0; + line-height: 1.5; } + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: 15px; } + +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 15px; + border-top: 1px solid #eceeef; } + .modal-footer > :not(:first-child) { + margin-left: .25rem; } + .modal-footer > :not(:last-child) { + margin-right: .25rem; } + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; } + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 30px auto; } + .modal-sm { + max-width: 300px; } } + +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; } } + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; } + .tooltip.show { + opacity: 0.9; } + .tooltip.tooltip-top, .tooltip.bs-tether-element-attached-bottom { + padding: 5px 0; + margin-top: -3px; } + .tooltip.tooltip-top .tooltip-inner::before, .tooltip.bs-tether-element-attached-bottom .tooltip-inner::before { + bottom: 0; + left: 50%; + margin-left: -5px; + content: ""; + border-width: 5px 5px 0; + border-top-color: #000; } + .tooltip.tooltip-right, .tooltip.bs-tether-element-attached-left { + padding: 0 5px; + margin-left: 3px; } + .tooltip.tooltip-right .tooltip-inner::before, .tooltip.bs-tether-element-attached-left .tooltip-inner::before { + top: 50%; + left: 0; + margin-top: -5px; + content: ""; + border-width: 5px 5px 5px 0; + border-right-color: #000; } + .tooltip.tooltip-bottom, .tooltip.bs-tether-element-attached-top { + padding: 5px 0; + margin-top: 3px; } + .tooltip.tooltip-bottom .tooltip-inner::before, .tooltip.bs-tether-element-attached-top .tooltip-inner::before { + top: 0; + left: 50%; + margin-left: -5px; + content: ""; + border-width: 0 5px 5px; + border-bottom-color: #000; } + .tooltip.tooltip-left, .tooltip.bs-tether-element-attached-right { + padding: 0 5px; + margin-left: -3px; } + .tooltip.tooltip-left .tooltip-inner::before, .tooltip.bs-tether-element-attached-right .tooltip-inner::before { + top: 50%; + right: 0; + margin-top: -5px; + content: ""; + border-width: 5px 0 5px 5px; + border-left-color: #000; } + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; } + .tooltip-inner::before { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + padding: 1px; + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; } + .popover.popover-top, .popover.bs-tether-element-attached-bottom { + margin-top: -10px; } + .popover.popover-top::before, .popover.popover-top::after, .popover.bs-tether-element-attached-bottom::before, .popover.bs-tether-element-attached-bottom::after { + left: 50%; + border-bottom-width: 0; } + .popover.popover-top::before, .popover.bs-tether-element-attached-bottom::before { + bottom: -11px; + margin-left: -11px; + border-top-color: rgba(0, 0, 0, 0.25); } + .popover.popover-top::after, .popover.bs-tether-element-attached-bottom::after { + bottom: -10px; + margin-left: -10px; + border-top-color: #fff; } + .popover.popover-right, .popover.bs-tether-element-attached-left { + margin-left: 10px; } + .popover.popover-right::before, .popover.popover-right::after, .popover.bs-tether-element-attached-left::before, .popover.bs-tether-element-attached-left::after { + top: 50%; + border-left-width: 0; } + .popover.popover-right::before, .popover.bs-tether-element-attached-left::before { + left: -11px; + margin-top: -11px; + border-right-color: rgba(0, 0, 0, 0.25); } + .popover.popover-right::after, .popover.bs-tether-element-attached-left::after { + left: -10px; + margin-top: -10px; + border-right-color: #fff; } + .popover.popover-bottom, .popover.bs-tether-element-attached-top { + margin-top: 10px; } + .popover.popover-bottom::before, .popover.popover-bottom::after, .popover.bs-tether-element-attached-top::before, .popover.bs-tether-element-attached-top::after { + left: 50%; + border-top-width: 0; } + .popover.popover-bottom::before, .popover.bs-tether-element-attached-top::before { + top: -11px; + margin-left: -11px; + border-bottom-color: rgba(0, 0, 0, 0.25); } + .popover.popover-bottom::after, .popover.bs-tether-element-attached-top::after { + top: -10px; + margin-left: -10px; + border-bottom-color: #f7f7f7; } + .popover.popover-bottom .popover-title::before, .popover.bs-tether-element-attached-top .popover-title::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 20px; + margin-left: -10px; + content: ""; + border-bottom: 1px solid #f7f7f7; } + .popover.popover-left, .popover.bs-tether-element-attached-right { + margin-left: -10px; } + .popover.popover-left::before, .popover.popover-left::after, .popover.bs-tether-element-attached-right::before, .popover.bs-tether-element-attached-right::after { + top: 50%; + border-right-width: 0; } + .popover.popover-left::before, .popover.bs-tether-element-attached-right::before { + right: -11px; + margin-top: -11px; + border-left-color: rgba(0, 0, 0, 0.25); } + .popover.popover-left::after, .popover.bs-tether-element-attached-right::after { + right: -10px; + margin-top: -10px; + border-left-color: #fff; } + +.popover-title { + padding: 8px 14px; + margin-bottom: 0; + font-size: 1rem; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-right-radius: calc(0.3rem - 1px); + border-top-left-radius: calc(0.3rem - 1px); } + .popover-title:empty { + display: none; } + +.popover-content { + padding: 9px 14px; } + +.popover::before, +.popover::after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } + +.popover::before { + content: ""; + border-width: 11px; } + +.popover::after { + content: ""; + border-width: 10px; } + +.carousel { + position: relative; } + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; } + +.carousel-item { + position: relative; + display: none; + width: 100%; } + @media (-webkit-transform-3d) { + .carousel-item { + transition: transform 0.6s ease-in-out; + backface-visibility: hidden; + perspective: 1000px; } } + @supports (transform: translate3d(0, 0, 0)) { + .carousel-item { + transition: transform 0.6s ease-in-out; + backface-visibility: hidden; + perspective: 1000px; } } + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: flex; } + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; } + +@media (-webkit-transform-3d) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + transform: translate3d(0, 0, 0); } + .carousel-item-next, + .active.carousel-item-right { + transform: translate3d(100%, 0, 0); } + .carousel-item-prev, + .active.carousel-item-left { + transform: translate3d(-100%, 0, 0); } } + +@supports (transform: translate3d(0, 0, 0)) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + transform: translate3d(0, 0, 0); } + .carousel-item-next, + .active.carousel-item-right { + transform: translate3d(100%, 0, 0); } + .carousel-item-prev, + .active.carousel-item-left { + transform: translate3d(-100%, 0, 0); } } + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; } + .carousel-control-prev:focus, .carousel-control-prev:hover, + .carousel-control-next:focus, + .carousel-control-next:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; } + +.carousel-control-prev { + left: 0; } + +.carousel-control-next { + right: 0; } + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; } + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); } + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); } + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: flex; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; } + .carousel-indicators li { + position: relative; + flex: 1 0 auto; + max-width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: rgba(255, 255, 255, 0.5); } + .carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; } + .carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; } + .carousel-indicators .active { + 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; } + +.align-baseline { + vertical-align: baseline !important; } + +.align-top { + vertical-align: top !important; } + +.align-middle { + vertical-align: middle !important; } + +.align-bottom { + vertical-align: bottom !important; } + +.align-text-bottom { + vertical-align: text-bottom !important; } + +.align-text-top { + vertical-align: text-top !important; } + +.bg-faded { + background-color: #f7f7f7; } + +.bg-primary { + background-color: #0275d8 !important; } + +a.bg-primary:focus, a.bg-primary:hover { + background-color: #025aa5 !important; } + +.bg-success { + background-color: #5cb85c !important; } + +a.bg-success:focus, a.bg-success:hover { + background-color: #449d44 !important; } + +.bg-info { + background-color: #5bc0de !important; } + +a.bg-info:focus, a.bg-info:hover { + background-color: #31b0d5 !important; } + +.bg-warning { + background-color: #f0ad4e !important; } + +a.bg-warning:focus, a.bg-warning:hover { + background-color: #ec971f !important; } + +.bg-danger { + background-color: #d9534f !important; } + +a.bg-danger:focus, a.bg-danger:hover { + background-color: #c9302c !important; } + +.bg-inverse { + background-color: #292b2c !important; } + +a.bg-inverse:focus, a.bg-inverse:hover { + background-color: #101112 !important; } + +.border-0 { + border: 0 !important; } + +.border-top-0 { + border-top: 0 !important; } + +.border-right-0 { + border-right: 0 !important; } + +.border-bottom-0 { + border-bottom: 0 !important; } + +.border-left-0 { + border-left: 0 !important; } + +.rounded { + border-radius: 0.25rem; } + +.rounded-top { + border-top-right-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + +.rounded-right { + border-bottom-right-radius: 0.25rem; + border-top-right-radius: 0.25rem; } + +.rounded-bottom { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; } + +.rounded-left { + border-bottom-left-radius: 0.25rem; + border-top-left-radius: 0.25rem; } + +.rounded-circle { + border-radius: 50%; } + +.rounded-0 { + border-radius: 0; } + +.clearfix::after { + display: block; + content: ""; + clear: both; } + +.d-none { + display: none !important; } + +.d-inline { + display: inline !important; } + +.d-inline-block { + display: inline-block !important; } + +.d-block { + display: block !important; } + +.d-table { + display: table !important; } + +.d-table-cell { + display: table-cell !important; } + +.d-flex { + display: flex !important; } + +.d-inline-flex { + display: inline-flex !important; } + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; } + .d-sm-inline { + display: inline !important; } + .d-sm-inline-block { + display: inline-block !important; } + .d-sm-block { + display: block !important; } + .d-sm-table { + display: table !important; } + .d-sm-table-cell { + display: table-cell !important; } + .d-sm-flex { + display: flex !important; } + .d-sm-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 768px) { + .d-md-none { + display: none !important; } + .d-md-inline { + display: inline !important; } + .d-md-inline-block { + display: inline-block !important; } + .d-md-block { + display: block !important; } + .d-md-table { + display: table !important; } + .d-md-table-cell { + display: table-cell !important; } + .d-md-flex { + display: flex !important; } + .d-md-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; } + .d-lg-inline { + display: inline !important; } + .d-lg-inline-block { + display: inline-block !important; } + .d-lg-block { + display: block !important; } + .d-lg-table { + display: table !important; } + .d-lg-table-cell { + display: table-cell !important; } + .d-lg-flex { + display: flex !important; } + .d-lg-inline-flex { + display: inline-flex !important; } } + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; } + .d-xl-inline { + display: inline !important; } + .d-xl-inline-block { + display: inline-block !important; } + .d-xl-block { + display: block !important; } + .d-xl-table { + display: table !important; } + .d-xl-table-cell { + display: table-cell !important; } + .d-xl-flex { + display: flex !important; } + .d-xl-inline-flex { + display: inline-flex !important; } } + +.flex-first { + order: -1; } + +.flex-last { + order: 1; } + +.flex-unordered { + order: 0; } + +.flex-row { + flex-direction: row !important; } + +.flex-column { + flex-direction: column !important; } + +.flex-row-reverse { + flex-direction: row-reverse !important; } + +.flex-column-reverse { + flex-direction: column-reverse !important; } + +.flex-wrap { + flex-wrap: wrap !important; } + +.flex-nowrap { + flex-wrap: nowrap !important; } + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; } + +.justify-content-start { + justify-content: flex-start !important; } + +.justify-content-end { + justify-content: flex-end !important; } + +.justify-content-center { + justify-content: center !important; } + +.justify-content-between { + justify-content: space-between !important; } + +.justify-content-around { + justify-content: space-around !important; } + +.align-items-start { + align-items: flex-start !important; } + +.align-items-end { + align-items: flex-end !important; } + +.align-items-center { + align-items: center !important; } + +.align-items-baseline { + align-items: baseline !important; } + +.align-items-stretch { + align-items: stretch !important; } + +.align-content-start { + align-content: flex-start !important; } + +.align-content-end { + align-content: flex-end !important; } + +.align-content-center { + align-content: center !important; } + +.align-content-between { + align-content: space-between !important; } + +.align-content-around { + align-content: space-around !important; } + +.align-content-stretch { + align-content: stretch !important; } + +.align-self-auto { + align-self: auto !important; } + +.align-self-start { + align-self: flex-start !important; } + +.align-self-end { + align-self: flex-end !important; } + +.align-self-center { + align-self: center !important; } + +.align-self-baseline { + align-self: baseline !important; } + +.align-self-stretch { + align-self: stretch !important; } + +@media (min-width: 576px) { + .flex-sm-first { + order: -1; } + .flex-sm-last { + order: 1; } + .flex-sm-unordered { + order: 0; } + .flex-sm-row { + flex-direction: row !important; } + .flex-sm-column { + flex-direction: column !important; } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; } + .flex-sm-wrap { + flex-wrap: wrap !important; } + .flex-sm-nowrap { + flex-wrap: nowrap !important; } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .justify-content-sm-start { + justify-content: flex-start !important; } + .justify-content-sm-end { + justify-content: flex-end !important; } + .justify-content-sm-center { + justify-content: center !important; } + .justify-content-sm-between { + justify-content: space-between !important; } + .justify-content-sm-around { + justify-content: space-around !important; } + .align-items-sm-start { + align-items: flex-start !important; } + .align-items-sm-end { + align-items: flex-end !important; } + .align-items-sm-center { + align-items: center !important; } + .align-items-sm-baseline { + align-items: baseline !important; } + .align-items-sm-stretch { + align-items: stretch !important; } + .align-content-sm-start { + align-content: flex-start !important; } + .align-content-sm-end { + align-content: flex-end !important; } + .align-content-sm-center { + align-content: center !important; } + .align-content-sm-between { + align-content: space-between !important; } + .align-content-sm-around { + align-content: space-around !important; } + .align-content-sm-stretch { + align-content: stretch !important; } + .align-self-sm-auto { + align-self: auto !important; } + .align-self-sm-start { + align-self: flex-start !important; } + .align-self-sm-end { + align-self: flex-end !important; } + .align-self-sm-center { + align-self: center !important; } + .align-self-sm-baseline { + align-self: baseline !important; } + .align-self-sm-stretch { + align-self: stretch !important; } } + +@media (min-width: 768px) { + .flex-md-first { + order: -1; } + .flex-md-last { + order: 1; } + .flex-md-unordered { + order: 0; } + .flex-md-row { + flex-direction: row !important; } + .flex-md-column { + flex-direction: column !important; } + .flex-md-row-reverse { + flex-direction: row-reverse !important; } + .flex-md-column-reverse { + flex-direction: column-reverse !important; } + .flex-md-wrap { + flex-wrap: wrap !important; } + .flex-md-nowrap { + flex-wrap: nowrap !important; } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .justify-content-md-start { + justify-content: flex-start !important; } + .justify-content-md-end { + justify-content: flex-end !important; } + .justify-content-md-center { + justify-content: center !important; } + .justify-content-md-between { + justify-content: space-between !important; } + .justify-content-md-around { + justify-content: space-around !important; } + .align-items-md-start { + align-items: flex-start !important; } + .align-items-md-end { + align-items: flex-end !important; } + .align-items-md-center { + align-items: center !important; } + .align-items-md-baseline { + align-items: baseline !important; } + .align-items-md-stretch { + align-items: stretch !important; } + .align-content-md-start { + align-content: flex-start !important; } + .align-content-md-end { + align-content: flex-end !important; } + .align-content-md-center { + align-content: center !important; } + .align-content-md-between { + align-content: space-between !important; } + .align-content-md-around { + align-content: space-around !important; } + .align-content-md-stretch { + align-content: stretch !important; } + .align-self-md-auto { + align-self: auto !important; } + .align-self-md-start { + align-self: flex-start !important; } + .align-self-md-end { + align-self: flex-end !important; } + .align-self-md-center { + align-self: center !important; } + .align-self-md-baseline { + align-self: baseline !important; } + .align-self-md-stretch { + align-self: stretch !important; } } + +@media (min-width: 992px) { + .flex-lg-first { + order: -1; } + .flex-lg-last { + order: 1; } + .flex-lg-unordered { + order: 0; } + .flex-lg-row { + flex-direction: row !important; } + .flex-lg-column { + flex-direction: column !important; } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; } + .flex-lg-wrap { + flex-wrap: wrap !important; } + .flex-lg-nowrap { + flex-wrap: nowrap !important; } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .justify-content-lg-start { + justify-content: flex-start !important; } + .justify-content-lg-end { + justify-content: flex-end !important; } + .justify-content-lg-center { + justify-content: center !important; } + .justify-content-lg-between { + justify-content: space-between !important; } + .justify-content-lg-around { + justify-content: space-around !important; } + .align-items-lg-start { + align-items: flex-start !important; } + .align-items-lg-end { + align-items: flex-end !important; } + .align-items-lg-center { + align-items: center !important; } + .align-items-lg-baseline { + align-items: baseline !important; } + .align-items-lg-stretch { + align-items: stretch !important; } + .align-content-lg-start { + align-content: flex-start !important; } + .align-content-lg-end { + align-content: flex-end !important; } + .align-content-lg-center { + align-content: center !important; } + .align-content-lg-between { + align-content: space-between !important; } + .align-content-lg-around { + align-content: space-around !important; } + .align-content-lg-stretch { + align-content: stretch !important; } + .align-self-lg-auto { + align-self: auto !important; } + .align-self-lg-start { + align-self: flex-start !important; } + .align-self-lg-end { + align-self: flex-end !important; } + .align-self-lg-center { + align-self: center !important; } + .align-self-lg-baseline { + align-self: baseline !important; } + .align-self-lg-stretch { + align-self: stretch !important; } } + +@media (min-width: 1200px) { + .flex-xl-first { + order: -1; } + .flex-xl-last { + order: 1; } + .flex-xl-unordered { + order: 0; } + .flex-xl-row { + flex-direction: row !important; } + .flex-xl-column { + flex-direction: column !important; } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; } + .flex-xl-wrap { + flex-wrap: wrap !important; } + .flex-xl-nowrap { + flex-wrap: nowrap !important; } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; } + .justify-content-xl-start { + justify-content: flex-start !important; } + .justify-content-xl-end { + justify-content: flex-end !important; } + .justify-content-xl-center { + justify-content: center !important; } + .justify-content-xl-between { + justify-content: space-between !important; } + .justify-content-xl-around { + justify-content: space-around !important; } + .align-items-xl-start { + align-items: flex-start !important; } + .align-items-xl-end { + align-items: flex-end !important; } + .align-items-xl-center { + align-items: center !important; } + .align-items-xl-baseline { + align-items: baseline !important; } + .align-items-xl-stretch { + align-items: stretch !important; } + .align-content-xl-start { + align-content: flex-start !important; } + .align-content-xl-end { + align-content: flex-end !important; } + .align-content-xl-center { + align-content: center !important; } + .align-content-xl-between { + align-content: space-between !important; } + .align-content-xl-around { + align-content: space-around !important; } + .align-content-xl-stretch { + align-content: stretch !important; } + .align-self-xl-auto { + align-self: auto !important; } + .align-self-xl-start { + align-self: flex-start !important; } + .align-self-xl-end { + align-self: flex-end !important; } + .align-self-xl-center { + align-self: center !important; } + .align-self-xl-baseline { + align-self: baseline !important; } + .align-self-xl-stretch { + align-self: stretch !important; } } + +.float-left { + float: left !important; } + +.float-right { + float: right !important; } + +.float-none { + float: none !important; } + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; } + .float-sm-right { + float: right !important; } + .float-sm-none { + float: none !important; } } + +@media (min-width: 768px) { + .float-md-left { + float: left !important; } + .float-md-right { + float: right !important; } + .float-md-none { + float: none !important; } } + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; } + .float-lg-right { + float: right !important; } + .float-lg-none { + float: none !important; } } + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; } + .float-xl-right { + float: right !important; } + .float-xl-none { + float: none !important; } } + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; } + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; } + +.sticky-top { + position: sticky; + top: 0; + z-index: 1030; } + +.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; } + +.w-25 { + width: 25% !important; } + +.w-50 { + width: 50% !important; } + +.w-75 { + width: 75% !important; } + +.w-100 { + width: 100% !important; } + +.h-25 { + height: 25% !important; } + +.h-50 { + height: 50% !important; } + +.h-75 { + height: 75% !important; } + +.h-100 { + height: 100% !important; } + +.mw-100 { + max-width: 100% !important; } + +.mh-100 { + max-height: 100% !important; } + +.m-0 { + margin: 0 0 !important; } + +.mt-0 { + margin-top: 0 !important; } + +.mr-0 { + margin-right: 0 !important; } + +.mb-0 { + margin-bottom: 0 !important; } + +.ml-0 { + margin-left: 0 !important; } + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; } + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; } + +.m-1 { + margin: 0.25rem 0.25rem !important; } + +.mt-1 { + margin-top: 0.25rem !important; } + +.mr-1 { + margin-right: 0.25rem !important; } + +.mb-1 { + margin-bottom: 0.25rem !important; } + +.ml-1 { + margin-left: 0.25rem !important; } + +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; } + +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; } + +.m-2 { + margin: 0.5rem 0.5rem !important; } + +.mt-2 { + margin-top: 0.5rem !important; } + +.mr-2 { + margin-right: 0.5rem !important; } + +.mb-2 { + margin-bottom: 0.5rem !important; } + +.ml-2 { + margin-left: 0.5rem !important; } + +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; } + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; } + +.m-3 { + margin: 1rem 1rem !important; } + +.mt-3 { + margin-top: 1rem !important; } + +.mr-3 { + margin-right: 1rem !important; } + +.mb-3 { + margin-bottom: 1rem !important; } + +.ml-3 { + margin-left: 1rem !important; } + +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; } + +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; } + +.m-4 { + margin: 1.5rem 1.5rem !important; } + +.mt-4 { + margin-top: 1.5rem !important; } + +.mr-4 { + margin-right: 1.5rem !important; } + +.mb-4 { + margin-bottom: 1.5rem !important; } + +.ml-4 { + margin-left: 1.5rem !important; } + +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; } + +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; } + +.m-5 { + margin: 3rem 3rem !important; } + +.mt-5 { + margin-top: 3rem !important; } + +.mr-5 { + margin-right: 3rem !important; } + +.mb-5 { + margin-bottom: 3rem !important; } + +.ml-5 { + margin-left: 3rem !important; } + +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; } + +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; } + +.p-0 { + padding: 0 0 !important; } + +.pt-0 { + padding-top: 0 !important; } + +.pr-0 { + padding-right: 0 !important; } + +.pb-0 { + padding-bottom: 0 !important; } + +.pl-0 { + padding-left: 0 !important; } + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; } + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; } + +.p-1 { + padding: 0.25rem 0.25rem !important; } + +.pt-1 { + padding-top: 0.25rem !important; } + +.pr-1 { + padding-right: 0.25rem !important; } + +.pb-1 { + padding-bottom: 0.25rem !important; } + +.pl-1 { + padding-left: 0.25rem !important; } + +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; } + +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; } + +.p-2 { + padding: 0.5rem 0.5rem !important; } + +.pt-2 { + padding-top: 0.5rem !important; } + +.pr-2 { + padding-right: 0.5rem !important; } + +.pb-2 { + padding-bottom: 0.5rem !important; } + +.pl-2 { + padding-left: 0.5rem !important; } + +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; } + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; } + +.p-3 { + padding: 1rem 1rem !important; } + +.pt-3 { + padding-top: 1rem !important; } + +.pr-3 { + padding-right: 1rem !important; } + +.pb-3 { + padding-bottom: 1rem !important; } + +.pl-3 { + padding-left: 1rem !important; } + +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; } + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } + +.p-4 { + padding: 1.5rem 1.5rem !important; } + +.pt-4 { + padding-top: 1.5rem !important; } + +.pr-4 { + padding-right: 1.5rem !important; } + +.pb-4 { + padding-bottom: 1.5rem !important; } + +.pl-4 { + padding-left: 1.5rem !important; } + +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; } + +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; } + +.p-5 { + padding: 3rem 3rem !important; } + +.pt-5 { + padding-top: 3rem !important; } + +.pr-5 { + padding-right: 3rem !important; } + +.pb-5 { + padding-bottom: 3rem !important; } + +.pl-5 { + padding-left: 3rem !important; } + +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; } + +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; } + +.m-auto { + margin: auto !important; } + +.mt-auto { + margin-top: auto !important; } + +.mr-auto { + margin-right: auto !important; } + +.mb-auto { + margin-bottom: auto !important; } + +.ml-auto { + margin-left: auto !important; } + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; } + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; } + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 0 !important; } + .mt-sm-0 { + margin-top: 0 !important; } + .mr-sm-0 { + margin-right: 0 !important; } + .mb-sm-0 { + margin-bottom: 0 !important; } + .ml-sm-0 { + margin-left: 0 !important; } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; } + .m-sm-1 { + margin: 0.25rem 0.25rem !important; } + .mt-sm-1 { + margin-top: 0.25rem !important; } + .mr-sm-1 { + margin-right: 0.25rem !important; } + .mb-sm-1 { + margin-bottom: 0.25rem !important; } + .ml-sm-1 { + margin-left: 0.25rem !important; } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .m-sm-2 { + margin: 0.5rem 0.5rem !important; } + .mt-sm-2 { + margin-top: 0.5rem !important; } + .mr-sm-2 { + margin-right: 0.5rem !important; } + .mb-sm-2 { + margin-bottom: 0.5rem !important; } + .ml-sm-2 { + margin-left: 0.5rem !important; } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; } + .m-sm-3 { + margin: 1rem 1rem !important; } + .mt-sm-3 { + margin-top: 1rem !important; } + .mr-sm-3 { + margin-right: 1rem !important; } + .mb-sm-3 { + margin-bottom: 1rem !important; } + .ml-sm-3 { + margin-left: 1rem !important; } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; } + .m-sm-4 { + margin: 1.5rem 1.5rem !important; } + .mt-sm-4 { + margin-top: 1.5rem !important; } + .mr-sm-4 { + margin-right: 1.5rem !important; } + .mb-sm-4 { + margin-bottom: 1.5rem !important; } + .ml-sm-4 { + margin-left: 1.5rem !important; } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; } + .m-sm-5 { + margin: 3rem 3rem !important; } + .mt-sm-5 { + margin-top: 3rem !important; } + .mr-sm-5 { + margin-right: 3rem !important; } + .mb-sm-5 { + margin-bottom: 3rem !important; } + .ml-sm-5 { + margin-left: 3rem !important; } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; } + .p-sm-0 { + padding: 0 0 !important; } + .pt-sm-0 { + padding-top: 0 !important; } + .pr-sm-0 { + padding-right: 0 !important; } + .pb-sm-0 { + padding-bottom: 0 !important; } + .pl-sm-0 { + padding-left: 0 !important; } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; } + .p-sm-1 { + padding: 0.25rem 0.25rem !important; } + .pt-sm-1 { + padding-top: 0.25rem !important; } + .pr-sm-1 { + padding-right: 0.25rem !important; } + .pb-sm-1 { + padding-bottom: 0.25rem !important; } + .pl-sm-1 { + padding-left: 0.25rem !important; } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .p-sm-2 { + padding: 0.5rem 0.5rem !important; } + .pt-sm-2 { + padding-top: 0.5rem !important; } + .pr-sm-2 { + padding-right: 0.5rem !important; } + .pb-sm-2 { + padding-bottom: 0.5rem !important; } + .pl-sm-2 { + padding-left: 0.5rem !important; } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; } + .p-sm-3 { + padding: 1rem 1rem !important; } + .pt-sm-3 { + padding-top: 1rem !important; } + .pr-sm-3 { + padding-right: 1rem !important; } + .pb-sm-3 { + padding-bottom: 1rem !important; } + .pl-sm-3 { + padding-left: 1rem !important; } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } + .p-sm-4 { + padding: 1.5rem 1.5rem !important; } + .pt-sm-4 { + padding-top: 1.5rem !important; } + .pr-sm-4 { + padding-right: 1.5rem !important; } + .pb-sm-4 { + padding-bottom: 1.5rem !important; } + .pl-sm-4 { + padding-left: 1.5rem !important; } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; } + .p-sm-5 { + padding: 3rem 3rem !important; } + .pt-sm-5 { + padding-top: 3rem !important; } + .pr-sm-5 { + padding-right: 3rem !important; } + .pb-sm-5 { + padding-bottom: 3rem !important; } + .pl-sm-5 { + padding-left: 3rem !important; } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; } + .m-sm-auto { + margin: auto !important; } + .mt-sm-auto { + margin-top: auto !important; } + .mr-sm-auto { + margin-right: auto !important; } + .mb-sm-auto { + margin-bottom: auto !important; } + .ml-sm-auto { + margin-left: auto !important; } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; } } + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 0 !important; } + .mt-md-0 { + margin-top: 0 !important; } + .mr-md-0 { + margin-right: 0 !important; } + .mb-md-0 { + margin-bottom: 0 !important; } + .ml-md-0 { + margin-left: 0 !important; } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; } + .m-md-1 { + margin: 0.25rem 0.25rem !important; } + .mt-md-1 { + margin-top: 0.25rem !important; } + .mr-md-1 { + margin-right: 0.25rem !important; } + .mb-md-1 { + margin-bottom: 0.25rem !important; } + .ml-md-1 { + margin-left: 0.25rem !important; } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .m-md-2 { + margin: 0.5rem 0.5rem !important; } + .mt-md-2 { + margin-top: 0.5rem !important; } + .mr-md-2 { + margin-right: 0.5rem !important; } + .mb-md-2 { + margin-bottom: 0.5rem !important; } + .ml-md-2 { + margin-left: 0.5rem !important; } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; } + .m-md-3 { + margin: 1rem 1rem !important; } + .mt-md-3 { + margin-top: 1rem !important; } + .mr-md-3 { + margin-right: 1rem !important; } + .mb-md-3 { + margin-bottom: 1rem !important; } + .ml-md-3 { + margin-left: 1rem !important; } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; } + .m-md-4 { + margin: 1.5rem 1.5rem !important; } + .mt-md-4 { + margin-top: 1.5rem !important; } + .mr-md-4 { + margin-right: 1.5rem !important; } + .mb-md-4 { + margin-bottom: 1.5rem !important; } + .ml-md-4 { + margin-left: 1.5rem !important; } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; } + .m-md-5 { + margin: 3rem 3rem !important; } + .mt-md-5 { + margin-top: 3rem !important; } + .mr-md-5 { + margin-right: 3rem !important; } + .mb-md-5 { + margin-bottom: 3rem !important; } + .ml-md-5 { + margin-left: 3rem !important; } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; } + .p-md-0 { + padding: 0 0 !important; } + .pt-md-0 { + padding-top: 0 !important; } + .pr-md-0 { + padding-right: 0 !important; } + .pb-md-0 { + padding-bottom: 0 !important; } + .pl-md-0 { + padding-left: 0 !important; } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; } + .p-md-1 { + padding: 0.25rem 0.25rem !important; } + .pt-md-1 { + padding-top: 0.25rem !important; } + .pr-md-1 { + padding-right: 0.25rem !important; } + .pb-md-1 { + padding-bottom: 0.25rem !important; } + .pl-md-1 { + padding-left: 0.25rem !important; } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .p-md-2 { + padding: 0.5rem 0.5rem !important; } + .pt-md-2 { + padding-top: 0.5rem !important; } + .pr-md-2 { + padding-right: 0.5rem !important; } + .pb-md-2 { + padding-bottom: 0.5rem !important; } + .pl-md-2 { + padding-left: 0.5rem !important; } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; } + .p-md-3 { + padding: 1rem 1rem !important; } + .pt-md-3 { + padding-top: 1rem !important; } + .pr-md-3 { + padding-right: 1rem !important; } + .pb-md-3 { + padding-bottom: 1rem !important; } + .pl-md-3 { + padding-left: 1rem !important; } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } + .p-md-4 { + padding: 1.5rem 1.5rem !important; } + .pt-md-4 { + padding-top: 1.5rem !important; } + .pr-md-4 { + padding-right: 1.5rem !important; } + .pb-md-4 { + padding-bottom: 1.5rem !important; } + .pl-md-4 { + padding-left: 1.5rem !important; } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; } + .p-md-5 { + padding: 3rem 3rem !important; } + .pt-md-5 { + padding-top: 3rem !important; } + .pr-md-5 { + padding-right: 3rem !important; } + .pb-md-5 { + padding-bottom: 3rem !important; } + .pl-md-5 { + padding-left: 3rem !important; } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; } + .m-md-auto { + margin: auto !important; } + .mt-md-auto { + margin-top: auto !important; } + .mr-md-auto { + margin-right: auto !important; } + .mb-md-auto { + margin-bottom: auto !important; } + .ml-md-auto { + margin-left: auto !important; } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; } } + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 0 !important; } + .mt-lg-0 { + margin-top: 0 !important; } + .mr-lg-0 { + margin-right: 0 !important; } + .mb-lg-0 { + margin-bottom: 0 !important; } + .ml-lg-0 { + margin-left: 0 !important; } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; } + .m-lg-1 { + margin: 0.25rem 0.25rem !important; } + .mt-lg-1 { + margin-top: 0.25rem !important; } + .mr-lg-1 { + margin-right: 0.25rem !important; } + .mb-lg-1 { + margin-bottom: 0.25rem !important; } + .ml-lg-1 { + margin-left: 0.25rem !important; } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .m-lg-2 { + margin: 0.5rem 0.5rem !important; } + .mt-lg-2 { + margin-top: 0.5rem !important; } + .mr-lg-2 { + margin-right: 0.5rem !important; } + .mb-lg-2 { + margin-bottom: 0.5rem !important; } + .ml-lg-2 { + margin-left: 0.5rem !important; } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; } + .m-lg-3 { + margin: 1rem 1rem !important; } + .mt-lg-3 { + margin-top: 1rem !important; } + .mr-lg-3 { + margin-right: 1rem !important; } + .mb-lg-3 { + margin-bottom: 1rem !important; } + .ml-lg-3 { + margin-left: 1rem !important; } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; } + .m-lg-4 { + margin: 1.5rem 1.5rem !important; } + .mt-lg-4 { + margin-top: 1.5rem !important; } + .mr-lg-4 { + margin-right: 1.5rem !important; } + .mb-lg-4 { + margin-bottom: 1.5rem !important; } + .ml-lg-4 { + margin-left: 1.5rem !important; } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; } + .m-lg-5 { + margin: 3rem 3rem !important; } + .mt-lg-5 { + margin-top: 3rem !important; } + .mr-lg-5 { + margin-right: 3rem !important; } + .mb-lg-5 { + margin-bottom: 3rem !important; } + .ml-lg-5 { + margin-left: 3rem !important; } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; } + .p-lg-0 { + padding: 0 0 !important; } + .pt-lg-0 { + padding-top: 0 !important; } + .pr-lg-0 { + padding-right: 0 !important; } + .pb-lg-0 { + padding-bottom: 0 !important; } + .pl-lg-0 { + padding-left: 0 !important; } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; } + .p-lg-1 { + padding: 0.25rem 0.25rem !important; } + .pt-lg-1 { + padding-top: 0.25rem !important; } + .pr-lg-1 { + padding-right: 0.25rem !important; } + .pb-lg-1 { + padding-bottom: 0.25rem !important; } + .pl-lg-1 { + padding-left: 0.25rem !important; } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .p-lg-2 { + padding: 0.5rem 0.5rem !important; } + .pt-lg-2 { + padding-top: 0.5rem !important; } + .pr-lg-2 { + padding-right: 0.5rem !important; } + .pb-lg-2 { + padding-bottom: 0.5rem !important; } + .pl-lg-2 { + padding-left: 0.5rem !important; } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; } + .p-lg-3 { + padding: 1rem 1rem !important; } + .pt-lg-3 { + padding-top: 1rem !important; } + .pr-lg-3 { + padding-right: 1rem !important; } + .pb-lg-3 { + padding-bottom: 1rem !important; } + .pl-lg-3 { + padding-left: 1rem !important; } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } + .p-lg-4 { + padding: 1.5rem 1.5rem !important; } + .pt-lg-4 { + padding-top: 1.5rem !important; } + .pr-lg-4 { + padding-right: 1.5rem !important; } + .pb-lg-4 { + padding-bottom: 1.5rem !important; } + .pl-lg-4 { + padding-left: 1.5rem !important; } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; } + .p-lg-5 { + padding: 3rem 3rem !important; } + .pt-lg-5 { + padding-top: 3rem !important; } + .pr-lg-5 { + padding-right: 3rem !important; } + .pb-lg-5 { + padding-bottom: 3rem !important; } + .pl-lg-5 { + padding-left: 3rem !important; } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; } + .m-lg-auto { + margin: auto !important; } + .mt-lg-auto { + margin-top: auto !important; } + .mr-lg-auto { + margin-right: auto !important; } + .mb-lg-auto { + margin-bottom: auto !important; } + .ml-lg-auto { + margin-left: auto !important; } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; } } + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 0 !important; } + .mt-xl-0 { + margin-top: 0 !important; } + .mr-xl-0 { + margin-right: 0 !important; } + .mb-xl-0 { + margin-bottom: 0 !important; } + .ml-xl-0 { + margin-left: 0 !important; } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; } + .m-xl-1 { + margin: 0.25rem 0.25rem !important; } + .mt-xl-1 { + margin-top: 0.25rem !important; } + .mr-xl-1 { + margin-right: 0.25rem !important; } + .mb-xl-1 { + margin-bottom: 0.25rem !important; } + .ml-xl-1 { + margin-left: 0.25rem !important; } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; } + .m-xl-2 { + margin: 0.5rem 0.5rem !important; } + .mt-xl-2 { + margin-top: 0.5rem !important; } + .mr-xl-2 { + margin-right: 0.5rem !important; } + .mb-xl-2 { + margin-bottom: 0.5rem !important; } + .ml-xl-2 { + margin-left: 0.5rem !important; } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; } + .m-xl-3 { + margin: 1rem 1rem !important; } + .mt-xl-3 { + margin-top: 1rem !important; } + .mr-xl-3 { + margin-right: 1rem !important; } + .mb-xl-3 { + margin-bottom: 1rem !important; } + .ml-xl-3 { + margin-left: 1rem !important; } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; } + .m-xl-4 { + margin: 1.5rem 1.5rem !important; } + .mt-xl-4 { + margin-top: 1.5rem !important; } + .mr-xl-4 { + margin-right: 1.5rem !important; } + .mb-xl-4 { + margin-bottom: 1.5rem !important; } + .ml-xl-4 { + margin-left: 1.5rem !important; } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; } + .m-xl-5 { + margin: 3rem 3rem !important; } + .mt-xl-5 { + margin-top: 3rem !important; } + .mr-xl-5 { + margin-right: 3rem !important; } + .mb-xl-5 { + margin-bottom: 3rem !important; } + .ml-xl-5 { + margin-left: 3rem !important; } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; } + .p-xl-0 { + padding: 0 0 !important; } + .pt-xl-0 { + padding-top: 0 !important; } + .pr-xl-0 { + padding-right: 0 !important; } + .pb-xl-0 { + padding-bottom: 0 !important; } + .pl-xl-0 { + padding-left: 0 !important; } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; } + .p-xl-1 { + padding: 0.25rem 0.25rem !important; } + .pt-xl-1 { + padding-top: 0.25rem !important; } + .pr-xl-1 { + padding-right: 0.25rem !important; } + .pb-xl-1 { + padding-bottom: 0.25rem !important; } + .pl-xl-1 { + padding-left: 0.25rem !important; } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; } + .p-xl-2 { + padding: 0.5rem 0.5rem !important; } + .pt-xl-2 { + padding-top: 0.5rem !important; } + .pr-xl-2 { + padding-right: 0.5rem !important; } + .pb-xl-2 { + padding-bottom: 0.5rem !important; } + .pl-xl-2 { + padding-left: 0.5rem !important; } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; } + .p-xl-3 { + padding: 1rem 1rem !important; } + .pt-xl-3 { + padding-top: 1rem !important; } + .pr-xl-3 { + padding-right: 1rem !important; } + .pb-xl-3 { + padding-bottom: 1rem !important; } + .pl-xl-3 { + padding-left: 1rem !important; } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } + .p-xl-4 { + padding: 1.5rem 1.5rem !important; } + .pt-xl-4 { + padding-top: 1.5rem !important; } + .pr-xl-4 { + padding-right: 1.5rem !important; } + .pb-xl-4 { + padding-bottom: 1.5rem !important; } + .pl-xl-4 { + padding-left: 1.5rem !important; } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; } + .p-xl-5 { + padding: 3rem 3rem !important; } + .pt-xl-5 { + padding-top: 3rem !important; } + .pr-xl-5 { + padding-right: 3rem !important; } + .pb-xl-5 { + padding-bottom: 3rem !important; } + .pl-xl-5 { + padding-left: 3rem !important; } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; } + .m-xl-auto { + margin: auto !important; } + .mt-xl-auto { + margin-top: auto !important; } + .mr-xl-auto { + margin-right: auto !important; } + .mb-xl-auto { + margin-bottom: auto !important; } + .ml-xl-auto { + margin-left: auto !important; } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; } } + +.text-justify { + text-align: justify !important; } + +.text-nowrap { + white-space: nowrap !important; } + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + +.text-left { + text-align: left !important; } + +.text-right { + text-align: right !important; } + +.text-center { + text-align: center !important; } + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; } + .text-sm-right { + text-align: right !important; } + .text-sm-center { + text-align: center !important; } } + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; } + .text-md-right { + text-align: right !important; } + .text-md-center { + text-align: center !important; } } + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; } + .text-lg-right { + text-align: right !important; } + .text-lg-center { + text-align: center !important; } } + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; } + .text-xl-right { + text-align: right !important; } + .text-xl-center { + text-align: center !important; } } + +.text-lowercase { + text-transform: lowercase !important; } + +.text-uppercase { + text-transform: uppercase !important; } + +.text-capitalize { + text-transform: capitalize !important; } + +.font-weight-normal { + font-weight: normal; } + +.font-weight-bold { + font-weight: bold; } + +.font-italic { + font-style: italic; } + +.text-white { + color: #fff !important; } + +.text-muted { + color: #636c72 !important; } + +a.text-muted:focus, a.text-muted:hover { + color: #4b5257 !important; } + +.text-primary { + color: #0275d8 !important; } + +a.text-primary:focus, a.text-primary:hover { + color: #025aa5 !important; } + +.text-success { + color: #5cb85c !important; } + +a.text-success:focus, a.text-success:hover { + color: #449d44 !important; } + +.text-info { + color: #5bc0de !important; } + +a.text-info:focus, a.text-info:hover { + color: #31b0d5 !important; } + +.text-warning { + color: #f0ad4e !important; } + +a.text-warning:focus, a.text-warning:hover { + color: #ec971f !important; } + +.text-danger { + color: #d9534f !important; } + +a.text-danger:focus, a.text-danger:hover { + color: #c9302c !important; } + +.text-gray-dark { + color: #292b2c !important; } + +a.text-gray-dark:focus, a.text-gray-dark:hover { + color: #101112 !important; } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; } + +.invisible { + visibility: hidden !important; } + +.hidden-xs-up { + display: none !important; } + +@media (max-width: 575px) { + .hidden-xs-down { + display: none !important; } } + +@media (min-width: 576px) { + .hidden-sm-up { + display: none !important; } } + +@media (max-width: 767px) { + .hidden-sm-down { + display: none !important; } } + +@media (min-width: 768px) { + .hidden-md-up { + display: none !important; } } + +@media (max-width: 991px) { + .hidden-md-down { + display: none !important; } } + +@media (min-width: 992px) { + .hidden-lg-up { + display: none !important; } } + +@media (max-width: 1199px) { + .hidden-lg-down { + display: none !important; } } + +@media (min-width: 1200px) { + .hidden-xl-up { + display: none !important; } } + +.hidden-xl-down { + display: none !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; } } + +.alert-debug { + background-color: #fff; + border-color: #d6e9c6; + color: #000; } + +.alert-error { + background-color: #f2dede; + border-color: #eed3d7; + color: #b94a48; } + +#navbar-logo { + width: 48px; + height: 48px; + transition: transform .8s ease-in-out; } + #navbar-logo:hover { + transform: rotate(360deg); } + +body { + max-width: 100%; } + +.navbar-brand { + width: 100%; } + +#membercard { + color: #000; + background-color: #8C50A5; + border-radius: 14px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); + background-image: url("/static/images/membership_card_background.png"); + width: 430px; + height: 240px; + margin: auto; + text-shadow: 1px 1px #FFF; + position: relative; } + #membercard .middle { + font-size: 2rem; + text-align: center; + margin: 50px; } + #membercard .date { + padding: 14px; } + +html { + position: relative; + min-height: 100%; } + +body { + margin-bottom: 60px; } + +.footer { + position: absolute; + bottom: 0; + width: 100%; + height: 60px; + line-height: 60px; + background-color: #f5f5f5; } + +@media (max-width: 991px) { + .card-columns { + column-count: 2; } } + +@media (max-width: 767px) { + .card-columns { + column-count: 1; } } + +@media (max-width: 767px) { + .card { + margin-bottom: 0.75rem; } } + +.card-deck { + margin-bottom: 30px; } + +.card-title { + color: #292b2c; } + +#feeds img { + width: 100%; } + +.title { + margin-bottom: 0; } + +.thumbnail { + margin-bottom: 15px; } + +.modal-body iframe { + width: 100%; + height: 100%; } + +.modal-dialog { + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + margin: 50px; + padding: 0; + max-width: None; } + +.modal-content { + height: auto; + min-height: 100%; + border-radius: 0; } + +[hidden][style="display: block;"] { + display: block !important; } + +.imgfit { + width: 100%; + height: auto; + overflow: hidden; } diff --git a/mhackspace/static/sass/project.scss b/mhackspace/static/sass/project.scss index 6aceb3b..9939766 100644 --- a/mhackspace/static/sass/project.scss +++ b/mhackspace/static/sass/project.scss @@ -9,6 +9,7 @@ @import "components/feeds"; @import "components/blog"; +@import "components/wiki"; //////////////////////////////// //Django Toolbar// @@ -21,3 +22,9 @@ [hidden][style="display: block;"] { display: block !important; } + +.imgfit { + width:100%; + height: auto; + overflow: hidden; +} diff --git a/mhackspace/subscriptions/helper.py b/mhackspace/subscriptions/helper.py index 434bc63..dee8d5b 100644 --- a/mhackspace/subscriptions/helper.py +++ b/mhackspace/subscriptions/helper.py @@ -1,23 +1,35 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import -from django.contrib import messages +import logging +from datetime import datetime from django.contrib.auth.models import Group +from django.utils.dateparse import parse_datetime from mhackspace.users.models import Membership +from mhackspace.users.models import MEMBERSHIP_CANCELLED, MEMBERSHIP_ACTIVE + +logger = logging.getLogger(__name__) def create_or_update_membership(user, signup_details, complete=False): + start_date = signup_details.get('start_date') + if not isinstance(start_date, datetime): + start_date = parse_datetime(start_date) try: - member = Membership.objects.get(user=user) + member = Membership.objects.get(email=signup_details.get('email')) + # Only update if newer than last record, this way we only get the latest status + # cancellation and changed payment will not be counted against current status + if start_date < member.date: + return True except Membership.DoesNotExist: member = Membership() member.user = user if complete is True: - member.status = Membership.lookup_status(name=signup_details.get('status')) + member.status = MEMBERSHIP_ACTIVE member.email = signup_details.get('email') member.reference = signup_details.get('reference') member.payment = signup_details.get('amount') - member.date = signup_details.get('start_date') + member.date = start_date member.save() @@ -25,6 +37,21 @@ def create_or_update_membership(user, signup_details, complete=False): return False # sign up not completed # add user to group on success - group = Group.objects.get(name='members') - user.groups.add(group) + + if user: + try: + group = Group.objects.get(name='members') + user.groups.add(group) + except: + logger.error('Members group does not exist') return True # Sign up finished + + +def cancel_membership(user): + member = Membership.objects.get(user=user) + member.status = MEMBERSHIP_CANCELLED + member.save() + + group = Group.objects.get(name='members') + user.groups.remove(group) + return True diff --git a/mhackspace/subscriptions/management/commands/update_membership_status.py b/mhackspace/subscriptions/management/commands/update_membership_status.py index c88901e..f3accb9 100644 --- a/mhackspace/subscriptions/management/commands/update_membership_status.py +++ b/mhackspace/subscriptions/management/commands/update_membership_status.py @@ -1,5 +1,3 @@ -from datetime import datetime -from django.utils import timezone from django.contrib.auth.models import Group from django.forms.models import model_to_dict from django.core.management.base import BaseCommand @@ -8,11 +6,14 @@ from mhackspace.users.models import Membership, User from mhackspace.subscriptions.helper import create_or_update_membership +# this should be done in bulk, create the objects and save all at once +# for now its not an issue, because of small membership size + + def update_subscriptions(provider_name): provider = select_provider('gocardless') Membership.objects.all().delete() - subscriptions = [] group = Group.objects.get(name='members') @@ -24,18 +25,10 @@ def update_subscriptions(provider_name): except User.DoesNotExist: user_model = None - subscriptions.append( - Membership( - user=user_model, - email=sub.get('email'), - reference=sub.get('reference'), - payment=10.00, - date= sub.get('start_date'), - # date=timezone.now(), - status=Membership.lookup_status(name=sub.get('status')) - ) - ) - yield model_to_dict(subscriptions[-1]) + create_or_update_membership( + user=user_model, + signup_details=sub, + complete=True) class Command(BaseCommand): @@ -49,11 +42,11 @@ class Command(BaseCommand): provider = select_provider('gocardless') Membership.objects.all().delete() - subscriptions = [] group = Group.objects.get(name='members') for sub in provider.fetch_subscriptions(): + prefix = '' sub['amount'] = sub['amount'] * 0.01 try: user_model = User.objects.get(email=sub.get('email')) @@ -61,37 +54,23 @@ class Command(BaseCommand): user_model.groups.add(group) except User.DoesNotExist: user_model = None - self.style.NOTICE( - '\tMissing User {reference} - {payment} - {status} - {email}'.format(**{ - 'reference': sub.get('reference'), - 'payment': sub.get('amount'), - 'status': sub.get('status'), - 'email': sub.get('email') - })) - continue + prefix = 'NO USER - ' create_or_update_membership(user=user_model, signup_details=sub, complete=True) - subscriptions.append( - Membership( - user=user_model, - email=sub.get('email'), - reference=sub.get('reference'), - payment=sub.get('amount'), - date=sub.get('start_date'), - status=Membership.lookup_status(name=sub.get('status')) - ) - ) - self.stdout.write( - self.style.SUCCESS( - '\t{reference} - {payment} - {status} - {email}'.format(**{ - 'reference': sub.get('reference'), - 'payment': sub.get('amount'), - 'status': sub.get('status'), - 'email': sub.get('email') - }))) + message = '\t{prefix}{date} - {reference} - {payment} - {status} - {email}'.format(**{ + 'prefix': prefix, + 'date': sub.get('start_date'), + 'reference': sub.get('reference'), + 'payment': sub.get('amount'), + 'status': sub.get('status'), + 'email': sub.get('email') + }) - Membership.objects.bulk_create(subscriptions) + if user_model: + self.stdout.write(self.style.SUCCESS(message)) + else: + self.stdout.write(self.style.NOTICE(message)) diff --git a/mhackspace/subscriptions/payments.py b/mhackspace/subscriptions/payments.py index 5c4c864..641d952 100644 --- a/mhackspace/subscriptions/payments.py +++ b/mhackspace/subscriptions/payments.py @@ -1,6 +1,6 @@ from pprint import pprint import pytz -import gocardless_pro as gocardless +import gocardless_pro import braintree import logging @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) PROVIDER_ID = {'gocardless':1, 'braintree': 2} PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'} + def select_provider(type): if type == "gocardless": return gocardless_provider() if type == "braintree": return braintree_provider() @@ -20,6 +21,7 @@ def select_provider(type): log.exception('[scaffold] - "No Provider for ' + type) assert 0, "No Provider for " + type + class gocardless_provider: """ gocardless test account details 20-00-00, 55779911 @@ -29,20 +31,10 @@ class gocardless_provider: def __init__(self): # gocardless are changing there api, not sure if we can switch yet - self.client = gocardless.Client( + self.client = gocardless_pro.Client( access_token=payment_providers['gocardless']['credentials']['access_token'], environment=payment_providers['gocardless']['environment']) - def subscribe_confirm(self, args): - response = gocardless.client.confirm_resource(args) - subscription = gocardless.client.subscription(args.get('resource_id')) - return { - 'amount': subscription.amount, - 'start_date': subscription.created_at, - 'reference': subscription.id, - 'success': response.success - } - def fetch_customers(self): """Fetch list of customers payments""" for customer in self.client.customers.list().records: @@ -78,11 +70,18 @@ class gocardless_provider: def get_token(self): return 'N/A' - def cancel_subscription(self, reference): + def cancel_subscription(self, user, reference): try: - subscription = gocardless.client.subscription(reference) - response = subscription.cancel() - except gocardless.exceptions.ClientError: + subscription = self.client.subscriptions.get(reference) + response = self.client.subscriptions.cancel(reference) + except gocardless_pro.errors.InvalidApiUsageError as e: + if e.code is 404: + logger.info('Cancel subscription failed user not found %s %s' % (e.code, e)) + return { + 'success': False + } + except Exception as e: + logger.info('Cancel subscription failed unknown reason code %s %s' % (e.code, e)) return { 'success': False } @@ -90,7 +89,7 @@ class gocardless_provider: 'amount': subscription.amount, 'start_date': subscription.created_at, 'reference': subscription.id, - 'success': response.get('success', False) + 'success': True if response.get('status_code') is '200' else False } def create_subscription(self, user, session, amount, @@ -115,18 +114,13 @@ class gocardless_provider: # response = self.client.redirect_flows.complete(r, params={ # "session_token": session # }) - response = self.client.redirect_flows.get(r) + response = self.client.redirect_flows.complete(r, params={'session_token': session}) # response = self.client.redirect_flows.get(provider_response.get('redirect_flow_id')) - # response = gocardless.client.confirm_resource(provider_response) - # subscription = gocardless.client.subscription(provider_response.get('resource_id')) user_id = response.links.customer mandate_id = response.links.mandate - # user = subscription.user() user = self.client.customers.get(response.links.customer) mandate = self.client.mandates.get(response.links.mandate) - logging.debug(user) - logging.debug(mandate) # for some reason go cardless is in pence, so 20.00 needs to be sent as 2000 # what genious decided that was a good idea, now looks like i am charging £2000 :p @@ -180,8 +174,8 @@ class braintree_provider: def confirm_subscription(self, args): if self.provider == 'gocardless': - response = gocardless.client.confirm_resource(args) - subscription = gocardless.client.subscription(args.get('resource_id')) + response = gocardless_pro.client.confirm_resource(args) + subscription = gocardless_pro.client.subscription(args.get('resource_id')) return { 'amount': subscription.amount, 'start_date': subscription.created_at, @@ -199,255 +193,3 @@ class braintree_provider: 'reference': paying_member.reference, 'amount': paying_member.amount} - -class payment: - """ - https://developer.gocardless.com/api-reference/#redirect-flows-create-a-redirect-flow - paypal reference = https://github.com/paypal/PayPal-Python-SDK - gocardless reference = https://github.com/paypal/PayPal-Python-SDK - """ - #~ def __call__(self, **args): - #~ return self - - def __init__(self, provider='gocardless', style='payment', mode='sandbox'): - self.provider = provider - self.environment = int(mode=='production') - self.provider_id = PROVIDER_ID.get(provider) - - print(payment_providers) - if provider == 'paypal': - paypal.configure(**payment_providers[provider]['credentials']) - return - - gocardless_pro.Client( - access_token=payment_providers[provider]['credentials']['access_token'], - environment=payment_providers[provider]) - #~ environment = int('production' = payment_providers[provider]['environment']) - gocardless.environment = payment_providers[provider]['environment'] - gocardless.set_details(**payment_providers[provider]['credentials']) - merchant = gocardless.client.merchant() - - def lookup_provider_by_id(self, provider_id): - return PROVIDER_NAME.get(provider_id, None) - - def make_donation(self, amount, reference, redirect_success, redirect_failure): - if self.provider == 'paypal': - payment = paypal.Payment({ - "intent": "sale", - "payer": {"payment_method": "paypal"}, - "redirect_urls": { - "return_url": redirect_success, - "cancel_url": redirect_failure}, - - "transactions": [{ - "amount": { - "total": amount, - "currency": "GBP"}, - "description": reference}]}) - - payment_response = payment.create() - print('payment create') - if payment_response: - print(payment_response) - for link in payment.links: - if link.method == "REDIRECT": - redirect_url = str(link.href) - print(redirect_url) - return str(redirect_url) - else: - print("Error while creating payment:") - print(payment.error) - - if self.provider == 'gocardless': - return gocardless.client.new_bill_url( - float(amount), - name=reference, - redirect_uri=redirect_success) - - return 'Error something went wrong' - - def fetch_subscriptions(self): - if self.provider == 'gocardless': - merchant = gocardless.client.merchant() - for paying_member in merchant.subscriptions(): - user=paying_member.user() - print(dir(paying_member)) - print(paying_member.next_interval_start) - print(paying_member.status) - print(dir(paying_member.user())) - yield { - 'email': user.email, - 'start_date': paying_member.created_at, - 'reference': paying_member.id, - 'amount': paying_member.amount} - - - def confirm_subscription(self, args): - if self.provider == 'gocardless': - response = gocardless.client.confirm_resource(args) - subscription = gocardless.client.subscription(args.get('resource_id')) - return { - 'amount': subscription.amount, - 'start_date': subscription.created_at, - 'reference': subscription.id - } - - if self.provider == 'paypal': - print('subscribe_confirm') - payment_token = args.get('token', '') - billing_agreement_response = paypal.BillingAgreement.execute(payment_token) - amount = 0 - print(billing_agreement_response) - print(billing_agreement_response.id) - for row in billing_agreement_response.plan.payment_definitions: - amount = row.amount.value - - return { - 'amount': amount, - 'start_date': billing_agreement_response.start_date, - 'reference': billing_agreement_response.id - } - - return None - - def unsubscribe(self, reference): - if self.provider == 'gocardless': - print('unsubscribe gocardless') - subscription = gocardless.client.subscription(reference) - print(subscription.cancel()) - - if self.provider == 'paypal': - # this may be wrong - # ManageRecurringPaymentsProfileStatus - print(reference) - billing_plan = paypal.BillingAgreement.find(reference) - print(billing_plan) - print(billing_plan.error) - #~ billing_plan.replace([{"op": "replace","path": "/","value": {"state":"DELETED"}}]) - print(billing_plan.error) - #~ invoice = paypal.Invoice.find(reference) - options = { - "subject": "Cancelling membership", - "note": "Canceling invoice", - "send_to_merchant": True, - "send_to_payer": True - } - - if billing_plan.cancel(options): # return True or False - print("Invoice[%s] cancel successfully" % (invoice.id)) - else: - print(billing_plan.error) - - - def subscribe(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'): - if self.provider == 'gocardless': - return gocardless.client.new_subscription_url( - amount=float(amount), - interval_length=interval_length, - interval_unit=interval_unit, - name=name, - redirect_uri=redirect_success) - - if self.provider == 'paypal': - billing_plan = paypal.BillingPlan({ - "name": name, - "description": "Membership subscription", - "merchant_preferences": { - "auto_bill_amount": "yes", - "cancel_url": redirect_failure, - "initial_fail_amount_action": "continue", - "max_fail_attempts": "1", - "return_url": redirect_success, - "setup_fee": { - "currency": "GBP", - "value": amount - } - }, - "payment_definitions": [{ - "amount": { - "currency": "GBP", - "value": amount - }, - "cycles": "0", - "frequency": interval_unit, - "frequency_interval": interval_length, - "name": "Regular 1", - "type": "REGULAR" - } - ], - "type": "INFINITE" - }) - print('create bill') - - response = billing_plan.create() - - billing_plan = paypal.BillingPlan.find(billing_plan.id) - - if billing_plan.activate(): - start_date = datetime.utcnow() + timedelta(minutes=10) - billing_agreement = paypal.BillingAgreement({ - "name": billing_plan.name, - "description": name, - "start_date": start_date.strftime('%Y-%m-%dT%H:%M:%SZ'), - "plan": {"id": str(billing_plan.id)}, - "payer": {"payment_method": "paypal"} - }) - - if billing_agreement.create(): - print('billing agreement id') - print(billing_agreement.id) - - for link in billing_agreement.links: - if link.rel == "approval_url": - approval_url = link.href - return approval_url - else: - print(billing_agreement.error) - print('failed') - - def confirm(self, args): - confirm_details = {} - confirm_details['successfull'] = False - print('---------------------') - print(args) - - from pprint import pprint - if self.provider == 'paypal': - print(args.get('paymentId')) - print(args.get('PayerID')) - payment = paypal.Payment.find(args.get('paymentId')) - pprint(payment) - print(pprint(payment)) - print(payment) - - confirm_details['name'] = payment['payer']['payer_info'].first_name + ' ' + payment['payer']['payer_info'].last_name - confirm_details['user'] = payment['payer']['payer_info'].email - confirm_details['status'] = payment.state - confirm_details['amount'] = payment['transactions'][0]['amount'].total - confirm_details['created'] = payment.create_time - confirm_details['reference'] = payment.id - pprint(confirm_details) - - - if payment.execute({"payer_id": args.get('PayerID')}): # return True or False - confirm_details['successfull'] = True - print("Payment[%s] execute successfully" % (args.get('paymentId'))) - else: - print(payment.error) - return confirm_details - - if self.provider == 'gocardless': - bill_id = args.get('resource_id') - gocardless.client.confirm_resource(args) - if bill_id: - bill = gocardless.client.bill(bill_id) - confirm_details['name'] = bill.name - confirm_details['user'] = bill.user - confirm_details['status'] = bill.status - confirm_details['amount'] = bill.amount - #~ confirm_details['amount_minus_fees'] = bill.amount_minus_fees - confirm_details['created'] = bill.created_at - confirm_details['reference'] = bill_id - confirm_details['successfull'] = True - return confirm_details - return None diff --git a/mhackspace/subscriptions/tests/mocks.py b/mhackspace/subscriptions/tests/mocks.py new file mode 100644 index 0000000..58c5ccd --- /dev/null +++ b/mhackspace/subscriptions/tests/mocks.py @@ -0,0 +1,64 @@ +from test_plus.test import TestCase +from mock import patch, Mock, MagicMock, PropertyMock +from mhackspace.users.models import Membership +import django.utils.timezone +from collections import namedtuple + +from mhackspace.subscriptions.payments import gocardless_provider + + +class gocardlessMocks(TestCase): + + def setUp(self): + self.date_now = django.utils.timezone.now() + self.user = self.make_user() + self.auth_gocardless() + + def create_membership_record(self): + member = Membership() + member.user = self.user + member.payment = '20.00' + member.date = self.date_now + member.save() + return member + + @patch('mhackspace.subscriptions.payments.gocardless_pro', autospec=True) + def auth_gocardless(self, mock_request): + RedirectFlow = namedtuple('RedirectFlow', 'links') + Links = namedtuple('Links', 'mandate, customer') + mp = RedirectFlow( + links=Links(mandate='02', customer='01')) + + self.provider = gocardless_provider() + self.provider.client.redirect_flows.get = PropertyMock(return_value=mp) + + return self.provider + + def mock_success_responses(self): + subscription_properties = Mock( + id='02', + status='active', + amount=20.00, + created_at='date' + ) + + mock_list = MagicMock() + mock_list_records = MagicMock(side_effect=[subscription_properties]) + mock_list.records.return_value = mock_list_records + + self.provider.client.subscriptions.list = mock_list + ApiResponse = namedtuple('ApiResponse', 'api_response, created_at') + ApiResponseStatus = namedtuple('ApiResponseStatus', 'status_code') + + self.provider.client.subscriptions.create = Mock( + return_value=ApiResponse( + created_at=self.date_now, + api_response=ApiResponseStatus(status_code='200')) + ) + + self.provider.client.subscriptions.get = Mock( + return_value=subscription_properties) + + self.provider.client.subscriptions.cancel = PropertyMock( + return_value={'status_code': '200'}) + diff --git a/mhackspace/subscriptions/tests/test_payment_gateways.py b/mhackspace/subscriptions/tests/test_payment_gateways.py index 62200d0..2a35305 100644 --- a/mhackspace/subscriptions/tests/test_payment_gateways.py +++ b/mhackspace/subscriptions/tests/test_payment_gateways.py @@ -2,57 +2,31 @@ # -*- coding: utf-8 -*- from test_plus.test import TestCase from unittest import skip -from mock import patch, Mock +from mock import patch, Mock, MagicMock +from mhackspace.users.models import Membership +import django.utils.timezone -from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider +from mhackspace.subscriptions.payments import gocardless_provider, braintree_provider +from mhackspace.subscriptions.tests.mocks import gocardlessMocks -class TestPaymentGatewaysGocardless(TestCase): + +class TestPaymentGatewaysGocardless(gocardlessMocks): def setUp(self): - self.auth_gocardless() + super().setUp() - @patch('mhackspace.subscriptions.payments.gocardless.request.requests.get', autospec=True) - def auth_gocardless(self, mock_request): - # mock braintree initalisation request - mock_request.return_value = Mock(ok=True) - mock_request.return_value.json.return_value = { - "id": "1", - "created_at": "2011-11-18T17:07:09Z", - "access_token": "test_token", - "next_payout_date": "2011-11-18T17:07:09Z" - } + def test_unsubscribe(self): + self.mock_success_responses() - with patch('gocardless.resources.Merchant') as mock_subscription: - self.provider = gocardless_provider() - return self.provider #self.provider - - @skip("Need to implement") - @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True) - def test_unsubscribe(self, mock_subscription): - mock_subscription.return_value = Mock(success='success') - mock_subscription.cancel.return_value = Mock( - id='01', - status='active', - amount=20.00, - created_at='date' - ) - result = self.provider.cancel_subscription(reference='M01') + result = self.provider.cancel_subscription(user=self.user, reference='M01') self.assertEqual(result.get('amount'), 20.00) - self.assertEqual(result.get('start_date'), 'date') - self.assertEqual(result.get('reference'), '01') - self.assertEqual(result.get('success'), 'success') + self.assertEqual(result.get('reference'), '02') + self.assertEqual(result.get('success'), True) - @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True) - @patch('mhackspace.subscriptions.payments.gocardless.client.confirm_resource', autospec=True) - def test_confirm_subscription_callback(self, mock_confirm, mock_subscription): - mock_confirm.return_value = Mock(success='success') - mock_subscription.return_value = Mock( - id='01', - status='active', - amount=20.00, - created_at='date' - ) + def test_confirm_subscription_callback(self): + self.mock_success_responses() + membership = self.create_membership_record() request_params = { 'resource_uri': 'http://gocardless/resource/url/01', @@ -62,66 +36,23 @@ class TestPaymentGatewaysGocardless(TestCase): 'state': 'inactive' } - result = self.provider.subscribe_confirm(request_params) - self.assertEqual(result.get('amount'), 20.00) - self.assertEqual(result.get('start_date'), 'date') - self.assertEqual(result.get('reference'), '01') - self.assertEqual(result.get('success'), 'success') + # membership = Membership.objects.get(user=self.user) + result = self.provider.confirm_subscription( + membership=membership, + session=None, + provider_response={'redirect_flow_id': 'redirect_mock_url'}, + name='test') + self.assertEqual(result.get('amount'), '20.00') + self.assertEqual(result.get('reference'), '02') + self.assertEqual(result.get('success'), '200') def test_fetch_subscription_gocardless(self): - item = Mock( - id='01', - status='active', - amount=20.00, - created_at='date' - ) - item.user.return_value = Mock(email='test@test.com') - - self.provider.client = Mock() - self.provider.client.subscriptions = Mock(return_value=[item]) - - # mock out gocardless subscriptions method, and return our own values + self.mock_success_responses() for item in self.provider.fetch_subscriptions(): self.assertEqual(item.get('status'), 'active') self.assertEqual(item.get('email'), 'test@test.com') - self.assertEqual(item.get('reference'), '01') - self.assertEqual(item.get('start_date'), 'date') + self.assertEqual(item.get('reference'), '02') self.assertEqual(item.get('amount'), 20.00) - -class DisabledestPaymentGatewaysBraintree(TestCase): - @patch('mhackspace.subscriptions.payments.braintree.Configuration.configure') - def auth_braintree(self, mock_request): - # mock braintree initalisation request - mock_request.return_value = Mock(ok=True) - mock_request.return_value.json.return_value = { - "id": "1", - "created_at": "2011-11-18T17:07:09Z", - "access_token": "test_token", - "next_payout_date": "2011-11-18T17:07:09Z" - } - - self.provider = braintree_provider() - - @patch('mhackspace.subscriptions.payments.braintree.Subscription.search') - def test_fetch_subscription_braintree(self, mock_request): - provider = self.auth_braintree() - - items = [Mock( - id='01', - status='active', - amount=20.00, - reference='ref01', - created_at='date' - )] - items[-1].user.return_value = Mock(email='test@test.com') - - mock_request.return_value = items - for item in self.provider.fetch_subscriptions(): - self.assertEqual(item.get('status'), 'active') - self.assertEqual(item.get('email'), 'test@test.com') - self.assertEqual(item.get('reference'), 'ref01') - self.assertEqual(item.get('start_date'), 'date') - self.assertEqual(item.get('amount'), 20.00) diff --git a/mhackspace/subscriptions/tests/test_views.py b/mhackspace/subscriptions/tests/test_views.py index e611d96..ebe2756 100644 --- a/mhackspace/subscriptions/tests/test_views.py +++ b/mhackspace/subscriptions/tests/test_views.py @@ -1,11 +1,15 @@ from django.contrib.messages.storage.fallback import FallbackStorage # from django.contrib.auth.models import Group +from django.test import Client from django.test import RequestFactory from django.core.urlresolvers import reverse from test_plus.test import TestCase from mock import patch, Mock from mhackspace.users.models import Membership -from mhackspace.users.models import Membership +from mhackspace.users.models import User + +from mhackspace.subscriptions.payments import gocardless_provider +from mhackspace.subscriptions.tests.mocks import gocardlessMocks from ..views import ( MembershipCancelView, @@ -15,53 +19,72 @@ from ..views import ( ) -class BaseUserTestCase(TestCase): +class BaseUserTestCase(gocardlessMocks): fixtures = ['groups'] def setUp(self): - self.user = self.make_user() + super().setUp() + # self.user = self.make_user() + # self.user.save() self.factory = RequestFactory() + self.client = Client() + self.client.login( + username=self.user.username, + password=self.user.password) class TestSubscriptionSuccessRedirectView(BaseUserTestCase): - @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True) - @patch('mhackspace.subscriptions.views.select_provider', autospec=True) - def test_success_redirect_url(self, mock_subscription, mock_provider): - mock_subscription.return_value = mock_provider - mock_provider.confirm_subscription.return_value = { - 'amount': 20.00, - 'start_date': '2017-01-01T17:07:09Z', - 'reference': 'MH0001', - 'email': 'user@test.com', - 'success': True - } + # @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True) + # @patch('mhackspace.subscriptions.views.select_provider', autospec=True) + def test_success_redirect_url(self): + self.mock_success_responses() + self.create_membership_record() + # mock_gocardless.subscriptions.create.return_value = 'temp' + # mock_provider.confirm_subscription.return_value = { + # 'amount': 20.00, + # 'start_date': '2017-01-01T17:07:09Z', + # 'reference': 'MH0001', + # 'email': 'user@test.com', + # 'success': True + # } - request = self.factory.post( + response = self.client.post( reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), { 'resource_id': 'R01', 'resource_type': 'subscription', 'resource_url': 'https://sandbox.gocardless.com', 'signature': 'test_signature' - } + }, + follow=True ) - setattr(request, 'session', 'session') - messages = FallbackStorage(request) - setattr(request, '_messages', messages) - request.user = self.user + # print('=============================') + # setattr(request, 'session', 'session') + # messages = FallbackStorage(request) + # setattr(request, '_messages', messages) + # request.user = user1 - view = MembershipJoinSuccessView() - view.request = request - self.assertEqual( - view.get_redirect_url(provider ='gocardless'), - reverse('users:detail', kwargs={'username': self.user.username}) - ) + # view = MembershipJoinSuccessView() + # view.request = request + # print(self.user) + self.assertRedirects( + response, + expected_url='/accounts/login/?next=/membership/gocardless/success', + status_code=302, + target_status_code=200) + # self.assertEqual( + # view.get_redirect_url(provider ='gocardless'), + # reverse('users:detail', kwargs={'username': self.user.username}) + # ) + # view = Memhttps://www.youtube.com/bershipJoinSuccessView() + # view.request = request members = Membership.objects.all() self.assertEqual(members.count(), 1) - @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True) - def test_failure_redirect_url(self, mock_obj): + # @patch('mhackspace.subscriptions.payments.gocardless_pro.client.subscriptions', autospec=True) + def test_failure_redirect_url(self): + self.mock_success_responses() # Instantiate the view directly. Never do this outside a test! # Generate a fake request request = self.factory.post( diff --git a/mhackspace/subscriptions/views.py b/mhackspace/subscriptions/views.py index 4529621..bef25b9 100644 --- a/mhackspace/subscriptions/views.py +++ b/mhackspace/subscriptions/views.py @@ -14,7 +14,7 @@ from mhackspace.users.models import User, Membership from mhackspace.users.models import MEMBERSHIP_CANCELLED from mhackspace.users.forms import MembershipJoinForm from mhackspace.subscriptions.payments import select_provider -from mhackspace.subscriptions.helper import create_or_update_membership +from mhackspace.subscriptions.helper import create_or_update_membership, cancel_membership class MembershipCancelView(LoginRequiredMixin, RedirectView): @@ -28,21 +28,16 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView): member = Membership.objects.filter(user=self.request.user).first() result = provider.cancel_subscription( + user=self.request.user, reference=member.reference ) - if result.get('success') is True: - # set membership to cancelled on success - member.status = MEMBERSHIP_CANCELLED - member.save() - - # remove user from group on success - group = Group.objects.get(name='members') - self.request.user.groups.remove(group) - messages.add_message( - self.request, - messages.SUCCESS, - 'Your membership has now been cancelled') + # if result.get('success') is True: + cancel_membership(user=self.request.user) + messages.add_message( + self.request, + messages.SUCCESS, + 'Your membership has now been cancelled') kwargs['username'] = self.request.user.get_username() return super(MembershipCancelView, self).get_redirect_url(*args, **kwargs) @@ -68,7 +63,7 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView): result = { 'email': self.request.user.email, 'reference': user_code, - 'amount': form_subscription.cleaned_data.get('amount', 20.00) * 0.01, + 'amount': form_subscription.cleaned_data.get('amount', 20.00), 'start_date': timezone.now() } @@ -99,9 +94,10 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): payment_provider = 'gocardless' provider = select_provider(payment_provider) + print(self.request.user) membership = Membership.objects.get(user=self.request.user) - name="Membership your membership id is MH%s" % membership.reference + name = "Membership your membership id is MH%s" % membership.reference result = provider.confirm_subscription( membership=membership, session=self.request.session.session_key, @@ -110,6 +106,8 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView): ) # if something went wrong return to profile with an error + + result['start_date'] = timezone.now() if result.get('success') is False: messages.add_message( self.request, diff --git a/mhackspace/templates/admin/users/user/change_list.html b/mhackspace/templates/admin/users/membership/change_list.html similarity index 100% rename from mhackspace/templates/admin/users/user/change_list.html rename to mhackspace/templates/admin/users/membership/change_list.html diff --git a/mhackspace/templates/base.html b/mhackspace/templates/base.html index f0fc887..d4b87dc 100644 --- a/mhackspace/templates/base.html +++ b/mhackspace/templates/base.html @@ -168,6 +168,7 @@
{% block footer-left %}{% endblock footer-left %} © {% now "Y" %} Maidstone Hackspace + Constitution
diff --git a/mhackspace/templates/users/rfid_form.html b/mhackspace/templates/users/rfid_form.html new file mode 100644 index 0000000..4e1762d --- /dev/null +++ b/mhackspace/templates/users/rfid_form.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load crispy_forms_tags %} + +{% block title %}{{ user.username }}{% endblock %} + +{% block content %} +

{{ user.username }}

+
+ {% csrf_token %} + {{ form|crispy }} + {{ form_blurb|crispy }} +
+
+ +
+
+
+{% endblock %} diff --git a/mhackspace/templates/users/user_detail.html b/mhackspace/templates/users/user_detail.html index 9e354ea..6cd00ad 100644 --- a/mhackspace/templates/users/user_detail.html +++ b/mhackspace/templates/users/user_detail.html @@ -34,7 +34,7 @@ {% endif %}
- {% if membership.get_status %} + {% if membership.is_active %}
Joined {{membership.date}}
@@ -74,6 +74,7 @@

My Info + My Rfid Cards E-Mail

diff --git a/mhackspace/users/admin.py b/mhackspace/users/admin.py index ba7ca58..ef57971 100644 --- a/mhackspace/users/admin.py +++ b/mhackspace/users/admin.py @@ -9,9 +9,10 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.http import HttpResponseRedirect from django.urls import reverse from django.conf.urls import url -from .models import User, Membership, MEMBERSHIP_STATUS_CHOICES +from .models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES -from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions +# from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions +from mhackspace.users.tasks import update_users_memebership_status class MyUserChangeForm(UserChangeForm): @@ -46,21 +47,27 @@ class MyUserAdmin(AuthUserAdmin): list_display = ('username', 'name', 'is_superuser') search_fields = ['name'] + +@admin.register(Membership) +class MembershipAdmin(ModelAdmin): + list_display = ('user_id', 'email', 'payment', 'date', 'status') + list_filter = ('status',) + def get_urls(self): - urls = super(MyUserAdmin, self).get_urls() + urls = super(MembershipAdmin, self).get_urls() my_urls = [ url(r'^refresh/payments/$', self.admin_site.admin_view(self.refresh_payments)) ] return my_urls + urls def refresh_payments(self, request): - for user in update_subscriptions(provider_name='gocardless'): - continue - self.message_user(request, 'Successfully imported refresh users payment status') - return HttpResponseRedirect(reverse('admin:feeds_article_changelist')) + update_users_memebership_status() + # for user in update_subscriptions(provider_name='gocardless'): + # continue + self.message_user(request, 'Successfully triggered user payment refresh') + return HttpResponseRedirect(reverse('admin:index')) -@admin.register(Membership) -class MembershipAdmin(ModelAdmin): - list_display = ('user', 'payment', 'date', 'status') - list_filter = ('status',) +@admin.register(Rfid) +class RfidAdmin(ModelAdmin): + list_display = ('code', 'description') diff --git a/mhackspace/users/migrations/0004_rfid.py b/mhackspace/users/migrations/0004_rfid.py new file mode 100644 index 0000000..ae8ea00 --- /dev/null +++ b/mhackspace/users/migrations/0004_rfid.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-04-27 18:25 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_merge_20170226_0844'), + ] + + operations = [ + migrations.CreateModel( + name='Rfid', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.PositiveIntegerField()), + ('description', models.CharField(blank=True, max_length=255, verbose_name='Short rfid description')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/mhackspace/users/migrations/0005_merge_20170912_2037.py b/mhackspace/users/migrations/0005_merge_20170912_2037.py new file mode 100644 index 0000000..3e8e01b --- /dev/null +++ b/mhackspace/users/migrations/0005_merge_20170912_2037.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-12 20:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_auto_20170813_1557'), + ('users', '0004_rfid'), + ] + + operations = [ + ] diff --git a/mhackspace/users/migrations/0005_merge_20170913_0740.py b/mhackspace/users/migrations/0005_merge_20170913_0740.py new file mode 100644 index 0000000..f09943e --- /dev/null +++ b/mhackspace/users/migrations/0005_merge_20170913_0740.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-13 07:40 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0004_auto_20170813_1557'), + ('users', '0004_rfid'), + ] + + operations = [ + ] diff --git a/mhackspace/users/migrations/0006_merge_20170913_1718.py b/mhackspace/users/migrations/0006_merge_20170913_1718.py new file mode 100644 index 0000000..1f06f7f --- /dev/null +++ b/mhackspace/users/migrations/0006_merge_20170913_1718.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-13 17:18 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0005_merge_20170912_2037'), + ('users', '0005_merge_20170913_0740'), + ] + + operations = [ + ] diff --git a/mhackspace/users/migrations/0007_auto_20170914_2021.py b/mhackspace/users/migrations/0007_auto_20170914_2021.py new file mode 100644 index 0000000..2b65e49 --- /dev/null +++ b/mhackspace/users/migrations/0007_auto_20170914_2021.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-14 20:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_merge_20170913_1718'), + ] + + operations = [ + migrations.AlterField( + model_name='rfid', + name='code', + field=models.CharField(max_length=7), + ), + ] diff --git a/mhackspace/users/migrations/0008_auto_20170917_0948.py b/mhackspace/users/migrations/0008_auto_20170917_0948.py new file mode 100644 index 0000000..5fdfbb3 --- /dev/null +++ b/mhackspace/users/migrations/0008_auto_20170917_0948.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-17 09:48 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_auto_20170914_2021'), + ] + + operations = [ + migrations.AlterField( + model_name='membership', + name='email', + field=models.CharField(max_length=255, unique=True), + ), + migrations.AlterField( + model_name='membership', + name='user', + field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/mhackspace/users/models.py b/mhackspace/users/models.py index 1f0808a..bd2031d 100644 --- a/mhackspace/users/models.py +++ b/mhackspace/users/models.py @@ -45,44 +45,54 @@ class Blurb(models.Model): skills = models.CharField(max_length=255) description = models.TextField() -MEMBERSHIP_CANCELLED = 0 +MEMBERSHIP_ACTIVE = 1 +MEMBERSHIP_CANCELLED = 4 MEMBERSHIP_STATUS_CHOICES = ( (0, 'Guest user'), - (1, 'Active membership'), + (MEMBERSHIP_ACTIVE, 'Active membership'), (3, 'Membership Expired'), - (4, 'Membership Cancelled') + (MEMBERSHIP_CANCELLED, 'Membership Cancelled') ) MEMBERSHIP_STRING = { 0: 'Guest user', - 1: 'Active membership', - 3: 'Membership Expired' + MEMBERSHIP_ACTIVE: 'Active membership', + 3: 'Membership Expired', + MEMBERSHIP_CANCELLED: 'Membership Cancelled' } MEMBERSHIP_STATUS = { 'signup': 0, # This means the user has not completed signup - 'active': 1, - 'cancelled': 2 + 'active': MEMBERSHIP_ACTIVE, + 'expired': 3, + 'cancelled': MEMBERSHIP_CANCELLED } + class Membership(models.Model): - user = models.ForeignKey( + user = models.OneToOneField( settings.AUTH_USER_MODEL, null=True, blank=True, default=None, - related_name='user' + related_name='user', + unique=True ) payment = models.DecimalField(max_digits=6, decimal_places=2, default=0.0) date = models.DateTimeField() reference = models.CharField(max_length=255) status = models.PositiveSmallIntegerField(default=0, choices=MEMBERSHIP_STATUS_CHOICES) - email = models.CharField(max_length=255) + email = models.CharField(max_length=255, unique=True) @property def get_status(self): return MEMBERSHIP_STRING[self.status] + def is_active(self): + if self.status is MEMBERSHIP_ACTIVE: + return True + return False + def lookup_status(name): if not name: return 0 @@ -90,3 +100,20 @@ class Membership(models.Model): def __str__(self): return self.reference + + +# users rfid card to user mapping, user can have more than one card +class Rfid(models.Model): + code = models.CharField(max_length=7) + description = models.CharField(_('Short rfid description'), blank=True, max_length=255) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, blank=True, + # related_name='rfid_user' + ) + + def __str__(self): + return self.description + + def name(self): + return self.user.name diff --git a/mhackspace/users/tasks.py b/mhackspace/users/tasks.py index 9a72ff8..d733d3e 100644 --- a/mhackspace/users/tasks.py +++ b/mhackspace/users/tasks.py @@ -4,5 +4,4 @@ from mhackspace.subscriptions.management.commands.update_membership_status impor @shared_task def update_users_memebership_status(): - for user in update_subscriptions(provider_name='gocardless'): - continue + update_subscriptions(provider_name='gocardless') diff --git a/mhackspace/users/tests/test_serializer.py b/mhackspace/users/tests/test_serializer.py new file mode 100644 index 0000000..18f0ec8 --- /dev/null +++ b/mhackspace/users/tests/test_serializer.py @@ -0,0 +1,16 @@ +from test_plus.test import TestCase +from django.db import models +from allauth.utils import serialize_instance + + +class TestSerializeUser(TestCase): + + def setUp(self): + self.user = self.make_user() + + def test_serialize(self): + """check we can serialize the user object for allauth, custom types can break it""" + result = serialize_instance(self.user) + self.assertTrue( + isinstance(result, dict), + ) diff --git a/mhackspace/users/urls.py b/mhackspace/users/urls.py index 6ae4b6f..530b556 100644 --- a/mhackspace/users/urls.py +++ b/mhackspace/users/urls.py @@ -26,4 +26,9 @@ urlpatterns = [ view=views.UserUpdateView.as_view(), name='update' ), + url( + regex=r'^-access-cards$', + view=views.RfidCardsUpdateView.as_view(), + name='access_cards' + ), ] diff --git a/mhackspace/users/views.py b/mhackspace/users/views.py index c124721..43f5b2f 100644 --- a/mhackspace/users/views.py +++ b/mhackspace/users/views.py @@ -2,10 +2,11 @@ from __future__ import absolute_import, unicode_literals from django.core.urlresolvers import reverse -from django.views.generic import DetailView, ListView, RedirectView, UpdateView +from django.views.generic import DetailView, ListView, RedirectView, UpdateView, CreateView from django.contrib.auth.mixins import LoginRequiredMixin +from .models import Rfid from .models import User from .models import Blurb from .models import Membership @@ -65,6 +66,18 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): return super(UserUpdateView, self).form_valid(form) + +class RfidCardsUpdateView(LoginRequiredMixin, CreateView): + fields = ['user', 'code', 'description', ] + model = Rfid + + def form_valid(self, form): + user = self.request.user + form.instance.user = user + return super(RfidCardsUpdateView, self).form_valid(form) + + + class UserListView(LoginRequiredMixin, ListView): model = User # These next two lines tell the view to index lookups by username diff --git a/requirements/base.txt b/requirements/base.txt index 22a23a7..353bb77 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -61,7 +61,10 @@ git+https://github.com/olymk2/scaffold.git git+git://github.com/django-wiki/django-wiki.git djangorestframework==3.6.3 +djangorestframework-jwt django-filter==1.0.2 +coreapi +# api libraries end draceditor==1.1.8 # django-spirit