Repository: django-extensions/django-extensions

Issue:
Add support for IPython Notebook to shell_plus
[IPython Notebook](http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html) is a Python shell which uses the browser as its front end. It has in-line graphing capabilities and a rich interactive user interface.

`shell_plus` could have an extra option for using IPython Notebook instead of the terminal-based IPython shell.



Discussion:

Resolving patch:
diff --git a/django_extensions/management/commands/shell_plus.py b/django_extensions/management/commands/shell_plus.py
--- a/django_extensions/management/commands/shell_plus.py
+++ b/django_extensions/management/commands/shell_plus.py
@@ -1,5 +1,6 @@
 import os
 from django.core.management.base import NoArgsCommand
+from django_extensions.management.shells import import_objects
 from optparse import make_option
 import time
 
@@ -8,6 +9,8 @@ class Command(NoArgsCommand):
     option_list = NoArgsCommand.option_list + (
         make_option('--ipython', action='store_true', dest='ipython',
             help='Tells Django to use IPython, not BPython.'),
+        make_option('--notebook', action='store_true', dest='notebook',
+            help='Tells Django to use IPython Notebook.'),
         make_option('--plain', action='store_true', dest='plain',
             help='Tells Django to use plain Python, not BPython nor IPython.'),
         make_option('--no-pythonrc', action='store_true', dest='no_pythonrc',
@@ -24,13 +27,8 @@ class Command(NoArgsCommand):
     requires_model_validation = True
 
     def handle_noargs(self, **options):
-        # XXX: (Temporary) workaround for ticket #1796: force early loading of all
-        # models from installed apps. (this is fixed by now, but leaving it here
-        # for people using 0.96 or older trunk (pre [5919]) versions.
-        from django.db.models.loading import get_models, get_apps
-        loaded_models = get_models()
-
-        use_ipython = options.get('ipython', False)
+        use_notebook = options.get('notebook', False)
+        use_ipython = options.get('ipython', use_notebook)
         use_plain = options.get('plain', False)
         use_pythonrc = not options.get('no_pythonrc', True)
 
@@ -63,50 +61,6 @@ def execute(self, sql, params=()):
         # Set up a dictionary to serve as the environment for the shell, so
         # that tab completion works on objects that are imported at runtime.
         # See ticket 5082.
-        from django.conf import settings
-        imported_objects = {'settings': settings}
-
-        dont_load_cli = options.get('dont_load') # optparse will set this to [] if it doensnt exists
-        dont_load_conf = getattr(settings, 'SHELL_PLUS_DONT_LOAD', [])
-        dont_load = dont_load_cli + dont_load_conf
-        quiet_load = options.get('quiet_load')
-
-        model_aliases = getattr(settings, 'SHELL_PLUS_MODEL_ALIASES', {})
-
-        for app_mod in get_apps():
-            app_models = get_models(app_mod)
-            if not app_models:
-                continue
-
-            app_name = app_mod.__name__.split('.')[-2]
-            if app_name in dont_load:
-                continue
-
-            app_aliases = model_aliases.get(app_name, {})
-            model_labels = []
-
-            for model in app_models:
-                try:
-                    imported_object = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
-                    model_name = model.__name__
-
-                    if "%s.%s" % (app_name, model_name) in dont_load:
-                        continue
-
-                    alias = app_aliases.get(model_name, model_name)
-                    imported_objects[alias] = imported_object
-                    if model_name == alias:
-                        model_labels.append(model_name)
-                    else:
-                        model_labels.append("%s (as %s)" % (model_name, alias))
-
-                except AttributeError, e:
-                    if not quiet_load:
-                        print self.style.ERROR("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_name, str(e)))
-                    continue
-            if not quiet_load:
-                print self.style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], ", ".join(model_labels)))
-
         try:
             if use_plain:
                 # Don't bother loading B/IPython, because the user wants plain Python.
@@ -116,17 +70,33 @@ def execute(self, sql, params=()):
                     # User wants IPython
                     raise ImportError
                 from bpython import embed
