external JSON data fails to output anything in Jekyll - json

I'm trying to fetch external JSON data into Jekyll and things are not working out.
I found this code (link to a gist), that is a fork of another gist... this just adds the external method ("from url").
I tried to morph that into my own tag plugin (or what ever they are called), in order to simplify it and possibly resolve some issues:
_plugins/externaljson.rb
require 'json'
require 'net/http'
module ExternalJSON
class ExternalJSON_tag < Liquid::Tag
def initialize(tag_name, text, tokens)
super
#text = text
end
def render(context)
if /(.+) from (.+)/.match(#text)
url = context[$2].strip
uri = URI( url )
response = Net::HTTP.get( uri )
data = JSON.parse( response )
context[$1] = JSON data
return ''
end
end
end
end
Liquid::Template.register_tag('externalJSON', ExternalJSON::ExternalJSON_tag)
...but I didn't really solve all my issues or learn much of anything from that. The only thing I think I learned is that the problem is likely somewhere between parsing the file in ruby and jekyll reading it.
I ran this test using the tag plugin code above(↑):
---
layout: default
---
<!-- Using the code I modified -->
<!-- This capture exists to combine a string and a variable, but it's just a static url for the purposes of this example -->
{% capture myUrl %}
https://api.guildwars2.com/v2/recipes/2889
{% endcapture %}
{% externalJSON jsonData from myUrl %}
{% for data in jsonData %}
{{ data}}
{% endfor %}
<!-- Jekyll's native way of handling local data files -->
<!-- I just saved that json data from the url above(↑) locally for this one -->
{% for data in site.data.example %}
{{ data }}
{% endfor %}
This test made me realize that both methods output the data slightly differently.
My external attempt:
{"type":"Meal","output_item_id":12210,"output_item_count":2,"min_rating":0,"time_to_craft_ms":1000,"disciplines":["Chef"],"flags":[],"ingredients":[{"item_id":24359,"count":1},{"item_id":12132,"count":1}],"id":2889,"chat_link":"[&CUkLAAA=]"}
Jekyll's native method (for local files)
{"type"=>"Meal", "output_item_id"=>12210, "output_item_count"=>2, "min_rating"=>0, "time_to_craft_ms"=>1000, "disciplines"=>["Chef"], "flags"=>[], "ingredients"=>[{"item_id"=>24359, "count"=>1}, {"item_id"=>12132, "count"=>1}], "id"=>2889, "chat_link"=>"[&CUkLAAA=]"}
And if I try to do for example {{ data.type }}, my external attempt returns nothing and the Jekyll method returns the value just like it should. I just can't figure out how to change the formatting or what ever the missing piece is there.
What am I doing wrong?

replace your render(context) with following:
def render(context)
if /(.+) from url (.+)/.match(#text)
resp = Net::HTTP.get_response(URI($2.strip))
data = resp.body
context[$1] = JSON data
nil
else
# syntax error
raise ArgumentError, 'ERROR:bad_syntax'
end
end
Then call the data like so:
{% externalJSON data from url http://foo.json %}
This will provide you with a data object that can be called to render individual keys.
If the data is an array, loop through the elements and call the desired key
{% for entry in data %}
{{ entry.type }}
{% endfor %}
If the data is an object (hash), call the key directly.
{{ data.type }}

Related

Render multiple Altair charts into a webpage

To create one webpage with multiple Altair charts, all nicely formatted and easily deployable to GitHub Pages, I am trying to get several tools to work together. I am rendering the charts' json into a markdown document with jinja and then using Hugo to convert the markdown to HTML. I am using a Hugo partial (in extend_head.html, shown below) to include the scripts for Vega, Vega-Lite, and VegaEmbed, and I see them in the HTML of the webpage.
extend_head.html
{{- /* Head custom content area start */ -}}
{{- /* Insert any custom code (web-analytics, resources, etc.) - it will appear in the <head></head> section of every page. */ -}}
{{- /* Can be overwritten by partial with the same name in the global layouts. */ -}}
{{- /* Head custom content area end */ -}}
{{- /* To render LaTex math */ -}}
{{ if or .Params.math .Site.Params.math }}
{{ partial "math.html" . }}
{{ end }}
<script src="https://cdn.jsdelivr.net/npm/vega#5"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite#4.8.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed#6"></script>
I also have the following Hugo shortcode to call vegaEmbed. It includes the .Get method with a positional argument.
altairshortcode.html
<p>
<div id="vis"></div>
<script type="text/javascript">
var spec = {{.Get 0}}
vegaEmbed('#vis', spec);
</script>
</p>
The markdown template prior to rendering figure1's json looks like
---
title: "Aa1chart"
date: 2023-01-29T15:43:31-05:00
draft: false
---
Figure 1.
{{ '{{' }}< altairshortcode {{ figure1 }} >{{ '}}' }}
The Python code that's telling jinja to render the json into the template is (omitting the creation of the plot itself)
from pathlib import Path
from jinja2 import Template
# a scatter plot was generated with Altair and confirmed to display; omitted for brevity
p = Path.cwd()/'content/posts/aa1chart.md'
template = Template(p.read_text())
o = Path.cwd()/'content/posts/aa1chart.md'
o.write_text(template.render({
'figure1': figure1.to_json(),
}
)
)
After rendering the jinja template with the chart json, hugo runs, but hugo server -D, which launches a local server, gives the following error: unrecognized character in shortcode action: U+007B '{'. Note: Parameters with non-alphanumeric args must be quoted
After manually enclosing the json in double quotes, I got a different error unterminated quoted string in shortcode parameter-argument: '{
Some alternatives tried:
Using a named, instead of positional, parameter in the shortcode: Hugo runs, but hugo server -D errors: got quoted positional parameter. Cannot mix named and positional parameters. In the markdown, I had {{ '{{' }}< altairshortcode cjson={{ stage0 }} >{{ '}}' }}, and {{.Get "cjson"}} in the shortcode.
{{ .Inner }} converts the chart's json to a string, and so the chart does not appear in the webpage.
Placing Altair's figure1.to_html() instead of figure1.to_json() can display one chart in the webpage, but if multiple chart placeholders exist in the markdown, only one chart appears, and it is the last chart in the first position.
Looking at Hugo's built-in shortcodes, I hypothesize that if I utilized more logic or functions in my custom shortcode, such as toJSON, the json-to-string conversion can be prevented.

Unable to loop through array when using 'eleventy-cache-assets'

In eleventy, I'm using the 'eleventy-cache-assets' utility to retrieve API data from TMDB and place it in a cache. The JSON is successfully retrieved and stored in the cache. I can also use the Nunjucks dump filter to dump the full JSON to a page. However, I cannot run a for loop over the JSON content. It's behaving as if the data does not exist. I'm likely making a schoolboy error here but I can't see it.
Here's the JS that retrieves the data (successfully).
module.exports = async function () {
try {
// Grabs either the fresh remote data or cached data (will always be fresh live)
let json = await Cache(
`https://api.themoviedb.org/3/movie/upcoming?api_key=${TOKEN}&language=en-GB&region=GB`,
{
duration: "1d", // 1 day
type: "json",
}
);
return {
films: json,
};
} catch (e) {
return {
films: 0,
};
}
};
Here is how I'm trying to loop through the content. The else condition is returning. When I remove the else condition, nothing is returned (just the empty ul). If I've targeted the node incorrectly, I should have x number of empty li tags, but I don't.
<ul>
{% for film in films %}
<li>{{ results.title }}</li>
{% else %}
<li>This displays if the 'films' collection were empty</li>
{% endfor %}
</ul>
The issue is likely just that you're wrapping the result from the API in an additional object, and eleventy adds an additional wrapper based on the file name of the data file, so you need to modify the way you access the results.
According to the TMDB documentation, the API will return something like this (JSON-encoded):
const json = {
page: 1,
results: [
{/** ... */},
{/** ... */},
{/** ... */},
]
}
Then you're wrapping that result in another object:
return {
films: json,
};
Finally, eleventy makes that data available to templates under a variable name based on the name of your data file — i.e. the file contains your API call. You didn't mention the name of the file, let's assume it lives in your data directory as tmdb.js. That means the variable accessible to your template will look something like this:
const tmdb = {
films: {
page: 1,
results: [
{/** ... */},
{/** ... */},
{/** ... */},
]
}
}
See the documentation on how eleventy parses data files for more information.
This means you can access the results in this way:
<ul>
{% for film in tmdb.films.results %}
<li>{{ film.title }}</li>
{% else %}
<li>This displays if the 'films' collection were empty</li>
{% endfor %}
</ul>
Note also that your original code used {{ results.title }} which isn't defined in your scope, I changed that to use the loop variable film. Also, adjust the tmdb variable to the name of your data file.
I would also recommend not wrapping the response of the API in an additional object (the key films also implies it's a list of films, not the complete API response). It doesn't really add anything and only increases complexity.

Twig - Set twig variables from manifest.json

I'm trying to implement a Webpack environment on my Craft 3 projects. In order to dynamically call my hashed resources I'm outputting the in a manifest.json file and importing it into my template.
manifest.json
{"app":["js/app3bfb132e4187389fccd4.js","css/app53079ca4a05210b4af0c.css"],"vendor":"js/vendor49171fe3f01c19da9296.js"}
index.twig
{% set manifest %}
{% include './manifest.json' %}
{% endset %}
The output of that variable is a string. Is there anyway to encode it so that the variables are accessible/printable using only Twig? (Using {{ manifest.app }} for example)
You have to decode the JSON first. I would suggest one of the two approaches:
Create custom manifest function for Twig which will return decoded manifest object
or create json_decode filter for Twig, decode included json content and use it
manifest function
<?php
namespace App\Twig;
class ManifestExtension extends \Twig_Extension
{
private $manifestFile;
public function __construct($manifestFile)
{
$this->manifestFile = $manifestFile;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('manifest', array($this, 'manifest')),
);
}
public function manifest()
{
$content = file_get_contents($this->manifestFile);
return json_decode($content);
}
}
You can register it as a service in services.yml providing path to the manifest.json file.
App\Twig\ManifestExtension:
class: 'App\Twig\ManifestExtension'
arguments: ['%kernel.project_dir%/../public/manifest.json']
tags: [twig.extension]
Usage:
{% set manifest = manifest() %}
json_decode filter
It's already been covered here:
Decoding JSON in Twig
Usage:
{% set manifest %}
{% include './manifest.json' %}
{% endset %}
{% set manifest = manifest | json_decode %}

Creating my first Custom Template in django

I am building my first app in Django 1.8 with Python 2.7, I need to pass some value from the view.py to my HTML template.
I am using the following code
in Views.py
import datetime
from django import template
from django.shortcuts import render_to_response
from django.template import RequestContext
register = template.Library()
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = str(format_string)
def render(self, context):
now = datetime.datetime.now()
print "Render in CurrentTimeNode:", now
return now.strftime(self.format_string)
#register.simple_tag
def current_time(format_string):
print "Current Time"
return CurrentTimeNode(datetime.datetime.now().strftime(format_string))
current_time = register.tag(current_time)
def post(request):
datos = "hola"
print datos
return render_to_response('sitio/post.html', { 'datos':datos} , RequestContext(request))
In Post.html
<html>
<head>
some title
</head>
<body>
{% current_time %}
{{ timezone }}
</body>
</html>
Where I want to get the time using the tag "current_time" and dump in my html file. I am getting the following message error:
Exception Type: TemplateSyntaxError
Exception Value:
Invalid block tag: 'current_time'
What is missing to register the Block Tag?
It'd be nice if you can properly format your code (I can't edit your post for some reason). Judging by your post though, the issue is that you are not loading the tag. You should put the current_time function in a folder called "templatetags" (this folder should be at the same level as your views.py and models.py file). Add the
__init__.py
file to ensure the directory is treated as a Python package.
Next, in the templatetags folder, place current_time function in a file called current_time.py. Then in your template, add this line to the top of the template:
{% load current_time %}
Look at the documentation here for more information: https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/

404 error constructing link from parameter from "def get(self, notebook_id):"

I am getting the dreaded 404 html error message with this url
http://localhost:8088/editnotebook/Roger
The link is produced with this href.
{% for page in pages %}
<li> {{ page }} </li>
{% endfor %}
The python code is as follows.
def get(self, notebook_id):
iden = notebook_id
notebook = db.get(db.Key.from_path('Notebooks', iden))
self.render_template('editnotebook.html', {'notebook': notebook})
The model is as follows.
from models import Notes, Notebooks
class Notebooks(db.Model):
user = db.StringProperty()
moreinfo = db.StringProperty(multiline=True)
deleteRequested = db.BooleanProperty(default=False)
Is that enough information to tell why I am getting the 404 error and maybe fix it?
This question is related to the question here.
hyperslug really answered this by noticing that I had not posted my routes.py code.
In the routes.py you have to add the regex ([\w]+) to accommodate the link parameter.