multiple variables not defined - template - django-templates

I am trying to check whether none of 3 variables is defined in Django template to display a warning but I cannot find a way to do it correctly. I've tried:
{% if ((approved_users is None) and (approve_awaiting_users is None) and (archived_users is None) %},
{% if not (approved_users or approve_awaiting_users or archived_users) %},
{% if not approved_users and not approve_awaiting_users and not archived_users %},
all with multiple bracket versions but they cannot be parsed. What is the correct way to do it?

Related

Is it possible to use a liquid "where" array filter with nested properties?

I'm trying to filter an array of blocks using block settings. I can filter by properties like "type" using the following syntax:
{% assign example = section.blocks | where: "type", "photos" %}
What I need to do is filter by block settings, something like this:
{% assign example = section.blocks | where: settings.collection, collection.handle %}
The above example is failing silently.
A note: Currently I am accomplishing what I need using a capture with a for loop and an if statement, and then assigning with a split — but the code is so bloated, and doing all that for a simple filter operation seems ridiculous. I find myself constantly feeling like I'm fighting with liquid, and I guess I'm hoping it might be just a bit more elegant than I'm giving it credit for.
I don't know much about Ruby, but it seems you can't pass nested properties with dot notation to the where filter. However, after seeing people accessing nested values using map, I tested mixing the two, and the map filter seems to work well for this case.
I have a boolean setting called default in my blocks, and I got the settings object for the last block with default set to true using this:
{% assign obj = section.blocks | map: 'settings' | where: 'default' | last %}
Of course, then you can't get data outside of the settings object that was extracted. For that I think you really would need to loop through the section.blocks and find filter manually using the if tag.
You are doing it wrong. where will work only at the root element. In your case section.blocks is the root element so where can be used for something like section.blocks.abcd_property.
Rough example: {% assign example = section.blocks | where: 'collection', collection.handle %} will load all section blocks having their collection property as collection.handle value
This will work
{% if settings.collection == collection.handle %}
{% assign example = section.blocks %}
{% else %}
{% assign example = '' | split: '' %}
{% endif %}
Previously used map which loses outer data but found string notation works with where for nested properties:
E.g., Using a posts collection where each .md file has the front-matter:
header:
isArchived: true
The following liquid snippet filters archived posts via header.isArchived:
{% assign archived = site.posts | where: "header.isArchived", true %}

How to serve template based on collection handle (Shopify)

I have a number of different collection templates. They differ in the filters that they contain. Some have three filter dropdowns, some only have one.
I would like to display different templates based on the collection type (handle). I know that you can manually change template in the admin, but I want to handle this programmatically within Liquid so the client doesn't have to worry about toggling alternate templates.
I have had no luck in the documentation or Shopify community forums.
An example of the logic:
If collection.handle = handle-name,
Then use collection template X,
Else default collection template
Grateful for any pointers.
For detailed information you may refer to Shopify documentation on Creating Alternate Templates or a similar blog post on Partners Blog.
These articles suggest creating files with particular collection handle, that can be selected manually. Since you do not want to do that and have specified that only filters change based on different collections, so instead of replacing the whole template you can simply include the different snippet or section based on collection handle.
Example, inside collection.liquid
{% if collection.handle == 'collection-1' %}
{% section 'collection-filters-1' %}
{% elsif collection.handle == 'collection-2' %}
{% section 'collection-filters-2' %}
{% else %}
{% section 'collection-filters-default' %}
{% endif %}

Shopify(Liquid) multiple conditions in if statement

I am looping through all collections, and creating a preview item with each collection title, image and link. But I have 15 collections I would like to exclude.
Currently I am using 'contains' to exclude the 15 I don't want, but am wondering if theres a cleaner way to write this since its a really long if condition.
Thanks in advance!
Example below:
{% for collection in collections %}
{% if collection.title contains 'collection-1' or collection.title
contains 'collection-2' or collection.title contains 'collection-3'
or collection.title contains 'collection-4' or collection.title
contains 'collection-5' %}
{% else %}
// build item here
{% endif %}
{% endfor %}
I would create an array of exclusions and check to see if my exclusion array contains the collection in question. (And rather than the title, I would use the collection handle as the handle is guaranteed to only be 'clean' names and guaranteed to be unique)
Example:
{% assign collection_exclusion_array = 'collection-1, collection-2, collection-3, collection-4, collection-5' | remove: ' ' | split: ',' %}
{% for collection in collections %}
{% if collection_exclusion_array contains collection.handle %}
{% continue %}
{% endif %}
{% comment %} Build items here {% endcomment %}
{% endfor %}
How it works:
We cannot directly create arrays in Liquid - we can only make one by taking a string and using the split filter to create our array.
By using handles, we guarantee that our list values only contains letters, numbers and hyphens - there's no chance that our delimiter (in this case, the comma) can accidentally show up as part of the value.
We don't want spaces to be part of the array values, so we remove them before we use the split filter. We could instead just not put spaces between each value, but in my brain that reads like a terrible abuse of grammar. Either omitting spaces the first time or removing them after creating your string will work.
Now that we have our array of exclusions, when we loop through collections we can check to see if the current collection's handle shows up in the list.
If found, skip to the next collection using the continue statement - this saves a layer of indentation since we don't have to have an empty if followed by an else that contains everything that we want to do.
And there you go! Hope it helps :)
NB: For more information on handles in Shopify, see https://help.shopify.com/en/themes/liquid/basics/handle
An alternate method to achieve your exclusions:
If you give your collections some sort of flag that indicates that they shouldn't show up in your collection loop, you can manage each collection directly, rather than maintaining a separate list.
If we look at the collection page in your admin, though, we don't get a lot that's helpful: all we see are things like title, description, etc. Not even a place to give the collection a specific tag!
Fortunately, collections are able to have metafields - Shopify just has that feature hidden from normal users. Metafields allow you to create additional information for objects in your store (products, collections, pages, etc.), which you can then reference through Liquid.
You can read more about Shopify's use of metafields here: https://www.shopify.com/partners/blog/110057030-using-metafields-in-your-shopify-theme
My previous favourite plugin for accessing metafields was ShopifyFD, a browser extension that would let you view and edit that metadata right on your collection page, but unfortunately Shopify's recent changes to the admin have broken that plugin. The author is working on a new version, but it's not ready at the time of writing: https://freakdesign.com.au/blogs/news/shopifyfd-and-the-current-case-of-the-broken-tool
(Note: I haven't tried any of the other metafield-editing tools listed in the above linked article - when ShopifyFD started having trouble, I started doing my metafield editing using the admin API and creating/posting the requests myself: https://help.shopify.com/en/api/reference/metafield)
Once you have a way to easily set metafields (which, surprisingly, seems to be the hard part right now), your for-loop logic is extremely simple. Let's assume that the metafield you create for this purpose has the namespace 'preview' and the key 'exclude':
{% for collection in collections %}
{% if collection.metafields.preview.exclude %}
{% continue %}
{% endif %}
{% comment %} Do stuff! {% endcomment %}
{% endfor %}
This will now skip any collection that has any value set in your custom field, so if you change your mind about any current or future collection all that needs to change is the one metafield on the collection itself.

Jinja / Django for loop range not working

I'm building a django template to duplicate images based on an argument passed from the view; the template then uses Jinja2 in a for loop to duplicate the image.
BUT, I can only get this to work by passing a list I make in the view. If I try to use the jinja range, I get an error ("Could not parse the remainder: ...").
Reading this link, I swear I'm using the right syntax.
template
{% for i in range(variable) %}
<img src=...>
{% endfor %}
I checked the variable I was passing in; it's type int. Heck, I even tried to get rid of the variable (for testing) and tried using a hard-coded number:
{% for i in range(5) %}
<img src=...>
{% endfor %}
I get the following error:
Could not parse the remainder: '(5)' from 'range(5)'
If I pass to the template a list in the arguments dictionary (and use the list in place of the range statement), it works; the image is repeated however many times I want.
What am I missing? The docs on Jinja (for loop and range) and the previous link all tell me that this should work with range and a variable.
Soooo.... based on Franndy's comment that this isn't automatically supported by Django, and following their link, which leads to this link, I found how to write your own filter.
Inside views.py:
from django.template.defaulttags import register
#register.filter
def get_range(value):
return range(value)
Then, inside template:
{% for i in variable|get_range %}
<img src=...>
{% endfor %}

Shopify liquid: How can I conditionally include snippets in Shopify liquid?

I would like to include a snippet in a template but only if the snippet file exist. Is there any way I can do it?
Now I'm just using:
{% include 'snippetName' %}
But this throws the error:
Liquid error: Could not find asset snippets/snippetName.liquid
The reason I need such a functionality is because I have a background process that adds the snippet later on.
Had this problem myself. This was my solution:
{% capture the_snippet_content %}{% include the_snippet %}{% endcapture %}
{% unless the_snippet_content contains "Liquid error" %}
{% include reviews_snippet %}
{% endunless %}
Basically capture the snippet’s content as a variable.
If there is no snippet Shopify generates the error:
Liquid error: Could not find asset
snippets/caroline-flint-reviews.liquid
So check to see if it’s generated that… if so don’t print the snippet
:D
Of course this would break if you intended your snippet to include "Liquid error" or if Shopify ever change the error message.
Extending on Jon's answer;
Create a file called snippet.liquid
{% capture snippet_content %}{% include snippet %}{% endcapture %}
{% unless snippet_content contains "Liquid error" %}
{{ snippet_content }}
{% endunless %}
Then when you want to include a file only if it exists
{% include 'snippet' with 'filename_of_include' %}
Okay, Coming here in 2021.
The include syntax is deprecated and infrequently used, also extending #a.wmly answer, this should be the latest syntax replacing include with render:
{% capture snippet_content %}{% render 'your-snippet-name' %}{% endcapture %}
{% if snippet_content contains "Could not find asset" %}
{% comment %} do nothing {% endcomment %}
{% else %}
{% render 'your-snippet-name' %}
{% endif %}
references for include vs render : https://shopify.dev/docs/themes/liquid/reference/tags/deprecated-tags#include
Alternatively, you could create your own tag which does a check on the existence of the file, before attempting to process it.
https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
#vovafeldman Not sure why you can't have a blank snippet, but there's no file exists.
The only other option I can think of is since you are using a BG process to generate the snippet (and I assume upload it), you can always use the template API to upload the version of the template that includes the snippet at the same time.
Using the code listed above by Jon or a.wmly both still gave me errors. However, simply writing
{% include 'snippet_name' %}
worked just fine.
Note that this only worked for files located in the "snippets/" folder. So Templates, for instance, did not work using this method.