├── mysite ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── bokehdash ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── admin.py ├── tests.py ├── apps.py ├── templates │ └── bokehdash │ │ ├── home.html │ │ ├── bokeh_server.html │ │ ├── boxplot.html │ │ ├── base.html │ │ └── login.html ├── forms.py ├── BokehApps │ ├── sliders.py │ ├── boxplot.py │ └── selection_histogram.py └── views.py ├── db.sqlite3 ├── static ├── js │ ├── bokeh-api.json │ ├── bokeh-gl.json │ ├── bokeh-tables.json │ ├── bokeh-widgets.json │ ├── compiler │ │ └── compile.js │ └── bokeh.json └── admin │ ├── fonts │ ├── Roboto-Bold-webfont.woff │ ├── Roboto-Light-webfont.woff │ ├── Roboto-Regular-webfont.woff │ ├── README.txt │ └── LICENSE.txt │ ├── js │ ├── cancel.js │ ├── prepopulate.min.js │ ├── jquery.init.js │ ├── prepopulate_init.js │ ├── popup_response.js │ ├── collapse.min.js │ ├── change_form.js │ ├── vendor │ │ ├── xregexp │ │ │ └── LICENSE-XREGEXP.txt │ │ └── jquery │ │ │ └── LICENSE-JQUERY.txt │ ├── collapse.js │ ├── prepopulate.js │ ├── actions.min.js │ ├── timeparse.js │ ├── inlines.min.js │ ├── SelectBox.js │ ├── actions.js │ ├── admin │ │ └── RelatedObjectLookups.js │ ├── urlify.js │ ├── calendar.js │ ├── core.js │ ├── SelectFilter2.js │ └── inlines.js │ ├── img │ ├── tooltag-arrowright.svg │ ├── README.txt │ ├── icon-addlink.svg │ ├── tooltag-add.svg │ ├── icon-changelink.svg │ ├── icon-deletelink.svg │ ├── icon-yes.svg │ ├── search.svg │ ├── icon-alert.svg │ ├── icon-no.svg │ ├── inline-delete.svg │ ├── icon-unknown.svg │ ├── icon-unknown-alt.svg │ ├── icon-clock.svg │ ├── gis │ │ ├── move_vertex_on.svg │ │ └── move_vertex_off.svg │ ├── icon-calendar.svg │ ├── calendar-icons.svg │ ├── LICENSE │ ├── sorting-icons.svg │ └── selector-icons.svg │ └── css │ ├── fonts.css │ ├── dashboard.css │ ├── login.css │ ├── rtl.css │ ├── changelists.css │ ├── forms.css │ └── widgets.css ├── documentation ├── config_files │ ├── requirements.txt │ ├── gunicorn.service │ ├── bokehserver.service │ ├── mysite │ ├── sudoers │ └── sshd_config └── Linode_Instructions.txt ├── .gitattributes ├── manage.py ├── .gitignore ├── LICENSE └── ReadMe.md /mysite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bokehdash/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bokehdash/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonoAnalytics/BokehDjango/HEAD/db.sqlite3 -------------------------------------------------------------------------------- /bokehdash/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /bokehdash/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /bokehdash/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /static/js/bokeh-api.json: -------------------------------------------------------------------------------- 1 | ["api/charts","api/index","api/linalg","api/main","api/models","api/palettes","api/plotting"] -------------------------------------------------------------------------------- /bokehdash/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BokehdashConfig(AppConfig): 5 | name = 'bokehdash' 6 | -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonoAnalytics/BokehDjango/HEAD/static/admin/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonoAnalytics/BokehDjango/HEAD/static/admin/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonoAnalytics/BokehDjango/HEAD/static/admin/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /static/admin/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Roboto webfont source: https://www.google.com/fonts/specimen/Roboto 2 | Weights used in this project: Light (300), Regular (400), Bold (700) 3 | -------------------------------------------------------------------------------- /static/js/bokeh-gl.json: -------------------------------------------------------------------------------- 1 | ["models/glyphs/webgl/base","models/glyphs/webgl/gloo2","models/glyphs/webgl/index","models/glyphs/webgl/line","models/glyphs/webgl/main","models/glyphs/webgl/markers"] -------------------------------------------------------------------------------- /bokehdash/templates/bokehdash/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'bokehdash/base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Welcome, {{ user.username }}!

6 |
7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /bokehdash/templates/bokehdash/bokeh_server.html: -------------------------------------------------------------------------------- 1 | {% extends 'bokehdash/base.html' %} 2 | {% block content %} 3 |
4 | {% load static %} 5 |
6 |

{{ graphname }}

7 | {{server_script|safe}} 8 | {% endblock %} -------------------------------------------------------------------------------- /static/admin/js/cancel.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | $(function() { 4 | $('.cancel-link').click(function(e) { 5 | e.preventDefault(); 6 | window.history.back(); 7 | }); 8 | }); 9 | })(django.jQuery); 10 | -------------------------------------------------------------------------------- /static/js/bokeh-tables.json: -------------------------------------------------------------------------------- 1 | ["models/widgets/tables/cell_editors","models/widgets/tables/cell_formatters","models/widgets/tables/data_table","models/widgets/tables/index","models/widgets/tables/main","models/widgets/tables/table_column","models/widgets/tables/table_widget","models/widgets/widget"] -------------------------------------------------------------------------------- /documentation/config_files/requirements.txt: -------------------------------------------------------------------------------- 1 | bokeh==0.12.9 2 | certifi==2016.2.28 3 | Django==1.11.3 4 | gunicorn==19.7.1 5 | Jinja2==2.9.6 6 | MarkupSafe==1.0 7 | numpy==1.13.3 8 | pandas==0.21.0 9 | python-dateutil==2.6.1 10 | pytz==2017.2 11 | PyYAML==3.12 12 | six==1.11.0 13 | tornado==4.5.2 14 | wincertstore==0.2 15 | -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/README.txt: -------------------------------------------------------------------------------- 1 | All icons are taken from Font Awesome (http://fontawesome.io/) project. 2 | The Font Awesome font is licensed under the SIL OFL 1.1: 3 | - http://scripts.sil.org/OFL 4 | 5 | SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG 6 | Font-Awesome-SVG-PNG is licensed under the MIT license (see file license 7 | in current folder). 8 | -------------------------------------------------------------------------------- /static/admin/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/tooltag-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(c){c.fn.prepopulate=function(e,f,g){return this.each(function(){var a=c(this),b=function(){if(!a.data("_changed")){var b=[];c.each(e,function(a,d){d=c(d);0 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /*global django:true, jQuery:false*/ 2 | /* Puts the included jQuery into our own namespace using noConflict and passing 3 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 4 | * namespace (i.e. this preserves pre-existing values for both window.$ and 5 | * window.jQuery). 6 | */ 7 | var django = django || {}; 8 | django.jQuery = jQuery.noConflict(true); 9 | -------------------------------------------------------------------------------- /static/admin/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /bokehdash/templates/bokehdash/boxplot.html: -------------------------------------------------------------------------------- 1 | {% extends 'bokehdash/base.html' %} 2 | {% block content %} 3 |
4 | {% load static %} 5 | 6 | 7 |
8 |
9 |

{{graphname }}

10 | {{div|safe}} 11 | {{js|safe}} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /documentation/config_files/gunicorn.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=gunicorn daemon 3 | After=network.target 4 | 5 | [Service] 6 | User=deployer1 7 | Group=www-data 8 | WorkingDirectory=/home/deployer1/BokehDjango 9 | ExecStart=/home/deployer1/envs/BokehDjango352/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/deployer1/BokehDjango/mysite/mysite.sock mysite.wsgi:application 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /static/admin/img/icon-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for BokehDjango project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /static/admin/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('../fonts/Roboto-Bold-webfont.woff'); 4 | font-weight: 700; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: url('../fonts/Roboto-Regular-webfont.woff'); 11 | font-weight: 400; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Roboto'; 17 | src: url('../fonts/Roboto-Light-webfont.woff'); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /documentation/config_files/bokehserver.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Bokeh Server 3 | After=network.target 4 | 5 | [Service] 6 | Environment="BOKEH_SECRET_KEY=kitjOI83DgklnTuUykyHYABBCaV8oItJTZTQqVBav97G" 7 | User=deployer1 8 | Group=www-data 9 | WorkingDirectory=/home/deployer1/BokehDjango/bokehdash/BokehApps 10 | ExecStart=/home/deployer1/envs/BokehDjango352/bin/bokeh serve sliders.py selection_histogram.py --allow-websocket-origin 45.33.6.39 --address 127.0.0.1 --session-ids external-signed 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | -o-text-overflow: ellipsis; 27 | } 28 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate_init.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | var fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields'); 4 | $.each(fields, function(index, field) { 5 | $('.empty-form .form-row .field-' + field.name + ', .empty-form.form-row .field-' + field.name).addClass('prepopulated_field'); 6 | $(field.id).data('dependency_list', field.dependency_list).prepopulate( 7 | field.dependency_ids, field.maxLength, field.allowUnicode 8 | ); 9 | }); 10 | })(django.jQuery); 11 | -------------------------------------------------------------------------------- /static/admin/img/icon-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/inline-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/js/popup_response.js: -------------------------------------------------------------------------------- 1 | /*global opener */ 2 | (function() { 3 | 'use strict'; 4 | var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse); 5 | switch(initData.action) { 6 | case 'change': 7 | opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value); 8 | break; 9 | case 'delete': 10 | opener.dismissDeleteRelatedObjectPopup(window, initData.value); 11 | break; 12 | default: 13 | opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj); 14 | break; 15 | } 16 | })(); 17 | -------------------------------------------------------------------------------- /static/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(b,c){0===a(c).find("div.errors").length&&a(c).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(b){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return!1})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /static/admin/img/icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-unknown-alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/admin/js/change_form.js: -------------------------------------------------------------------------------- 1 | /*global showAddAnotherPopup, showRelatedObjectLookupPopup showRelatedObjectPopup updateRelatedObjectLinks*/ 2 | 3 | (function($) { 4 | 'use strict'; 5 | $(document).ready(function() { 6 | var modelName = $('#django-admin-form-add-constants').data('modelName'); 7 | $('body').on('click', '.add-another', function(e) { 8 | e.preventDefault(); 9 | var event = $.Event('django:add-another-related'); 10 | $(this).trigger(event); 11 | if (!event.isDefaultPrevented()) { 12 | showAddAnotherPopup(this); 13 | } 14 | }); 15 | 16 | if (modelName) { 17 | $('form#' + modelName + '_form :input:visible:enabled:first').focus(); 18 | } 19 | }); 20 | })(django.jQuery); 21 | -------------------------------------------------------------------------------- /static/js/bokeh-widgets.json: -------------------------------------------------------------------------------- 1 | ["models/widgets/abstract_button","models/widgets/abstract_icon","models/widgets/abstract_slider","models/widgets/autocomplete_input","models/widgets/button","models/widgets/checkbox_button_group","models/widgets/checkbox_group","models/widgets/common","models/widgets/date_picker","models/widgets/date_range_slider","models/widgets/date_slider","models/widgets/div","models/widgets/dropdown","models/widgets/index","models/widgets/input_widget","models/widgets/main","models/widgets/markup","models/widgets/multiselect","models/widgets/panel","models/widgets/paragraph","models/widgets/password_input","models/widgets/pretext","models/widgets/radio_button_group","models/widgets/radio_group","models/widgets/range_slider","models/widgets/selectbox","models/widgets/slider","models/widgets/tabs","models/widgets/text_input","models/widgets/toggle","models/widgets/widget"] -------------------------------------------------------------------------------- /bokehdash/templates/bokehdash/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Bokeh Server via Django 10 | 11 | 12 | 13 |
14 | logout 15 |
16 | boxplot 17 | sliders 18 | selection histogram 19 |
20 |
21 |
22 | {% block content %} 23 | {% endblock %} 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /documentation/config_files/mysite: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name 45.33.6.39; 4 | 5 | access_log /var/log/mysite.access.log; 6 | error_log /var/log/mysite.error.log debug; 7 | 8 | location /static/ { 9 | alias /home/deployer1/BokehDjango/static/; 10 | } 11 | 12 | # Intercept bokehproxy urls to send to bokeh process 13 | location /bokehproxy/ { 14 | proxy_pass http://127.0.0.1:5006/; 15 | proxy_set_header Upgrade $http_upgrade; 16 | proxy_set_header Connection "upgrade"; 17 | proxy_http_version 1.1; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header Host $host:$server_port; 20 | proxy_buffering off; 21 | } 22 | 23 | location / { 24 | include proxy_params; 25 | proxy_pass http://unix:/home/deployer1/BokehDjango/mysite/mysite.sock; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | # Ignored Files and Directories 50 | .idea 51 | *.pyc -------------------------------------------------------------------------------- /bokehdash/templates/bokehdash/login.html: -------------------------------------------------------------------------------- 1 | {% block content %} 2 |

Log in

3 | {% if form.errors %} 4 |

Your username and password didn't match. Please try again.

5 | {% endif %} 6 |
7 | {% csrf_token %} 8 | 9 | {% for field in form %} 10 |

11 | {{ field.label_tag }}
12 | {{ field }}
13 | {% for error in field.errors %} 14 |

{{ error }}

15 | {% endfor %} 16 | {% if field.help_text %} 17 |

{{ field.help_text }}

18 | {% endif %} 19 |

