Struggles unpacking a two-dimensional list in template - python

I'm passing a bunch of data into my template but am having a hard time breaking apart a zipped list of items. No matter what I try, I always get the following error.
Need 2 values to unpack in for loop; got 0.
Heres my code:
views.py
import requests
from django.shortcuts import render
from django.http import HttpResponse
dictionary, words = [[], []], []
def home(request, username='johnny'):
template_name = 'main/index.html'
url = "https://www.duolingo.com/users/{}".format(username)
getUserData(url)
context = {
'username': username,
'dictionary': dictionary,
'words': words,
}
# print(context)
return render(request, template_name, context)
def getUserData(url):
response = requests.get(url)
userdata = response.json()
wordlists, explanations = [], []
for language in userdata['language_data']:
for index in userdata['language_data'][language]['skills']:
if index.get('levels_finished') > 0:
wordList = index.get("words")
wordlists.append(wordList)
explanations.append(index.get("explanation"))
for wordItem in wordList:
words.append(wordItem)
dictionary = list(zip(wordlists, explanations))
relevant template
{% block content %}
{% for words, exp in dictionary %}
{{ words }}
{{ exp|safe }}
{% endfor %}
{% endblock %}
I've tested this code, it works.
Once I refactored in Django to put wordLists in an array with explanations, things go to hell. If I print(dictionary) at the end of the method, the data shows in the console. Not sure what else I'm missing.

Your problem is with scope. the dictionary(variable) which you are returning from home function(as context) and dictionary in getUserData function are not in same scope. So whenever you are updating getUserData method's dictionary, its not being updated in home. I would not recommend your approach for dictionary as its using global variable. I would recommend something like this:
def getUserData(url):
response = requests.get(url)
userdata = response.json()
wordlists, explanations, words = [], [], []
for language in userdata['language_data']:
for index in userdata['language_data'][language]['skills']:
if index.get('levels_finished') > 0:
wordList = index.get("words")
wordlists.append(wordList)
explanations.append(index.get("explanation"))
for wordItem in wordList:
words.append(wordItem)
return list(zip(wordlists, explanations)), words # return the value of dictionary from here
def home(request, username='johnny'):
template_name = 'main/index.html'
url = "https://www.duolingo.com/users/{}".format(username)
dictionary, words = getUserData(url) # catch value of dictionary
context = {
'username': username,
'dictionary': dictionary,
'words': words,
}
# print(context)
return render(request, template_name, context)

Related

How to use JSON data on Django View

I need to use data as JSON from an API in Django View here is my JSON data;
[{'Count': 5491}]
I need to pass just value of the "Count" key to HTML and views.py is as below;
def serviceReport(request):
data = mb.post('/api/card/423/query/json')
context = {
'count' : data['Count']
}
return render(request, 'service_report.html', context)
I get error like this;
Exception Type: TypeError
Exception Value:
list indices must be integers or slices, not str
What I want is to pass value of count key to service_report.html and also I want to pass multiple JSON datas like data2, data3 as data on views.py how can I do it?
The json is returning a list that contains a dict.
[{'Count': 5491}]
The brackets are the list so access that with data[0]
def serviceReport(request):
data = mb.post('/api/card/423/query/json')
context = {
'count' : data[0]['Count']
}
return render(request, 'service_report.html', context)
Views.py:
def serviceReport(request):
data = mb.post('/api/card/423/query/json')
context = {
'data' : data
}
return render(request, 'service_report.html', context)
html template:
In Django you can use {% %} to use Python code into html,
so you can do something like this.
<div>
{% for element in data %}
<div>{{element.data}}</div>
{% endfor %}
</div>
Also if you want to check what's on your data, you could just use {{data}}
In anycase, i suggest you do a for in your views.py to append just the "Count" data into a list and then pass that list to the context.

Tell what model a queryset is from in the template

I am creating a list containing items from two different models and passing it to my template. Here is my view function:
def newsfeed(request):
Text = Post.objects.all().order_by('-Timestamp')
Images = ImagePost.objects.all().order_by('-Timestamp')
Posts = []
while True:
if Text[0].Timestamp >= Images[0].Timestamp:
Posts.append(Post.objects.get(id=Text[0].id))
Text = Text.exclude(id=Text[0].id)
else:
Posts.append(ImagePost.objects.get(id=Images[0].id))
Images = Images.exclude(id=Images[0].id)
if len(Text) == 0:
for i in Images:
Posts.append(i)
break
elif len(Images) == 0:
for i in Text:
Posts.append(i)
break
print(Posts[:6])
return render(request, 'campaign/newsfeed.html', {
"posts": Posts,
})
I need a way to find out which model each item in the list was from in the template so that I know how to render the item. Is there a way to tell without sending further data to the template?
You can give both models (or their common super class) a method:
def model_name(self):
return self.__class__.__name__
And in the template, you can check:
{% for p in posts %}
{% if p.model_name == 'ImagePost'%}
# ...
{% endif%}
{% endfor %}
If these are models from third-party packages, you can always just set attributes in the view:
for i in Images:
i.model_name = 'ImagePost'
Posts.append(i)

