From c9f0d14c09571cb2cf5b0f5a08fd16e97c33eda4 Mon Sep 17 00:00:00 2001 From: Matthias BUSSONNIER Date: Wed, 7 May 2014 17:59:37 +0200 Subject: [PATCH 001/316] unify visual line handling --- IPython/html/static/notebook/js/cell.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 032b413dc..5a481c55f 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -70,7 +70,12 @@ var IPython = (function (IPython) { cm_config : { indentUnit : 4, readOnly: false, - theme: "default" + theme: "default", + extraKeys: { + "Cmd-Right":"goLineRight", + "End":"goLineRight", + "Cmd-Left":"goLineLeft" + } } }; From fe37c14bdf2c0507a2aaf861c6d2be855632b85b Mon Sep 17 00:00:00 2001 From: watercrossing Date: Thu, 15 May 2014 14:35:59 +0100 Subject: [PATCH 002/316] add page-up and page-down functionality to the autocomplete dropdown --- IPython/html/static/notebook/js/completer.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index ecac3bd76..fda33d827 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -351,6 +351,18 @@ var IPython = (function (IPython) { } index = Math.min(Math.max(index, 0), options.length-1); this.sel[0].selectedIndex = index; + } else if (code == keycodes.pageup || code == keycodes.pagedown) { + CodeMirror.e_stop(event); + + var options = this.sel.find('option'); + var index = this.sel[0].selectedIndex; + if (code == keycodes.pageup) { + index -= 10; // As 10 is the hard coded size of the drop down menu + } else { + index += 10; + } + index = Math.min(Math.max(index, 0), options.length-1); + this.sel[0].selectedIndex = index; } else if (code == keycodes.left || code == keycodes.right) { this.close(); } From 1be538a94584ec8d572ee3460b6824c2235c9704 Mon Sep 17 00:00:00 2001 From: John Zwinck Date: Tue, 24 Jun 2014 15:09:51 +0800 Subject: [PATCH 003/316] Fix typo in docs --- docs/source/notebook/notebook.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/notebook/notebook.rst b/docs/source/notebook/notebook.rst index 6c7a85bf5..2eca0d135 100644 --- a/docs/source/notebook/notebook.rst +++ b/docs/source/notebook/notebook.rst @@ -64,7 +64,7 @@ colleagues. .. _JSON: http://en.wikipedia.org/wiki/JSON Notebooks may be exported to a range of static formats, including HTML (for -example, for blog posts), reStructeredText, LaTeX, PDF, and slide shows, via +example, for blog posts), reStructuredText, LaTeX, PDF, and slide shows, via the new :ref:`nbconvert ` command. Furthermore, any ``.ipynb`` notebook document available from a public From 9c2c680e8fbfe3b0a8b791423f54e98f07e3dbed Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 18 Jun 2014 11:00:59 -0400 Subject: [PATCH 004/316] Creating an entry point for notebook manager extensions - Firing app_initialized.DashboardApp event when loaded the notebook manager - Updating tree.html template to load nbextensions through custom.js --- IPython/html/static/custom/custom.js | 13 +++++++++++-- IPython/html/static/tree/js/main.js | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/IPython/html/static/custom/custom.js b/IPython/html/static/custom/custom.js index f8d1afee0..efa70e842 100644 --- a/IPython/html/static/custom/custom.js +++ b/IPython/html/static/custom/custom.js @@ -12,7 +12,7 @@ * * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook. * - * Example : + * __Example 1:__ * * Create a custom button in toolbar that execute `%qtconsole` in kernel * and hence open a qtconsole attached to the same kernel as the current notebook @@ -30,7 +30,16 @@ * ]); * }); * - * Example : + * __Example 2:__ + * + * At the completion of the dashboard loading, load an unofficial javascript extension + * that is installed in profile/static/custom/ + * + * $([IPython.events]).on('app_initialized.DashboardApp', function(){ + * require(['custom/unofficial_extension.js']) + * }); + * + * __Example 3:__ * * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` * to load custom script into the notebook. diff --git a/IPython/html/static/tree/js/main.js b/IPython/html/static/tree/js/main.js index 92e400d14..acd647dba 100644 --- a/IPython/html/static/tree/js/main.js +++ b/IPython/html/static/tree/js/main.js @@ -70,7 +70,8 @@ $(document).ready(function () { enable_autorefresh(); IPython.page.show(); - + $([IPython.events]).trigger('app_initialized.DashboardApp'); + // bound the upload method to the on change of the file select list $("#alternate_upload").change(function (event){ IPython.notebook_list.handleFilesUpload(event,'form'); From e64f689374771614fa8beaeb4e5388dda11afe9c Mon Sep 17 00:00:00 2001 From: MinRK Date: Tue, 24 Jun 2014 14:25:24 -0700 Subject: [PATCH 005/316] =?UTF-8?q?don=E2=80=99t=20import=20IPython.parall?= =?UTF-8?q?el=20until=20it=E2=80=99s=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in IPython.html.notebookapp --- .../html/services/clusters/clustermanager.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/IPython/html/services/clusters/clustermanager.py b/IPython/html/services/clusters/clustermanager.py index 66fb529ed..fecde707a 100644 --- a/IPython/html/services/clusters/clustermanager.py +++ b/IPython/html/services/clusters/clustermanager.py @@ -21,7 +21,6 @@ from zmq.eventloop import ioloop from IPython.config.configurable import LoggingConfigurable from IPython.utils.traitlets import Dict, Instance, CFloat -from IPython.parallel.apps.ipclusterapp import IPClusterStart from IPython.core.profileapp import list_profiles_in from IPython.core.profiledir import ProfileDir from IPython.utils import py3compat @@ -33,17 +32,6 @@ from IPython.utils.path import get_ipython_dir #----------------------------------------------------------------------------- -class DummyIPClusterStart(IPClusterStart): - """Dummy subclass to skip init steps that conflict with global app. - - Instantiating and initializing this class should result in fully configured - launchers, but no other side effects or state. - """ - - def init_signal(self): - pass - def reinit_logging(self): - pass class ClusterManager(LoggingConfigurable): @@ -59,6 +47,20 @@ class ClusterManager(LoggingConfigurable): return IOLoop.instance() def build_launchers(self, profile_dir): + from IPython.parallel.apps.ipclusterapp import IPClusterStart + + class DummyIPClusterStart(IPClusterStart): + """Dummy subclass to skip init steps that conflict with global app. + + Instantiating and initializing this class should result in fully configured + launchers, but no other side effects or state. + """ + + def init_signal(self): + pass + def reinit_logging(self): + pass + starter = DummyIPClusterStart(log=self.log) starter.initialize(['--profile-dir', profile_dir]) cl = starter.controller_launcher From de0a7d785068c77160480018d2d8557e64a4d388 Mon Sep 17 00:00:00 2001 From: MinRK Date: Tue, 1 Apr 2014 21:22:22 -0700 Subject: [PATCH 006/316] use utils.log.get_logger where appropriate --- IPython/html/base/handlers.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 56772f979..d8d107cf1 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -1,21 +1,7 @@ -"""Base Tornado handlers for the notebook. - -Authors: - -* Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +"""Base Tornado handlers for the notebook.""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import functools import json From 3178a184c0db13e8e9420030e9b77a564ccc0bcd Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Wed, 25 Jun 2014 17:51:55 +0200 Subject: [PATCH 007/316] Add initial implementation of 2-handle range sliders for integers. --- IPython/html/static/widgets/js/widget_int.js | 90 ++++++++++++-------- IPython/html/widgets/__init__.py | 2 +- IPython/html/widgets/interaction.py | 11 ++- IPython/html/widgets/widget_int.py | 38 ++++++++- 4 files changed, 98 insertions(+), 43 deletions(-) diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index f7a6c6eb3..0c609fd31 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -25,35 +25,35 @@ define(["widgets/js/widget"], function(WidgetManager){ .appendTo(this.$el) .addClass('widget-hlabel') .hide(); - + this.$slider = $('
') .slider({}) .addClass('slider'); - // Put the slider in a container + // Put the slider in a container this.$slider_container = $('
') .addClass('widget-hslider') .append(this.$slider); this.$el_to_style = this.$slider_container; // Set default element to style this.$el.append(this.$slider_container); - + this.$readout = $('
') .appendTo(this.$el) .addClass('widget-hreadout') .hide(); - + // Set defaults. this.update(); }, - + update : function(options){ // Update the contents of this view // - // Called when the model is changed. The model may have been + // Called when the model is changed. The model may have been // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { // JQuery slider option keys. These keys happen to have a // one-to-one mapping with the corrosponding keys of the model. - var jquery_slider_keys = ['step', 'max', 'min', 'disabled']; + var jquery_slider_keys = ['step', 'max', 'min', 'disabled', 'range']; var that = this; _.each(jquery_slider_keys, function(key, i) { var model_value = that.model.get(key); @@ -68,15 +68,25 @@ define(["widgets/js/widget"], function(WidgetManager){ // of orientation change. Before applying the new // workaround, we set the value to the minimum to // make sure that the horizontal placement of the - // handle in the vertical slider is always + // handle in the vertical slider is always // consistent. var orientation = this.model.get('orientation'); var value = this.model.get('min'); - this.$slider.slider('option', 'value', value); + if (this.model.get('range')) { + this.$slider.slider('option', 'values', [value, value]); + } else { + this.$slider.slider('option', 'value', value); + } this.$slider.slider('option', 'orientation', orientation); value = this.model.get('value'); - this.$slider.slider('option', 'value', value); - this.$readout.text(value); + if (this.model.get('range')) { + this.$slider.slider('option', 'values', value); + this.$readout.text(value.join("-")); + } else { + this.$slider.slider('option', 'value', value); + this.$readout.text(value); + } + // Use the right CSS classes for vertical & horizontal sliders if (orientation=='vertical') { @@ -116,7 +126,7 @@ define(["widgets/js/widget"], function(WidgetManager){ MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]); this.$label.show(); } - + var readout = this.model.get('readout'); if (readout) { this.$readout.show(); @@ -126,21 +136,27 @@ define(["widgets/js/widget"], function(WidgetManager){ } return IntSliderView.__super__.update.apply(this); }, - + events: { // Dictionary of events and their handlers. "slide" : "handleSliderChange" - }, + }, - handleSliderChange: function(e, ui) { + handleSliderChange: function(e, ui) { // Called when the slider value is changed. - // Calling model.set will trigger all of the other views of the + // Calling model.set will trigger all of the other views of the // model to update. - var actual_value = this._validate_slide_value(ui.value); + if (this.model.get("range")) { + var actual_value = ui.values.map(this._validate_slide_value); + this.$readout.text(actual_value.join("-")); + } else { + var actual_value = this._validate_slide_value(ui.value); + this.$readout.text(actual_value); + } this.model.set('value', actual_value, {updated_view: this}); - this.$readout.text(actual_value); this.touch(); + }, _validate_slide_value: function(x) { @@ -154,7 +170,7 @@ define(["widgets/js/widget"], function(WidgetManager){ WidgetManager.register_widget_view('IntSliderView', IntSliderView); - var IntTextView = IPython.DOMWidgetView.extend({ + var IntTextView = IPython.DOMWidgetView.extend({ render : function(){ // Called when view is rendered. this.$el @@ -170,18 +186,18 @@ define(["widgets/js/widget"], function(WidgetManager){ this.$el_to_style = this.$textbox; // Set default element to style this.update(); // Set defaults. }, - + update : function(options){ // Update the contents of this view // - // Called when the model is changed. The model may have been + // Called when the model is changed. The model may have been // changed by another view or by a state update from the back-end. if (options === undefined || options.updated_view != this) { var value = this.model.get('value'); if (this._parse_value(this.$textbox.val()) != value) { this.$textbox.val(value); } - + if (this.model.get('disabled')) { this.$textbox.attr('disabled','disabled'); } else { @@ -208,20 +224,20 @@ define(["widgets/js/widget"], function(WidgetManager){ // Fires only when control is validated or looses focus. "change input" : "handleChanged" - }, - - handleChanging: function(e) { + }, + + handleChanging: function(e) { // Handles and validates user input. - + // Try to parse value as a int. var numericalValue = 0; if (e.target.value !== '') { var trimmed = e.target.value.trim(); if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) { - numericalValue = this._parse_value(e.target.value); - } + numericalValue = this._parse_value(e.target.value); + } } - + // If parse failed, reset value to value stored in model. if (isNaN(numericalValue)) { e.target.value = this.model.get('value'); @@ -232,18 +248,18 @@ define(["widgets/js/widget"], function(WidgetManager){ if (this.model.get('min') !== undefined) { numericalValue = Math.max(this.model.get('min'), numericalValue); } - + // Apply the value if it has changed. if (numericalValue != this.model.get('value')) { - - // Calling model.set will trigger all of the other views of the + + // Calling model.set will trigger all of the other views of the // model to update. this.model.set('value', numericalValue, {updated_view: this}); this.touch(); } } }, - + handleChanged: function(e) { // Applies validated input. if (this.model.get('value') != e.target.value) { @@ -279,18 +295,18 @@ define(["widgets/js/widget"], function(WidgetManager){ .appendTo(this.$progress); this.update(); // Set defaults. }, - + update : function(){ // Update the contents of this view // - // Called when the model is changed. The model may have been + // Called when the model is changed. The model may have been // changed by another view or by a state update from the back-end. var value = this.model.get('value'); var max = this.model.get('max'); var min = this.model.get('min'); var percent = 100.0 * (value - min) / (max - min); this.$bar.css('width', percent + '%'); - + var description = this.model.get('description'); if (description.length === 0) { this.$label.hide(); @@ -300,7 +316,7 @@ define(["widgets/js/widget"], function(WidgetManager){ this.$label.show(); } return ProgressView.__super__.update.apply(this); - }, + }, }); WidgetManager.register_widget_view('ProgressView', ProgressView); diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py index 7b6d0a836..803e28e5a 100644 --- a/IPython/html/widgets/__init__.py +++ b/IPython/html/widgets/__init__.py @@ -5,7 +5,7 @@ from .widget_button import ButtonWidget from .widget_container import ContainerWidget, PopupWidget from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget from .widget_image import ImageWidget -from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget +from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget, IntRangeSliderWidget from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget from .widget_selectioncontainer import TabWidget, AccordionWidget from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index 1b59de88e..637868251 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -23,7 +23,7 @@ from inspect import getcallargs from IPython.core.getipython import get_ipython from IPython.html.widgets import (Widget, TextWidget, FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget, - ContainerWidget, DOMWidget) + ContainerWidget, DOMWidget, IntRangeSliderWidget) from IPython.display import display, clear_output from IPython.utils.py3compat import string_types, unicode_type from IPython.utils.traitlets import HasTraits, Any, Unicode @@ -107,6 +107,15 @@ def _widget_abbrev(o): else: cls = FloatSliderWidget return cls(value=value, min=min, max=max, step=step) + elif _matches(o, [float_or_int]*4): + min, low, high, max = o + if not min <= low <= high <= max: + raise ValueError("Range input expects min <= low <= high <= max, got %r" % o) + if all(isinstance(_, int) for _ in o): + cls = IntRangeSliderWidget + else: + cls = FloatRangeSliderWidget + return cls(value=(low, high), min=min, max=max) else: return _widget_abbrev_single_value(o) diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py index 4c9aa2c0d..824e28aa8 100644 --- a/IPython/html/widgets/widget_int.py +++ b/IPython/html/widgets/widget_int.py @@ -1,4 +1,4 @@ -"""IntWidget class. +"""IntWidget class. Represents an unbounded int using a widget. """ @@ -14,13 +14,13 @@ Represents an unbounded int using a widget. # Imports #----------------------------------------------------------------------------- from .widget import DOMWidget -from IPython.utils.traitlets import Unicode, CInt, Bool, Enum +from IPython.utils.traitlets import Unicode, CInt, Bool, Enum, Tuple #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- class _IntWidget(DOMWidget): - value = CInt(0, help="Int value", sync=True) + value = CInt(0, help="Int value", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) @@ -51,10 +51,40 @@ class BoundedIntTextWidget(_BoundedIntWidget): class IntSliderWidget(_BoundedIntWidget): _view_name = Unicode('IntSliderView', sync=True) - orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) + range = Bool(False, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) class IntProgressWidget(_BoundedIntWidget): _view_name = Unicode('ProgressView', sync=True) + +class _IntRangeWidget(_IntWidget): + value = Tuple(CInt, CInt, default_value=(0, 1), help="Low and high int values", sync=True) + +class _BoundedIntRangeWidget(_IntRangeWidget): + step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True) + max = CInt(100, help="Max value", sync=True) + min = CInt(0, help="Min value", sync=True) + + def __init__(self, *pargs, **kwargs): + """Constructor""" + DOMWidget.__init__(self, *pargs, **kwargs) + self.on_trait_change(self._validate, ['value', 'min', 'max']) + + def _validate(self, name, old, new): + """Validate min <= low <= high <= max""" + if name == "value": + low, high = new + low = max(low, self.min) + high = min(high, self.max) + self.value = (min(low, high), max(low, high)) + + +class IntRangeSliderWidget(_BoundedIntRangeWidget): + _view_name = Unicode('IntSliderView', sync=True) + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + help="Vertical or horizontal.", sync=True) + range = Bool(True, help="Display a range selector", sync=True) + readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) From 945a8406747cd3b819b64e88aad40c2d8a4b4643 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Wed, 25 Jun 2014 18:09:44 +0200 Subject: [PATCH 008/316] Add float implementation of range widget --- IPython/html/widgets/__init__.py | 2 +- IPython/html/widgets/widget_float.py | 38 +++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py index 803e28e5a..c0b3d2a32 100644 --- a/IPython/html/widgets/__init__.py +++ b/IPython/html/widgets/__init__.py @@ -3,7 +3,7 @@ from .widget import Widget, DOMWidget, CallbackDispatcher from .widget_bool import CheckboxWidget, ToggleButtonWidget from .widget_button import ButtonWidget from .widget_container import ContainerWidget, PopupWidget -from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget +from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget, FloatRangeSliderWidget from .widget_image import ImageWidget from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget, IntRangeSliderWidget from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget diff --git a/IPython/html/widgets/widget_float.py b/IPython/html/widgets/widget_float.py index 7ddcd90d0..31ec6d1cc 100644 --- a/IPython/html/widgets/widget_float.py +++ b/IPython/html/widgets/widget_float.py @@ -1,4 +1,4 @@ -"""FloatWidget class. +"""FloatWidget class. Represents an unbounded float using a widget. """ @@ -14,13 +14,13 @@ Represents an unbounded float using a widget. # Imports #----------------------------------------------------------------------------- from .widget import DOMWidget -from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum +from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum, Tuple #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- class _FloatWidget(DOMWidget): - value = CFloat(0.0, help="Float value", sync=True) + value = CFloat(0.0, help="Float value", sync=True) disabled = Bool(False, help="Enable or disable user changes", sync=True) description = Unicode(help="Description of the value this widget represents", sync=True) @@ -52,10 +52,40 @@ class BoundedFloatTextWidget(_BoundedFloatWidget): class FloatSliderWidget(_BoundedFloatWidget): _view_name = Unicode('FloatSliderView', sync=True) - orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', help="Vertical or horizontal.", sync=True) + range = Bool(False, help="Display a range selector", sync=True) readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) class FloatProgressWidget(_BoundedFloatWidget): _view_name = Unicode('ProgressView', sync=True) + +class _FloatRangeWidget(_FloatWidget): + value = Tuple(CFloat, CFloat, default_value=(0.0, 1.0), help="Low and high float values", sync=True) + +class _BoundedFloatRangeWidget(_FloatRangeWidget): + step = CFloat(1.0, help="Minimum step that the value can take (ignored by some views)", sync=True) + max = CFloat(100.0, help="Max value", sync=True) + min = CFloat(0.0, help="Min value", sync=True) + + def __init__(self, *pargs, **kwargs): + """Constructor""" + DOMWidget.__init__(self, *pargs, **kwargs) + self.on_trait_change(self._validate, ['value', 'min', 'max']) + + def _validate(self, name, old, new): + """Validate min <= low <= high <= max""" + if name == "value": + low, high = new + low = max(low, self.min) + high = min(high, self.max) + self.value = (min(low, high), max(low, high)) + + +class FloatRangeSliderWidget(_BoundedFloatRangeWidget): + _view_name = Unicode('FloatSliderView', sync=True) + orientation = Enum([u'horizontal', u'vertical'], u'horizontal', + help="Vertical or horizontal.", sync=True) + range = Bool(True, help="Display a range selector", sync=True) + readout = Bool(True, help="Display the current value of the slider next to it.", sync=True) From 5524077e874beb85676f47c1e1c292caa9124a62 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Wed, 25 Jun 2014 18:10:59 +0200 Subject: [PATCH 009/316] Add an extended range abbreviation including a step value --- IPython/html/widgets/interaction.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index 637868251..4bb7cbcce 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -23,7 +23,7 @@ from inspect import getcallargs from IPython.core.getipython import get_ipython from IPython.html.widgets import (Widget, TextWidget, FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget, - ContainerWidget, DOMWidget, IntRangeSliderWidget) + ContainerWidget, DOMWidget, IntRangeSliderWidget, FloatRangeSliderWidget) from IPython.display import display, clear_output from IPython.utils.py3compat import string_types, unicode_type from IPython.utils.traitlets import HasTraits, Any, Unicode @@ -116,6 +116,17 @@ def _widget_abbrev(o): else: cls = FloatRangeSliderWidget return cls(value=(low, high), min=min, max=max) + elif _matches(o, [float_or_int]*5): + min, low, high, max, step = o + if not min <= low <= high <= max: + raise ValueError("Range input expects min <= low <= high <= max, got %r" % o) + if step <= 0: + raise ValueError("step must be >= 0, not %r" % step) + if all(isinstance(_, int) for _ in o): + cls = IntRangeSliderWidget + else: + cls = FloatRangeSliderWidget + return cls(value=(low, high), min=min, max=max, step=step) else: return _widget_abbrev_single_value(o) From ad4cb2c35a323f474c6926c3e44dd238cf3113a1 Mon Sep 17 00:00:00 2001 From: MinRK Date: Wed, 25 Jun 2014 14:31:15 -0700 Subject: [PATCH 010/316] fix stream output created by raw_input was using incorrect 'name', when it should have been 'stream', creating invalid content in notebook documents. --- IPython/html/static/notebook/js/outputarea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index 182242824..bf690ae18 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -794,7 +794,7 @@ var IPython = (function (IPython) { } var content = { output_type : 'stream', - name : 'stdout', + stream : 'stdout', text : theprompt.text() + echo + '\n' } // remove form container From 59a461f928ac49e22d3c81ae13d568501aa136ae Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Thu, 26 Jun 2014 21:11:05 +0200 Subject: [PATCH 011/316] Fix format problem when showing an error --- IPython/html/widgets/interaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index 4bb7cbcce..113c2fe5e 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -110,7 +110,7 @@ def _widget_abbrev(o): elif _matches(o, [float_or_int]*4): min, low, high, max = o if not min <= low <= high <= max: - raise ValueError("Range input expects min <= low <= high <= max, got %r" % o) + raise ValueError("Range input expects min <= low <= high <= max, got {0}".format(o)) if all(isinstance(_, int) for _ in o): cls = IntRangeSliderWidget else: @@ -119,7 +119,7 @@ def _widget_abbrev(o): elif _matches(o, [float_or_int]*5): min, low, high, max, step = o if not min <= low <= high <= max: - raise ValueError("Range input expects min <= low <= high <= max, got %r" % o) + raise ValueError("Range input expects min <= low <= high <= max, got {0}".format(o)) if step <= 0: raise ValueError("step must be >= 0, not %r" % step) if all(isinstance(_, int) for _ in o): From c85155b25d91dae52cacf329a2e2fcc84ac78455 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Thu, 26 Jun 2014 21:11:41 +0200 Subject: [PATCH 012/316] Add some tests for the 4-5 int/float form in interact() --- .../html/widgets/tests/test_interaction.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py index c5fe40db4..b525e9737 100644 --- a/IPython/html/widgets/tests/test_interaction.py +++ b/IPython/html/widgets/tests/test_interaction.py @@ -220,6 +220,84 @@ def test_list_tuple_3_float(): ) check_widgets(c, tup=d, lis=d) +def test_list_tuple_4_int(): + with nt.assert_raises(ValueError): + c = interactive(f, tup=(4, 3, 2, 1)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2, 3, 2)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(2, 1, 3, 4)) + for min, low, high, max in [(0, 1, 2, 3), (0, 0, 0, 0), (1, 2, 2, 3), (-2, -1, 1, 2)]: + c = interactive(f, tup=(min, low, high, max), lis=[min, low, high, max]) + nt.assert_equal(len(c.children), 2) + d = dict( + cls=widgets.IntRangeSliderWidget, + min=min, + max=max, + value=(low, high), + range=True, + readout=True + ) + check_widgets(c, tup=d, lis=d) + +def test_list_tuple_5_int(): + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2, 3, 4, 0)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2, 3, 4, -1)) + for min, low, high, max, step in [(0, 1, 2, 3, 1), (0, 0, 0, 0, 1), (1, 2, 2, 3, 2), (-2, -1, 1, 2, 3)]: + c = interactive(f, tup=(min, low, high, max, step), lis=[min, low, high, max, step]) + nt.assert_equal(len(c.children), 2) + d = dict( + cls=widgets.IntRangeSliderWidget, + min=min, + max=max, + value=(low, high), + step=step, + range=True, + readout=True + ) + check_widgets(c, tup=d, lis=d) + +def test_list_tuple_4_float(): + with nt.assert_raises(ValueError): + c = interactive(f, tup=(4, 3., 2, 1)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2, 3, 2.)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(2, 1., 3, 4)) + for min, low, high, max in [(0, 1., 2, 3), (0, 0, 0., 0), (1., 2., 2., 3.1415), (-2.5, -1, 1, 2.5)]: + c = interactive(f, tup=(min, low, high, max), lis=[min, low, high, max]) + nt.assert_equal(len(c.children), 2) + d = dict( + cls=widgets.FloatRangeSliderWidget, + min=min, + max=max, + value=(low, high), + range=True, + readout=True + ) + check_widgets(c, tup=d, lis=d) + +def test_list_tuple_5_float(): + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2., 3., 4, 0)) + with nt.assert_raises(ValueError): + c = interactive(f, tup=(1, 2, 3., 4., -0.5)) + for min, low, high, max, step in [(0, 1, 2, 3, 0.01), (0, 0, 0, 0, 0.1), (1, 2, 2, 3, 2.718), (-2, -1.5, 1.5, 2, 2)]: + c = interactive(f, tup=(min, low, high, max, step), lis=[min, low, high, max, step]) + nt.assert_equal(len(c.children), 2) + d = dict( + cls=widgets.FloatRangeSliderWidget, + min=min, + max=max, + value=(low, high), + step=step, + range=True, + readout=True + ) + check_widgets(c, tup=d, lis=d) + def test_list_tuple_str(): values = ['hello', 'there', 'guy'] first = values[0] From 4fcf0df53a456a079c9ebcda2b907c0d6f20c8dc Mon Sep 17 00:00:00 2001 From: Julia Evans Date: Thu, 24 Apr 2014 18:35:38 -0400 Subject: [PATCH 013/316] Add preprocessor test inputs to find_package_data() --- setupbase.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setupbase.py b/setupbase.py index 05548d145..9482b10bf 100644 --- a/setupbase.py +++ b/setupbase.py @@ -185,7 +185,12 @@ def find_package_data(): 'IPython.html.tests' : js_tests, 'IPython.qt.console' : ['resources/icon/*.svg'], 'IPython.nbconvert' : nbconvert_templates + - ['tests/files/*.*', 'exporters/tests/files/*.*'], + [ + 'tests/files/*.*', + 'exporters/tests/files/*.*', + 'preprocessor/tests/input/*.*', + 'preprocessor/tests/expected/*.*', + ], 'IPython.nbconvert.filters' : ['marked.js'], 'IPython.nbformat' : ['tests/*.ipynb','v3/v3.withref.json'] } From 06adb299c01c28bd4f89b8f3a50dfae1d38b0887 Mon Sep 17 00:00:00 2001 From: Julia Evans Date: Thu, 24 Apr 2014 19:24:14 -0400 Subject: [PATCH 014/316] Oops, preprocessor*s* --- setupbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setupbase.py b/setupbase.py index 9482b10bf..85ef2f6c9 100644 --- a/setupbase.py +++ b/setupbase.py @@ -188,8 +188,8 @@ def find_package_data(): [ 'tests/files/*.*', 'exporters/tests/files/*.*', - 'preprocessor/tests/input/*.*', - 'preprocessor/tests/expected/*.*', + 'preprocessors/tests/input/*.*', + 'preprocessors/tests/expected/*.*', ], 'IPython.nbconvert.filters' : ['marked.js'], 'IPython.nbformat' : ['tests/*.ipynb','v3/v3.withref.json'] From 78a8cb877f1326a4a4d57159830b3bff82842f96 Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 31 Jan 2014 20:54:29 -0800 Subject: [PATCH 015/316] remove KernelApp.parent_appname This allowed the Kernel to load config from ipython_qtconsole_config.py or ipython_notebook_config.py, depending on the parent that started the kernel. This is of limited usefulness, and can add weird, unexpected side effects. --- IPython/html/notebookapp.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 9c555d640..0533f8a1e 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -589,11 +589,8 @@ class NotebookApp(BaseIPythonApplication): def init_kernel_argv(self): """construct the kernel arguments""" - self.kernel_argv = [] - # Kernel should inherit default config file from frontend - self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) # Kernel should get *absolute* path to profile directory - self.kernel_argv.extend(["--profile-dir", self.profile_dir.location]) + self.kernel_argv = ["--profile-dir", self.profile_dir.location] def init_configurables(self): # force Session default to be secure From 11bcc6e1e5bbecb41855cb104d280432677097ef Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 26 Jun 2014 15:16:07 -0700 Subject: [PATCH 016/316] merge separate input/expected notebooks for execute preprocessor --- setupbase.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setupbase.py b/setupbase.py index 85ef2f6c9..7c5fe0548 100644 --- a/setupbase.py +++ b/setupbase.py @@ -188,8 +188,7 @@ def find_package_data(): [ 'tests/files/*.*', 'exporters/tests/files/*.*', - 'preprocessors/tests/input/*.*', - 'preprocessors/tests/expected/*.*', + 'preprocessors/tests/files/*.*', ], 'IPython.nbconvert.filters' : ['marked.js'], 'IPython.nbformat' : ['tests/*.ipynb','v3/v3.withref.json'] From acf0da01d9a690dfd0768321ec61c5fc633f772f Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 27 Jun 2014 17:47:56 -0700 Subject: [PATCH 017/316] make CORS configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit allows setting CORS headers. - cors_origin sets Access-Control-Allow-Origin directly - cors_origin_pat allows setting Access-Control-Allow-Origin via regular expression, since the header spec itself doesn’t support complex access[1] - cors_credentials sets Access-Control-Allow-Credentials: true To allow CORS from everywhere: ipython notebook —NotebookApp.cors_origin='*' --- IPython/html/base/handlers.py | 42 +++++++++++++++++++++++ IPython/html/base/zmqhandlers.py | 58 ++++++++++++++++++++------------ IPython/html/notebookapp.py | 35 +++++++++++++++++-- 3 files changed, 112 insertions(+), 23 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index d8d107cf1..604e0f027 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -152,6 +152,48 @@ class IPythonHandler(AuthenticatedHandler): def project_dir(self): return self.notebook_manager.notebook_dir + #--------------------------------------------------------------- + # CORS + #--------------------------------------------------------------- + + @property + def cors_origin(self): + """Normal Access-Control-Allow-Origin""" + return self.settings.get('cors_origin', '') + + @property + def cors_origin_pat(self): + """Regular expression version of cors_origin""" + return self.settings.get('cors_origin_pat', None) + + @property + def cors_credentials(self): + """Whether to set Access-Control-Allow-Credentials""" + return self.settings.get('cors_credentials', False) + + def set_default_headers(self): + """Add CORS headers, if defined""" + super(IPythonHandler, self).set_default_headers() + if self.cors_origin: + self.set_header("Access-Control-Allow-Origin", self.cors_origin) + elif self.cors_origin_pat: + origin = self.get_origin() + if origin and self.cors_origin_pat.match(origin): + self.set_header("Access-Control-Allow-Origin", origin) + if self.cors_credentials: + self.set_header("Access-Control-Allow-Credentials", 'true') + + def get_origin(self): + # Handle WebSocket Origin naming convention differences + # The difference between version 8 and 13 is that in 8 the + # client sends a "Sec-Websocket-Origin" header and in 13 it's + # simply "Origin". + if "Origin" in self.request.headers: + origin = self.request.headers.get("Origin") + else: + origin = self.request.headers.get("Sec-Websocket-Origin", None) + return origin + #--------------------------------------------------------------- # template rendering #--------------------------------------------------------------- diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 8999b2672..dc18bb883 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -15,6 +15,8 @@ try: except ImportError: from Cookie import SimpleCookie # Py 2 import logging + +import tornado from tornado import web from tornado import websocket @@ -26,29 +28,35 @@ from .handlers import IPythonHandler class ZMQStreamHandler(websocket.WebSocketHandler): - - def same_origin(self): - """Check to see that origin and host match in the headers.""" - - # The difference between version 8 and 13 is that in 8 the - # client sends a "Sec-Websocket-Origin" header and in 13 it's - # simply "Origin". - if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"): - origin_header = self.request.headers.get("Sec-Websocket-Origin") - else: - origin_header = self.request.headers.get("Origin") + + def check_origin(self, origin): + """Check Origin == Host or CORS origins.""" + if self.cors_origin == '*': + return True host = self.request.headers.get("Host") # If no header is provided, assume we can't verify origin - if(origin_header is None or host is None): + if(origin is None or host is None): + return False + + host_origin = "{0}://{1}".format(self.request.protocol, host) + + # OK if origin matches host + if origin == host_origin: + return True + + # Check CORS headers + if self.cors_origin: + if self.cors_origin == '*': + return True + else: + return self.cors_origin == origin + elif self.cors_origin_pat: + return bool(self.cors_origin_pat.match(origin)) + else: + # No CORS headers, deny the request return False - - parsed_origin = urlparse(origin_header) - origin = parsed_origin.netloc - - # Check to see that origin matches host directly, including ports - return origin == host def clear_cookie(self, *args, **kwargs): """meaningless for websockets""" @@ -96,13 +104,21 @@ class ZMQStreamHandler(websocket.WebSocketHandler): class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): + def set_default_headers(self): + """Undo the set_default_headers in IPythonHandler + + which doesn't make sense for websockets + """ + pass def open(self, kernel_id): self.kernel_id = cast_unicode(kernel_id, 'ascii') # Check to see that origin matches host directly, including ports - if not self.same_origin(): - self.log.warn("Cross Origin WebSocket Attempt.") - raise web.HTTPError(404) + # Tornado 4 already does CORS checking + if tornado.version_info[0] < 4: + if not self.check_origin(self.get_origin()): + self.log.warn("Cross Origin WebSocket Attempt.") + raise web.HTTPError(404) self.session = Session(config=self.config) self.save_on_message = self.on_message diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 0533f8a1e..cf5288ba4 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -12,6 +12,7 @@ import json import logging import os import random +import re import select import signal import socket @@ -333,8 +334,34 @@ class NotebookApp(BaseIPythonApplication): self.file_to_run = base self.notebook_dir = path - # Network related information. - + # Network related information + + cors_origin = Unicode('', config=True, + help="""Set the Access-Control-Allow-Origin header + + Use '*' to allow any origin to access your server. + + Mutually exclusive with cors_origin_pat. + """ + ) + + cors_origin_pat = Unicode('', config=True, + help="""Use a regular expression for the Access-Control-Allow-Origin header + + Requests from an origin matching the expression will get replies with: + + Access-Control-Allow-Origin: origin + + where `origin` is the origin of the request. + + Mutually exclusive with cors_origin. + """ + ) + + cors_credentials = Bool(False, config=True, + help="Set the Access-Control-Allow-Credentials: true header" + ) + ip = Unicode('localhost', config=True, help="The IP address the notebook server will listen on." ) @@ -622,6 +649,10 @@ class NotebookApp(BaseIPythonApplication): def init_webapp(self): """initialize tornado webapp and httpserver""" + self.webapp_settings['cors_origin'] = self.cors_origin + self.webapp_settings['cors_origin_pat'] = re.compile(self.cors_origin_pat) + self.webapp_settings['cors_credentials'] = self.cors_credentials + self.web_app = NotebookWebApplication( self, self.kernel_manager, self.notebook_manager, self.cluster_manager, self.session_manager, self.kernel_spec_manager, From 2460879e8123f749ce81180f92614f59134b60ed Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 27 Jun 2014 17:53:14 -0700 Subject: [PATCH 018/316] Remove a sleep no longer needed with the current base zmq version --- IPython/html/notebookapp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 0533f8a1e..8104ec2ca 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -714,8 +714,6 @@ class NotebookApp(BaseIPythonApplication): This doesn't work on Windows. """ - # FIXME: remove this delay when pyzmq dependency is >= 2.1.11 - time.sleep(0.1) info = self.log.info info('interrupted') print(self.notebook_info()) From db5b58360e2aa0f7221df3e89e2c77c8ff5f8dbb Mon Sep 17 00:00:00 2001 From: Matthias BUSSONNIER Date: Mon, 16 Jun 2014 17:53:52 +0200 Subject: [PATCH 019/316] remove backward compat `container` in output area as planed. --- IPython/html/static/notebook/js/outputarea.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index 182242824..e6e1e6202 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -569,11 +569,6 @@ var IPython = (function (IPython) { var toinsert = this.create_output_subarea(md, "output_javascript", type); IPython.keyboard_manager.register_events(toinsert); element.append(toinsert); - // FIXME TODO : remove `container element for 3.0` - //backward compat, js should be eval'ed in a context where `container` is defined. - var container = element; - container.show = function(){console.log('Warning "container.show()" is deprecated.')}; - // end backward compat // Fix for ipython/issues/5293, make sure `element` is the area which // output can be inserted into at the time of JS execution. From 412605917c4933f851b79e5da0fbea818d3721a4 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Mon, 30 Jun 2014 15:02:41 +0200 Subject: [PATCH 020/316] Adjust range style so that the vertical range marker is visible --- IPython/html/static/widgets/less/widgets.less | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IPython/html/static/widgets/less/widgets.less b/IPython/html/static/widgets/less/widgets.less index 4eed2e213..28996f6f6 100644 --- a/IPython/html/static/widgets/less/widgets.less +++ b/IPython/html/static/widgets/less/widgets.less @@ -122,6 +122,11 @@ height : 28px !important; margin-top : -8px !important; } + + .ui-slider-range { + height : 12px !important; + margin-top : -4px !important; + } } } @@ -160,6 +165,11 @@ height : 14px !important; margin-left : -9px; } + + .ui-slider-range { + width : 12px !important; + margin-left : -1px !important; + } } } From 390d2b3e928f3e733acb1e69325a23f32240f0e1 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Mon, 30 Jun 2014 15:03:34 +0200 Subject: [PATCH 021/316] Compiled changes to minified CSS --- IPython/html/static/style/ipython.min.css | 2 ++ IPython/html/static/style/style.min.css | 2 ++ 2 files changed, 4 insertions(+) diff --git a/IPython/html/static/style/ipython.min.css b/IPython/html/static/style/ipython.min.css index bf64fb856..0373a3b6f 100644 --- a/IPython/html/static/style/ipython.min.css +++ b/IPython/html/static/style/ipython.min.css @@ -157,7 +157,9 @@ div.cell.text_cell.rendered{padding:0} .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top} .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;} .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:350px;height:5px;max-height:5px;margin-top:13px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}.widget-hslider .ui-slider{border:0 !important;background:none !important;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important} +.widget-hslider .ui-slider .ui-slider-range{height:12px !important;margin-top:-4px !important} .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;display:flex;flex-direction:column;align-items:stretch}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px} +.widget-vslider .ui-slider .ui-slider-range{width:12px !important;margin-left:-1px !important} .widget-text{width:350px;margin:0 !important} .widget-listbox{width:350px;margin-bottom:0} .widget-numeric-text{width:150px;margin:0 !important} diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 2b202eeae..ce02bad84 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -1441,7 +1441,9 @@ div.cell.text_cell.rendered{padding:0} .widget-vreadout{padding-top:5px;text-align:center;vertical-align:text-top} .slide-track{border:1px solid #ccc;background:#fff;border-radius:4px;} .widget-hslider{padding-left:8px;padding-right:5px;overflow:visible;width:350px;height:5px;max-height:5px;margin-top:13px;margin-bottom:10px;border:1px solid #ccc;background:#fff;border-radius:4px;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch}.widget-hslider .ui-slider{border:0 !important;background:none !important;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch;display:flex;flex-direction:row;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-hslider .ui-slider .ui-slider-handle{width:14px !important;height:28px !important;margin-top:-8px !important} +.widget-hslider .ui-slider .ui-slider-range{height:12px !important;margin-top:-4px !important} .widget-vslider{padding-bottom:8px;overflow:visible;width:5px;max-width:5px;height:250px;margin-left:12px;border:1px solid #ccc;background:#fff;border-radius:4px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;display:flex;flex-direction:column;align-items:stretch}.widget-vslider .ui-slider{border:0 !important;background:none !important;margin-left:-4px;margin-top:5px;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;display:flex;flex-direction:column;align-items:stretch;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;flex:1}.widget-vslider .ui-slider .ui-slider-handle{width:28px !important;height:14px !important;margin-left:-9px} +.widget-vslider .ui-slider .ui-slider-range{width:12px !important;margin-left:-1px !important} .widget-text{width:350px;margin:0 !important} .widget-listbox{width:350px;margin-bottom:0} .widget-numeric-text{width:150px;margin:0 !important} From 1edc97e34a2d5b9bb3daab2dcca8352297dc552a Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 30 Jun 2014 10:40:31 -0700 Subject: [PATCH 022/316] s/cors_/allow_/ add notes about Tornado 4, and comments, updates per review --- IPython/html/base/handlers.py | 24 ++++++++++++------------ IPython/html/base/zmqhandlers.py | 25 +++++++++++++------------ IPython/html/notebookapp.py | 16 ++++++++-------- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 604e0f027..e8e60297b 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -157,30 +157,30 @@ class IPythonHandler(AuthenticatedHandler): #--------------------------------------------------------------- @property - def cors_origin(self): + def allow_origin(self): """Normal Access-Control-Allow-Origin""" - return self.settings.get('cors_origin', '') + return self.settings.get('allow_origin', '') @property - def cors_origin_pat(self): - """Regular expression version of cors_origin""" - return self.settings.get('cors_origin_pat', None) + def allow_origin_pat(self): + """Regular expression version of allow_origin""" + return self.settings.get('allow_origin_pat', None) @property - def cors_credentials(self): + def allow_credentials(self): """Whether to set Access-Control-Allow-Credentials""" - return self.settings.get('cors_credentials', False) + return self.settings.get('allow_credentials', False) def set_default_headers(self): """Add CORS headers, if defined""" super(IPythonHandler, self).set_default_headers() - if self.cors_origin: - self.set_header("Access-Control-Allow-Origin", self.cors_origin) - elif self.cors_origin_pat: + if self.allow_origin: + self.set_header("Access-Control-Allow-Origin", self.allow_origin) + elif self.allow_origin_pat: origin = self.get_origin() - if origin and self.cors_origin_pat.match(origin): + if origin and self.allow_origin_pat.match(origin): self.set_header("Access-Control-Allow-Origin", origin) - if self.cors_credentials: + if self.allow_credentials: self.set_header("Access-Control-Allow-Credentials", 'true') def get_origin(self): diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index dc18bb883..3e3f45123 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -30,8 +30,12 @@ from .handlers import IPythonHandler class ZMQStreamHandler(websocket.WebSocketHandler): def check_origin(self, origin): - """Check Origin == Host or CORS origins.""" - if self.cors_origin == '*': + """Check Origin == Host or Access-Control-Allow-Origin. + + Tornado >= 4 calls this method automatically, raising 403 if it returns False. + We call it explicitly in `open` on Tornado < 4. + """ + if self.allow_origin == '*': return True host = self.request.headers.get("Host") @@ -47,15 +51,12 @@ class ZMQStreamHandler(websocket.WebSocketHandler): return True # Check CORS headers - if self.cors_origin: - if self.cors_origin == '*': - return True - else: - return self.cors_origin == origin - elif self.cors_origin_pat: - return bool(self.cors_origin_pat.match(origin)) + if self.allow_origin: + return self.allow_origin == origin + elif self.allow_origin_pat: + return bool(self.allow_origin_pat.match(origin)) else: - # No CORS headers, deny the request + # No CORS headers deny the request return False def clear_cookie(self, *args, **kwargs): @@ -117,8 +118,8 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): # Tornado 4 already does CORS checking if tornado.version_info[0] < 4: if not self.check_origin(self.get_origin()): - self.log.warn("Cross Origin WebSocket Attempt.") - raise web.HTTPError(404) + self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin()) + raise web.HTTPError(403) self.session = Session(config=self.config) self.save_on_message = self.on_message diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index cf5288ba4..3ee425939 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -336,16 +336,16 @@ class NotebookApp(BaseIPythonApplication): # Network related information - cors_origin = Unicode('', config=True, + allow_origin = Unicode('', config=True, help="""Set the Access-Control-Allow-Origin header Use '*' to allow any origin to access your server. - Mutually exclusive with cors_origin_pat. + Takes precedence over allow_origin_pat. """ ) - cors_origin_pat = Unicode('', config=True, + allow_origin_pat = Unicode('', config=True, help="""Use a regular expression for the Access-Control-Allow-Origin header Requests from an origin matching the expression will get replies with: @@ -354,11 +354,11 @@ class NotebookApp(BaseIPythonApplication): where `origin` is the origin of the request. - Mutually exclusive with cors_origin. + Ignored if allow_origin is set. """ ) - cors_credentials = Bool(False, config=True, + allow_credentials = Bool(False, config=True, help="Set the Access-Control-Allow-Credentials: true header" ) @@ -649,9 +649,9 @@ class NotebookApp(BaseIPythonApplication): def init_webapp(self): """initialize tornado webapp and httpserver""" - self.webapp_settings['cors_origin'] = self.cors_origin - self.webapp_settings['cors_origin_pat'] = re.compile(self.cors_origin_pat) - self.webapp_settings['cors_credentials'] = self.cors_credentials + self.webapp_settings['allow_origin'] = self.allow_origin + self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) + self.webapp_settings['allow_credentials'] = self.allow_credentials self.web_app = NotebookWebApplication( self, self.kernel_manager, self.notebook_manager, From 1cb0cbf4b9f87e4eccd9d6cfd5b4340550677993 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Thu, 3 Jul 2014 12:01:56 +0200 Subject: [PATCH 023/316] Remove 4-5 tuple forms from interact() --- IPython/html/widgets/interaction.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index 113c2fe5e..1b59de88e 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -23,7 +23,7 @@ from inspect import getcallargs from IPython.core.getipython import get_ipython from IPython.html.widgets import (Widget, TextWidget, FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget, - ContainerWidget, DOMWidget, IntRangeSliderWidget, FloatRangeSliderWidget) + ContainerWidget, DOMWidget) from IPython.display import display, clear_output from IPython.utils.py3compat import string_types, unicode_type from IPython.utils.traitlets import HasTraits, Any, Unicode @@ -107,26 +107,6 @@ def _widget_abbrev(o): else: cls = FloatSliderWidget return cls(value=value, min=min, max=max, step=step) - elif _matches(o, [float_or_int]*4): - min, low, high, max = o - if not min <= low <= high <= max: - raise ValueError("Range input expects min <= low <= high <= max, got {0}".format(o)) - if all(isinstance(_, int) for _ in o): - cls = IntRangeSliderWidget - else: - cls = FloatRangeSliderWidget - return cls(value=(low, high), min=min, max=max) - elif _matches(o, [float_or_int]*5): - min, low, high, max, step = o - if not min <= low <= high <= max: - raise ValueError("Range input expects min <= low <= high <= max, got {0}".format(o)) - if step <= 0: - raise ValueError("step must be >= 0, not %r" % step) - if all(isinstance(_, int) for _ in o): - cls = IntRangeSliderWidget - else: - cls = FloatRangeSliderWidget - return cls(value=(low, high), min=min, max=max, step=step) else: return _widget_abbrev_single_value(o) From b7bb76f073ec4a526b660a97bcd4b4498b691a65 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Thu, 3 Jul 2014 12:05:49 +0200 Subject: [PATCH 024/316] Fix validate logic if min/max are changed --- IPython/html/widgets/widget_float.py | 8 +++++--- IPython/html/widgets/widget_int.py | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/IPython/html/widgets/widget_float.py b/IPython/html/widgets/widget_float.py index 31ec6d1cc..cc55d9d7b 100644 --- a/IPython/html/widgets/widget_float.py +++ b/IPython/html/widgets/widget_float.py @@ -78,9 +78,11 @@ class _BoundedFloatRangeWidget(_FloatRangeWidget): """Validate min <= low <= high <= max""" if name == "value": low, high = new - low = max(low, self.min) - high = min(high, self.max) - self.value = (min(low, high), max(low, high)) + else: + low, high = self.value + low = max(low, self.min) + high = min(high, self.max) + self.value = (min(low, high), max(low, high)) class FloatRangeSliderWidget(_BoundedFloatRangeWidget): diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py index 824e28aa8..ec8648f9c 100644 --- a/IPython/html/widgets/widget_int.py +++ b/IPython/html/widgets/widget_int.py @@ -77,9 +77,11 @@ class _BoundedIntRangeWidget(_IntRangeWidget): """Validate min <= low <= high <= max""" if name == "value": low, high = new - low = max(low, self.min) - high = min(high, self.max) - self.value = (min(low, high), max(low, high)) + else: + low, high = self.value + low = max(low, self.min) + high = min(high, self.max) + self.value = (min(low, high), max(low, high)) class IntRangeSliderWidget(_BoundedIntRangeWidget): From bfd029f5abbcd2fc50d45653a5a53ae801052916 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Thu, 3 Jul 2014 12:08:53 +0200 Subject: [PATCH 025/316] Remove tests for 4-5 tuples, add tests for validate logic --- .../html/widgets/tests/test_interaction.py | 100 ++++-------------- 1 file changed, 22 insertions(+), 78 deletions(-) diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py index b525e9737..f6f83a0bb 100644 --- a/IPython/html/widgets/tests/test_interaction.py +++ b/IPython/html/widgets/tests/test_interaction.py @@ -220,84 +220,6 @@ def test_list_tuple_3_float(): ) check_widgets(c, tup=d, lis=d) -def test_list_tuple_4_int(): - with nt.assert_raises(ValueError): - c = interactive(f, tup=(4, 3, 2, 1)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2, 3, 2)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(2, 1, 3, 4)) - for min, low, high, max in [(0, 1, 2, 3), (0, 0, 0, 0), (1, 2, 2, 3), (-2, -1, 1, 2)]: - c = interactive(f, tup=(min, low, high, max), lis=[min, low, high, max]) - nt.assert_equal(len(c.children), 2) - d = dict( - cls=widgets.IntRangeSliderWidget, - min=min, - max=max, - value=(low, high), - range=True, - readout=True - ) - check_widgets(c, tup=d, lis=d) - -def test_list_tuple_5_int(): - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2, 3, 4, 0)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2, 3, 4, -1)) - for min, low, high, max, step in [(0, 1, 2, 3, 1), (0, 0, 0, 0, 1), (1, 2, 2, 3, 2), (-2, -1, 1, 2, 3)]: - c = interactive(f, tup=(min, low, high, max, step), lis=[min, low, high, max, step]) - nt.assert_equal(len(c.children), 2) - d = dict( - cls=widgets.IntRangeSliderWidget, - min=min, - max=max, - value=(low, high), - step=step, - range=True, - readout=True - ) - check_widgets(c, tup=d, lis=d) - -def test_list_tuple_4_float(): - with nt.assert_raises(ValueError): - c = interactive(f, tup=(4, 3., 2, 1)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2, 3, 2.)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(2, 1., 3, 4)) - for min, low, high, max in [(0, 1., 2, 3), (0, 0, 0., 0), (1., 2., 2., 3.1415), (-2.5, -1, 1, 2.5)]: - c = interactive(f, tup=(min, low, high, max), lis=[min, low, high, max]) - nt.assert_equal(len(c.children), 2) - d = dict( - cls=widgets.FloatRangeSliderWidget, - min=min, - max=max, - value=(low, high), - range=True, - readout=True - ) - check_widgets(c, tup=d, lis=d) - -def test_list_tuple_5_float(): - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2., 3., 4, 0)) - with nt.assert_raises(ValueError): - c = interactive(f, tup=(1, 2, 3., 4., -0.5)) - for min, low, high, max, step in [(0, 1, 2, 3, 0.01), (0, 0, 0, 0, 0.1), (1, 2, 2, 3, 2.718), (-2, -1.5, 1.5, 2, 2)]: - c = interactive(f, tup=(min, low, high, max, step), lis=[min, low, high, max, step]) - nt.assert_equal(len(c.children), 2) - d = dict( - cls=widgets.FloatRangeSliderWidget, - min=min, - max=max, - value=(low, high), - step=step, - range=True, - readout=True - ) - check_widgets(c, tup=d, lis=d) - def test_list_tuple_str(): values = ['hello', 'there', 'guy'] first = values[0] @@ -558,3 +480,25 @@ def test_custom_description(): value='text', description='foo', ) + +def test_int_range_logic(): + irsw = widgets.IntRangeSliderWidget + w = irsw(value=(2, 4), min=0, max=6) + check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) + w.value = (4, 2) + check_widget(w, cls=irsw, value=(2, 4), min=0, max=6) + w.min = 3 + check_widget(w, cls=irsw, value=(3, 4), min=3, max=6) + w.max = 3 + check_widget(w, cls=irsw, value=(3, 3), min=3, max=3) + +def test_float_range_logic(): + frsw = widgets.FloatRangeSliderWidget + w = frsw(value=(.2, .4), min=0., max=.6) + check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) + w.value = (.4, .2) + check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6) + w.min = .3 + check_widget(w, cls=frsw, value=(.3, .4), min=.3, max=.6) + w.max = .3 + check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3) From 0d9ba93307e0b0d80a6fa733974679974c444167 Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 3 Jul 2014 09:18:11 -0700 Subject: [PATCH 026/316] persist notebook server cookie secret in security dir prevents loss of login after relaunching the notebook server closes #6075 --- IPython/html/notebookapp.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 8104ec2ca..fad0dc740 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -6,6 +6,7 @@ from __future__ import print_function +import base64 import errno import io import json @@ -357,6 +358,14 @@ class NotebookApp(BaseIPythonApplication): help="""The full path to a private key file for usage with SSL/TLS.""" ) + cookie_secret_file = Unicode(config=True, + help="""The file where the cookie secret is stored.""" + ) + def _cookie_secret_file_default(self): + if self.profile_dir is None: + return '' + return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret') + cookie_secret = Bytes(b'', config=True, help="""The random bytes used to secure cookies. By default this is a new random number every time you start the Notebook. @@ -367,7 +376,26 @@ class NotebookApp(BaseIPythonApplication): """ ) def _cookie_secret_default(self): - return os.urandom(1024) + if os.path.exists(self.cookie_secret_file): + with io.open(self.cookie_secret_file, 'rb') as f: + return f.read() + else: + secret = base64.encodestring(os.urandom(1024)) + self._write_cookie_secret_file(secret) + return secret + + def _write_cookie_secret_file(self, secret): + """write my secret to my secret_file""" + self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file) + with io.open(self.cookie_secret_file, 'wb') as f: + f.write(secret) + try: + os.chmod(self.cookie_secret_file, 0o600) + except OSError: + self.log.warn( + "Could not set permissions on %s", + self.cookie_secret_file + ) password = Unicode(u'', config=True, help="""Hashed password to use for web authentication. From 979ef8257dc05349e89da38498d7a3fb72dd2ced Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 7 Jul 2014 14:56:00 -0500 Subject: [PATCH 027/316] add some documentation notes about trust being per-profile --- docs/source/notebook/security.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/notebook/security.rst b/docs/source/notebook/security.rst index b5d22cdb6..8a3130f6e 100644 --- a/docs/source/notebook/security.rst +++ b/docs/source/notebook/security.rst @@ -49,6 +49,12 @@ the IPython profile's security directory. By default, this is:: ~/.ipython/profile_default/security/notebook_secret +.. note:: + + The notebook secret being stored in the profile means that + loading a notebook in another profile results in it being untrusted, + unless you copy or symlink the notebook secret to share it across profiles. + When a notebook is opened by a user, the server computes a signature with the user's key, and compares it with the signature stored in the notebook's metadata. If the signature matches, HTML and Javascript From 14ff754cdf4b336aa0c99970b1531d0838c4a881 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Mon, 9 Jun 2014 13:38:44 -0700 Subject: [PATCH 028/316] Allow a widget to be displayed more than once within a parent widget. --- IPython/html/static/widgets/js/widget.js | 72 ++++++++++++++++++------ IPython/html/widgets/widget_container.py | 31 +--------- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 1f48bee38..b90b62f4f 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -283,7 +283,8 @@ function(WidgetManager, _, Backbone){ // Public constructor. this.model.on('change',this.update,this); this.options = parameters.options; - this.child_views = []; + this.child_model_views = {}; + this.child_views = {}; this.model.views.push(this); }, @@ -303,17 +304,34 @@ function(WidgetManager, _, Backbone){ // it would be great to have the widget manager add the cell metadata // to the subview without having to add it here. var child_view = this.model.widget_manager.create_view(child_model, options || {}, this); - this.child_views[child_model.id] = child_view; + + // Associate the view id with the model id. + if (this.child_model_views[child_model.id] === undefined) { + this.child_model_views[child_model.id] = []; + } + this.child_model_views[child_model.id].push(child_view.id); + + // Remember the view by id. + this.child_views[child_view.id] = child_view; return child_view; }, delete_child_view: function(child_model, options) { // Delete a child view that was previously created using create_child_view. - var view = this.child_views[child_model.id]; - if (view !== undefined) { - delete this.child_views[child_model.id]; - view.remove(); - child_model.views.pop(view); + var view_ids = this.child_model_views[child_model.id]; + if (view_ids !== undefined) { + + // Remove every view associate with the model id. + for (var i =0; i < view_ids.length; i++) { + var view_id = view_ids[i]; + var view = this.child_views[view_id]; + views.remove(); + delete this.child_views[view_id]; + child_model.views.pop(view); + } + + // Remove the view list specific to this model. + delete this.child_model_views[child_model.id]; } }, @@ -332,16 +350,38 @@ function(WidgetManager, _, Backbone){ // removed items - _.each(_.difference(old_list, new_list), function(item, index, list) { + _.each(this.difference(old_list, new_list), function(item, index, list) { removed_callback(item); }, this); // added items - _.each(_.difference(new_list, old_list), function(item, index, list) { + _.each(this.difference(new_list, old_list), function(item, index, list) { added_callback(item); }, this); }, + difference: function(a, b) { + // Calculate the difference of two lists by contents. + // + // This function is like the underscore difference function + // except it will not fail when given a list with duplicates. + // i.e.: + // diff([1, 2, 2, 3], [3, 2]) + // Underscores results: + // [1] + // This method: + // [1, 2] + var contents = a.slice(0); + var found_index; + for (var i = 0; i < b.length; i++) { + found_index = _.indexOf(contents, b[i]); + if (found_index >= 0) { + contents.splice(found_index, 1); + } + } + return contents; + }, + callbacks: function(){ // Create msg callbacks for a comm msg. return this.model.callbacks(this); @@ -411,16 +451,16 @@ function(WidgetManager, _, Backbone){ var css = this.model.get('_css'); if (css === undefined) {return;} - for (var i = 0; i < css.length; i++) { + var that = this; + _.each(css, function(css_traits, selector){ // Apply the css traits to all elements that match the selector. - var selector = css[i][0]; - var elements = this._get_selector_element(selector); + var elements = that._get_selector_element(selector); if (elements.length > 0) { - var trait_key = css[i][1]; - var trait_value = css[i][2]; - elements.css(trait_key ,trait_value); + _.each(css_traits, function(css_value, css_key){ + elements.css(css_key, css_value); + }); } - } + }); }, _get_selector_element: function (selector) { diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py index f70fa6b24..b9d12d6c6 100644 --- a/IPython/html/widgets/widget_container.py +++ b/IPython/html/widgets/widget_container.py @@ -2,24 +2,13 @@ Represents a container that can be used to group other widgets. """ -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# + +# Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- from .widget import DOMWidget from IPython.utils.traitlets import Unicode, Tuple, TraitError -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - class ContainerWidget(DOMWidget): _view_name = Unicode('ContainerView', sync=True) @@ -38,22 +27,6 @@ class ContainerWidget(DOMWidget): for child in self._children: child._handle_displayed() - def _children_changed(self, name, old, new): - """Validate children list. - - Makes sure only one instance of any given model can exist in the - children list. - An excellent post on uniqifiers is available at - http://www.peterbe.com/plog/uniqifiers-benchmark - which provides the inspiration for using this implementation. Below - I've implemented the `f5` algorithm using Python comprehensions.""" - if new is not None: - seen = {} - def add_item(i): - seen[i.model_id] = True - return i - self._children = [add_item(i) for i in new if not i.model_id in seen] - class PopupWidget(ContainerWidget): _view_name = Unicode('PopupView', sync=True) From 636362787b0a68c1e9ffc8e2c37792b1e4e11b04 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 10 Jun 2014 09:57:50 -0700 Subject: [PATCH 029/316] Fixed buggy behavior --- IPython/html/static/widgets/js/widget.js | 22 ++++++++++--------- .../static/widgets/js/widget_container.js | 18 +++++++-------- .../widgets/js/widget_selectioncontainer.js | 15 ++++++------- IPython/html/widgets/widget_container.py | 6 ++--- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index b90b62f4f..6d7136456 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -321,18 +321,20 @@ function(WidgetManager, _, Backbone){ var view_ids = this.child_model_views[child_model.id]; if (view_ids !== undefined) { - // Remove every view associate with the model id. - for (var i =0; i < view_ids.length; i++) { - var view_id = view_ids[i]; - var view = this.child_views[view_id]; - views.remove(); - delete this.child_views[view_id]; - child_model.views.pop(view); + // Only delete the first view in the list. + var view_id = view_ids[0]; + var view = this.child_views[view_id]; + delete this.child_views[view_id]; + delete view_ids[0]; + child_model.views.pop(view); + + // Remove the view list specific to this model if it is empty. + if (view_ids.length === 0) { + delete this.child_model_views[child_model.id]; } - - // Remove the view list specific to this model. - delete this.child_model_views[child_model.id]; + return view; } + return null; }, do_diff: function(old_list, new_list, removed_callback, added_callback) { diff --git a/IPython/html/static/widgets/js/widget_container.js b/IPython/html/static/widgets/js/widget_container.js index f7cad2aa8..cc05f7225 100644 --- a/IPython/html/static/widgets/js/widget_container.js +++ b/IPython/html/static/widgets/js/widget_container.js @@ -22,9 +22,9 @@ define(["widgets/js/widget"], function(WidgetManager) { this.$el.addClass('widget-container') .addClass('vbox'); this.children={}; - this.update_children([], this.model.get('_children')); - this.model.on('change:_children', function(model, value, options) { - this.update_children(model.previous('_children'), value); + this.update_children([], this.model.get('children')); + this.model.on('change:children', function(model, value, options) { + this.update_children(model.previous('children'), value); }, this); this.update(); @@ -51,8 +51,7 @@ define(["widgets/js/widget"], function(WidgetManager) { remove_child_model: function(model) { // Called when a model is removed from the children list. - this.child_views[model.id].remove(); - this.delete_child_view(model); + this.delete_child_view(model).remove(); }, add_child_model: function(model) { @@ -187,9 +186,9 @@ define(["widgets/js/widget"], function(WidgetManager) { this._shown_once = false; this.popped_out = true; - this.update_children([], this.model.get('_children')); - this.model.on('change:_children', function(model, value, options) { - this.update_children(model.previous('_children'), value); + this.update_children([], this.model.get('children')); + this.model.on('change:children', function(model, value, options) { + this.update_children(model.previous('children'), value); }, this); this.update(); @@ -257,8 +256,7 @@ define(["widgets/js/widget"], function(WidgetManager) { remove_child_model: function(model) { // Called when a child is removed from children list. - this.child_views[model.id].remove(); - this.delete_child_view(model); + this.delete_child_view(model).remove(); }, add_child_model: function(model) { diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index e7981de10..fc35596e0 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -25,9 +25,9 @@ define(["widgets/js/widget"], function(WidgetManager){ .addClass('panel-group'); this.containers = []; this.model_containers = {}; - this.update_children([], this.model.get('_children')); - this.model.on('change:_children', function(model, value, options) { - this.update_children(model.previous('_children'), value); + this.update_children([], this.model.get('children')); + this.model.on('change:children', function(model, value, options) { + this.update_children(model.previous('children'), value); }, this); this.model.on('change:selected_index', function(model, value, options) { this.update_selected_index(model.previous('selected_index'), value, options); @@ -163,9 +163,9 @@ define(["widgets/js/widget"], function(WidgetManager){ .addClass('tab-content') .appendTo(this.$el); this.containers = []; - this.update_children([], this.model.get('_children')); - this.model.on('change:_children', function(model, value, options) { - this.update_children(model.previous('_children'), value); + this.update_children([], this.model.get('children')); + this.model.on('change:children', function(model, value, options) { + this.update_children(model.previous('children'), value); }, this); // Trigger model displayed events for any models that are child to @@ -190,12 +190,11 @@ define(["widgets/js/widget"], function(WidgetManager){ remove_child_model: function(model) { // Called when a child is removed from children list. - var view = this.child_views[model.id]; + var view = this.delete_child_view(model); this.containers.splice(view.parent_tab.tab_text_index, 1); view.parent_tab.remove(); view.parent_container.remove(); view.remove(); - this.delete_child_view(model); }, add_child_model: function(model) { diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py index b9d12d6c6..dce2a2f00 100644 --- a/IPython/html/widgets/widget_container.py +++ b/IPython/html/widgets/widget_container.py @@ -15,16 +15,14 @@ class ContainerWidget(DOMWidget): # Child widgets in the container. # Using a tuple here to force reassignment to update the list. # When a proper notifying-list trait exists, that is what should be used here. - children = Tuple() - _children = Tuple(sync=True) - + children = Tuple(sync=True) def __init__(self, **kwargs): super(ContainerWidget, self).__init__(**kwargs) self.on_displayed(ContainerWidget._fire_children_displayed) def _fire_children_displayed(self): - for child in self._children: + for child in self.children: child._handle_displayed() From f1269d3de3f2de74138726d194261f81a9840e77 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Wed, 18 Jun 2014 18:37:45 -0700 Subject: [PATCH 030/316] Add order respecting method --- IPython/html/static/widgets/js/widget.js | 42 ++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 6d7136456..1c787f667 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -337,7 +337,7 @@ function(WidgetManager, _, Backbone){ return null; }, - do_diff: function(old_list, new_list, removed_callback, added_callback) { + do_diff: function(old_list, new_list, removed_callback, added_callback, respect_order) { // Difference a changed list and call remove and add callbacks for // each removed and added item in the new list. // @@ -349,17 +349,39 @@ function(WidgetManager, _, Backbone){ // Callback that is called for each item removed. // added_callback : Callback(item) // Callback that is called for each item added. + // [respect_order] : bool [True] + // Whether or not the order of the list matters. + + if (respect_order || respect_order===undefined) { + // Walk the lists until an unequal entry is found. + var i; + for (i = 0; i < new_list.length; i++) { + if (i < old_list.length || new_list[i] !== old_list[i]) { + break; + } + } + // Remove the non-matching items from the old list. + for (var j = i; j < old_list.length; j++) { + console.log(j, old_list.length, old_list[j]); + removed_callback(old_list[j]); + } - // removed items - _.each(this.difference(old_list, new_list), function(item, index, list) { - removed_callback(item); - }, this); - - // added items - _.each(this.difference(new_list, old_list), function(item, index, list) { - added_callback(item); - }, this); + // Add the rest of the new list items. + for (i; i < new_list.length; i++) { + added_callback(new_list[i]); + } + } else { + // removed items + _.each(this.difference(old_list, new_list), function(item, index, list) { + removed_callback(item); + }, this); + + // added items + _.each(this.difference(new_list, old_list), function(item, index, list) { + added_callback(item); + }, this); + } }, difference: function(a, b) { From f51903ca037973779e857d167d877c01fb8c189d Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Thu, 19 Jun 2014 14:05:18 -0700 Subject: [PATCH 031/316] Bug fixes --- IPython/html/static/widgets/js/widget.js | 5 +++-- IPython/html/static/widgets/js/widget_int.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 1c787f667..70c4a0b8e 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -286,6 +286,7 @@ function(WidgetManager, _, Backbone){ this.child_model_views = {}; this.child_views = {}; this.model.views.push(this); + this.id = this.id || IPython.utils.uuid(); }, update: function(){ @@ -304,6 +305,7 @@ function(WidgetManager, _, Backbone){ // it would be great to have the widget manager add the cell metadata // to the subview without having to add it here. var child_view = this.model.widget_manager.create_view(child_model, options || {}, this); + child_view.id = child_view.id || IPython.utils.uuid(); // Associate the view id with the model id. if (this.child_model_views[child_model.id] === undefined) { @@ -325,7 +327,7 @@ function(WidgetManager, _, Backbone){ var view_id = view_ids[0]; var view = this.child_views[view_id]; delete this.child_views[view_id]; - delete view_ids[0]; + view_ids.splice(0,1); child_model.views.pop(view); // Remove the view list specific to this model if it is empty. @@ -363,7 +365,6 @@ function(WidgetManager, _, Backbone){ // Remove the non-matching items from the old list. for (var j = i; j < old_list.length; j++) { - console.log(j, old_list.length, old_list[j]); removed_callback(old_list[j]); } diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js index f7a6c6eb3..6a9420564 100644 --- a/IPython/html/static/widgets/js/widget_int.js +++ b/IPython/html/static/widgets/js/widget_int.js @@ -55,6 +55,7 @@ define(["widgets/js/widget"], function(WidgetManager){ // one-to-one mapping with the corrosponding keys of the model. var jquery_slider_keys = ['step', 'max', 'min', 'disabled']; var that = this; + that.$slider.slider({}); _.each(jquery_slider_keys, function(key, i) { var model_value = that.model.get(key); if (model_value !== undefined) { From 3a9d888f02caa321f878e998affa639f765167f4 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 20 Jun 2014 10:47:04 -0700 Subject: [PATCH 032/316] Removed respect_order and finally removed the children dict of the containerview --- IPython/html/static/widgets/js/widget.js | 62 ++++--------------- .../static/widgets/js/widget_container.js | 2 - 2 files changed, 13 insertions(+), 51 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 70c4a0b8e..c85b84ccf 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -339,7 +339,7 @@ function(WidgetManager, _, Backbone){ return null; }, - do_diff: function(old_list, new_list, removed_callback, added_callback, respect_order) { + do_diff: function(old_list, new_list, removed_callback, added_callback) { // Difference a changed list and call remove and add callbacks for // each removed and added item in the new list. // @@ -351,60 +351,24 @@ function(WidgetManager, _, Backbone){ // Callback that is called for each item removed. // added_callback : Callback(item) // Callback that is called for each item added. - // [respect_order] : bool [True] - // Whether or not the order of the list matters. - - if (respect_order || respect_order===undefined) { - // Walk the lists until an unequal entry is found. - var i; - for (i = 0; i < new_list.length; i++) { - if (i < old_list.length || new_list[i] !== old_list[i]) { - break; - } - } - // Remove the non-matching items from the old list. - for (var j = i; j < old_list.length; j++) { - removed_callback(old_list[j]); + // Walk the lists until an unequal entry is found. + var i; + for (i = 0; i < new_list.length; i++) { + if (i < old_list.length || new_list[i] !== old_list[i]) { + break; } + } - // Add the rest of the new list items. - for (i; i < new_list.length; i++) { - added_callback(new_list[i]); - } - } else { - // removed items - _.each(this.difference(old_list, new_list), function(item, index, list) { - removed_callback(item); - }, this); - - // added items - _.each(this.difference(new_list, old_list), function(item, index, list) { - added_callback(item); - }, this); + // Remove the non-matching items from the old list. + for (var j = i; j < old_list.length; j++) { + removed_callback(old_list[j]); } - }, - difference: function(a, b) { - // Calculate the difference of two lists by contents. - // - // This function is like the underscore difference function - // except it will not fail when given a list with duplicates. - // i.e.: - // diff([1, 2, 2, 3], [3, 2]) - // Underscores results: - // [1] - // This method: - // [1, 2] - var contents = a.slice(0); - var found_index; - for (var i = 0; i < b.length; i++) { - found_index = _.indexOf(contents, b[i]); - if (found_index >= 0) { - contents.splice(found_index, 1); - } + // Add the rest of the new list items. + for (i; i < new_list.length; i++) { + added_callback(new_list[i]); } - return contents; }, callbacks: function(){ diff --git a/IPython/html/static/widgets/js/widget_container.js b/IPython/html/static/widgets/js/widget_container.js index cc05f7225..95e519d35 100644 --- a/IPython/html/static/widgets/js/widget_container.js +++ b/IPython/html/static/widgets/js/widget_container.js @@ -21,7 +21,6 @@ define(["widgets/js/widget"], function(WidgetManager) { // Called when view is rendered. this.$el.addClass('widget-container') .addClass('vbox'); - this.children={}; this.update_children([], this.model.get('children')); this.model.on('change:children', function(model, value, options) { this.update_children(model.previous('children'), value); @@ -80,7 +79,6 @@ define(["widgets/js/widget"], function(WidgetManager) { render: function(){ // Called when view is rendered. var that = this; - this.children={}; this.$el.on("remove", function(){ that.$backdrop.remove(); From 988fd1b5f9c99fb15799b046753c88ae4690cc1d Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Fri, 20 Jun 2014 11:28:00 -0700 Subject: [PATCH 033/316] Reverted strange change that I don't remember making to update method. --- IPython/html/static/widgets/js/widget.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index c85b84ccf..3d2e1f108 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -440,16 +440,16 @@ function(WidgetManager, _, Backbone){ var css = this.model.get('_css'); if (css === undefined) {return;} - var that = this; - _.each(css, function(css_traits, selector){ + for (var i = 0; i < css.length; i++) { // Apply the css traits to all elements that match the selector. - var elements = that._get_selector_element(selector); + var selector = css[i][0]; + var elements = this._get_selector_element(selector); if (elements.length > 0) { - _.each(css_traits, function(css_value, css_key){ - elements.css(css_key, css_value); - }); + var trait_key = css[i][1]; + var trait_value = css[i][2]; + elements.css(trait_key ,trait_value); } - }); + } }, _get_selector_element: function (selector) { From fa39d914460366d522a2570affea29dc7635c681 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 8 Jul 2014 11:22:47 -0500 Subject: [PATCH 034/316] Review comments --- IPython/html/static/widgets/js/widget.js | 6 +++--- IPython/html/static/widgets/js/widget_container.js | 4 ++-- IPython/html/static/widgets/js/widget_selectioncontainer.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 3d2e1f108..87522855c 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -304,8 +304,8 @@ function(WidgetManager, _, Backbone){ // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior // it would be great to have the widget manager add the cell metadata // to the subview without having to add it here. - var child_view = this.model.widget_manager.create_view(child_model, options || {}, this); - child_view.id = child_view.id || IPython.utils.uuid(); + options = $.merge({ parent: this }, options || {}); + var child_view = this.model.widget_manager.create_view(child_model, options, this); // Associate the view id with the model id. if (this.child_model_views[child_model.id] === undefined) { @@ -318,7 +318,7 @@ function(WidgetManager, _, Backbone){ return child_view; }, - delete_child_view: function(child_model, options) { + pop_child_view: function(child_model) { // Delete a child view that was previously created using create_child_view. var view_ids = this.child_model_views[child_model.id]; if (view_ids !== undefined) { diff --git a/IPython/html/static/widgets/js/widget_container.js b/IPython/html/static/widgets/js/widget_container.js index 95e519d35..57d265027 100644 --- a/IPython/html/static/widgets/js/widget_container.js +++ b/IPython/html/static/widgets/js/widget_container.js @@ -50,7 +50,7 @@ define(["widgets/js/widget"], function(WidgetManager) { remove_child_model: function(model) { // Called when a model is removed from the children list. - this.delete_child_view(model).remove(); + this.pop_child_view(model).remove(); }, add_child_model: function(model) { @@ -254,7 +254,7 @@ define(["widgets/js/widget"], function(WidgetManager) { remove_child_model: function(model) { // Called when a child is removed from children list. - this.delete_child_view(model).remove(); + this.pop_child_view(model).remove(); }, add_child_model: function(model) { diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index fc35596e0..651a631b2 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -92,7 +92,7 @@ define(["widgets/js/widget"], function(WidgetManager){ this.containers.splice(accordion_group.container_index, 1); delete this.model_containers[model.id]; accordion_group.remove(); - this.delete_child_view(model); + this.pop_child_view(model); }, add_child_model: function(model) { @@ -190,7 +190,7 @@ define(["widgets/js/widget"], function(WidgetManager){ remove_child_model: function(model) { // Called when a child is removed from children list. - var view = this.delete_child_view(model); + var view = this.pop_child_view(model); this.containers.splice(view.parent_tab.tab_text_index, 1); view.parent_tab.remove(); view.parent_container.remove(); From df47869fb04ddb94a453f04e5666e3ec4cf3e2a2 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 8 Jul 2014 11:48:58 -0500 Subject: [PATCH 035/316] Move displayed event to view. --- IPython/html/static/widgets/js/manager.js | 1 + IPython/html/static/widgets/js/widget.js | 1 - IPython/html/static/widgets/js/widget_container.js | 12 ++++++------ .../static/widgets/js/widget_selectioncontainer.js | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index d1af78c0a..4aba4d27e 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -86,6 +86,7 @@ cell.widget_area.show(); this._handle_display_view(view); cell.widget_subarea.append(view.$el); + view.trigger('displayed'); } } }; diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index 87522855c..ce06396c7 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -83,7 +83,6 @@ function(WidgetManager, _, Backbone){ break; case 'display': this.widget_manager.display_view(msg, this); - this.trigger('displayed'); break; } }, diff --git a/IPython/html/static/widgets/js/widget_container.js b/IPython/html/static/widgets/js/widget_container.js index 57d265027..02bcf03d5 100644 --- a/IPython/html/static/widgets/js/widget_container.js +++ b/IPython/html/static/widgets/js/widget_container.js @@ -30,11 +30,11 @@ define(["widgets/js/widget"], function(WidgetManager) { // Trigger model displayed events for any models that are child to // this model when this model is displayed. var that = this; - this.model.on('displayed', function(){ + this.on('displayed', function(){ that.is_displayed = true; for (var property in that.child_views) { if (that.child_views.hasOwnProperty(property)) { - that.child_views[property].model.trigger('displayed'); + that.child_views[property].trigger('displayed'); } } }); @@ -60,7 +60,7 @@ define(["widgets/js/widget"], function(WidgetManager) { // Trigger the displayed event if this model is displayed. if (this.is_displayed) { - model.trigger('displayed'); + view.trigger('displayed'); } }, @@ -192,11 +192,11 @@ define(["widgets/js/widget"], function(WidgetManager) { // Trigger model displayed events for any models that are child to // this model when this model is displayed. - this.model.on('displayed', function(){ + this.on('displayed', function(){ that.is_displayed = true; for (var property in that.child_views) { if (that.child_views.hasOwnProperty(property)) { - that.child_views[property].model.trigger('displayed'); + that.child_views[property].trigger('displayed'); } } }); @@ -264,7 +264,7 @@ define(["widgets/js/widget"], function(WidgetManager) { // Trigger the displayed event if this model is displayed. if (this.is_displayed) { - model.trigger('displayed'); + view.trigger('displayed'); } }, diff --git a/IPython/html/static/widgets/js/widget_selectioncontainer.js b/IPython/html/static/widgets/js/widget_selectioncontainer.js index 651a631b2..7dd49e053 100644 --- a/IPython/html/static/widgets/js/widget_selectioncontainer.js +++ b/IPython/html/static/widgets/js/widget_selectioncontainer.js @@ -36,14 +36,14 @@ define(["widgets/js/widget"], function(WidgetManager){ this.update_titles(value); }, this); var that = this; - this.model.on('displayed', function() { + this.on('displayed', function() { this.update_titles(); // Trigger model displayed events for any models that are child to // this model when this model is displayed. that.is_displayed = true; for (var property in that.child_views) { if (that.child_views.hasOwnProperty(property)) { - that.child_views[property].model.trigger('displayed'); + that.child_views[property].trigger('displayed'); } } }, this); @@ -137,7 +137,7 @@ define(["widgets/js/widget"], function(WidgetManager){ // Trigger the displayed event if this model is displayed. if (this.is_displayed) { - model.trigger('displayed'); + view.trigger('displayed'); } }, }); @@ -170,11 +170,11 @@ define(["widgets/js/widget"], function(WidgetManager){ // Trigger model displayed events for any models that are child to // this model when this model is displayed. - this.model.on('displayed', function(){ + this.on('displayed', function(){ that.is_displayed = true; for (var property in that.child_views) { if (that.child_views.hasOwnProperty(property)) { - that.child_views[property].model.trigger('displayed'); + that.child_views[property].trigger('displayed'); } } }); @@ -233,7 +233,7 @@ define(["widgets/js/widget"], function(WidgetManager){ // Trigger the displayed event if this model is displayed. if (this.is_displayed) { - model.trigger('displayed'); + view.trigger('displayed'); } }, From 1a30a12302a2e20e281932963de4bf6b17bb7a08 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 8 Jul 2014 13:43:18 -0500 Subject: [PATCH 036/316] Ahhh bug fix! --- IPython/html/static/widgets/js/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js index ce06396c7..6c711a96a 100644 --- a/IPython/html/static/widgets/js/widget.js +++ b/IPython/html/static/widgets/js/widget.js @@ -303,7 +303,7 @@ function(WidgetManager, _, Backbone){ // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior // it would be great to have the widget manager add the cell metadata // to the subview without having to add it here. - options = $.merge({ parent: this }, options || {}); + options = $.extend({ parent: this }, options || {}); var child_view = this.model.widget_manager.create_view(child_model, options, this); // Associate the view id with the model id. From 2bcbd3c5cb3bd4b3514f141a446ed6520d8b0033 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 9 Jul 2014 00:03:16 -0500 Subject: [PATCH 037/316] Check for pids when listing nbserver processes --- IPython/html/notebookapp.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 2dc404c5a..85bf440e5 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -74,6 +74,7 @@ from IPython.kernel.zmq.session import default_secure, Session from IPython.nbformat.sign import NotebookNotary from IPython.utils.importstring import import_item from IPython.utils import submodule +from IPython.utils.process import check_pid from IPython.utils.traitlets import ( Dict, Unicode, Integer, List, Bool, Bytes, Instance, DottedObjectName, TraitError, @@ -844,6 +845,7 @@ class NotebookApp(BaseIPythonApplication): 'secure': bool(self.certfile), 'base_url': self.base_url, 'notebook_dir': os.path.abspath(self.notebook_dir), + 'pid': os.getpid() } def write_server_info_file(self): @@ -917,8 +919,17 @@ def list_running_servers(profile='default'): for file in os.listdir(pd.security_dir): if file.startswith('nbserver-'): with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f: - yield json.load(f) + info = json.load(f) + # Simple check whether that process is really still running + if check_pid(info['pid']): + yield info + else: + # If the process has died, try to delete its info file + try: + os.unlink(file) + except OSError: + pass # TODO: This should warn or log or something #----------------------------------------------------------------------------- # Main entry point #----------------------------------------------------------------------------- From 55f455ab551e07c69aa6023dc971542318ead30d Mon Sep 17 00:00:00 2001 From: MinRK Date: Wed, 9 Jul 2014 16:25:36 -0500 Subject: [PATCH 038/316] only set allow_origin_pat if defined fixes the default behavior to be as intended (require Origin == Host) --- IPython/html/notebookapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 2dc404c5a..2c8324dd6 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -678,7 +678,8 @@ class NotebookApp(BaseIPythonApplication): def init_webapp(self): """initialize tornado webapp and httpserver""" self.webapp_settings['allow_origin'] = self.allow_origin - self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) + if self.allow_origin_pat: + self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) self.webapp_settings['allow_credentials'] = self.allow_credentials self.web_app = NotebookWebApplication( From 855815d207b4510b2599f08994b1636beb9c0011 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Tue, 10 Jun 2014 12:42:51 -0700 Subject: [PATCH 039/316] Make page.html require.js friendly. --- IPython/html/static/auth/js/loginwidget.js | 31 +++++++--------- IPython/html/static/base/js/namespace.js | 42 +++++----------------- IPython/html/static/base/js/page.js | 34 +++++++----------- IPython/html/static/base/js/pagemain.js | 21 +++-------- IPython/html/static/base/js/utils.js | 21 ++++++----- IPython/html/templates/page.html | 12 ++----- 6 files changed, 51 insertions(+), 110 deletions(-) diff --git a/IPython/html/static/auth/js/loginwidget.js b/IPython/html/static/auth/js/loginwidget.js index 329ba0e0e..05e4fe99b 100644 --- a/IPython/html/static/auth/js/loginwidget.js +++ b/IPython/html/static/auth/js/loginwidget.js @@ -1,20 +1,16 @@ -//---------------------------------------------------------------------------- -// Copyright (C) 2008-2011 The IPython Development Team -// -// Distributed under the terms of the BSD License. The full license is in -// the file COPYING, distributed as part of this software. -//---------------------------------------------------------------------------- - -//============================================================================ -// Login button -//============================================================================ - -var IPython = (function (IPython) { +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'base/js/namespace', + 'base/js/utils', + 'components/jquery/jquery.min', +], function(IPython, Utils, $){ "use strict"; var LoginWidget = function (selector, options) { options = options || {}; - this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl"); + this.base_url = options.base_url || Utils.get_body_data("baseUrl"); this.selector = selector; if (this.selector !== undefined) { this.element = $(selector); @@ -31,13 +27,13 @@ var IPython = (function (IPython) { LoginWidget.prototype.bind_events = function () { var that = this; this.element.find("button#logout").click(function () { - window.location = IPython.utils.url_join_encode( + window.location = Utils.url_join_encode( that.base_url, "logout" ); }); this.element.find("button#login").click(function () { - window.location = IPython.utils.url_join_encode( + window.location = Utils.url_join_encode( that.base_url, "login" ); @@ -47,6 +43,5 @@ var IPython = (function (IPython) { // Set module variables IPython.LoginWidget = LoginWidget; - return IPython; - -}(IPython)); + return LoginWidget; +}); \ No newline at end of file diff --git a/IPython/html/static/base/js/namespace.js b/IPython/html/static/base/js/namespace.js index 3b36198f5..b3d39afbc 100644 --- a/IPython/html/static/base/js/namespace.js +++ b/IPython/html/static/base/js/namespace.js @@ -1,34 +1,8 @@ -//---------------------------------------------------------------------------- -// Copyright (C) 2011 The IPython Development Team -// -// Distributed under the terms of the BSD License. The full license is in -// the file COPYING, distributed as part of this software. -//---------------------------------------------------------------------------- - -var IPython = IPython || {}; - -IPython.version = "3.0.0-dev"; - -IPython.namespace = function (ns_string) { - "use strict"; - - var parts = ns_string.split('.'), - parent = IPython, - i; - - // String redundant leading global - if (parts[0] === "IPython") { - parts = parts.slice(1); - } - - for (i=0; i