{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "As the core user guides including the [Introduction](../getting_started/Introduction.ipynb) have demonstrated, it is easy to display Panel apps in the notebook, launch them from an interactive Python prompt, and deploy them as a standalone Bokeh server app from the commandline. However, it is also often useful to embed a Panel app in large web application, such as a Django web server. Using Panel with Django requires a bit more work than for notebooks and Bokeh servers.\n", "\n", "To run this example app yourself, you will first need to install django 2 (e.g. `conda install \"django=2\"`).\n", "\n", "Additionally you should also install the `channels` library (using `pip install channels` or `conda install channels -c conda-forge`). This makes it possible to run bokeh without launching a separate Tornado server." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Configuration\n", "\n", "Before we start adding a bokeh app to our Django server we have to set up some of the basic plumbing. In the `examples/apps/django2/project` folder we will add some basic configurations.\n", "\n", "First of all we need to set up a Asynchronous Server Gateway Interface (ASGI) instead of the usual WSGI setup. For this purpose we add `examples/apps/django2/project/asgi.py`:\n", "\n", "```python\n", "import os\n", "\n", "import django\n", "\n", "from channels.routing import get_default_application\n", "\n", "os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')\n", "\n", "django.setup()\n", "\n", "application = get_default_application()\n", "```\n", "\n", "\n", "Next we need to ensure the routing is configured correctly to handle a bokeh server in `examples/apps/django2/project/routing.py`:\n", "\n", "\n", "```python\n", "from channels.auth import AuthMiddlewareStack\n", "from channels.routing import ProtocolTypeRouter, URLRouter\n", "from django.apps import apps\n", "\n", "bokeh_app_config = apps.get_app_config('bokeh.server.django')\n", "\n", "application = ProtocolTypeRouter({\n", " 'websocket': AuthMiddlewareStack(URLRouter(bokeh_app_config.routes.get_websocket_urlpatterns())),\n", " 'http': AuthMiddlewareStack(URLRouter(bokeh_app_config.routes.get_http_urlpatterns())),\n", "})\n", "```\n", "\n", "Lastly we need to add some configuration to `examples/apps/django2/project/settings.py`. As a first step we need to add both `channels` and `bokeh.server.django` to the ``INSTALLED_APPS``:\n", "\n", "```python\n", "\n", "INSTALLED_APPS = [\n", " ...\n", " 'channels',\n", " 'bokeh.server.django',\n", "]\n", "```\n", "\n", "Secondly we need to declare the `bokehjsdir` as part of the `STATICFILES_DIRS`:\n", "\n", "```python\n", "from bokeh.settings import bokehjsdir\n", "\n", "STATICFILES_DIRS = [bokehjsdir()]\n", "```\n", "\n", "Now we need to add any templates we have:\n", "\n", "```python\n", "TEMPLATES = [\n", " {\n", " 'DIRS': [os.path.join(BASE_DIR, 'sliders', 'templates')],\n", " ...\n", " }\n", "]\n", "```\n", "\n", "and lastly add the app(s) and `static_extensions()` to the `urlpatterns` in the `urls.py` file:\n", "\n", "```python\n", "from bokeh.server.django import autoload, static_extensions\n", "from django.apps import apps\n", "from django.contrib import admin\n", "from django.urls import path, include\n", "from django.contrib.staticfiles.urls import staticfiles_urlpatterns\n", "\n", "import sliders.pn_app as sliders_app\n", "\n", "pn_app_config = apps.get_app_config('bokeh.server.django')\n", "\n", "urlpatterns = [\n", " path('sliders/', include('sliders.urls')),\n", " path('admin/', admin.site.urls),\n", "]\n", "\n", "bokeh_apps = [\n", " autoload(\"sliders\", sliders_app.app),\n", "]\n", "\n", "urlpatterns += static_extensions()\n", "urlpatterns += staticfiles_urlpatterns()\n", "```\n", "\n", "Now it's time to configure an actual app and add it to our Django server.\n", "\n", "## Sliders app\n", "\n", "Based on a standard Django2 app template, this app shows how to integrate Panel with a Django view\n", "\n", "The sliders app is in `examples/apps/django2/sliders`. We will cover the following additions/modifications to the Django2 app template:\n", "\n", " * `sliders/sinewave.py`: a parameterized object (representing your pre-existing code)\n", "\n", " * `sliders/pn_app.py`: creates an app function from the SineWave class\n", "\n", " * `sliders/apps.py`: how a Django app can import and use Bokeh server\n", "\n", " * `sliders/views.py` and `templates/base.html`: getting the Bokeh app into a Django view" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![screenshot of sliders app](../_static/sliders.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To start with, in `sliders/sinewave.py` we create a parameterized object to serve as a placeholder for your own, existing code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import param\n", "from bokeh.models import ColumnDataSource\n", "from bokeh.plotting import figure\n", "\n", "\n", "class SineWave(param.Parameterized):\n", " offset = param.Number(default=0.0, bounds=(-5.0, 5.0))\n", " amplitude = param.Number(default=1.0, bounds=(-5.0, 5.0))\n", " phase = param.Number(default=0.0, bounds=(0.0, 2 * np.pi))\n", " frequency = param.Number(default=1.0, bounds=(0.1, 5.1))\n", " N = param.Integer(default=200, bounds=(0, None))\n", " x_range = param.Range(default=(0, 4 * np.pi), bounds=(0, 4 * np.pi))\n", " y_range = param.Range(default=(-2.5, 2.5), bounds=(-10, 10))\n", "\n", " def __init__(self, **params):\n", " super(SineWave, self).__init__(**params)\n", " x, y = self.sine()\n", " self.cds = ColumnDataSource(data=dict(x=x, y=y))\n", " self.plot = figure(plot_height=400, plot_width=400,\n", " tools=\"crosshair, pan, reset, save, wheel_zoom\",\n", " x_range=self.x_range, y_range=self.y_range)\n", " self.plot.line('x', 'y', source=self.cds, line_width=3, line_alpha=0.6)\n", "\n", " @param.depends('N', 'frequency', 'amplitude', 'offset', 'phase', 'x_range', 'y_range', watch=True)\n", " def update_plot(self):\n", " x, y = self.sine()\n", " self.cds.data = dict(x=x, y=y)\n", " self.plot.x_range.start, self.plot.x_range.end = self.x_range\n", " self.plot.y_range.start, self.plot.y_range.end = self.y_range\n", "\n", " def sine(self):\n", " x = np.linspace(0, 4 * np.pi, self.N)\n", " y = self.amplitude * np.sin(self.frequency * x + self.phase) + self.offset\n", " return x, y\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However the app itself is defined we need to configure an entry point, which is a function that accepts a bokeh Document and adds the application to it. In case of the slider app it looks like this:\n", "\n", "```python\n", "import panel as pn\n", "\n", "from .sinewave import SineWave\n", "\n", "def app(doc):\n", " sw = SineWave()\n", " row = pn.Row(sw.param, sw.plot)\n", " row.server_doc(doc)\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we create a ``views.py`` file which returns a view the Django server can render:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# Create your views here.\n", "from bokeh.embed import server_document\n", "\n", "from django.http import HttpRequest, HttpResponse\n", "from django.shortcuts import render\n", "\n", "\n", "def sliders(request: HttpRequest) -> HttpResponse:\n", " script = server_document(request.build_absolute_uri())\n", " return render(request, \"base.html\", dict(script=script))\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `base.html` template should be in the `TEMPLATES` `DIRS` directory we declared in the `settings.py` file above. A very basic template might look like this but can be as complex as you need:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```html\n", "\n", "\n", " \n", " Panel in Django: sliders\n", " \n", " \n", " {% block content %}\n", " {{ script|safe }}\t \n", " {% endblock %}\n", " \n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we declare a `urls.py` file to declare the urlpattern where to serve the sliders app to Django:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "from django.urls import path\n", "\n", "from . import views\n", "\n", "app_name = 'sliders'\n", "urlpatterns = [\n", " path('', views.sliders, name='sliders'),\n", "]\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should be able to run this app yourself by changing to the `examples/apps/django2` directory and then running: `python manage.py runserver`; then visit http://localhost:8000/sliders in your browser to try the app." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiple apps\n", "\n", "\n", "This is the most basic configuration for a bokeh server. It is of course possible to add multiple apps in the same way and then registering them with Django in the way described in the [configuration](#Configuration) section above. To see a multi-app Django server have a look at ``examples/apps/django_multi_apps`` and launch it with `python manage.py runserver` as before." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }