Skip to content
Snippets Groups Projects
Commit 873aac47 authored by jan.bednarik's avatar jan.bednarik
Browse files

uniweb: Remove Jupyter notebooks

parent 8ffb3f8d
No related branches found
No related tags found
2 merge requests!173Release,!172Upgrade
Pipeline #2342 passed
......@@ -4,4 +4,4 @@ line_length = 88
multi_line_output = 3
default_section = "THIRDPARTY"
include_trailing_comma = true
known_third_party = arrow,django,environ,faker,ics,markdown,modelcluster,nbconvert,pirates,pytest,pytz,requests,sentry_sdk,snapshottest,taggit,traitlets,wagtail,wagtailmetadata
known_third_party = arrow,django,environ,faker,ics,markdown,modelcluster,pirates,pytest,pytz,requests,sentry_sdk,snapshottest,taggit,wagtail,wagtailmetadata
......@@ -79,20 +79,6 @@ Kalendář se stáhne při uložení modelu obsahujícího `CalendarMixin`.
Appka přidává management command `update_callendars`, který stahuje a updatuje
kalendáře. Je třeba ho pravidelně volat na pozadí (přes CRON).
### Jupyter notebooky
Appka Uniweb umí vložit do stránky Jupyter notebook a zobrazit jeho výstup.
Pokud některé buňky nechceš generovat do výstupní stránky, nastav u nich tag
"exclude".
Pokud chceš generovat jen výstup dané buňky, použij tag "output"
Pozor: u plotly grafů je nutno zadat tagem "output" výstup buňky s inicializací
knihovny, tedy něco kde je "import plotly" apod. Pokud celou takovou buňku
vynecháš tagem "exclude", žádné grafy se nezobrazí.
## Deployment
### Konfigurace
......
......@@ -12,6 +12,4 @@ requests
ics
arrow
sentry-sdk
nbconvert<6
traitlets
Markdown
......@@ -7,14 +7,11 @@
anyascii==0.1.7 # via wagtail
arrow==0.14.7 # via -r base.in, ics
asgiref==3.3.1 # via django
attrs==20.3.0 # via jsonschema
beautifulsoup4==4.8.2 # via wagtail
bleach==3.3.0 # via nbconvert
certifi==2020.12.5 # via requests, sentry-sdk
cffi==1.14.4 # via cryptography
chardet==4.0.0 # via requests
cryptography==3.4.3 # via josepy, mozilla-django-oidc, pyopenssl
defusedxml==0.6.0 # via nbconvert
django-environ==0.4.5 # via -r base.in
django-extensions==3.1.1 # via -r base.in
django-filter==2.4.0 # via wagtail
......@@ -26,53 +23,37 @@ django-treebeard==4.4 # via wagtail
django==3.1.6 # via django-filter, django-redis, django-settings-export, django-taggit, django-treebeard, djangorestframework, mozilla-django-oidc, wagtail
djangorestframework==3.12.2 # via wagtail
draftjs-exporter==2.1.7 # via wagtail
entrypoints==0.3 # via nbconvert
et-xmlfile==1.0.1 # via openpyxl
html5lib==1.1 # via wagtail
ics==0.7 # via -r base.in
idna==2.10 # via requests
ipython-genutils==0.2.0 # via nbformat, traitlets
jdcal==1.4.1 # via openpyxl
jinja2==2.11.3 # via nbconvert
josepy==1.6.0 # via mozilla-django-oidc
jsonschema==3.2.0 # via nbformat
jupyter-core==4.7.1 # via nbconvert, nbformat
l18n==2020.6.1 # via wagtail
markdown==3.3.3 # via -r base.in
markupsafe==1.1.1 # via jinja2
mistune==0.8.4 # via nbconvert
mozilla-django-oidc==1.2.4 # via pirates
nbconvert==5.6.1 # via -r base.in
nbformat==5.1.2 # via nbconvert
numpy==1.20.1 # via opencv-python
opencv-python==4.5.1.48 # via -r base.in
openpyxl==3.0.6 # via tablib
packaging==20.9 # via bleach
pandocfilters==1.4.3 # via nbconvert
pillow==8.1.0 # via wagtail
pirates==0.5.0 # via -r base.in
psycopg2-binary==2.8.6 # via -r base.in
pycparser==2.20 # via cffi
pygments==2.7.4 # via nbconvert
pyopenssl==20.0.1 # via josepy
pyparsing==2.4.7 # via packaging
pyrsistent==0.17.3 # via jsonschema
python-dateutil==2.8.1 # via arrow, ics
pytz==2021.1 # via django, django-modelcluster, l18n
redis==3.5.3 # via django-redis
requests==2.25.1 # via -r base.in, mozilla-django-oidc, wagtail
sentry-sdk==0.19.5 # via -r base.in
six==1.15.0 # via bleach, html5lib, ics, josepy, jsonschema, l18n, mozilla-django-oidc, pyopenssl, python-dateutil
six==1.15.0 # via html5lib, ics, josepy, l18n, mozilla-django-oidc, pyopenssl, python-dateutil
soupsieve==2.1 # via beautifulsoup4
sqlparse==0.4.1 # via django
tablib[xls,xlsx]==3.0.0 # via wagtail
tatsu==5.5.0 # via ics
testpath==0.4.4 # via nbconvert
traitlets==5.0.5 # via -r base.in, jupyter-core, nbconvert, nbformat
urllib3==1.26.3 # via requests, sentry-sdk
wagtail-metadata==3.4.0 # via -r base.in
wagtail==2.12 # via -r base.in, wagtail-metadata
webencodings==0.5.1 # via bleach, html5lib
webencodings==0.5.1 # via html5lib
whitenoise==5.2.0 # via -r base.in
willow==1.4 # via wagtail
xlrd==2.0.1 # via tablib
......
......@@ -208,10 +208,6 @@ class UniwebContentMixin(models.Model):
template="uniweb/blocks/table.html",
),
),
(
"jupyter",
DocumentChooserBlock(label="Jupyter notebook", group="ostatní"),
),
],
verbose_name="obsah stránky",
blank=True,
......
{%- extends 'display_priority.tpl' -%}
{% from 'celltags.tpl' import celltags %}
{% block codecell %}
<div class="cell border-box-sizing code_cell rendered{{ celltags(cell) }}">
{{ super() }}
</div>
{%- endblock codecell %}
{% block input_group -%}
<div class="input">
{{ super() }}
</div>
{% endblock input_group %}
{% block output_group %}
<div class="output_wrapper">
<div class="output">
{{ super() }}
</div>
<div class="output_divider" style="margin-bottom: 5px;">
</div>
</div>
{% endblock output_group %}
{% block in_prompt -%}
<div class="prompt input_prompt">
{%- if cell.execution_count is defined -%}
In&nbsp;[{{ cell.execution_count|replace(None, "&nbsp;") }}]:
{%- else -%}
In&nbsp;[&nbsp;]:
{%- endif -%}
</div>
{%- endblock in_prompt %}
{% block empty_in_prompt -%}
<div class="prompt input_prompt">
</div>
{%- endblock empty_in_prompt %}
{#
output_prompt doesn't do anything in HTML,
because there is a prompt div in each output area (see output block)
#}
{% block output_prompt %}
{% endblock output_prompt %}
{% block input %}
<div class="inner_cell">
<div class="input_area">
{{ cell.source }}
</div>
</div>
{%- endblock input %}
{% block output_area_prompt %}
{%- if output.output_type == 'execute_result' -%}
<div class="prompt output_prompt">
{%- if cell.execution_count is defined -%}
Out[{{ cell.execution_count|replace(None, "&nbsp;") }}]:
{%- else -%}
Out[&nbsp;]:
{%- endif -%}
{%- else -%}
<div class="prompt">
{%- endif -%}
</div>
{% endblock output_area_prompt %}
{% block output %}
<div class="output_area">
{% if resources.global_content_filter.include_output_prompt %}
{{ self.output_area_prompt() }}
{% endif %}
{{ super() }}
</div>
{% endblock output %}
{% block markdowncell scoped %}
<div class="cell border-box-sizing text_cell rendered{{ celltags(cell) }}">
{%- if resources.global_content_filter.include_input_prompt-%}
{{ self.empty_in_prompt() }}
{%- endif -%}
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
{{ cell.source | markdown2html | strip_files_prefix }}
</div>
</div>
</div>
{%- endblock markdowncell %}
{% block unknowncell scoped %}
unknown type {{ cell.type }}
{% endblock unknowncell %}
{% block execute_result -%}
{%- set extra_class="output_execute_result" -%}
{% block data_priority scoped %}
{{ super() }}
{% endblock data_priority %}
{%- set extra_class="" -%}
{%- endblock execute_result %}
{% block stream_stdout -%}
<div class="output_subarea output_stream output_stdout output_text">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stdout %}
{% block stream_stderr -%}
<div class="output_subarea output_stream output_stderr output_text">
<pre>
{{- output.text | ansi2html -}}
</pre>
</div>
{%- endblock stream_stderr %}
{% block data_svg scoped -%}
<div class="output_svg output_subarea {{ extra_class }}">
{%- if output.svg_filename %}
<img src="{{ output.svg_filename | posix_path }}">
{%- else %}
{{ output.data['image/svg+xml'] }}
{%- endif %}
</div>
{%- endblock data_svg %}
{% block data_html scoped -%}
<div class="output_html rendered_html output_subarea {{ extra_class }}">
{{ output.data['text/html'] }}
</div>
{%- endblock data_html %}
{% block data_markdown scoped -%}
<div class="output_markdown rendered_html output_subarea {{ extra_class }}">
{{ output.data['text/markdown'] | markdown2html }}
</div>
{%- endblock data_markdown %}
{% block data_png scoped %}
<div class="output_png output_subarea {{ extra_class }}">
{%- if 'image/png' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/png'] | posix_path }}"
{%- else %}
<img src="data:image/png;base64,{{ output.data['image/png'] }}"
{%- endif %}
{%- set width=output | get_metadata('width', 'image/png') -%}
{%- if width is not none %}
width={{ width }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/png') -%}
{%- if height is not none %}
height={{ height }}
{%- endif %}
{%- if output | get_metadata('unconfined', 'image/png') %}
class="unconfined"
{%- endif %}
{%- set alttext=(output | get_metadata('alt', 'image/png')) or (cell | get_metadata('alt')) -%}
{%- if alttext is not none %}
alt="{{ alttext }}"
{%- endif %}
>
</div>
{%- endblock data_png %}
{% block data_jpg scoped %}
<div class="output_jpeg output_subarea {{ extra_class }}">
{%- if 'image/jpeg' in output.metadata.get('filenames', {}) %}
<img src="{{ output.metadata.filenames['image/jpeg'] | posix_path }}"
{%- else %}
<img src="data:image/jpeg;base64,{{ output.data['image/jpeg'] }}"
{%- endif %}
{%- set width=output | get_metadata('width', 'image/jpeg') -%}
{%- if width is not none %}
width={{ width }}
{%- endif %}
{%- set height=output | get_metadata('height', 'image/jpeg') -%}
{%- if height is not none %}
height={{ height }}
{%- endif %}
{%- if output | get_metadata('unconfined', 'image/jpeg') %}
class="unconfined"
{%- endif %}
{%- set alttext=(output | get_metadata('alt', 'image/jpeg')) or (cell | get_metadata('alt')) -%}
{%- if alttext is not none %}
alt="{{ alttext }}"
{%- endif %}
>
</div>
{%- endblock data_jpg %}
{% block data_latex scoped %}
<div class="output_latex output_subarea {{ extra_class }}">
{{ output.data['text/latex'] }}
</div>
{%- endblock data_latex %}
{% block error -%}
<div class="output_subarea output_text output_error">
<pre>
{{- super() -}}
</pre>
</div>
{%- endblock error %}
{%- block traceback_line %}
{{ line | ansi2html }}
{%- endblock traceback_line %}
{%- block data_text scoped %}
<div class="output_text output_subarea {{ extra_class }}">
<pre>
{{- output.data['text/plain'] | ansi2html -}}
</pre>
</div>
{%- endblock -%}
{%- block data_javascript scoped %}
{% set div_id = uuid4() %}
<div id="{{ div_id }}"></div>
<div class="output_subarea output_javascript {{ extra_class }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
{{ output.data['application/javascript'] }}
</script>
</div>
{%- endblock -%}
{%- block data_widget_state scoped %}
{% set div_id = uuid4() %}
{% set datatype_list = output.data | filter_data_type %}
{% set datatype = datatype_list[0]%}
<div id="{{ div_id }}"></div>
<div class="output_subarea output_widget_state {{ extra_class }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps }}
</script>
</div>
{%- endblock data_widget_state -%}
{%- block data_widget_view scoped %}
{% set div_id = uuid4() %}
{% set datatype_list = output.data | filter_data_type %}
{% set datatype = datatype_list[0]%}
<div id="{{ div_id }}"></div>
<div class="output_subarea output_widget_view {{ extra_class }}">
<script type="text/javascript">
var element = $('#{{ div_id }}');
</script>
<script type="{{ datatype }}">
{{ output.data[datatype] | json_dumps }}
</script>
</div>
{%- endblock data_widget_view -%}
{%- block footer %}
{% set mimetype = 'application/vnd.jupyter.widget-state+json'%}
{% if mimetype in nb.metadata.get("widgets",{})%}
<script type="{{ mimetype }}">
{{ nb.metadata.widgets[mimetype] | json_dumps }}
</script>
{% endif %}
{{ super() }}
{%- endblock footer-%}
{%- macro celltags(cell) -%}
{% if cell.metadata.tags | length > 0 -%}
{% for tag in cell.metadata.tags -%}
{{ ' celltag_' ~ tag -}}
{%- endfor -%}
{%- endif %}
{%- endmacro %}
{%- macro mathjax(url='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_HTML') -%}
<!-- Load mathjax -->
<script src="{{url}}"></script>
<!-- MathJax configuration -->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true,
processEnvironments: true
},
// Center justify equations in code and markdown cells. Elsewhere
// we use CSS to left justify single line equations in code cells.
displayAlign: 'center',
"HTML-CSS": {
styles: {'.MathJax_Display': {"margin": 0}},
linebreaks: { automatic: true }
}
});
</script>
<!-- End of mathjax configuration -->
{%- endmacro %}
{%- extends 'basic.tpl' -%}
{% from 'mathjax.tpl' import mathjax %}
{%- block header -%}
{%- block html_head -%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
{% block ipywidgets %}
{%- if "widgets" in nb.metadata -%}
<script>
(function() {
function addWidgetsRenderer() {
var mimeElement = document.querySelector('script[type="application/vnd.jupyter.widget-view+json"]');
var scriptElement = document.createElement('script');
var widgetRendererSrc = '{{ resources.ipywidgets_base_url }}@jupyter-widgets/html-manager@*/dist/embed-amd.js';
var widgetState;
// Fallback for older version:
try {
widgetState = mimeElement && JSON.parse(mimeElement.innerHTML);
if (widgetState && (widgetState.version_major < 2 || !widgetState.version_major)) {
widgetRendererSrc = '{{ resources.ipywidgets_base_url }}jupyter-js-widgets@*/dist/embed.js';
}
} catch(e) {}
scriptElement.src = widgetRendererSrc;
document.body.appendChild(scriptElement);
}
document.addEventListener('DOMContentLoaded', addWidgetsRenderer);
}());
</script>
{%- endif -%}
{% endblock ipywidgets %}
{% for css in resources.inlining.css -%}
<style type="text/css">
{{ css }}
</style>
{% endfor %}
<style type="text/css">
/* Overrides of notebook CSS for static HTML export */
div#notebook {
overflow: visible;
border-top: none;
}
{%- if resources.global_content_filter.no_prompt-%}
div#notebook-container{
padding: 6ex 12ex 8ex 12ex;
}
{%- endif -%}
@media print {
div.cell {
display: block;
page-break-inside: avoid;
}
div.output_wrapper {
display: block;
page-break-inside: avoid;
}
div.output {
display: block;
page-break-inside: avoid;
}
}
</style>
<!-- Loading mathjax macro -->
{{ mathjax() }}
{%- endblock html_head -%}
{%- endblock header -%}
{% block body %}
<div tabindex="-1" id="notebook" class="border-box-sizing">
<div class="container" id="notebook-container">
{{ super() }}
</div>
</div>
{%- endblock body %}
{% block footer %}
{{ super() }}
{% endblock footer %}
{% load uniweb_filters wagtailcore_tags wagtailimages_tags %}
{% load wagtailcore_tags wagtailimages_tags %}
<section class="mb-8 lg:mb-16">
{% for block in page.content %}
{% if block.block_type == "jupyter" %}
<div class="content-block my-4 clearfix{% if forloop.first %} mt-8 lg:mt-12{% endif %}">
{{ block|jupyterize }}
</div>
{% else %}
{% include_block block with first=forloop.first %}
{% endif %}
{% include_block block with first=forloop.first %}
{% endfor %}
</section>
# definice vlastnich tagu pro templates
import os
import nbconvert
from django import template
from django.utils.safestring import mark_safe
from traitlets.config import Config
register = template.Library()
@register.filter
def jupyterize(value):
"""Exportuje jupyterovsky notebook do raw HTML s vyuzitim vlastnich templates.
Vynechava tagy
"""
# lokalni soubor
filename = os.path.join(value.value.file.storage.location, value.value.file.name)
# konvertuj do HTML s pouzitim vlastni template
c = Config()
c.TagRemovePreprocessor.enabled = True # Nutne
c.TagRemovePreprocessor.remove_cell_tags = ["exclude"]
c.TagRemovePreprocessor.remove_input_tags = ["output"]
c.TemplateExporter.exclude_output_prompt = (
True # potlaci prazdne vystupy typu "Out[8]"
)
c.preprocessors = ["TagRemovePreprocessor"]
nb_body, _ = nbconvert.TemplateExporter(
config=c, template_file="uniweb/templates/jupyter/my.tpl"
).from_filename(filename)
# HACK: fucking ugly. Ale netusim kde se to tam bere, asi nekde v hloubi notebookovskych templates
if nb_body.startswith("None"):
nb_body = nb_body[4:]
return mark_safe(nb_body)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment