How can I exclude drafts from showing up in collections? Liquid/Eleventy - arrays

I'm building a website with Eleventy for the first time, and even though I have worked with Liquid for a while now, I just can't crack this one.
I want to simplify the architecture as much as possible. This is why I have assigned my collections to variables:
{% assign blogposts = collections.posts %}
so later on in the site I can just write a really short and sweet:
{% for post in blogposts %}...{% endfor %}
instead of assigning the collections just above it in a file.
Now here's my problem:
I would love to filter away posts marked as draft (using draft: true in the frontmatter). I thought I'd be able to do it like one of these (I've tried...)
{% assign blogposts = collections.posts | draft: false %}
{% assign blogposts = collections.posts | where: draft, false %}
{% assign blogposts = collections.posts | where: data.draft, false %}
{% assign blogposts = collections.posts_nl | where: "draft", "false" %}
Does anyone know how I can accomplish this? Unfortunately it feels to me like the version of liquid used in Eleventy is an older one, and the documentation I can find never seems to address how I can do this. I would really appreciate some help! :)

Instead of filtering the collection in your template, you can use the configuration API to create a filtered collection in your .eleventy.js file. This should work:
// .eleventy.js
eleventyConfig.addCollection('posts', collections => {
// get all posts by tag 'post'
return collections.getFilteredByTag('post')
// exclude all drafts
.filter(post => !Boolean(post.data.draft))
});
If you need both both the filtered list of posts as well as the complete list, you can also use different aliases:
// .eleventy.js
eleventyConfig.addCollection('posts', collections => {
return collections.getFilteredByTag('post');
});
eleventyConfig.addCollection('published_posts', collections => {
// get all posts by tag 'post'
return collections.getFilteredByTag('post')
// exclude all drafts
.filter(post => !Boolean(post.data.draft))
});

Related

Comparing two arrays, checking for matching values