+                imported_objects = import_objects(options, self.style)
                 embed(imported_objects)
             except ImportError:
                 try:
-                    from IPython import embed
-                    embed(user_ns=imported_objects)
+                    if use_notebook:
+                        from django.conf import settings
+                        from IPython.frontend.html.notebook import notebookapp
+                        app = notebookapp.NotebookApp.instance()
+                        ipython_arguments = getattr(
+                            settings,
+                            'IPYTHON_ARGUMENTS',
+                            ['--ext',
+                             'django_extensions.management.notebook_extension'])
+                        app.initialize(ipython_arguments)
+                        app.start()
+                    else:
+                        from IPython import embed
+                        imported_objects = import_objects(options, self.style)
+                        embed(user_ns=imported_objects)
                 except ImportError:
                     # IPython < 0.11
                     # Explicitly pass an empty list as arguments, because otherwise
                     # IPython would use sys.argv from this script.
+                    # Notebook not supported for IPython < 0.11.
                     try:
                         from IPython.Shell import IPShell
+                        imported_objects = import_objects(options, self.style)
                         shell = IPShell(argv=[], user_ns=imported_objects)
                         shell.mainloop()
                     except ImportError:
@@ -135,6 +105,7 @@ def execute(self, sql, params=()):
         except ImportError:
             # Using normal Python shell
             import code
+            imported_objects = import_objects(options, self.style)
             try:
                 # Try activating rlcompleter, because it's handy.
                 import readline
diff --git a/django_extensions/management/notebook_extension.py b/django_extensions/management/notebook_extension.py
new file mode 100644
--- /dev/null
+++ b/django_extensions/management/notebook_extension.py
@@ -0,0 +1,6 @@
+def load_ipython_extension(ipython):
+    from django.core.management.color import no_style
+    from django_extensions.management.shells import import_objects
+    imported_objects = import_objects(options={'dont_load': []},
+                                      style=no_style())
+    ipython.push(imported_objects)
diff --git a/django_extensions/management/shells.py b/django_extensions/management/shells.py
new file mode 100644
--- /dev/null
+++ b/django_extensions/management/shells.py
@@ -0,0 +1,56 @@
+class ObjectImportError(Exception):
+    pass
+
+
+def import_objects(options, style):
+    # XXX: (Temporary) workaround for ticket #1796: force early loading of all
+    # models from installed apps. (this is fixed by now, but leaving it here
+    # for people using 0.96 or older trunk (pre [5919]) versions.
+    from django.db.models.loading import get_models, get_apps
+    loaded_models = get_models()
+
+    from django.conf import settings
+    imported_objects = {'settings': settings}
+
+    dont_load_cli = options.get('dont_load') # optparse will set this to [] if it doensnt exists
+    dont_load_conf = getattr(settings, 'SHELL_PLUS_DONT_LOAD', [])
+    dont_load = dont_load_cli + dont_load_conf
+    quiet_load = options.get('quiet_load')
+
+    model_aliases = getattr(settings, 'SHELL_PLUS_MODEL_ALIASES', {})
+
+    for app_mod in get_apps():
+        app_models = get_models(app_mod)
+        if not app_models:
+            continue
+
+        app_name = app_mod.__name__.split('.')[-2]
+        if app_name in dont_load:
+            continue
+
+        app_aliases = model_aliases.get(app_name, {})
+        model_labels = []
+
+        for model in app_models:
+            try:
+                imported_object = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
+                model_name = model.__name__
+
+                if "%s.%s" % (app_name, model_name) in dont_load:
+                    continue
+
+                alias = app_aliases.get(model_name, model_name)
+                imported_objects[alias] = imported_object
+                if model_name == alias:
+                    model_labels.append(model_name)
+                else:
+                    model_labels.append("%s (as %s)" % (model_name, alias))
+
+            except AttributeError, e:
+                if not quiet_load:
+                    print style.ERROR("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_name, str(e)))
+                continue
+        if not quiet_load:
+            print style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], ", ".join(model_labels)))
+
+    return imported_objects


Test patch: