url templatetag with "safe" arguments? - django-templates

I'm trying to use the {% url %} template tag but with an argument to be substituted out later in Javascript. It looks something like this:
var pid = '7a8b323f-52b1-466c-91d3-b4i4d85b1c32';
var status_url = '{% url quote_status form_urlname inquiry_id instance_id '{0}' %}'.format(pid);
I tried using both {% autoescape off %} and |safe, neither of which seemed to work. Is there a good way to make this happen?

(snip previous answer, sorry, didn't read carefully enough)
If the argument is required to build the url, it just won't work - the templatetag is executed on the server, the javascript is executed on the browser.

Related

Liquid - Load all products from collection handler not working

I am trying to load all products by the given handler from a collection.
They will click on a box (each box is a different collection), then it should load all products for that collection.
My issue is that my assigned variable cannot read the input from javascript.
I am doing onclick(id, name, handle) where i catch the handle and pass it to the liquid.
My code is:
function loadProducts(collectionH) {
var html = '';
var handlerString = collectionH;
console.log('Loading products...');
{{collectionHandleNew}} = handlerString;
console.log({{collectionHandleNew}}); // log the handler
// Make sure the current product name is loaded
{% if collectionHandleNew -%}
{% if selectedCategory -%}
console.log('Collection handle is set: ' + {{collectionHandleNew}});
{%- for product in collections[collectionHandleNew].products -%}
console.log({{product.id}});
{%- endfor -%}
{% else -%}
console.log('Selected category not found');
{% endif -%}
{% else -%}
console.log('Collection handle is not available');
{% endif -%}
return html;
}
The console is showing this:
view the image
The trick here is to remember that Liquid and Javascript serve two completely different purposes:
Liquid is a templating language that is parsed server-side to generate the documents that are sent to the client's browser, and is never seen by the client.
Javascript is a programming language that is parsed client-side to do dynamic things on the page and is never executed on the server.
Why is this distinction important? Because it means that we can use Liquid to generate Javascript, but the reverse is never true. It also means that any variables that we pass from Liquid to Javascript will only be current as of the time the document is generated and cannot be affected by anything that happens on the page once it's been rendered!
If your code needs to fetch a collection dynamically, I would recommend creating a function that takes a collection handle and loads the products entirely through Javascript using your favourite tool (fetch, jQuery.getJSON, XMLHttpRequest, Axios, etc).
To fetch products from a collection from the storefront, you can use /collections/<some-collection-handle>/products.json (for example, /collections/drawstring-bag-hoodies/products.json). If you're feeling fancy, you might also consider looking into the storefront GraphQL API instead of the traditional REST API.
One final note: Whenever you are dumping variables from Liquid into Javascript, I strongly recommend using the | json Liquid filter to ensure that the resulting output is Javascript-legal. There are lots of ways a Liquid variable dump can break Javascript, such as when the variable is unexpectedly empty, when it contains ' or ", when it contains line breaks, etc. By running the Liquid variable through this filter, the resulting output will be wrapped in the appropriate brackets or quotes, all special characters within will be properly escaped, and empty values will print null.

Using environment-dependent source specifications in DBT

I have a bit of an odd problem where I need to union together multiple source databases in production, but only one in lower environments. I'm using DBT and would like to use the source functionality so I can trace the origin of my data lineage, but I'm not quite sure how to handle this case. Here's the naive non-source approach:
{% set clouds = [1, 2, 3] %} {# this clouds variable will come from the environment, instead of hard coded. In lower envs, it would just be [1] #}
{% for cloudId in clouds %}
select *
from raw_{{ cloudId }}.users
{% if not loop.last %}
union all
{% endif %}
{% endfor %}
This isn't ideal, because I'm referencing my raw_n schema(s) directly. I'd love to have something like this:
version: 2
sources:
{% for cloud in env('CLOUDS') %}
- name: raw_{{ cloud }}
schema: raw_{{ cloud }}
database: raw
tables:
- name: users
identifier: users
{% endfor %}
So I can actually use the source() function in the sql files.
I'm not sure how to make such a configuration possible based on environment. Can this just simply not be done in dbt?
Since source is just a python/jinja function you can pass variables to it. So the following should work:
{% if target.name == `prod` %} {# this clouds variable will come from the environment, instead of hard coded. In lower envs, it would just be [1] #}
{% set clouds = [1, 2, 3] %}
{% else %}
{% set clouds = [1] %}
{% endif %}
{% for cloudId in clouds %}
select *
from {{ source(cloudId, 'users') }}
{% if not loop.last %}
union all
{% endif %}
{% endfor %}
as for the environment part you would have to use env_var function but those are always strings so you would write env_var('my_list').split(',') assuming its comma separated.
EDIT:
Per askers, comments revised solution to include info as to what environment is being used
EDIT #2:
I know we left this off on a rather unhelpful note but now I am having a different issue that suggests a solution that might be more helpful for you.
in dbt_project.yaml you can specify multiple paths to models/tests/seeds etc. you can also specify dynamic paths. So you could potentially modify your models-path to something like this: model-path: ['models','models_{{ target.name }}'] with this you have multiple source.yml models/source.yml will include all sources that don't change between dev/test/prodand then sources that do need to vary will be inmodels_{{ target.name }}`.
The same goes for models that will use them.
I know this isn't dynamic sources file still but it preserves lineages, and you do it in yaml just like you wanted.
Setting context here, I believe your primary interest is in working with the dbt docs / lineage graph for a prod / dev case?
In that case, as you are highlighting, the manifest is generated from the source.yml files within your model directories. So - effectively what you are asking about is the way to "activate" different versions of a single source.yml file based on environment?
Fair warning: dbt core's intentions doesn't align with that use case. So let's explore some alternatives.
If you want to hack something that is dbt-cli / local only, Jeremy lays out that you could approach this via bash/stdout:
The bash-y way
Leveraging the fact that dbt/Jinja can write anything
its heart desires to stdout, pipe the stdout contents somewhere else.
$ dbt run-operation generate_source --args 'schema_name: jaffle_shop'
> models/staging/jaffle_shop/src_jaffle_shop.yml
At least one reason that he points out is that there would be security implications if the dbt jinja compiler was un-sandboxed from the /target destination so I wouldn't expect this to change from dbt-core in the future.
A non-dbt helper tool.
From later in the same comment:
At some point, I'm so on board with the request—I'm just not sure if
dbt Core can be the right tool for the job, or it's a better fit for a
helper tool written in python.
Use git hooks to "switch" the active "source.yml" file in that directory?
This is just an idea that I haven't even looked into because it's somewhat far-fetched but basically use your environment variables to activate pre-run hooks that set source-dev.yml to gitignore in production and vice versa? The files would have to be defined statically so I'm not sure that helps anyway.

How do I add a metafield to liquid in Shopify?

I'm trying to edit a section in my Shopify theme which displays a video. I'm using the section on product pages and the URL for the video is inserted by using the theme editor tool. As such the same video inserted will show on every product page using that template. However, I would like to use a different video on each product page. I can't see a way to do this through the theme editor unless I duplicate the template for each and every product that needs its own video URL (seems a bit overkill).
I found this code which controls the video section, and the bit I'm trying to figure out is how I can change the 'assign video_id' to use a metafield so that I can add this per product by simply adding the video URL on a specific product page. I can't think of an easier way to achieve what I need, but thought changing a metafield for each product that needs a custom video URL would be the best way to do it, working possibly similar to this:
{% if template.name == 'product' %}
{% if product.metafields.my_fields.product_video_url %}
{{ product.metafields.my_fields.product_video_url }}
{% endif %}
{% endif %}
If anyone had an idea of how I can do this using the code below and inserting a metafield (and even better, a metafield IF it's filled, otherwise default back to video_url.id) I would really appreciate it.
{%- liquid
assign bg_color = section.settings.background-color
assign button_text = section.settings.button-text | escape | truncate: 30
assign button_url = section.settings.button-url | url_escape
assign full_width = section.settings.full-width
assign heading = section.settings.heading | escape
assign darken_video = section.settings.darken-video
assign light_text = section.settings.light-text
assign section_height = section.settings.section-height
assign sub_heading = section.settings.sub-heading | escape
assign thumbnail = section.settings.image
assign video_url = section.settings.video-url
if video_url.id
assign video_id = video_url.id
else
assign video_id = '_9VUPq3SxOc'
endif
assign cover_link = false
if button_text == blank and button_url != blank
assign cover_link = true
endif
assign button_type = 'button'
if light_text
assign button_type = 'inverted-secondary-button'
endif
-%}
Thank you for any help at all.
Metafields are pretty simple, and very useful. First off, you can set them in the Admin. Create one with a description.
Note the concept of my_fields is really dumb, instead, think of it as "context", the "what am I in all this". So you might use a namespace here, like "product_videos". Hence, at this point, think of it as a box named, with whatever is inside the box, still a mystery!
product.metafields.product_videos
Or your company name. Or your cat's name. Or whatever turns your crank. my_fields is just generic filler. Means nothing.
Once you get over that hump, that you've now got a namespace, you can actually get down to the nitty-gritty. Anything of value to you is likely to be a string here, a string representing a video. So you want to make the type of the Metafield to be text. A string of text.
To be of much use, you need a key. You want to grab onto the key! So if a product has a special movie, let the key guide you right to it! The key is going to be something brilliant like; video_id! So now you have a product, with a Metafield resource, in your namespace, product_videos, with a key! Namely video_id. And you can then go to that product in your Shopify Admin, and at the bottom of the details page, fill in the answer to the question of video_id. Give it some info. Where to find the video. What it is called. Anything useful to you.
Now in your theme, just reference {{ product.metafields.product_videos.video_id }} when you need it.

In ansible how to initialise a variable from another variable?

In an Ansible role, how to define a variable depending on another one?
I am designing a role and want its interface to understand a playbook variable like framework_enable_java = yes or framework_enable_java = mysql tomcat and want to write a vars/main.yml files that defines boolean values
framework_enable_java_core
framework_enable_java_mysql
framework_enable_java_tomcat
according to the content of framework_enable_java. I tried the obvious definitions similar to
framework_enable_java_mysql: 'mysql' in framework_enable_java
and several more or less subtle approaches like
framework_enable_java_mysql: {{ 'mysql' in framework_enable_java }}
or
{% if 'mysql' in framework_enable_java %}
framework_enable_java_mysql: yes
{% else %}
framework_enable_java_mysql: no
{% endif %}
None of them turned out to be working. The similar looking question is unrelated as it is more like implementing variable indirection than variable deduction.
Is it at all possible to write the desired vars/main.yml for my role? How would it look like? If it is not possible, what would be the best way to make these deductions? (e.g. using a task include?)
Answer from the comments:
framework_enable_java_mysql: "{{ 'mysql' in framework_enable_java }}"
Double quotes are essential here because otherwise YAML parser tries to construct an object(dictionary) and not templated variable.

How do I test script on my website that detects country location?

I have a website that is hosted with Adobe Business Catalyst. I have the following liquid code that checks for country location and redirects the user accordingly:
<script> window.location =
{% if globals.visitor.country == 'AU' %}
"/au"
{% elsif globals.visitor.country == 'US' %}
"/us"
{% elsif globals.visitor.country == 'GB' %}
"/gb"
{% else %}
"/store-unavailable"
{% endif %}
</script>
I am located in Australia, so I can only check the country location for Australia. How would I test the script for the other countries?
Thanks
It really depends on how liquid gets the information about the visitor. Probably it takes the language from the user-agent information send by the browser. If this is the case, you can simply change the language in your browser settings (for Firefox: Edit - Settings - Content - Language).
If it is not the case, it probably looks up the IP. Then you would have to appear with another IP (by using a proxy - if you use TOR you can see what country that IP is from).
You should use a proxy server to trick the browser that you are coming from a different country.
You can also google "test website from different country" and you will find many solutions.