Django - variable value, TypeError

I created a tag like this:
#register.inclusion_tag('post/comment_block.html')
def limit_amount_in_a_page(page_nr=1, post_id=1, amount=5):
starting_index = page_nr*amount
for index in range(starting_index, starting_index + amount):
dosomething()
has_prev = (page_nr != 0)
has_next = ((page_nr + 1) * amount) < comments.count()
return {
something
}
The problem is : page_nr is always not an int.
and this is how I call the tag and assign the value to page_nr in the tag:
{% limit_amount_in_a_page page_nr=my_page_nr post_id=post.id amount=4 %}
this is where the value of my_page_nr comes from:
def to_post_page(request, post_id, page_nr):
post = get_object_or_404(Post, id=post_id)
form = CommentForm()
comments = Comment.objects.filter(pk=post_id)
return render(request, 'post/posts.html', {
'post': post,
'form': form,
'comments': comments,
'my_page_nr': page_nr,
})
this is the url calling the view:
url(r'^(?P<post_id>[0-9]+)/(?P<page_nr>[0-9]+)/$', views.to_post_page, name="post"),
{% for post in my_posts %}
<li>{{post.title}}</li>
{% endfor %}
The value passed to this url tag should be a int. As you can see, I passed a 0.
Really appreciate for any help!
The value for page_nr is extracted from the URL, and is therefore a string. If you need it to be an int, it's simple to convert it - you could do this in the view, for example:
return render(request, 'post/posts.html', {
'post': post,
'form': form,
'comments': comments,
'my_page_nr': int(page_nr),
})

Create in python a dictionary from JSON url

