Django Templates generate messy whitespace HTML - django-templates

How can I make the generated HTML be cleaner in terms of whitespce?
Django Templating seems to be very sloppy about it.
For example, tags it recognizes, like IFs or FORs are parsed then replaced by an empty line.
Another example is when I include a file with N linkes of HTML code. If the include statement is tabbed, the first linke from the included file is indented propertly, the rest are pulled to the left.
And so on.
{% spaceless %} doesn't seem to do anything.
Is there a setting somewhere about how whitespace should be treated?
Or another solution?
Thank you.

I found this while looking for the answer to the same question and it seems like there isn't a clean and clear way to do this using the Django syntax (that I have found but I may have overlooked something) so on that note I'd recommend Jinja2. I have experience with using it for whitespace removal with SaltStack. One method is change {% this %} to {% this -%} which causes no newline to be appended so if you have a line containing only {% this -%} then it won't appear as anything in the generated html.

You can override the NodeList's render method as I've done. See my question with working code (applies only to the block and include tags):
Proper indentation in Django templates (without monkey-patching)?

There is a ticket in Django's issue tracker about having better handling of whitespace. It was closed as wontfix in 2014, with the following comment:
As far as I know, the consensus among the core team is that:
the Django template language is good enough for generating HTML, which isn't sensitive to whitespace;
the long term plan is to replace it with a better engine (most likely Jinja), not to keep working on it;
if you have more specialized needs (want to generate RTF?) just use another template engine, there are several to choose from.
For HTML output, I'm personally fine with the messy whitespace. It will have minimal effect on HTTP response sizes, if the responses are compressed.
I've had to work on a few cases where precise whitespace and newline control is important (for example, I have a template for Telegram messages, each newline will be a line break in the final message). To tidy up the template, I ended up writing a couple custom tags: {% linemode %} and {% line %}.
Example template:
{% linemode %}
{% line %}Line one.{% endline %}
This content will be ignored.
{% if True %}
{% line %}Line two.{% endline %}
{% endif %}
{% endlinemode %}
Result:
Line one.
Line two.
The idea here is that, the {% linemode %} block will throw away everything that is not also wrapped in {% line %} tags. That way, the {% if ... %} and other bits don't add unwanted spaces or newlines. Source here.

Related

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 %}

Whitespace control in Shopify Liquid

Shopify recently added whitespace control to the Liquid templating language:
https://help.shopify.com/themes/liquid/basics/whitespace
You essentially add an hyphen in your tag syntax {{- -}}, {%- -%} to strip whitespace (html empty line) outputted by a tag. For example:
{%- assign variable = "hello" -%}
{{ variable }}
Renders:
hello
Instead of:
 
hello
Is there a way to turns this on for all assign tags? and/or all specific control flow or iteration tags?
Indeed. You turn it on when you type your Liquid. Let your fingers do that talking!
If you load up your theme in a text editor, you can use the common Find All command to find all instances of assign. Use that to replace the surrounding {% %} with {%- -%}. Pretty much all there is to it. Repeat for any keywords you like.

Using Ember & Handlebars with Django 1.4.2

I'm trying to get Ember & Handlebars to work with Django 1.4.2 without much success. I'm using Django-ember (latest version from here): https://github.com/noirbizarre/django-ember, Handlebars.js (1.0.rc.1) & Ember.js (1.0.0-pre.2).
Following the Django-ember instructions, I've added 'ember' to the installed apps in settings, placed {% load ember %} at the top of my template, and then placed this in a block:
{% handlebars "application" %}
{{App.name}}
{% endhandlebars %}
where I've declared App with name variable in a js file.
The output is just blank however, no script tag is inserted as viewed in the browser console window (Chrome).
If I completely remove the javascript includes, the script tag is rendered - just with "{{App.name}}" left entact as expected. So it looks like, Ember/Handlebars isn't rendering the template correctly. Any ideas why?
A probably related quirk is that if I try
{% tplhandlebars "tpl-infos" %}
{{total}} {% trans "result(s)." %}
<p>{% trans "Min" %}: {{min}}</p>
<p>{% trans "Max" %}: {{max}}</p>
{% endtplhandlebars %}
it throws a template error about {% trans being an invalid tag. This can be fixed by including {% load i18n %} but seeing as this isn't in the Django-Ember instructions, I'm inclined to think this is a sign of a bigger problem; perhaps it's not designed to work with Django 1.4.2.
Update
Seems it was actually rendering after all but that it's wrapping it in a div and putting it right at the bottom of the page where I hadn't noticed it. So, now the next question is how do I get this back at it's orignal point in the html?

Django Templates extends tag problem

I am using
{% extends "base.html" %}
I get the following error
must be the first tag in the template.
Can any one please help
place {% extends "base.html" %} in line 1 of your editor. Literally put it in line 1. REMOVE all comments at the top if you have any..
It must be the very first django template tag in your template.
Documentation says:
If you use {% extends %} in a
template, it must be the first
template tag in that template.
Template inheritance won't work,
otherwise.
Documentation can be found here
I also got into that problem.
I was using comment as the first tag it wasn't working.After I removed that it worked.
Used this:
{% comment %} inheriting the base html {% endcomment %}
To describe what I was doing.Got error.
Removed this and used extend as the first template tag.Worked!!!!
Always remember to mention the {% extends '<TEMPLATE_NAME>' %} in the first line itself, don't even try to put comments on the first line.
This will surely resolve the error!