├── django_mobile_app_distribution ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0005_auto_20160118_1759.py │ ├── 0004_auto_20150921_0813.py │ ├── 0003_auto_20150807_1250.py │ ├── 0006_auto_20160408_1701.py │ ├── 0002_auto_20150730_1357.py │ └── 0001_initial.py ├── static │ └── django_mobile_app_distribution │ │ ├── css │ │ ├── app_list.sass │ │ ├── app_list.css │ │ ├── app_list.css.map │ │ ├── app.css │ │ ├── normalize.css │ │ └── foundation.min.css │ │ └── js │ │ ├── vendor │ │ ├── jquery.cookie.js │ │ ├── placeholder.js │ │ ├── fastclick.js │ │ └── modernizr.js │ │ └── foundation │ │ ├── foundation.min.js │ │ └── foundation.js ├── locale │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── ja │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── exceptions.py ├── templates │ ├── admin │ │ └── base_site.html │ └── django_mobile_app_distribution │ │ ├── email_notification.html │ │ ├── logout.html │ │ ├── base.html │ │ ├── login.html │ │ └── app_list.html ├── tests.py ├── urls.py ├── auth_urls.py ├── storage.py ├── forms.py ├── settings.py ├── views.py ├── admin.py └── models.py ├── .gitignore ├── MANIFEST.in ├── LICENSE ├── setup.py ├── CHANGES.rst └── README.md /django_mobile_app_distribution/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg* 2 | dev.py 3 | build 4 | dist 5 | .DS_Store 6 | *.pyc 7 | *.log 8 | .idea/* 9 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/app_list.sass: -------------------------------------------------------------------------------- 1 | .app 2 | margin-bottom: 55px 3 | 4 | .app_info 5 | margin-bottom: 15px -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Proper-Job/django-mobile-app-distribution/HEAD/django_mobile_app_distribution/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Proper-Job/django-mobile-app-distribution/HEAD/django_mobile_app_distribution/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Proper-Job/django-mobile-app-distribution/HEAD/django_mobile_app_distribution/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/app_list.css: -------------------------------------------------------------------------------- 1 | .app { 2 | margin-bottom: 55px; } 3 | 4 | .app_info { 5 | margin-bottom: 15px; } 6 | 7 | /*# sourceMappingURL=app_list.css.map */ 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGES.rst README.md LICENSE 2 | 3 | recursive-include django_mobile_app_distribution/static * 4 | recursive-include django_mobile_app_distribution/templates * 5 | recursive-include django_mobile_app_distribution/locale * -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/app_list.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AACA,IAAI;EACF,aAAa,EAAE,IAAI;;AAErB,SAAS;EACP,aAAa,EAAE,IAAI", 4 | "sources": ["app_list.sass"], 5 | "names": [], 6 | "file": "app_list.css" 7 | } -------------------------------------------------------------------------------- /django_mobile_app_distribution/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | class MobileAppDistributionConfigurationException(Exception): 5 | """A misconfiguration within the Django Mobile App Distribution settings is causing invalid states""" 6 | pass 7 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | a { 3 | color: black; 4 | } 5 | 6 | button, .button { 7 | background-color: black; 8 | } 9 | 10 | button:hover, button:focus, .button:hover, .button:focus { 11 | background-color: gray; 12 | } 13 | */ -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} 5 | 6 | {% block branding %} 7 |

{% trans 'Django administration' %}

8 | {% endblock %} 9 | 10 | {% block nav-global %}{% endblock %} 11 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.conf.urls import url 4 | 5 | from django_mobile_app_distribution import views 6 | 7 | urlpatterns = [ 8 | url(r'^$', views.index, name='django_mobile_app_distribution_index'), 9 | url(r'^apk/(?P\d{1,10})$', views.send_apk, name='django_mobile_app_distribution_send_apk'), 10 | url(r'^plist/(?P\d{1,10})\.plist$', views.ios_app_plist, name='django_mobile_app_distribution_ios_app_plist'), 11 | ] 12 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/auth_urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.conf.urls import url 4 | from django.contrib.auth import views as auth_views 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^login/$', 9 | auth_views.login, 10 | {'template_name': 'django_mobile_app_distribution/login.html'}, 11 | name='auth_login'), 12 | url(r'^logout/$', 13 | auth_views.logout, 14 | {'template_name': 'django_mobile_app_distribution/logout.html'}, 15 | name='auth_logout'), 16 | ] 17 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/django_mobile_app_distribution/email_notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ app_name }} 5 | 6 | 11 | 12 | 13 | {% load i18n l10n %} 14 | {% blocktrans %}Version {{ app_version }} of {{ app_name }} for {{ os }} is available for download.
Please visit {{ download_url }} to install the app.{% endblocktrans %} 15 | 16 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/django_mobile_app_distribution/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "django_mobile_app_distribution/base.html" %} 2 | {% load i18n staticfiles %} 3 | {% block title %}{% trans "logged out successfully" %}{% endblock %} 4 | {% block content %} 5 | 6 |
7 |
8 |

{% trans 'You were logged out successfully.' %}

9 |
10 |
11 |
12 | 15 |
16 | 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/storage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.core.files.storage import FileSystemStorage 5 | from django.utils.deconstruct import deconstructible 6 | import django_mobile_app_distribution.settings as app_dist_settings 7 | 8 | @deconstructible 9 | class CustomFileSystemStorage(FileSystemStorage): 10 | 11 | def __init__(self, location=None, base_url=None, file_permissions_mode=None, directory_permissions_mode=None): 12 | super(CustomFileSystemStorage, self).__init__( 13 | app_dist_settings.MOBILE_APP_DISTRIBUTION_ANDROID_FILE_STORAGE_PATH, 14 | base_url, 15 | file_permissions_mode, 16 | directory_permissions_mode 17 | ) 18 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0005_auto_20160118_1759.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import django_mobile_app_distribution.storage 6 | import django_mobile_app_distribution.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('django_mobile_app_distribution', '0004_auto_20150921_0813'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='androidapp', 18 | name='app_binary', 19 | field=models.FileField(upload_to=django_mobile_app_distribution.models.normalize_android_filename, verbose_name='APK file', storage=django_mobile_app_distribution.storage.CustomFileSystemStorage()), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0004_auto_20150921_0813.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.core.files.storage 6 | import django_mobile_app_distribution.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('django_mobile_app_distribution', '0003_auto_20150807_1250'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='androidapp', 18 | name='app_binary', 19 | field=models.FileField(upload_to=django_mobile_app_distribution.models.normalize_android_filename, storage=django.core.files.storage.FileSystemStorage(location='/Users/moritz/Alp-Phone/Projects/mobile_app_distribution/migrations_generator/migrations_generator/android'), verbose_name='APK file'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0003_auto_20150807_1250.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_mobile_app_distribution.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('django_mobile_app_distribution', '0002_auto_20150730_1357'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='iosapp', 17 | name='display_image', 18 | field=models.ImageField(default='', help_text='57x57 PNG', upload_to=django_mobile_app_distribution.models.normalize_image_filename, blank=True), 19 | ), 20 | migrations.AddField( 21 | model_name='iosapp', 22 | name='full_size_image', 23 | field=models.ImageField(default='', help_text='512x512 PNG', upload_to=django_mobile_app_distribution.models.normalize_image_filename, blank=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/django_mobile_app_distribution/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load i18n staticfiles %} 3 | 4 | 5 | {% block title %}{% trans "Mobile App Distribution" %}{% endblock %} 6 | 7 | 8 | 9 | 10 | {% block extra_style %}{% endblock %} 11 | {% block extra_script %}{% endblock %} 12 | {% block extra_head %}{% endblock %} 13 | 14 | 15 | 16 | {% block content %}{% endblock %} 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Moritz Pfeiffer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/vendor/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2013 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}}); 9 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import logging 4 | 5 | from django.forms import ModelForm, ValidationError 6 | from django.utils.translation import ugettext_lazy as _ 7 | from django_mobile_app_distribution.models import IosApp, AndroidApp 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class AppAdminForm(ModelForm): 13 | def clean(self): 14 | cleaned_data = super(AppAdminForm, self).clean() 15 | 16 | user = cleaned_data.get('user') 17 | groups = cleaned_data.get('groups') 18 | 19 | if not user and not groups: 20 | raise ValidationError(_('Please assign a user or group to the app.')) 21 | 22 | return cleaned_data 23 | 24 | 25 | class iOSAppAdminForm(AppAdminForm): 26 | class Meta: 27 | model = IosApp 28 | fields = '__all__' 29 | 30 | def clean(self): 31 | cleaned_data = super(AppAdminForm, self).clean() 32 | 33 | display_image = cleaned_data.get('display_image') 34 | full_size_image = cleaned_data.get('full_size_image') 35 | 36 | if (display_image or full_size_image) and not (display_image and full_size_image): 37 | error = ValidationError(_('Please provide a display and a full size image.')) 38 | self.add_error('display_image', error) 39 | self.add_error('full_size_image', error) 40 | 41 | return cleaned_data 42 | 43 | 44 | class AndroidAppAdminForm(AppAdminForm): 45 | class Meta: 46 | model = AndroidApp 47 | fields = '__all__' 48 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read() 5 | 6 | # allow setup.py to be run from any path 7 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 8 | 9 | setup( 10 | name='django-mobile-app-distribution', 11 | version='0.5', 12 | packages=find_packages(), 13 | include_package_data=True, 14 | license='MIT License', 15 | description='A Django app that adds iOS and Android app upload functionality to the Django admin interface. Provides a mobile optimized HTML fronted for clients to download Ad Hoc mobile applications using their iOS or Android devices.', 16 | long_description=README, 17 | url='https://github.com/Proper-Job/django-mobile-app-distribution', 18 | author='Moritz Pfeiffer', 19 | author_email='moritz.pfeiffer@alp-phone.ch', 20 | zip_safe=False, 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Environment :: Web Environment', 24 | 'Framework :: Django', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3.4', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Topic :: Internet :: WWW/HTTP', 33 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 34 | ], 35 | install_requires=['future', 'Pillow'] 36 | ) 37 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/django_mobile_app_distribution/login.html: -------------------------------------------------------------------------------- 1 | {% extends "django_mobile_app_distribution/base.html" %} 2 | {% load i18n staticfiles %} 3 | {% block title %}{% trans "customer login" %}{% endblock %} 4 | {% block content %} 5 | 6 |
{% csrf_token %} 7 |
8 | 9 |
10 |
11 | {{ form.username.label }} 12 |
13 |
14 | {{ form.username }} 15 |
16 |
17 | 18 |
19 |
20 | {{ form.password.label }} 21 |
22 |
23 | {{ form.password }} 24 |
25 |
26 | 27 |
28 |
29 | {% if form.errors %} 30 | {% trans "invalid login credentials" %} 31 | {% endif %} 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0006_auto_20160408_1701.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-04-08 17:01 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django_mobile_app_distribution.models 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('django_mobile_app_distribution', '0005_auto_20160118_1759'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='app', 20 | name='createdAt', 21 | field=models.DateTimeField(auto_now_add=True, verbose_name='created date'), 22 | ), 23 | migrations.AlterField( 24 | model_name='app', 25 | name='updatedAt', 26 | field=models.DateTimeField(auto_now=True, verbose_name='updated date'), 27 | ), 28 | migrations.AlterField( 29 | model_name='iosapp', 30 | name='display_image', 31 | field=models.ImageField(blank=True, default='', help_text='57x57 PNG', upload_to=django_mobile_app_distribution.models.normalize_image_filename, verbose_name='display image'), 32 | ), 33 | migrations.AlterField( 34 | model_name='iosapp', 35 | name='full_size_image', 36 | field=models.ImageField(blank=True, default='', help_text='512x512 PNG', upload_to=django_mobile_app_distribution.models.normalize_image_filename, verbose_name='full size image'), 37 | ), 38 | migrations.AlterField( 39 | model_name='userinfo', 40 | name='language', 41 | field=models.CharField(choices=[('en', 'English'), ('de', 'Deutsch'), ('ja', '日本語')], default='en', max_length=20, verbose_name='language'), 42 | ), 43 | migrations.AlterField( 44 | model_name='userinfo', 45 | name='user', 46 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'), 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/vendor/placeholder.js: -------------------------------------------------------------------------------- 1 | /*! http://mths.be/placeholder v2.0.9 by @mathias */ 2 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(b){var c={},d=/^jQuery\d+$/;return a.each(b.attributes,function(a,b){b.specified&&!d.test(b.name)&&(c[b.name]=b.value)}),c}function c(b,c){var d=this,f=a(d);if(d.value==f.attr("placeholder")&&f.hasClass("placeholder"))if(f.data("placeholder-password")){if(f=f.hide().nextAll('input[type="password"]:first').show().attr("id",f.removeAttr("id").data("placeholder-id")),b===!0)return f[0].value=c;f.focus()}else d.value="",f.removeClass("placeholder"),d==e()&&d.select()}function d(){var d,e=this,f=a(e),g=this.id;if(""===e.value){if("password"===e.type){if(!f.data("placeholder-textinput")){try{d=f.clone().attr({type:"text"})}catch(h){d=a("").attr(a.extend(b(this),{type:"text"}))}d.removeAttr("name").data({"placeholder-password":f,"placeholder-id":g}).bind("focus.placeholder",c),f.data({"placeholder-textinput":d,"placeholder-id":g}).before(d)}f=f.removeAttr("id").hide().prevAll('input[type="text"]:first').attr("id",g).show()}f.addClass("placeholder"),f[0].value=f.attr("placeholder")}else f.removeClass("placeholder")}function e(){try{return document.activeElement}catch(a){}}var f,g,h="[object OperaMini]"==Object.prototype.toString.call(window.operamini),i="placeholder"in document.createElement("input")&&!h,j="placeholder"in document.createElement("textarea")&&!h,k=a.valHooks,l=a.propHooks;i&&j?(g=a.fn.placeholder=function(){return this},g.input=g.textarea=!0):(g=a.fn.placeholder=function(){var a=this;return a.filter((i?"textarea":":input")+"[placeholder]").not(".placeholder").bind({"focus.placeholder":c,"blur.placeholder":d}).data("placeholder-enabled",!0).trigger("blur.placeholder"),a},g.input=i,g.textarea=j,f={get:function(b){var c=a(b),d=c.data("placeholder-password");return d?d[0].value:c.data("placeholder-enabled")&&c.hasClass("placeholder")?"":b.value},set:function(b,f){var g=a(b),h=g.data("placeholder-password");return h?h[0].value=f:g.data("placeholder-enabled")?(""===f?(b.value=f,b!=e()&&d.call(b)):g.hasClass("placeholder")?c.call(b,!0,f)||(b.value=f):b.value=f,g):b.value=f}},i||(k.input=f,l.value=f),j||(k.textarea=f,l.value=f),a(function(){a(document).delegate("form","submit.placeholder",function(){var b=a(".placeholder",this).each(c);setTimeout(function(){b.each(d)},10)})}),a(window).bind("beforeunload.placeholder",function(){a(".placeholder").each(function(){this.value=""})}))}); 3 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0002_auto_20150730_1357.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_mobile_app_distribution.models 6 | import django.core.files.storage 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('django_mobile_app_distribution', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='androidapp', 18 | options={'verbose_name': 'Android App', 'verbose_name_plural': 'Android Apps', 'ordering': ('name', 'operating_system', '-version', '-updatedAt')}, 19 | ), 20 | migrations.AlterField( 21 | model_name='androidapp', 22 | name='app_binary', 23 | field=models.FileField(verbose_name='APK file', upload_to=django_mobile_app_distribution.models.normalize_android_filename, storage=django.core.files.storage.FileSystemStorage(location='/Users/moritz/Alp-Phone/Projects/mobile_app_distribution/ota_ad_hoc_management/ota_ad_hoc_management/android')), 24 | ), 25 | migrations.AlterField( 26 | model_name='androidapp', 27 | name='operating_system', 28 | field=models.CharField(verbose_name='Operating system', default='Android', max_length=50, choices=[('iOS', 'iOS'), ('Android', 'Android')], editable=False), 29 | ), 30 | migrations.AlterField( 31 | model_name='app', 32 | name='groups', 33 | field=models.ManyToManyField(verbose_name='Groups', default=None, blank=True, to='auth.Group', related_name='apps'), 34 | ), 35 | migrations.AlterField( 36 | model_name='app', 37 | name='updatedAt', 38 | field=models.DateTimeField(auto_now=True), 39 | ), 40 | migrations.AlterField( 41 | model_name='iosapp', 42 | name='bundle_identifier', 43 | field=models.CharField(verbose_name='Bundle identifier', default='', max_length=200, help_text='e.g. org.example.app'), 44 | ), 45 | migrations.AlterField( 46 | model_name='iosapp', 47 | name='operating_system', 48 | field=models.CharField(verbose_name='Operating system', default='iOS', max_length=50, choices=[('iOS', 'iOS'), ('Android', 'Android')], editable=False), 49 | ), 50 | migrations.AlterField( 51 | model_name='userinfo', 52 | name='language', 53 | field=models.CharField(default='en', max_length=20, choices=[('en', 'English'), ('de', 'Deutsch')]), 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/templates/django_mobile_app_distribution/app_list.html: -------------------------------------------------------------------------------- 1 | {% extends "django_mobile_app_distribution/base.html" %} 2 | {% load i18n staticfiles %} 3 | 4 | {% block title %}{% trans "app list view" %}{% endblock %} 5 | 6 | {% block extra_head %} 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 | 12 |
13 | 16 |
17 | 18 | {% for app in apps %} 19 |
20 |
21 |
22 |

{{ app.name }}

23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 | {% trans 'operating system' %}: 31 |
32 | {{ app.operating_system }} 33 |
34 | 35 |
36 | {% trans 'version' %}: 37 |
38 | {{ app.version }} 39 |
40 | 41 |
42 | {% trans 'build date' %}: 43 |
44 | {{ app.updatedAt|date:"d M Y H:i" }} 45 |
46 | 47 |
48 | {% trans 'comment' %}: 49 |
50 | {{ app.comment }} 51 |
52 |
53 |
54 |
55 | {% if app.operating_system == ios_identifier %} 56 | 57 | {% trans 'Install App' %} 58 | 59 | {% else %} 60 | 61 | {% trans 'Install App' %} 62 | 63 | {% endif %} 64 |
65 |
66 |
67 |
68 | {% endfor %} 69 | 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog for Django Mobile App Distribution 2 | ============================================ 3 | 4 | 0.5 (2018-01-25) 5 | ---------------- 6 | 7 | - Django 2.0 compatibility (Josh Marshall) 8 | 9 | 10 | 0.4 (2017-03-15) 11 | ---------------- 12 | 13 | - Django 1.10 compatibility 14 | 15 | 16 | 0.3.12 (2016-04-08) 17 | ------------------- 18 | 19 | - Django 1.9 compatibility (Ryosuke Ito) 20 | - Japanese translations (Ryosuke Ito) 21 | - Fallback to Django >= 1.8 django.conf.settings.BASE_DIR directive if BASE_PATH is not defined (Ryosuke Ito) 22 | 23 | 24 | 0.3.11 (2016-01-18) 25 | ------------------- 26 | 27 | - Fixed migrations being created when app is deployed. 28 | 29 | s 30 | 0.3.10 (2015-09-21) 31 | ------------------- 32 | 33 | - Fixed packaging 34 | 35 | 36 | 0.3.9 (2015-09-21) 37 | ------------------ 38 | 39 | - Fixed admin model forms ImproperlyConfigured exception. 40 | 41 | 42 | 0.3.8 (2015-09-10) 43 | ------------------ 44 | 45 | - Fix plist syntax error in iOS 9 style manifest.plist. 46 | 47 | 48 | 0.3.7 (2015-08-07) 49 | ------------------ 50 | 51 | - Added support for iOS 9 style manifest.plist configuration. 52 | - Dropped support for Django < 1.7, use 0.3.6 for that. 53 | 54 | 55 | 0.3.6 (2015-07-30) 56 | ------------------ 57 | 58 | - Added Python 3 and Django 1.8 support. 59 | 60 | 61 | 0.3.5 (2015-04-29) 62 | ------------------ 63 | 64 | - Removed logo from login and logout templates. Removed header from app_list template. 65 | 66 | 67 | 0.3.4 (2015-04-29) 68 | ------------------ 69 | 70 | - Added way to customize default color scheme. 71 | 72 | 73 | 0.3.3 (2015-04-28) 74 | ---------------- 75 | 76 | - Added Zurb Foundation CSS framework and modernized login, logout and app_list templates. 77 | 78 | 79 | 0.3.1 (2015-04-28) 80 | ---------------- 81 | 82 | - Added support for Django >= 1.7 style migrations. 83 | 84 | 85 | 0.3 (2014-11-18) 86 | ---------------- 87 | 88 | - Version 0.3 is not backwards compatible since Xcode 6 has changed the ad hoc process considerably. You'll have to delete and re-add all iOS apps. 89 | - Fixed deployment to iOS 8 clients. See https://buildozer.io/ios8 for more details. 90 | - Plist file is now automatically generated for iOS apps since Xcode 6 no longer provides it. 91 | - Fixed group distribution for Android apps 92 | 93 | 94 | 95 | 0.2 (2014-03-27) 96 | ------------------ 97 | 98 | - Added South dependency to facilitate schmema migration. Checkout README for instructions to upgrade from version 0.1.x to version 0.2. 99 | - Added the ability to associate apps with user groups. This makes it possible to make a single app available to a group of users. 100 | - Added search fields to the app admin change lists. 101 | 102 | 103 | 0.1.3 (2014-03-19) 104 | ------------------ 105 | 106 | - Using ugettext_lazy instead of ugettext in models.py, which is noticeable if you have a dynamic language switcher in the admin interface. 107 | 108 | 109 | 0.1.2 (2013-08-15) 110 | ------------------ 111 | 112 | - Django Mobile App Distribution no longer registers a custom User object class, in case other apps already do that. 113 | * As a consequence the UserInfo attributes cannot be changed from the User changeform any longer. Instead the UserInfo object can be edited standalone. 114 | - User specific language preferences are now respected in the frontend HTML and email messages. 115 | - Fixes in README 116 | 117 | 118 | 0.1.1 (2013-08-13) 119 | ------------------ 120 | 121 | - Fixed template url reverse on Django 1.5 - by using {% load url from future %} in templates 122 | 123 | 0.1.0 (2013-08-12) 124 | ------------------ 125 | 126 | - Initial release -------------------------------------------------------------------------------- /django_mobile_app_distribution/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_mobile_app_distribution.models 6 | from django.conf import settings 7 | import django.core.files.storage 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('auth', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='App', 20 | fields=[ 21 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 22 | ('name', models.CharField(max_length=200, verbose_name='App name')), 23 | ('comment', models.CharField(max_length=200, null=True, verbose_name='Comment', blank=True)), 24 | ('version', models.CharField(max_length=200, verbose_name='Bundle version')), 25 | ('updatedAt', models.DateTimeField(auto_now=True, auto_now_add=True)), 26 | ('createdAt', models.DateTimeField(auto_now_add=True)), 27 | ], 28 | options={ 29 | }, 30 | bases=(models.Model,), 31 | ), 32 | migrations.CreateModel( 33 | name='AndroidApp', 34 | fields=[ 35 | ('app_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='django_mobile_app_distribution.App', on_delete=models.deletion.CASCADE)), 36 | ('operating_system', models.CharField(default=b'Android', verbose_name='Operating System', max_length=50, editable=False, choices=[(b'iOS', b'iOS'), (b'Android', b'Android')])), 37 | ('app_binary', models.FileField(upload_to=django_mobile_app_distribution.models.normalize_android_filename, storage=django.core.files.storage.FileSystemStorage(location=b'/Users/moritz/Alp-Phone/Projects/mobile_app_distribution/migrations_generator/migrations_generator/android'), verbose_name='APK file')), 38 | ], 39 | options={ 40 | 'ordering': ('name', 'operating_system', '-version', '-updatedAt'), 41 | 'verbose_name': 'Android app', 42 | 'verbose_name_plural': 'Android apps', 43 | }, 44 | bases=('django_mobile_app_distribution.app',), 45 | ), 46 | migrations.CreateModel( 47 | name='IosApp', 48 | fields=[ 49 | ('app_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='django_mobile_app_distribution.App', on_delete=models.deletion.CASCADE)), 50 | ('operating_system', models.CharField(default=b'iOS', verbose_name='Operating System', max_length=50, editable=False, choices=[(b'iOS', b'iOS'), (b'Android', b'Android')])), 51 | ('app_binary', models.FileField(upload_to=django_mobile_app_distribution.models.normalize_ios_filename, verbose_name='IPA file')), 52 | ('bundle_identifier', models.CharField(default=b'', help_text='e.g. org.example.app', max_length=200, verbose_name='Bundle identifier')), 53 | ], 54 | options={ 55 | 'ordering': ('name', 'operating_system', '-version', '-updatedAt'), 56 | 'verbose_name': 'iOS App', 57 | 'verbose_name_plural': 'iOS Apps', 58 | }, 59 | bases=('django_mobile_app_distribution.app',), 60 | ), 61 | migrations.CreateModel( 62 | name='UserInfo', 63 | fields=[ 64 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 65 | ('language', models.CharField(default=b'en', max_length=20, choices=[(b'en', b'English'), (b'de', b'Deutsch')])), 66 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.deletion.CASCADE)), 67 | ], 68 | options={ 69 | 'verbose_name': 'Extended user info', 70 | 'verbose_name_plural': 'Extended user info', 71 | }, 72 | bases=(models.Model,), 73 | ), 74 | migrations.AddField( 75 | model_name='app', 76 | name='groups', 77 | field=models.ManyToManyField(related_name='apps', default=None, to='auth.Group', blank=True, null=True, verbose_name='Groups'), 78 | preserve_default=True, 79 | ), 80 | migrations.AddField( 81 | model_name='app', 82 | name='user', 83 | field=models.ForeignKey(related_name='apps', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, verbose_name='User', on_delete=models.deletion.CASCADE), 84 | preserve_default=True, 85 | ), 86 | ] 87 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from os.path import join 4 | from django.conf import settings 5 | 6 | 7 | def get(key, default): 8 | return getattr(settings, key, default) 9 | 10 | IOS = 'iOS' 11 | IOS_PLIST = 'iOS Plist' 12 | ANDROID = 'Android' 13 | OS_CHOICES = ( 14 | (IOS, 'iOS'), 15 | (ANDROID, 'Android'), 16 | ) 17 | 18 | MOBILE_APP_DISTRIBUTION_CONTENT_TYPES = { 19 | IOS: 'application/octet-stream ipa', 20 | IOS_PLIST: 'text/xml plist', 21 | ANDROID: 'application/vnd.android.package-archive' 22 | } 23 | 24 | MOBILE_APP_DISTRIBUTION_IOS_UPLOAD_TO_DIRECTORY_NAME = get( 25 | 'MOBILE_APP_DISTRIBUTION_IOS_UPLOAD_TO_DIRECTORY_NAME', 26 | 'ios_apps' 27 | ) 28 | MOBILE_APP_DISTRIBUTION_ANDROID_UPLOAD_TO_DIRECTORY_NAME = get( 29 | 'MOBILE_APP_DISTRIBUTION_ANDROID_UPLOAD_TO_DIRECTORY_NAME', 30 | 'android_apps' 31 | ) 32 | MOBILE_APP_DISTRIBUTION_APP_ICON_DIRECTORY_NAME = get( 33 | 'MOBILE_APP_DISTRIBUTION_ANDROID_UPLOAD_TO_DIRECTORY_NAME', 34 | 'app_icons' 35 | ) 36 | MOBILE_APP_DISTRIBUTION_ANDROID_FILE_STORAGE_PATH = join( 37 | get('settings.BASE_PATH', settings.BASE_DIR), 38 | get('MOBILE_APP_DISTRIBUTION_ANDROID_FILE_STORAGE_PATH', 'android') 39 | ) 40 | 41 | EMAIL_LINK_COLOR_HEX = '#267d87' 42 | 43 | ENGLISH = 'en' 44 | GERMAN = 'de' 45 | JAPANESE = 'ja' 46 | 47 | LANGUAGES = ( 48 | (ENGLISH, 'English'), 49 | (GERMAN, 'Deutsch'), 50 | (JAPANESE, '日本語'), 51 | ) 52 | 53 | PLIST_APP_URL = '__app_url__' 54 | PLIST_BUNDLE_IDENTIFIER = '__bundle_identifier__' 55 | PLIST_BUNDLE_VERSION = '__bundle_version__' 56 | PLIST_APP_TITLE = '__app_title__' 57 | PLIST_DISPLAY_IMAGE = '__display_image' 58 | PLIST_FULL_SIZE_IMAGE = '__full_size_image' 59 | 60 | IOS_PLIST_BLUEPRINT = """ 61 | 62 | 63 | 64 | items 65 | 66 | 67 | assets 68 | 69 | 70 | kind 71 | software-package 72 | url 73 | {url} 74 | 75 | 76 | metadata 77 | 78 | bundle-identifier 79 | {bundle_id}-ios8 80 | bundle-version 81 | {bundle_version} 82 | kind 83 | software 84 | title 85 | {app_title} 86 | 87 | 88 | 89 | 90 | 91 | """.format( 92 | url=PLIST_APP_URL, 93 | bundle_id=PLIST_BUNDLE_IDENTIFIER, 94 | bundle_version=PLIST_BUNDLE_VERSION, 95 | app_title=PLIST_APP_TITLE 96 | ) 97 | 98 | # Docs: http://help.apple.com/deployment/ios/#/apda0e3426d7 99 | IOS_PLIST_BLUEPRINT_IOS9 = """ 100 | 101 | 102 | 103 | items 104 | 105 | 106 | assets 107 | 108 | 109 | kind 110 | software-package 111 | url 112 | {url} 113 | 114 | 115 | kind 116 | display-image 117 | url 118 | {display_image} 119 | 120 | 121 | kind 122 | full-size-image 123 | url 124 | {full_size_image} 125 | 126 | 127 | metadata 128 | 129 | bundle-identifier 130 | {bundle_id} 131 | bundle-version 132 | {bundle_version} 133 | kind 134 | software 135 | title 136 | {app_title} 137 | 138 | 139 | 140 | 141 | 142 | """.format( 143 | url=PLIST_APP_URL, 144 | bundle_id=PLIST_BUNDLE_IDENTIFIER, 145 | bundle_version=PLIST_BUNDLE_VERSION, 146 | app_title=PLIST_APP_TITLE, 147 | display_image=PLIST_DISPLAY_IMAGE, 148 | full_size_image=PLIST_FULL_SIZE_IMAGE 149 | ) 150 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import logging 4 | import os.path 5 | from itertools import chain 6 | from operator import attrgetter 7 | from os.path import basename 8 | from wsgiref.util import FileWrapper 9 | 10 | from future.builtins import (int, map, open) 11 | from django.contrib.auth.decorators import login_required 12 | try: 13 | from django.contrib.sites.models import get_current_site 14 | except ImportError: 15 | from django.contrib.sites.shortcuts import get_current_site 16 | from django.core.exceptions import MultipleObjectsReturned 17 | from django.http import HttpResponse, HttpResponseForbidden, Http404 18 | from django.shortcuts import render 19 | from django.utils import translation 20 | 21 | import django_mobile_app_distribution.settings as app_dist_settings 22 | from django_mobile_app_distribution.models import IosApp, AndroidApp, UserInfo 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | 27 | @login_required 28 | def index(request): 29 | try: 30 | # Activate client's language preference 31 | lang = request.user.userinfo.language 32 | translation.activate(lang) 33 | except UserInfo.DoesNotExist: 34 | pass 35 | 36 | ios_user_apps = IosApp.objects.filter(user_id__exact=request.user.id) 37 | android_user_apps = AndroidApp.objects.filter(user_id__exact=request.user.id) 38 | apps = list(chain(ios_user_apps, android_user_apps)) 39 | 40 | ios_group_apps = IosApp.objects.filter(groups__in=request.user.groups.all()) 41 | android_group_apps = AndroidApp.objects.filter(groups__in=request.user.groups.all()) 42 | group_apps = list(chain(ios_group_apps, android_group_apps)) 43 | for group_app in group_apps: 44 | if group_app not in apps: 45 | apps.append(group_app) 46 | 47 | apps.sort(key=attrgetter('updatedAt'), reverse=True) 48 | apps.sort(key=attrgetter('version'), reverse=True) 49 | apps.sort(key=attrgetter('operating_system'), reverse=True) # let iOS come first 50 | apps.sort(key=attrgetter('name')) 51 | 52 | return render(request, 'django_mobile_app_distribution/app_list.html', { 53 | 'apps': apps, 54 | 'ios_identifier': app_dist_settings.IOS, 55 | 'site_url': get_current_site(request).domain 56 | }) 57 | 58 | 59 | @login_required 60 | def send_apk(request, app_id): 61 | """ 62 | Send a file through Django without loading the whole file into 63 | memory at once. The FileWrapper will turn the file object into an 64 | iterator for chunks of 8KB. 65 | """ 66 | android_app = None 67 | try: 68 | android_app = AndroidApp.objects.get(pk=app_id) 69 | except (AndroidApp.DoesNotExist, MultipleObjectsReturned): 70 | return HttpResponse('App does not exist', status=404) 71 | 72 | authenticated = False 73 | if android_app.user: 74 | if android_app.user.id == request.user.id: 75 | authenticated = True 76 | 77 | if not authenticated: 78 | app_group_ids = android_app.groups.all().values_list('pk', flat=True) 79 | app_group_ids = list(map(int, app_group_ids)) 80 | for user_group in request.user.groups.all(): 81 | user_group_id = int(user_group.id) 82 | if user_group_id in app_group_ids: 83 | authenticated = True 84 | break 85 | 86 | if not authenticated: 87 | return HttpResponseForbidden('This is not your app') 88 | 89 | filename = os.path.join( 90 | app_dist_settings.MOBILE_APP_DISTRIBUTION_ANDROID_FILE_STORAGE_PATH, 91 | android_app.app_binary.name 92 | ) 93 | response = HttpResponse(FileWrapper(open(filename, 'rb'))) 94 | response['Content-Length'] = os.path.getsize(filename) 95 | response['Content-Type'] = app_dist_settings.MOBILE_APP_DISTRIBUTION_CONTENT_TYPES[android_app.operating_system] 96 | response['Content-Disposition'] = 'inline; filename=%s' % basename(filename) 97 | return response 98 | 99 | 100 | def ios_app_plist(request, app_id): 101 | 102 | ios_app = None 103 | try: 104 | ios_app = IosApp.objects.get(pk=app_id) 105 | except (IosApp.DoesNotExist, MultipleObjectsReturned): 106 | raise Http404 107 | 108 | from . import settings as mad_settings 109 | plist = '' 110 | if ios_app.display_image and ios_app.full_size_image: 111 | plist = mad_settings.IOS_PLIST_BLUEPRINT_IOS9 112 | plist = plist.replace(mad_settings.PLIST_DISPLAY_IMAGE, ios_app.get_display_image_url()) 113 | plist = plist.replace(mad_settings.PLIST_FULL_SIZE_IMAGE, ios_app.get_full_size_image_url()) 114 | else: 115 | plist = mad_settings.IOS_PLIST_BLUEPRINT 116 | 117 | plist = plist.replace(mad_settings.PLIST_APP_URL, ios_app.get_binary_url()) 118 | plist = plist.replace(mad_settings.PLIST_BUNDLE_IDENTIFIER, ios_app.bundle_identifier) 119 | plist = plist.replace(mad_settings.PLIST_BUNDLE_VERSION, ios_app.version) 120 | plist = plist.replace(mad_settings.PLIST_APP_TITLE, ios_app.name) 121 | 122 | return HttpResponse( 123 | plist, 124 | content_type=mad_settings.MOBILE_APP_DISTRIBUTION_CONTENT_TYPES[mad_settings.IOS_PLIST] 125 | ) 126 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/ja/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-04-08 22:08+0900\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=1; plural=0;\n" 20 | 21 | #: admin.py:73 22 | #, python-format 23 | msgid "" 24 | "Version %(app_version)s of %(app_name)s for %(os)s is available for download" 25 | msgstr "" 26 | "%(os)s 版 %(app_name)s のバージョン %(app_version)s がダウンロードできます" 27 | 28 | #: admin.py:75 29 | #, python-format 30 | msgid "" 31 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 32 | "download.\n" 33 | "Please visit %(download_url)s to install the app." 34 | msgstr "" 35 | "%(os)s 版 %(app_name)s のバージョン %(app_version)s がダウンロードできま" 36 | "す。%(download_url)s からインストールしてください。" 37 | 38 | #: admin.py:90 39 | #, python-format 40 | msgid "" 41 | "%(recipient_count)s user was notified of %(app_name)s %(app_version)s " 42 | "availability." 43 | msgid_plural "" 44 | "%(recipient_count)s users were notified of %(app_name)s %(app_version)s " 45 | "availability." 46 | msgstr[0] "" 47 | "%(recipient_count)s 人のユーザーに %(app_name)s %(app_version)s が利用可能に" 48 | "なったことを通知しました。" 49 | msgstr[1] "" 50 | "%(recipient_count)s 人のユーザーに %(app_name)s %(app_version)s が利用可能に" 51 | "なったことを通知しました。" 52 | 53 | #: admin.py:100 54 | msgid "Nobody was notified by email because nobody's email address is set." 55 | msgstr "" 56 | "送信先メールアドレスが設定されていないため、メール通知はされませんでした。" 57 | 58 | #: admin.py:103 59 | msgid "Notify clients of app availability" 60 | msgstr "アプリが利用可能になったことをクライアントに通知する" 61 | 62 | #: admin.py:110 models.py:65 63 | msgid "User" 64 | msgstr "ユーザー" 65 | 66 | #: admin.py:118 models.py:71 67 | msgid "Groups" 68 | msgstr "グループ" 69 | 70 | #: admin.py:127 admin.py:142 71 | msgid "App info" 72 | msgstr "アプリ情報" 73 | 74 | #: admin.py:130 75 | msgid "Provide these deploy on iOS 9" 76 | msgstr "iOS 9 向け" 77 | 78 | #: forms.py:20 79 | msgid "Please assign a user or group to the app." 80 | msgstr "アプリのユーザーまたはグループを入力してください。" 81 | 82 | #: forms.py:37 83 | msgid "Please provide a display and a full size image." 84 | msgstr "フルサイズ画像とディスプレイ画像を提供してください。" 85 | 86 | #: models.py:52 87 | msgid "user" 88 | msgstr "ユーザー" 89 | 90 | #: models.py:53 91 | msgid "language" 92 | msgstr "言語" 93 | 94 | #: models.py:59 models.py:60 95 | msgid "Extended user info" 96 | msgstr "拡張ユーザー情報" 97 | 98 | #: models.py:73 99 | msgid "App name" 100 | msgstr "アプリ名" 101 | 102 | #: models.py:74 103 | msgid "Comment" 104 | msgstr "コメント" 105 | 106 | #: models.py:75 107 | msgid "Bundle version" 108 | msgstr "バンドルバージョン" 109 | 110 | #: models.py:76 111 | msgid "updated date" 112 | msgstr "更新日時" 113 | 114 | #: models.py:77 115 | msgid "created date" 116 | msgstr "作成日時" 117 | 118 | #: models.py:89 models.py:171 119 | msgid "Operating system" 120 | msgstr "オペレーティングシステム" 121 | 122 | #: models.py:92 123 | msgid "IPA file" 124 | msgstr "IPA ファイル" 125 | 126 | #: models.py:95 127 | msgid "Bundle identifier" 128 | msgstr "バンドル ID" 129 | 130 | #: models.py:97 131 | msgid "e.g. org.example.app" 132 | msgstr "例: org.example.app" 133 | 134 | #: models.py:101 135 | msgid "display image" 136 | msgstr "ディスプレイ画像" 137 | 138 | #: models.py:108 139 | msgid "full size image" 140 | msgstr "フルサイズ画像" 141 | 142 | #: models.py:158 143 | msgid "iOS App" 144 | msgstr "iOS アプリ" 145 | 146 | #: models.py:159 147 | msgid "iOS Apps" 148 | msgstr "iOS アプリ" 149 | 150 | #: models.py:174 151 | msgid "APK file" 152 | msgstr "APK ファイル" 153 | 154 | #: models.py:177 155 | msgid "Android App" 156 | msgstr "Android アプリ" 157 | 158 | #: models.py:178 159 | msgid "Android Apps" 160 | msgstr "Android アプリ" 161 | 162 | #: templates/admin/base_site.html:4 163 | msgid "Django site admin" 164 | msgstr "Django サイト管理" 165 | 166 | #: templates/admin/base_site.html:7 167 | msgid "Django administration" 168 | msgstr "Django 管理" 169 | 170 | #: templates/django_mobile_app_distribution/app_list.html:5 171 | msgid "app list view" 172 | msgstr "アプリ一覧ビュー" 173 | 174 | #: templates/django_mobile_app_distribution/app_list.html:15 175 | msgid "logout" 176 | msgstr "ログアウト" 177 | 178 | #: templates/django_mobile_app_distribution/app_list.html:31 179 | msgid "operating system" 180 | msgstr "オペレーティングシステム" 181 | 182 | #: templates/django_mobile_app_distribution/app_list.html:37 183 | msgid "version" 184 | msgstr "バージョン" 185 | 186 | #: templates/django_mobile_app_distribution/app_list.html:43 187 | msgid "build date" 188 | msgstr "ビルド日時" 189 | 190 | #: templates/django_mobile_app_distribution/app_list.html:49 191 | msgid "comment" 192 | msgstr "コメント" 193 | 194 | #: templates/django_mobile_app_distribution/app_list.html:58 195 | #: templates/django_mobile_app_distribution/app_list.html:62 196 | msgid "Install App" 197 | msgstr "アプリをインストール" 198 | 199 | #: templates/django_mobile_app_distribution/base.html:5 200 | msgid "Mobile App Distribution" 201 | msgstr "モバイルアプリ配布" 202 | 203 | #: templates/django_mobile_app_distribution/email_notification.html:14 204 | #, python-format 205 | msgid "" 206 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 207 | "download.
Please visit %(download_url)s to install the app." 208 | msgstr "" 209 | "%(os)s 版 %(app_name)s のバージョン %(app_version)s がダウンロードできます。" 210 | "
%(download_url)s からインストールしてください。" 211 | 212 | #: templates/django_mobile_app_distribution/login.html:4 213 | msgid "customer login" 214 | msgstr "顧客ログイン" 215 | 216 | #: templates/django_mobile_app_distribution/login.html:31 217 | msgid "invalid login credentials" 218 | msgstr "不正なログイン認証" 219 | 220 | #: templates/django_mobile_app_distribution/login.html:38 221 | #: templates/django_mobile_app_distribution/logout.html:14 222 | msgid "login" 223 | msgstr "ログイン" 224 | 225 | #: templates/django_mobile_app_distribution/logout.html:4 226 | msgid "logged out successfully" 227 | msgstr "ログアウトしました" 228 | 229 | #: templates/django_mobile_app_distribution/logout.html:9 230 | msgid "You were logged out successfully." 231 | msgstr "ログインしました。" 232 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import logging 5 | 6 | from django.contrib import admin, messages 7 | from django.contrib.auth.models import User 8 | 9 | try: 10 | from django.contrib.sites.models import get_current_site 11 | except ImportError: 12 | from django.contrib.sites.shortcuts import get_current_site 13 | from django.core.mail import EmailMultiAlternatives 14 | import django 15 | if django.VERSION >= (1, 10): 16 | from django.urls import reverse 17 | else: 18 | from django.core.urlresolvers import reverse 19 | from django.template.loader import render_to_string 20 | from django.utils import translation 21 | from django.utils.translation import ugettext_lazy as _, ungettext_lazy 22 | 23 | from django_mobile_app_distribution import settings as _settings 24 | from django_mobile_app_distribution.models import IosApp, AndroidApp 25 | from django_mobile_app_distribution.forms import iOSAppAdminForm, AndroidAppAdminForm 26 | from django_mobile_app_distribution.models import UserInfo 27 | 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | 32 | class UserInfoAdmin(admin.ModelAdmin): 33 | model = UserInfo 34 | list_display = ['user', 'language'] 35 | list_editable = ['language'] 36 | search_fields = ['user__username'] 37 | 38 | 39 | class NotifiableModelAdmin(admin.ModelAdmin): 40 | actions = ['notify_client'] 41 | search_fields = ['name', 'user__username', 'groups__name', 'comment'] 42 | 43 | def notify_client(self, request, queryset): 44 | for app in queryset.all(): 45 | recipients = [] 46 | 47 | if app.user and app.user.email: 48 | recipients.append(app.user.email) 49 | 50 | if app.groups.count() > 0: 51 | group_users = User.objects.filter(groups__in=app.groups.all()) 52 | for user in group_users: 53 | if user.email and user.email not in recipients: 54 | recipients.append(user.email) 55 | 56 | if recipients: 57 | recipient_count = len(recipients) 58 | if request.user.email and request.user.email not in recipients: 59 | recipients.append(request.user.email) 60 | 61 | try: 62 | # Try and send email in client's preferred language 63 | # This doesn't make much sense for apps distributed to groups 64 | # hence the catch all except clause 65 | lang = app.user.userinfo.language 66 | translation.activate(lang) 67 | except Exception: 68 | pass 69 | 70 | domain = get_current_site(request).domain 71 | index_url = reverse('django_mobile_app_distribution_index') 72 | data = { 73 | 'email_link_color_hex': _settings.EMAIL_LINK_COLOR_HEX, 74 | 'app_name': app.name, 75 | 'app_version': app.version, 76 | 'os': app.operating_system, 77 | 'download_url': '/'.join(s.strip('/') for s in (domain, index_url)) 78 | } 79 | 80 | email = EmailMultiAlternatives() 81 | email.bcc = recipients 82 | email.subject = _('Version %(app_version)s of %(app_name)s for %(os)s is available for download') % data 83 | email.body = _( 84 | 'Version %(app_version)s of %(app_name)s for %(os)s is available for download.\n' 85 | 'Please visit %(download_url)s to install the app.' 86 | ) % data 87 | email.attach_alternative( 88 | render_to_string('django_mobile_app_distribution/email_notification.html', data), 89 | 'text/html' 90 | ) 91 | 92 | # Reset to system language 93 | translation.deactivate() 94 | 95 | email.send(fail_silently=False) 96 | messages.add_message( 97 | request, 98 | messages.INFO, ungettext_lazy( 99 | '%(recipient_count)s user was notified of %(app_name)s %(app_version)s availability.', 100 | '%(recipient_count)s users were notified of %(app_name)s %(app_version)s availability.', 101 | recipient_count) % { 102 | 'recipient_count' : recipient_count, 103 | 'app_name' : app.name, 104 | 'app_version': app.version 105 | }, 106 | fail_silently=True) 107 | else: 108 | messages.add_message( 109 | request, messages.ERROR, _('Nobody was notified by email because nobody\'s email address is set.'), 110 | fail_silently=True 111 | ) 112 | notify_client.short_description = _('Notify clients of app availability') 113 | 114 | def user_display_name(self, instance): 115 | if instance.user: 116 | return instance.user.username 117 | else: 118 | return '' 119 | user_display_name.short_description = _('User') 120 | user_display_name.admin_order_field = 'user' 121 | 122 | def groups_display_name(self, instance): 123 | if instance.groups.count() > 0: 124 | return ", ".join(str(group) for group in instance.groups.all()) 125 | else: 126 | return '' 127 | groups_display_name.short_description = _('Groups') 128 | 129 | 130 | class IosAppAdmin(NotifiableModelAdmin): 131 | form = iOSAppAdminForm 132 | list_display = ('name', 'user_display_name', 'groups_display_name', 'version', 'comment', 'updatedAt') 133 | filter_horizontal = ['groups'] 134 | 135 | fieldsets = ( 136 | (_('App info'), { 137 | 'fields': ('user', 'groups', 'name', 'version', 'bundle_identifier', 'app_binary', 'comment') 138 | }), 139 | (_('Provide these deploy on iOS 9'), { 140 | 'fields': ('display_image', 'full_size_image') 141 | }), 142 | ) 143 | 144 | 145 | class AndroidAppAdmin(NotifiableModelAdmin): 146 | form = AndroidAppAdminForm 147 | list_display = ('name', 'user_display_name', 'groups_display_name', 'version', 'comment', 'updatedAt') 148 | filter_horizontal = ['groups'] 149 | 150 | fieldsets = ( 151 | (_('App info'), { 152 | 'fields': ('user', 'groups', 'name', 'version', 'app_binary', 'comment') 153 | }), 154 | ) 155 | 156 | 157 | admin.site.register(IosApp, IosAppAdmin) 158 | admin.site.register(AndroidApp, AndroidAppAdmin) 159 | admin.site.register(UserInfo, UserInfoAdmin) 160 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Django Mobile App Distribution 1.0\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2016-04-08 18:49+0200\n" 11 | "PO-Revision-Date: 2016-04-08 18:50+0200\n" 12 | "Last-Translator: Moritz Pfeiffer \n" 13 | "Language-Team: Alp Phone GmbH \n" 14 | "Language: en\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "X-Generator: Poedit 1.8.7\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: admin.py:76 22 | #, python-format 23 | msgid "" 24 | "Version %(app_version)s of %(app_name)s for %(os)s is available for download" 25 | msgstr "" 26 | 27 | #: admin.py:78 28 | #, python-format 29 | msgid "" 30 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 31 | "download.\n" 32 | "Please visit %(download_url)s to install the app." 33 | msgstr "" 34 | 35 | #: admin.py:93 36 | #, python-format 37 | msgid "" 38 | "%(recipient_count)s user was notified of %(app_name)s %(app_version)s " 39 | "availability." 40 | msgid_plural "" 41 | "%(recipient_count)s users were notified of %(app_name)s %(app_version)s " 42 | "availability." 43 | msgstr[0] "" 44 | msgstr[1] "" 45 | 46 | #: admin.py:103 47 | msgid "Nobody was notified by email because nobody's email address is set." 48 | msgstr "" 49 | 50 | #: admin.py:106 51 | msgid "Notify clients of app availability" 52 | msgstr "" 53 | 54 | #: admin.py:113 models.py:65 55 | msgid "User" 56 | msgstr "" 57 | 58 | #: admin.py:121 models.py:71 59 | msgid "Groups" 60 | msgstr "" 61 | 62 | #: admin.py:130 admin.py:145 63 | msgid "App info" 64 | msgstr "App info" 65 | 66 | #: admin.py:133 67 | msgid "Provide these deploy on iOS 9" 68 | msgstr "" 69 | 70 | #: forms.py:20 71 | msgid "Please assign a user or group to the app." 72 | msgstr "" 73 | 74 | #: forms.py:37 75 | msgid "Please provide a display and a full size image." 76 | msgstr "" 77 | 78 | #: models.py:52 79 | msgid "user" 80 | msgstr "User" 81 | 82 | #: models.py:53 83 | msgid "language" 84 | msgstr "" 85 | 86 | #: models.py:59 models.py:60 87 | msgid "Extended user info" 88 | msgstr "" 89 | 90 | #: models.py:73 91 | msgid "App name" 92 | msgstr "App name" 93 | 94 | #: models.py:74 95 | msgid "Comment" 96 | msgstr "Comment" 97 | 98 | #: models.py:75 99 | msgid "Bundle version" 100 | msgstr "Bundle version" 101 | 102 | #: models.py:76 103 | msgid "updated date" 104 | msgstr "Updated at" 105 | 106 | #: models.py:77 107 | msgid "created date" 108 | msgstr "Created at" 109 | 110 | #: models.py:89 models.py:171 111 | msgid "Operating system" 112 | msgstr "Operating System" 113 | 114 | #: models.py:92 115 | msgid "IPA file" 116 | msgstr "IPA file" 117 | 118 | #: models.py:95 119 | msgid "Bundle identifier" 120 | msgstr "" 121 | 122 | #: models.py:97 123 | msgid "e.g. org.example.app" 124 | msgstr "" 125 | 126 | #: models.py:101 127 | msgid "display image" 128 | msgstr "" 129 | 130 | #: models.py:108 131 | msgid "full size image" 132 | msgstr "" 133 | 134 | #: models.py:158 135 | msgid "iOS App" 136 | msgstr "" 137 | 138 | #: models.py:159 139 | msgid "iOS Apps" 140 | msgstr "" 141 | 142 | #: models.py:174 143 | msgid "APK file" 144 | msgstr "APK file" 145 | 146 | #: models.py:177 147 | msgid "Android App" 148 | msgstr "Android app" 149 | 150 | #: models.py:178 151 | msgid "Android Apps" 152 | msgstr "Android apps" 153 | 154 | #: templates/admin/base_site.html:4 155 | msgid "Django site admin" 156 | msgstr "Ad Hoc management" 157 | 158 | #: templates/admin/base_site.html:7 159 | msgid "Django administration" 160 | msgstr "Mobile App Distribution" 161 | 162 | #: templates/django_mobile_app_distribution/app_list.html:4 163 | msgid "app list view" 164 | msgstr "App overview" 165 | 166 | #: templates/django_mobile_app_distribution/app_list.html:14 167 | msgid "logout" 168 | msgstr "Logout" 169 | 170 | #: templates/django_mobile_app_distribution/app_list.html:30 171 | msgid "operating system" 172 | msgstr "Operating System" 173 | 174 | #: templates/django_mobile_app_distribution/app_list.html:36 175 | msgid "version" 176 | msgstr "Version" 177 | 178 | #: templates/django_mobile_app_distribution/app_list.html:42 179 | msgid "build date" 180 | msgstr "Created on" 181 | 182 | #: templates/django_mobile_app_distribution/app_list.html:48 183 | msgid "comment" 184 | msgstr "Comment" 185 | 186 | #: templates/django_mobile_app_distribution/app_list.html:57 187 | #: templates/django_mobile_app_distribution/app_list.html:61 188 | msgid "Install App" 189 | msgstr "Install App" 190 | 191 | #: templates/django_mobile_app_distribution/base.html:5 192 | msgid "Mobile App Distribution" 193 | msgstr "" 194 | 195 | #: templates/django_mobile_app_distribution/email_notification.html:14 196 | #, python-format 197 | msgid "" 198 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 199 | "download.
Please visit %(download_url)s to install the app." 200 | msgstr "" 201 | 202 | #: templates/django_mobile_app_distribution/login.html:3 203 | msgid "customer login" 204 | msgstr "Customer center" 205 | 206 | #: templates/django_mobile_app_distribution/login.html:30 207 | msgid "invalid login credentials" 208 | msgstr "The combination of user name and password you entered is invalid." 209 | 210 | #: templates/django_mobile_app_distribution/login.html:37 211 | #: templates/django_mobile_app_distribution/logout.html:13 212 | msgid "login" 213 | msgstr "Login" 214 | 215 | #: templates/django_mobile_app_distribution/logout.html:3 216 | msgid "logged out successfully" 217 | msgstr "Logged out successfully" 218 | 219 | #: templates/django_mobile_app_distribution/logout.html:8 220 | msgid "You were logged out successfully." 221 | msgstr "Logged out successfully." 222 | 223 | #~ msgid "" 224 | #~ "\n" 225 | #~ "\t\tYou were logged out successfully. Log in again.\n" 227 | #~ "\t" 228 | #~ msgstr "" 229 | #~ "\n" 230 | #~ "\t\tYou were logged out successfully. Log in again.\n" 232 | #~ "\t" 233 | 234 | #~ msgid "download file" 235 | #~ msgstr "File" 236 | 237 | #~ msgid "Binary info" 238 | #~ msgstr "File info" 239 | 240 | #~ msgid "Don't change this file name!" 241 | #~ msgstr "Don't change this file name!" 242 | 243 | #~ msgid "" 244 | #~ "Paste the file name and Ad Hoc URL into the Xcode Enterprise Ad Hoc " 245 | #~ "dialog." 246 | #~ msgstr "" 247 | #~ "Paste the file name and Ad Hoc URL into the Xcode Enterprise Ad Hoc " 248 | #~ "dialog." 249 | 250 | #~ msgid "File name:" 251 | #~ msgstr "File name:" 252 | 253 | #~ msgid "Ad Hoc URL:" 254 | #~ msgstr "Ad Hoc URL:" 255 | 256 | #~ msgid "Version" 257 | #~ msgstr "Version" 258 | 259 | #~ msgid "Build date" 260 | #~ msgstr "Created on" 261 | 262 | #~ msgid "File name" 263 | #~ msgstr "File name:" 264 | 265 | #~ msgid "Ad Hoc ipa file" 266 | #~ msgstr "Ad Hoc ipa file" 267 | 268 | #~ msgid "Ad Hoc plist" 269 | #~ msgstr "Ad Hoc plist" 270 | 271 | #~ msgid "over the air ad hoc app management" 272 | #~ msgstr "Over the air Ad Hoc App Management" 273 | 274 | #~ msgid "file name" 275 | #~ msgstr "File name" 276 | 277 | #~ msgid "ios app" 278 | #~ msgstr "iOS app" 279 | 280 | #~ msgid "ios apps" 281 | #~ msgstr "iOS Apps" 282 | 283 | #~ msgid "ios install link" 284 | #~ msgstr "iOS install link" 285 | 286 | #~ msgid "not set" 287 | #~ msgstr "Not set" 288 | 289 | #~ msgid "plist url" 290 | #~ msgstr "Plist url" 291 | 292 | #~ msgid "app" 293 | #~ msgstr "App" 294 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | import logging 5 | import os 6 | from unicodedata import normalize 7 | 8 | import django 9 | 10 | if django.VERSION >= (1, 10): 11 | from django.urls import reverse 12 | else: 13 | from django.core.urlresolvers import reverse 14 | 15 | from django.contrib.auth.models import User, Group 16 | from django.contrib.sites.models import Site 17 | from django.db import models 18 | from django.db.models.signals import post_save 19 | from django.utils import six 20 | from django.utils.encoding import python_2_unicode_compatible, force_text 21 | from django.utils.translation import ugettext_lazy as _ 22 | 23 | import django_mobile_app_distribution.settings as app_dist_settings 24 | from django_mobile_app_distribution.exceptions import MobileAppDistributionConfigurationException 25 | from django_mobile_app_distribution.storage import CustomFileSystemStorage 26 | 27 | if six.PY2: 28 | from urlparse import urljoin 29 | else: 30 | from urllib.parse import urljoin 31 | 32 | 33 | log = logging.getLogger(__name__) 34 | 35 | 36 | _MISSING_SITE_MESSAGE = "The site framework's domain name is used to generate the plist and binary links. " \ 37 | "Please configure your current site properly. Also make sure that the SITE_ID in your " \ 38 | "settings file matches the primary key of your current site." 39 | 40 | def normalize_filename(dirname, filename): 41 | dirname = force_text(dirname) 42 | filename = force_text(normalize('NFKD', filename).encode('ascii', 'ignore')) 43 | return force_text(os.path.join(dirname, filename)) 44 | 45 | 46 | def normalize_ios_filename(instance, filename): 47 | return normalize_filename(app_dist_settings.MOBILE_APP_DISTRIBUTION_IOS_UPLOAD_TO_DIRECTORY_NAME, filename) 48 | 49 | 50 | def normalize_android_filename(instance, filename): 51 | return normalize_filename(app_dist_settings.MOBILE_APP_DISTRIBUTION_ANDROID_UPLOAD_TO_DIRECTORY_NAME, filename) 52 | 53 | def normalize_image_filename(instance, filename): 54 | return normalize_filename(app_dist_settings.MOBILE_APP_DISTRIBUTION_APP_ICON_DIRECTORY_NAME, filename) 55 | 56 | 57 | @python_2_unicode_compatible 58 | class UserInfo(models.Model): 59 | user = models.OneToOneField(User, verbose_name=_('user'), on_delete=models.deletion.CASCADE) 60 | language = models.CharField(max_length=20, choices=app_dist_settings.LANGUAGES, default=app_dist_settings.ENGLISH, verbose_name=_('language')) 61 | 62 | def __str__(self): 63 | return self.user.username 64 | 65 | class Meta: 66 | verbose_name = _('Extended user info') 67 | verbose_name_plural = _('Extended user info') 68 | 69 | 70 | @python_2_unicode_compatible 71 | class App(models.Model): 72 | user = models.ForeignKey(User, blank=True, null=True, default=None, related_name='apps', verbose_name=_('User'), on_delete=models.deletion.CASCADE) 73 | groups = models.ManyToManyField( 74 | Group, 75 | blank=True, 76 | related_name='apps', 77 | default=None, 78 | verbose_name=_('Groups') 79 | ) 80 | name = models.CharField(max_length=200, verbose_name=_('App name')) 81 | comment = models.CharField(max_length=200, verbose_name=_('Comment'), blank=True, null=True) 82 | version = models.CharField(max_length=200, verbose_name=_('Bundle version')) 83 | updatedAt = models.DateTimeField(auto_now=True, editable=False, verbose_name=_('updated date')) 84 | createdAt = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created date')) 85 | 86 | def __str__(self): 87 | return self.name 88 | 89 | 90 | class IosApp(App): 91 | 92 | operating_system = models.CharField( 93 | max_length=50, 94 | choices=app_dist_settings.OS_CHOICES, 95 | default=app_dist_settings.IOS, 96 | verbose_name=_('Operating system'), 97 | editable=False 98 | ) 99 | app_binary = models.FileField(upload_to=normalize_ios_filename, verbose_name=_('IPA file')) 100 | bundle_identifier = models.CharField( 101 | max_length=200, 102 | verbose_name=_('Bundle identifier'), 103 | default='', 104 | help_text=_('e.g. org.example.app') 105 | ) 106 | display_image = models.ImageField( 107 | upload_to=normalize_image_filename, 108 | verbose_name=_('display image'), 109 | default='', 110 | help_text='57x57 PNG', 111 | blank=True 112 | ) 113 | full_size_image = models.ImageField( 114 | upload_to=normalize_image_filename, 115 | verbose_name=_('full size image'), 116 | default='', 117 | help_text='512x512 PNG', 118 | blank=True 119 | ) 120 | 121 | def get_binary_url(self): 122 | if not self.app_binary: 123 | return None 124 | Site.objects.clear_cache() 125 | try: 126 | Site.objects.get_current() 127 | except Exception: 128 | raise MobileAppDistributionConfigurationException(_MISSING_SITE_MESSAGE) 129 | 130 | return urljoin(Site.objects.get_current().domain, self.app_binary.url) 131 | 132 | def get_plist_url(self): 133 | Site.objects.clear_cache() 134 | current_site = None 135 | try: 136 | current_site = Site.objects.get_current() 137 | except Exception: 138 | raise MobileAppDistributionConfigurationException(_MISSING_SITE_MESSAGE) 139 | return urljoin( 140 | current_site.domain, 141 | reverse('django_mobile_app_distribution_ios_app_plist', kwargs={'app_id': self.pk}) 142 | ) 143 | 144 | def get_display_image_url(self): 145 | Site.objects.clear_cache() 146 | current_site = None 147 | try: 148 | current_site = Site.objects.get_current() 149 | except Exception: 150 | raise MobileAppDistributionConfigurationException(_MISSING_SITE_MESSAGE) 151 | 152 | return urljoin(Site.objects.get_current().domain, self.display_image.url) 153 | 154 | def get_full_size_image_url(self): 155 | Site.objects.clear_cache() 156 | current_site = None 157 | try: 158 | current_site = Site.objects.get_current() 159 | except Exception: 160 | raise MobileAppDistributionConfigurationException(_MISSING_SITE_MESSAGE) 161 | 162 | return urljoin(Site.objects.get_current().domain, self.full_size_image.url) 163 | 164 | class Meta: 165 | verbose_name = _('iOS App') 166 | verbose_name_plural = _('iOS Apps') 167 | ordering = ('name', 'operating_system', '-version', '-updatedAt',) 168 | 169 | 170 | fs = CustomFileSystemStorage() 171 | 172 | 173 | class AndroidApp(App): 174 | operating_system = models.CharField( 175 | max_length=50, 176 | choices=app_dist_settings.OS_CHOICES, 177 | default=app_dist_settings.ANDROID, 178 | verbose_name=_('Operating system'), 179 | editable=False 180 | ) 181 | app_binary = models.FileField(upload_to=normalize_android_filename, verbose_name=_('APK file'), storage=fs) 182 | 183 | class Meta: 184 | verbose_name = _('Android App') 185 | verbose_name_plural = _('Android Apps') 186 | ordering = ( 'name', 'operating_system', '-version', '-updatedAt',) 187 | 188 | 189 | def create_user_info(sender, instance, created, **kwargs): 190 | try: 191 | instance.userinfo 192 | except Exception: 193 | try: 194 | UserInfo.objects.create(user=instance) 195 | except Exception: 196 | # happens when creating a superuser when syncdb is run before the tables are installed 197 | pass 198 | 199 | post_save.connect(create_user_info, sender=User) 200 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/vendor/fastclick.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";/** 2 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. 3 | * 4 | * @codingstandard ftlabs-jsv2 5 | * @copyright The Financial Times Limited [All Rights Reserved] 6 | * @license MIT License (see LICENSE.txt) 7 | */ 8 | function a(b,d){function e(a,b){return function(){return a.apply(b,arguments)}}var f;if(d=d||{},this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.touchBoundary=d.touchBoundary||10,this.layer=b,this.tapDelay=d.tapDelay||200,this.tapTimeout=d.tapTimeout||700,!a.notNeeded(b)){for(var g=["onMouse","onClick","onTouchStart","onTouchMove","onTouchEnd","onTouchCancel"],h=this,i=0,j=g.length;j>i;i++)h[g[i]]=e(h[g[i]],h);c&&(b.addEventListener("mouseover",this.onMouse,!0),b.addEventListener("mousedown",this.onMouse,!0),b.addEventListener("mouseup",this.onMouse,!0)),b.addEventListener("click",this.onClick,!0),b.addEventListener("touchstart",this.onTouchStart,!1),b.addEventListener("touchmove",this.onTouchMove,!1),b.addEventListener("touchend",this.onTouchEnd,!1),b.addEventListener("touchcancel",this.onTouchCancel,!1),Event.prototype.stopImmediatePropagation||(b.removeEventListener=function(a,c,d){var e=Node.prototype.removeEventListener;"click"===a?e.call(b,a,c.hijacked||c,d):e.call(b,a,c,d)},b.addEventListener=function(a,c,d){var e=Node.prototype.addEventListener;"click"===a?e.call(b,a,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(b,a,c,d)}),"function"==typeof b.onclick&&(f=b.onclick,b.addEventListener("click",function(a){f(a)},!1),b.onclick=null)}}var b=navigator.userAgent.indexOf("Windows Phone")>=0,c=navigator.userAgent.indexOf("Android")>0&&!b,d=/iP(ad|hone|od)/.test(navigator.userAgent)&&!b,e=d&&/OS 4_\d(_\d)?/.test(navigator.userAgent),f=d&&/OS [6-7]_\d/.test(navigator.userAgent),g=navigator.userAgent.indexOf("BB10")>0;a.prototype.needsClick=function(a){switch(a.nodeName.toLowerCase()){case"button":case"select":case"textarea":if(a.disabled)return!0;break;case"input":if(d&&"file"===a.type||a.disabled)return!0;break;case"label":case"iframe":case"video":return!0}return/\bneedsclick\b/.test(a.className)},a.prototype.needsFocus=function(a){switch(a.nodeName.toLowerCase()){case"textarea":return!0;case"select":return!c;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},a.prototype.sendClick=function(a,b){var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent(this.determineEventType(a),!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},a.prototype.determineEventType=function(a){return c&&"select"===a.tagName.toLowerCase()?"mousedown":"click"},a.prototype.focus=function(a){var b;d&&a.setSelectionRange&&0!==a.type.indexOf("date")&&"time"!==a.type&&"month"!==a.type?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},a.prototype.updateScrollParent=function(a){var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},a.prototype.getTargetElementFromEventTarget=function(a){return a.nodeType===Node.TEXT_NODE?a.parentNode:a},a.prototype.onTouchStart=function(a){var b,c,f;if(a.targetTouches.length>1)return!0;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],d){if(f=window.getSelection(),f.rangeCount&&!f.isCollapsed)return!0;if(!e){if(c.identifier&&c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTimec||Math.abs(b.pageY-this.touchStartY)>c?!0:!1},a.prototype.onTouchMove=function(a){return this.trackingClick?((this.targetElement!==this.getTargetElementFromEventTarget(a.target)||this.touchHasMoved(a))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},a.prototype.findControl=function(a){return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},a.prototype.onTouchEnd=function(a){var b,g,h,i,j,k=this.targetElement;if(!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTimethis.tapTimeout)return!0;if(this.cancelNextClick=!1,this.lastClickTime=a.timeStamp,g=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,f&&(j=a.changedTouches[0],k=document.elementFromPoint(j.pageX-window.pageXOffset,j.pageY-window.pageYOffset)||k,k.fastClickScrollParent=this.targetElement.fastClickScrollParent),h=k.tagName.toLowerCase(),"label"===h){if(b=this.findControl(k)){if(this.focus(k),c)return!1;k=b}}else if(this.needsFocus(k))return a.timeStamp-g>100||d&&window.top!==window&&"input"===h?(this.targetElement=null,!1):(this.focus(k),this.sendClick(k,a),d&&"select"===h||(this.targetElement=null,a.preventDefault()),!1);return d&&!e&&(i=k.fastClickScrollParent,i&&i.fastClickLastScrollTop!==i.scrollTop)?!0:(this.needsClick(k)||(a.preventDefault(),this.sendClick(k,a)),!1)},a.prototype.onTouchCancel=function(){this.trackingClick=!1,this.targetElement=null},a.prototype.onMouse=function(a){return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},a.prototype.onClick=function(a){var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},a.prototype.destroy=function(){var a=this.layer;c&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchmove",this.onTouchMove,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},a.notNeeded=function(a){var b,d,e,f;if("undefined"==typeof window.ontouchstart)return!0;if(d=+(/Chrome\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1]){if(!c)return!0;if(b=document.querySelector("meta[name=viewport]")){if(-1!==b.content.indexOf("user-scalable=no"))return!0;if(d>31&&document.documentElement.scrollWidth<=window.outerWidth)return!0}}if(g&&(e=navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/),e[1]>=10&&e[2]>=3&&(b=document.querySelector("meta[name=viewport]")))){if(-1!==b.content.indexOf("user-scalable=no"))return!0;if(document.documentElement.scrollWidth<=window.outerWidth)return!0}return"none"===a.style.msTouchAction||"manipulation"===a.style.touchAction?!0:(f=+(/Firefox\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1],f>=27&&(b=document.querySelector("meta[name=viewport]"),b&&(-1!==b.content.indexOf("user-scalable=no")||document.documentElement.scrollWidth<=window.outerWidth))?!0:"none"===a.style.touchAction||"manipulation"===a.style.touchAction?!0:!1)},a.attach=function(b,c){return new a(b,c)},"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(function(){return a}):"undefined"!=typeof module&&module.exports?(module.exports=a.attach,module.exports.FastClick=a):window.FastClick=a}(); 9 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/foundation/foundation.min.js: -------------------------------------------------------------------------------- 1 | (function(l,h,n,m){(function(a){for(var b=a.length,c=l("head");b--;)0===c.has("."+a[b]).length&&c.append('')})("foundation-mq-small foundation-mq-small-only foundation-mq-medium foundation-mq-medium-only foundation-mq-large foundation-mq-large-only foundation-mq-xlarge foundation-mq-xlarge-only foundation-mq-xxlarge foundation-data-attribute-namespace".split(" "));l(function(){"undefined"!==typeof FastClick&&"undefined"!==typeof n.body&&FastClick.attach(n.body)});var g=function(a, 2 | b){if("string"===typeof a){if(b){var c;if(b.jquery){if(c=b[0],!c)return b}else c=b;return l(c.querySelectorAll(a))}return l(n.querySelectorAll(a))}return l(a,b)},p=function(a){var b=[];a||b.push("data");0 #mq-test-1 { width: 42px; }';c.insertBefore(k,d);b=42===e.offsetWidth;c.removeChild(k); 5 | return{matches:b,media:a}}}(n);(function(a){function b(){c&&(e(b),g&&a.fx.tick())}for(var c,d=0,k=["webkit","moz"],e=h.requestAnimationFrame,f=h.cancelAnimationFrame,g="undefined"!==typeof a.fx;d").appendTo("head")[0].sheet,global:{namespace:m},init:function(a,b,c,d,k){c=[a,c,d,k];d=[];this.rtl=/rtl/i.test(g("html").attr("dir"));this.scope=a||this.scope;this.set_namespace();if(b&&"string"===typeof b&&!/reflow/i.test(b))this.libs.hasOwnProperty(b)&&d.push(this.init_lib(b,c));else for(var e in this.libs)d.push(this.init_lib(e,b));g(h).load(function(){g(h).trigger("resize.fndtn.clearing").trigger("resize.fndtn.dropdown").trigger("resize.fndtn.equalizer").trigger("resize.fndtn.interchange").trigger("resize.fndtn.joyride").trigger("resize.fndtn.magellan").trigger("resize.fndtn.topbar").trigger("resize.fndtn.slider")}); 9 | return a},init_lib:function(a,b){if(this.libs.hasOwnProperty(a)){this.patch(this.libs[a]);if(b&&b.hasOwnProperty(a))return"undefined"!==typeof this.libs[a].settings?l.extend(!0,this.libs[a].settings,b[a]):"undefined"!==typeof this.libs[a].defaults&&l.extend(!0,this.libs[a].defaults,b[a]),this.libs[a].init.apply(this.libs[a],[this.scope,b[a]]);b=b instanceof Array?b:Array(b);return this.libs[a].init.apply(this.libs[a],b)}return function(){}},patch:function(a){a.scope=this.scope;a.namespace=this.global.namespace; 10 | a.rtl=this.rtl;a.data_options=this.utils.data_options;a.attr_name=p;a.add_namespace=q;a.bindings=r;a.S=this.utils.S},inherit:function(a,b){for(var c=b.split(" "),d=c.length;d--;)this.utils.hasOwnProperty(c[d])&&(a[c[d]]=this.utils[c[d]])},set_namespace:function(){var a=this.global.namespace===m?l(".foundation-data-attribute-namespace").css("font-family"):this.global.namespace;this.global.namespace=a===m||/false/i.test(a)?"":a},libs:{},utils:{S:g,throttle:function(a,b){var c=null;return function(){var d= 11 | this,k=arguments;null==c&&(c=setTimeout(function(){a.apply(d,k);c=null},b))}},debounce:function(a,b,c){var d,k;return function(){var e=this,f=arguments,g=c&&!d;clearTimeout(d);d=setTimeout(function(){d=null;c||(k=a.apply(e,f))},b);g&&(k=a.apply(e,f));return k}},data_options:function(a,b){function c(a){return!isNaN(a-0)&&null!==a&&""!==a&&!1!==a&&!0!==a}function d(a){return"string"===typeof a?l.trim(a):a}b=b||"options";var g={},e,f,h;e=function(a){var c=Foundation.global.namespace;return 0');var c=Foundation.media_queries,d=l("."+b).css("font-family"); 13 | if("string"===typeof d||d instanceof String)d=d.replace(/^['\\/"]+|(;\s?})+|['\\/"]+$/g,"");c[a]=d}},add_custom_rule:function(a,b){b===m&&Foundation.stylesheet?Foundation.stylesheet.insertRule(a,Foundation.stylesheet.cssRules.length):Foundation.media_queries[b]!==m&&Foundation.stylesheet.insertRule("@media "+Foundation.media_queries[b]+"{ "+a+" }")},image_loaded:function(a,b){var c=this,d=a.length;0===d&&b(a);a.each(function(){t(c.S(this),function(){--d;0===d&&b(a)})})},random_str:function(){this.fidx|| 14 | (this.fidx=0);this.prefix=this.prefix||[this.name||"F",(+new Date).toString(36)].join("-");return this.prefix+(this.fidx++).toString(36)},match:function(a){return h.matchMedia(a).matches},is_small_up:function(){return this.match(Foundation.media_queries.small)},is_medium_up:function(){return this.match(Foundation.media_queries.medium)},is_large_up:function(){return this.match(Foundation.media_queries.large)},is_xlarge_up:function(){return this.match(Foundation.media_queries.xlarge)},is_xxlarge_up:function(){return this.match(Foundation.media_queries.xxlarge)}, 15 | is_small_only:function(){return!this.is_medium_up()&&!this.is_large_up()&&!this.is_xlarge_up()&&!this.is_xxlarge_up()},is_medium_only:function(){return this.is_medium_up()&&!this.is_large_up()&&!this.is_xlarge_up()&&!this.is_xxlarge_up()},is_large_only:function(){return this.is_medium_up()&&this.is_large_up()&&!this.is_xlarge_up()&&!this.is_xxlarge_up()},is_xlarge_only:function(){return this.is_medium_up()&&this.is_large_up()&&this.is_xlarge_up()&&!this.is_xxlarge_up()},is_xxlarge_only:function(){return this.is_medium_up()&& 16 | this.is_large_up()&&this.is_xlarge_up()&&this.is_xxlarge_up()}}};l.fn.foundation=function(){var a=Array.prototype.slice.call(arguments,0);return this.each(function(){Foundation.init.apply(Foundation,[this].concat(a));return this})}})(jQuery,window,window.document); 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Django Mobile App Distribution 2 | 3 | Django Mobile App Distribution is a Django app that allows you to distribute iPhone, iPad and Android apps over the air to your clients. 4 | 5 | It is made up of 2 components: 6 | 7 | * A Django Admin interface that allows you to upload and assign apps to users. 8 | * A mobile optimized, login protected download area where your clients can download apps that were associated with their login credentials. 9 | * Supports Python 2.7, 3 (tested on 3.4, 3.5) and Django >= 1.7. 10 | 11 | #Installation Django >= 1.7 12 | 13 | - ``pip install django-mobile-app-distribution`` 14 | - Add ``django_mobile_app_distribution`` to your ``INSTALLED_APPS`` list in your project's settings.py. 15 | - Add ``django.contrib.sites`` to the list of ``INSTALLED_APPS`` in your project's settings.py. 16 | - Enable the [messages framework][message_framework_20] 17 | - Make sure you have set [MEDIA_ROOT][media_root_20], [MEDIA_URL][media_url_20], [STATIC_URL][static_url_20] and [STATIC_ROOT][static_root_20]. 18 | - Add ``BASE_PATH`` (or ``BASE_DIR``) to your project's settings.py, e.g. ``import os.path BASE_PATH = os.path.dirname(__file__)``. In order to create an Android upload folder on the same level as your project's settings.py this has to be set. 19 | - Run ``python manage.py migrate`` 20 | - Run ``python manage.py collectstatic`` 21 | - If you like things tidy you can install [django-cleanup][django_cleanup_17], which removes uploaded files when the associated models are deleted. 22 | - Make sure the ``android/android_apps`` folder on the same level as your project's settings.py is readable and writable by your webserver. 23 | * If your webserver cannot create them for you, you have to create them by hand. See Security considerations below for more information. 24 | - Include ``django_mobile_app_distribution.urls`` into your project's urls.py file at the mount point of your choosing (see below). This will be where your client downloads her apps. 25 | - Include ``django_mobile_app_distribution.auth_urls`` into your project's urls.py (see below). 26 | - Add [LOGIN_REDIRECT_URL][login_redirect_url_20] to your project's settings.py. This is the URL you chose in step 7. If you're using the example below, set it to ``/distribute/``. 27 | - Add the [SITE_ID][site_id_20] value in your project's settings.py to the primary key of the Site object that represents your site. 28 | - Login to the Django Admin and add your server's URL to the Site object's domain name (create one if necessary). On the development server this would be ``http://127.0.0.1:8000/`` 29 | 30 | [site_id_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#site-id 31 | [django_cleanup_17]: https://github.com/un1t/django-cleanup 32 | [login_redirect_url_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#login-redirect-url 33 | [message_framework_20]: https://docs.djangoproject.com/en/2.0/ref/contrib/messages/ 34 | [media_root_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#media-root 35 | [media_url_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#media-url 36 | [static_root_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#static-root 37 | [static_url_20]: https://docs.djangoproject.com/en/2.0/ref/settings/#static-url 38 | 39 | #URL setup 40 | 41 | Inside your project's `urls.py` 42 | 43 | from django.conf.urls import include, url 44 | from django.contrib import admin 45 | urlpatterns = [ 46 | url(r'^admin/', include(admin.site.urls)), 47 | url(r'^distribute/', include('django_mobile_app_distribution.urls')), 48 | url(r'^accounts/', include('django_mobile_app_distribution.auth_urls')), 49 | ] 50 | 51 | 52 | Inside your project's `settings.py` file 53 | 54 | import os.path 55 | BASE_PATH = os.path.dirname(__file__) # `BASE_DIR` is also available. 56 | LOGIN_REDIRECT_URL = '/distribute/' 57 | SITE_ID = 1 58 | 59 | #Security considerations 60 | 61 | By default iOS apps are uploaded to a folder called ``ios_apps`` within your ``MEDIA_ROOT``. 62 | This should generally be safe enough as Ad Hoc iOS apps are provisioned to run on a limited number of devices. 63 | 64 | On Android however a hijacked signed APK file could be redistributed against your client's wishes which is to be avoided at all cost. 65 | To this end Android apps are uploaded with a custom instance of ``FileSystemStorage``. By default, Android apps are uploaded to a folder called ``android`` on the same level as your project's settings.py. The default upload path within the ``android`` folder is ``android_apps``. 66 | You can change the default upload and file storage paths with the following directives in your project's settings.py: 67 | 68 | * `MOBILE_APP_DISTRIBUTION_IOS_UPLOAD_TO_DIRECTORY_NAME` 69 | * `MOBILE_APP_DISTRIBUTION_ANDROID_UPLOAD_TO_DIRECTORY_NAME` 70 | * `MOBILE_APP_DISTRIBUTION_ANDROID_FILE_STORAGE_PATH` 71 | 72 | .. note:: Make sure the ``android/android_apps`` folder is readable and writable by your webserver, but not served by your webserver. 73 | 74 | #Notify clients of available app downloads by email 75 | 76 | Django Mobile App Distribution exposes an Admin Action that allows you to notify your clients once you've uploaded and app. 77 | An email message is generated that contains a link to the download page. 78 | In order for email messaging to work you need to set the following fields in your settings.py module: 79 | 80 | * [EMAIL_HOST][EMAIL_HOST] 81 | * [EMAIL_PORT][EMAIL_PORT] 82 | * [EMAIL_HOST_USER][EMAIL_HOST_USER] 83 | * [EMAIL_HOST_PASSWORD][EMAIL_HOST_PASSWORD] 84 | * [EMAIL_USE_TLS][EMAIL_USE_TLS] 85 | * [DEFAULT_FROM_EMAIL][DEFAULT_FROM_EMAIL] 86 | 87 | [EMAIL_HOST]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-EMAIL_HOST 88 | [EMAIL_PORT]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-EMAIL_PORT 89 | [EMAIL_HOST_USER]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-EMAIL_HOST_USER 90 | [EMAIL_HOST_PASSWORD]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-EMAIL_HOST_PASSWORD 91 | [EMAIL_USE_TLS]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-EMAIL_USE_TLS 92 | [DEFAULT_FROM_EMAIL]: https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-DEFAULT_FROM_EMAIL 93 | 94 | 95 | #Usage 96 | 97 | 1. Create a Django Admin User object that represents your client. 98 | 2. Make sure your clients can't login to the Django Admin Interface by unchecking the ``Staff status`` and ``Superuser status`` fields. 99 | 3. Assign a group membership to the user if you want to distribute your apps to a group of users. 100 | 4. Enter your client's email address if you want to be able to notify him or her of the availability of new apps. 101 | 5. After you save a user object the Django_mobile_app_distribution in the admin interface exposes an extended user info object that allows you to change the correspondence language for that user. 102 | 6. Create iOS or Android Apps to your liking. 103 | 7. Use the admin action in the change list to notify users of the availability of new apps. 104 | 105 | #Android specifics 106 | 107 | In case you get a permission denied error when uploading an Android APK, make sure that the ``android/android_apps`` folder on the same level as your project's settings.py is writable by your webserver. 108 | 109 | 110 | #Export your iOS app for *Over the Air* distribution 111 | 112 | * In your browser log into the Django Admin and navigate to **Django_mobile_app_distribution > IOS Apps** 113 | * Create a new iOS app. 114 | * Choose a user or group 115 | * Add app name, bundle version, bundle identifier and comment 116 | * Open Xcode 117 | * In Xcode export your app as an archive: **Product > Archive** 118 | * Make sure you have got your provisioning right and your signing with a distribution certificate 119 | * Go to **Organizer > Archives** 120 | * Select your archive and hit **Export** 121 | * Choose **Save for Enterprise or Ad-Hoc deployment** 122 | * Choose your codesign identity 123 | * In Xcode hit **Export** 124 | * Choose a folder to save to and remember it 125 | * In your browser upload the IPA file into the respective field 126 | * On the download page you should be able to download and install over the air with properly provisioned devices 127 | 128 | 129 | #Customizing the default color scheme 130 | 131 | The frontend templates make use of the Zurb Foundation CSS framework 5.5.1. 132 | In line with foundation's customization rules there is an ``app.css`` file you can override to customize the default color scheme. 133 | To do that create the following folder inside one of your own apps: 134 | 135 | **static/django_mobile_app_distribution/css/** 136 | 137 | Make sure your app comes before ``django_mobile_app_distribution`` in the list of ``INSTALLED_APPS``. 138 | Inside that folder create a file called ``app.css``. There you can do custom styling, for instance: 139 | 140 | a { 141 | color: black; 142 | } 143 | 144 | button, .button { 145 | background-color: black; 146 | } 147 | 148 | button:hover, button:focus, .button:hover, .button:focus { 149 | background-color: gray; 150 | } 151 | 152 | 153 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Django Mobile App Distribution 1.0\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2016-04-08 18:47+0200\n" 11 | "PO-Revision-Date: 2016-04-08 18:49+0200\n" 12 | "Last-Translator: Moritz Pfeiffer \n" 13 | "Language-Team: Alp Phone GmbH \n" 14 | "Language: de\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 1.8.7\n" 20 | 21 | #: admin.py:76 22 | #, python-format 23 | msgid "" 24 | "Version %(app_version)s of %(app_name)s for %(os)s is available for download" 25 | msgstr "" 26 | "Version %(app_version)s der %(app_name)s App für %(os)s steht zum Download " 27 | "bereit" 28 | 29 | #: admin.py:78 30 | #, python-format 31 | msgid "" 32 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 33 | "download.\n" 34 | "Please visit %(download_url)s to install the app." 35 | msgstr "" 36 | "Version %(app_version)s der %(app_name)s App für %(os)s steht zum Download " 37 | "bereit.\n" 38 | "Bitte besuchen Sie %(download_url)s um die App zu installieren." 39 | 40 | #: admin.py:93 41 | #, python-format 42 | msgid "" 43 | "%(recipient_count)s user was notified of %(app_name)s %(app_version)s " 44 | "availability." 45 | msgid_plural "" 46 | "%(recipient_count)s users were notified of %(app_name)s %(app_version)s " 47 | "availability." 48 | msgstr[0] "" 49 | "%(recipient_count)s Benutzer wurde über die Verfügbarkeit der %(app_name)s " 50 | "%(app_version)s App informiert." 51 | msgstr[1] "" 52 | "%(recipient_count)s Benutzer wurden über die Verfügbarkeit der %(app_name)s " 53 | "%(app_version)s App informiert." 54 | 55 | #: admin.py:103 56 | msgid "Nobody was notified by email because nobody's email address is set." 57 | msgstr "" 58 | "Niemand wurde per E-Mail benachrichtigt, weil bei niemandem die E-Mail-" 59 | "Adresse gesetzt ist." 60 | 61 | #: admin.py:106 62 | msgid "Notify clients of app availability" 63 | msgstr "Kunden über die Verfügbarkeit der Apps informieren." 64 | 65 | #: admin.py:113 models.py:65 66 | msgid "User" 67 | msgstr "Benutzer" 68 | 69 | #: admin.py:121 models.py:71 70 | msgid "Groups" 71 | msgstr "Gruppen" 72 | 73 | #: admin.py:130 admin.py:145 74 | msgid "App info" 75 | msgstr "App Infos" 76 | 77 | #: admin.py:133 78 | msgid "Provide these deploy on iOS 9" 79 | msgstr "Angeben um auf iOS 9 bereitzustellen" 80 | 81 | #: forms.py:20 82 | msgid "Please assign a user or group to the app." 83 | msgstr "Bitte weisen Sie der App einen Benutzer oder eine Gruppe zu." 84 | 85 | #: forms.py:37 86 | msgid "Please provide a display and a full size image." 87 | msgstr "Bitte laden Sie ein “display\" und “full size” Bild hoch." 88 | 89 | #: models.py:52 90 | msgid "user" 91 | msgstr "Benutzer" 92 | 93 | #: models.py:53 94 | msgid "language" 95 | msgstr "Sprache" 96 | 97 | #: models.py:59 models.py:60 98 | msgid "Extended user info" 99 | msgstr "Erweiterte Benutzereinstellungen" 100 | 101 | #: models.py:73 102 | msgid "App name" 103 | msgstr "App Name" 104 | 105 | #: models.py:74 106 | msgid "Comment" 107 | msgstr "Bemerkung" 108 | 109 | #: models.py:75 110 | msgid "Bundle version" 111 | msgstr "Bundle Version" 112 | 113 | #: models.py:76 114 | msgid "updated date" 115 | msgstr "Geändert am" 116 | 117 | #: models.py:77 118 | msgid "created date" 119 | msgstr "Erstellt am" 120 | 121 | #: models.py:89 models.py:171 122 | msgid "Operating system" 123 | msgstr "Betriebssystem" 124 | 125 | #: models.py:92 126 | msgid "IPA file" 127 | msgstr "IPA Datei" 128 | 129 | #: models.py:95 130 | msgid "Bundle identifier" 131 | msgstr "Bundle identifier" 132 | 133 | #: models.py:97 134 | msgid "e.g. org.example.app" 135 | msgstr "z.B. org.example.app" 136 | 137 | #: models.py:101 138 | msgid "display image" 139 | msgstr "Display Bild" 140 | 141 | #: models.py:108 142 | msgid "full size image" 143 | msgstr "Full size Bild" 144 | 145 | #: models.py:158 146 | msgid "iOS App" 147 | msgstr "iOS App" 148 | 149 | #: models.py:159 150 | msgid "iOS Apps" 151 | msgstr "iOS Apps" 152 | 153 | #: models.py:174 154 | msgid "APK file" 155 | msgstr "APK Datei" 156 | 157 | #: models.py:177 158 | msgid "Android App" 159 | msgstr "Android App" 160 | 161 | #: models.py:178 162 | msgid "Android Apps" 163 | msgstr "Android Apps" 164 | 165 | #: templates/admin/base_site.html:4 166 | msgid "Django site admin" 167 | msgstr "Ad Hoc Verwaltung" 168 | 169 | #: templates/admin/base_site.html:7 170 | msgid "Django administration" 171 | msgstr "Mobile App Distribution" 172 | 173 | #: templates/django_mobile_app_distribution/app_list.html:4 174 | msgid "app list view" 175 | msgstr "App Übersicht" 176 | 177 | #: templates/django_mobile_app_distribution/app_list.html:14 178 | msgid "logout" 179 | msgstr "Abmelden" 180 | 181 | #: templates/django_mobile_app_distribution/app_list.html:30 182 | msgid "operating system" 183 | msgstr "Betriebssystem" 184 | 185 | #: templates/django_mobile_app_distribution/app_list.html:36 186 | msgid "version" 187 | msgstr "Version" 188 | 189 | #: templates/django_mobile_app_distribution/app_list.html:42 190 | msgid "build date" 191 | msgstr "Erstellt" 192 | 193 | #: templates/django_mobile_app_distribution/app_list.html:48 194 | msgid "comment" 195 | msgstr "Bemerkung" 196 | 197 | #: templates/django_mobile_app_distribution/app_list.html:57 198 | #: templates/django_mobile_app_distribution/app_list.html:61 199 | msgid "Install App" 200 | msgstr "App Installieren" 201 | 202 | #: templates/django_mobile_app_distribution/base.html:5 203 | msgid "Mobile App Distribution" 204 | msgstr "Mobile App Distribution" 205 | 206 | #: templates/django_mobile_app_distribution/email_notification.html:14 207 | #, python-format 208 | msgid "" 209 | "Version %(app_version)s of %(app_name)s for %(os)s is available for " 210 | "download.
Please visit %(download_url)s to install the app." 211 | msgstr "" 212 | "Version %(app_version)s der %(app_name)s App für %(os)s steht zum Download " 213 | "bereit.
Bitte besuchen Sie %(download_url)s um die App zu installieren." 214 | 215 | #: templates/django_mobile_app_distribution/login.html:3 216 | msgid "customer login" 217 | msgstr "Kundenbereich" 218 | 219 | #: templates/django_mobile_app_distribution/login.html:30 220 | msgid "invalid login credentials" 221 | msgstr "Ihr Benutzername oder Passwort sind falsch" 222 | 223 | #: templates/django_mobile_app_distribution/login.html:37 224 | #: templates/django_mobile_app_distribution/logout.html:13 225 | msgid "login" 226 | msgstr "Anmelden" 227 | 228 | #: templates/django_mobile_app_distribution/logout.html:3 229 | msgid "logged out successfully" 230 | msgstr "Sie wurden erfolgreich abgemeldet" 231 | 232 | #: templates/django_mobile_app_distribution/logout.html:8 233 | msgid "You were logged out successfully." 234 | msgstr "Sie wurden erfolgreich abgemeldet." 235 | 236 | #~ msgid "" 237 | #~ "\n" 238 | #~ "\t\tYou were logged out successfully.
Log in again.\n" 240 | #~ "\t" 241 | #~ msgstr "" 242 | #~ "\n" 243 | #~ "\t\tSie wurden erfolgreich abgemeldet. Erneut anmelden.\n" 245 | #~ "\t" 246 | 247 | #~ msgid "download file" 248 | #~ msgstr "App" 249 | 250 | #~ msgid "Binary info" 251 | #~ msgstr "Datei Infos" 252 | 253 | #~ msgid "Don't change this file name!" 254 | #~ msgstr "Diesen Dateinamen nicht ändern!" 255 | 256 | #~ msgid "" 257 | #~ "Paste the file name and Ad Hoc URL into the Xcode Enterprise Ad Hoc " 258 | #~ "dialog." 259 | #~ msgstr "" 260 | #~ "Kopiere den Dateinamen und die Ad Hoc URL in die entsprechenden Felder im " 261 | #~ "Xcode Ad Hoc Dialog." 262 | 263 | #~ msgid "File name:" 264 | #~ msgstr "Datei Name:" 265 | 266 | #~ msgid "Ad Hoc URL:" 267 | #~ msgstr "Ad Hoc URL:" 268 | 269 | #~ msgid "Version" 270 | #~ msgstr "Version" 271 | 272 | #~ msgid "Build date" 273 | #~ msgstr "Erstellt" 274 | 275 | #~ msgid "File name" 276 | #~ msgstr "Datei Name:" 277 | 278 | #~ msgid "Ad Hoc ipa file" 279 | #~ msgstr "Ad Hoc ipa Datei" 280 | 281 | #~ msgid "Ad Hoc plist" 282 | #~ msgstr "Ad Hoc Pliste" 283 | 284 | #~ msgid "" 285 | #~ "The user %s does not have an email address. Please add an email address " 286 | #~ "and try again." 287 | #~ msgstr "" 288 | #~ "Der Benutzer %s hat keine E-Mail-Adresse. Bitte fügen Sie eine E-Mail-" 289 | #~ "Adresse hinzu und probieren Sie es erneut." 290 | 291 | #~ msgid "over the air ad hoc app management" 292 | #~ msgstr "Over the air Ad Hoc Verwaltung" 293 | 294 | #~ msgid "file name" 295 | #~ msgstr "Datei Name" 296 | 297 | #~ msgid "ios app" 298 | #~ msgstr "iOS App" 299 | 300 | #~ msgid "ios apps" 301 | #~ msgstr "iOS Apps" 302 | 303 | #~ msgid "download" 304 | #~ msgstr "Datei" 305 | 306 | #~ msgid "ios install link" 307 | #~ msgstr "iOS Installationslink" 308 | 309 | #~ msgid "not set" 310 | #~ msgstr "Nicht verfügbar" 311 | 312 | #~ msgid "plist url" 313 | #~ msgstr "Plistdatei" 314 | 315 | #~ msgid "app" 316 | #~ msgstr "App" 317 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/vendor/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modernizr v2.8.3 3 | * www.modernizr.com 4 | * 5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton 6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/ 7 | */ 8 | window.Modernizr=function(a,b,c){function d(a){t.cssText=a}function e(a,b){return d(x.join(a+";")+(b||""))}function f(a,b){return typeof a===b}function g(a,b){return!!~(""+a).indexOf(b)}function h(a,b){for(var d in a){var e=a[d];if(!g(e,"-")&&t[e]!==c)return"pfx"==b?e:!0}return!1}function i(a,b,d){for(var e in a){var g=b[a[e]];if(g!==c)return d===!1?a[e]:f(g,"function")?g.bind(d||b):g}return!1}function j(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+z.join(d+" ")+d).split(" ");return f(b,"string")||f(b,"undefined")?h(e,b):(e=(a+" "+A.join(d+" ")+d).split(" "),i(e,b,c))}function k(){o.input=function(c){for(var d=0,e=c.length;e>d;d++)E[c[d]]=!!(c[d]in u);return E.list&&(E.list=!(!b.createElement("datalist")||!a.HTMLDataListElement)),E}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),o.inputtypes=function(a){for(var d,e,f,g=0,h=a.length;h>g;g++)u.setAttribute("type",e=a[g]),d="text"!==u.type,d&&(u.value=v,u.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&u.style.WebkitAppearance!==c?(q.appendChild(u),f=b.defaultView,d=f.getComputedStyle&&"textfield"!==f.getComputedStyle(u,null).WebkitAppearance&&0!==u.offsetHeight,q.removeChild(u)):/^(search|tel)$/.test(e)||(d=/^(url|email)$/.test(e)?u.checkValidity&&u.checkValidity()===!1:u.value!=v)),D[a[g]]=!!d;return D}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var l,m,n="2.8.3",o={},p=!0,q=b.documentElement,r="modernizr",s=b.createElement(r),t=s.style,u=b.createElement("input"),v=":)",w={}.toString,x=" -webkit- -moz- -o- -ms- ".split(" "),y="Webkit Moz O ms",z=y.split(" "),A=y.toLowerCase().split(" "),B={svg:"http://www.w3.org/2000/svg"},C={},D={},E={},F=[],G=F.slice,H=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:r+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=r,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=q.style.overflow,q.style.overflow="hidden",q.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),q.style.overflow=i),!!g},I=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return H("@media "+b+" { #"+r+" { position: absolute; } }",function(b){d="absolute"==(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position}),d},J=function(){function a(a,e){e=e||b.createElement(d[a]||"div"),a="on"+a;var g=a in e;return g||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(a,""),g=f(e[a],"function"),f(e[a],"undefined")||(e[a]=c),e.removeAttribute(a))),e=null,g}var d={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return a}(),K={}.hasOwnProperty;m=f(K,"undefined")||f(K.call,"undefined")?function(a,b){return b in a&&f(a.constructor.prototype[b],"undefined")}:function(a,b){return K.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=G.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(G.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(G.call(arguments)))};return d}),C.flexbox=function(){return j("flexWrap")},C.flexboxlegacy=function(){return j("boxDirection")},C.canvas=function(){var a=b.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))},C.canvastext=function(){return!(!o.canvas||!f(b.createElement("canvas").getContext("2d").fillText,"function"))},C.webgl=function(){return!!a.WebGLRenderingContext},C.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:H(["@media (",x.join("touch-enabled),("),r,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=9===a.offsetTop}),c},C.geolocation=function(){return"geolocation"in navigator},C.postmessage=function(){return!!a.postMessage},C.websqldatabase=function(){return!!a.openDatabase},C.indexedDB=function(){return!!j("indexedDB",a)},C.hashchange=function(){return J("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},C.history=function(){return!(!a.history||!history.pushState)},C.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},C.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},C.rgba=function(){return d("background-color:rgba(150,255,150,.5)"),g(t.backgroundColor,"rgba")},C.hsla=function(){return d("background-color:hsla(120,40%,100%,.5)"),g(t.backgroundColor,"rgba")||g(t.backgroundColor,"hsla")},C.multiplebgs=function(){return d("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(t.background)},C.backgroundsize=function(){return j("backgroundSize")},C.borderimage=function(){return j("borderImage")},C.borderradius=function(){return j("borderRadius")},C.boxshadow=function(){return j("boxShadow")},C.textshadow=function(){return""===b.createElement("div").style.textShadow},C.opacity=function(){return e("opacity:.55"),/^0.55$/.test(t.opacity)},C.cssanimations=function(){return j("animationName")},C.csscolumns=function(){return j("columnCount")},C.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return d((a+"-webkit- ".split(" ").join(b+a)+x.join(c+a)).slice(0,-a.length)),g(t.backgroundImage,"gradient")},C.cssreflections=function(){return j("boxReflect")},C.csstransforms=function(){return!!j("transform")},C.csstransforms3d=function(){var a=!!j("perspective");return a&&"webkitPerspective"in q.style&&H("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},C.csstransitions=function(){return j("transition")},C.fontface=function(){var a;return H('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&0===g.indexOf(d.split(" ")[0])}),a},C.generatedcontent=function(){var a;return H(["#",r,"{font:0/0 a}#",r,':after{content:"',v,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},C.video=function(){var a=b.createElement("video"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""))}catch(d){}return c},C.audio=function(){var a=b.createElement("audio"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(d){}return c},C.localstorage=function(){try{return localStorage.setItem(r,r),localStorage.removeItem(r),!0}catch(a){return!1}},C.sessionstorage=function(){try{return sessionStorage.setItem(r,r),sessionStorage.removeItem(r),!0}catch(a){return!1}},C.webworkers=function(){return!!a.Worker},C.applicationcache=function(){return!!a.applicationCache},C.svg=function(){return!!b.createElementNS&&!!b.createElementNS(B.svg,"svg").createSVGRect},C.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==B.svg},C.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(w.call(b.createElementNS(B.svg,"animate")))},C.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(w.call(b.createElementNS(B.svg,"clipPath")))};for(var L in C)m(C,L)&&(l=L.toLowerCase(),o[l]=C[L](),F.push((o[l]?"":"no-")+l));return o.input||k(),o.addTest=function(a,b){if("object"==typeof a)for(var d in a)m(a,d)&&o.addTest(d,a[d]);else{if(a=a.toLowerCase(),o[a]!==c)return o;b="function"==typeof b?b():b,"undefined"!=typeof p&&p&&(q.className+=" "+(b?"":"no-")+a),o[a]=b}return o},d(""),s=u=null,function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=s.elements;return"string"==typeof a?a.split(" "):a}function e(a){var b=r[a[p]];return b||(b={},q++,a[p]=q,r[q]=b),b}function f(a,c,d){if(c||(c=b),k)return c.createElement(a);d||(d=e(c));var f;return f=d.cache[a]?d.cache[a].cloneNode():o.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!f.canHaveChildren||n.test(a)||f.tagUrn?f:d.frag.appendChild(f)}function g(a,c){if(a||(a=b),k)return a.createDocumentFragment();c=c||e(a);for(var f=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)f.createElement(h[g]);return f}function h(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?f(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function i(a){a||(a=b);var d=e(a);return!s.shivCSS||j||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||h(a,d),a}var j,k,l="3.7.0",m=a.html5||{},n=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,o=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,p="_html5shiv",q=0,r={};!function(){try{var a=b.createElement("a");a.innerHTML="",j="hidden"in a,k=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){j=!0,k=!0}}();var s={elements:m.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:l,shivCSS:m.shivCSS!==!1,supportsUnknownElements:k,shivMethods:m.shivMethods!==!1,type:"default",shivDocument:i,createElement:f,createDocumentFragment:g};a.html5=s,i(b)}(this,b),o._version=n,o._prefixes=x,o._domPrefixes=A,o._cssomPrefixes=z,o.mq=I,o.hasEvent=J,o.testProp=function(a){return h([a])},o.testAllProps=j,o.testStyles=H,o.prefixed=function(a,b,c){return b?j(a,b,c):j(a,"pfx")},q.className=q.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(p?" js "+F.join(" "):""),o}(this,this.document); 9 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/js/foundation/foundation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Foundation Responsive Library 3 | * http://foundation.zurb.com 4 | * Copyright 2014, ZURB 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | (function ($, window, document, undefined) { 10 | 'use strict'; 11 | 12 | var header_helpers = function (class_array) { 13 | var i = class_array.length; 14 | var head = $('head'); 15 | 16 | while (i--) { 17 | if (head.has('.' + class_array[i]).length === 0) { 18 | head.append(''); 19 | } 20 | } 21 | }; 22 | 23 | header_helpers([ 24 | 'foundation-mq-small', 25 | 'foundation-mq-small-only', 26 | 'foundation-mq-medium', 27 | 'foundation-mq-medium-only', 28 | 'foundation-mq-large', 29 | 'foundation-mq-large-only', 30 | 'foundation-mq-xlarge', 31 | 'foundation-mq-xlarge-only', 32 | 'foundation-mq-xxlarge', 33 | 'foundation-data-attribute-namespace']); 34 | 35 | // Enable FastClick if present 36 | 37 | $(function () { 38 | if (typeof FastClick !== 'undefined') { 39 | // Don't attach to body if undefined 40 | if (typeof document.body !== 'undefined') { 41 | FastClick.attach(document.body); 42 | } 43 | } 44 | }); 45 | 46 | // private Fast Selector wrapper, 47 | // returns jQuery object. Only use where 48 | // getElementById is not available. 49 | var S = function (selector, context) { 50 | if (typeof selector === 'string') { 51 | if (context) { 52 | var cont; 53 | if (context.jquery) { 54 | cont = context[0]; 55 | if (!cont) { 56 | return context; 57 | } 58 | } else { 59 | cont = context; 60 | } 61 | return $(cont.querySelectorAll(selector)); 62 | } 63 | 64 | return $(document.querySelectorAll(selector)); 65 | } 66 | 67 | return $(selector, context); 68 | }; 69 | 70 | // Namespace functions. 71 | 72 | var attr_name = function (init) { 73 | var arr = []; 74 | if (!init) { 75 | arr.push('data'); 76 | } 77 | if (this.namespace.length > 0) { 78 | arr.push(this.namespace); 79 | } 80 | arr.push(this.name); 81 | 82 | return arr.join('-'); 83 | }; 84 | 85 | var add_namespace = function (str) { 86 | var parts = str.split('-'), 87 | i = parts.length, 88 | arr = []; 89 | 90 | while (i--) { 91 | if (i !== 0) { 92 | arr.push(parts[i]); 93 | } else { 94 | if (this.namespace.length > 0) { 95 | arr.push(this.namespace, parts[i]); 96 | } else { 97 | arr.push(parts[i]); 98 | } 99 | } 100 | } 101 | 102 | return arr.reverse().join('-'); 103 | }; 104 | 105 | // Event binding and data-options updating. 106 | 107 | var bindings = function (method, options) { 108 | var self = this, 109 | bind = function(){ 110 | var $this = S(this), 111 | should_bind_events = !$this.data(self.attr_name(true) + '-init'); 112 | $this.data(self.attr_name(true) + '-init', $.extend({}, self.settings, (options || method), self.data_options($this))); 113 | 114 | if (should_bind_events) { 115 | self.events(this); 116 | } 117 | }; 118 | 119 | if (S(this.scope).is('[' + this.attr_name() +']')) { 120 | bind.call(this.scope); 121 | } else { 122 | S('[' + this.attr_name() +']', this.scope).each(bind); 123 | } 124 | // # Patch to fix #5043 to move this *after* the if/else clause in order for Backbone and similar frameworks to have improved control over event binding and data-options updating. 125 | if (typeof method === 'string') { 126 | return this[method].call(this, options); 127 | } 128 | 129 | }; 130 | 131 | var single_image_loaded = function (image, callback) { 132 | function loaded () { 133 | callback(image[0]); 134 | } 135 | 136 | function bindLoad () { 137 | this.one('load', loaded); 138 | 139 | if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { 140 | var src = this.attr( 'src' ), 141 | param = src.match( /\?/ ) ? '&' : '?'; 142 | 143 | param += 'random=' + (new Date()).getTime(); 144 | this.attr('src', src + param); 145 | } 146 | } 147 | 148 | if (!image.attr('src')) { 149 | loaded(); 150 | return; 151 | } 152 | 153 | if (image[0].complete || image[0].readyState === 4) { 154 | loaded(); 155 | } else { 156 | bindLoad.call(image); 157 | } 158 | }; 159 | 160 | /* 161 | https://github.com/paulirish/matchMedia.js 162 | */ 163 | 164 | window.matchMedia = window.matchMedia || (function ( doc ) { 165 | 166 | 'use strict'; 167 | 168 | var bool, 169 | docElem = doc.documentElement, 170 | refNode = docElem.firstElementChild || docElem.firstChild, 171 | // fakeBody required for 172 | fakeBody = doc.createElement( 'body' ), 173 | div = doc.createElement( 'div' ); 174 | 175 | div.id = 'mq-test-1'; 176 | div.style.cssText = 'position:absolute;top:-100em'; 177 | fakeBody.style.background = 'none'; 178 | fakeBody.appendChild(div); 179 | 180 | return function (q) { 181 | 182 | div.innerHTML = '­'; 183 | 184 | docElem.insertBefore( fakeBody, refNode ); 185 | bool = div.offsetWidth === 42; 186 | docElem.removeChild( fakeBody ); 187 | 188 | return { 189 | matches : bool, 190 | media : q 191 | }; 192 | 193 | }; 194 | 195 | }( document )); 196 | 197 | /* 198 | * jquery.requestAnimationFrame 199 | * https://github.com/gnarf37/jquery-requestAnimationFrame 200 | * Requires jQuery 1.8+ 201 | * 202 | * Copyright (c) 2012 Corey Frang 203 | * Licensed under the MIT license. 204 | */ 205 | 206 | (function(jQuery) { 207 | 208 | 209 | // requestAnimationFrame polyfill adapted from Erik Möller 210 | // fixes from Paul Irish and Tino Zijdel 211 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 212 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 213 | 214 | var animating, 215 | lastTime = 0, 216 | vendors = ['webkit', 'moz'], 217 | requestAnimationFrame = window.requestAnimationFrame, 218 | cancelAnimationFrame = window.cancelAnimationFrame, 219 | jqueryFxAvailable = 'undefined' !== typeof jQuery.fx; 220 | 221 | for (; lastTime < vendors.length && !requestAnimationFrame; lastTime++) { 222 | requestAnimationFrame = window[ vendors[lastTime] + 'RequestAnimationFrame' ]; 223 | cancelAnimationFrame = cancelAnimationFrame || 224 | window[ vendors[lastTime] + 'CancelAnimationFrame' ] || 225 | window[ vendors[lastTime] + 'CancelRequestAnimationFrame' ]; 226 | } 227 | 228 | function raf() { 229 | if (animating) { 230 | requestAnimationFrame(raf); 231 | 232 | if (jqueryFxAvailable) { 233 | jQuery.fx.tick(); 234 | } 235 | } 236 | } 237 | 238 | if (requestAnimationFrame) { 239 | // use rAF 240 | window.requestAnimationFrame = requestAnimationFrame; 241 | window.cancelAnimationFrame = cancelAnimationFrame; 242 | 243 | if (jqueryFxAvailable) { 244 | jQuery.fx.timer = function (timer) { 245 | if (timer() && jQuery.timers.push(timer) && !animating) { 246 | animating = true; 247 | raf(); 248 | } 249 | }; 250 | 251 | jQuery.fx.stop = function () { 252 | animating = false; 253 | }; 254 | } 255 | } else { 256 | // polyfill 257 | window.requestAnimationFrame = function (callback) { 258 | var currTime = new Date().getTime(), 259 | timeToCall = Math.max(0, 16 - (currTime - lastTime)), 260 | id = window.setTimeout(function () { 261 | callback(currTime + timeToCall); 262 | }, timeToCall); 263 | lastTime = currTime + timeToCall; 264 | return id; 265 | }; 266 | 267 | window.cancelAnimationFrame = function (id) { 268 | clearTimeout(id); 269 | }; 270 | 271 | } 272 | 273 | }( $ )); 274 | 275 | function removeQuotes (string) { 276 | if (typeof string === 'string' || string instanceof String) { 277 | string = string.replace(/^['\\/"]+|(;\s?})+|['\\/"]+$/g, ''); 278 | } 279 | 280 | return string; 281 | } 282 | 283 | window.Foundation = { 284 | name : 'Foundation', 285 | 286 | version : '5.5.1', 287 | 288 | media_queries : { 289 | 'small' : S('.foundation-mq-small').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 290 | 'small-only' : S('.foundation-mq-small-only').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 291 | 'medium' : S('.foundation-mq-medium').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 292 | 'medium-only' : S('.foundation-mq-medium-only').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 293 | 'large' : S('.foundation-mq-large').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 294 | 'large-only' : S('.foundation-mq-large-only').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 295 | 'xlarge' : S('.foundation-mq-xlarge').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 296 | 'xlarge-only' : S('.foundation-mq-xlarge-only').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, ''), 297 | 'xxlarge' : S('.foundation-mq-xxlarge').css('font-family').replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g, '') 298 | }, 299 | 300 | stylesheet : $('').appendTo('head')[0].sheet, 301 | 302 | global : { 303 | namespace : undefined 304 | }, 305 | 306 | init : function (scope, libraries, method, options, response) { 307 | var args = [scope, method, options, response], 308 | responses = []; 309 | 310 | // check RTL 311 | this.rtl = /rtl/i.test(S('html').attr('dir')); 312 | 313 | // set foundation global scope 314 | this.scope = scope || this.scope; 315 | 316 | this.set_namespace(); 317 | 318 | if (libraries && typeof libraries === 'string' && !/reflow/i.test(libraries)) { 319 | if (this.libs.hasOwnProperty(libraries)) { 320 | responses.push(this.init_lib(libraries, args)); 321 | } 322 | } else { 323 | for (var lib in this.libs) { 324 | responses.push(this.init_lib(lib, libraries)); 325 | } 326 | } 327 | 328 | S(window).load(function () { 329 | S(window) 330 | .trigger('resize.fndtn.clearing') 331 | .trigger('resize.fndtn.dropdown') 332 | .trigger('resize.fndtn.equalizer') 333 | .trigger('resize.fndtn.interchange') 334 | .trigger('resize.fndtn.joyride') 335 | .trigger('resize.fndtn.magellan') 336 | .trigger('resize.fndtn.topbar') 337 | .trigger('resize.fndtn.slider'); 338 | }); 339 | 340 | return scope; 341 | }, 342 | 343 | init_lib : function (lib, args) { 344 | if (this.libs.hasOwnProperty(lib)) { 345 | this.patch(this.libs[lib]); 346 | 347 | if (args && args.hasOwnProperty(lib)) { 348 | if (typeof this.libs[lib].settings !== 'undefined') { 349 | $.extend(true, this.libs[lib].settings, args[lib]); 350 | } else if (typeof this.libs[lib].defaults !== 'undefined') { 351 | $.extend(true, this.libs[lib].defaults, args[lib]); 352 | } 353 | return this.libs[lib].init.apply(this.libs[lib], [this.scope, args[lib]]); 354 | } 355 | 356 | args = args instanceof Array ? args : new Array(args); 357 | return this.libs[lib].init.apply(this.libs[lib], args); 358 | } 359 | 360 | return function () {}; 361 | }, 362 | 363 | patch : function (lib) { 364 | lib.scope = this.scope; 365 | lib.namespace = this.global.namespace; 366 | lib.rtl = this.rtl; 367 | lib['data_options'] = this.utils.data_options; 368 | lib['attr_name'] = attr_name; 369 | lib['add_namespace'] = add_namespace; 370 | lib['bindings'] = bindings; 371 | lib['S'] = this.utils.S; 372 | }, 373 | 374 | inherit : function (scope, methods) { 375 | var methods_arr = methods.split(' '), 376 | i = methods_arr.length; 377 | 378 | while (i--) { 379 | if (this.utils.hasOwnProperty(methods_arr[i])) { 380 | scope[methods_arr[i]] = this.utils[methods_arr[i]]; 381 | } 382 | } 383 | }, 384 | 385 | set_namespace : function () { 386 | 387 | // Description: 388 | // Don't bother reading the namespace out of the meta tag 389 | // if the namespace has been set globally in javascript 390 | // 391 | // Example: 392 | // Foundation.global.namespace = 'my-namespace'; 393 | // or make it an empty string: 394 | // Foundation.global.namespace = ''; 395 | // 396 | // 397 | 398 | // If the namespace has not been set (is undefined), try to read it out of the meta element. 399 | // Otherwise use the globally defined namespace, even if it's empty ('') 400 | var namespace = ( this.global.namespace === undefined ) ? $('.foundation-data-attribute-namespace').css('font-family') : this.global.namespace; 401 | 402 | // Finally, if the namsepace is either undefined or false, set it to an empty string. 403 | // Otherwise use the namespace value. 404 | this.global.namespace = ( namespace === undefined || /false/i.test(namespace) ) ? '' : namespace; 405 | }, 406 | 407 | libs : {}, 408 | 409 | // methods that can be inherited in libraries 410 | utils : { 411 | 412 | // Description: 413 | // Fast Selector wrapper returns jQuery object. Only use where getElementById 414 | // is not available. 415 | // 416 | // Arguments: 417 | // Selector (String): CSS selector describing the element(s) to be 418 | // returned as a jQuery object. 419 | // 420 | // Scope (String): CSS selector describing the area to be searched. Default 421 | // is document. 422 | // 423 | // Returns: 424 | // Element (jQuery Object): jQuery object containing elements matching the 425 | // selector within the scope. 426 | S : S, 427 | 428 | // Description: 429 | // Executes a function a max of once every n milliseconds 430 | // 431 | // Arguments: 432 | // Func (Function): Function to be throttled. 433 | // 434 | // Delay (Integer): Function execution threshold in milliseconds. 435 | // 436 | // Returns: 437 | // Lazy_function (Function): Function with throttling applied. 438 | throttle : function (func, delay) { 439 | var timer = null; 440 | 441 | return function () { 442 | var context = this, args = arguments; 443 | 444 | if (timer == null) { 445 | timer = setTimeout(function () { 446 | func.apply(context, args); 447 | timer = null; 448 | }, delay); 449 | } 450 | }; 451 | }, 452 | 453 | // Description: 454 | // Executes a function when it stops being invoked for n seconds 455 | // Modified version of _.debounce() http://underscorejs.org 456 | // 457 | // Arguments: 458 | // Func (Function): Function to be debounced. 459 | // 460 | // Delay (Integer): Function execution threshold in milliseconds. 461 | // 462 | // Immediate (Bool): Whether the function should be called at the beginning 463 | // of the delay instead of the end. Default is false. 464 | // 465 | // Returns: 466 | // Lazy_function (Function): Function with debouncing applied. 467 | debounce : function (func, delay, immediate) { 468 | var timeout, result; 469 | return function () { 470 | var context = this, args = arguments; 471 | var later = function () { 472 | timeout = null; 473 | if (!immediate) { 474 | result = func.apply(context, args); 475 | } 476 | }; 477 | var callNow = immediate && !timeout; 478 | clearTimeout(timeout); 479 | timeout = setTimeout(later, delay); 480 | if (callNow) { 481 | result = func.apply(context, args); 482 | } 483 | return result; 484 | }; 485 | }, 486 | 487 | // Description: 488 | // Parses data-options attribute 489 | // 490 | // Arguments: 491 | // El (jQuery Object): Element to be parsed. 492 | // 493 | // Returns: 494 | // Options (Javascript Object): Contents of the element's data-options 495 | // attribute. 496 | data_options : function (el, data_attr_name) { 497 | data_attr_name = data_attr_name || 'options'; 498 | var opts = {}, ii, p, opts_arr, 499 | data_options = function (el) { 500 | var namespace = Foundation.global.namespace; 501 | 502 | if (namespace.length > 0) { 503 | return el.data(namespace + '-' + data_attr_name); 504 | } 505 | 506 | return el.data(data_attr_name); 507 | }; 508 | 509 | var cached_options = data_options(el); 510 | 511 | if (typeof cached_options === 'object') { 512 | return cached_options; 513 | } 514 | 515 | opts_arr = (cached_options || ':').split(';'); 516 | ii = opts_arr.length; 517 | 518 | function isNumber (o) { 519 | return !isNaN (o - 0) && o !== null && o !== '' && o !== false && o !== true; 520 | } 521 | 522 | function trim (str) { 523 | if (typeof str === 'string') { 524 | return $.trim(str); 525 | } 526 | return str; 527 | } 528 | 529 | while (ii--) { 530 | p = opts_arr[ii].split(':'); 531 | p = [p[0], p.slice(1).join(':')]; 532 | 533 | if (/true/i.test(p[1])) { 534 | p[1] = true; 535 | } 536 | if (/false/i.test(p[1])) { 537 | p[1] = false; 538 | } 539 | if (isNumber(p[1])) { 540 | if (p[1].indexOf('.') === -1) { 541 | p[1] = parseInt(p[1], 10); 542 | } else { 543 | p[1] = parseFloat(p[1]); 544 | } 545 | } 546 | 547 | if (p.length === 2 && p[0].length > 0) { 548 | opts[trim(p[0])] = trim(p[1]); 549 | } 550 | } 551 | 552 | return opts; 553 | }, 554 | 555 | // Description: 556 | // Adds JS-recognizable media queries 557 | // 558 | // Arguments: 559 | // Media (String): Key string for the media query to be stored as in 560 | // Foundation.media_queries 561 | // 562 | // Class (String): Class name for the generated tag 563 | register_media : function (media, media_class) { 564 | if (Foundation.media_queries[media] === undefined) { 565 | $('head').append(''); 566 | Foundation.media_queries[media] = removeQuotes($('.' + media_class).css('font-family')); 567 | } 568 | }, 569 | 570 | // Description: 571 | // Add custom CSS within a JS-defined media query 572 | // 573 | // Arguments: 574 | // Rule (String): CSS rule to be appended to the document. 575 | // 576 | // Media (String): Optional media query string for the CSS rule to be 577 | // nested under. 578 | add_custom_rule : function (rule, media) { 579 | if (media === undefined && Foundation.stylesheet) { 580 | Foundation.stylesheet.insertRule(rule, Foundation.stylesheet.cssRules.length); 581 | } else { 582 | var query = Foundation.media_queries[media]; 583 | 584 | if (query !== undefined) { 585 | Foundation.stylesheet.insertRule('@media ' + 586 | Foundation.media_queries[media] + '{ ' + rule + ' }'); 587 | } 588 | } 589 | }, 590 | 591 | // Description: 592 | // Performs a callback function when an image is fully loaded 593 | // 594 | // Arguments: 595 | // Image (jQuery Object): Image(s) to check if loaded. 596 | // 597 | // Callback (Function): Function to execute when image is fully loaded. 598 | image_loaded : function (images, callback) { 599 | var self = this, 600 | unloaded = images.length; 601 | 602 | if (unloaded === 0) { 603 | callback(images); 604 | } 605 | 606 | images.each(function () { 607 | single_image_loaded(self.S(this), function () { 608 | unloaded -= 1; 609 | if (unloaded === 0) { 610 | callback(images); 611 | } 612 | }); 613 | }); 614 | }, 615 | 616 | // Description: 617 | // Returns a random, alphanumeric string 618 | // 619 | // Arguments: 620 | // Length (Integer): Length of string to be generated. Defaults to random 621 | // integer. 622 | // 623 | // Returns: 624 | // Rand (String): Pseudo-random, alphanumeric string. 625 | random_str : function () { 626 | if (!this.fidx) { 627 | this.fidx = 0; 628 | } 629 | this.prefix = this.prefix || [(this.name || 'F'), (+new Date).toString(36)].join('-'); 630 | 631 | return this.prefix + (this.fidx++).toString(36); 632 | }, 633 | 634 | // Description: 635 | // Helper for window.matchMedia 636 | // 637 | // Arguments: 638 | // mq (String): Media query 639 | // 640 | // Returns: 641 | // (Boolean): Whether the media query passes or not 642 | match : function (mq) { 643 | return window.matchMedia(mq).matches; 644 | }, 645 | 646 | // Description: 647 | // Helpers for checking Foundation default media queries with JS 648 | // 649 | // Returns: 650 | // (Boolean): Whether the media query passes or not 651 | 652 | is_small_up : function () { 653 | return this.match(Foundation.media_queries.small); 654 | }, 655 | 656 | is_medium_up : function () { 657 | return this.match(Foundation.media_queries.medium); 658 | }, 659 | 660 | is_large_up : function () { 661 | return this.match(Foundation.media_queries.large); 662 | }, 663 | 664 | is_xlarge_up : function () { 665 | return this.match(Foundation.media_queries.xlarge); 666 | }, 667 | 668 | is_xxlarge_up : function () { 669 | return this.match(Foundation.media_queries.xxlarge); 670 | }, 671 | 672 | is_small_only : function () { 673 | return !this.is_medium_up() && !this.is_large_up() && !this.is_xlarge_up() && !this.is_xxlarge_up(); 674 | }, 675 | 676 | is_medium_only : function () { 677 | return this.is_medium_up() && !this.is_large_up() && !this.is_xlarge_up() && !this.is_xxlarge_up(); 678 | }, 679 | 680 | is_large_only : function () { 681 | return this.is_medium_up() && this.is_large_up() && !this.is_xlarge_up() && !this.is_xxlarge_up(); 682 | }, 683 | 684 | is_xlarge_only : function () { 685 | return this.is_medium_up() && this.is_large_up() && this.is_xlarge_up() && !this.is_xxlarge_up(); 686 | }, 687 | 688 | is_xxlarge_only : function () { 689 | return this.is_medium_up() && this.is_large_up() && this.is_xlarge_up() && this.is_xxlarge_up(); 690 | } 691 | } 692 | }; 693 | 694 | $.fn.foundation = function () { 695 | var args = Array.prototype.slice.call(arguments, 0); 696 | 697 | return this.each(function () { 698 | Foundation.init.apply(Foundation, [this].concat(args)); 699 | return this; 700 | }); 701 | }; 702 | 703 | }(jQuery, window, window.document)); 704 | -------------------------------------------------------------------------------- /django_mobile_app_distribution/static/django_mobile_app_distribution/css/foundation.min.css: -------------------------------------------------------------------------------- 1 | meta.foundation-version{font-family:"/5.5.1/"}meta.foundation-mq-small{font-family:"/only screen/";width:0}meta.foundation-mq-small-only{font-family:"/only screen and (max-width: 40em)/";width:0}meta.foundation-mq-medium{font-family:"/only screen and (min-width:40.063em)/";width:40.063em}meta.foundation-mq-medium-only{font-family:"/only screen and (min-width:40.063em) and (max-width:64em)/";width:40.063em}meta.foundation-mq-large{font-family:"/only screen and (min-width:64.063em)/";width:64.063em}meta.foundation-mq-large-only{font-family:"/only screen and (min-width:64.063em) and (max-width:90em)/";width:64.063em}meta.foundation-mq-xlarge{font-family:"/only screen and (min-width:90.063em)/";width:90.063em}meta.foundation-mq-xlarge-only{font-family:"/only screen and (min-width:90.063em) and (max-width:120em)/";width:90.063em}meta.foundation-mq-xxlarge{font-family:"/only screen and (min-width:120.063em)/";width:120.063em}meta.foundation-data-attribute-namespace{font-family:false}html,body{height:100%}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html,body{font-size:100%}body{background:#fff;color:#333;padding:0;margin:0;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal;font-style:normal;line-height:1.5;position:relative;cursor:auto}a:hover{cursor:pointer}img{max-width:100%;height:auto}img{-ms-interpolation-mode:bicubic}#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none !important}.left{float:left !important}.right{float:right !important}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.hide{display:none}.invisible{visibility:hidden}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}img{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.row{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.row.collapse>.column,.row.collapse>.columns{padding-left:0;padding-right:0}.row.collapse .row{margin-left:0;margin-right:0}.row .row{width:auto;margin-left:-0.9375em;margin-right:-0.9375em;margin-top:0;margin-bottom:0;max-width:none}.row .row:before,.row .row:after{content:" ";display:table}.row .row:after{clear:both}.row .row.collapse{width:auto;margin:0;max-width:none}.row .row.collapse:before,.row .row.collapse:after{content:" ";display:table}.row .row.collapse:after{clear:both}.column,.columns{padding-left:0.9375em;padding-right:0.9375em;width:100%;float:left}[class*="column"]+[class*="column"]:last-child{float:right}[class*="column"]+[class*="column"].end{float:left}@media only screen{.small-push-0{position:relative;left:0%;right:auto}.small-pull-0{position:relative;right:0%;left:auto}.small-push-1{position:relative;left:8.33333%;right:auto}.small-pull-1{position:relative;right:8.33333%;left:auto}.small-push-2{position:relative;left:16.66667%;right:auto}.small-pull-2{position:relative;right:16.66667%;left:auto}.small-push-3{position:relative;left:25%;right:auto}.small-pull-3{position:relative;right:25%;left:auto}.small-push-4{position:relative;left:33.33333%;right:auto}.small-pull-4{position:relative;right:33.33333%;left:auto}.small-push-5{position:relative;left:41.66667%;right:auto}.small-pull-5{position:relative;right:41.66667%;left:auto}.small-push-6{position:relative;left:50%;right:auto}.small-pull-6{position:relative;right:50%;left:auto}.small-push-7{position:relative;left:58.33333%;right:auto}.small-pull-7{position:relative;right:58.33333%;left:auto}.small-push-8{position:relative;left:66.66667%;right:auto}.small-pull-8{position:relative;right:66.66667%;left:auto}.small-push-9{position:relative;left:75%;right:auto}.small-pull-9{position:relative;right:75%;left:auto}.small-push-10{position:relative;left:83.33333%;right:auto}.small-pull-10{position:relative;right:83.33333%;left:auto}.small-push-11{position:relative;left:91.66667%;right:auto}.small-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375em;padding-right:0.9375em;float:left}.small-1{width:8.33333%}.small-2{width:16.66667%}.small-3{width:25%}.small-4{width:33.33333%}.small-5{width:41.66667%}.small-6{width:50%}.small-7{width:58.33333%}.small-8{width:66.66667%}.small-9{width:75%}.small-10{width:83.33333%}.small-11{width:91.66667%}.small-12{width:100%}.small-offset-0{margin-left:0% !important}.small-offset-1{margin-left:8.33333% !important}.small-offset-2{margin-left:16.66667% !important}.small-offset-3{margin-left:25% !important}.small-offset-4{margin-left:33.33333% !important}.small-offset-5{margin-left:41.66667% !important}.small-offset-6{margin-left:50% !important}.small-offset-7{margin-left:58.33333% !important}.small-offset-8{margin-left:66.66667% !important}.small-offset-9{margin-left:75% !important}.small-offset-10{margin-left:83.33333% !important}.small-offset-11{margin-left:91.66667% !important}.small-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}.column.small-centered,.columns.small-centered{margin-left:auto;margin-right:auto;float:none}.column.small-uncentered,.columns.small-uncentered{margin-left:0;margin-right:0;float:left}.column.small-centered:last-child,.columns.small-centered:last-child{float:none}.column.small-uncentered:last-child,.columns.small-uncentered:last-child{float:left}.column.small-uncentered.opposite,.columns.small-uncentered.opposite{float:right}.row.small-collapse>.column,.row.small-collapse>.columns{padding-left:0;padding-right:0}.row.small-collapse .row{margin-left:0;margin-right:0}.row.small-uncollapse>.column,.row.small-uncollapse>.columns{padding-left:0.9375em;padding-right:0.9375em;float:left}}@media only screen and (min-width: 40.063em){.medium-push-0{position:relative;left:0%;right:auto}.medium-pull-0{position:relative;right:0%;left:auto}.medium-push-1{position:relative;left:8.33333%;right:auto}.medium-pull-1{position:relative;right:8.33333%;left:auto}.medium-push-2{position:relative;left:16.66667%;right:auto}.medium-pull-2{position:relative;right:16.66667%;left:auto}.medium-push-3{position:relative;left:25%;right:auto}.medium-pull-3{position:relative;right:25%;left:auto}.medium-push-4{position:relative;left:33.33333%;right:auto}.medium-pull-4{position:relative;right:33.33333%;left:auto}.medium-push-5{position:relative;left:41.66667%;right:auto}.medium-pull-5{position:relative;right:41.66667%;left:auto}.medium-push-6{position:relative;left:50%;right:auto}.medium-pull-6{position:relative;right:50%;left:auto}.medium-push-7{position:relative;left:58.33333%;right:auto}.medium-pull-7{position:relative;right:58.33333%;left:auto}.medium-push-8{position:relative;left:66.66667%;right:auto}.medium-pull-8{position:relative;right:66.66667%;left:auto}.medium-push-9{position:relative;left:75%;right:auto}.medium-pull-9{position:relative;right:75%;left:auto}.medium-push-10{position:relative;left:83.33333%;right:auto}.medium-pull-10{position:relative;right:83.33333%;left:auto}.medium-push-11{position:relative;left:91.66667%;right:auto}.medium-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375em;padding-right:0.9375em;float:left}.medium-1{width:8.33333%}.medium-2{width:16.66667%}.medium-3{width:25%}.medium-4{width:33.33333%}.medium-5{width:41.66667%}.medium-6{width:50%}.medium-7{width:58.33333%}.medium-8{width:66.66667%}.medium-9{width:75%}.medium-10{width:83.33333%}.medium-11{width:91.66667%}.medium-12{width:100%}.medium-offset-0{margin-left:0% !important}.medium-offset-1{margin-left:8.33333% !important}.medium-offset-2{margin-left:16.66667% !important}.medium-offset-3{margin-left:25% !important}.medium-offset-4{margin-left:33.33333% !important}.medium-offset-5{margin-left:41.66667% !important}.medium-offset-6{margin-left:50% !important}.medium-offset-7{margin-left:58.33333% !important}.medium-offset-8{margin-left:66.66667% !important}.medium-offset-9{margin-left:75% !important}.medium-offset-10{margin-left:83.33333% !important}.medium-offset-11{margin-left:91.66667% !important}.medium-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}.column.medium-centered,.columns.medium-centered{margin-left:auto;margin-right:auto;float:none}.column.medium-uncentered,.columns.medium-uncentered{margin-left:0;margin-right:0;float:left}.column.medium-centered:last-child,.columns.medium-centered:last-child{float:none}.column.medium-uncentered:last-child,.columns.medium-uncentered:last-child{float:left}.column.medium-uncentered.opposite,.columns.medium-uncentered.opposite{float:right}.row.medium-collapse>.column,.row.medium-collapse>.columns{padding-left:0;padding-right:0}.row.medium-collapse .row{margin-left:0;margin-right:0}.row.medium-uncollapse>.column,.row.medium-uncollapse>.columns{padding-left:0.9375em;padding-right:0.9375em;float:left}.push-0{position:relative;left:0%;right:auto}.pull-0{position:relative;right:0%;left:auto}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}}@media only screen and (min-width: 64.063em){.large-push-0{position:relative;left:0%;right:auto}.large-pull-0{position:relative;right:0%;left:auto}.large-push-1{position:relative;left:8.33333%;right:auto}.large-pull-1{position:relative;right:8.33333%;left:auto}.large-push-2{position:relative;left:16.66667%;right:auto}.large-pull-2{position:relative;right:16.66667%;left:auto}.large-push-3{position:relative;left:25%;right:auto}.large-pull-3{position:relative;right:25%;left:auto}.large-push-4{position:relative;left:33.33333%;right:auto}.large-pull-4{position:relative;right:33.33333%;left:auto}.large-push-5{position:relative;left:41.66667%;right:auto}.large-pull-5{position:relative;right:41.66667%;left:auto}.large-push-6{position:relative;left:50%;right:auto}.large-pull-6{position:relative;right:50%;left:auto}.large-push-7{position:relative;left:58.33333%;right:auto}.large-pull-7{position:relative;right:58.33333%;left:auto}.large-push-8{position:relative;left:66.66667%;right:auto}.large-pull-8{position:relative;right:66.66667%;left:auto}.large-push-9{position:relative;left:75%;right:auto}.large-pull-9{position:relative;right:75%;left:auto}.large-push-10{position:relative;left:83.33333%;right:auto}.large-pull-10{position:relative;right:83.33333%;left:auto}.large-push-11{position:relative;left:91.66667%;right:auto}.large-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375em;padding-right:0.9375em;float:left}.large-1{width:8.33333%}.large-2{width:16.66667%}.large-3{width:25%}.large-4{width:33.33333%}.large-5{width:41.66667%}.large-6{width:50%}.large-7{width:58.33333%}.large-8{width:66.66667%}.large-9{width:75%}.large-10{width:83.33333%}.large-11{width:91.66667%}.large-12{width:100%}.large-offset-0{margin-left:0% !important}.large-offset-1{margin-left:8.33333% !important}.large-offset-2{margin-left:16.66667% !important}.large-offset-3{margin-left:25% !important}.large-offset-4{margin-left:33.33333% !important}.large-offset-5{margin-left:41.66667% !important}.large-offset-6{margin-left:50% !important}.large-offset-7{margin-left:58.33333% !important}.large-offset-8{margin-left:66.66667% !important}.large-offset-9{margin-left:75% !important}.large-offset-10{margin-left:83.33333% !important}.large-offset-11{margin-left:91.66667% !important}.large-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}.column.large-centered,.columns.large-centered{margin-left:auto;margin-right:auto;float:none}.column.large-uncentered,.columns.large-uncentered{margin-left:0;margin-right:0;float:left}.column.large-centered:last-child,.columns.large-centered:last-child{float:none}.column.large-uncentered:last-child,.columns.large-uncentered:last-child{float:left}.column.large-uncentered.opposite,.columns.large-uncentered.opposite{float:right}.row.large-collapse>.column,.row.large-collapse>.columns{padding-left:0;padding-right:0}.row.large-collapse .row{margin-left:0;margin-right:0}.row.large-uncollapse>.column,.row.large-uncollapse>.columns{padding-left:0.9375em;padding-right:0.9375em;float:left}.push-0{position:relative;left:0%;right:auto}.pull-0{position:relative;right:0%;left:auto}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}}.panel{border-style:solid;border-width:1px;border-color:#d8d8d8;margin-bottom:1.25rem;padding:1.25rem;background:#f2f2f2;color:#333}.panel>:first-child{margin-top:0}.panel>:last-child{margin-bottom:0}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6,.panel p,.panel li,.panel dl{color:#333}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6{line-height:1;margin-bottom:0.625rem}.panel h1.subheader,.panel h2.subheader,.panel h3.subheader,.panel h4.subheader,.panel h5.subheader,.panel h6.subheader{line-height:1.4}.panel.callout{border-style:solid;border-width:1px;border-color:#c6e9ee;margin-bottom:1.25rem;padding:1.25rem;background:#f0f9fa;color:#333}.panel.callout>:first-child{margin-top:0}.panel.callout>:last-child{margin-bottom:0}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6,.panel.callout p,.panel.callout li,.panel.callout dl{color:#333}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6{line-height:1;margin-bottom:0.625rem}.panel.callout h1.subheader,.panel.callout h2.subheader,.panel.callout h3.subheader,.panel.callout h4.subheader,.panel.callout h5.subheader,.panel.callout h6.subheader{line-height:1.4}.panel.callout a:not(.button){color:#2a808d}.panel.callout a:not(.button):hover,.panel.callout a:not(.button):focus{color:#246e79}.panel.radius{border-radius:3px}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.text-justify{text-align:justify !important}@media only screen and (max-width: 40em){.small-only-text-left{text-align:left !important}.small-only-text-right{text-align:right !important}.small-only-text-center{text-align:center !important}.small-only-text-justify{text-align:justify !important}}@media only screen{.small-text-left{text-align:left !important}.small-text-right{text-align:right !important}.small-text-center{text-align:center !important}.small-text-justify{text-align:justify !important}}@media only screen and (min-width: 40.063em) and (max-width: 64em){.medium-only-text-left{text-align:left !important}.medium-only-text-right{text-align:right !important}.medium-only-text-center{text-align:center !important}.medium-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 40.063em){.medium-text-left{text-align:left !important}.medium-text-right{text-align:right !important}.medium-text-center{text-align:center !important}.medium-text-justify{text-align:justify !important}}@media only screen and (min-width: 64.063em) and (max-width: 90em){.large-only-text-left{text-align:left !important}.large-only-text-right{text-align:right !important}.large-only-text-center{text-align:center !important}.large-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 64.063em){.large-text-left{text-align:left !important}.large-text-right{text-align:right !important}.large-text-center{text-align:center !important}.large-text-justify{text-align:justify !important}}@media only screen and (min-width: 90.063em) and (max-width: 120em){.xlarge-only-text-left{text-align:left !important}.xlarge-only-text-right{text-align:right !important}.xlarge-only-text-center{text-align:center !important}.xlarge-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 90.063em){.xlarge-text-left{text-align:left !important}.xlarge-text-right{text-align:right !important}.xlarge-text-center{text-align:center !important}.xlarge-text-justify{text-align:justify !important}}@media only screen and (min-width: 120.063em) and (max-width: 99999999em){.xxlarge-only-text-left{text-align:left !important}.xxlarge-only-text-right{text-align:right !important}.xxlarge-only-text-center{text-align:center !important}.xxlarge-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 120.063em){.xxlarge-text-left{text-align:left !important}.xxlarge-text-right{text-align:right !important}.xxlarge-text-center{text-align:center !important}.xxlarge-text-justify{text-align:justify !important}}div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}a{color:#2a808d;text-decoration:none;line-height:inherit}a:hover,a:focus{color:#246e79}a img{border:none}p{font-family:inherit;font-weight:normal;font-size:1rem;line-height:1.6;margin-bottom:1.25rem;text-rendering:optimizeLegibility}p.lead{font-size:1.21875rem;line-height:1.6}p aside{font-size:0.875rem;line-height:1.35;font-style:italic}h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal;font-style:normal;color:#333;text-rendering:optimizeLegibility;margin-top:0.2rem;margin-bottom:0.5rem;line-height:1.4}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-size:60%;color:#7a7a7a;line-height:0}h1{font-size:2.125rem}h2{font-size:1.6875rem}h3{font-size:1.375rem}h4{font-size:1.125rem}h5{font-size:1.125rem}h6{font-size:1rem}.subheader{line-height:1.4;color:#7a7a7a;font-weight:normal;margin-top:0.2rem;margin-bottom:0.5rem}hr{border:solid #ddd;border-width:1px 0 0;clear:both;margin:1.25rem 0 1.1875rem;height:0}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:normal;color:#333;background-color:#dadfe4;border-width:1px;border-style:solid;border-color:#bfcad2;padding:0.125rem 0.3125rem 0.0625rem}ul,ol,dl{font-size:1rem;line-height:1.6;margin-bottom:1.25rem;list-style-position:outside;font-family:inherit}ul{margin-left:1.1rem}ul.no-bullet{margin-left:0}ul.no-bullet li ul,ul.no-bullet li ol{margin-left:1.25rem;margin-bottom:0;list-style:none}ul li ul,ul li ol{margin-left:1.25rem;margin-bottom:0}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square;margin-left:1.1rem}ul.circle{list-style-type:circle;margin-left:1.1rem}ul.disc{list-style-type:disc;margin-left:1.1rem}ul.no-bullet{list-style:none}ol{margin-left:1.4rem}ol li ul,ol li ol{margin-left:1.25rem;margin-bottom:0}dl dt{margin-bottom:0.3rem;font-weight:bold}dl dd{margin-bottom:0.75rem}abbr,acronym{text-transform:uppercase;font-size:90%;color:#333;cursor:help}abbr{text-transform:none}abbr[title]{border-bottom:1px dotted #ddd}blockquote{margin:0 0 1.25rem;padding:0.5625rem 1.25rem 0 1.1875rem;border-left:1px solid #ddd}blockquote cite{display:block;font-size:0.8125rem;color:#626262}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:#626262}blockquote,blockquote p{line-height:1.6;color:#7a7a7a}.vcard{display:inline-block;margin:0 0 1.25rem 0;border:1px solid #ddd;padding:0.625rem 0.75rem}.vcard li{margin:0;display:block}.vcard .fn{font-weight:bold;font-size:0.9375rem}.vevent .summary{font-weight:bold}.vevent abbr{cursor:default;text-decoration:none;font-weight:bold;border:none;padding:0 0.0625rem}@media only screen and (min-width: 40.063em){h1,h2,h3,h4,h5,h6{line-height:1.4}h1{font-size:2.75rem}h2{font-size:2.3125rem}h3{font-size:1.6875rem}h4{font-size:1.4375rem}h5{font-size:1.125rem}h6{font-size:1rem}}button,.button{border-style:solid;border-width:0;cursor:pointer;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal;line-height:normal;margin:0 0 1.25rem;position:relative;text-decoration:none;text-align:center;-webkit-appearance:none;-moz-appearance:none;border-radius:0;display:inline-block;padding-top:1rem;padding-right:2rem;padding-bottom:1.0625rem;padding-left:2rem;font-size:1rem;background-color:#2a808d;border-color:#226671;color:#fff;transition:background-color 300ms ease-out}button:hover,button:focus,.button:hover,.button:focus{background-color:#226671}button:hover,button:focus,.button:hover,.button:focus{color:#fff}button.secondary,.button.secondary{background-color:#8296a6;border-color:#62798a;color:#fff}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{background-color:#62798a}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{color:#fff}button.success,.button.success{background-color:#68c09e;border-color:#46a781;color:#fff}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{background-color:#46a781}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{color:#fff}button.alert,.button.alert{background-color:#c60f13;border-color:#9e0c0f;color:#fff}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{background-color:#9e0c0f}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{color:#fff}button.warning,.button.warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff}button.warning:hover,button.warning:focus,.button.warning:hover,.button.warning:focus{background-color:#cf6e0e}button.warning:hover,button.warning:focus,.button.warning:hover,.button.warning:focus{color:#fff}button.info,.button.info{background-color:#a0d3e8;border-color:#61b6d9;color:#333}button.info:hover,button.info:focus,.button.info:hover,.button.info:focus{background-color:#61b6d9}button.info:hover,button.info:focus,.button.info:hover,.button.info:focus{color:#fff}button.large,.button.large{padding-top:1.125rem;padding-right:2.25rem;padding-bottom:1.1875rem;padding-left:2.25rem;font-size:1.25rem}button.small,.button.small{padding-top:0.875rem;padding-right:1.75rem;padding-bottom:0.9375rem;padding-left:1.75rem;font-size:0.8125rem}button.tiny,.button.tiny{padding-top:0.625rem;padding-right:1.25rem;padding-bottom:0.6875rem;padding-left:1.25rem;font-size:0.6875rem}button.expand,.button.expand{padding-right:0;padding-left:0;width:100%}button.left-align,.button.left-align{text-align:left;text-indent:0.75rem}button.right-align,.button.right-align{text-align:right;padding-right:0.75rem}button.radius,.button.radius{border-radius:3px}button.round,.button.round{border-radius:1000px}button.disabled,button[disabled],.button.disabled,.button[disabled]{background-color:#2a808d;border-color:#226671;color:#fff;cursor:default;opacity:0.7;box-shadow:none}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#226671}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{color:#fff}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#2a808d}button.disabled.secondary,button[disabled].secondary,.button.disabled.secondary,.button[disabled].secondary{background-color:#8296a6;border-color:#62798a;color:#fff;cursor:default;opacity:0.7;box-shadow:none}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#62798a}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{color:#fff}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#8296a6}button.disabled.success,button[disabled].success,.button.disabled.success,.button[disabled].success{background-color:#68c09e;border-color:#46a781;color:#fff;cursor:default;opacity:0.7;box-shadow:none}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#46a781}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{color:#fff}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#68c09e}button.disabled.alert,button[disabled].alert,.button.disabled.alert,.button[disabled].alert{background-color:#c60f13;border-color:#9e0c0f;color:#fff;cursor:default;opacity:0.7;box-shadow:none}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#9e0c0f}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{color:#fff}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#c60f13}button.disabled.warning,button[disabled].warning,.button.disabled.warning,.button[disabled].warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff;cursor:default;opacity:0.7;box-shadow:none}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{background-color:#cf6e0e}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{color:#fff}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{background-color:#f08a24}button.disabled.info,button[disabled].info,.button.disabled.info,.button[disabled].info{background-color:#a0d3e8;border-color:#61b6d9;color:#333;cursor:default;opacity:0.7;box-shadow:none}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{background-color:#61b6d9}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{color:#fff}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{background-color:#a0d3e8}button::-moz-focus-inner{border:0;padding:0}@media only screen and (min-width: 40.063em){button,.button{display:inline-block}}form{margin:0 0 1rem}form .row .row{margin:0 -0.5rem}form .row .row .column,form .row .row .columns{padding:0 0.5rem}form .row .row.collapse{margin:0}form .row .row.collapse .column,form .row .row.collapse .columns{padding:0}form .row .row.collapse input{-webkit-border-bottom-right-radius:0;-webkit-border-top-right-radius:0;border-bottom-right-radius:0;border-top-right-radius:0}form .row input.column,form .row input.columns,form .row textarea.column,form .row textarea.columns{padding-left:0.5rem}label{font-size:0.875rem;color:#4d4d4d;cursor:pointer;display:block;font-weight:normal;line-height:1.5;margin-bottom:0}label.right{float:none !important;text-align:right}label.inline{margin:0 0 1rem 0;padding:0.5625rem 0}label small{text-transform:capitalize;color:#676767}.prefix,.postfix{display:block;position:relative;z-index:2;text-align:center;width:100%;padding-top:0;padding-bottom:0;border-style:solid;border-width:1px;overflow:visible;font-size:0.875rem;height:2.3125rem;line-height:2.3125rem}.postfix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;border:none}.prefix.button{padding-left:0;padding-right:0;padding-top:0;padding-bottom:0;text-align:center;border:none}.prefix.button.radius{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.postfix.button.radius{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}.prefix.button.round{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.postfix.button.round{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}span.prefix,label.prefix{background:#f2f2f2;border-right:none;color:#333;border-color:#ccc}span.postfix,label.postfix{background:#f2f2f2;border-left:none;color:#333;border-color:#ccc}input[type="text"],input[type="password"],input[type="date"],input[type="datetime"],input[type="datetime-local"],input[type="month"],input[type="week"],input[type="email"],input[type="number"],input[type="search"],input[type="tel"],input[type="time"],input[type="url"],input[type="color"],textarea{-webkit-appearance:none;border-radius:0;background-color:#fff;font-family:inherit;border-style:solid;border-width:1px;border-color:#ccc;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);color:rgba(0,0,0,0.75);display:block;font-size:0.875rem;margin:0 0 1rem 0;padding:0.5rem;height:2.3125rem;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;transition:all 0.15s linear}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,input[type="color"]:focus,textarea:focus{background:#fafafa;border-color:#999;outline:none}input[type="text"]:disabled,input[type="password"]:disabled,input[type="date"]:disabled,input[type="datetime"]:disabled,input[type="datetime-local"]:disabled,input[type="month"]:disabled,input[type="week"]:disabled,input[type="email"]:disabled,input[type="number"]:disabled,input[type="search"]:disabled,input[type="tel"]:disabled,input[type="time"]:disabled,input[type="url"]:disabled,input[type="color"]:disabled,textarea:disabled{background-color:#ddd;cursor:default}input[type="text"][disabled],input[type="text"][readonly],fieldset[disabled] input[type="text"],input[type="password"][disabled],input[type="password"][readonly],fieldset[disabled] input[type="password"],input[type="date"][disabled],input[type="date"][readonly],fieldset[disabled] input[type="date"],input[type="datetime"][disabled],input[type="datetime"][readonly],fieldset[disabled] input[type="datetime"],input[type="datetime-local"][disabled],input[type="datetime-local"][readonly],fieldset[disabled] input[type="datetime-local"],input[type="month"][disabled],input[type="month"][readonly],fieldset[disabled] input[type="month"],input[type="week"][disabled],input[type="week"][readonly],fieldset[disabled] input[type="week"],input[type="email"][disabled],input[type="email"][readonly],fieldset[disabled] input[type="email"],input[type="number"][disabled],input[type="number"][readonly],fieldset[disabled] input[type="number"],input[type="search"][disabled],input[type="search"][readonly],fieldset[disabled] input[type="search"],input[type="tel"][disabled],input[type="tel"][readonly],fieldset[disabled] input[type="tel"],input[type="time"][disabled],input[type="time"][readonly],fieldset[disabled] input[type="time"],input[type="url"][disabled],input[type="url"][readonly],fieldset[disabled] input[type="url"],input[type="color"][disabled],input[type="color"][readonly],fieldset[disabled] input[type="color"],textarea[disabled],textarea[readonly],fieldset[disabled] textarea{background-color:#ddd;cursor:default}input[type="text"].radius,input[type="password"].radius,input[type="date"].radius,input[type="datetime"].radius,input[type="datetime-local"].radius,input[type="month"].radius,input[type="week"].radius,input[type="email"].radius,input[type="number"].radius,input[type="search"].radius,input[type="tel"].radius,input[type="time"].radius,input[type="url"].radius,input[type="color"].radius,textarea.radius{border-radius:3px}form .row .prefix-radius.row.collapse input,form .row .prefix-radius.row.collapse textarea,form .row .prefix-radius.row.collapse select,form .row .prefix-radius.row.collapse button{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}form .row .prefix-radius.row.collapse .prefix{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}form .row .postfix-radius.row.collapse input,form .row .postfix-radius.row.collapse textarea,form .row .postfix-radius.row.collapse select,form .row .postfix-radius.row.collapse button{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}form .row .postfix-radius.row.collapse .postfix{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}form .row .prefix-round.row.collapse input,form .row .prefix-round.row.collapse textarea,form .row .prefix-round.row.collapse select,form .row .prefix-round.row.collapse button{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}form .row .prefix-round.row.collapse .prefix{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}form .row .postfix-round.row.collapse input,form .row .postfix-round.row.collapse textarea,form .row .postfix-round.row.collapse select,form .row .postfix-round.row.collapse button{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}form .row .postfix-round.row.collapse .postfix{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}input[type="submit"]{-webkit-appearance:none;border-radius:0}textarea[rows]{height:auto}textarea{max-width:100%}select{-webkit-appearance:none !important;border-radius:0;background-color:#FAFAFA;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+);background-position:100% center;background-repeat:no-repeat;border-style:solid;border-width:1px;border-color:#ccc;padding:0.5rem;font-size:0.875rem;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;color:rgba(0,0,0,0.75);line-height:normal;border-radius:0;height:2.3125rem}select::-ms-expand{display:none}select.radius{border-radius:3px}select:hover{background-color:#f3f3f3;border-color:#999}select:disabled{background-color:#ddd;cursor:default}select[multiple]{height:auto}input[type="file"],input[type="checkbox"],input[type="radio"],select{margin:0 0 1rem 0}input[type="checkbox"]+label,input[type="radio"]+label{display:inline-block;margin-left:0.5rem;margin-right:1rem;margin-bottom:0;vertical-align:baseline}input[type="file"]{width:100%}fieldset{border:1px solid #ddd;padding:1.25rem;margin:1.125rem 0}fieldset legend{font-weight:bold;background:#fff;padding:0 0.1875rem;margin:0;margin-left:-0.1875rem}[data-abide] .error small.error,[data-abide] .error span.error,[data-abide] span.error,[data-abide] small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#c60f13;color:#fff}[data-abide] span.error,[data-abide] small.error{display:none}span.error,small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#c60f13;color:#fff}.error input,.error textarea,.error select{margin-bottom:0}.error input[type="checkbox"],.error input[type="radio"]{margin-bottom:1rem}.error label,.error label.error{color:#c60f13}.error small.error{display:block;padding:0.375rem 0.5625rem 0.5625rem;margin-top:-1px;margin-bottom:1rem;font-size:0.75rem;font-weight:normal;font-style:italic;background:#c60f13;color:#fff}.error>label>small{color:#676767;background:transparent;padding:0;text-transform:capitalize;font-style:normal;font-size:60%;margin:0;display:inline}.error span.error-message{display:block}input.error,textarea.error,select.error{margin-bottom:0}label.error{color:#c60f13}@media only screen{.show-for-small-only,.show-for-small-up,.show-for-small,.show-for-small-down,.hide-for-medium-only,.hide-for-medium-up,.hide-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.hide-for-small-only,.hide-for-small-up,.hide-for-small,.hide-for-small-down,.show-for-medium-only,.show-for-medium-up,.show-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.visible-for-small-only,.visible-for-small-up,.visible-for-small,.visible-for-small-down,.hidden-for-medium-only,.hidden-for-medium-up,.hidden-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.hidden-for-small-only,.hidden-for-small-up,.hidden-for-small,.hidden-for-small-down,.visible-for-medium-only,.visible-for-medium-up,.visible-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}table.show-for-small-only,table.show-for-small-up,table.show-for-small,table.show-for-small-down,table.hide-for-medium-only,table.hide-for-medium-up,table.hide-for-medium,table.show-for-medium-down,table.hide-for-large-only,table.hide-for-large-up,table.hide-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.show-for-small-only,thead.show-for-small-up,thead.show-for-small,thead.show-for-small-down,thead.hide-for-medium-only,thead.hide-for-medium-up,thead.hide-for-medium,thead.show-for-medium-down,thead.hide-for-large-only,thead.hide-for-large-up,thead.hide-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.show-for-small-only,tbody.show-for-small-up,tbody.show-for-small,tbody.show-for-small-down,tbody.hide-for-medium-only,tbody.hide-for-medium-up,tbody.hide-for-medium,tbody.show-for-medium-down,tbody.hide-for-large-only,tbody.hide-for-large-up,tbody.hide-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.show-for-small-only,tr.show-for-small-up,tr.show-for-small,tr.show-for-small-down,tr.hide-for-medium-only,tr.hide-for-medium-up,tr.hide-for-medium,tr.show-for-medium-down,tr.hide-for-large-only,tr.hide-for-large-up,tr.hide-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.show-for-small-only,td.show-for-small-only,th.show-for-small-up,td.show-for-small-up,th.show-for-small,td.show-for-small,th.show-for-small-down,td.show-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.hide-for-medium-up,td.hide-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.show-for-medium-down,td.show-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.hide-for-large-up,td.hide-for-large-up,th.hide-for-large,td.hide-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 40.063em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.show-for-medium-only,.show-for-medium-up,.show-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.hide-for-medium-only,.hide-for-medium-up,.hide-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.visible-for-medium-only,.visible-for-medium-up,.visible-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.hidden-for-medium-only,.hidden-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.show-for-medium-only,table.show-for-medium-up,table.show-for-medium,table.show-for-medium-down,table.hide-for-large-only,table.hide-for-large-up,table.hide-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.show-for-medium-only,thead.show-for-medium-up,thead.show-for-medium,thead.show-for-medium-down,thead.hide-for-large-only,thead.hide-for-large-up,thead.hide-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.show-for-medium-only,tbody.show-for-medium-up,tbody.show-for-medium,tbody.show-for-medium-down,tbody.hide-for-large-only,tbody.hide-for-large-up,tbody.hide-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.show-for-medium-only,tr.show-for-medium-up,tr.show-for-medium,tr.show-for-medium-down,tr.hide-for-large-only,tr.hide-for-large-up,tr.hide-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.show-for-medium-only,td.show-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.show-for-medium,td.show-for-medium,th.show-for-medium-down,td.show-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.hide-for-large-up,td.hide-for-large-up,th.hide-for-large,td.hide-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 64.063em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.show-for-large-only,table.show-for-large-up,table.show-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.show-for-large-only,thead.show-for-large-up,thead.show-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.show-for-large-only,tbody.show-for-large-up,tbody.show-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.show-for-large-only,tr.show-for-large-up,tr.show-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.show-for-large-only,td.show-for-large-only,th.show-for-large-up,td.show-for-large-up,th.show-for-large,td.show-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 90.063em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.hide-for-large-only,.show-for-large-up,.hide-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.show-for-large-only,.hide-for-large-up,.show-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.hidden-for-large-only,.visible-for-large-up,.hidden-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.visible-for-large-only,.hidden-for-large-up,.visible-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-large-only,table.show-for-large-up,table.hide-for-large,table.hide-for-large-down,table.show-for-xlarge-only,table.show-for-xlarge-up,table.show-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-large-only,thead.show-for-large-up,thead.hide-for-large,thead.hide-for-large-down,thead.show-for-xlarge-only,thead.show-for-xlarge-up,thead.show-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-large-only,tbody.show-for-large-up,tbody.hide-for-large,tbody.hide-for-large-down,tbody.show-for-xlarge-only,tbody.show-for-xlarge-up,tbody.show-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-large-only,tr.show-for-large-up,tr.hide-for-large,tr.hide-for-large-down,tr.show-for-xlarge-only,tr.show-for-xlarge-up,tr.show-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.show-for-large-up,td.show-for-large-up,th.hide-for-large,td.hide-for-large,th.hide-for-large-down,td.hide-for-large-down,th.show-for-xlarge-only,td.show-for-xlarge-only,th.show-for-xlarge-up,td.show-for-xlarge-up,th.show-for-xlarge,td.show-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 120.063em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.hide-for-large-only,.show-for-large-up,.hide-for-large,.hide-for-large-down,.hide-for-xlarge-only,.show-for-xlarge-up,.hide-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.show-for-large-only,.hide-for-large-up,.show-for-large,.show-for-large-down,.show-for-xlarge-only,.hide-for-xlarge-up,.show-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.hidden-for-large-only,.visible-for-large-up,.hidden-for-large,.hidden-for-large-down,.hidden-for-xlarge-only,.visible-for-xlarge-up,.hidden-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.visible-for-large-only,.hidden-for-large-up,.visible-for-large,.visible-for-large-down,.visible-for-xlarge-only,.hidden-for-xlarge-up,.visible-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.hidden-for-xxlarge-down{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-large-only,table.show-for-large-up,table.hide-for-large,table.hide-for-large-down,table.hide-for-xlarge-only,table.show-for-xlarge-up,table.hide-for-xlarge,table.hide-for-xlarge-down,table.show-for-xxlarge-only,table.show-for-xxlarge-up,table.show-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-large-only,thead.show-for-large-up,thead.hide-for-large,thead.hide-for-large-down,thead.hide-for-xlarge-only,thead.show-for-xlarge-up,thead.hide-for-xlarge,thead.hide-for-xlarge-down,thead.show-for-xxlarge-only,thead.show-for-xxlarge-up,thead.show-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-large-only,tbody.show-for-large-up,tbody.hide-for-large,tbody.hide-for-large-down,tbody.hide-for-xlarge-only,tbody.show-for-xlarge-up,tbody.hide-for-xlarge,tbody.hide-for-xlarge-down,tbody.show-for-xxlarge-only,tbody.show-for-xxlarge-up,tbody.show-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-large-only,tr.show-for-large-up,tr.hide-for-large,tr.hide-for-large-down,tr.hide-for-xlarge-only,tr.show-for-xlarge-up,tr.hide-for-xlarge,tr.hide-for-xlarge-down,tr.show-for-xxlarge-only,tr.show-for-xxlarge-up,tr.show-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.show-for-large-up,td.show-for-large-up,th.hide-for-large,td.hide-for-large,th.hide-for-large-down,td.hide-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.show-for-xlarge-up,td.show-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.hide-for-xlarge-down,td.hide-for-xlarge-down,th.show-for-xxlarge-only,td.show-for-xxlarge-only,th.show-for-xxlarge-up,td.show-for-xxlarge-up,th.show-for-xxlarge,td.show-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.hide-for-landscape,table.show-for-portrait{display:table !important}thead.hide-for-landscape,thead.show-for-portrait{display:table-header-group !important}tbody.hide-for-landscape,tbody.show-for-portrait{display:table-row-group !important}tr.hide-for-landscape,tr.show-for-portrait{display:table-row !important}td.hide-for-landscape,td.show-for-portrait,th.hide-for-landscape,th.show-for-portrait{display:table-cell !important}@media only screen and (orientation: landscape){.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.show-for-landscape,table.hide-for-portrait{display:table !important}thead.show-for-landscape,thead.hide-for-portrait{display:table-header-group !important}tbody.show-for-landscape,tbody.hide-for-portrait{display:table-row-group !important}tr.show-for-landscape,tr.hide-for-portrait{display:table-row !important}td.show-for-landscape,td.hide-for-portrait,th.show-for-landscape,th.hide-for-portrait{display:table-cell !important}}@media only screen and (orientation: portrait){.show-for-portrait,.hide-for-landscape{display:inherit !important}.hide-for-portrait,.show-for-landscape{display:none !important}table.show-for-portrait,table.hide-for-landscape{display:table !important}thead.show-for-portrait,thead.hide-for-landscape{display:table-header-group !important}tbody.show-for-portrait,tbody.hide-for-landscape{display:table-row-group !important}tr.show-for-portrait,tr.hide-for-landscape{display:table-row !important}td.show-for-portrait,td.hide-for-landscape,th.show-for-portrait,th.hide-for-landscape{display:table-cell !important}}.show-for-touch{display:none !important}.hide-for-touch{display:inherit !important}.touch .show-for-touch{display:inherit !important}.touch .hide-for-touch{display:none !important}table.hide-for-touch{display:table !important}.touch table.show-for-touch{display:table !important}thead.hide-for-touch{display:table-header-group !important}.touch thead.show-for-touch{display:table-header-group !important}tbody.hide-for-touch{display:table-row-group !important}.touch tbody.show-for-touch{display:table-row-group !important}tr.hide-for-touch{display:table-row !important}.touch tr.show-for-touch{display:table-row !important}td.hide-for-touch{display:table-cell !important}.touch td.show-for-touch{display:table-cell !important}th.hide-for-touch{display:table-cell !important}.touch th.show-for-touch{display:table-cell !important}.print-only{display:none !important}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}.show-for-print{display:block}.hide-for-print{display:none}table.show-for-print{display:table !important}thead.show-for-print{display:table-header-group !important}tbody.show-for-print{display:table-row-group !important}tr.show-for-print{display:table-row !important}td.show-for-print{display:table-cell !important}th.show-for-print{display:table-cell !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.hide-on-print{display:none !important}.print-only{display:block !important}.hide-for-print{display:none !important}.show-for-print{display:inherit !important}}@media print{.show-for-print{display:block}.hide-for-print{display:none}table.show-for-print{display:table !important}thead.show-for-print{display:table-header-group !important}tbody.show-for-print{display:table-row-group !important}tr.show-for-print{display:table-row !important}td.show-for-print{display:table-cell !important}th.show-for-print{display:table-cell !important}} 2 | --------------------------------------------------------------------------------