I want to create a list in HTML of locations from JSON API url in python.
#app.route('/api')
def api():
url = urlopen('https://api.openaq.org/v1/locations?country=GB').read()
#encoded we do not need it
#encoded_data = json.dumps(url)
#create variables
array = []
data = {}
#decoded
decoded_data = json.loads(url)
#we search for result each entry
for i in decoded_data["results"]:
#append every entry to an array
array.append(i["location"])
#we create a dictionary from that array created which has a variable (for jinja2) called location
data = [dict(location=array)]
return render_template('api.html', data=data)
But instead of receiving each element, I get this:
[u'Aberdeen', u'Aberdeen Union Street Roadside', u'Aberdeen Wellington Road', u'Armagh Roadside', u'Aston Hill', u'Auchencorth Moss', u'Ballymena Ballykeel', u'Barnsley Gawber', u'Barnstaple A39', u'Bath Roadside', u'Belfast Centre', u"Belfast Stockman's Lane", u'Billingham', u'Birkenhead Borough Road', u'Birmingham A4540 Roads...
Edit: Template
{% if data %}
<ul>
{% for d in data %}
<li>{{ d.location }}</li>
{% endfor %}
</ul>
{% else %}
<p class="lead">
You should not see this msg, otherwise, check the code again.
</p>
{% endif %}
I broke my answer down a bit because I didn't want to activate flask.
import requests
def api():
res = requests.get('https://api.openaq.org/v1/locations?country=GB')
data = res.json()['results']
return data
#app.route('/api')
def api():
res = requests.get('https://api.openaq.org/v1/locations?country=GB')
try:
data = res.json()['results']
except KeyError:
data = None
# this is the logic that you use in your template, I moved it out here
# cause i don't want to start a flask instance
for d in data:
print d['location']
return render_template('api.html', data=data)
api()
Basically I use the requests module which can return a json. I pass the results to the data varible. I used a for loop to demo how it would work in your template. Basically pass in the data as a dictionary and get the location via iteration d['location']
So the code to use is
import requests
#app.route('/api')
def api():
res = requests.get('https://api.openaq.org/v1/locations?country=GB')
try:
data = res.json()['results']
except KeyError:
data = None
return render_template('api.html', data=data)
You are converting the array to a dict, but then you are putting the dict inside an array of length 1, with the only object being the dict. The issue is, your template is then expecting each element in the array to be a dictionary, with a "location" field.
You either can remove the square brackets from the conversion data = dict(location=array) and then update your template to just do for d in data.location, or you can update your append call to append a dictionary item instead of a string: array.append({"location": i["location"]})
Couple of things:
url is a bytes object, which will not work with json.loads(str). So you'll have to convert it to a string either by doing json.loads(str(url,'utf-8')) or the method suggested by #Mattia
#louhoula is correct. But, in case you are expecting data to be a list of dictionaries each containing a location key (that's the idea I get by looking at your template), then you should change d.location in your template to :
{% if 'location' in d.keys(): %}
{{ d['location'] }}
{% else %}
<p class="lead">
You should not see this msg, otherwise, check the code again.
</p>
{% endif %}
Try this:
import urllib.request, json
url = 'https://api.openaq.org/v1/locations?country=GB'
response = urllib.request.urlopen(url);
decoded_data = json.loads(response.read().decode("utf-8"))

How to add meta keyword with django

I am using below code to add meta keywords -
in view.py
#template_render("mysite/category.html")
def category(request, slug):
slug = slug.lower()
product_type = local_settings.CATEGORY_NAME_TO_ID.get(slug, False)
if not product_type:
raise Http404
products = models.Product.objects.active().filter(product_type = product_type).all()
return { 'products' : products, 'slug' : slug, 'key':'wholesale ipad, ipad with retina display, ipad mini, ipad 3, ipad 2',}
And in template file -
{% extends "base.html"%}
{%load appletrade_tags%}
{% block key %}siteTrade - {{key}}{% endblock %}
{%block title%}site Trade - {{slug}}{%endblock%}
But it's not reflecting. I have checked in view source there is no keyword.
But Yes,title is reflecting.
Can you please help me to find out where I am wrong ?
EDIT :
base.html
{% extends "base.html"%}
{% block key %}{%if page.key%}{{page.key}}{%else%}{{block.super}}{%endif%}{% endblock %}
{% block desc %}{%if page.desc%}{{page.desc}}{%else%}{{block.super}}{%endif%}{% endblock %}
{%block title%}{%if page.title%}{{page.title}}{%else%}{{block.super}}{%endif%}{%endblock%}
{%block content%}
{%endblock%}
You need to be using either render or render_to_response to pass a context to the template. Is the slug object appearing on the page?
from django.shortcuts import render_to_response
def category(request, slug):
slug = slug.lower()
product_type = local_settings.CATEGORY_NAME_TO_ID.get(slug, False)
if not product_type:
raise Http404
products = models.Product.objects.active().filter(product_type = product_type)
context = {
'slug': slug,
'products': products,
'key': 'wholesale ipad, ipad with retina display, ipad mini, ipad 3, ipad 2',
}
return render_to_response('appletrade/category.html', context, context_instance=RequestContext(request))
Here is a way to automate keywords for your django site. I don't like manually entering things anyways.
Here's a function to read your template file and count primary words, and return the list of the words used in the page.
# meta_gen.py
# create meta keywords for webpage automagically
# MIT License
# Author: Daniel P. Clark 6ftdan#gmail.com
import collections, string, StringIO, re
from django.utils.html import strip_tags
# Counts words in template files and insert keywords into page
word_count_min = 2
word_length_min = 4
nogo_list = [] # Optional list of words you may not want as meta tags.
# meta_keywords ( html string ) =>
# returns non html keyword list, as a comma seperated string,
# for words fitting qualifications as chosen above.
def meta_keywords(str_file):
c = collections.Counter()
strIO_Obj = StringIO.StringIO(str_file)
for line in strIO_Obj.readlines():
c.update(re.findall(r"[\w']+", strip_tags(line).lower()))
wordlist = []
for word, count in c.most_common():
if len(word) > (word_length_min-1) and count > (word_count_min-1) and word not in nogo_list: wordlist.append(word)
return string.join(wordlist, ',')
Place meta_gen.py in your main project folder. Then add these pieces to each of your views.py files.
# views.py
from django.shortcuts import render_to_response
from django.template.loader import render_to_string
from project.meta_gen import meta_keywords
this_template = "apptemplate.html"
def tabs(request):
return render_to_response(this_template, { 'title' : "Page Name", 'keys' : meta_keywords(render_to_string(this_template)) })
And lastly in your main template base.html you place the meta tag for keywords.
# base.html
<head>
<title>Site Name - {{ title }}</title>
<meta name="keywords" content="{{ keys }}" />
</head>
And that's it. All pages that inherit the base template and have the views.py code will insert keywords meta tags with words that repeat on your pages.
I realize that this can be improved upon and optimized. Speed isn't a concern for me. So input is welcome.

Categories

Resources