How to use safe filter on custom Django tag? - python-3.x

I'm trying to implement a custom Django tag that will formulate an import statement in Javascript to load my vue3 app as well as its components template html files using a get request in axios.
The custom tag in my templatetags directory looks like this:
templatetags/vuecomponents.py
from django import template
from django.templatetags.static import static
register = template.Library()
#register.simple_tag
def v_load_app_component(app_name, script_name, components):
components = components.strip("[]").split(", ")
app_script = static(f"js/{script_name}.js")
comps = [static(f"components/{name}.html") for name in components]
return f"import {{{ app_name }}} from \"{app_script}?{components[0]}={comps[0]}\""
Right now it only loads the first component as I just want a prototype. The only issue is when I drop this into a template like so:
createpost.html
<script type="module">
{% v_load_app_component "creator" "internalpostform" "[internalpostform]" %}
// OUTPUTS:
// import { creator } from "static/js/internalpostform.js?internalpostform=internalpostform.html"
creator.mount("#app")
</script>
It outputs the relevant import statement as:
import { creator } from "static/js/internalpostform.js?internalpostform=internalpostform.html"
With the double quotes escaped. Even when I tried to apply the safe filter ({% v_load_app_component "creator" "internalpostform" "[internalpostform]"|safe %}) it still escaped the output of my custom tag function.
How can I make it to where the output of my custom tag doesn't automatically have symbols converted to html entities?

I found it after a little digging in Django's documentation. The safe filter is only for variables i.e. {{ variable|safe }} but does not apply to a tag {% tag "argument"|safe %}.
To prevent Django from escaping the output of a tag you simply use {% autoescape off %}
{% autoescape off %}
{% v_load_app_component "creator" "internalpostform" "[internalpostform]" %}
{% endautoescape %}
This results in the desired behavior.

Related

FastAPI - How to render Jinja2 template with variable that contains special characters?

I'm trying to render this Jinja2 template:
#app.get("/", response_class=HTMLResponse)
async def read_root(request: Request, item: Optional[str] = None):
item = "<script>"
return templates.TemplateResponse('index.html', {
'request': request,
'item': item
})
but when I use the variable in the HTML template, for example:
<h1>{{item}}</h1>
it shows this in the code:
<h1><script></h1>
is there anyway to decode that variable to show as it is declared?
FastAPI uses the templating support in starlette, which sets the Jinja2 autoescape option by default.
You can disable that like this:
templates = Jinja2Templates(directory="templates")
templates.env.autoescape = False
With more recent versions of starlette, you may also be able to do this:
templates = Jinja2Templates(directory="templates", autoescape=False)
...but with the version I'm running the latter option fails. The former option should work in any case.
Just found out how to do it,
adding in the HTML:
{% autoescape false %}
{{ your_variable }}
{% endautoescape %}
Another way is to wrap the markup you consider safe to render in markupsafe.Markup.

Rendering a component inside another in Nunjucks & Fractal

I'm working on a design system and just getting my head around the component way of thinking.
We're using Nunjucks and Fractal.
We have an existing component, an accordion which gets it's data from a JSON object. The props being title and content.
I've built a component, like a table of contents, which displays a list of styled links, using a simple loop, pulling in from a ToC.config.js file.
I need to include that new component within the accordion component, the ToC has a title and an array containing urls and text for each link.
At present I've just copied the HTML from the accordion's view in Fractal, but that's probably not the way to go, but visually it's as the designer requires.
I can also render the accordion correctly if I put all the links, HTML and all in the config, as a string, but that's not the way it should work either.
What I want to do, which is probably the proper way, is pull that accordion in and populate it with my ToC component.
{% render '#my-accordion', 'accordion: items' %} doesn't do what I want and I can't seem to figure out how to achieve what I need to do.
Something like so:
{% render '#my-accordion' %}
// Pass in data from ToC, somehow
{{ title: title }}
{% for item in items %}
{{ item.text etc }}
{% endfor %}
Then I would have my accordion component, where its title, links and HTML etc are those from my ToC component. Sorry for the rough pseudo above, it's more an example of what I want to do. I can't copy code to an external resource.
using render doesn't appear to be the way to go, unless I'm missing something? I can't pass the list through as a string as it has classes and aria for the current page etc and content authors will be building pages with these.
A little nudge in the right direction would be great.
You can add a render-filter that similar to include and macro.
import 'nunjucks.min.js';
var env = new nunjucks.Environment({autoescape: true});
env.addFilter('render', function (template, ctx) {
const html = env.render(template, ctx); // try-catch
return env.filters.safe(html);
});
Usage
// table.njk
<table>
{% for e in rows %}
{% 'row.njk' | render({row: e}) %}
{% endfor %}
</table>
// row.njk
<tr>
{% for c in row.cols %}
<td> {{ c.name }} </td>
{% endfor %}
</tr>

What is the version of #component (from .blade) for twig file?

