# Views
Views contain a class that inherits from `UnicornView` for the component's Python code.
To follow typical naming conventions, the view will convert the component's name to be more Pythonic. For example, if the component name is `hello-world`, the template file name will also be `hello-world.html`. However, the view file name will be `hello_world.py` and it will contain one class named `HelloWorldView`.
This allows `Unicorn` to connect the template and view using convention instead of configuration. Using the `startunicorn` management command is the easiest way to make sure that components are created correctly.
## Example view
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
pass
```
## Class variables
`Unicorn` will serialize/deserialize view class variables to JSON as needed for interactive parts of the component.
Automatically handled field types:
- `str`
- `int`
- `Decimal`
- `float`
- `list`
- `dictionary`
- [`Django Model`](django-models.md#model)
- [`Django QuerySet`](django-models.md#queryset)
- `dataclass`
- `Pydantic` models
- [Custom classes](views.md#custom-class)
### A word of caution about mutable class variables
Be careful when using a default mutable class variables, namely `list`, `dictionary`, and objects. As mentioned in [A Word About Names and Objects](https://docs.python.org/3.8/tutorial/classes.html#tut-object) defining a mutable default for a class variable can have subtle and unexpected consequences -- it _will_ cause component instances to share state which is usually not the intention.
```python
# sentence.py
from django_unicorn.components import UnicornView
# This will cause unexpected consequences
class SentenceView(UnicornView):
words: list[str] = [] # all SentenceView instances will share a reference to one list in memory
word_counts: dict[str, int] = {} # all SentenceView instances will share a reference to one dictionary in memory
def add_word(self, word: str):
...
```
The correct way to initialize a mutable object.
```python
# sentence.py
from django_unicorn.components import UnicornView
class SentenceView(UnicornView):
words: list[str] # not setting a default value is valid
word_counts: dict[str, int] = None # using None for the default is valid
def mount(self):
self.words = [] # initialize a new list every time a SentenceView is initialized and mounted
self.word_counts = {} # initialize a new dictionary every time a SentenceView is initialized and mounted
def add_word(self, word: str):
...
```
`list`, `dictionaries`, and objects all run into this problem, so be sure to initialize mutable objects in the component's `mount` function.
### Class variable type hints
Type hints on fields help `Unicorn` ensure that the field will always have the appropriate type.
```python
# rating.py
from django_unicorn.components import UnicornView
class RatingView(UnicornView):
rating: float = 0
def calculate_percentage(self):
assert isinstance(rating, float)
print(self.rating / 100.0)
```
Without the `float` type hint on `rating`, Python will complain that `rating` is a `str`.
### Custom class
Custom classes need to define how they are serialized. If you have access to the object to serialize, you can define a `to_json` method on the object to return a dictionary that can be used to serialize. Inheriting from `unicorn.components.UnicornField` is a quick way to serialize a custom class, but it uses `self.__dict__` under the hood, so it is not doing anything particularly smart.
Another option is to set the `form_class` on the component and utilize [Django's built-in forms and widgets](validation.md) to handle how the class should be deserialized.
```python
# hello_world.py
from django_unicorn.components import UnicornView, UnicornField
class Author(UnicornField):
def mount(self):
self.name = 'Neil Gaiman'
# Not needed because inherited from `UnicornField`
# def to_json(self):
# return {'name': self.name}
class HelloWorldView(UnicornView):
author = Author()
```
```html
```
```{danger}
Never put sensitive data into a public property because that information will publicly available in the HTML source code, unless explicitly prevented with [`javascript_exclude`](views.md#javascript_exclude).
```
## Class properties
### template_name
By default, the component name is used to determine what template should be used. For example, `hello_world.HelloWorldView` would by default use `unicorn/hello-world.html`. However, you can specify a particular template by setting `template_name` in the component.
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
template_name = "unicorn/hello-world.html"
```
### template_html
Template HTML can be defined inline on the component instead of using an external HTML file.
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
template_html = """
Count: {{ count }}
"""
...
```
## Instance properties
### component_args
The arguments passed into the component.
```html
{% unicorn 'hello-arg' 'World' %}
```
```python
# hello_arg.py
from django_unicorn.components import UnicornView
class HelloArgView(UnicornView):
def mount(self):
assert self.component_args[0] == "World"
```
### component_kwargs
The keyword arguments passed into the component.
```html
{% unicorn 'hello-kwarg' hello='World' %}
```
```python
# hello_kwarg.py
from django_unicorn.components import UnicornView
class HelloKwargView(UnicornView):
def mount(self):
assert self.component_kwargs["hello"] == "World"
```
### request
The current `request`.
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
def mount(self):
print("Initial request that rendered the component", self.request)
def test(self):
print("AJAX request that re-renders the component", self.request)
```
### force_render
Forces the component to render. This is not normally needed for the current component, but is sometimes needed when a child component needs a parent to re-render.
```python
# filter.py
from django_unicorn.components import UnicornView
class FilterView(UnicornView):
search = ""
def updated_search(self, query):
self.parent.load_table()
if query:
self.parent.books = list(filter(lambda f: query.lower() in f.name.lower(), self.parent.books))
# Forces the parent to re-render instead of just the current child component
self.parent.force_render = True
```
## Custom methods
Defined component instance methods with no arguments (other than `self`) are available in the Django template context and can be called like a property.
```python
# states.py
from django_unicorn.components import UnicornView
class StateView(UnicornView):
def all_states(self):
return ["Alabama", "Alaska", "Arizona", ...]
```
```html
{% for state in all_states %}
{{ state }}
{% endfor %}
{% endverbatim %}
```
:::{tip}
If the method is intensive and will be called multiple times, it can be cached with Django's `cached_property` to prevent duplicate API requests or database queries. The method will only be executed once per component rendering.
```python
# states.py
from django.utils.functional import cached_property
from django_unicorn.components import UnicornView
class StateView(UnicornView):
@cached_property
def all_states(self):
return ["Alabama", "Alaska", "Arizona", ...]
```
:::
## Instance methods
### mount()
Gets called when the component gets initialized or [reset](actions.md#reset).
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
name = "original"
def mount(self):
self.name = "mounted"
```
### hydrate()
Gets called when the component data gets set.
```python
# hello_world.py
from django_unicorn.components import UnicornView
class HelloWorldView(UnicornView):
name = "original"
def hydrate(self):
self.name = "hydrated"
```
### updating(property_name, property_value)
Gets called before each property that will get set. This can be called multiple times in certain instances, e.g. during a debounce.
### updated(property_name, property_value)
Gets called after each property gets set. This can be called multiple times in certain instances, e.g. during a debounce.
### resolved(property_name, property_value)
Gets called after the specified property gets set. This will only get called once.
### updating\_{property_name}(property_value)
Gets called before the specified property gets set. This can be called multiple times in certain instances, e.g. during a debounce.
### updated\_{property_name}(property_value)
Gets called after the specified property gets set. This can be called multiple times in certain instances, e.g. during a debounce.
### resolved\_{property_name}(property_value)
Gets called after the specified property gets set. This will only get called once.
### calling(name, args)
Gets called before each method that gets called.
### called(name, args)
Gets called after each method gets called.
### complete()
Gets called after all methods have been called.
### rendered(html)
Gets called after the component has been rendered.
### parent_rendered(html)
Gets called after the component's parent has been rendered (if applicable).
## Meta
Classes that derive from `UnicornView` can include a `Meta` class that provides some advanced options for the component.
### exclude
By default, all public attributes of the component are included in the context of the Django template and available to JavaScript. One way to protect internal-only data is to prefix the attribute name with `_` to indicate it should stay private.
```python
# hello_state.py
from django_unicorn.components import UnicornView
class HelloStateView(UnicornView):
_all_states = (
"Alabama",
"Alaska",
...
"Wisconsin",
"Wyoming",
)
```
Another way to prevent that data from being available to the component template is to add it to the `Meta` class's `exclude` tuple.
```python
# hello_state.py
from django_unicorn.components import UnicornView
class HelloStateView(UnicornView):
all_states = (
"Alabama",
"Alaska",
...
"Wisconsin",
"Wyoming",
)
class Meta:
exclude = ("all_states", )
```
### javascript_exclude
To allow an attribute to be included in the the context to be used by a Django template, but not exposed to JavaScript, add it to the `Meta` class's `javascript_exclude` tuple.
```html
{% for state in all_states %}
{{ state }}
{% endfor %}
```
```python
# hello_state.py
from django_unicorn.components import UnicornView
class HelloStateView(UnicornView):
all_states = (
"Alabama",
"Alaska",
...
"Wisconsin",
"Wyoming",
)
class Meta:
javascript_exclude = ("all_states", )
```
### safe
By default, `unicorn` HTML encodes updated field values to prevent XSS attacks. You need to explicitly opt-in to allow a field to be returned without being encoded by adding it to the `Meta` class's `safe` tuple.
```html
{{ something_safe }}
```
```python
# safe_example.py
from django_unicorn.components import UnicornView
class SafeExampleView(UnicornView):
something_safe = ""
class Meta:
safe = ("something_safe", )
```
````{note}
A context variable can also be marked as `safe` in the template with the normal Django template filter.
```html