Skip to main content

Prototype and debug `Unicorn` components without creating a complete Django application.

Project description

django-unicorn-playground 🦄🛝

The Unicorn Playground provides a way to prototype and debug Unicorn components without creating a complete Django application. It can either be run as a standalone script or by installing the library.

Standalone Script

The benefit of the standalone script is that dependencies are defined in the file and pipx handles creating the virtual environment when the script is running.

  1. Install pipx
  2. Create a new file called counter.py
  3. Add the following code to counter.py
# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "django-unicorn-playground"
# ]
# ///

from django_unicorn.components import UnicornView
from django_unicorn_playground import UnicornPlayground

class CounterView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    count: int

    def increment(self):
        count += 1
    
    def decrement(self):
        count -= 1

if __name__ == "__main__":
    UnicornPlayground(__file__).runserver()
  1. pipx run counter.py
  2. Go to https://localhost:8000

CLI

Another option is to install the django-unicorn-playground library. This provides a CLI that can be used to run components. This approach uses basically the same code, but doesn't require the script inline metadata or the call to runserver at the end -- the CLI takes care of running the development server directly. Those portions can be included, though, and will not prevent the CLI from being usable.

  1. pipx install django-unicorn-playground
  2. Create counter.py with the following code:
from django_unicorn.components import UnicornView

class CounterView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    count: int

    def increment(self):
        count += 1
    
    def decrement(self):
        count -= 1
  1. unicorn counter.py
  2. Go to https://localhost:8000

Example components

There are a few example components in the examples directory.

They can be run with something like pipx run --no-cache examples/counter.py.

Template HTML

The component's HTML can be initialized in a few ways.

UnicornView.template_file attribute

The HTML can be set with a class-level template_html field.

from django_unicorn.components import UnicornView

class TestView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    ...

UnicornView.template_file method

The HTML can be returns from a template_html instance method.

from django_unicorn.components import UnicornView

class TestView(UnicornView):
    def template_html(self): 
        return """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    ...

HTML file

Similar to a typical django-unicorn setup, the component HTML can be a separate template file. This is the fallback and will only be searched for if the template_view field or method is not defined on the component.

  1. cd to the same directory as the component Python file you created
  2. mkdir -p templates/unicorn
  3. touch {COMPONENT-NAME}.html, e.g. for a component Python named counter.py create counter.html
  4. Add the component HTML to the newly created file

Using a Python HTML builder

Any Python library that generates normal HTML strings work great with django-unicorn-playground.

Some examples of libraries below:

haitch

import haitch
from django_unicorn.components import UnicornView

class TestComponent(UnicornView)
   def template_html(self):
       return haitch.div()(
           haitch.button("Increment +", **{"unicorn:click": "increment"}),
           haitch.button("Decrement -", **{"unicorn:click": "decrement"}),
           haitch.div("{{ count }}"),
       )
   
   ...

htpy

import htpy
from django_unicorn.components import UnicornView

class TestComponent(UnicornView)
   def template_html(self):
       return htpy.div()[
           htpy.button({"unicorn:click": "increment"})["Increment +"],
           htpy.button({"unicorn:click": "decrement"})["Decrement -"],
           htpy.div()["{{ count }}"],
       ]
   
   ...

dominate

from dominate import tags as dom
from django_unicorn.components import UnicornView

class TestComponent(UnicornView)
   def template_html(self):
       return dom.div(
           dom.button("Increment +", **unicorn.click(self.increment)),
           dom.button("Decrement -", **unicorn.click(self.decrement)),
           dom.div("{{ count }}"),
       )
   
   ...

Unicorn HTML helpers

When using a Python HTML builder like the above, there are a few helper methods which make it a little cleaner to build Unicorn-specific HTML.

For example, if using haitch instead of doing this:

import haitch
from django_unicorn.components import UnicornView

class TestComponent(UnicornView)
   def template_html(self):
       return haitch.div()(
           haitch.button("Increment +", **{"unicorn:click": "increment"}),
           haitch.button("Decrement -", **{"unicorn:click": "decrement"}),
           haitch.div("{{ count }}"),
       )
   
   ...

Using helper methods:

import haitch
from django_unicorn.components import UnicornView
from django_unicorn_playground.html import django, unicorn

class TestComponent(UnicornView)
   def template_html(self):
       return haitch.div()(
           haitch.button("Increment +", **unicorn.click(self.increment)),
           haitch.button("Decrement -", **unicorn.click(self.decrement)),
           haitch.div(django.variable("count")),
       )
   
   ...

Local development

Inline script metadata

Using the inline script metadata with pipx seems a little quirky and I could not get editable installs working reliably. I also tried hatch run which had it's own issues. Not sure if there are other approaches.

As far as I can tell, the best approach is to use an absolute file path like "django_unicorn_playground @ file:///Users/adam/Source/adamghill/django-unicorn-playground/dist/django_unicorn_playground-0.1.0-py3-none-any.whl" (note the triple forward-slash after "file:") as a dependency, and rebuilding and re-running the script without any caching like this: poetry build && pipx run --no-cache examples/counter.py any time you make a code change.

Note: you will need to update the component's dependency so it points to the path on your machine.

However, there is a just command to make re-building for local dev slightly less painful.

  1. Install just
  2. just serve examples/counter.py

CLI

Working locally with the CLI is more straight-forward than the inline script metadata approach.

  1. poetry install
  2. poetry run unicorn examples/counter.py

Acknowledgments

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_unicorn_playground-0.1.0.tar.gz (10.2 kB view hashes)

Uploaded Source

Built Distribution

django_unicorn_playground-0.1.0-py3-none-any.whl (10.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page