Using HubL (as I'm building a module in HubSpot), I have two arrays:
topics : Which is a list of topics.
all_tags: Which is an array of all the blog tags in the system.
If I dump out these arrays, this is what it will return:
{{ topics }} prints the following: [Data, Accounting, Insight]
{{ all_tags }} prints the following: [Accounting, Press, Data]
So essentially, {{ topics }} has a tag ("Insight") that doesn't exist in the system yet.
What I'm trying to do is to create a third array, which will contain matching results from the two above arrays. For example, topics_final, once returned, should print [Data, Accounting].
But, when printing {{ topics_final }}, the array is empty.
What I've tried:
<!-- this gets all tags -->
{% set all_tags = blog_topics( blog_id , 250) %}
<!-- create arrays -->
{% set topics = [] %}
{% set topics_final = [] %}
<!-- append topic data to the array -->
{% for item in module.add_topics.topics %}
{% set topic_option = item|striptags %}
{% do topics.append( topic_option ) %}
{% endfor %}
<!-- check if topic tags exists in HubSpot -->
{% for topics in all_tags %}
{% if topics in all_tags %}
{{ topics }}
<!-- results with above
Data, Accounting, Insight
-->
{% else %}
else
{% endif %}
{% endfor %}
With the above, it just prints out the {{ topics }}, even though Insight isn't in the all_tags array.
Note: Tagging Jinja2 as the syntax is similar
A combination of the filter reject and the built in test in could help you achieve this.
Sadly, it seems the reject filter does not accept negating a test, still, you can reject all the elements of topics that are not in all_tags then reject the said elements from the topics list.
So that ends with:
{{ topics | reject('in', topics | reject('in', all_tags) | list) | list }}
Switch yields:
[
"Data",
"Accounting"
]

TemplateSyntaxError - Invalid filter: 'translate' [duplicate]

I want to use AngularJS with Django however they both use {{ }} as their template tags. Is there an easy way to change one of the two to use some other custom templating tag?
For Angular 1.0 you should use the $interpolateProvider apis to configure the interpolation symbols: http://docs.angularjs.org/api/ng.$interpolateProvider.
Something like this should do the trick:
myModule.config(function($interpolateProvider) {
$interpolateProvider.startSymbol('{[{');
$interpolateProvider.endSymbol('}]}');
});
Keep in mind two things:
mixing server-side and client-side templates is rarely a good idea and should be used with caution. The main issues are: maintainability (hard to read) and security (double interpolation could expose a new security vector - e.g. while escaping of serverside and clientside templating by themselves might be secure, their combination might not be).
if you start using third-party directives (components) that use {{ }} in their templates then your configuration will break them. (fix pending)
While there is nothing we can do about the first issue, except for warning people, we do need to address the second issue.
you can maybe try verbatim Django template tag
and use it like this :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
{% verbatim %}
<div ng-app="">
<p>10 is {{ 5 + 5 }}</p>
</div>
{% endverbatim %}
If you did separate sections of page properly then you can easily use angularjs tags in "raw" tag scope.
In jinja2
{% raw %}
// here you can write angularjs template tags.
{% endraw %}
In Django template (above 1.5)
{% verbatim %}
// here you can write angularjs template tags.
{% endverbatim %}
We created a very simple filter in Django 'ng' that makes it easy to mix the two:
foo.html:
...
<div>
{{ django_context_var }}
{{ 'angularScopeVar' | ng }}
{{ 'angularScopeFunction()' | ng }}
</div>
...
The ng filter looks like this:
from django import template
from django.utils import safestring
register = template.Library()
#register.filter(name='ng')
def Angularify(value):
return safestring.mark_safe('{{%s}}' % value)
So I got some great help in the Angular IRC channel today. It turns out you can change Angular's template tags very easily. The necessary snippets below should be included after your angular include (the given example appears on their mailing lists and would use (()) as the new template tags, substitute for your own):
angular.markup('(())', function(text, textNode, parentElement){
if (parentElement[0].nodeName.toLowerCase() == 'script') return;
text = text.replace(/\(\(/g,'{{').replace(/\)\)/g, '}}');
textNode.text(text);
return angular.markup('{{}}').call(this, text, textNode, parentElement);
});
angular.attrMarkup('(())', function(value, name, element){
value = value.replace(/\(\(/g,'{{').replace(/\)\)/, '}}');
element[0].setAttribute(name, value);
return angular.attrMarkup('{{}}').call(this, value, name, element);
});
Also, I was pointed to an upcoming enhancement that will expose startSymbol and endSymbol properties that can be set to whatever tags you desire.
I vote against using double parentheses (()) as template tag. It may work well as long as no function call is involved but when tried the following
ng:disabled=(($invalidWidgets.visible()))
with Firefox (10.0.2) on Mac I got a terribly long error instead of the intended logic. <[]> went well for me, at least up until now.
Edit 2012-03-29:
Please note that $invalidWidgets is deprecated. However I'd still use another wrapper than double braces. For any angular version higher than 0.10.7 (I guess) you could change the wrapper a lot easier in your app / module definition:
angular.module('YourAppName', [], function ($interpolateProvider) {
$interpolateProvider.startSymbol('<[');
$interpolateProvider.endSymbol(']>');
});
API docs.
You could always use ng-bind instead of {{ }}
http://docs.angularjs.org/api/ng/directive/ngBind
<span ng-bind="name"></span>
I found the code below helpful. I found the code here: http://djangosnippets.org/snippets/2787/
"""
filename: angularjs.py
Usage:
{% ng Some.angular.scope.content %}
e.g.
{% load angularjs %}
<div ng-init="yourName = 'foobar'">
<p>{% ng yourName %}</p>
</div>
"""
from django import template
register = template.Library()
class AngularJS(template.Node):
def __init__(self, bits):
self.ng = bits
def render(self, ctx):
return "{{%s}}" % " ".join(self.ng[1:])
def do_angular(parser, token):
bits = token.split_contents()
return AngularJS(bits)
register.tag('ng', do_angular)
If you use django 1.5 and newer use:
{% verbatim %}
{{if dying}}Still alive.{{/if}}
{% endverbatim %}
If you are stuck with django 1.2 on appengine extend the django syntax with the verbatim template command like this ...
from django import template
register = template.Library()
class VerbatimNode(template.Node):
def __init__(self, text):
self.text = text
def render(self, context):
return self.text
#register.tag
def verbatim(parser, token):
text = []
while 1:
token = parser.tokens.pop(0)
if token.contents == 'endverbatim':
break
if token.token_type == template.TOKEN_VAR:
text.append('{{')
elif token.token_type == template.TOKEN_BLOCK:
text.append('{%')
text.append(token.contents)
if token.token_type == template.TOKEN_VAR:
text.append('}}')
elif token.token_type == template.TOKEN_BLOCK:
text.append('%}')
return VerbatimNode(''.join(text))
In your file use:
from google.appengine.ext.webapp import template
template.register_template_library('utilities.verbatim_template_tag')
Source:
http://bamboobig.blogspot.co.at/2011/09/notebook-using-jquery-templates-in.html
You can tell Django to output {{ and }}, as well as other reserved template strings by using the {% templatetag %} tag.
For instance, using {% templatetag openvariable %} would output {{.
I would stick with a solution that uses both django tags {{}} as well angularjs {{}} with a either a verbatim section or templatetag.
That is simply because you can change the way angularjs works (as mentioned) via the $interpolateProvider.startSymbol $interpolateProvider.endSymbol but if you start to use other angularjs components like the ui-bootstrap you will find that some of the templates are ALREADY built with standard angularjs tags {{ }}.
For example look at https://github.com/angular-ui/bootstrap/blob/master/template/dialog/message.html.
If you do any server-side interpolation, the only correct way to do this is with <>
$interpolateProvider.startSymbol('<{').endSymbol('}>');
Anything else is an XSS vector.
This is because any Angular delimiters which are not escaped by Django can be entered by the user into the interpolated string; if someone sets their username as "{{evil_code}}", Angular will happily run it. If you use a character than Django escapes, however, this won't happen.

Django - Access list values on template

I've to get access to all values availables in a list(array) in a Django template. All examples I find online, use dictionaries instead. So, the list is passed correctly to the template, but the following code doesn't work:
{% for item in array %}
field_names = field_names + "{{ item }},";
{% if forloop.last %}
field_names = field_names + "{{ item }}";
{% endif %}
{% endfor %}
I could access the values using {{ field.[0] }} , {{ field.[1] }} , etc. But, I have to make it work independently of the list's size.
Can anybody give me a hint?
P.S. - I cannot use a dictionary to pass the values to the template, since that code wasn't donne by me, and I'm not supposed to touch it.
In your view:
my_joined_list = ",".join(somelist)
In your template:
field_names = {{ my_joined_list }}
Resolved it!! I just passed the variable to the template exactly as i needed it and used {{ array|safe }} to resolve it to a Javascript variable. Done!

TWIG - variable == objectattribute

I am new to Twig and Symfony2 (and PHP as well). I currently have an array of objects where I can access an attribute by doing {{result.attribute1}} after a {for result in results} statement.
I would like to use a variable {{var| removePath}} (where removePath is an extension I made in Twig), and use it to iterate over the results array. What I would like to do is return the row if {{var| removePath}} == result.attribute1.
Is this possible to do in Twig? If so, how should I approach this? I have already tried something similar to the code found below, but the "else" statement is executed (saying there's no match). I also tried var == result.attribute, but this didn't work either.
{% for result in results %}
{% if var|removePath in result.attribute1 %}
{{ var | removePath }} exists.
{% else %}
{{ var | removePath }} doesn't exist in array.
{% endif %}{% endfor %}
Thus I think I have 2 questions;
1) Is it possible in TWIG (if so, how)
2) Is there an easier/better way to get the whole row (object array also contains attributes 2, 3 & 4, and I would like to return those that are in common with attribute 1 (if it matches var))
See schema of my array recently added
Thank you in advance for your help! I hope my question is understandable :S
Just surround the expression with brackets.
use (var|removePath) which groups an expression.
example:
{% if ( 1+1 ) == 2 %}
And you can use the attribute() function to access properties and methods of an object/array.
{{ attribute(object, method) }}
{{ attribute(object, method, arguments) }}
{{ attribute(array, item) }}
attribute chapter in the twig docs.
Further shorten template code using the ternary operator like this:
{% set strippedVar = var|removePath %}
{{ strippedVar }}{{ (strippedVar in result.attribute1) ? 'exists' : 'does not exist' }}.
Setting the filtered variable to another prevents executing the filter multiple times.

Testing for presence of form instance in Jinja2

I'm using WTforms with Jinja2 and want to change my templates page title depending on whether I am creating a new instance of editing an existing form object.
This is what I wrote in the template:
{% block title %}{% if form.obj %}Edit{% else %}New{% endif %} Post{% endblock %}
What I expect to see:
if the form is filled out I expect to see "Edit Post" in the page title.
if the form is empty I expect to see "New Post" in the page title.
What I get: "New Post" in both instances.
Here is my PostHandler that is passing the form values.
def with_post(fun):
def decorate(self, post_id=None):
post = None
if post_id:
post = models.BlogPost.get_by_id(int(post_id))
if not post:
self.error(404)
return
fun(self, post)
return decorate
class PostHandler(BaseHandler):
def render_form(self, form):
self.render_to_response("edit.html", {'form': form})
#with_post
def get(self, post):
self.render_form(MyForm(obj=post))
#with_post
def post(self, post):
form = MyForm(formdata=self.request.POST, obj=post)
if post and form.validate():
form.populate_obj(post)
post.put()
post.publish()
self.render_to_response("published.html", {'post': post})
elif self.request.POST and form.validate():
post = models.BlogPost()
post.title = form.title.data
post.body = form.body.data
post.tags = form.tags.data
post.publish()
self.render_to_response("published.html", {'post': post})
else:
self.render_to_response('edit.html', {'form':form})
In short, all I'm trying to do is test whether the form is filled, and change my page title "New Post" or "Edit Post" accordingly.
While Form accepts a obj argument, it doesn't actually store it, it just uses that obj to fill in any blanks that formdata didn't provide. So when you ask Jinja2 {% if form.obj %} it's always going to be False, because there is never a obj property (unless you have a field that happens to be called obj of course).
If you're editing a post, you'll have an id to work with so you know which post to update in the database, so where are you currently storing that? Assuming you store it as a hidden field, you could just do:
{% if form.id.data == None %}Must be a New form {% endif %}
If you wanted to check if the entire form was empty, you could access the form.data dictionary, and make sure all the entries are None, although you need to be careful, because I know that FileField returns a u'None' instead of a real None, so you'd have to double check what Fields you care about.

Resources