20 | {% endfor %} 21 | 22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /documentation/config_files/sudoers: -------------------------------------------------------------------------------- 1 | # 2 | # This file MUST be edited with the 'visudo' command as root. 3 | # 4 | # Please consider adding local content in /etc/sudoers.d/ instead of 5 | # directly modifying this file. 6 | # 7 | # See the man page for details on how to write a sudoers file. 8 | # 9 | Defaults env_reset 10 | Defaults mail_badpass 11 | Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" 12 | 13 | # Host alias specification 14 | 15 | # User alias specification 16 | 17 | # Cmnd alias specification 18 | 19 | # User privilege specification 20 | root ALL=(ALL:ALL) ALL 21 | 22 | # Members of the admin group may gain root privileges 23 | %admin ALL=(ALL) ALL 24 | 25 | # Allow members of group sudo to execute any command 26 | %sudo ALL=(ALL:ALL) ALL 27 | 28 | # See sudoers(5) for more information on "#include" directives: 29 | 30 | #includedir /etc/sudoers.d 31 | %deployers ALL=(ALL) ALL 32 | -------------------------------------------------------------------------------- /bokehdash/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class SignUpForm(UserCreationForm): 7 | first_name = forms.CharField( 8 | max_length=30, 9 | required=False, 10 | help_text='Optional.') 11 | last_name = forms.CharField( 12 | max_length=30, 13 | required=False, 14 | help_text='Optional.') 15 | email = forms.EmailField( 16 | max_length=254, 17 | help_text='Required. Inform a valid email address.') 18 | 19 | class Meta: 20 | model = User 21 | fields = ( 22 | 'username', 23 | 'first_name', 24 | 'last_name', 25 | 'email', 26 | 'password1', 27 | 'password2') 28 | 29 | 30 | class UploadFileForm(forms.Form): 31 | file = forms.FileField() 32 | 33 | 34 | class ViewCapaForm(forms.Form): 35 | action_type = forms.CharField(widget=forms.HiddenInput()) 36 | -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/admin/img/icon-calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kono Analytics 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/admin/img/calendar-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Bokeh Server using Django Tutorial 2 | The intention of this project is to list all of the steps necessary to run static Bokeh plots and interactive 3 | Bokeh Server plots on the Django web framework. 4 | 5 | It includes instructions necessary to launch on a production server. I used Linode, but the instructions should be 6 | generic enough to extend to other hosting platforms. 7 | 8 | ## Requirements: 9 | * Ubuntu 16.04 LTS 10 | * Python 3.5.2 11 | 12 | Step by Step instructions are located in the documentation folder in a file called Linode_Instructions.txt 13 | 14 | ## Attributes: 15 | * Matthew Makai's book, "Full Stack Python Guide to Deployments", was very helpful with regards to providing a tutorial 16 | to launch a hardened production server. 17 | http://www.deploypython.com/ 18 | * The Bokeh Google Group, especially Bryan Van de Ven on the Anaconda team, helped whenever I had any problems or 19 | questions. https://groups.google.com/a/continuum.io/forum/#!forum/bokeh 20 | 21 | 22 | 23 | ## Contact 24 | Feel free to email with any questions/comments/suggestions/bug reports/feedback. 25 | 26 | info@konoanalytics.com 27 | https://konoanalytics.com 28 | -------------------------------------------------------------------------------- /static/admin/img/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Code Charm Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /static/admin/img/sorting-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /static/admin/js/vendor/xregexp/LICENSE-XREGEXP.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007-2012 Steven Levithan 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 | -------------------------------------------------------------------------------- /static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | /*global gettext*/ 2 | (function($) { 3 | 'use strict'; 4 | $(document).ready(function() { 5 | // Add anchor tag for Show/Hide link 6 | $("fieldset.collapse").each(function(i, elem) { 7 | // Don't hide if fields in this fieldset have errors 8 | if ($(elem).find("div.errors").length === 0) { 9 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 11 | ')'); 12 | } 13 | }); 14 | // Add toggle to anchor tag 15 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 16 | if ($(this).closest("fieldset").hasClass("collapsed")) { 17 | // Show 18 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 19 | } else { 20 | // Hide 21 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 22 | } 23 | return false; 24 | }); 25 | }); 26 | })(django.jQuery); 27 | -------------------------------------------------------------------------------- /static/admin/js/vendor/jquery/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | ==== 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /mysite/urls.py: -------------------------------------------------------------------------------- 1 | """BokehDjango URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from django.contrib.auth import views as auth_views 19 | 20 | from bokehdash import views as bokehdash_views 21 | 22 | 23 | urlpatterns = [ 24 | url(r'^admin/', admin.site.urls), 25 | url(r'^login/$', auth_views.login, {'template_name': 'bokehdash/login.html'}, name='login'), 26 | url(r'^logout/$', auth_views.logout, {'next_page': 'login'}, name='logout'), 27 | url(r'^$', bokehdash_views.home, name='home'), 28 | url(r'^boxplot/', bokehdash_views.boxplot_view, name="boxplot"), 29 | url(r'^sliders/', bokehdash_views.sliders_view, name="sliders"), 30 | url(r'^histogram/', bokehdash_views.histogram_view, name="histogram"), 31 | 32 | ] 33 | -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #f8f8f8; 5 | } 6 | 7 | .login #header { 8 | height: auto; 9 | padding: 5px 16px; 10 | } 11 | 12 | .login #header h1 { 13 | font-size: 18px; 14 | } 15 | 16 | .login #header h1 a { 17 | color: #fff; 18 | } 19 | 20 | .login #content { 21 | padding: 20px 20px 0; 22 | } 23 | 24 | .login #container { 25 | background: #fff; 26 | border: 1px solid #eaeaea; 27 | border-radius: 4px; 28 | overflow: hidden; 29 | width: 28em; 30 | min-width: 300px; 31 | margin: 100px auto; 32 | } 33 | 34 | .login #content-main { 35 | width: 100%; 36 | } 37 | 38 | .login .form-row { 39 | padding: 4px 0; 40 | float: left; 41 | width: 100%; 42 | border-bottom: none; 43 | } 44 | 45 | .login .form-row label { 46 | padding-right: 0.5em; 47 | line-height: 2em; 48 | font-size: 1em; 49 | clear: both; 50 | color: #333; 51 | } 52 | 53 | .login .form-row #id_username, .login .form-row #id_password { 54 | clear: both; 55 | padding: 8px; 56 | width: 100%; 57 | -webkit-box-sizing: border-box; 58 | -moz-box-sizing: border-box; 59 | box-sizing: border-box; 60 | } 61 | 62 | .login span.help { 63 | font-size: 10px; 64 | display: block; 65 | } 66 | 67 | .login .submit-row { 68 | clear: both; 69 | padding: 1em 0 0 9.4em; 70 | margin: 0; 71 | border: none; 72 | background: none; 73 | text-align: left; 74 | } 75 | 76 | .login .password-reset-link { 77 | text-align: center; 78 | } 79 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | /*global URLify*/ 2 | (function($) { 3 | 'use strict'; 4 | $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { 5 | /* 6 | Depends on urlify.js 7 | Populates a selected field with the values of the dependent fields, 8 | URLifies and shortens the string. 9 | dependencies - array of dependent fields ids 10 | maxLength - maximum length of the URLify'd string 11 | allowUnicode - Unicode support of the URLify'd string 12 | */ 13 | return this.each(function() { 14 | var prepopulatedField = $(this); 15 | 16 | var populate = function() { 17 | // Bail if the field's value has been changed by the user 18 | if (prepopulatedField.data('_changed')) { 19 | return; 20 | } 21 | 22 | var values = []; 23 | $.each(dependencies, function(i, field) { 24 | field = $(field); 25 | if (field.val().length > 0) { 26 | values.push(field.val()); 27 | } 28 | }); 29 | prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); 30 | }; 31 | 32 | prepopulatedField.data('_changed', false); 33 | prepopulatedField.change(function() { 34 | prepopulatedField.data('_changed', true); 35 | }); 36 | 37 | if (!prepopulatedField.val()) { 38 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 39 | } 40 | }); 41 | }; 42 | })(django.jQuery); 43 | -------------------------------------------------------------------------------- /bokehdash/BokehApps/sliders.py: -------------------------------------------------------------------------------- 1 | ''' Present an interactive function explorer with slider widgets. 2 | Scrub the sliders to change the properties of the ``sin`` curve, or 3 | type into the title text box to update the title of the plot. 4 | Use the ``bokeh serve`` command to run the example by executing: 5 | bokeh serve sliders.py 6 | at your command prompt. Then navigate to the URL 7 | http://localhost:5006/sliders 8 | in your browser. 9 | ''' 10 | import numpy as np 11 | 12 | from bokeh.io import curdoc 13 | from bokeh.layouts import row, widgetbox 14 | from bokeh.models import ColumnDataSource 15 | from bokeh.models.widgets import Slider, TextInput 16 | from bokeh.plotting import figure 17 | 18 | # Set up data 19 | N = 200 20 | x = np.linspace(0, 4*np.pi, N) 21 | y = np.sin(x) 22 | source = ColumnDataSource(data=dict(x=x, y=y)) 23 | 24 | 25 | # Set up plot 26 | plot = figure(plot_height=400, plot_width=400, title="my sine wave", 27 | tools="crosshair,pan,reset,save,wheel_zoom", 28 | x_range=[0, 4*np.pi], y_range=[-2.5, 2.5]) 29 | 30 | plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) 31 | 32 | 33 | # Set up widgets 34 | text = TextInput(title="title", value='my sine wave') 35 | offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1) 36 | amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1) 37 | phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi) 38 | freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1) 39 | 40 | 41 | # Set up callbacks 42 | def update_title(attrname, old, new): 43 | plot.title.text = text.value 44 | 45 | text.on_change('value', update_title) 46 | 47 | def update_data(attrname, old, new): 48 | 49 | # Get the current slider values 50 | a = amplitude.value 51 | b = offset.value 52 | w = phase.value 53 | k = freq.value 54 | 55 | # Generate the new curve 56 | x = np.linspace(0, 4*np.pi, N) 57 | y = a*np.sin(k*x + w) + b 58 | 59 | source.data = dict(x=x, y=y) 60 | 61 | for w in [offset, amplitude, phase, freq]: 62 | w.on_change('value', update_data) 63 | 64 | 65 | # Set up layouts and add to document 66 | inputs = widgetbox(text, offset, amplitude, phase, freq) 67 | 68 | curdoc().add_root(row(inputs, plot, width=800)) 69 | curdoc().title = "Sliders" -------------------------------------------------------------------------------- /bokehdash/BokehApps/boxplot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | from bokeh.plotting import figure, show, output_file 5 | from bokeh.embed import components 6 | from bokeh.resources import CDN 7 | 8 | # generate some synthetic time series for six different categories 9 | cats = list("abcdef") 10 | yy = np.random.randn(2000) 11 | g = np.random.choice(cats, 2000) 12 | for i, l in enumerate(cats): 13 | yy[g == l] += i // 2 14 | df = pd.DataFrame(dict(score=yy, group=g)) 15 | 16 | # find the quartiles and IQR for each category 17 | groups = df.groupby('group') 18 | q1 = groups.quantile(q=0.25) 19 | q2 = groups.quantile(q=0.5) 20 | q3 = groups.quantile(q=0.75) 21 | iqr = q3 - q1 22 | upper = q3 + 1.5*iqr 23 | lower = q1 - 1.5*iqr 24 | 25 | # find the outliers for each category 26 | def outliers(group): 27 | cat = group.name 28 | return group[(group.score > upper.loc[cat]['score']) | (group.score < lower.loc[cat]['score'])]['score'] 29 | out = groups.apply(outliers).dropna() 30 | 31 | # prepare outlier data for plotting, we need coordinates for every outlier. 32 | if not out.empty: 33 | outx = [] 34 | outy = [] 35 | for cat in cats: 36 | # only add outliers if they exist 37 | if not out.loc[cat].empty: 38 | for value in out[cat]: 39 | outx.append(cat) 40 | outy.append(value) 41 | 42 | p = figure(tools="save", background_fill_color="#EFE8E2", title="", x_range=cats) 43 | 44 | # if no outliers, shrink lengths of stems to be no longer than the minimums or maximums 45 | qmin = groups.quantile(q=0.00) 46 | qmax = groups.quantile(q=1.00) 47 | upper.score = [min([x,y]) for (x,y) in zip(list(qmax.loc[:,'score']),upper.score)] 48 | lower.score = [max([x,y]) for (x,y) in zip(list(qmin.loc[:,'score']),lower.score)] 49 | 50 | # stems 51 | p.segment(cats, upper.score, cats, q3.score, line_color="black") 52 | p.segment(cats, lower.score, cats, q1.score, line_color="black") 53 | 54 | # boxes 55 | p.vbar(cats, 0.7, q2.score, q3.score, fill_color="#E08E79", line_color="black") 56 | p.vbar(cats, 0.7, q1.score, q2.score, fill_color="#3B8686", line_color="black") 57 | 58 | # whiskers (almost-0 height rects simpler than segments) 59 | p.rect(cats, lower.score, 0.2, 0.01, line_color="black") 60 | p.rect(cats, upper.score, 0.2, 0.01, line_color="black") 61 | 62 | # outliers 63 | if not out.empty: 64 | p.circle(outx, outy, size=6, color="#F38630", fill_alpha=0.6) 65 | 66 | p.xgrid.grid_line_color = None 67 | p.ygrid.grid_line_color = "white" 68 | p.grid.grid_line_width = 2 69 | p.xaxis.major_label_text_font_size="12pt" 70 | 71 | # changes to Bokeh Standard Code Below 72 | 73 | #output_file("boxplot.html", title="boxplot.py example") 74 | 75 | #show(p) 76 | 77 | js,div=components(p) 78 | cdn_js=CDN.js_files[0] 79 | cdn_css=CDN.css_files[0] -------------------------------------------------------------------------------- /bokehdash/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render, redirect 3 | 4 | from .BokehApps import boxplot 5 | 6 | from bokeh.embed import server_session 7 | from bokeh.util import session_id 8 | 9 | @login_required 10 | def home(request): 11 | return render( 12 | request, 13 | 'bokehdash/home.html', 14 | ) 15 | 16 | @login_required 17 | def boxplot_view(request): 18 | print(boxplot) 19 | context = {"graphname": "Boxplot", 20 | "div": boxplot.div, 21 | "js": boxplot.js, 22 | "cdn_js": boxplot.cdn_js, 23 | "cdn_css": boxplot.cdn_css, 24 | } 25 | return render(request, 'bokehdash/boxplot.html', context) 26 | 27 | @login_required() 28 | def sliders_view(request): 29 | # Define bokeh endpoint url 30 | bokeh_server_url = "%sbokehproxy/sliders" % (request.build_absolute_uri(location='/')) 31 | 32 | # Generate bokeh session token so user can access plot, this is done for all logged in users per the @login_required decorator 33 | # ensuring only logged in users can view plots 34 | 35 | # Using newer bokeh server_session method vs. deprecated bokeh.embed.autoload_server 36 | # Note: session_id.generate_session_id() relies on the presence of BOKEH_SECRET_KEY defined in settings.py via an OS variable 37 | server_script = server_session(None, session_id=session_id.generate_session_id(), url=bokeh_server_url) 38 | 39 | # Tip: More elaborate permission checks can be made using Django's user system, to generate (or not) bokeh session accesss tokens: 40 | # if user.is_authenticated() and user.has_perm("bokehdash.change_plot"): 41 | # server_session(None, session_id=....) 42 | # else: 43 | # HttpResponseRedirect("You can't see this plot") 44 | # Tip2: More elaborate permissions checks can also be made with other method decorators @user_passes_test, @permission_required 45 | # (besides @login_reqired) 46 | 47 | # Proceed with context and response 48 | context = {"graphname":"Sliders", 49 | "server_script": server_script, 50 | } 51 | return render(request, 'bokehdash/bokeh_server.html', context) 52 | 53 | 54 | @login_required() 55 | def histogram_view(request): 56 | bokeh_server_url = "%sbokehproxy/selection_histogram" % (request.build_absolute_uri(location='/')) 57 | server_script = server_session(None, session_id=session_id.generate_session_id(), url=bokeh_server_url) 58 | context = {"graphname": "Selection Histogram", 59 | "server_script": server_script, 60 | } 61 | return render(request, 'bokehdash/bokeh_server.html', context) 62 | 63 | 64 | def view_404(request): 65 | # make a redirect to homepage 66 | return redirect('/') # or redirect('name-of-index-url') -------------------------------------------------------------------------------- /documentation/config_files/sshd_config: -------------------------------------------------------------------------------- 1 | # Package generated configuration file 2 | # See the sshd_config(5) manpage for details 3 | 4 | # What ports, IPs and protocols we listen for 5 | Port 22 6 | # Use these options to restrict which interfaces/protocols sshd will bind to 7 | #ListenAddress :: 8 | #ListenAddress 0.0.0.0 9 | Protocol 2 10 | # HostKeys for protocol version 2 11 | HostKey /etc/ssh/ssh_host_rsa_key 12 | HostKey /etc/ssh/ssh_host_dsa_key 13 | HostKey /etc/ssh/ssh_host_ecdsa_key 14 | HostKey /etc/ssh/ssh_host_ed25519_key 15 | #Privilege Separation is turned on for security 16 | UsePrivilegeSeparation yes 17 | 18 | # Lifetime and size of ephemeral version 1 server key 19 | KeyRegenerationInterval 3600 20 | ServerKeyBits 1024 21 | 22 | # Logging 23 | SyslogFacility AUTH 24 | LogLevel INFO 25 | 26 | # Authentication: 27 | LoginGraceTime 120 28 | PermitRootLogin no 29 | StrictModes yes 30 | 31 | RSAAuthentication yes 32 | PubkeyAuthentication yes 33 | #AuthorizedKeysFile %h/.ssh/authorized_keys 34 | 35 | # Don't read the user's ~/.rhosts and ~/.shosts files 36 | IgnoreRhosts yes 37 | # For this to work you will also need host keys in /etc/ssh_known_hosts 38 | RhostsRSAAuthentication no 39 | # similar for protocol version 2 40 | HostbasedAuthentication no 41 | # Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication 42 | #IgnoreUserKnownHosts yes 43 | 44 | # To enable empty passwords, change to yes (NOT RECOMMENDED) 45 | PermitEmptyPasswords no 46 | 47 | # Change to yes to enable challenge-response passwords (beware issues with 48 | # some PAM modules and threads) 49 | ChallengeResponseAuthentication no 50 | 51 | # Change to no to disable tunnelled clear text passwords 52 | PasswordAuthentication no 53 | 54 | # Kerberos options 55 | #KerberosAuthentication no 56 | #KerberosGetAFSToken no 57 | #KerberosOrLocalPasswd yes 58 | #KerberosTicketCleanup yes 59 | 60 | # GSSAPI options 61 | #GSSAPIAuthentication no 62 | #GSSAPICleanupCredentials yes 63 | 64 | X11Forwarding yes 65 | X11DisplayOffset 10 66 | PrintMotd no 67 | PrintLastLog yes 68 | TCPKeepAlive yes 69 | #UseLogin no 70 | 71 | #MaxStartups 10:30:60 72 | #Banner /etc/issue.net 73 | 74 | # Allow client to pass locale environment variables 75 | AcceptEnv LANG LC_* 76 | 77 | Subsystem sftp /usr/lib/openssh/sftp-server 78 | 79 | # Set this to 'yes' to enable PAM authentication, account processing, 80 | # and session processing. If this is enabled, PAM authentication will 81 | # be allowed through the ChallengeResponseAuthentication and 82 | # PasswordAuthentication. Depending on your PAM configuration, 83 | # PAM authentication via ChallengeResponseAuthentication may bypass 84 | # the setting of "PermitRootLogin without-password". 85 | # If you just want the PAM account and session checks to run without 86 | # PAM authentication, then enable this but set PasswordAuthentication 87 | # and ChallengeResponseAuthentication to 'no'. 88 | UsePAM no 89 | -------------------------------------------------------------------------------- /static/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(e){var b=a.extend({},a.fn.actions.defaults,e),g=a(this),k=!1,l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},p=function(){n(); 2 | a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){var a;c===g.length?(a=!0,l()):(a=!1,p());return a})};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().click(function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&& 4 | a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){k=!0});a('form#changelist-form button[name="index"]').click(function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); 5 | a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; 6 | a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){var e=a("tr input.action-select");0 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /static/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | var timeParsePatterns = [ 4 | // 9 5 | { 6 | re: /^\d{1,2}$/i, 7 | handler: function(bits) { 8 | if (bits[0].length === 1) { 9 | return '0' + bits[0] + ':00'; 10 | } else { 11 | return bits[0] + ':00'; 12 | } 13 | } 14 | }, 15 | // 13:00 16 | { 17 | re: /^\d{2}[:.]\d{2}$/i, 18 | handler: function(bits) { 19 | return bits[0].replace('.', ':'); 20 | } 21 | }, 22 | // 9:00 23 | { 24 | re: /^\d[:.]\d{2}$/i, 25 | handler: function(bits) { 26 | return '0' + bits[0].replace('.', ':'); 27 | } 28 | }, 29 | // 3 am / 3 a.m. / 3am 30 | { 31 | re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 32 | handler: function(bits) { 33 | var hour = parseInt(bits[1]); 34 | if (hour === 12) { 35 | hour = 0; 36 | } 37 | if (bits[2].toLowerCase() === 'p') { 38 | if (hour === 12) { 39 | hour = 0; 40 | } 41 | return (hour + 12) + ':00'; 42 | } else { 43 | if (hour < 10) { 44 | return '0' + hour + ':00'; 45 | } else { 46 | return hour + ':00'; 47 | } 48 | } 49 | } 50 | }, 51 | // 3.30 am / 3:15 a.m. / 3.00am 52 | { 53 | re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 54 | handler: function(bits) { 55 | var hour = parseInt(bits[1]); 56 | var mins = parseInt(bits[2]); 57 | if (mins < 10) { 58 | mins = '0' + mins; 59 | } 60 | if (hour === 12) { 61 | hour = 0; 62 | } 63 | if (bits[3].toLowerCase() === 'p') { 64 | if (hour === 12) { 65 | hour = 0; 66 | } 67 | return (hour + 12) + ':' + mins; 68 | } else { 69 | if (hour < 10) { 70 | return '0' + hour + ':' + mins; 71 | } else { 72 | return hour + ':' + mins; 73 | } 74 | } 75 | } 76 | }, 77 | // noon 78 | { 79 | re: /^no/i, 80 | handler: function(bits) { 81 | return '12:00'; 82 | } 83 | }, 84 | // midnight 85 | { 86 | re: /^mid/i, 87 | handler: function(bits) { 88 | return '00:00'; 89 | } 90 | } 91 | ]; 92 | 93 | function parseTimeString(s) { 94 | for (var i = 0; i < timeParsePatterns.length; i++) { 95 | var re = timeParsePatterns[i].re; 96 | var handler = timeParsePatterns[i].handler; 97 | var bits = re.exec(s); 98 | if (bits) { 99 | return handler(bits); 100 | } 101 | } 102 | return s; 103 | } 104 | 105 | window.parseTimeString = parseTimeString; 106 | })(); 107 | -------------------------------------------------------------------------------- /bokehdash/BokehApps/selection_histogram.py: -------------------------------------------------------------------------------- 1 | ''' Present a scatter plot with linked histograms on both axes. 2 | Use the ``bokeh serve`` command to run the example by executing: 3 | bokeh serve selection_histogram.py 4 | at your command prompt. Then navigate to the URL 5 | http://localhost:5006/selection_histogram 6 | in your browser. 7 | ''' 8 | 9 | import numpy as np 10 | 11 | from bokeh.layouts import row, column 12 | from bokeh.models import BoxSelectTool, LassoSelectTool, Spacer 13 | from bokeh.plotting import figure, curdoc 14 | 15 | # create three normal population samples with different parameters 16 | x1 = np.random.normal(loc=5.0, size=400) * 100 17 | y1 = np.random.normal(loc=10.0, size=400) * 10 18 | 19 | x2 = np.random.normal(loc=5.0, size=800) * 50 20 | y2 = np.random.normal(loc=5.0, size=800) * 10 21 | 22 | x3 = np.random.normal(loc=55.0, size=200) * 10 23 | y3 = np.random.normal(loc=4.0, size=200) * 10 24 | 25 | x = np.concatenate((x1, x2, x3)) 26 | y = np.concatenate((y1, y2, y3)) 27 | 28 | TOOLS="pan,wheel_zoom,box_select,lasso_select,reset" 29 | 30 | # create the scatter plot 31 | p = figure(tools=TOOLS, plot_width=600, plot_height=600, min_border=10, min_border_left=50, 32 | toolbar_location="above", x_axis_location=None, y_axis_location=None, 33 | title="Linked Histograms") 34 | p.background_fill_color = "#fafafa" 35 | p.select(BoxSelectTool).select_every_mousemove = False 36 | p.select(LassoSelectTool).select_every_mousemove = False 37 | 38 | r = p.scatter(x, y, size=3, color="#3A5785", alpha=0.6) 39 | 40 | # create the horizontal histogram 41 | hhist, hedges = np.histogram(x, bins=20) 42 | hzeros = np.zeros(len(hedges)-1) 43 | hmax = max(hhist)*1.1 44 | 45 | LINE_ARGS = dict(color="#3A5785", line_color=None) 46 | 47 | ph = figure(toolbar_location=None, plot_width=p.plot_width, plot_height=200, x_range=p.x_range, 48 | y_range=(-hmax, hmax), min_border=10, min_border_left=50, y_axis_location="right") 49 | ph.xgrid.grid_line_color = None 50 | ph.yaxis.major_label_orientation = np.pi/4 51 | ph.background_fill_color = "#fafafa" 52 | 53 | ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hhist, color="white", line_color="#3A5785") 54 | hh1 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.5, **LINE_ARGS) 55 | hh2 = ph.quad(bottom=0, left=hedges[:-1], right=hedges[1:], top=hzeros, alpha=0.1, **LINE_ARGS) 56 | 57 | # create the vertical histogram 58 | vhist, vedges = np.histogram(y, bins=20) 59 | vzeros = np.zeros(len(vedges)-1) 60 | vmax = max(vhist)*1.1 61 | 62 | pv = figure(toolbar_location=None, plot_width=200, plot_height=p.plot_height, x_range=(-vmax, vmax), 63 | y_range=p.y_range, min_border=10, y_axis_location="right") 64 | pv.ygrid.grid_line_color = None 65 | pv.xaxis.major_label_orientation = np.pi/4 66 | pv.background_fill_color = "#fafafa" 67 | 68 | pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vhist, color="white", line_color="#3A5785") 69 | vh1 = pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vzeros, alpha=0.5, **LINE_ARGS) 70 | vh2 = pv.quad(left=0, bottom=vedges[:-1], top=vedges[1:], right=vzeros, alpha=0.1, **LINE_ARGS) 71 | 72 | layout = column(row(p, pv), row(ph, Spacer(width=200, height=200))) 73 | 74 | curdoc().add_root(layout) 75 | curdoc().title = "Selection Histogram" 76 | 77 | def update(attr, old, new): 78 | inds = np.array(new['1d']['indices']) 79 | if len(inds) == 0 or len(inds) == len(x): 80 | hhist1, hhist2 = hzeros, hzeros 81 | vhist1, vhist2 = vzeros, vzeros 82 | else: 83 | neg_inds = np.ones_like(x, dtype=np.bool) 84 | neg_inds[inds] = False 85 | hhist1, _ = np.histogram(x[inds], bins=hedges) 86 | vhist1, _ = np.histogram(y[inds], bins=vedges) 87 | hhist2, _ = np.histogram(x[neg_inds], bins=hedges) 88 | vhist2, _ = np.histogram(y[neg_inds], bins=vedges) 89 | 90 | hh1.data_source.data["top"] = hhist1 91 | hh2.data_source.data["top"] = -hhist2 92 | vh1.data_source.data["right"] = vhist1 93 | vh2.data_source.data["right"] = -vhist2 94 | 95 | r.data_source.on_change('selected', update) -------------------------------------------------------------------------------- /mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for BokehDjango project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Note Bokeh_SECRET_KEY must also be used/set when starting up Bokeh daemon 16 | # Obtain your own key by typing "bokeh secret" in a terminal 17 | # the key goes below, and in the bokehserver.service file 18 | os.environ["BOKEH_SECRET_KEY"] = "kitjOI83DgklnTuUykyHYABBCaV8oItJTZTQqVBav97G" 19 | os.environ["BOKEH_SIGN_SESSIONS"] = "True" 20 | 21 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 22 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 23 | 24 | 25 | # Quick-start development settings - unsuitable for production 26 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 27 | 28 | # SECURITY WARNING: keep the secret key used in production secret! 29 | # generate your own secret key by going here: https://www.miniwebtool.com/django-secret-key-generator/ 30 | SECRET_KEY = '_+(!$kcic)jdc4*1+pq&ts7goh8k)udk203_m+@nh**g20$luc' 31 | 32 | # SECURITY WARNING: don't run with debug turned on in production! 33 | DEBUG = False 34 | 35 | ALLOWED_HOSTS = ['45.33.6.39'] 36 | 37 | 38 | # Application definition 39 | 40 | INSTALLED_APPS = [ 41 | 'bokehdash', 42 | 'django.contrib.admin', 43 | 'django.contrib.auth', 44 | 'django.contrib.contenttypes', 45 | 'django.contrib.sessions', 46 | 'django.contrib.messages', 47 | 'django.contrib.staticfiles', 48 | ] 49 | 50 | MIDDLEWARE = [ 51 | 'django.middleware.security.SecurityMiddleware', 52 | 'django.contrib.sessions.middleware.SessionMiddleware', 53 | 'django.middleware.common.CommonMiddleware', 54 | 'django.middleware.csrf.CsrfViewMiddleware', 55 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 56 | 'django.contrib.messages.middleware.MessageMiddleware', 57 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 58 | ] 59 | 60 | ROOT_URLCONF = 'mysite.urls' 61 | 62 | TEMPLATES = [ 63 | { 64 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 65 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 66 | , 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'mysite.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 84 | 85 | DATABASES = { 86 | 'default': { 87 | 'ENGINE': 'django.db.backends.sqlite3', 88 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 89 | } 90 | } 91 | 92 | 93 | # Password validation 94 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 95 | 96 | AUTH_PASSWORD_VALIDATORS = [ 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 108 | }, 109 | ] 110 | 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 114 | 115 | LANGUAGE_CODE = 'en-us' 116 | 117 | TIME_ZONE = 'UTC' 118 | 119 | USE_I18N = True 120 | 121 | USE_L10N = True 122 | 123 | USE_TZ = True 124 | 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 128 | 129 | STATIC_URL = '/static/' 130 | STATIC_ROOT = os.path.join(BASE_DIR, 'static/') 131 | 132 | LOGIN_URL = 'login' 133 | LOGOUT_URL = 'logout' 134 | LOGIN_REDIRECT_URL = 'home' -------------------------------------------------------------------------------- /static/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(c){c.fn.formset=function(b){var a=c.extend({},c.fn.formset.defaults,b),d=c(this);b=d.parent();var k=function(a,g,l){var b=new RegExp("("+g+"-(\\d+|__prefix__))");g=g+"-"+l;c(a).prop("for")&&c(a).prop("for",c(a).prop("for").replace(b,g));a.id&&(a.id=a.id.replace(b,g));a.name&&(a.name=a.name.replace(b,g))},e=c("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),l=parseInt(e.val(),10),g=c("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),h=""===g.val()||0'+a.addText+""),m=b.find("tr:last a")):(d.filter(":last").after('"),m=d.filter(":last").next().find("a")));m.click(function(b){b.preventDefault();b=c("#"+a.prefix+"-empty"); 3 | var f=b.clone(!0);f.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);f.is("tr")?f.children(":last").append('"):f.is("ul")||f.is("ol")?f.append('
  • '+a.deleteText+"
  • "):f.children(":first").append(''+a.deleteText+"");f.find("*").each(function(){k(this,a.prefix,e.val())});f.insertBefore(c(b)); 4 | c(e).val(parseInt(e.val(),10)+1);l+=1;""!==g.val()&&0>=g.val()-e.val()&&m.parent().hide();f.find("a."+a.deleteCssClass).click(function(b){b.preventDefault();f.remove();--l;a.removed&&a.removed(f);c(document).trigger("formset:removed",[f,a.prefix]);b=c("."+a.formCssClass);c("#id_"+a.prefix+"-TOTAL_FORMS").val(b.length);(""===g.val()||0 { 11 | const message = error.message; 12 | if (error.location == null) { 13 | const text = [file || "", message].join(":"); 14 | return { message, text }; 15 | } 16 | else { 17 | const location = error.location; 18 | const line = location.first_line + 1; 19 | const column = location.first_column + 1; 20 | const text = [file || "", line, column, message].join(":"); 21 | let markerLen = 2; 22 | if (location.first_line === location.last_line) 23 | markerLen += location.last_column - location.first_column; 24 | const extract = error.code.split('\n')[line - 1]; 25 | const annotated = [ 26 | text, 27 | " " + extract, 28 | " " + Array(column).join(' ') + Array(markerLen).join('^'), 29 | ].join('\n'); 30 | return { message, line, column, text, extract, annotated }; 31 | } 32 | }; 33 | const mkLessError = (error, file) => { 34 | const message = error.message; 35 | const line = error.line; 36 | const column = error.column + 1; 37 | const text = [file || "", line, column, message].join(":"); 38 | const extract = error.extract[line]; 39 | const annotated = [text, " " + extract].join("\n"); 40 | return { message, line, column, text, extract, annotated }; 41 | }; 42 | const mkTypeScriptError = (diagnostic) => { 43 | let { line, character: column } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); 44 | line += 1; 45 | column += 1; 46 | const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 47 | const text = [diagnostic.file.fileName, line, column, message].join(":"); 48 | return { message, line, column, text }; 49 | }; 50 | const reply = (data) => { 51 | process.stdout.write(JSON.stringify(data)); 52 | process.stdout.write("\n"); 53 | }; 54 | const compile_and_resolve_deps = (input) => { 55 | let code; 56 | switch (input.lang) { 57 | case "coffeescript": 58 | try { 59 | code = coffee.compile(input.code, { bare: true, shiftLine: true }); 60 | } 61 | catch (error) { 62 | return reply({ error: mkCoffeescriptError(error, input.file) }); 63 | } 64 | break; 65 | case "javascript": 66 | case "typescript": 67 | code = input.code; 68 | break; 69 | case "less": 70 | const options = { 71 | paths: [path.dirname(input.file)], 72 | compress: true, 73 | ieCompat: false, 74 | }; 75 | less.render(input.code, options, (error, output) => { 76 | if (error != null) 77 | reply({ error: mkLessError(error, input.file) }); 78 | else 79 | reply({ code: output.css }); 80 | }); 81 | return; 82 | default: 83 | throw new Error(`unsupported input type: ${input.lang}`); 84 | } 85 | const result = ts.transpileModule(code, { 86 | fileName: input.file, 87 | reportDiagnostics: true, 88 | compilerOptions: { 89 | noEmitOnError: false, 90 | noImplicitAny: false, 91 | target: ts.ScriptTarget.ES5, 92 | module: ts.ModuleKind.CommonJS, 93 | jsx: ts.JsxEmit.React, 94 | reactNamespace: "DOM", 95 | }, 96 | }); 97 | if (result.diagnostics != null && result.diagnostics.length > 0) { 98 | const diagnostic = result.diagnostics[0]; 99 | return reply({ error: mkTypeScriptError(diagnostic) }); 100 | } 101 | const source = result.outputText; 102 | try { 103 | const deps = detective(source); 104 | return reply({ code: source, deps: deps }); 105 | } 106 | catch (error) { 107 | return reply({ error: error }); 108 | } 109 | }; 110 | if (yargs_1.argv.file != null) { 111 | const input = { 112 | code: fs.readFileSync(yargs_1.argv.file, "utf-8"), 113 | lang: yargs_1.argv.lang || "coffeescript", 114 | file: yargs_1.argv.file, 115 | }; 116 | compile_and_resolve_deps(input); 117 | } 118 | else { 119 | const stdin = process.stdin; 120 | stdin.resume(); 121 | stdin.setEncoding("utf-8"); 122 | let data = ""; 123 | stdin.on("data", (chunk) => data += chunk); 124 | stdin.on("end", () => compile_and_resolve_deps(JSON.parse(data))); 125 | } 126 | -------------------------------------------------------------------------------- /static/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .module ul, .module ol { 34 | margin-left: 0; 35 | margin-right: 1.5em; 36 | } 37 | 38 | .addlink, .changelink { 39 | padding-left: 0; 40 | padding-right: 16px; 41 | background-position: 100% 1px; 42 | } 43 | 44 | .deletelink { 45 | padding-left: 0; 46 | padding-right: 16px; 47 | background-position: 100% 1px; 48 | } 49 | 50 | .object-tools { 51 | float: left; 52 | } 53 | 54 | thead th:first-child, 55 | tfoot td:first-child { 56 | border-left: none; 57 | } 58 | 59 | /* LAYOUT */ 60 | 61 | #user-tools { 62 | right: auto; 63 | left: 0; 64 | text-align: left; 65 | } 66 | 67 | div.breadcrumbs { 68 | text-align: right; 69 | } 70 | 71 | #content-main { 72 | float: right; 73 | } 74 | 75 | #content-related { 76 | float: left; 77 | margin-left: -300px; 78 | margin-right: auto; 79 | } 80 | 81 | .colMS { 82 | margin-left: 300px; 83 | margin-right: 0; 84 | } 85 | 86 | /* SORTABLE TABLES */ 87 | 88 | table thead th.sorted .sortoptions { 89 | float: left; 90 | } 91 | 92 | thead th.sorted .text { 93 | padding-right: 0; 94 | padding-left: 42px; 95 | } 96 | 97 | /* dashboard styles */ 98 | 99 | .dashboard .module table td a { 100 | padding-left: .6em; 101 | padding-right: 16px; 102 | } 103 | 104 | /* changelists styles */ 105 | 106 | .change-list .filtered table { 107 | border-left: none; 108 | border-right: 0px none; 109 | } 110 | 111 | #changelist-filter { 112 | right: auto; 113 | left: 0; 114 | border-left: none; 115 | border-right: none; 116 | } 117 | 118 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 119 | margin-right: 0; 120 | margin-left: 280px; 121 | } 122 | 123 | #changelist-filter li.selected { 124 | border-left: none; 125 | padding-left: 10px; 126 | margin-left: 0; 127 | border-right: 5px solid #eaeaea; 128 | padding-right: 10px; 129 | margin-right: -15px; 130 | } 131 | 132 | .filtered .actions { 133 | margin-left: 280px; 134 | margin-right: 0; 135 | } 136 | 137 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 138 | border-right: none; 139 | border-left: none; 140 | } 141 | 142 | /* FORMS */ 143 | 144 | .aligned label { 145 | padding: 0 0 3px 1em; 146 | float: right; 147 | } 148 | 149 | .submit-row { 150 | text-align: left 151 | } 152 | 153 | .submit-row p.deletelink-box { 154 | float: right; 155 | } 156 | 157 | .submit-row input.default { 158 | margin-left: 0; 159 | } 160 | 161 | .vDateField, .vTimeField { 162 | margin-left: 2px; 163 | } 164 | 165 | .aligned .form-row input { 166 | margin-left: 5px; 167 | } 168 | 169 | form .aligned p.help, form .aligned div.help { 170 | clear: right; 171 | } 172 | 173 | form ul.inline li { 174 | float: right; 175 | padding-right: 0; 176 | padding-left: 7px; 177 | } 178 | 179 | input[type=submit].default, .submit-row input.default { 180 | float: left; 181 | } 182 | 183 | fieldset .field-box { 184 | float: right; 185 | margin-left: 20px; 186 | margin-right: 0; 187 | } 188 | 189 | .errorlist li { 190 | background-position: 100% 12px; 191 | padding: 0; 192 | } 193 | 194 | .errornote { 195 | background-position: 100% 12px; 196 | padding: 10px 12px; 197 | } 198 | 199 | /* WIDGETS */ 200 | 201 | .calendarnav-previous { 202 | top: 0; 203 | left: auto; 204 | right: 10px; 205 | } 206 | 207 | .calendarnav-next { 208 | top: 0; 209 | right: auto; 210 | left: 10px; 211 | } 212 | 213 | .calendar caption, .calendarbox h2 { 214 | text-align: center; 215 | } 216 | 217 | .selector { 218 | float: right; 219 | } 220 | 221 | .selector .selector-filter { 222 | text-align: right; 223 | } 224 | 225 | .inline-deletelink { 226 | float: left; 227 | } 228 | 229 | form .form-row p.datetime { 230 | overflow: hidden; 231 | } 232 | 233 | .related-widget-wrapper { 234 | float: right; 235 | } 236 | 237 | /* MISC */ 238 | 239 | .inline-related h2, .inline-group h2 { 240 | text-align: right 241 | } 242 | 243 | .inline-related h3 span.delete { 244 | padding-right: 20px; 245 | padding-left: inherit; 246 | left: 10px; 247 | right: inherit; 248 | float:left; 249 | } 250 | 251 | .inline-related h3 span.delete label { 252 | margin-left: inherit; 253 | margin-right: 2px; 254 | } 255 | 256 | /* IE7 specific bug fixes */ 257 | 258 | div.colM { 259 | position: relative; 260 | } 261 | 262 | .submit-row input { 263 | float: left; 264 | } 265 | -------------------------------------------------------------------------------- /static/js/bokeh.json: -------------------------------------------------------------------------------- 1 | ["base","client/connection","client/session","core/bokeh_events","core/build_views","core/dom","core/dom_view","core/enums","core/has_props","core/hittest","core/layout/layout_canvas","core/layout/side_panel","core/layout/solver","core/logging","core/properties","core/property_mixins","core/selection_manager","core/selector","core/settings","core/signaling","core/ui_events","core/util/array","core/util/bbox","core/util/callback","core/util/canvas","core/util/color","core/util/data_structures","core/util/eq","core/util/math","core/util/object","core/util/proj4","core/util/projections","core/util/refs","core/util/selection","core/util/serialization","core/util/spatial","core/util/string","core/util/svg_colors","core/util/templating","core/util/text","core/util/throttle","core/util/types","core/util/wheel","core/util/zoom","core/view","core/visuals","document","embed","main","model","models/annotations/annotation","models/annotations/arrow","models/annotations/arrow_head","models/annotations/band","models/annotations/box_annotation","models/annotations/color_bar","models/annotations/index","models/annotations/label","models/annotations/label_set","models/annotations/legend","models/annotations/legend_item","models/annotations/poly_annotation","models/annotations/span","models/annotations/text_annotation","models/annotations/title","models/annotations/tooltip","models/annotations/whisker","models/axes/axis","models/axes/categorical_axis","models/axes/continuous_axis","models/axes/datetime_axis","models/axes/index","models/axes/linear_axis","models/axes/log_axis","models/callbacks/customjs","models/callbacks/index","models/callbacks/open_url","models/canvas/canvas","models/canvas/cartesian_frame","models/canvas/index","models/expressions/expression","models/expressions/index","models/expressions/stack","models/filters/boolean_filter","models/filters/customjs_filter","models/filters/filter","models/filters/group_filter","models/filters/index","models/filters/index_filter","models/formatters/basic_tick_formatter","models/formatters/categorical_tick_formatter","models/formatters/datetime_tick_formatter","models/formatters/func_tick_formatter","models/formatters/index","models/formatters/log_tick_formatter","models/formatters/mercator_tick_formatter","models/formatters/numeral_tick_formatter","models/formatters/printf_tick_formatter","models/formatters/tick_formatter","models/glyphs/annular_wedge","models/glyphs/annulus","models/glyphs/arc","models/glyphs/bezier","models/glyphs/box","models/glyphs/circle","models/glyphs/ellipse","models/glyphs/glyph","models/glyphs/hbar","models/glyphs/image","models/glyphs/image_rgba","models/glyphs/image_url","models/glyphs/index","models/glyphs/line","models/glyphs/multi_line","models/glyphs/oval","models/glyphs/patch","models/glyphs/patches","models/glyphs/quad","models/glyphs/quadratic","models/glyphs/ray","models/glyphs/rect","models/glyphs/segment","models/glyphs/text","models/glyphs/vbar","models/glyphs/wedge","models/glyphs/xy_glyph","models/graphs/graph_hit_test_policy","models/graphs/index","models/graphs/layout_provider","models/graphs/static_layout_provider","models/grids/grid","models/grids/index","models/index","models/layouts/box","models/layouts/column","models/layouts/index","models/layouts/layout_dom","models/layouts/row","models/layouts/spacer","models/layouts/widget_box","models/mappers/categorical_color_mapper","models/mappers/color_mapper","models/mappers/index","models/mappers/linear_color_mapper","models/mappers/log_color_mapper","models/markers/index","models/markers/marker","models/plots/gmap_plot","models/plots/gmap_plot_canvas","models/plots/index","models/plots/plot","models/plots/plot_canvas","models/ranges/data_range","models/ranges/data_range1d","models/ranges/factor_range","models/ranges/index","models/ranges/range","models/ranges/range1d","models/renderers/glyph_renderer","models/renderers/graph_renderer","models/renderers/guide_renderer","models/renderers/index","models/renderers/renderer","models/scales/categorical_scale","models/scales/index","models/scales/linear_scale","models/scales/log_scale","models/scales/scale","models/sources/ajax_data_source","models/sources/cds_view","models/sources/column_data_source","models/sources/columnar_data_source","models/sources/data_source","models/sources/geojson_data_source","models/sources/index","models/sources/remote_data_source","models/tickers/adaptive_ticker","models/tickers/basic_ticker","models/tickers/categorical_ticker","models/tickers/composite_ticker","models/tickers/continuous_ticker","models/tickers/datetime_ticker","models/tickers/days_ticker","models/tickers/fixed_ticker","models/tickers/index","models/tickers/log_ticker","models/tickers/mercator_ticker","models/tickers/months_ticker","models/tickers/single_interval_ticker","models/tickers/ticker","models/tickers/util","models/tickers/years_ticker","models/tiles/bbox_tile_source","models/tiles/dynamic_image_renderer","models/tiles/image_pool","models/tiles/image_source","models/tiles/index","models/tiles/mercator_tile_source","models/tiles/quadkey_tile_source","models/tiles/tile_renderer","models/tiles/tile_source","models/tiles/tile_utils","models/tiles/tms_tile_source","models/tiles/wmts_tile_source","models/tools/actions/action_tool","models/tools/actions/help_tool","models/tools/actions/redo_tool","models/tools/actions/reset_tool","models/tools/actions/save_tool","models/tools/actions/undo_tool","models/tools/actions/zoom_in_tool","models/tools/actions/zoom_out_tool","models/tools/button_tool","models/tools/gestures/box_select_tool","models/tools/gestures/box_zoom_tool","models/tools/gestures/gesture_tool","models/tools/gestures/lasso_select_tool","models/tools/gestures/pan_tool","models/tools/gestures/poly_select_tool","models/tools/gestures/select_tool","models/tools/gestures/tap_tool","models/tools/gestures/wheel_pan_tool","models/tools/gestures/wheel_zoom_tool","models/tools/index","models/tools/inspectors/crosshair_tool","models/tools/inspectors/hover_tool","models/tools/inspectors/inspect_tool","models/tools/on_off_button","models/tools/tool","models/tools/tool_proxy","models/tools/toolbar","models/tools/toolbar_base","models/tools/toolbar_box","models/transforms/customjs_transform","models/transforms/dodge","models/transforms/index","models/transforms/interpolator","models/transforms/jitter","models/transforms/linear_interpolator","models/transforms/step_interpolator","models/transforms/transform","polyfill","protocol/message","protocol/receiver","safely","version"] -------------------------------------------------------------------------------- /static/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | var SelectBox = { 4 | cache: {}, 5 | init: function(id) { 6 | var box = document.getElementById(id); 7 | var node; 8 | SelectBox.cache[id] = []; 9 | var cache = SelectBox.cache[id]; 10 | var boxOptions = box.options; 11 | var boxOptionsLength = boxOptions.length; 12 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 13 | node = boxOptions[i]; 14 | cache.push({value: node.value, text: node.text, displayed: 1}); 15 | } 16 | }, 17 | redisplay: function(id) { 18 | // Repopulate HTML select box from cache 19 | var box = document.getElementById(id); 20 | var node; 21 | $(box).empty(); // clear all options 22 | var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag 23 | var cache = SelectBox.cache[id]; 24 | for (var i = 0, j = cache.length; i < j; i++) { 25 | node = cache[i]; 26 | if (node.displayed) { 27 | var new_option = new Option(node.text, node.value, false, false); 28 | // Shows a tooltip when hovering over the option 29 | new_option.setAttribute("title", node.text); 30 | new_options += new_option.outerHTML; 31 | } 32 | } 33 | new_options += ''; 34 | box.outerHTML = new_options; 35 | }, 36 | filter: function(id, text) { 37 | // Redisplay the HTML select box, displaying only the choices containing ALL 38 | // the words in text. (It's an AND search.) 39 | var tokens = text.toLowerCase().split(/\s+/); 40 | var node, token; 41 | var cache = SelectBox.cache[id]; 42 | for (var i = 0, j = cache.length; i < j; i++) { 43 | node = cache[i]; 44 | node.displayed = 1; 45 | var node_text = node.text.toLowerCase(); 46 | var numTokens = tokens.length; 47 | for (var k = 0; k < numTokens; k++) { 48 | token = tokens[k]; 49 | if (node_text.indexOf(token) === -1) { 50 | node.displayed = 0; 51 | break; // Once the first token isn't found we're done 52 | } 53 | } 54 | } 55 | SelectBox.redisplay(id); 56 | }, 57 | delete_from_cache: function(id, value) { 58 | var node, delete_index = null; 59 | var cache = SelectBox.cache[id]; 60 | for (var i = 0, j = cache.length; i < j; i++) { 61 | node = cache[i]; 62 | if (node.value === value) { 63 | delete_index = i; 64 | break; 65 | } 66 | } 67 | cache.splice(delete_index, 1); 68 | }, 69 | add_to_cache: function(id, option) { 70 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 71 | }, 72 | cache_contains: function(id, value) { 73 | // Check if an item is contained in the cache 74 | var node; 75 | var cache = SelectBox.cache[id]; 76 | for (var i = 0, j = cache.length; i < j; i++) { 77 | node = cache[i]; 78 | if (node.value === value) { 79 | return true; 80 | } 81 | } 82 | return false; 83 | }, 84 | move: function(from, to) { 85 | var from_box = document.getElementById(from); 86 | var option; 87 | var boxOptions = from_box.options; 88 | var boxOptionsLength = boxOptions.length; 89 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 90 | option = boxOptions[i]; 91 | var option_value = option.value; 92 | if (option.selected && SelectBox.cache_contains(from, option_value)) { 93 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 94 | SelectBox.delete_from_cache(from, option_value); 95 | } 96 | } 97 | SelectBox.redisplay(from); 98 | SelectBox.redisplay(to); 99 | }, 100 | move_all: function(from, to) { 101 | var from_box = document.getElementById(from); 102 | var option; 103 | var boxOptions = from_box.options; 104 | var boxOptionsLength = boxOptions.length; 105 | for (var i = 0, j = boxOptionsLength; i < j; i++) { 106 | option = boxOptions[i]; 107 | var option_value = option.value; 108 | if (SelectBox.cache_contains(from, option_value)) { 109 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 110 | SelectBox.delete_from_cache(from, option_value); 111 | } 112 | } 113 | SelectBox.redisplay(from); 114 | SelectBox.redisplay(to); 115 | }, 116 | sort: function(id) { 117 | SelectBox.cache[id].sort(function(a, b) { 118 | a = a.text.toLowerCase(); 119 | b = b.text.toLowerCase(); 120 | try { 121 | if (a > b) { 122 | return 1; 123 | } 124 | if (a < b) { 125 | return -1; 126 | } 127 | } 128 | catch (e) { 129 | // silently fail on IE 'unknown' exception 130 | } 131 | return 0; 132 | } ); 133 | }, 134 | select_all: function(id) { 135 | var box = document.getElementById(id); 136 | var boxOptions = box.options; 137 | var boxOptionsLength = boxOptions.length; 138 | for (var i = 0; i < boxOptionsLength; i++) { 139 | boxOptions[i].selected = 'selected'; 140 | } 141 | } 142 | }; 143 | window.SelectBox = SelectBox; 144 | })(django.jQuery); 145 | -------------------------------------------------------------------------------- /documentation/Linode_Instructions.txt: -------------------------------------------------------------------------------- 1 | ######################################## 2 | # Secure the Server 3 | ######################################## 4 | 5 | # If you decide to host this via Linode and don't already have a Linode account, 6 | # please consider using the following referral link to register. This gives me a modest amount of Linode "credit" 7 | # that will allow me to keep the demo site up and running at http://45.33.6.39 8 | 9 | # https://www.linode.com/?r=8b4f4ca1bfe379f086fbacc8dde3a5c138296e48 10 | # referral code: 8b4f4ca1bfe379f086fbacc8dde3a5c138296e48 11 | 12 | # Rebuild as Ubuntu 16.04 LTS 13 | # Boot 14 | # Log in as root 15 | 16 | # update the machine 17 | apt-get update 18 | apt-get upgrade 19 | apt-get install fail2ban 20 | vi /etc/ssh/sshd_config 21 | PermitRootLogin no 22 | PasswordAuthentication no 23 | UsePAM no 24 | apt-get install git-core 25 | apt-get install virtualenv 26 | apt-get install gunicorn 27 | apt-get install nginx 28 | 29 | ######################################## 30 | # Setting up the user 31 | ########################################/ 32 | 33 | # create the new deployers group 34 | /usr/sbin/groupadd deployers 35 | # back up the sudoers file 36 | cp /etc/sudoers /etc/sudoers-backup 37 | # modify the sudo list so the deployers group has sudo privileges 38 | (cat /etc/sudoers-backup ; echo "%deployers ALL=(ALL) ALL") > /etc/sudoers 39 | # ensure the appropriate permissions are on the sudoers file 40 | chmod 0440 /etc/sudoers 41 | 42 | # create the new user. be sure to use your own name here 43 | /usr/sbin/useradd -c "Jonathan Bennett" -m -g deployers deployer1 44 | # set up a password for the new user. you'll need the password to run sudo commands 45 | /usr/bin/passwd deployer1 46 | 47 | # add the deployer user to the deployers group 48 | /usr/sbin/usermod -a -G deployers deployer1 49 | # create a directory for the deployer's public key and authorized_keys file 50 | mkdir /home/deployer1/.ssh 51 | 52 | # create a public/private key pair on your local computer 53 | # create the authorized_keys file (the public key) on your webserver 54 | vi /home/deployer1/.ssh/authorized_keys 55 | 56 | # change the owner and group of the .ssh directory to deployer 57 | # and deployers, respectively 58 | chown -R deployer1 /home/deployer1/.ssh 59 | chgrp -R deployers /home/deployer1/.ssh 60 | 61 | # restart SSH Service 62 | service ssh reload 63 | 64 | # before logging out as root, check to make sure you can log in as deployer 65 | # if it works log out of the root SSH session 66 | 67 | # from now on, log in as deployer1 68 | 69 | # create a deploy key 70 | mkdir ~/deploy_key 71 | cd ~/deploy_key 72 | ssh-keygen -t rsa -b 2048 73 | 74 | # save the private key in our current directory ./deploy_key 75 | # Press enter twice when prompted for a passphrase. We will not use a passphrase on the deploy key. 76 | 77 | # add the deploy_key.pub to github 78 | 79 | 80 | ######################################## 81 | # Source Control 82 | ######################################## 83 | 84 | # make sure the deploy key is set up on your github repository 85 | cd /home/deployer1 86 | # the next line must point to your personal repository, so replace jonolsu with your github ID 87 | ssh-agent bash -c 'ssh-add /home/deployer1/deploy_key/deploy_key; git clone git@github.com:jonolsu/BokehDjango.git' 88 | 89 | 90 | ######################################## 91 | # Create the Environment 92 | ######################################## 93 | 94 | mkdir /home/deployer1/envs/ 95 | # determine which version of python3 is installed and name the environment appropriately 96 | python3 --version 97 | virtualenv -p python3 /home/deployer1/envs/BokehDjango352 98 | /bin/bash 99 | source /home/deployer1/envs/BokehDjango352/bin/activate 100 | pip install -r /home/deployer1/BokehDjango/documentation/config_files/requirements.txt 101 | 102 | 103 | ######################################## 104 | # Setting up the Web Server 105 | # https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04 106 | ######################################## 107 | 108 | # set up the firewall 109 | sudo ufw allow 22 110 | sudo ufw allow 80 111 | sudo ufw enable 112 | 113 | 114 | # create gunicorn.service file (gunicorn.service is located in the config_files directory of the repository) 115 | sudo vi /etc/systemd/system/gunicorn.service 116 | 117 | # start the Gunicorn service we created and enable it so that it starts at boot: 118 | sudo systemctl start gunicorn 119 | sudo systemctl enable gunicorn 120 | 121 | # Configure Nginx to Proxy Pass to Gunicorn (mysite is located in the config_files directory of the repository) 122 | # replace the up address with your server's ip address 123 | sudo vi /etc/nginx/sites-available/mysite 124 | 125 | # enable the file 126 | sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled 127 | 128 | # 129 | sudo ufw allow 'Nginx Full' 130 | 131 | # restart Nginx 132 | sudo systemctl restart nginx 133 | 134 | 135 | ######################################## 136 | # Configuring the Bokeh Server 137 | ######################################## 138 | 139 | # create bokehserver.service file (bokehserver.service is located in the config_files directory of the repository) 140 | sudo vi /etc/systemd/system/bokehserver.service 141 | 142 | # start the bokehserver service we created and enable it so that it starts at boot: 143 | sudo systemctl start bokehserver 144 | sudo systemctl enable bokehserver 145 | 146 | ######################################## 147 | # Ready to Go 148 | ######################################## 149 | 150 | # reboot your system and navigate to your website 151 | sudo shutdown -r now 152 | # use the browser to navigate to your website http://45.33.6.39 153 | # userid is regularuser 154 | # password is 1234password1234 155 | # administrator id is admin 156 | # password is 1234password1234 157 | 158 | ######################################## 159 | # Other 160 | ######################################## 161 | 162 | # if you update your Django Application 163 | cd /home/deployer1/BokehDjango 164 | ssh-agent bash -c 'ssh-add /home/deployer1/deploy_key/deploy_key; git pull origin master' 165 | sudo systemctl restart gunicorn 166 | 167 | # if you change gunicorn service 168 | sudo systemctl daemon-reload 169 | sudo systemctl restart gunicorn 170 | 171 | # if you change bokehserver service 172 | sudo systemctl daemon-reload 173 | sudo systemctl restart bokehserver 174 | 175 | # if you change nginx 176 | sudo nginx -t && sudo systemctl restart nginx 177 | -------------------------------------------------------------------------------- /static/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | /*global gettext, interpolate, ngettext*/ 2 | (function($) { 3 | 'use strict'; 4 | var lastChecked; 5 | 6 | $.fn.actions = function(opts) { 7 | var options = $.extend({}, $.fn.actions.defaults, opts); 8 | var actionCheckboxes = $(this); 9 | var list_editable_changed = false; 10 | var showQuestion = function() { 11 | $(options.acrossClears).hide(); 12 | $(options.acrossQuestions).show(); 13 | $(options.allContainer).hide(); 14 | }, 15 | showClear = function() { 16 | $(options.acrossClears).show(); 17 | $(options.acrossQuestions).hide(); 18 | $(options.actionContainer).toggleClass(options.selectedClass); 19 | $(options.allContainer).show(); 20 | $(options.counterContainer).hide(); 21 | }, 22 | reset = function() { 23 | $(options.acrossClears).hide(); 24 | $(options.acrossQuestions).hide(); 25 | $(options.allContainer).hide(); 26 | $(options.counterContainer).show(); 27 | }, 28 | clearAcross = function() { 29 | reset(); 30 | $(options.acrossInput).val(0); 31 | $(options.actionContainer).removeClass(options.selectedClass); 32 | }, 33 | checker = function(checked) { 34 | if (checked) { 35 | showQuestion(); 36 | } else { 37 | reset(); 38 | } 39 | $(actionCheckboxes).prop("checked", checked) 40 | .parent().parent().toggleClass(options.selectedClass, checked); 41 | }, 42 | updateCounter = function() { 43 | var sel = $(actionCheckboxes).filter(":checked").length; 44 | // data-actions-icnt is defined in the generated HTML 45 | // and contains the total amount of objects in the queryset 46 | var actions_icnt = $('.action-counter').data('actionsIcnt'); 47 | $(options.counterContainer).html(interpolate( 48 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 49 | sel: sel, 50 | cnt: actions_icnt 51 | }, true)); 52 | $(options.allToggle).prop("checked", function() { 53 | var value; 54 | if (sel === actionCheckboxes.length) { 55 | value = true; 56 | showQuestion(); 57 | } else { 58 | value = false; 59 | clearAcross(); 60 | } 61 | return value; 62 | }); 63 | }; 64 | // Show counter by default 65 | $(options.counterContainer).show(); 66 | // Check state of checkboxes and reinit state if needed 67 | $(this).filter(":checked").each(function(i) { 68 | $(this).parent().parent().toggleClass(options.selectedClass); 69 | updateCounter(); 70 | if ($(options.acrossInput).val() === 1) { 71 | showClear(); 72 | } 73 | }); 74 | $(options.allToggle).show().click(function() { 75 | checker($(this).prop("checked")); 76 | updateCounter(); 77 | }); 78 | $("a", options.acrossQuestions).click(function(event) { 79 | event.preventDefault(); 80 | $(options.acrossInput).val(1); 81 | showClear(); 82 | }); 83 | $("a", options.acrossClears).click(function(event) { 84 | event.preventDefault(); 85 | $(options.allToggle).prop("checked", false); 86 | clearAcross(); 87 | checker(0); 88 | updateCounter(); 89 | }); 90 | lastChecked = null; 91 | $(actionCheckboxes).click(function(event) { 92 | if (!event) { event = window.event; } 93 | var target = event.target ? event.target : event.srcElement; 94 | if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) { 95 | var inrange = false; 96 | $(lastChecked).prop("checked", target.checked) 97 | .parent().parent().toggleClass(options.selectedClass, target.checked); 98 | $(actionCheckboxes).each(function() { 99 | if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) { 100 | inrange = (inrange) ? false : true; 101 | } 102 | if (inrange) { 103 | $(this).prop("checked", target.checked) 104 | .parent().parent().toggleClass(options.selectedClass, target.checked); 105 | } 106 | }); 107 | } 108 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 109 | lastChecked = target; 110 | updateCounter(); 111 | }); 112 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { 113 | list_editable_changed = true; 114 | }); 115 | $('form#changelist-form button[name="index"]').click(function(event) { 116 | if (list_editable_changed) { 117 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 118 | } 119 | }); 120 | $('form#changelist-form input[name="_save"]').click(function(event) { 121 | var action_changed = false; 122 | $('select option:selected', options.actionContainer).each(function() { 123 | if ($(this).val()) { 124 | action_changed = true; 125 | } 126 | }); 127 | if (action_changed) { 128 | if (list_editable_changed) { 129 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 130 | } else { 131 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 132 | } 133 | } 134 | }); 135 | }; 136 | /* Setup plugin defaults */ 137 | $.fn.actions.defaults = { 138 | actionContainer: "div.actions", 139 | counterContainer: "span.action-counter", 140 | allContainer: "div.actions span.all", 141 | acrossInput: "div.actions input.select-across", 142 | acrossQuestions: "div.actions span.question", 143 | acrossClears: "div.actions span.clear", 144 | allToggle: "#action-toggle", 145 | selectedClass: "selected" 146 | }; 147 | $(document).ready(function() { 148 | var $actionsEls = $('tr input.action-select'); 149 | if ($actionsEls.length > 0) { 150 | $actionsEls.actions(); 151 | } 152 | }); 153 | })(django.jQuery); 154 | -------------------------------------------------------------------------------- /static/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | /*global SelectBox, interpolate*/ 2 | // Handles related-objects functionality: lookup link for raw_id_fields 3 | // and Add Another links. 4 | 5 | (function($) { 6 | 'use strict'; 7 | 8 | // IE doesn't accept periods or dashes in the window name, but the element IDs 9 | // we use to generate popup window names may contain them, therefore we map them 10 | // to allowed characters in a reversible way so that we can locate the correct 11 | // element when the popup window is dismissed. 12 | function id_to_windowname(text) { 13 | text = text.replace(/\./g, '__dot__'); 14 | text = text.replace(/\-/g, '__dash__'); 15 | return text; 16 | } 17 | 18 | function windowname_to_id(text) { 19 | text = text.replace(/__dot__/g, '.'); 20 | text = text.replace(/__dash__/g, '-'); 21 | return text; 22 | } 23 | 24 | function showAdminPopup(triggeringLink, name_regexp, add_popup) { 25 | var name = triggeringLink.id.replace(name_regexp, ''); 26 | name = id_to_windowname(name); 27 | var href = triggeringLink.href; 28 | if (add_popup) { 29 | if (href.indexOf('?') === -1) { 30 | href += '?_popup=1'; 31 | } else { 32 | href += '&_popup=1'; 33 | } 34 | } 35 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 36 | win.focus(); 37 | return false; 38 | } 39 | 40 | function showRelatedObjectLookupPopup(triggeringLink) { 41 | return showAdminPopup(triggeringLink, /^lookup_/, true); 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') !== -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showRelatedObjectPopup(triggeringLink) { 56 | return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false); 57 | } 58 | 59 | function updateRelatedObjectLinks(triggeringLink) { 60 | var $this = $(triggeringLink); 61 | var siblings = $this.nextAll('.change-related, .delete-related'); 62 | if (!siblings.length) { 63 | return; 64 | } 65 | var value = $this.val(); 66 | if (value) { 67 | siblings.each(function() { 68 | var elm = $(this); 69 | elm.attr('href', elm.attr('data-href-template').replace('__fk__', value)); 70 | }); 71 | } else { 72 | siblings.removeAttr('href'); 73 | } 74 | } 75 | 76 | function dismissAddRelatedObjectPopup(win, newId, newRepr) { 77 | var name = windowname_to_id(win.name); 78 | var elem = document.getElementById(name); 79 | if (elem) { 80 | var elemName = elem.nodeName.toUpperCase(); 81 | if (elemName === 'SELECT') { 82 | elem.options[elem.options.length] = new Option(newRepr, newId, true, true); 83 | } else if (elemName === 'INPUT') { 84 | if (elem.className.indexOf('vManyToManyRawIdAdminField') !== -1 && elem.value) { 85 | elem.value += ',' + newId; 86 | } else { 87 | elem.value = newId; 88 | } 89 | } 90 | // Trigger a change event to update related links if required. 91 | $(elem).trigger('change'); 92 | } else { 93 | var toId = name + "_to"; 94 | var o = new Option(newRepr, newId); 95 | SelectBox.add_to_cache(toId, o); 96 | SelectBox.redisplay(toId); 97 | } 98 | win.close(); 99 | } 100 | 101 | function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) { 102 | var id = windowname_to_id(win.name).replace(/^edit_/, ''); 103 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); 104 | var selects = $(selectsSelector); 105 | selects.find('option').each(function() { 106 | if (this.value === objId) { 107 | this.textContent = newRepr; 108 | this.value = newId; 109 | } 110 | }); 111 | win.close(); 112 | } 113 | 114 | function dismissDeleteRelatedObjectPopup(win, objId) { 115 | var id = windowname_to_id(win.name).replace(/^delete_/, ''); 116 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); 117 | var selects = $(selectsSelector); 118 | selects.find('option').each(function() { 119 | if (this.value === objId) { 120 | $(this).remove(); 121 | } 122 | }).trigger('change'); 123 | win.close(); 124 | } 125 | 126 | // Global for testing purposes 127 | window.id_to_windowname = id_to_windowname; 128 | window.windowname_to_id = windowname_to_id; 129 | 130 | window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup; 131 | window.dismissRelatedLookupPopup = dismissRelatedLookupPopup; 132 | window.showRelatedObjectPopup = showRelatedObjectPopup; 133 | window.updateRelatedObjectLinks = updateRelatedObjectLinks; 134 | window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup; 135 | window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup; 136 | window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup; 137 | 138 | // Kept for backward compatibility 139 | window.showAddAnotherPopup = showRelatedObjectPopup; 140 | window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup; 141 | 142 | $(document).ready(function() { 143 | $("a[data-popup-opener]").click(function(event) { 144 | event.preventDefault(); 145 | opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener")); 146 | }); 147 | $('body').on('click', '.related-widget-wrapper-link', function(e) { 148 | e.preventDefault(); 149 | if (this.href) { 150 | var event = $.Event('django:show-related', {href: this.href}); 151 | $(this).trigger(event); 152 | if (!event.isDefaultPrevented()) { 153 | showRelatedObjectPopup(this); 154 | } 155 | } 156 | }); 157 | $('body').on('change', '.related-widget-wrapper select', function(e) { 158 | var event = $.Event('django:update-related'); 159 | $(this).trigger(event); 160 | if (!event.isDefaultPrevented()) { 161 | updateRelatedObjectLinks(this); 162 | } 163 | }); 164 | $('.related-widget-wrapper select').trigger('change'); 165 | $('body').on('click', '.related-lookup', function(e) { 166 | e.preventDefault(); 167 | var event = $.Event('django:lookup-related'); 168 | $(this).trigger(event); 169 | if (!event.isDefaultPrevented()) { 170 | showRelatedObjectLookupPopup(this); 171 | } 172 | }); 173 | }); 174 | 175 | })(django.jQuery); 176 | -------------------------------------------------------------------------------- /static/admin/css/changelists.css: -------------------------------------------------------------------------------- 1 | /* CHANGELISTS */ 2 | 3 | #changelist { 4 | position: relative; 5 | width: 100%; 6 | } 7 | 8 | #changelist table { 9 | width: 100%; 10 | } 11 | 12 | .change-list .hiddenfields { display:none; } 13 | 14 | .change-list .filtered table { 15 | border-right: none; 16 | } 17 | 18 | .change-list .filtered { 19 | min-height: 400px; 20 | } 21 | 22 | .change-list .filtered .results, .change-list .filtered .paginator, 23 | .filtered #toolbar, .filtered div.xfull { 24 | margin-right: 280px; 25 | width: auto; 26 | } 27 | 28 | .change-list .filtered table tbody th { 29 | padding-right: 1em; 30 | } 31 | 32 | #changelist-form .results { 33 | overflow-x: auto; 34 | } 35 | 36 | #changelist .toplinks { 37 | border-bottom: 1px solid #ddd; 38 | } 39 | 40 | #changelist .paginator { 41 | color: #666; 42 | border-bottom: 1px solid #eee; 43 | background: #fff; 44 | overflow: hidden; 45 | } 46 | 47 | /* CHANGELIST TABLES */ 48 | 49 | #changelist table thead th { 50 | padding: 0; 51 | white-space: nowrap; 52 | vertical-align: middle; 53 | } 54 | 55 | #changelist table thead th.action-checkbox-column { 56 | width: 1.5em; 57 | text-align: center; 58 | } 59 | 60 | #changelist table tbody td.action-checkbox { 61 | text-align: center; 62 | } 63 | 64 | #changelist table tfoot { 65 | color: #666; 66 | } 67 | 68 | /* TOOLBAR */ 69 | 70 | #changelist #toolbar { 71 | padding: 8px 10px; 72 | margin-bottom: 15px; 73 | border-top: 1px solid #eee; 74 | border-bottom: 1px solid #eee; 75 | background: #f8f8f8; 76 | color: #666; 77 | } 78 | 79 | #changelist #toolbar form input { 80 | border-radius: 4px; 81 | font-size: 14px; 82 | padding: 5px; 83 | color: #333; 84 | } 85 | 86 | #changelist #toolbar form #searchbar { 87 | height: 19px; 88 | border: 1px solid #ccc; 89 | padding: 2px 5px; 90 | margin: 0; 91 | vertical-align: top; 92 | font-size: 13px; 93 | } 94 | 95 | #changelist #toolbar form #searchbar:focus { 96 | border-color: #999; 97 | } 98 | 99 | #changelist #toolbar form input[type="submit"] { 100 | border: 1px solid #ccc; 101 | padding: 2px 10px; 102 | margin: 0; 103 | vertical-align: middle; 104 | background: #fff; 105 | box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; 106 | cursor: pointer; 107 | color: #333; 108 | } 109 | 110 | #changelist #toolbar form input[type="submit"]:focus, 111 | #changelist #toolbar form input[type="submit"]:hover { 112 | border-color: #999; 113 | } 114 | 115 | #changelist #changelist-search img { 116 | vertical-align: middle; 117 | margin-right: 4px; 118 | } 119 | 120 | /* FILTER COLUMN */ 121 | 122 | #changelist-filter { 123 | position: absolute; 124 | top: 0; 125 | right: 0; 126 | z-index: 1000; 127 | width: 240px; 128 | background: #f8f8f8; 129 | border-left: none; 130 | margin: 0; 131 | } 132 | 133 | #changelist-filter h2 { 134 | font-size: 14px; 135 | text-transform: uppercase; 136 | letter-spacing: 0.5px; 137 | padding: 5px 15px; 138 | margin-bottom: 12px; 139 | border-bottom: none; 140 | } 141 | 142 | #changelist-filter h3 { 143 | font-weight: 400; 144 | font-size: 14px; 145 | padding: 0 15px; 146 | margin-bottom: 10px; 147 | } 148 | 149 | #changelist-filter ul { 150 | margin: 5px 0; 151 | padding: 0 15px 15px; 152 | border-bottom: 1px solid #eaeaea; 153 | } 154 | 155 | #changelist-filter ul:last-child { 156 | border-bottom: none; 157 | padding-bottom: none; 158 | } 159 | 160 | #changelist-filter li { 161 | list-style-type: none; 162 | margin-left: 0; 163 | padding-left: 0; 164 | } 165 | 166 | #changelist-filter a { 167 | display: block; 168 | color: #999; 169 | text-overflow: ellipsis; 170 | overflow-x: hidden; 171 | } 172 | 173 | #changelist-filter li.selected { 174 | border-left: 5px solid #eaeaea; 175 | padding-left: 10px; 176 | margin-left: -15px; 177 | } 178 | 179 | #changelist-filter li.selected a { 180 | color: #5b80b2; 181 | } 182 | 183 | #changelist-filter a:focus, #changelist-filter a:hover, 184 | #changelist-filter li.selected a:focus, 185 | #changelist-filter li.selected a:hover { 186 | color: #036; 187 | } 188 | 189 | /* DATE DRILLDOWN */ 190 | 191 | .change-list ul.toplinks { 192 | display: block; 193 | float: left; 194 | padding: 0; 195 | margin: 0; 196 | width: 100%; 197 | } 198 | 199 | .change-list ul.toplinks li { 200 | padding: 3px 6px; 201 | font-weight: bold; 202 | list-style-type: none; 203 | display: inline-block; 204 | } 205 | 206 | .change-list ul.toplinks .date-back a { 207 | color: #999; 208 | } 209 | 210 | .change-list ul.toplinks .date-back a:focus, 211 | .change-list ul.toplinks .date-back a:hover { 212 | color: #036; 213 | } 214 | 215 | /* PAGINATOR */ 216 | 217 | .paginator { 218 | font-size: 13px; 219 | padding-top: 10px; 220 | padding-bottom: 10px; 221 | line-height: 22px; 222 | margin: 0; 223 | border-top: 1px solid #ddd; 224 | } 225 | 226 | .paginator a:link, .paginator a:visited { 227 | padding: 2px 6px; 228 | background: #79aec8; 229 | text-decoration: none; 230 | color: #fff; 231 | } 232 | 233 | .paginator a.showall { 234 | padding: 0; 235 | border: none; 236 | background: none; 237 | color: #5b80b2; 238 | } 239 | 240 | .paginator a.showall:focus, .paginator a.showall:hover { 241 | background: none; 242 | color: #036; 243 | } 244 | 245 | .paginator .end { 246 | margin-right: 6px; 247 | } 248 | 249 | .paginator .this-page { 250 | padding: 2px 6px; 251 | font-weight: bold; 252 | font-size: 13px; 253 | vertical-align: top; 254 | } 255 | 256 | .paginator a:focus, .paginator a:hover { 257 | color: white; 258 | background: #036; 259 | } 260 | 261 | /* ACTIONS */ 262 | 263 | .filtered .actions { 264 | margin-right: 280px; 265 | border-right: none; 266 | } 267 | 268 | #changelist table input { 269 | margin: 0; 270 | vertical-align: baseline; 271 | } 272 | 273 | #changelist table tbody tr.selected { 274 | background-color: #FFFFCC; 275 | } 276 | 277 | #changelist .actions { 278 | padding: 10px; 279 | background: #fff; 280 | border-top: none; 281 | border-bottom: none; 282 | line-height: 24px; 283 | color: #999; 284 | } 285 | 286 | #changelist .actions.selected { 287 | background: #fffccf; 288 | border-top: 1px solid #fffee8; 289 | border-bottom: 1px solid #edecd6; 290 | } 291 | 292 | #changelist .actions span.all, 293 | #changelist .actions span.action-counter, 294 | #changelist .actions span.clear, 295 | #changelist .actions span.question { 296 | font-size: 13px; 297 | margin: 0 0.5em; 298 | display: none; 299 | } 300 | 301 | #changelist .actions:last-child { 302 | border-bottom: none; 303 | } 304 | 305 | #changelist .actions select { 306 | vertical-align: top; 307 | height: 24px; 308 | background: none; 309 | color: #000; 310 | border: 1px solid #ccc; 311 | border-radius: 4px; 312 | font-size: 14px; 313 | padding: 0 0 0 4px; 314 | margin: 0; 315 | margin-left: 10px; 316 | } 317 | 318 | #changelist .actions select:focus { 319 | border-color: #999; 320 | } 321 | 322 | #changelist .actions label { 323 | display: inline-block; 324 | vertical-align: middle; 325 | font-size: 13px; 326 | } 327 | 328 | #changelist .actions .button { 329 | font-size: 13px; 330 | border: 1px solid #ccc; 331 | border-radius: 4px; 332 | background: #fff; 333 | box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset; 334 | cursor: pointer; 335 | height: 24px; 336 | line-height: 1; 337 | padding: 4px 8px; 338 | margin: 0; 339 | color: #333; 340 | } 341 | 342 | #changelist .actions .button:focus, #changelist .actions .button:hover { 343 | border-color: #999; 344 | } 345 | -------------------------------------------------------------------------------- /static/admin/js/urlify.js: -------------------------------------------------------------------------------- 1 | /*global XRegExp*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | var LATIN_MAP = { 6 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 7 | 'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 8 | 'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 9 | 'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 10 | 'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a', 11 | 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 12 | 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 13 | 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 14 | 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 15 | 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' 16 | }; 17 | var LATIN_SYMBOLS_MAP = { 18 | '©': '(c)' 19 | }; 20 | var GREEK_MAP = { 21 | 'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h', 22 | 'θ': '8', 'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': '3', 23 | 'ο': 'o', 'π': 'p', 'ρ': 'r', 'σ': 's', 'τ': 't', 'υ': 'y', 'φ': 'f', 24 | 'χ': 'x', 'ψ': 'ps', 'ω': 'w', 'ά': 'a', 'έ': 'e', 'ί': 'i', 'ό': 'o', 25 | 'ύ': 'y', 'ή': 'h', 'ώ': 'w', 'ς': 's', 'ϊ': 'i', 'ΰ': 'y', 'ϋ': 'y', 26 | 'ΐ': 'i', 'Α': 'A', 'Β': 'B', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z', 27 | 'Η': 'H', 'Θ': '8', 'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N', 28 | 'Ξ': '3', 'Ο': 'O', 'Π': 'P', 'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y', 29 | 'Φ': 'F', 'Χ': 'X', 'Ψ': 'PS', 'Ω': 'W', 'Ά': 'A', 'Έ': 'E', 'Ί': 'I', 30 | 'Ό': 'O', 'Ύ': 'Y', 'Ή': 'H', 'Ώ': 'W', 'Ϊ': 'I', 'Ϋ': 'Y' 31 | }; 32 | var TURKISH_MAP = { 33 | 'ş': 's', 'Ş': 'S', 'ı': 'i', 'İ': 'I', 'ç': 'c', 'Ç': 'C', 'ü': 'u', 34 | 'Ü': 'U', 'ö': 'o', 'Ö': 'O', 'ğ': 'g', 'Ğ': 'G' 35 | }; 36 | var ROMANIAN_MAP = { 37 | 'ă': 'a', 'î': 'i', 'ș': 's', 'ț': 't', 'â': 'a', 38 | 'Ă': 'A', 'Î': 'I', 'Ș': 'S', 'Ț': 'T', 'Â': 'A' 39 | }; 40 | var RUSSIAN_MAP = { 41 | 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 42 | 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 43 | 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 44 | 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', 'ш': 'sh', 'щ': 'sh', 'ъ': '', 45 | 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya', 46 | 'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo', 47 | 'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'J', 'К': 'K', 'Л': 'L', 'М': 'M', 48 | 'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U', 49 | 'Ф': 'F', 'Х': 'H', 'Ц': 'C', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sh', 'Ъ': '', 50 | 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya' 51 | }; 52 | var UKRAINIAN_MAP = { 53 | 'Є': 'Ye', 'І': 'I', 'Ї': 'Yi', 'Ґ': 'G', 'є': 'ye', 'і': 'i', 54 | 'ї': 'yi', 'ґ': 'g' 55 | }; 56 | var CZECH_MAP = { 57 | 'č': 'c', 'ď': 'd', 'ě': 'e', 'ň': 'n', 'ř': 'r', 'š': 's', 'ť': 't', 58 | 'ů': 'u', 'ž': 'z', 'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R', 59 | 'Š': 'S', 'Ť': 'T', 'Ů': 'U', 'Ž': 'Z' 60 | }; 61 | var POLISH_MAP = { 62 | 'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's', 63 | 'ź': 'z', 'ż': 'z', 64 | 'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N', 'Ó': 'O', 'Ś': 'S', 65 | 'Ź': 'Z', 'Ż': 'Z' 66 | }; 67 | var LATVIAN_MAP = { 68 | 'ā': 'a', 'č': 'c', 'ē': 'e', 'ģ': 'g', 'ī': 'i', 'ķ': 'k', 'ļ': 'l', 69 | 'ņ': 'n', 'š': 's', 'ū': 'u', 'ž': 'z', 70 | 'Ā': 'A', 'Č': 'C', 'Ē': 'E', 'Ģ': 'G', 'Ī': 'I', 'Ķ': 'K', 'Ļ': 'L', 71 | 'Ņ': 'N', 'Š': 'S', 'Ū': 'U', 'Ž': 'Z' 72 | }; 73 | var ARABIC_MAP = { 74 | 'أ': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'g', 'ح': 'h', 'خ': 'kh', 'د': 'd', 75 | 'ذ': 'th', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's', 'ض': 'd', 'ط': 't', 76 | 'ظ': 'th', 'ع': 'aa', 'غ': 'gh', 'ف': 'f', 'ق': 'k', 'ك': 'k', 'ل': 'l', 'م': 'm', 77 | 'ن': 'n', 'ه': 'h', 'و': 'o', 'ي': 'y' 78 | }; 79 | var LITHUANIAN_MAP = { 80 | 'ą': 'a', 'č': 'c', 'ę': 'e', 'ė': 'e', 'į': 'i', 'š': 's', 'ų': 'u', 81 | 'ū': 'u', 'ž': 'z', 82 | 'Ą': 'A', 'Č': 'C', 'Ę': 'E', 'Ė': 'E', 'Į': 'I', 'Š': 'S', 'Ų': 'U', 83 | 'Ū': 'U', 'Ž': 'Z' 84 | }; 85 | var SERBIAN_MAP = { 86 | 'ђ': 'dj', 'ј': 'j', 'љ': 'lj', 'њ': 'nj', 'ћ': 'c', 'џ': 'dz', 87 | 'đ': 'dj', 'Ђ': 'Dj', 'Ј': 'j', 'Љ': 'Lj', 'Њ': 'Nj', 'Ћ': 'C', 88 | 'Џ': 'Dz', 'Đ': 'Dj' 89 | }; 90 | var AZERBAIJANI_MAP = { 91 | 'ç': 'c', 'ə': 'e', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u', 92 | 'Ç': 'C', 'Ə': 'E', 'Ğ': 'G', 'İ': 'I', 'Ö': 'O', 'Ş': 'S', 'Ü': 'U' 93 | }; 94 | var GEORGIAN_MAP = { 95 | 'ა': 'a', 'ბ': 'b', 'გ': 'g', 'დ': 'd', 'ე': 'e', 'ვ': 'v', 'ზ': 'z', 96 | 'თ': 't', 'ი': 'i', 'კ': 'k', 'ლ': 'l', 'მ': 'm', 'ნ': 'n', 'ო': 'o', 97 | 'პ': 'p', 'ჟ': 'j', 'რ': 'r', 'ს': 's', 'ტ': 't', 'უ': 'u', 'ფ': 'f', 98 | 'ქ': 'q', 'ღ': 'g', 'ყ': 'y', 'შ': 'sh', 'ჩ': 'ch', 'ც': 'c', 'ძ': 'dz', 99 | 'წ': 'w', 'ჭ': 'ch', 'ხ': 'x', 'ჯ': 'j', 'ჰ': 'h' 100 | }; 101 | 102 | var ALL_DOWNCODE_MAPS = [ 103 | LATIN_MAP, 104 | LATIN_SYMBOLS_MAP, 105 | GREEK_MAP, 106 | TURKISH_MAP, 107 | ROMANIAN_MAP, 108 | RUSSIAN_MAP, 109 | UKRAINIAN_MAP, 110 | CZECH_MAP, 111 | POLISH_MAP, 112 | LATVIAN_MAP, 113 | ARABIC_MAP, 114 | LITHUANIAN_MAP, 115 | SERBIAN_MAP, 116 | AZERBAIJANI_MAP, 117 | GEORGIAN_MAP 118 | ]; 119 | 120 | var Downcoder = { 121 | 'Initialize': function() { 122 | if (Downcoder.map) { // already made 123 | return; 124 | } 125 | Downcoder.map = {}; 126 | Downcoder.chars = []; 127 | for (var i = 0; i < ALL_DOWNCODE_MAPS.length; i++) { 128 | var lookup = ALL_DOWNCODE_MAPS[i]; 129 | for (var c in lookup) { 130 | if (lookup.hasOwnProperty(c)) { 131 | Downcoder.map[c] = lookup[c]; 132 | } 133 | } 134 | } 135 | for (var k in Downcoder.map) { 136 | if (Downcoder.map.hasOwnProperty(k)) { 137 | Downcoder.chars.push(k); 138 | } 139 | } 140 | Downcoder.regex = new RegExp(Downcoder.chars.join('|'), 'g'); 141 | } 142 | }; 143 | 144 | function downcode(slug) { 145 | Downcoder.Initialize(); 146 | return slug.replace(Downcoder.regex, function(m) { 147 | return Downcoder.map[m]; 148 | }); 149 | } 150 | 151 | 152 | function URLify(s, num_chars, allowUnicode) { 153 | // changes, e.g., "Petty theft" to "petty-theft" 154 | // remove all these words from the string before urlifying 155 | if (!allowUnicode) { 156 | s = downcode(s); 157 | } 158 | var removelist = [ 159 | "a", "an", "as", "at", "before", "but", "by", "for", "from", "is", 160 | "in", "into", "like", "of", "off", "on", "onto", "per", "since", 161 | "than", "the", "this", "that", "to", "up", "via", "with" 162 | ]; 163 | var r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); 164 | s = s.replace(r, ''); 165 | // if downcode doesn't hit, the char will be stripped here 166 | if (allowUnicode) { 167 | // Keep Unicode letters including both lowercase and uppercase 168 | // characters, whitespace, and dash; remove other characters. 169 | s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), ''); 170 | } else { 171 | s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars 172 | } 173 | s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces 174 | s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens 175 | s = s.toLowerCase(); // convert to lowercase 176 | return s.substring(0, num_chars); // trim to first num_chars chars 177 | } 178 | window.URLify = URLify; 179 | })(); 180 | -------------------------------------------------------------------------------- /static/admin/js/calendar.js: -------------------------------------------------------------------------------- 1 | /*global gettext, pgettext, get_format, quickElement, removeChildren, addEvent*/ 2 | /* 3 | calendar.js - Calendar functions by Adrian Holovaty 4 | depends on core.js for utility functions like removeChildren or quickElement 5 | */ 6 | 7 | (function() { 8 | 'use strict'; 9 | // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions 10 | var CalendarNamespace = { 11 | monthsOfYear: [ 12 | gettext('January'), 13 | gettext('February'), 14 | gettext('March'), 15 | gettext('April'), 16 | gettext('May'), 17 | gettext('June'), 18 | gettext('July'), 19 | gettext('August'), 20 | gettext('September'), 21 | gettext('October'), 22 | gettext('November'), 23 | gettext('December') 24 | ], 25 | daysOfWeek: [ 26 | pgettext('one letter Sunday', 'S'), 27 | pgettext('one letter Monday', 'M'), 28 | pgettext('one letter Tuesday', 'T'), 29 | pgettext('one letter Wednesday', 'W'), 30 | pgettext('one letter Thursday', 'T'), 31 | pgettext('one letter Friday', 'F'), 32 | pgettext('one letter Saturday', 'S') 33 | ], 34 | firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')), 35 | isLeapYear: function(year) { 36 | return (((year % 4) === 0) && ((year % 100) !== 0 ) || ((year % 400) === 0)); 37 | }, 38 | getDaysInMonth: function(month, year) { 39 | var days; 40 | if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) { 41 | days = 31; 42 | } 43 | else if (month === 4 || month === 6 || month === 9 || month === 11) { 44 | days = 30; 45 | } 46 | else if (month === 2 && CalendarNamespace.isLeapYear(year)) { 47 | days = 29; 48 | } 49 | else { 50 | days = 28; 51 | } 52 | return days; 53 | }, 54 | draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999 55 | var today = new Date(); 56 | var todayDay = today.getDate(); 57 | var todayMonth = today.getMonth() + 1; 58 | var todayYear = today.getFullYear(); 59 | var todayClass = ''; 60 | 61 | // Use UTC functions here because the date field does not contain time 62 | // and using the UTC function variants prevent the local time offset 63 | // from altering the date, specifically the day field. For example: 64 | // 65 | // ``` 66 | // var x = new Date('2013-10-02'); 67 | // var day = x.getDate(); 68 | // ``` 69 | // 70 | // The day variable above will be 1 instead of 2 in, say, US Pacific time 71 | // zone. 72 | var isSelectedMonth = false; 73 | if (typeof selected !== 'undefined') { 74 | isSelectedMonth = (selected.getUTCFullYear() === year && (selected.getUTCMonth() + 1) === month); 75 | } 76 | 77 | month = parseInt(month); 78 | year = parseInt(year); 79 | var calDiv = document.getElementById(div_id); 80 | removeChildren(calDiv); 81 | var calTable = document.createElement('table'); 82 | quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month - 1] + ' ' + year); 83 | var tableBody = quickElement('tbody', calTable); 84 | 85 | // Draw days-of-week header 86 | var tableRow = quickElement('tr', tableBody); 87 | for (var i = 0; i < 7; i++) { 88 | quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); 89 | } 90 | 91 | var startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); 92 | var days = CalendarNamespace.getDaysInMonth(month, year); 93 | 94 | var nonDayCell; 95 | 96 | // Draw blanks before first of month 97 | tableRow = quickElement('tr', tableBody); 98 | for (i = 0; i < startingPos; i++) { 99 | nonDayCell = quickElement('td', tableRow, ' '); 100 | nonDayCell.className = "nonday"; 101 | } 102 | 103 | function calendarMonth(y, m) { 104 | function onClick(e) { 105 | e.preventDefault(); 106 | callback(y, m, django.jQuery(this).text()); 107 | } 108 | return onClick; 109 | } 110 | 111 | // Draw days of month 112 | var currentDay = 1; 113 | for (i = startingPos; currentDay <= days; i++) { 114 | if (i % 7 === 0 && currentDay !== 1) { 115 | tableRow = quickElement('tr', tableBody); 116 | } 117 | if ((currentDay === todayDay) && (month === todayMonth) && (year === todayYear)) { 118 | todayClass = 'today'; 119 | } else { 120 | todayClass = ''; 121 | } 122 | 123 | // use UTC function; see above for explanation. 124 | if (isSelectedMonth && currentDay === selected.getUTCDate()) { 125 | if (todayClass !== '') { 126 | todayClass += " "; 127 | } 128 | todayClass += "selected"; 129 | } 130 | 131 | var cell = quickElement('td', tableRow, '', 'class', todayClass); 132 | var link = quickElement('a', cell, currentDay, 'href', '#'); 133 | addEvent(link, 'click', calendarMonth(year, month)); 134 | currentDay++; 135 | } 136 | 137 | // Draw blanks after end of month (optional, but makes for valid code) 138 | while (tableRow.childNodes.length < 7) { 139 | nonDayCell = quickElement('td', tableRow, ' '); 140 | nonDayCell.className = "nonday"; 141 | } 142 | 143 | calDiv.appendChild(calTable); 144 | } 145 | }; 146 | 147 | // Calendar -- A calendar instance 148 | function Calendar(div_id, callback, selected) { 149 | // div_id (string) is the ID of the element in which the calendar will 150 | // be displayed 151 | // callback (string) is the name of a JavaScript function that will be 152 | // called with the parameters (year, month, day) when a day in the 153 | // calendar is clicked 154 | this.div_id = div_id; 155 | this.callback = callback; 156 | this.today = new Date(); 157 | this.currentMonth = this.today.getMonth() + 1; 158 | this.currentYear = this.today.getFullYear(); 159 | if (typeof selected !== 'undefined') { 160 | this.selected = selected; 161 | } 162 | } 163 | Calendar.prototype = { 164 | drawCurrent: function() { 165 | CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected); 166 | }, 167 | drawDate: function(month, year, selected) { 168 | this.currentMonth = month; 169 | this.currentYear = year; 170 | 171 | if(selected) { 172 | this.selected = selected; 173 | } 174 | 175 | this.drawCurrent(); 176 | }, 177 | drawPreviousMonth: function() { 178 | if (this.currentMonth === 1) { 179 | this.currentMonth = 12; 180 | this.currentYear--; 181 | } 182 | else { 183 | this.currentMonth--; 184 | } 185 | this.drawCurrent(); 186 | }, 187 | drawNextMonth: function() { 188 | if (this.currentMonth === 12) { 189 | this.currentMonth = 1; 190 | this.currentYear++; 191 | } 192 | else { 193 | this.currentMonth++; 194 | } 195 | this.drawCurrent(); 196 | }, 197 | drawPreviousYear: function() { 198 | this.currentYear--; 199 | this.drawCurrent(); 200 | }, 201 | drawNextYear: function() { 202 | this.currentYear++; 203 | this.drawCurrent(); 204 | } 205 | }; 206 | window.Calendar = Calendar; 207 | window.CalendarNamespace = CalendarNamespace; 208 | })(); 209 | -------------------------------------------------------------------------------- /static/admin/js/core.js: -------------------------------------------------------------------------------- 1 | // Core javascript helper functions 2 | 3 | // basic browser identification & version 4 | var isOpera = (navigator.userAgent.indexOf("Opera") >= 0) && parseFloat(navigator.appVersion); 5 | var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); 6 | 7 | // Cross-browser event handlers. 8 | function addEvent(obj, evType, fn) { 9 | 'use strict'; 10 | if (obj.addEventListener) { 11 | obj.addEventListener(evType, fn, false); 12 | return true; 13 | } else if (obj.attachEvent) { 14 | var r = obj.attachEvent("on" + evType, fn); 15 | return r; 16 | } else { 17 | return false; 18 | } 19 | } 20 | 21 | function removeEvent(obj, evType, fn) { 22 | 'use strict'; 23 | if (obj.removeEventListener) { 24 | obj.removeEventListener(evType, fn, false); 25 | return true; 26 | } else if (obj.detachEvent) { 27 | obj.detachEvent("on" + evType, fn); 28 | return true; 29 | } else { 30 | return false; 31 | } 32 | } 33 | 34 | function cancelEventPropagation(e) { 35 | 'use strict'; 36 | if (!e) { 37 | e = window.event; 38 | } 39 | e.cancelBubble = true; 40 | if (e.stopPropagation) { 41 | e.stopPropagation(); 42 | } 43 | } 44 | 45 | // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]); 46 | function quickElement() { 47 | 'use strict'; 48 | var obj = document.createElement(arguments[0]); 49 | if (arguments[2]) { 50 | var textNode = document.createTextNode(arguments[2]); 51 | obj.appendChild(textNode); 52 | } 53 | var len = arguments.length; 54 | for (var i = 3; i < len; i += 2) { 55 | obj.setAttribute(arguments[i], arguments[i + 1]); 56 | } 57 | arguments[1].appendChild(obj); 58 | return obj; 59 | } 60 | 61 | // "a" is reference to an object 62 | function removeChildren(a) { 63 | 'use strict'; 64 | while (a.hasChildNodes()) { 65 | a.removeChild(a.lastChild); 66 | } 67 | } 68 | 69 | // ---------------------------------------------------------------------------- 70 | // Find-position functions by PPK 71 | // See http://www.quirksmode.org/js/findpos.html 72 | // ---------------------------------------------------------------------------- 73 | function findPosX(obj) { 74 | 'use strict'; 75 | var curleft = 0; 76 | if (obj.offsetParent) { 77 | while (obj.offsetParent) { 78 | curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); 79 | obj = obj.offsetParent; 80 | } 81 | // IE offsetParent does not include the top-level 82 | if (isIE && obj.parentElement) { 83 | curleft += obj.offsetLeft - obj.scrollLeft; 84 | } 85 | } else if (obj.x) { 86 | curleft += obj.x; 87 | } 88 | return curleft; 89 | } 90 | 91 | function findPosY(obj) { 92 | 'use strict'; 93 | var curtop = 0; 94 | if (obj.offsetParent) { 95 | while (obj.offsetParent) { 96 | curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); 97 | obj = obj.offsetParent; 98 | } 99 | // IE offsetParent does not include the top-level 100 | if (isIE && obj.parentElement) { 101 | curtop += obj.offsetTop - obj.scrollTop; 102 | } 103 | } else if (obj.y) { 104 | curtop += obj.y; 105 | } 106 | return curtop; 107 | } 108 | 109 | //----------------------------------------------------------------------------- 110 | // Date object extensions 111 | // ---------------------------------------------------------------------------- 112 | (function() { 113 | 'use strict'; 114 | Date.prototype.getTwelveHours = function() { 115 | var hours = this.getHours(); 116 | if (hours === 0) { 117 | return 12; 118 | } 119 | else { 120 | return hours <= 12 ? hours : hours - 12; 121 | } 122 | }; 123 | 124 | Date.prototype.getTwoDigitMonth = function() { 125 | return (this.getMonth() < 9) ? '0' + (this.getMonth() + 1) : (this.getMonth() + 1); 126 | }; 127 | 128 | Date.prototype.getTwoDigitDate = function() { 129 | return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); 130 | }; 131 | 132 | Date.prototype.getTwoDigitTwelveHour = function() { 133 | return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours(); 134 | }; 135 | 136 | Date.prototype.getTwoDigitHour = function() { 137 | return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); 138 | }; 139 | 140 | Date.prototype.getTwoDigitMinute = function() { 141 | return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); 142 | }; 143 | 144 | Date.prototype.getTwoDigitSecond = function() { 145 | return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); 146 | }; 147 | 148 | Date.prototype.getHourMinute = function() { 149 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); 150 | }; 151 | 152 | Date.prototype.getHourMinuteSecond = function() { 153 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); 154 | }; 155 | 156 | Date.prototype.getFullMonthName = function() { 157 | return typeof window.CalendarNamespace === "undefined" 158 | ? this.getTwoDigitMonth() 159 | : window.CalendarNamespace.monthsOfYear[this.getMonth()]; 160 | }; 161 | 162 | Date.prototype.strftime = function(format) { 163 | var fields = { 164 | B: this.getFullMonthName(), 165 | c: this.toString(), 166 | d: this.getTwoDigitDate(), 167 | H: this.getTwoDigitHour(), 168 | I: this.getTwoDigitTwelveHour(), 169 | m: this.getTwoDigitMonth(), 170 | M: this.getTwoDigitMinute(), 171 | p: (this.getHours() >= 12) ? 'PM' : 'AM', 172 | S: this.getTwoDigitSecond(), 173 | w: '0' + this.getDay(), 174 | x: this.toLocaleDateString(), 175 | X: this.toLocaleTimeString(), 176 | y: ('' + this.getFullYear()).substr(2, 4), 177 | Y: '' + this.getFullYear(), 178 | '%': '%' 179 | }; 180 | var result = '', i = 0; 181 | while (i < format.length) { 182 | if (format.charAt(i) === '%') { 183 | result = result + fields[format.charAt(i + 1)]; 184 | ++i; 185 | } 186 | else { 187 | result = result + format.charAt(i); 188 | } 189 | ++i; 190 | } 191 | return result; 192 | }; 193 | 194 | // ---------------------------------------------------------------------------- 195 | // String object extensions 196 | // ---------------------------------------------------------------------------- 197 | String.prototype.pad_left = function(pad_length, pad_string) { 198 | var new_string = this; 199 | for (var i = 0; new_string.length < pad_length; i++) { 200 | new_string = pad_string + new_string; 201 | } 202 | return new_string; 203 | }; 204 | 205 | String.prototype.strptime = function(format) { 206 | var split_format = format.split(/[.\-/]/); 207 | var date = this.split(/[.\-/]/); 208 | var i = 0; 209 | var day, month, year; 210 | while (i < split_format.length) { 211 | switch (split_format[i]) { 212 | case "%d": 213 | day = date[i]; 214 | break; 215 | case "%m": 216 | month = date[i] - 1; 217 | break; 218 | case "%Y": 219 | year = date[i]; 220 | break; 221 | case "%y": 222 | year = date[i]; 223 | break; 224 | } 225 | ++i; 226 | } 227 | // Create Date object from UTC since the parsed value is supposed to be 228 | // in UTC, not local time. Also, the calendar uses UTC functions for 229 | // date extraction. 230 | return new Date(Date.UTC(year, month, day)); 231 | }; 232 | 233 | })(); 234 | // ---------------------------------------------------------------------------- 235 | // Get the computed style for and element 236 | // ---------------------------------------------------------------------------- 237 | function getStyle(oElm, strCssRule) { 238 | 'use strict'; 239 | var strValue = ""; 240 | if(document.defaultView && document.defaultView.getComputedStyle) { 241 | strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); 242 | } 243 | else if(oElm.currentStyle) { 244 | strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) { 245 | return p1.toUpperCase(); 246 | }); 247 | strValue = oElm.currentStyle[strCssRule]; 248 | } 249 | return strValue; 250 | } 251 | -------------------------------------------------------------------------------- /static/admin/css/forms.css: -------------------------------------------------------------------------------- 1 | @import url('widgets.css'); 2 | 3 | /* FORM ROWS */ 4 | 5 | .form-row { 6 | overflow: hidden; 7 | padding: 10px; 8 | font-size: 13px; 9 | border-bottom: 1px solid #eee; 10 | } 11 | 12 | .form-row img, .form-row input { 13 | vertical-align: middle; 14 | } 15 | 16 | .form-row label input[type="checkbox"] { 17 | margin-top: 0; 18 | vertical-align: 0; 19 | } 20 | 21 | form .form-row p { 22 | padding-left: 0; 23 | } 24 | 25 | .hidden { 26 | display: none; 27 | } 28 | 29 | /* FORM LABELS */ 30 | 31 | label { 32 | font-weight: normal; 33 | color: #666; 34 | font-size: 13px; 35 | } 36 | 37 | .required label, label.required { 38 | font-weight: bold; 39 | color: #333; 40 | } 41 | 42 | /* RADIO BUTTONS */ 43 | 44 | form ul.radiolist li { 45 | list-style-type: none; 46 | } 47 | 48 | form ul.radiolist label { 49 | float: none; 50 | display: inline; 51 | } 52 | 53 | form ul.radiolist input[type="radio"] { 54 | margin: -2px 4px 0 0; 55 | padding: 0; 56 | } 57 | 58 | form ul.inline { 59 | margin-left: 0; 60 | padding: 0; 61 | } 62 | 63 | form ul.inline li { 64 | float: left; 65 | padding-right: 7px; 66 | } 67 | 68 | /* ALIGNED FIELDSETS */ 69 | 70 | .aligned label { 71 | display: block; 72 | padding: 4px 10px 0 0; 73 | float: left; 74 | width: 160px; 75 | word-wrap: break-word; 76 | line-height: 1; 77 | } 78 | 79 | .aligned label:not(.vCheckboxLabel):after { 80 | content: ''; 81 | display: inline-block; 82 | vertical-align: middle; 83 | height: 26px; 84 | } 85 | 86 | .aligned label + p, .aligned label + div.help, .aligned label + div.readonly { 87 | padding: 6px 0; 88 | margin-top: 0; 89 | margin-bottom: 0; 90 | margin-left: 170px; 91 | } 92 | 93 | .aligned ul label { 94 | display: inline; 95 | float: none; 96 | width: auto; 97 | } 98 | 99 | .aligned .form-row input { 100 | margin-bottom: 0; 101 | } 102 | 103 | .colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { 104 | width: 350px; 105 | } 106 | 107 | form .aligned ul { 108 | margin-left: 160px; 109 | padding-left: 10px; 110 | } 111 | 112 | form .aligned ul.radiolist { 113 | display: inline-block; 114 | margin: 0; 115 | padding: 0; 116 | } 117 | 118 | form .aligned p.help, 119 | form .aligned div.help { 120 | clear: left; 121 | margin-top: 0; 122 | margin-left: 160px; 123 | padding-left: 10px; 124 | } 125 | 126 | form .aligned label + p.help, 127 | form .aligned label + div.help { 128 | margin-left: 0; 129 | padding-left: 0; 130 | } 131 | 132 | form .aligned p.help:last-child, 133 | form .aligned div.help:last-child { 134 | margin-bottom: 0; 135 | padding-bottom: 0; 136 | } 137 | 138 | form .aligned input + p.help, 139 | form .aligned textarea + p.help, 140 | form .aligned select + p.help, 141 | form .aligned input + div.help, 142 | form .aligned textarea + div.help, 143 | form .aligned select + div.help { 144 | margin-left: 160px; 145 | padding-left: 10px; 146 | } 147 | 148 | form .aligned ul li { 149 | list-style: none; 150 | } 151 | 152 | form .aligned table p { 153 | margin-left: 0; 154 | padding-left: 0; 155 | } 156 | 157 | .aligned .vCheckboxLabel { 158 | float: none; 159 | width: auto; 160 | display: inline-block; 161 | vertical-align: -3px; 162 | padding: 0 0 5px 5px; 163 | } 164 | 165 | .aligned .vCheckboxLabel + p.help, 166 | .aligned .vCheckboxLabel + div.help { 167 | margin-top: -4px; 168 | } 169 | 170 | .colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { 171 | width: 610px; 172 | } 173 | 174 | .checkbox-row p.help, 175 | .checkbox-row div.help { 176 | margin-left: 0; 177 | padding-left: 0; 178 | } 179 | 180 | fieldset .field-box { 181 | float: left; 182 | margin-right: 20px; 183 | } 184 | 185 | /* WIDE FIELDSETS */ 186 | 187 | .wide label { 188 | width: 200px; 189 | } 190 | 191 | form .wide p, 192 | form .wide input + p.help, 193 | form .wide input + div.help { 194 | margin-left: 200px; 195 | } 196 | 197 | form .wide p.help, 198 | form .wide div.help { 199 | padding-left: 38px; 200 | } 201 | 202 | form div.help ul { 203 | padding-left: 0; 204 | margin-left: 0; 205 | } 206 | 207 | .colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { 208 | width: 450px; 209 | } 210 | 211 | /* COLLAPSED FIELDSETS */ 212 | 213 | fieldset.collapsed * { 214 | display: none; 215 | } 216 | 217 | fieldset.collapsed h2, fieldset.collapsed { 218 | display: block; 219 | } 220 | 221 | fieldset.collapsed { 222 | border: 1px solid #eee; 223 | border-radius: 4px; 224 | overflow: hidden; 225 | } 226 | 227 | fieldset.collapsed h2 { 228 | background: #f8f8f8; 229 | color: #666; 230 | } 231 | 232 | fieldset .collapse-toggle { 233 | color: #fff; 234 | } 235 | 236 | fieldset.collapsed .collapse-toggle { 237 | background: transparent; 238 | display: inline; 239 | color: #447e9b; 240 | } 241 | 242 | /* MONOSPACE TEXTAREAS */ 243 | 244 | fieldset.monospace textarea { 245 | font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; 246 | } 247 | 248 | /* SUBMIT ROW */ 249 | 250 | .submit-row { 251 | padding: 12px 14px; 252 | margin: 0 0 20px; 253 | background: #f8f8f8; 254 | border: 1px solid #eee; 255 | border-radius: 4px; 256 | text-align: right; 257 | overflow: hidden; 258 | } 259 | 260 | body.popup .submit-row { 261 | overflow: auto; 262 | } 263 | 264 | .submit-row input { 265 | height: 35px; 266 | line-height: 15px; 267 | margin: 0 0 0 5px; 268 | } 269 | 270 | .submit-row input.default { 271 | margin: 0 0 0 8px; 272 | text-transform: uppercase; 273 | } 274 | 275 | .submit-row p { 276 | margin: 0.3em; 277 | } 278 | 279 | .submit-row p.deletelink-box { 280 | float: left; 281 | margin: 0; 282 | } 283 | 284 | .submit-row a.deletelink { 285 | display: block; 286 | background: #ba2121; 287 | border-radius: 4px; 288 | padding: 10px 15px; 289 | height: 15px; 290 | line-height: 15px; 291 | color: #fff; 292 | } 293 | 294 | .submit-row a.deletelink:focus, 295 | .submit-row a.deletelink:hover, 296 | .submit-row a.deletelink:active { 297 | background: #a41515; 298 | } 299 | 300 | /* CUSTOM FORM FIELDS */ 301 | 302 | .vSelectMultipleField { 303 | vertical-align: top; 304 | } 305 | 306 | .vCheckboxField { 307 | border: none; 308 | } 309 | 310 | .vDateField, .vTimeField { 311 | margin-right: 2px; 312 | margin-bottom: 4px; 313 | } 314 | 315 | .vDateField { 316 | min-width: 6.85em; 317 | } 318 | 319 | .vTimeField { 320 | min-width: 4.7em; 321 | } 322 | 323 | .vURLField { 324 | width: 30em; 325 | } 326 | 327 | .vLargeTextField, .vXMLLargeTextField { 328 | width: 48em; 329 | } 330 | 331 | .flatpages-flatpage #id_content { 332 | height: 40.2em; 333 | } 334 | 335 | .module table .vPositiveSmallIntegerField { 336 | width: 2.2em; 337 | } 338 | 339 | .vTextField { 340 | width: 20em; 341 | } 342 | 343 | .vIntegerField { 344 | width: 5em; 345 | } 346 | 347 | .vBigIntegerField { 348 | width: 10em; 349 | } 350 | 351 | .vForeignKeyRawIdAdminField { 352 | width: 5em; 353 | } 354 | 355 | /* INLINES */ 356 | 357 | .inline-group { 358 | padding: 0; 359 | margin: 0 0 30px; 360 | } 361 | 362 | .inline-group thead th { 363 | padding: 8px 10px; 364 | } 365 | 366 | .inline-group .aligned label { 367 | width: 160px; 368 | } 369 | 370 | .inline-related { 371 | position: relative; 372 | } 373 | 374 | .inline-related h3 { 375 | margin: 0; 376 | color: #666; 377 | padding: 5px; 378 | font-size: 13px; 379 | background: #f8f8f8; 380 | border-top: 1px solid #eee; 381 | border-bottom: 1px solid #eee; 382 | } 383 | 384 | .inline-related h3 span.delete { 385 | float: right; 386 | } 387 | 388 | .inline-related h3 span.delete label { 389 | margin-left: 2px; 390 | font-size: 11px; 391 | } 392 | 393 | .inline-related fieldset { 394 | margin: 0; 395 | background: #fff; 396 | border: none; 397 | width: 100%; 398 | } 399 | 400 | .inline-related fieldset.module h3 { 401 | margin: 0; 402 | padding: 2px 5px 3px 5px; 403 | font-size: 11px; 404 | text-align: left; 405 | font-weight: bold; 406 | background: #bcd; 407 | color: #fff; 408 | } 409 | 410 | .inline-group .tabular fieldset.module { 411 | border: none; 412 | } 413 | 414 | .inline-related.tabular fieldset.module table { 415 | width: 100%; 416 | } 417 | 418 | .last-related fieldset { 419 | border: none; 420 | } 421 | 422 | .inline-group .tabular tr.has_original td { 423 | padding-top: 2em; 424 | } 425 | 426 | .inline-group .tabular tr td.original { 427 | padding: 2px 0 0 0; 428 | width: 0; 429 | _position: relative; 430 | } 431 | 432 | .inline-group .tabular th.original { 433 | width: 0px; 434 | padding: 0; 435 | } 436 | 437 | .inline-group .tabular td.original p { 438 | position: absolute; 439 | left: 0; 440 | height: 1.1em; 441 | padding: 2px 9px; 442 | overflow: hidden; 443 | font-size: 9px; 444 | font-weight: bold; 445 | color: #666; 446 | _width: 700px; 447 | } 448 | 449 | .inline-group ul.tools { 450 | padding: 0; 451 | margin: 0; 452 | list-style: none; 453 | } 454 | 455 | .inline-group ul.tools li { 456 | display: inline; 457 | padding: 0 5px; 458 | } 459 | 460 | .inline-group div.add-row, 461 | .inline-group .tabular tr.add-row td { 462 | color: #666; 463 | background: #f8f8f8; 464 | padding: 8px 10px; 465 | border-bottom: 1px solid #eee; 466 | } 467 | 468 | .inline-group .tabular tr.add-row td { 469 | padding: 8px 10px; 470 | border-bottom: 1px solid #eee; 471 | } 472 | 473 | .inline-group ul.tools a.add, 474 | .inline-group div.add-row a, 475 | .inline-group .tabular tr.add-row td a { 476 | background: url(../img/icon-addlink.svg) 0 1px no-repeat; 477 | padding-left: 16px; 478 | font-size: 12px; 479 | } 480 | 481 | .empty-form { 482 | display: none; 483 | } 484 | 485 | /* RELATED FIELD ADD ONE / LOOKUP */ 486 | 487 | .add-another, .related-lookup { 488 | margin-left: 5px; 489 | display: inline-block; 490 | vertical-align: middle; 491 | background-repeat: no-repeat; 492 | background-size: 14px; 493 | } 494 | 495 | .add-another { 496 | width: 16px; 497 | height: 16px; 498 | background-image: url(../img/icon-addlink.svg); 499 | } 500 | 501 | .related-lookup { 502 | width: 16px; 503 | height: 16px; 504 | background-image: url(../img/search.svg); 505 | } 506 | 507 | form .related-widget-wrapper ul { 508 | display: inline-block; 509 | margin-left: 0; 510 | padding-left: 0; 511 | } 512 | 513 | .clearable-file-input input { 514 | margin-top: 0; 515 | } 516 | -------------------------------------------------------------------------------- /static/admin/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /static/admin/css/widgets.css: -------------------------------------------------------------------------------- 1 | /* SELECTOR (FILTER INTERFACE) */ 2 | 3 | .selector { 4 | width: 800px; 5 | float: left; 6 | } 7 | 8 | .selector select { 9 | width: 380px; 10 | height: 17.2em; 11 | } 12 | 13 | .selector-available, .selector-chosen { 14 | float: left; 15 | width: 380px; 16 | text-align: center; 17 | margin-bottom: 5px; 18 | } 19 | 20 | .selector-chosen select { 21 | border-top: none; 22 | } 23 | 24 | .selector-available h2, .selector-chosen h2 { 25 | border: 1px solid #ccc; 26 | border-radius: 4px 4px 0 0; 27 | } 28 | 29 | .selector-chosen h2 { 30 | background: #79aec8; 31 | color: #fff; 32 | } 33 | 34 | .selector .selector-available h2 { 35 | background: #f8f8f8; 36 | color: #666; 37 | } 38 | 39 | .selector .selector-filter { 40 | background: white; 41 | border: 1px solid #ccc; 42 | border-width: 0 1px; 43 | padding: 8px; 44 | color: #999; 45 | font-size: 10px; 46 | margin: 0; 47 | text-align: left; 48 | } 49 | 50 | .selector .selector-filter label, 51 | .inline-group .aligned .selector .selector-filter label { 52 | float: left; 53 | margin: 7px 0 0; 54 | width: 18px; 55 | height: 18px; 56 | padding: 0; 57 | overflow: hidden; 58 | line-height: 1; 59 | } 60 | 61 | .selector .selector-available input { 62 | width: 320px; 63 | margin-left: 8px; 64 | } 65 | 66 | .selector ul.selector-chooser { 67 | float: left; 68 | width: 22px; 69 | background-color: #eee; 70 | border-radius: 10px; 71 | margin: 10em 5px 0 5px; 72 | padding: 0; 73 | } 74 | 75 | .selector-chooser li { 76 | margin: 0; 77 | padding: 3px; 78 | list-style-type: none; 79 | } 80 | 81 | .selector select { 82 | padding: 0 10px; 83 | margin: 0 0 10px; 84 | border-radius: 0 0 4px 4px; 85 | } 86 | 87 | .selector-add, .selector-remove { 88 | width: 16px; 89 | height: 16px; 90 | display: block; 91 | text-indent: -3000px; 92 | overflow: hidden; 93 | cursor: default; 94 | opacity: 0.3; 95 | } 96 | 97 | .active.selector-add, .active.selector-remove { 98 | opacity: 1; 99 | } 100 | 101 | .active.selector-add:hover, .active.selector-remove:hover { 102 | cursor: pointer; 103 | } 104 | 105 | .selector-add { 106 | background: url(../img/selector-icons.svg) 0 -96px no-repeat; 107 | } 108 | 109 | .active.selector-add:focus, .active.selector-add:hover { 110 | background-position: 0 -112px; 111 | } 112 | 113 | .selector-remove { 114 | background: url(../img/selector-icons.svg) 0 -64px no-repeat; 115 | } 116 | 117 | .active.selector-remove:focus, .active.selector-remove:hover { 118 | background-position: 0 -80px; 119 | } 120 | 121 | a.selector-chooseall, a.selector-clearall { 122 | display: inline-block; 123 | height: 16px; 124 | text-align: left; 125 | margin: 1px auto 3px; 126 | overflow: hidden; 127 | font-weight: bold; 128 | line-height: 16px; 129 | color: #666; 130 | text-decoration: none; 131 | opacity: 0.3; 132 | } 133 | 134 | a.active.selector-chooseall:focus, a.active.selector-clearall:focus, 135 | a.active.selector-chooseall:hover, a.active.selector-clearall:hover { 136 | color: #447e9b; 137 | } 138 | 139 | a.active.selector-chooseall, a.active.selector-clearall { 140 | opacity: 1; 141 | } 142 | 143 | a.active.selector-chooseall:hover, a.active.selector-clearall:hover { 144 | cursor: pointer; 145 | } 146 | 147 | a.selector-chooseall { 148 | padding: 0 18px 0 0; 149 | background: url(../img/selector-icons.svg) right -160px no-repeat; 150 | cursor: default; 151 | } 152 | 153 | a.active.selector-chooseall:focus, a.active.selector-chooseall:hover { 154 | background-position: 100% -176px; 155 | } 156 | 157 | a.selector-clearall { 158 | padding: 0 0 0 18px; 159 | background: url(../img/selector-icons.svg) 0 -128px no-repeat; 160 | cursor: default; 161 | } 162 | 163 | a.active.selector-clearall:focus, a.active.selector-clearall:hover { 164 | background-position: 0 -144px; 165 | } 166 | 167 | /* STACKED SELECTORS */ 168 | 169 | .stacked { 170 | float: left; 171 | width: 490px; 172 | } 173 | 174 | .stacked select { 175 | width: 480px; 176 | height: 10.1em; 177 | } 178 | 179 | .stacked .selector-available, .stacked .selector-chosen { 180 | width: 480px; 181 | } 182 | 183 | .stacked .selector-available { 184 | margin-bottom: 0; 185 | } 186 | 187 | .stacked .selector-available input { 188 | width: 422px; 189 | } 190 | 191 | .stacked ul.selector-chooser { 192 | height: 22px; 193 | width: 50px; 194 | margin: 0 0 10px 40%; 195 | background-color: #eee; 196 | border-radius: 10px; 197 | } 198 | 199 | .stacked .selector-chooser li { 200 | float: left; 201 | padding: 3px 3px 3px 5px; 202 | } 203 | 204 | .stacked .selector-chooseall, .stacked .selector-clearall { 205 | display: none; 206 | } 207 | 208 | .stacked .selector-add { 209 | background: url(../img/selector-icons.svg) 0 -32px no-repeat; 210 | cursor: default; 211 | } 212 | 213 | .stacked .active.selector-add { 214 | background-position: 0 -48px; 215 | cursor: pointer; 216 | } 217 | 218 | .stacked .selector-remove { 219 | background: url(../img/selector-icons.svg) 0 0 no-repeat; 220 | cursor: default; 221 | } 222 | 223 | .stacked .active.selector-remove { 224 | background-position: 0 -16px; 225 | cursor: pointer; 226 | } 227 | 228 | .selector .help-icon { 229 | background: url(../img/icon-unknown.svg) 0 0 no-repeat; 230 | display: inline-block; 231 | vertical-align: middle; 232 | margin: -2px 0 0 2px; 233 | width: 13px; 234 | height: 13px; 235 | } 236 | 237 | .selector .selector-chosen .help-icon { 238 | background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat; 239 | } 240 | 241 | .selector .search-label-icon { 242 | background: url(../img/search.svg) 0 0 no-repeat; 243 | display: inline-block; 244 | height: 18px; 245 | width: 18px; 246 | } 247 | 248 | /* DATE AND TIME */ 249 | 250 | p.datetime { 251 | line-height: 20px; 252 | margin: 0; 253 | padding: 0; 254 | color: #666; 255 | font-weight: bold; 256 | } 257 | 258 | .datetime span { 259 | white-space: nowrap; 260 | font-weight: normal; 261 | font-size: 11px; 262 | color: #ccc; 263 | } 264 | 265 | .datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField { 266 | min-width: 0; 267 | margin-left: 5px; 268 | margin-bottom: 4px; 269 | } 270 | 271 | table p.datetime { 272 | font-size: 11px; 273 | margin-left: 0; 274 | padding-left: 0; 275 | } 276 | 277 | .datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon { 278 | position: relative; 279 | display: inline-block; 280 | vertical-align: middle; 281 | height: 16px; 282 | width: 16px; 283 | overflow: hidden; 284 | } 285 | 286 | .datetimeshortcuts .clock-icon { 287 | background: url(../img/icon-clock.svg) 0 0 no-repeat; 288 | } 289 | 290 | .datetimeshortcuts a:focus .clock-icon, 291 | .datetimeshortcuts a:hover .clock-icon { 292 | background-position: 0 -16px; 293 | } 294 | 295 | .datetimeshortcuts .date-icon { 296 | background: url(../img/icon-calendar.svg) 0 0 no-repeat; 297 | top: -1px; 298 | } 299 | 300 | .datetimeshortcuts a:focus .date-icon, 301 | .datetimeshortcuts a:hover .date-icon { 302 | background-position: 0 -16px; 303 | } 304 | 305 | .timezonewarning { 306 | font-size: 11px; 307 | color: #999; 308 | } 309 | 310 | /* URL */ 311 | 312 | p.url { 313 | line-height: 20px; 314 | margin: 0; 315 | padding: 0; 316 | color: #666; 317 | font-size: 11px; 318 | font-weight: bold; 319 | } 320 | 321 | .url a { 322 | font-weight: normal; 323 | } 324 | 325 | /* FILE UPLOADS */ 326 | 327 | p.file-upload { 328 | line-height: 20px; 329 | margin: 0; 330 | padding: 0; 331 | color: #666; 332 | font-size: 11px; 333 | font-weight: bold; 334 | } 335 | 336 | .aligned p.file-upload { 337 | margin-left: 170px; 338 | } 339 | 340 | .file-upload a { 341 | font-weight: normal; 342 | } 343 | 344 | .file-upload .deletelink { 345 | margin-left: 5px; 346 | } 347 | 348 | span.clearable-file-input label { 349 | color: #333; 350 | font-size: 11px; 351 | display: inline; 352 | float: none; 353 | } 354 | 355 | /* CALENDARS & CLOCKS */ 356 | 357 | .calendarbox, .clockbox { 358 | margin: 5px auto; 359 | font-size: 12px; 360 | width: 19em; 361 | text-align: center; 362 | background: white; 363 | border: 1px solid #ddd; 364 | border-radius: 4px; 365 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); 366 | overflow: hidden; 367 | position: relative; 368 | } 369 | 370 | .clockbox { 371 | width: auto; 372 | } 373 | 374 | .calendar { 375 | margin: 0; 376 | padding: 0; 377 | } 378 | 379 | .calendar table { 380 | margin: 0; 381 | padding: 0; 382 | border-collapse: collapse; 383 | background: white; 384 | width: 100%; 385 | } 386 | 387 | .calendar caption, .calendarbox h2 { 388 | margin: 0; 389 | text-align: center; 390 | border-top: none; 391 | background: #f5dd5d; 392 | font-weight: 700; 393 | font-size: 12px; 394 | color: #333; 395 | } 396 | 397 | .calendar th { 398 | padding: 8px 5px; 399 | background: #f8f8f8; 400 | border-bottom: 1px solid #ddd; 401 | font-weight: 400; 402 | font-size: 12px; 403 | text-align: center; 404 | color: #666; 405 | } 406 | 407 | .calendar td { 408 | font-weight: 400; 409 | font-size: 12px; 410 | text-align: center; 411 | padding: 0; 412 | border-top: 1px solid #eee; 413 | border-bottom: none; 414 | } 415 | 416 | .calendar td.selected a { 417 | background: #79aec8; 418 | color: #fff; 419 | } 420 | 421 | .calendar td.nonday { 422 | background: #f8f8f8; 423 | } 424 | 425 | .calendar td.today a { 426 | font-weight: 700; 427 | } 428 | 429 | .calendar td a, .timelist a { 430 | display: block; 431 | font-weight: 400; 432 | padding: 6px; 433 | text-decoration: none; 434 | color: #444; 435 | } 436 | 437 | .calendar td a:focus, .timelist a:focus, 438 | .calendar td a:hover, .timelist a:hover { 439 | background: #79aec8; 440 | color: white; 441 | } 442 | 443 | .calendar td a:active, .timelist a:active { 444 | background: #417690; 445 | color: white; 446 | } 447 | 448 | .calendarnav { 449 | font-size: 10px; 450 | text-align: center; 451 | color: #ccc; 452 | margin: 0; 453 | padding: 1px 3px; 454 | } 455 | 456 | .calendarnav a:link, #calendarnav a:visited, 457 | #calendarnav a:focus, #calendarnav a:hover { 458 | color: #999; 459 | } 460 | 461 | .calendar-shortcuts { 462 | background: white; 463 | font-size: 11px; 464 | line-height: 11px; 465 | border-top: 1px solid #eee; 466 | padding: 8px 0; 467 | color: #ccc; 468 | } 469 | 470 | .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { 471 | display: block; 472 | position: absolute; 473 | top: 8px; 474 | width: 15px; 475 | height: 15px; 476 | text-indent: -9999px; 477 | padding: 0; 478 | } 479 | 480 | .calendarnav-previous { 481 | left: 10px; 482 | background: url(../img/calendar-icons.svg) 0 0 no-repeat; 483 | } 484 | 485 | .calendarbox .calendarnav-previous:focus, 486 | .calendarbox .calendarnav-previous:hover { 487 | background-position: 0 -15px; 488 | } 489 | 490 | .calendarnav-next { 491 | right: 10px; 492 | background: url(../img/calendar-icons.svg) 0 -30px no-repeat; 493 | } 494 | 495 | .calendarbox .calendarnav-next:focus, 496 | .calendarbox .calendarnav-next:hover { 497 | background-position: 0 -45px; 498 | } 499 | 500 | .calendar-cancel { 501 | margin: 0; 502 | padding: 4px 0; 503 | font-size: 12px; 504 | background: #eee; 505 | border-top: 1px solid #ddd; 506 | color: #333; 507 | } 508 | 509 | .calendar-cancel:focus, .calendar-cancel:hover { 510 | background: #ddd; 511 | } 512 | 513 | .calendar-cancel a { 514 | color: black; 515 | display: block; 516 | } 517 | 518 | ul.timelist, .timelist li { 519 | list-style-type: none; 520 | margin: 0; 521 | padding: 0; 522 | } 523 | 524 | .timelist a { 525 | padding: 2px; 526 | } 527 | 528 | /* EDIT INLINE */ 529 | 530 | .inline-deletelink { 531 | float: right; 532 | text-indent: -9999px; 533 | background: url(../img/inline-delete.svg) 0 0 no-repeat; 534 | width: 16px; 535 | height: 16px; 536 | border: 0px none; 537 | } 538 | 539 | .inline-deletelink:focus, .inline-deletelink:hover { 540 | cursor: pointer; 541 | } 542 | 543 | /* RELATED WIDGET WRAPPER */ 544 | .related-widget-wrapper { 545 | float: left; /* display properly in form rows with multiple fields */ 546 | overflow: hidden; /* clear floated contents */ 547 | } 548 | 549 | .related-widget-wrapper-link { 550 | opacity: 0.3; 551 | } 552 | 553 | .related-widget-wrapper-link:link { 554 | opacity: .8; 555 | } 556 | 557 | .related-widget-wrapper-link:link:focus, 558 | .related-widget-wrapper-link:link:hover { 559 | opacity: 1; 560 | } 561 | 562 | select + .related-widget-wrapper-link, 563 | .related-widget-wrapper-link + .related-widget-wrapper-link { 564 | margin-left: 7px; 565 | } 566 | -------------------------------------------------------------------------------- /static/admin/js/SelectFilter2.js: -------------------------------------------------------------------------------- 1 | /*global SelectBox, addEvent, gettext, interpolate, quickElement, SelectFilter*/ 2 | /* 3 | SelectFilter2 - Turns a multiple-select box into a filter interface. 4 | 5 | Requires jQuery, core.js, and SelectBox.js. 6 | */ 7 | (function($) { 8 | 'use strict'; 9 | function findForm(node) { 10 | // returns the node of the form containing the given node 11 | if (node.tagName.toLowerCase() !== 'form') { 12 | return findForm(node.parentNode); 13 | } 14 | return node; 15 | } 16 | 17 | window.SelectFilter = { 18 | init: function(field_id, field_name, is_stacked) { 19 | if (field_id.match(/__prefix__/)) { 20 | // Don't initialize on empty forms. 21 | return; 22 | } 23 | var from_box = document.getElementById(field_id); 24 | from_box.id += '_from'; // change its ID 25 | from_box.className = 'filtered'; 26 | 27 | var ps = from_box.parentNode.getElementsByTagName('p'); 28 | for (var i = 0; i < ps.length; i++) { 29 | if (ps[i].className.indexOf("info") !== -1) { 30 | // Remove

    , because it just gets in the way. 31 | from_box.parentNode.removeChild(ps[i]); 32 | } else if (ps[i].className.indexOf("help") !== -1) { 33 | // Move help text up to the top so it isn't below the select 34 | // boxes or wrapped off on the side to the right of the add 35 | // button: 36 | from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild); 37 | } 38 | } 39 | 40 | //

    or
    41 | var selector_div = quickElement('div', from_box.parentNode); 42 | selector_div.className = is_stacked ? 'selector stacked' : 'selector'; 43 | 44 | //
    45 | var selector_available = quickElement('div', selector_div); 46 | selector_available.className = 'selector-available'; 47 | var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name])); 48 | quickElement( 49 | 'span', title_available, '', 50 | 'class', 'help help-tooltip help-icon', 51 | 'title', interpolate( 52 | gettext( 53 | 'This is the list of available %s. You may choose some by ' + 54 | 'selecting them in the box below and then clicking the ' + 55 | '"Choose" arrow between the two boxes.' 56 | ), 57 | [field_name] 58 | ) 59 | ); 60 | 61 | var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter'); 62 | filter_p.className = 'selector-filter'; 63 | 64 | var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input'); 65 | 66 | quickElement( 67 | 'span', search_filter_label, '', 68 | 'class', 'help-tooltip search-label-icon', 69 | 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name]) 70 | ); 71 | 72 | filter_p.appendChild(document.createTextNode(' ')); 73 | 74 | var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter")); 75 | filter_input.id = field_id + '_input'; 76 | 77 | selector_available.appendChild(from_box); 78 | var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_add_all_link'); 79 | choose_all.className = 'selector-chooseall'; 80 | 81 | //
      82 | var selector_chooser = quickElement('ul', selector_div); 83 | selector_chooser.className = 'selector-chooser'; 84 | var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link'); 85 | add_link.className = 'selector-add'; 86 | var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link'); 87 | remove_link.className = 'selector-remove'; 88 | 89 | //
      90 | var selector_chosen = quickElement('div', selector_div); 91 | selector_chosen.className = 'selector-chosen'; 92 | var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name])); 93 | quickElement( 94 | 'span', title_chosen, '', 95 | 'class', 'help help-tooltip help-icon', 96 | 'title', interpolate( 97 | gettext( 98 | 'This is the list of chosen %s. You may remove some by ' + 99 | 'selecting them in the box below and then clicking the ' + 100 | '"Remove" arrow between the two boxes.' 101 | ), 102 | [field_name] 103 | ) 104 | ); 105 | 106 | var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); 107 | to_box.className = 'filtered'; 108 | var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link'); 109 | clear_all.className = 'selector-clearall'; 110 | 111 | from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); 112 | 113 | // Set up the JavaScript event handlers for the select box filter interface 114 | var move_selection = function(e, elem, move_func, from, to) { 115 | if (elem.className.indexOf('active') !== -1) { 116 | move_func(from, to); 117 | SelectFilter.refresh_icons(field_id); 118 | } 119 | e.preventDefault(); 120 | }; 121 | addEvent(choose_all, 'click', function(e) { move_selection(e, this, SelectBox.move_all, field_id + '_from', field_id + '_to'); }); 122 | addEvent(add_link, 'click', function(e) { move_selection(e, this, SelectBox.move, field_id + '_from', field_id + '_to'); }); 123 | addEvent(remove_link, 'click', function(e) { move_selection(e, this, SelectBox.move, field_id + '_to', field_id + '_from'); }); 124 | addEvent(clear_all, 'click', function(e) { move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from'); }); 125 | addEvent(filter_input, 'keypress', function(e) { SelectFilter.filter_key_press(e, field_id); }); 126 | addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); 127 | addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); 128 | addEvent(selector_div, 'change', function(e) { 129 | if (e.target.tagName === 'SELECT') { 130 | SelectFilter.refresh_icons(field_id); 131 | } 132 | }); 133 | addEvent(selector_div, 'dblclick', function(e) { 134 | if (e.target.tagName === 'OPTION') { 135 | if (e.target.closest('select').id === field_id + '_to') { 136 | SelectBox.move(field_id + '_to', field_id + '_from'); 137 | } else { 138 | SelectBox.move(field_id + '_from', field_id + '_to'); 139 | } 140 | SelectFilter.refresh_icons(field_id); 141 | } 142 | }); 143 | addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); 144 | SelectBox.init(field_id + '_from'); 145 | SelectBox.init(field_id + '_to'); 146 | // Move selected from_box options to to_box 147 | SelectBox.move(field_id + '_from', field_id + '_to'); 148 | 149 | if (!is_stacked) { 150 | // In horizontal mode, give the same height to the two boxes. 151 | var j_from_box = $(from_box); 152 | var j_to_box = $(to_box); 153 | var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); }; 154 | if (j_from_box.outerHeight() > 0) { 155 | resize_filters(); // This fieldset is already open. Resize now. 156 | } else { 157 | // This fieldset is probably collapsed. Wait for its 'show' event. 158 | j_to_box.closest('fieldset').one('show.fieldset', resize_filters); 159 | } 160 | } 161 | 162 | // Initial icon refresh 163 | SelectFilter.refresh_icons(field_id); 164 | }, 165 | any_selected: function(field) { 166 | var any_selected = false; 167 | try { 168 | // Temporarily add the required attribute and check validity. 169 | // This is much faster in WebKit browsers than the fallback. 170 | field.attr('required', 'required'); 171 | any_selected = field.is(':valid'); 172 | field.removeAttr('required'); 173 | } catch (e) { 174 | // Browsers that don't support :valid (IE < 10) 175 | any_selected = field.find('option:selected').length > 0; 176 | } 177 | return any_selected; 178 | }, 179 | refresh_icons: function(field_id) { 180 | var from = $('#' + field_id + '_from'); 181 | var to = $('#' + field_id + '_to'); 182 | // Active if at least one item is selected 183 | $('#' + field_id + '_add_link').toggleClass('active', SelectFilter.any_selected(from)); 184 | $('#' + field_id + '_remove_link').toggleClass('active', SelectFilter.any_selected(to)); 185 | // Active if the corresponding box isn't empty 186 | $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0); 187 | $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0); 188 | }, 189 | filter_key_press: function(event, field_id) { 190 | var from = document.getElementById(field_id + '_from'); 191 | // don't submit form if user pressed Enter 192 | if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) { 193 | from.selectedIndex = 0; 194 | SelectBox.move(field_id + '_from', field_id + '_to'); 195 | from.selectedIndex = 0; 196 | event.preventDefault(); 197 | return false; 198 | } 199 | }, 200 | filter_key_up: function(event, field_id) { 201 | var from = document.getElementById(field_id + '_from'); 202 | var temp = from.selectedIndex; 203 | SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); 204 | from.selectedIndex = temp; 205 | return true; 206 | }, 207 | filter_key_down: function(event, field_id) { 208 | var from = document.getElementById(field_id + '_from'); 209 | // right arrow -- move across 210 | if ((event.which && event.which === 39) || (event.keyCode && event.keyCode === 39)) { 211 | var old_index = from.selectedIndex; 212 | SelectBox.move(field_id + '_from', field_id + '_to'); 213 | from.selectedIndex = (old_index === from.length) ? from.length - 1 : old_index; 214 | return false; 215 | } 216 | // down arrow -- wrap around 217 | if ((event.which && event.which === 40) || (event.keyCode && event.keyCode === 40)) { 218 | from.selectedIndex = (from.length === from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; 219 | } 220 | // up arrow -- wrap around 221 | if ((event.which && event.which === 38) || (event.keyCode && event.keyCode === 38)) { 222 | from.selectedIndex = (from.selectedIndex === 0) ? from.length - 1 : from.selectedIndex - 1; 223 | } 224 | return true; 225 | } 226 | }; 227 | 228 | addEvent(window, 'load', function(e) { 229 | $('select.selectfilter, select.selectfilterstacked').each(function() { 230 | var $el = $(this), 231 | data = $el.data(); 232 | SelectFilter.init($el.attr('id'), data.fieldName, parseInt(data.isStacked, 10)); 233 | }); 234 | }); 235 | 236 | })(django.jQuery); 237 | -------------------------------------------------------------------------------- /static/admin/js/inlines.js: -------------------------------------------------------------------------------- 1 | /*global DateTimeShortcuts, SelectFilter*/ 2 | /** 3 | * Django admin inlines 4 | * 5 | * Based on jQuery Formset 1.1 6 | * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) 7 | * @requires jQuery 1.2.6 or later 8 | * 9 | * Copyright (c) 2009, Stanislaus Madueke 10 | * All rights reserved. 11 | * 12 | * Spiced up with Code from Zain Memon's GSoC project 2009 13 | * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. 14 | * 15 | * Licensed under the New BSD License 16 | * See: http://www.opensource.org/licenses/bsd-license.php 17 | */ 18 | (function($) { 19 | 'use strict'; 20 | $.fn.formset = function(opts) { 21 | var options = $.extend({}, $.fn.formset.defaults, opts); 22 | var $this = $(this); 23 | var $parent = $this.parent(); 24 | var updateElementIndex = function(el, prefix, ndx) { 25 | var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); 26 | var replacement = prefix + "-" + ndx; 27 | if ($(el).prop("for")) { 28 | $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); 29 | } 30 | if (el.id) { 31 | el.id = el.id.replace(id_regex, replacement); 32 | } 33 | if (el.name) { 34 | el.name = el.name.replace(id_regex, replacement); 35 | } 36 | }; 37 | var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); 38 | var nextIndex = parseInt(totalForms.val(), 10); 39 | var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); 40 | // only show the add button if we are allowed to add more items, 41 | // note that max_num = None translates to a blank string. 42 | var showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0; 43 | $this.each(function(i) { 44 | $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); 45 | }); 46 | if ($this.length && showAddButton) { 47 | var addButton = options.addButton; 48 | if (addButton === null) { 49 | if ($this.prop("tagName") === "TR") { 50 | // If forms are laid out as table rows, insert the 51 | // "add" button in a new table row: 52 | var numCols = this.eq(-1).children().length; 53 | $parent.append('' + options.addText + ""); 54 | addButton = $parent.find("tr:last a"); 55 | } else { 56 | // Otherwise, insert it immediately after the last form: 57 | $this.filter(":last").after('"); 58 | addButton = $this.filter(":last").next().find("a"); 59 | } 60 | } 61 | addButton.click(function(e) { 62 | e.preventDefault(); 63 | var template = $("#" + options.prefix + "-empty"); 64 | var row = template.clone(true); 65 | row.removeClass(options.emptyCssClass) 66 | .addClass(options.formCssClass) 67 | .attr("id", options.prefix + "-" + nextIndex); 68 | if (row.is("tr")) { 69 | // If the forms are laid out in table rows, insert 70 | // the remove button into the last table cell: 71 | row.children(":last").append('"); 72 | } else if (row.is("ul") || row.is("ol")) { 73 | // If they're laid out as an ordered/unordered list, 74 | // insert an
    • after the last list item: 75 | row.append('
    • ' + options.deleteText + "
    • "); 76 | } else { 77 | // Otherwise, just insert the remove button as the 78 | // last child element of the form's container: 79 | row.children(":first").append('' + options.deleteText + ""); 80 | } 81 | row.find("*").each(function() { 82 | updateElementIndex(this, options.prefix, totalForms.val()); 83 | }); 84 | // Insert the new form when it has been fully edited 85 | row.insertBefore($(template)); 86 | // Update number of total forms 87 | $(totalForms).val(parseInt(totalForms.val(), 10) + 1); 88 | nextIndex += 1; 89 | // Hide add button in case we've hit the max, except we want to add infinitely 90 | if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) { 91 | addButton.parent().hide(); 92 | } 93 | // The delete button of each row triggers a bunch of other things 94 | row.find("a." + options.deleteCssClass).click(function(e1) { 95 | e1.preventDefault(); 96 | // Remove the parent form containing this button: 97 | row.remove(); 98 | nextIndex -= 1; 99 | // If a post-delete callback was provided, call it with the deleted form: 100 | if (options.removed) { 101 | options.removed(row); 102 | } 103 | $(document).trigger('formset:removed', [row, options.prefix]); 104 | // Update the TOTAL_FORMS form count. 105 | var forms = $("." + options.formCssClass); 106 | $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); 107 | // Show add button again once we drop below max 108 | if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) { 109 | addButton.parent().show(); 110 | } 111 | // Also, update names and ids for all remaining form controls 112 | // so they remain in sequence: 113 | var i, formCount; 114 | var updateElementCallback = function() { 115 | updateElementIndex(this, options.prefix, i); 116 | }; 117 | for (i = 0, formCount = forms.length; i < formCount; i++) { 118 | updateElementIndex($(forms).get(i), options.prefix, i); 119 | $(forms.get(i)).find("*").each(updateElementCallback); 120 | } 121 | }); 122 | // If a post-add callback was supplied, call it with the added form: 123 | if (options.added) { 124 | options.added(row); 125 | } 126 | $(document).trigger('formset:added', [row, options.prefix]); 127 | }); 128 | } 129 | return this; 130 | }; 131 | 132 | /* Setup plugin defaults */ 133 | $.fn.formset.defaults = { 134 | prefix: "form", // The form prefix for your django formset 135 | addText: "add another", // Text for the add link 136 | deleteText: "remove", // Text for the delete link 137 | addCssClass: "add-row", // CSS class applied to the add link 138 | deleteCssClass: "delete-row", // CSS class applied to the delete link 139 | emptyCssClass: "empty-row", // CSS class applied to the empty row 140 | formCssClass: "dynamic-form", // CSS class applied to each form in a formset 141 | added: null, // Function called each time a new form is added 142 | removed: null, // Function called each time a form is deleted 143 | addButton: null // Existing add button to use 144 | }; 145 | 146 | 147 | // Tabular inlines --------------------------------------------------------- 148 | $.fn.tabularFormset = function(options) { 149 | var $rows = $(this); 150 | var alternatingRows = function(row) { 151 | $($rows.selector).not(".add-row").removeClass("row1 row2") 152 | .filter(":even").addClass("row1").end() 153 | .filter(":odd").addClass("row2"); 154 | }; 155 | 156 | var reinitDateTimeShortCuts = function() { 157 | // Reinitialize the calendar and clock widgets by force 158 | if (typeof DateTimeShortcuts !== "undefined") { 159 | $(".datetimeshortcuts").remove(); 160 | DateTimeShortcuts.init(); 161 | } 162 | }; 163 | 164 | var updateSelectFilter = function() { 165 | // If any SelectFilter widgets are a part of the new form, 166 | // instantiate a new SelectFilter instance for it. 167 | if (typeof SelectFilter !== 'undefined') { 168 | $('.selectfilter').each(function(index, value) { 169 | var namearr = value.name.split('-'); 170 | SelectFilter.init(value.id, namearr[namearr.length - 1], false); 171 | }); 172 | $('.selectfilterstacked').each(function(index, value) { 173 | var namearr = value.name.split('-'); 174 | SelectFilter.init(value.id, namearr[namearr.length - 1], true); 175 | }); 176 | } 177 | }; 178 | 179 | var initPrepopulatedFields = function(row) { 180 | row.find('.prepopulated_field').each(function() { 181 | var field = $(this), 182 | input = field.find('input, select, textarea'), 183 | dependency_list = input.data('dependency_list') || [], 184 | dependencies = []; 185 | $.each(dependency_list, function(i, field_name) { 186 | dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id')); 187 | }); 188 | if (dependencies.length) { 189 | input.prepopulate(dependencies, input.attr('maxlength')); 190 | } 191 | }); 192 | }; 193 | 194 | $rows.formset({ 195 | prefix: options.prefix, 196 | addText: options.addText, 197 | formCssClass: "dynamic-" + options.prefix, 198 | deleteCssClass: "inline-deletelink", 199 | deleteText: options.deleteText, 200 | emptyCssClass: "empty-form", 201 | removed: alternatingRows, 202 | added: function(row) { 203 | initPrepopulatedFields(row); 204 | reinitDateTimeShortCuts(); 205 | updateSelectFilter(); 206 | alternatingRows(row); 207 | }, 208 | addButton: options.addButton 209 | }); 210 | 211 | return $rows; 212 | }; 213 | 214 | // Stacked inlines --------------------------------------------------------- 215 | $.fn.stackedFormset = function(options) { 216 | var $rows = $(this); 217 | var updateInlineLabel = function(row) { 218 | $($rows.selector).find(".inline_label").each(function(i) { 219 | var count = i + 1; 220 | $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); 221 | }); 222 | }; 223 | 224 | var reinitDateTimeShortCuts = function() { 225 | // Reinitialize the calendar and clock widgets by force, yuck. 226 | if (typeof DateTimeShortcuts !== "undefined") { 227 | $(".datetimeshortcuts").remove(); 228 | DateTimeShortcuts.init(); 229 | } 230 | }; 231 | 232 | var updateSelectFilter = function() { 233 | // If any SelectFilter widgets were added, instantiate a new instance. 234 | if (typeof SelectFilter !== "undefined") { 235 | $(".selectfilter").each(function(index, value) { 236 | var namearr = value.name.split('-'); 237 | SelectFilter.init(value.id, namearr[namearr.length - 1], false); 238 | }); 239 | $(".selectfilterstacked").each(function(index, value) { 240 | var namearr = value.name.split('-'); 241 | SelectFilter.init(value.id, namearr[namearr.length - 1], true); 242 | }); 243 | } 244 | }; 245 | 246 | var initPrepopulatedFields = function(row) { 247 | row.find('.prepopulated_field').each(function() { 248 | var field = $(this), 249 | input = field.find('input, select, textarea'), 250 | dependency_list = input.data('dependency_list') || [], 251 | dependencies = []; 252 | $.each(dependency_list, function(i, field_name) { 253 | dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id')); 254 | }); 255 | if (dependencies.length) { 256 | input.prepopulate(dependencies, input.attr('maxlength')); 257 | } 258 | }); 259 | }; 260 | 261 | $rows.formset({ 262 | prefix: options.prefix, 263 | addText: options.addText, 264 | formCssClass: "dynamic-" + options.prefix, 265 | deleteCssClass: "inline-deletelink", 266 | deleteText: options.deleteText, 267 | emptyCssClass: "empty-form", 268 | removed: updateInlineLabel, 269 | added: function(row) { 270 | initPrepopulatedFields(row); 271 | reinitDateTimeShortCuts(); 272 | updateSelectFilter(); 273 | updateInlineLabel(row); 274 | }, 275 | addButton: options.addButton 276 | }); 277 | 278 | return $rows; 279 | }; 280 | 281 | $(document).ready(function() { 282 | $(".js-inline-admin-formset").each(function() { 283 | var data = $(this).data(), 284 | inlineOptions = data.inlineFormset; 285 | switch(data.inlineType) { 286 | case "stacked": 287 | $(inlineOptions.name + "-group .inline-related").stackedFormset(inlineOptions.options); 288 | break; 289 | case "tabular": 290 | $(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularFormset(inlineOptions.options); 291 | break; 292 | } 293 | }); 294 | }); 295 | })(django.jQuery); 296 | --------------------------------------------------------------------------------