ypergen

- take a break from javascript

News

App examples

These are examples of writing a Django app with Hypergen. Be sure to read the sources.

Tutorials

Documentation

Documentation explaining and showing how Hypergen works.

Alternative template implementations

While the pure python template 'language' is the main template engine, two alternative implementations exists.

Compatibility

Hypergen is tested on the following combinations of Django and Python:

python 3.6python 3.7python 3.8python 3.9python 3.10
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.13
django 4.0.7
django 4.1.1

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 sourcesstatic/website/website.js
hypergen.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})
minidemoes/shoot_em_up_alt.py
from 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"))
minidemoes/shoot_em_up.py
from 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.py
from 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"))
views.py
from 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 chat2

from features import templates as features_templates

@liveview(re_path="^$", perm=NO_PERM_REQUIRED, base_template=base_template_monokai,
    user_plugins=[WebsocketPlugin(), 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("News")
    ul(
        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 master, 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=chat2.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 Dublin, 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=" "))

    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()))

    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("Templates", href=template.reverse()))
        li(a("Liveviews", href=liveviews.reverse()))
        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("Alternative template implementations")
    p("While the pure python template 'language' is the main template engine, two alternative ",
        " implementations exists.")
    with ul():
        li(a("Django html Templates", href=reverse("djangotemplates:djangotemplates")),
            " - Django html templates instead of python templates. ",
            "We could use some input on this, and are ready to polish it together with you.")
        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__)
urls.py
from 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")
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://highlightjs.org/static/demo/styles/base16/monokai.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("Support", href="https://github.com/runekaagaard/django-hypergen/issues")
                    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)))