diff --git a/.dockerignore b/.dockerignore index b2828c7..bd26c6c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ **.sqlite3 **/static-root -**/media-root -**/media \ No newline at end of file +**/media-root \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1f521f4..cb704c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ OpenShow/media/* -*/static-root/ \ No newline at end of file +*/media-root/ +*/static-root/ +.ipython/profile_default/history.sqlite +*db.sqlite3 \ No newline at end of file diff --git a/.ipython/profile_default/ipython_config.py b/.ipython/profile_default/ipython_config.py new file mode 100644 index 0000000..afe4a6b --- /dev/null +++ b/.ipython/profile_default/ipython_config.py @@ -0,0 +1,2 @@ +c.InteractiveShellApp.extensions = ["autoreload"] +c.InteractiveShellApp.exec_lines = ["%autoreload 2"] \ No newline at end of file diff --git a/.ipython/profile_default/startup/README b/.ipython/profile_default/startup/README new file mode 100644 index 0000000..61d4700 --- /dev/null +++ b/.ipython/profile_default/startup/README @@ -0,0 +1,11 @@ +This is the IPython startup directory + +.py and .ipy files in this directory will be run *prior* to any code or files specified +via the exec_lines or exec_files configurables whenever you load this profile. + +Files will be run in lexicographical order, so you can control the execution order of files +with a prefix, e.g.:: + + 00-first.py + 50-middle.py + 99-last.ipy diff --git a/Dockerfile b/Dockerfile index 4a84542..be6a480 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ RUN pip install -r requirements.txt COPY OpenShow . +RUN apk add ffmpeg + ARG OPENSHOW_DEBUG='False' ARG OPENSHOW_STATIC_ROOT='/static-root' ARG OPENSHOW_MEDIA_ROOT='/media-root' @@ -28,4 +30,4 @@ EXPOSE 8000/ ENTRYPOINT echo Migrating... \ && python manage.py migrate --no-input \ && python manage.py collectstatic --no-input \ - && python -m uvicorn --host 0.0.0.0 --port 8000 OpenShow.asgi:application + && honcho start diff --git a/OpenShow/OpenShow/api.py b/OpenShow/OpenShow/api.py new file mode 100644 index 0000000..f2e00a9 --- /dev/null +++ b/OpenShow/OpenShow/api.py @@ -0,0 +1,5 @@ +from ninja import NinjaAPI + +api = NinjaAPI() + +api.add_router("/slides/", "slides.api.router") \ No newline at end of file diff --git a/OpenShow/OpenShow/asgi.py b/OpenShow/OpenShow/asgi.py index 03e5b6c..4e9e275 100644 --- a/OpenShow/OpenShow/asgi.py +++ b/OpenShow/OpenShow/asgi.py @@ -14,18 +14,7 @@ import os import django from django.core.asgi import get_asgi_application -from django.urls import path, re_path -from channels.routing import ProtocolTypeRouter, URLRouter -from channels.auth import AuthMiddlewareStack -import django_eventstream os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") -application = ProtocolTypeRouter({ - 'http': URLRouter([ - path('events', AuthMiddlewareStack( - URLRouter(django_eventstream.routing.urlpatterns) - ), { 'channels': ['test'] }), - re_path(r'', get_asgi_application()), - ]), -}) \ No newline at end of file +application = get_asgi_application() \ No newline at end of file diff --git a/OpenShow/OpenShow/settings/base.py b/OpenShow/OpenShow/settings/base.py index 32ef4f1..2ef589c 100644 --- a/OpenShow/OpenShow/settings/base.py +++ b/OpenShow/OpenShow/settings/base.py @@ -20,6 +20,7 @@ # Application definition INSTALLED_APPS = [ + 'daphne', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -28,13 +29,15 @@ 'django.contrib.staticfiles', 'slides', 'slides.editor', - 'channels', 'django_eventstream', 'uubloomington_api_connector', 'django_feather', 'pjlink_integration', 'core', - 'deck_from_images', + 'django_srcdoc', + 'neapolitan', + 'django_extensions', + 'django_q', ] MIDDLEWARE = [ @@ -107,3 +110,21 @@ # Remove restriction on number of files uploaded for "deck from images" functionality DATA_UPLOAD_MAX_NUMBER_FILES = None + +# The example from https://django-q2.readthedocs.io/en/master/configure.html#orm-configuration +# We are currently only using django-q2 for managing media transcodes; the ORM broker should be fine. +# If this is ever used for more intensive parts of the system (scheduled slides, auto advance, etc.), it might be +# a good idea to look at a Redis (Valkey) setup instead. For now, simplicity is king. +Q_CLUSTER = { + 'name': 'DjangORM', + 'workers': 4, + 'timeout': None, + 'retry': 2147483647, + 'queue_limit': 50, + 'bulk': 10, + 'orm': 'default' +} + +SHELL_PLUS_IMPORTS = [ + 'from slides.editor.tasks import transcode_video' +] \ No newline at end of file diff --git a/OpenShow/OpenShow/settings/dev.py b/OpenShow/OpenShow/settings/dev.py index 9ff4ae6..c3ff79b 100644 --- a/OpenShow/OpenShow/settings/dev.py +++ b/OpenShow/OpenShow/settings/dev.py @@ -20,5 +20,5 @@ STATIC_URL = 'static/' -MEDIA_ROOT = f'{BASE_DIR}/media/' +MEDIA_ROOT = f'{BASE_DIR}/media-root/' MEDIA_URL = '/media/' diff --git a/OpenShow/OpenShow/urls.py b/OpenShow/OpenShow/urls.py index cd13592..da5ecdb 100644 --- a/OpenShow/OpenShow/urls.py +++ b/OpenShow/OpenShow/urls.py @@ -17,13 +17,16 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +import django_eventstream +from .api import api urlpatterns = [ path('', include('core.urls')), path('admin/', admin.site.urls), + path('api/', api.urls), path('slides/', include('slides.urls')), path('uubloomington/', include('uubloomington_api_connector.urls')), path('pjlink/', include('pjlink_integration.urls')), - path('deck_from_images/', include('deck_from_images.urls')), + path("events/", include(django_eventstream.urls), {"channels": ["test"]}), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/OpenShow/Procfile b/OpenShow/Procfile new file mode 100644 index 0000000..07e3cbd --- /dev/null +++ b/OpenShow/Procfile @@ -0,0 +1,2 @@ +web: python -m uvicorn --host 0.0.0.0 --port 8000 OpenShow.asgi:application +worker: python manage.py qcluster \ No newline at end of file diff --git a/OpenShow/Procfile.development b/OpenShow/Procfile.development new file mode 100644 index 0000000..0ac4ef0 --- /dev/null +++ b/OpenShow/Procfile.development @@ -0,0 +1,2 @@ +web: PYTHONUNBUFFERED=1 python manage.py runserver 8030 +worker: PYTHONUNBUFFERED=1 python manage.py qcluster \ No newline at end of file diff --git a/OpenShow/deck_from_images/__init__.py b/OpenShow/__init__.py similarity index 100% rename from OpenShow/deck_from_images/__init__.py rename to OpenShow/__init__.py diff --git a/OpenShow/core/static/core/extras.css b/OpenShow/core/static/core/extras.css new file mode 100644 index 0000000..afb12ed --- /dev/null +++ b/OpenShow/core/static/core/extras.css @@ -0,0 +1,46 @@ +body > header { + display: grid; + grid-template-columns: 5rem 1fr 5rem; + text-align: center; + height: 6rem; + padding: 0; + margin-bottom: 1rem; +} + +.header-right-button svg, +.header-back-button svg { + height: 100%; + width: 2rem; + margin: auto; +} + +.header-back-button a { + margin: 0; +} + +.sidebar-layout.double { + grid-template-columns: 18rem 2fr 1fr; + height: calc(100dvh - 7rem); +} + +h2 { + margin: 0; +} + +.hidden { + display: none; +} + +@media screen and (max-width: 50rem) { + .sidebar-layout.double { + display: flex; + flex-direction: column; + max-height: calc(100dvh - 7rem); + } +} + +@media screen and (min-width: 1420px) { + .sidebar-layout.double { + grid-template-columns: 18rem 2fr 25rem; + } +} \ No newline at end of file diff --git a/OpenShow/core/static/css/missing.css b/OpenShow/core/static/css/missing.css new file mode 100644 index 0000000..1e06b82 --- /dev/null +++ b/OpenShow/core/static/css/missing.css @@ -0,0 +1 @@ + *,:before,:after{box-sizing:border-box;background-repeat:no-repeat}:before,:after{-webkit-text-decoration:inherit;text-decoration:inherit;vertical-align:inherit}:root{cursor:default;overflow-wrap:break-word;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}abbr[title]{text-decoration:underline dotted}strong,b{font-weight:bolder}small{font-size:80%}audio,canvas,iframe,img,svg,video{vertical-align:middle}svg:not([fill]){fill:currentColor}table{border-collapse:collapse;text-indent:0;border-color:currentColor}button,input,select{margin:0}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}fieldset{border:1px solid #a0a0a0}progress{vertical-align:baseline}textarea{margin:0}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[hidden]{display:none!important}:focus-visible{outline:.2em solid var(--accent);z-index:32}iframe:focus-visible,html:focus-visible,body:focus-visible{outline:none}:target{outline:.2em solid var(--fg);z-index:2}details>summary:first-of-type{display:list-item}[aria-busy=true]{cursor:progress}[aria-disabled=true],[disabled]{cursor:not-allowed}datalist{display:none!important}:root{--gray-0:#f8fafb;--gray-1:#f2f4f6;--gray-2:#ebedef;--gray-3:#e0e4e5;--gray-4:#d1d6d8;--gray-5:#b1b6b9;--gray-6:#979b9d;--gray-7:#7e8282;--gray-8:#666968;--gray-9:#50514f;--gray-10:#3a3a37;--gray-11:#252521;--gray-12:#121210;--red-0:#fff5f5;--red-1:#ffe3e3;--red-2:#ffc9c9;--red-3:#ffa8a8;--red-4:#ff8787;--red-5:#ff6b6b;--red-6:#fa5252;--red-7:#f03e3e;--red-8:#e03131;--red-9:#c92a2a;--red-10:#b02525;--red-11:#962020;--red-12:#7d1a1a;--pink-0:#fff0f6;--pink-1:#ffdeeb;--pink-2:#fcc2d7;--pink-3:#faa2c1;--pink-4:#f783ac;--pink-5:#f06595;--pink-6:#e64980;--pink-7:#d6336c;--pink-8:#c2255c;--pink-9:#a61e4d;--pink-10:#8c1941;--pink-11:#731536;--pink-12:#59102a;--purple-0:#f8f0fc;--purple-1:#f3d9fa;--purple-2:#eebefa;--purple-3:#e599f7;--purple-4:#da77f2;--purple-5:#cc5de8;--purple-6:#be4bdb;--purple-7:#ae3ec9;--purple-8:#9c36b5;--purple-9:#862e9c;--purple-10:#702682;--purple-11:#5a1e69;--purple-12:#44174f;--violet-0:#f3f0ff;--violet-1:#e5dbff;--violet-2:#d0bfff;--violet-3:#b197fc;--violet-4:#9775fa;--violet-5:#845ef7;--violet-6:#7950f2;--violet-7:#7048e8;--violet-8:#6741d9;--violet-9:#5f3dc4;--violet-10:#5235ab;--violet-11:#462d91;--violet-12:#3a2578;--indigo-0:#edf2ff;--indigo-1:#dbe4ff;--indigo-2:#bac8ff;--indigo-3:#91a7ff;--indigo-4:#748ffc;--indigo-5:#5c7cfa;--indigo-6:#4c6ef5;--indigo-7:#4263eb;--indigo-8:#3b5bdb;--indigo-9:#364fc7;--indigo-10:#2f44ad;--indigo-11:#283a94;--indigo-12:#21307a;--blue-0:#e7f5ff;--blue-1:#d0ebff;--blue-2:#a5d8ff;--blue-3:#74c0fc;--blue-4:#4dabf7;--blue-5:#339af0;--blue-6:#228be6;--blue-7:#1c7ed6;--blue-8:#1971c2;--blue-9:#1864ab;--blue-10:#145591;--blue-11:#114678;--blue-12:#0d375e;--cyan-0:#e3fafc;--cyan-1:#c5f6fa;--cyan-2:#99e9f2;--cyan-3:#66d9e8;--cyan-4:#3bc9db;--cyan-5:#22b8cf;--cyan-6:#15aabf;--cyan-7:#1098ad;--cyan-8:#0c8599;--cyan-9:#0b7285;--cyan-10:#095c6b;--cyan-11:#074652;--cyan-12:#053038;--teal-0:#e6fcf5;--teal-1:#c3fae8;--teal-2:#96f2d7;--teal-3:#63e6be;--teal-4:#38d9a9;--teal-5:#20c997;--teal-6:#12b886;--teal-7:#0ca678;--teal-8:#099268;--teal-9:#087f5b;--teal-10:#066649;--teal-11:#054d37;--teal-12:#033325;--green-0:#ebfbee;--green-1:#d3f9d8;--green-2:#b2f2bb;--green-3:#8ce99a;--green-4:#69db7c;--green-5:#51cf66;--green-6:#40c057;--green-7:#37b24d;--green-8:#2f9e44;--green-9:#2b8a3e;--green-10:#237032;--green-11:#1b5727;--green-12:#133d1b;--lime-0:#f4fce3;--lime-1:#e9fac8;--lime-2:#d8f5a2;--lime-3:#c0eb75;--lime-4:#a9e34b;--lime-5:#94d82d;--lime-6:#82c91e;--lime-7:#74b816;--lime-8:#66a80f;--lime-9:#5c940d;--lime-10:#4c7a0b;--lime-11:#3c6109;--lime-12:#2c4706;--yellow-0:#fff9db;--yellow-1:#fff3bf;--yellow-2:#ffec99;--yellow-3:#ffe066;--yellow-4:#ffd43b;--yellow-5:#fcc419;--yellow-6:#fab005;--yellow-7:#f59f00;--yellow-8:#f08c00;--yellow-9:#e67700;--yellow-10:#b35c00;--yellow-11:#804200;--yellow-12:#663500;--orange-0:#fff4e6;--orange-1:#ffe8cc;--orange-2:#ffd8a8;--orange-3:#ffc078;--orange-4:#ffa94d;--orange-5:#ff922b;--orange-6:#fd7e14;--orange-7:#f76707;--orange-8:#e8590c;--orange-9:#d9480f;--orange-10:#bf400d;--orange-11:#99330b;--orange-12:#802b09;--choco-0:#fff8dc;--choco-1:#fce1bc;--choco-2:#f7ca9e;--choco-3:#f1b280;--choco-4:#e99b62;--choco-5:#df8545;--choco-6:#d46e25;--choco-7:#bd5f1b;--choco-8:#a45117;--choco-9:#8a4513;--choco-10:#703a13;--choco-11:#572f12;--choco-12:#3d210d;--brown-0:#faf4eb;--brown-1:#ede0d1;--brown-2:#e0cab7;--brown-3:#d3b79e;--brown-4:#c5a285;--brown-5:#b78f6d;--brown-6:#a87c56;--brown-7:#956b47;--brown-8:#825b3a;--brown-9:#6f4b2d;--brown-10:#5e3a21;--brown-11:#4e2b15;--brown-12:#422412;--sand-0:#f8fafb;--sand-1:#e6e4dc;--sand-2:#d5cfbd;--sand-3:#c2b9a0;--sand-4:#aea58c;--sand-5:#9a9178;--sand-6:#867c65;--sand-7:#736a53;--sand-8:#5f5746;--sand-9:#4b4639;--sand-10:#38352d;--sand-11:#252521;--sand-12:#121210;--camo-0:#f9fbe7;--camo-1:#e8ed9c;--camo-2:#d2df4e;--camo-3:#c2ce34;--camo-4:#b5bb2e;--camo-5:#a7a827;--camo-6:#999621;--camo-7:#8c851c;--camo-8:#7e7416;--camo-9:#6d6414;--camo-10:#5d5411;--camo-11:#4d460e;--camo-12:#36300a;--jungle-0:#ecfeb0;--jungle-1:#def39a;--jungle-2:#d0e884;--jungle-3:#c2dd6e;--jungle-4:#b5d15b;--jungle-5:#a8c648;--jungle-6:#9bbb36;--jungle-7:#8fb024;--jungle-8:#84a513;--jungle-9:#7a9908;--jungle-10:#658006;--jungle-11:#516605;--jungle-12:#3d4d04}html{font-family:var(--main-font);line-height:var(--rhythm);background:var(--bg);color:var(--fg);scroll-padding-block-start:calc(4*var(--gap))}body{margin:0}header,footer,section+section{margin-block:calc(2*var(--gap))}nav a{color:var(--accent);text-decoration:none}aside{font-size:.8em;line-height:calc(var(--rhythm)*2/3);--gap:calc(var(--rhythm)*var(--density)*2/3);border-block:1px solid var(--graphical-fg);padding-block:var(--gap);margin-block:calc(var(--gap)*3/2)}aside.bg{padding-inline:var(--gap)}aside h1,aside h2,aside h3,aside h4,aside h5,aside h6{text-transform:none;letter-spacing:none;font-size:1em}aside.big{color:var(--accent);background:0 0;border:none;border-radius:0;padding:0;font-style:italic}aside.big:not(:lang(ae)):not(:lang(ar)):not(:lang(arc)):not(:lang(bcc)):not(:lang(bqi)):not(:lang(ckb)):not(:lang(dv)):not(:lang(fa)):not(:lang(glk)):not(:lang(he)):not(:lang(ku)):not(:lang(mzn)):not(:lang(nqo)):not(:lang(pnb)):not(:lang(ps)):not(:lang(sd)):not(:lang(ug)):not(:lang(ur)):not(:lang(yi)){border-left:1px solid var(--muted-fg);padding-left:var(--rhythm)}aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--muted-fg);padding-right:var(--rhythm)}aside.big:-webkit-any(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),aside.big:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)){border-right:1px solid var(--muted-fg);padding-right:var(--rhythm)}h1,h2,h3,h4,h5,h6,.\