Tutorials
- Getting Started - a walk-through from scratch that gets you up and running
Documentation
Documentation explaining and showing how Hypergen works.
- Python Templates
- Liveviews
- Websockets
- Django HTML Templates
- Form inputs
- Client commands
- Partial loading and history support
- Hypergens global immutable context
- Notifications from Django messages
App examples
These are examples of writing a Django app with Hypergen. Be sure to read the sources.
- Hello hypergen
- Hello core only hypergen - no magic from liveview.py used
- TodoMVC
- Hypergen App template
- Shoot 'Em Duck
- Chat app using websockets
Other template implementations
- Game of life in pure c++ with Cython - example of the alpha Cython implementation.
Compatibility
Hypergen is tested on the following combinations of Django and Python:
python 3.6 | python 3.7 | python 3.8 | python 3.9 | python 3.10 | python 3.11 | python 3.12 | |
---|---|---|---|---|---|---|---|
django 1.11.29 | ✔ | ✔ | ✔ | ||||
django 2.0.13 | ✔ | ✔ | ✔ | ✔ | |||
django 2.1.15 | ✔ | ✔ | ✔ | ✔ | |||
django 2.2.28 | ✔ | ✔ | ✔ | ✔ | |||
django 3.0.14 | ✔ | ✔ | ✔ | ✔ | |||
django 3.1.14 | ✔ | ✔ | ✔ | ✔ | |||
django 3.2.22 | ✔ | ✔ | ✔ | ✔ | |||
django 4.0.10 | ✔ | ✔ | ✔ | ||||
django 4.1.12 | ✔ | ✔ | ✔ | ✔ | ✔ | ||
django 4.2.6 | ✔ | ✔ | ✔ | ✔ | ✔ |
Both pytest integration and testcafe end-to-end tests are run over the entire matrix.
Hypergen supports all browser versions from IE10 and forward.
Show sources
minidemoes/shoot_em_up.pyfrom hypergen.imports import *
from random import choice
from time import time
from django.templatetags.static import static
from django.views.decorators.cache import never_cache
from website.templates2 import base_example_template
@never_cache
@liveview(perm=NO_PERM_REQUIRED)
def shoot_em_up(request):
with base_example_template(__file__):
script("""
function play(url) {
new Audio(url).play()
}
""")
template(start_time=time())
@action(perm=NO_PERM_REQUIRED, target_id="shoot-em-up")
def fire(request, start_time, hits, target_num):
template(start_time, hits=hits + 1, target_num=target_num)
command("play", static("website/gun.mp3"))
def template(start_time=None, hits=0, target_num=-1):
target_num = choice(list(set(range(0, 5)) - {target_num}))
with div(id="shoot-em-up"):
for i in range(0, 5):
if i == target_num:
img(
id="fire",
src=static("website/target.svg"),
onmousedown=callback(fire, start_time, hits, target_num),
)
else:
img(src=static("website/duck.svg"))
if hits:
rate = round(hits / (time() - start_time), 2)
div(b("hits: "), hits)
div(b("rate: "), rate, "/s")
else:
div(b("warning:"), "🔊", sep=" ")
minidemoes/shoot_em_up_alt2.pyfrom hypergen.imports import *
from hypergen.incubation import SessionStore
from random import choice
from time import time
from django.templatetags.static import static
from django.views.decorators.cache import never_cache
from website.templates2 import doc_base_template
settings = dict(perm=NO_PERM_REQUIRED, base_template=doc_base_template(__file__, "shoot-em-up"))
class State(SessionStore):
def reset(self):
self.target_num = -1
self.hits = 0
self.start_time = time()
state = State()
@never_cache
@liveview(**settings)
def shoot_em_up_alt(request):
if request.method == "GET":
script("""
function play(url) {
new Audio(url).play()
}
""")
state.reset()
state.target_num = choice(list(set(range(0, 5)) - {state.target_num}))
for i in range(0, 5):
if i == state.target_num:
img(id="fire_alt", src=static("website/target.svg"), onmousedown=callback(fire_alt))
else:
img(src=static("website/duck.svg"))
if state.hits:
rate = round(state.hits / (time() - state.start_time), 2)
div(b("hits: "), state.hits)
div(b("rate: "), rate, "/s")
else:
div(b("warning:"), "🔊", sep=" ")
@action(base_view=shoot_em_up_alt, **settings)
def fire_alt(request):
state.hits += 1
command("play", static("website/gun.mp3"))
minidemoes/shoot_em_up_alt.pyfrom hypergen.imports import *
from random import choice
from time import time
from django.templatetags.static import static
from django.views.decorators.cache import never_cache
from website.templates2 import doc_base_template
def init_appstate():
return {"target_num": -1, "hits": 0, "start_time": time()}
settings = dict(
perm=NO_PERM_REQUIRED,
base_template=doc_base_template(__file__, "shoot-em-up"),
appstate=init_appstate,
)
@never_cache
@liveview(**settings)
def shoot_em_up_alt(request):
if request.method == "GET":
script("""
function play(url) {
new Audio(url).play()
}
""")
context.hypergen.appstate = init_appstate()
context.hypergen.appstate["target_num"] = choice(
list(set(range(0, 5)) - {context.hypergen.appstate["target_num"]}))
for i in range(0, 5):
if i == context.hypergen.appstate["target_num"]:
img(id="fire_alt", src=static("website/target.svg"), onmousedown=callback(fire_alt))
else:
img(src=static("website/duck.svg"))
if context.hypergen.appstate["hits"]:
rate = round(context.hypergen.appstate["hits"] / (time() - context.hypergen.appstate["start_time"]), 2)
div(b("hits: "), context.hypergen.appstate["hits"])
div(b("rate: "), rate, "/s")
else:
div(b("warning:"), "🔊", sep=" ")
@action(base_view=shoot_em_up_alt, **settings)
def fire_alt(request):
context.hypergen.appstate["hits"] += 1
command("play", static("website/gun.mp3"))
static/website/website.jshypergen.ready(() => {
console.log("website.js ready() called.")
hljs.configure({ignoreUnescapedHTML: true})
hljs.highlightAll()
document.querySelectorAll('pre.literal-block').forEach((el) => {
hljs.highlightElement(el)
})
}, {partial: true})
urls.pyfrom hypergen.hypergen import autourls
from website import views
from website.minidemoes import shoot_em_up, shoot_em_up_alt
app_name = 'website'
urlpatterns = autourls(views, namespace="website") + autourls(shoot_em_up, namespace="website") + autourls(
shoot_em_up_alt, namespace="website")
views.pyfrom hypergen.imports import *
from hypergen.plugins.alertify import AlertifyPlugin
from collections import defaultdict
from yaml import load
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
from django.urls import reverse
from notifications.views import notifications
from partialload.views import page1
from todomvc.views import todomvc, ALL
from inputs.views import inputs
from gameofcython.views import gameofcython
from djangotemplates.views import djangotemplates
from hellohypergen.views import counter
from website.templates2 import base_template, show_sources, base_template_monokai
from commands.views import commands
from globalcontext.views import globalcontext
from gettingstarted.views import begin
from apptemplate.views import my_view
from coredocs.views import template, liveviews
from website.minidemoes.shoot_em_up import shoot_em_up
from websockets.views import chat
from features import templates as features_templates
@liveview(re_path="^$", perm=NO_PERM_REQUIRED, base_template=base_template_monokai, user_plugins=[AlertifyPlugin()])
def home(request):
if hasattr(request, 'session') and not request.session.session_key:
request.session.save()
request.session.modified = True
with div(class_="hero"):
h2("Build reactive web apps, without leaving Django", class_="center hero")
p(i("Stay focused on the actual complexity of your app, while having 291% more fun!", sep=" "),
class_="center")
hr()
features_templates.main()
h2("Why hypergen?")
p("For a more technical explanation about the ", i("what"), " and the ", i("how"), ", check out our ",
a("github page", href="https://github.com/runekaagaard/django-hypergen/"), " and read the ",
a("documentation", href="/documentation/"), ".")
p("We've been building Django apps for over 12 years, and have played around with different "
"approaches. From nojs html pages, over jQuery enhanced apps to full blown server/client "
"separation with React. We love composing html with jsx but felt a lot of our time was spent "
"munging data between server and client, duplicating logic and keeping up with the extremely "
"vast and ever-changing javascript ecosystem.")
p("We felt there was a better way. For us.")
p("We wanted something that feels like ", i("one single thing."), " Just like Django does. ",
"We felt using html templates creates a second language and doesn't compose well. We also wanted ",
"pain-free binding of DOM events to server functions as well as a simple way to instruct ",
"the client to run javascript functions.")
p("And Hypergen was born.")
p("Thanks for reading this, drop by and ",
a("say hi", href="https://github.com/runekaagaard/django-hypergen/discussions"), " :)")
h2("Super duper quickstart")
p("Read the ", a("Getting Started", href="/gettingstarted/begin/"), " tutorial for a more thorough experience. ",
"For the most speedy start possible, run the following in your terminal.",
' Replace "myproject" and "myapp" with your own names.')
with div(class_="terminals", id_="quickstart"):
quickstart_template()
@action(perm=NO_PERM_REQUIRED, target_id="quickstart")
def quickstart(request, n, app_name):
quickstart_template(n=n, app_name=app_name)
def quickstart_template(n=0, app_name="myapp"):
div(
a("new project", onmousedown=callback(quickstart, 0, app_name), id_="b2",
class_="selected" if n == 0 else OMIT),
a("new app", onmousedown=callback(quickstart, 1, app_name), id_="b1", class_="selected" if n == 1 else OMIT),
)
# Project template
if n == 0:
with div(id_="startproject", class_="inner"):
pre(code(PROJECT), class_="terminal nohighlight")
p("Enjoy your new hypergen project at ", a("http://127.0.0.1:8000/", href="http://127.0.0.1:8000/"), " 🚀")
# App template
if n == 1:
with div(id_="startapp", class_="inner"):
pre(
code(APP),
class_="terminal nohighlight",
)
with ol():
li("Add", code("'hypergen'"), "and", code("'myapp'"), "to", code("INSTALLED_APPS"), "in",
code("settings.py"), sep=" ")
li("Add", code("'hypergen.context.context_middleware'"), "to", code("MIDDLEWARE"), "in",
code("settings.py"), sep=" ")
li("Add", code("path('myapp/', include(myapp.urls, namespace='myapp')),", title=IMPORTS), "to the ",
code("urlpatterns"), "variable of your projects main", code("urls.py"), "file", sep=" ")
p("Enjoy your new hypergen app at", code("http://127.0.0.1:8000/myapp/my_view/"), "🤯", sep=" ")
IMPORTS = """
Don't forget these imports:
from django.urls import path, include
import myapp.urls
""".strip()
PROJECT = """
python3 -m venv venv
source venv/bin/activate
pip install django django-hypergen
django-admin startproject \\
--template=https://github.com/runekaagaard/django-hypergen-project-template/archive/master.zip \\
myproject
cd myproject
python manage.py migrate
python manage.py runserver
""".strip()
APP = """
python manage.py startapp \\
--template=https://github.com/runekaagaard/django-hypergen-app-template/archive/master.zip \\
myapp
""".strip()
@liveview(perm=NO_PERM_REQUIRED, base_template=base_template)
def documentation(request):
h2("Tutorials")
ul(li(a("Getting Started", href=begin.reverse()), " - a walk-through from scratch that gets you up and running"))
h2("Documentation")
p("Documentation explaining and showing how Hypergen works.")
with ul():
li(a("Python Templates", href=template.reverse()))
li(a("Liveviews", href=liveviews.reverse()))
li(a("Websockets", href="/coredocs/websockets/"))
li(a("Django HTML Templates", href="/coredocs/django_templates/"))
li(a("Form inputs", href=inputs.reverse()))
li(a("Client commands", href=commands.reverse()))
li(a("Partial loading and history support", href=page1.reverse()))
li(a("Hypergens global immutable context", href=globalcontext.reverse()))
li(a("Notifications from Django messages", href=notifications.reverse()))
h2("App examples")
p("These are examples of writing a Django app with Hypergen. ", "Be sure to read the sources.")
with ul():
li(a("Hello hypergen", href=counter.reverse()))
li(a("Hello core only hypergen", href=reverse("hellocoreonly:counter")), " - no magic from liveview.py used")
li(a("TodoMVC", href=todomvc.reverse(ALL)))
li(a("Hypergen App template", href=my_view.reverse()))
li(a("Shoot 'Em Duck", href=shoot_em_up.reverse()))
li(a("Chat app using websockets", href=chat.reverse()), sep=" ")
h2("Other template implementations")
with ul():
li(a("Game of life in pure c++ with Cython", href=gameofcython.reverse()),
" - example of the alpha Cython implementation.")
h2("Compatibility")
p("Hypergen is ", a("tested", href="https://github.com/runekaagaard/django-hypergen/actions"),
" on the following combinations of Django and Python:", sep=" ")
with open("../.github/workflows/pytest.yml") as f:
pytest = load(f.read(), Loader=Loader)
adjm, xs, ys = defaultdict(list), set(), set()
for combination in pytest["jobs"]["build"]["strategy"]["matrix"]["include"]:
py, dj = combination["python-version"], combination["django-version"].replace("Django==", "")
adjm[py].append(dj)
xs.add(py)
ys.add(dj)
xs = sorted(xs, key=lambda x: int(str(x).split(".")[-1]))
ys = sorted(ys)
with table():
tr(th(), [th("python ", x) for x in xs])
for y in ys:
tr(th("django ", y),
[td("✔" if y in adjm[x] else "", style={"color": "green", "text-align": "center"}) for x in xs])
p("Both pytest integration and testcafe end-to-end tests are run over the entire matrix.")
p("Hypergen supports all browser versions from IE10 and forward.")
show_sources(__file__)
@liveview(perm=NO_PERM_REQUIRED, base_template=base_template)
def news(request):
h2("News")
ul(
li(b("2023-12-11"), "Releases", i("1.5.5"), "with various minor QOL improvements.", sep=" "),
li(b("2023-11-18"), "Releases", i("1.5.4"),
"which is a small bugfix release. Hypergen now again works without having channels installed.", sep=" "),
li(
b("2023-10-28"),
"A lot of recent releases only made master and not pypi but today we are proud to",
"announce that we are releasing the first stable release of Hypergen. We've been using it in production",
"for several years, built big systems with it and it's rock-solid enough to warrant the version number ",
i("1.5.2."),
"About this release: ",
ul(
li(
"Stable means ",
a("stable!", href="https://www.youtube.com/watch?v=oyLBGkS5ICk"),
" Barring security issues, we will not break your code. Everything that can be found in ",
"the docs will not change in a way that breaks existing userland code.",
),
li(a("Websockets", href="/coredocs/websockets/"),
"are stable and in a very usable state. We've started using them in production and are currently",
"pondering several quality-of-life improvements to them.", sep=" "),
li("Extend support so we now support from python 3.6 / django 1.11.29 up to python 3.12 / django 4.2.6."),
li("Lots of cleanups and refactorings."),
),
sep=" ",
),
li(b("2022-10-06"), "Another hotly requested feature has hit main.",
"Existing Django templates can now be extended with liveview capabilities!", "Check out the",
a("demo", href="/djangotemplates/"), "with sources and the",
a("documentation.", href="/coredocs/django_templates/"), sep=" "),
li(b("2022-10-01"), "One of the most requested features at Djangocon was websockets.",
"I'm happy to announce that websockets are now in main, and a release will happen soon.",
"You can see it in action as the snake game under", a("Features", href="/"), "and as the obligatory",
a("chat application.", href=chat.reverse()), sep=" "),
li(b("2022-09-27:"), "Thank you to the Djangocon organisers and all the wonderful",
"Django developers we met and listened too!",
"Hope to see you all in Edinburgh, next year <3.", "We have made available our",
a("slides", href="https://runekaagaard.github.io/hypergen-djangocon-2022/"), "and the",
a("example code.", href="https://github.com/runekaagaard/hypergen-djangocon-2022"), sep=" "),
li(b("2022-09-14:"), "Good news everybody! Hypergen been accepted as a",
a("workshop", href="https://pretalx.evolutio.pt/djangocon-europe-2022/talk/CFCFFF/"),
"at djangocon in Porto 2022-09-22. Hope to talk to you there!", sep=" "),
li(
b("2022-06-08:"),
"Pushed version 0.9.9 to pypi. Everything is looking good after a major refactoring. The next version will",
"be a release candidate for 1.0.0 which will be the first officially stable version of Hypergen.", sep=" "),
li(b("2022-06-07:"), "The first version of the documentation is complete.", sep=" "))
show_sources(__file__)
features.py
templates2.py# coding=utf-8
import inspect
import os
from glob import glob
from django.urls.base import reverse
from hypergen.imports import *
from hypergen.context import context as c
from django.templatetags.static import static
from contextlib import contextmanager
def base_head(monokai=False):
meta(charset="utf-8")
meta(http_equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title("Django Hypergen")
link("https://unpkg.com/simpledotcss@2.0.7/simple.min.css")
script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js")
link("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css")
link(static("website/website.css"))
script(src=static("website/website.js"), defer=True)
link(rel="apple-touch-icon", sizes="120x120", href=static("apple-touch-icon.png"))
link(rel="icon", type_="image/png", sizes="32x32", href=static("favicon-32x32.png"))
link(rel="icon", type_="image/png", sizes="16x16", href=static("favicon-16x16.png"))
link(rel="mask-icon", href=static("safari-pinned-tab.svg"), color="#5bbad5")
meta(name="msapplication-TileColor", content="#da532c")
meta(name="theme-color", content="#ffffff")
if monokai:
link(href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/monokai-sublime.min.css")
else:
link(href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/default.min.css")
@contextmanager
def base_template(monokai=False):
doctype()
with html():
with head():
base_head(monokai=monokai)
with body():
with header():
with nav():
a("Home", href="/", class_="current" if c.request.path == "/" else OMIT)
a("Documentation", href="/documentation/",
class_="current" if c.request.path == "/documentation/" else OMIT)
a("News", href="/news/", class_="current" if c.request.path == "/news/" else OMIT)
with a(href="https://github.com/runekaagaard/django-hypergen/"):
img(src=static("website/github.png"), class_="icon")
raw("Github")
with div(class_="title"):
h1(img(src=static("website/hypergen-logo.png"), class_="logo"), "ypergen")
span(" - take a break from javascript")
with main(id_="main" if not monokai else "main-monokai"):
yield
with footer():
p("Built with Hypergen, ", a("Simple.css", href="https://simplecss.org/", style={"color": "inherit"}),
" and ", span("❤", style={"color": "red"}))
base_template.target_id = "main"
@contextmanager
def base_template_monokai():
with base_template(monokai=True):
yield
base_template_monokai.target_id = "main-monokai"
@contextmanager
def base_example_template(file_=None):
with base_template():
with p():
a("Back to documentation", href=reverse("website:documentation"))
with div(id_="content"):
yield
if file_:
show_sources(file_)
def doc_base_template(file_=None, target_id="content"):
@contextmanager
def _doc_base_template(file_=None):
with base_template():
with p():
a("Back to documentation", href=reverse("website:documentation"))
with div(id_=target_id):
yield
if file_:
show_sources(file_)
_doc_base_template.target_id = target_id
return _doc_base_template
base_example_template.target_id = "content"
def show_sources(file_path):
omits = ("__", "migrations", ".css", "management", ".so", "gameofcython.html", ".cpp", ".png", ".svg", ".ico",
"webmanifest", "jpg", ".xml", ".svg", ".mp3")
hr()
with details():
summary("Show sources")
for fp in reversed(glob(os.path.dirname(file_path) + "/**/*.*", recursive=True)):
if any(x in fp for x in omits):
continue
title = fp.replace(os.path.dirname(file_path), "").lstrip("/")
b(title)
with open(fp) as f:
cls = "language-python" if title.endswith(".py") else OMIT
pre(code(f.read(), class_=cls))
def show_func(func):
pre(code(inspect.getsource(func)))