Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
[![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE - Do not remove or modify above line -->

[Unicorn](https://www.django-unicorn.com) adds modern reactive component functionality to your Django templates without having to learn a new templating language or fight with complicated JavaScript frameworks. It seamlessly extends Django past its server-side framework roots without giving up all of its niceties or forcing you to re-building your application. With Django Unicorn, you can quickly and easily add rich front-end interactions to your templates, all while using the power of Django.
[Unicorn](https://www.django-unicorn.com) adds modern reactive component functionality to your Django templates without having to learn a new templating language or fight with complicated JavaScript frameworks. It seamlessly extends Django past its server-side framework roots without giving up all of its niceties or forcing you to rebuild your application. With Django Unicorn, you can quickly and easily add rich front-end interactions to your templates, all while using the power of Django.

## ⚡ Getting started

Expand Down Expand Up @@ -61,12 +61,12 @@ urlpatterns = (

### 5. [Create a component](https://www.django-unicorn.com/docs/components/)

`python manage.py startunicorn COMPONENT_NAME`
`python manage.py startunicorn myapp COMPONENT_NAME`

`Unicorn` uses the term "component" to refer to a set of interactive functionality that can be put into templates. A component consists of a Django HTML template and a Python view class which contains the backend code. After running the management command, two new files will be created:

- `your_app/templates/unicorn/COMPONENT_NAME.html` (component template)
- `your_app/components/COMPONENT_NAME.py` (component view)
- `myapp/templates/unicorn/COMPONENT_NAME.html` (component template)
- `myapp/components/COMPONENT_NAME.py` (component view)

### 6. Add the component to your template

Expand All @@ -91,7 +91,7 @@ urlpatterns = (
The `unicorn:` attributes bind the element to data and can also trigger methods by listening for events, e.g. `click`, `input`, `keydown`, etc.

```html
<!-- ../templates/unicorn/todo.html -->
<!-- todo.html -->

<div>
<form unicorn:submit.prevent="add">
Expand All @@ -118,7 +118,7 @@ The `unicorn:` attributes bind the element to data and can also trigger methods
```

```python
# ../components/todo.py
# todo.py

from django_unicorn.components import UnicornView
from django import forms
Expand Down
27 changes: 27 additions & 0 deletions django_unicorn/components/unicorn_template_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from django_unicorn.errors import (
MissingComponentElementError,
MissingComponentViewElementError,
MultipleRootComponentElementError,
NoRootComponentElementError,
)
from django_unicorn.settings import get_minify_html_enabled, get_script_location
from django_unicorn.utils import generate_checksum, sanitize_html
Expand Down Expand Up @@ -59,6 +61,25 @@ def is_html_well_formed(html: str) -> bool:
return len(stack) == 0


def assert_has_single_wrapper_element(root_element: Tag, component_name: str) -> None:
# Check that the root element has at least one child
try:
next(root_element.descendants)
except StopIteration:
raise NoRootComponentElementError(
f"The '{component_name}' component does not appear to have one root element."
) from None

# Check that there is not more than one root element
parent_element = root_element.parent
tag_count = len([c for c in parent_element.children if isinstance(c, Tag)])

if tag_count > 1:
raise MultipleRootComponentElementError(
f"The '{component_name}' component appears to have multiple root elements."
) from None


class UnsortedAttributes(HTMLFormatter):
"""
Prevent beautifulsoup from re-ordering attributes.
Expand Down Expand Up @@ -122,6 +143,12 @@ def render(self):
# despite https://thehftguy.com/2020/07/28/making-beautifulsoup-parsing-10-times-faster/
soup = BeautifulSoup(content, features="html.parser")
root_element = get_root_element(soup)

try:
assert_has_single_wrapper_element(root_element, self.component.component_name)
except (NoRootComponentElementError, MultipleRootComponentElementError) as ex:
logger.warning(ex)

root_element["unicorn:id"] = self.component.component_id
root_element["unicorn:name"] = self.component.component_name
root_element["unicorn:key"] = self.component.component_key
Expand Down
8 changes: 8 additions & 0 deletions django_unicorn/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,13 @@ class MissingComponentViewElementError(Exception):
pass


class NoRootComponentElementError(Exception):
pass


class MultipleRootComponentElementError(Exception):
pass


class ComponentNotValidError(Exception):
pass
26 changes: 13 additions & 13 deletions docs/source/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@

** Breaking changes **

- responses will be HTML encoded going forward (to explicitly opt-in to previous behavior use [safe](advanced.md#safe))
- responses will be HTML encoded going forward (to explicitly opt-in to previous behavior use [safe](views.md#safe))

[All changes since 0.35.3](https://github.com/adamghill/django-unicorn/compare/0.35.3...0.36.0).

Expand All @@ -250,7 +250,7 @@

## v0.35.0

- [Trigger](advanced.md#trigger-model-update) an `input` or `blur` event for a model element from JavaScript.
- [Trigger](javascript.md#trigger-model-update) an `input` or `blur` event for a model element from JavaScript.
- [Visibility](visibility.md) event with `unicorn:visible` attribute.

**Breaking changes**
Expand Down Expand Up @@ -374,17 +374,17 @@
## v0.21.0

- Bug fix: Prevent disabled polls from firing at all.
- Support [`Decimal` field type](components.md#supported-property-types).
- Support [`dataclass` field type](components.md#supported-property-types).
- Use [type hints](components.md#property-type-hints) to cast fields to primitive Python types if possible.
- Support [`Decimal` field type](views.md#class-variables).
- Support [`dataclass` field type](views.md#class-variables).
- Use [type hints](views.md#class-variable-type-hints) to cast fields to primitive Python types if possible.

[All changes since 0.20.0](https://github.com/adamghill/django-unicorn/compare/0.20.0...0.21.0).

## v0.20.0

- Add ability to exclude component view properties from JavaScript to reduce the amount of data initially rendered to the page with [`javascript_exclude`](advanced.md#javascript_exclude).
- Add [`complete`](advanced.md#complete), [`rendered`](advanced.md#renderedhtml), [`parent_rendered`](advanced.md#parent_renderedhtml) component hooks.
- Call [JavaScript functions](advanced.md#javascript-integration) from a component view's method.
- Add ability to exclude component view properties from JavaScript to reduce the amount of data initially rendered to the page with [`javascript_exclude`](views.md#javascript_exclude).
- Add [`complete`](views.md#complete), [`rendered`](views.md#renderedhtml), [`parent_rendered`](views.md#parent_renderedhtml) component hooks.
- Call [JavaScript functions](javascript.md) from a component view's method.

[All changes since 0.19.0](https://github.com/adamghill/django-unicorn/compare/0.19.0...0.20.0).

Expand Down Expand Up @@ -525,8 +525,8 @@

## v0.10.0

- Add support for [passing kwargs](components.md#component-arguments) into the component on the template
- Provide access to the [current request](advanced.md#request) in the component's methods
- Add support for [passing kwargs](components.md#pass-data-to-a-component) into the component on the template
- Provide access to the [current request](views.md#request) in the component's methods

[All changes since 0.9.4](https://github.com/adamghill/django-unicorn/compare/0.9.4...0.10.0).

Expand Down Expand Up @@ -554,7 +554,7 @@

- [Loading states](loading-states.md) for improved UX.
- `$event` [special argument](actions.md#events) for `actions`.
- `u` [unicorn attribute](components.md#unicorn-attributes).
- `u` [unicorn attribute](templates.md#unicorn-attributes).
- `APPS` [setting](settings.md#apps) for determing where to look for components.
- Add support for parent elements for non-db models.
- Fix: Handle if `Meta` doesn't exist for db models.
Expand Down Expand Up @@ -628,7 +628,7 @@

- [Realtime validation](validation.md) of a Unicorn model.
- [Polling](polling.md) for component updates.
- [More component hooks](advanced.md)
- [More component hooks](views.md)

[All changes since 0.5.0](https://github.com/adamghill/django-unicorn/compare/0.5.0...0.6.0).

Expand All @@ -652,7 +652,7 @@

## v0.3.0

- Add [mount hook](advanced.md#mount).
- Add [mount hook](views.md#mount).
- Add [reset](actions.md#reset) action.
- Remove lag when typing fast in a text input and overall improved performance.
- Better error handling for exceptional cases.
Expand Down
Loading