I don't know if this is right version of #component for twig? Is it render?
{{ render ('mail::message') }}
{{ render ('mail::button', ['url' => '']) }}
or maybe I am not able to write the right syntax here?
Check out Twig's macro.
For the Blade
<!-- /resources/views/demo.blade.php -->
{{ $slot }}
<!-- component use -->
#component('demo')
content
#endcomponent
the basic Twig syntax would be
{# macro definition #}
{% macro demo(param) %}
{{ param }}
{% endmacro %}
{# macro use #}
{# first import and then you can #}
{{ demo('content') }}
You'll need to import the macro before calling it, either with from or import. To import (with either from or import) from the current file, use _self instead of a path. See the docs for more explanation
Your example
{{ render ('mail::button', ['url' => '']) }}
looks like it's coming from Blade's #include, something like
#include('mail::button, ['url' => ''])
For that, Twig's include might be a better fit. The relationship between macros and includes in Twig is similar to the relationship between components and includes in Blade. The basic Twig would be
{# partial.html #}
{{ url }}
{# partial use #}
{% include 'partial.html' with {url: ''} %}

OpenCart 3.x - including a new template file

I need to add a new OpenCart template file into another template file.
Essentially I've created a new head file in /theme/customtheme/template/common/ called "header_home.twig".
Then in home.twig, I've changed {{ header }} to say {{ header_home }}, but it's not displaying anything.
Basically, all I did was copy header.twig and rename it to header_home.twig, and put in "xxxxx" to see if it was calling the new file, which it's not. Instead, it's not displaying anything.
Here's what my home.twig now looks like:
{{ header_home }}
<div id="common-home" class="container">
<div class="row">{{ column_left }}
{% if column_left and column_right %}
{% set class = 'col-sm-6' %}
{% elseif column_left or column_right %}
{% set class = 'col-sm-9' %}
{% else %}
{% set class = 'col-sm-12' %}
{% endif %}
<div id="content" class="{{ class }}">{{ content_top }}{{ content_bottom }}</div>
{{ column_right }}</div>
</div>
{{ footer }}
I assume I'm somehow missing a step when it comes to adding a new template file? If someone could help me with adding in a new twig file, that would be fantastic.
You can either:
Change the name of Twig rendering template on the OpenCart level in the controller/common/header as #bogalakon pointed out (preferably to do this via ocmod so that your future OpenCart updates will not overwrite your hack).
or include another template on the Twig level
I.e.:
{{ header }} {# Original rendered to HTML OpenCart header - you can move it to your header_home.twig or just drop it #}
{% include customtheme/template/common/header_home.twig %}
<div id="common-home" class="container">
...
Twig is very powerful template language. And you can do much more than just a simple include! It's nice that OpenCart officially accepted it. But for now it's just Twig 1.24.2. Please refer to the Twig documentation.
I haven't learn OpenCart 3.x, but I guess you need to change
return $this->load->view('common/header', $data);
to
return $this->load->view('common/header_home', $data);
in catalog/controller/common/header.php.
In order to connect another template via the {{header_home}} construct, you need to create the header_home.php in the folder /controller/common/directory, create the header_home.twig file in the theme /*theme_name*/template/common/ folder and in the controller , which is responsible for the formation of the page, for example in the directory /controller/common/home.php
add a line:
'$ data [' header_home '] = $ this-> load-> controller (' common / header_home '); '
before:
'$ this-> response-> setOutput ($ this-> load-> view (' common / home ', $ data)); '
To add your own custom twig file and include it inside another existing one you need to do three things:
Create the actual custom twig file.
In your case it will be:
catalog/view/theme/customtheme/template/common/header_home.twig
Create a controller for that twig file. In your case you can just copy:
/catalog/controller/common/header.php and rename that to header_home.php. Edit this controller and change the class name to ControllerCommonHeaderHome usually on line 2.
Lastly, since you are going to include header_home inside home.twig, edit /catalog/controller/common/home.php and add the line $data['header'] = $this->load->controller('common/header_home'); after $data['header'] = $this->load->controller('common/header');
That's it. After you've done the steps above, you can now include {{ header_home }} inside home.twig. If you are editing the files directly, I find that sometimes I need to login to the admin of the website, go to design>theme editor, open up the files I added or changed, hit reset, and save. Refresh your website and you should see the changes.
The answer of #bogalakon is right but if you update your OpenCart core files you will lose your changes so, I suggest you to copy the header.php controller file and rename it to header_home.php and then edit the file and replace the line
return $this->load->view('common/header', $data);
with:
return $this->load->view('common/header_home', $data);
There are several ways to give a different value.
For example:
Consider the controller
$data['header'] = $this-> load-> controller ('common / header');
A variable {{header}} is available in the template
You can place a new template in another file (header_home.twig), and then you can immediately download a new template for use:
$data['**header**'] = $this-> load-> controller ('common / ***header_home***');
If you want use {{header}} in the template.
Or,
$data['**header_home**'] = $this-> load-> controller ('common / header_home');
For use {{header_home}} in the template.

Twig : Access to variable from outer scope in a form widget customization

I'm trying to customize a specific widget, like in the documentation : http://symfony.com/doc/current/cookbook/form/form_customization.html#how-to-customize-an-individual-field
The problem is that in this custom block, I need to use a variable from my actual template. I thought "blocks have access to variables from outer scopes", but apparently not in this case :
{% extends "CDASvBundle::layout.html.twig" %}
{% block _contact_activity1_widget %}
<select name="contact[activity1]">
{% for key, child_contact_categories in contact_categories_tab %}
<option value="{{key}}">{{child_contact_categories}}</option>
{% endfor %}
</select>
It's saying that contact_categories_tab is undefined, but outside of this block (in the normal content block for example), it works !
I tried something like :
{% use 'form_div_layout.html.twig' with contact_categories_tab as contact_categories_tab %}
But that doesn't either.. Though I'm not sure I understand if I have to use use and how !
I see one other solution that I haven't tried yet : put this customization in another template. But I don't really want to do that (few lines in a new template), there should be a way to do that in only ONE template ?!
Finally found the answer in a previous post :
Each symfony form type extents AbstractType class.
AbstactType class has method:
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->set('img_src', '120x100.jpg');
$view->set('my_variable', $foo);
}
You can create this method on your form type and next in your twig:
{{ asset(img_src) }}
Source : How to get entity or pass variable to Symfony2 twig form widget?

Resources