adamghill / django-unicorn

The magical reactive component framework for Django ✨
https://www.django-unicorn.com
MIT License
2.34k stars 119 forks source link

Dynamic page component loading #235

Open lorddaedra opened 3 years ago

lorddaedra commented 3 years ago

How to load page components by component name stored in variable or returned by function?

TemplateSyntaxError at /
'unicorn' tag's argument should be in quotes

views.py:

class HomeView(TemplateView):
    http_method_names: Final[list[str]] = ['get', 'head', 'options']
    template_name: Final[str] = 'base.html'
    extra_context: ClassVar[dict[str, str]] = {'page_component': 'home'}

home_view = login_required(HomeView.as_view())

base.html:

{% load static unicorn %}<!doctype html>
<html class="fixed">
<head>
    <meta charset="UTF-8">
... some meta here
    {% unicorn_scripts %}
</head>
<body>
{% csrf_token %}
{% unicorn page_component %}
... some js-scripts here
</body>
</html>

Traceback (most recent call last):

  File "/Users/lorddaedra/Library/Caches/pypoetry/virtualenvs/core--hfFaf5y-py3.9/lib/python3.9/site-packages/django_unicorn/templatetags/unicorn.py", line 51, in unicorn
    raise template.TemplateSyntaxError(

Exception Type: TemplateSyntaxError at /
Exception Value: 'unicorn' tag's argument should be in quotes
lorddaedra commented 3 years ago

So my idea: set page_component on view level or set dict with pairs url_pattern (or view name): page_component and load first level components dynamically for all pages.

lorddaedra commented 3 years ago

I suggest to add this feature:

UNICORN = {
    "DEFAULT_COMPONENT_NAME": '',  # empty str by default (same behaviour) or in format 'some.module:function', function MUST accept view
}

for example,

settings.py:

UNICORN = {
    "DEFAULT_COMPONENT_NAME_GETTER": 'frontend.utils:get_default_component_name',
}

base.html:

{% load static unicorn %}<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    {% unicorn_scripts %}
</head>
<body>
{% csrf_token %}
{% unicorn %}
</body>
</html>

urls.py:

    path('', views.home_view, name='home'),

frontend.utils.py:

get_default_component_name(view):
    return view.request.resolver_match.url_name

Returns 'home' if {% unicorn %} is used in home_view template so it's equal to {% unicorn 'home' %}

(It should work because of view is attached by django.views.generic.base.ContextMixin to context_data so you can get view from context in template layer)

Benefits for users: users can create url patterns with names related to page component names and use single base.html for all pages.

This is way to remove tons of templates like:

{% extends "base.html" %}{% load unicorn %}

{% block body %}
    {% unicorn 'home' %}
{% endblock %}
adamghill commented 3 years ago

This is an interesting idea! Let me think about it and see what makes the most sense to me. I should have time in a day or two to try out some ideas.

adamghill commented 3 years ago

My initial reactions:

  1. I explicitly check for quotes around the component name. I might be able to remove that logic and check the context if it doesn't have quotes to resolve the variable.
  2. The DEFAULT_COMPONENT_NAME_GETTER setting feels to me like it's removing some of the explicitness. Although, I do like that this be an opt-in feature -- just not sure it's worth the complexity in code.

One other thought: another approach for this same problem would be something like https://github.com/adamghill/django-unicorn/issues/179, right?

lorddaedra commented 3 years ago
  1. I like idea of dynamic components. Something like https://v3.vuejs.org/api/built-in-components.html#component I think this is required feature for some complex projects.

  2. About DEFAULT_COMPONENT_NAME_GETTER - it's just one of the possible implementations. May be it's not the best way to do it. It just allows to map url patterns/views to page components without creating page templates for views.

About problem. I think it's about how to implement root node of components tree, page router and page components (next to root node). We may use django default router (urls.py, urlpatterns), why not. But we would like to use components instead of traditional views&templates because of we can reload it without full page refresh.

So if we will try to combine views with components at the end we will see that we do not need templates and move all things to page components. And our templates will be something like

{% extends "base.html" %}{% load unicorn %}

{% block body %}
    {% unicorn 'home' %}
{% endblock %}

So just single main/base/home template and helper templates for views. And all other things inside components/component templates.

May be we can somehow do not create these view templates at all and create views directly from components. So, yes, #179 is about it. Another implementation.

lorddaedra commented 3 years ago

I think this is also related topic https://github.com/adamghill/django-unicorn/issues/183