How to Output More than Once In frontend In DJango - django

I am building a web app that makes use of the HackerNews API and I keep running into the same error.
I am trying to Output the 10 requested result from API but everytime i use the return response it only outputs the first result.
I want it to output 10 articles from the API instead of just the One.
This is my code:
from operator import itemgetter
import requests
from django.shortcuts import render
# Make an API call and store the response.
def home(request):
url = 'https://hacker-news.firebaseio.com/v0/topstories.json'
r = requests.get(url)
print(f"Status code: {r.status_code}")
# Process information about each submission.
submission_ids = r.json()
submission_dicts = []
for submission_id in submission_ids[:10]:
# Make a separate API call for each submission.
url = f"https://hacker-news.firebaseio.com/v0/item/{submission_id}.json"
r = requests.get(url)
print(f"id: {submission_id}\tstatus: {r.status_code}")
response_dict = r.json()
# Build a dictionary for each article.
submission_dict = {
'title': response_dict['title'],
'hn_link': f"http://news.ycombinator.com/item?id={submission_id}",
# 'comments': response_dict['descendants'],
}
submission_dicts.append(submission_dict)
# submission_dicts = sorted(submission_dicts, key=itemgetter('comments'),
# reverse=True)
for submission_dict in submission_dicts:
print(f"\nTitle: {submission_dict['title']}")
print(f"Discussion link: {submission_dict['hn_link']}")
# print(f"Comments: {submission_dict['comments']}")
count = 0
if count < 10:
return render(request, "news_api/home.html", submission_dict)

You're only getting one result because your return statement is calling "submission_dict". You probably wanted to call "submission_dict(s)" or something similar but it's hard to tell based on what has been commented out in your code. I think that's your primary problem. Without seeing your template, I don't know what your context object is supposed to look like.
But try this:
# views.py
def hacker_news_api_selector():
url = 'https://hacker-news.firebaseio.com/v0/topstories.json'
r = requests.get(url)
submission_list = r.json()
context = {}
context['objects'] = []
# Process information about each submission.
for submission_id in submission_list[:10]:
# Make a separate API call for each submission.
url = f"https://hacker-news.firebaseio.com/v0/item/{submission_id}.json"
r = requests.get(url)
response_dict = r.json()
context['objects'].append(response_dict)
return context
def home(request):
context = hacker_news_api_selector()
return render(request, "news_api/home.html", context)
And in the template...
# news_api/home.html
<html>
...
{% for x in objects %}
<li>{{ x.title }}</li>
{% endfor %}
</html>

Related

How to modify url in a django get request

I have a web page which displays some data (with get parameters to filter in the view) and I use a pagination.
To go through the data i use a link to change the page in the paginator :
<li class="paginator-data">Next</li>
Here is the view :
#login_required
def search_and_export(request):
# initialisation
page_obj = Tree.objects.all()
# FORM
if request.method == 'GET':
## Here I get GET parameters or a default value like
data = str(request.GET.get('data', 1))
page = str(request.GET.get('page', 1))
## creating a queryDict
querydict = QueryDict('data=' data + '&page=' + page)
big_request_form = BigRequestForm(querydict)
if big_request_form.is_valid():
# Getting data from front endda
data = big_request_form.cleaned_data['data']
# Extracting QuerySet
page_obj = filtering_function(data)
paginator = Paginator(page_obj,25)
page_obj = paginator.get_page(page)
page_obj.adjusted_elided_pages = paginator.get_elided_page_range(number = page, on_each_side=1, on_ends=1)
else:
big_request_form = BigRequestForm()
# return values
context = {
'big_request_form' : big_request_form,
'page_obj' : page_obj,
}
return render(request, 'filtered_stats.html', context)
Here is the problem : url looks like that after browsing a few pages :
http://127.0.0.1:8000/?data=x&page=2&page=3&page=4&page=5&page=6
The view is working well (It displays me the last page) but I got this ugly link.
I tried something like that :
request.GET.get = querydict
(with querydict a QueryDict object with only one page data)
But It didn't work. I also thought of parsing data in the template but maybe there is a better way.
What you want to do in HTML template is to get current URL. But actually you don't have to do it - relative path can be used instead. So just remove it:
<li class="paginator-data">Next</li>
The browser knows current URL and will use href attribute relative to it.
UPD
alternative - to keep other parameters in the URL is to construct "next URL" inside the view and pass it in context as next_url.
I.e. something like so:
In views:
next_url = request.get_full_path()
next_url.replace(f'page={current}', f'page={page_obj.next_page_number}')
context['next_url'] = next_url
In template:
<li class="paginator-data">Next</li>

Django - Get response to return image without saving file to models

I am using the tmdb api to return movie info as well as images.
Steps below of Get logic
Api request is made which provides movie info as well as "backdrop_path"
I then use this path to make another request for the jpg related to that movie.
Blocker
I'm unable to then output that jpg. It currently returns a url path as below.
Views.py
from django.shortcuts import render
from django.views.generic import TemplateView
import requests
import urllib
# Create your views here.
def index(request):
# Query API with user input
if 'movie' in request.GET:
api_key = 'api'
id = request.GET['movie']
url = 'https://api.themoviedb.org/3/search/movie?api_key={}&language=en-US&query={}&include_adult=false'
response = requests.get(url.format(api_key,id))
# successful request
if response.status_code == 200:
# Parse json output for key value pairs
tmdb = response.json()
# save image jpg
backdrop_path = tmdb['results'][0]['backdrop_path']
url = 'https://image.tmdb.org/t/p/original/{}'
gg = urllib.request.urlretrieve(url.format(backdrop_path), 'test.jpg')
context = {
'title': tmdb['results'][0]['original_title'],
'overview': tmdb['results'][0]['overview'],
'release_date': tmdb['results'][0]['release_date'],
'vote_average': tmdb['results'][0]['vote_average'],
'vote_count': tmdb['results'][0]['vote_count'],
'backdrop_path' : tmdb['results'][0]['backdrop_path'],
'jpg' : gg
}
return render(request, 'home.html', {'context': context})
else: # returns homepage if invalid request
return render(request, 'home.html')
else: # Homepage without GET request
return render(request, 'home.html')
urlretrieve doesn't return the image ready to be used, instead it returns a tuple with the local name of the file and the headers (as an HTTPMessage object as you can see in your example), which is what you're seeing.
However, I don't think returning a file in your response is ideal, nor it would work in your scenario. Since you seem to be using templates, what I would do is return the image url and use it in an image HTML tag, like this <img src="{{ jpg }}"/>

Flask / Jinja memoization

I have been trying to use and Flask-Cache's memoize feature to only return cached results of statusTS(), unless a certain condition is met in another request where the cache is then deleted.
It is not being deleted though, and the Jinja template still displays Online when it should infact display Offline because the server has been stopped. So it is returning a cached result when it should not.
#cache.memoize(60)
def statusTS(sid):
try:
server = Server.get_info(sid)
m = Masters.get_info(server.mid)
if not m.maintenance:
tsconn = ts3conn(server.mid)
tsconn.use(str(server.msid))
command = tsconn.send_command('serverinfo')
tsconn.disconnect()
if not command.data[0]['virtualserver_status'] == 'template':
return 'Online'
return 'Unknown'
except:
return 'Unknown'
app.jinja_env.globals.update(statusTS=statusTS)
Jinja template:
{% if statusTS(server.sid) == 'Online' %}
<span class="label label-success">
Online
</span>{% endif %}
This renders the view:
#app.route('/manage/')
def manage():
if g.user:
rl = requests_list(g.user.id)
admin = User.is_admin(g.user.id)
g.servers = get_servers_by_uid(g.user.id)
if 's' in request.args:
s = request.args.get('s')
s = literal_eval(s)
else:
s = None
return render_template('manage.html',
user=g.user,
servers=g.servers,
admin=admin,
globallimit=Config.get_opts('globallimit'),
news=News.get_latest(),
form=Form(),
masters=Masters.get_all(),
treply=rl,
s=s)
else:
return redirect(url_for('login'))
this is what is supposed to delete the entry.
#app.route('/stop/<id>/')
#limiter.limit("3/minute")
def stop(id):
if g.user:
if Server.is_owner(g.user.id, id):
m = Masters.get_info(Server.get_info(id).mid)
if not m.maintenance:
cache.delete_memoized(statusTS, id)
flash(stopTS(id))
return redirect(url_for('manage'))
else:
flash(
'You cannot stop this server while the master is locked for maintenance - please check for further info.')
return redirect(url_for('manage'))
else:
flash(
'You do not have permission to modify this server - please contact support.')
return redirect(url_for('manage'))
else:
return redirect(url_for('login'))
Your code looks correct. It could be a bug in the whichever backend cache you are using.
Toy example that works:
from flask import Flask
# noinspection PyUnresolvedReferences
from flask.ext.cache import Cache
app = Flask(__name__)
# Check Configuring Flask-Cache section for more details
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
#cache.memoize(timeout=10)
def statusTS(sid):
print('Cache Miss: {}.'.format(sid))
return sid
#app.route('/<int:status_id>')
def status(status_id):
return 'Status: ' + str(statusTS(status_id))
#app.route('/delete/<int:status_id>')
def delete(status_id):
print('Deleting Cache')
cache.delete_memoized(statusTS, status_id)
return 'Cache Deleted'
if __name__ == '__main__':
app.debug = True
app.run()
What I think is happening is that the delete is failing. I would step into the
cache.delete_memoized(statusTS, status_id) and see if it actually finds and deletes the cached function result. Check werkzeug.contrib.cache.SimpleCache
if you are using the cache type simple.

DJANGO simplejson

Good afternoon..i have a model with a class like this:
class Reportage:
def get_num(self):
end_date = self.date.end_date
start_date = self.date.start_date
duration = time.mktime(end_date.timetuple()) - time.mktime(start_date.timetuple())
delta_t = duration / 60
num = []
for t in range(0,duration,delta_t):
start = t + start_date
end = datetime.timedelta(0,t+delta_t) + start_date
n_num = self.get_num_in_interval(start,end)
num.append([t, n_num])
return num
I want to serialize with simplejson the array num [] in the views.py for passing in a second moment this array to a jquery script to plot it in a graph..
what's the code to serialize that array..?
I hope I was clear .. thanks in advance to all those who respond ..
Following #ninefingers' response. I think that your question is aimed on how to make that dumped json string available to a jQuery plugin.
# views.py
def my_view(request):
# do stuff
num = reportage_instance.get_num()
num_json = simplejson.dumps(num)
return render(request, 'template.html', {
'num_json': num_json,
})
In your template, you make available that json obj as a Javascript variable
# template.html
<html>
<body>
<script>
var NUM_JSON = {{num_json|safe}};
myScript.doSomething(NUM_JSON);
</script>
</body>
</html>
Now you can call regular JS with the NUM_JSON variable.
If you're looking to do this in a model, something like this would work:
# if this were a models.py file:
import simplejson
# other django imports:
class SomeModel(models.Model):
property = models.SomeField()...
def some_function(self):
num = []
# full num
simplejson.dumps(num)
That'll dump num to a string-representation of the json, which you can then return, or write to a file, and so on.
From a view you have a choice - but if your view is an ajax view returning some json for processing you might do this:
# views.py
# ...
def my_ajax_call(request, model_pk):
try:
mymodel = SomeModel.get(pk=model_pk)
except SomeModel.NotFoundException as e:
return HttpResonse("Unknown model", status=404)
else:
return HttpResponse(mymodel.some_function()) # returns simplejson.dumps(num)
This can then be used from a template in Javascript - the other answer shows you how you might approach that :)

How to capture multiple arguments using a single RegEx in my Django urls.py?

I've got an application that allows you to filter data via 3 fields. I'm trying to write a RegEx in my urls.py that can capture multiple combinations without having to write-out each possible combination it it's own URL.
Here's my urls.py:
#urls.py
urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
(r'^admin/', include(admin.site.urls)),
(r'(?P<key>\w*?)=(?P<value>\w*?)&|$', views.scriptFilter),
I tested the above RegEx at pythonregex.com and it appears to capture as many key/value pairs as I can throw at it. However, when I test try it in my app, Django only returns a queryset based on the first pair, and ignores the other pairs.
For example, if I enter this:
http://MYSITE/feature=1&session=1&
Django returns the data based on feature=1 only and ignores session=1.
Here's my views.py:
#views.py
def scriptFilter(request, key, value):
if key == 'session':
sessionID = value
qs = models.Script.objects.filter(session=sessionID)
elif key == 'product':
productID = value
qs = models.Script.objects.filter(product=productID)
elif key == 'feature':
featureID = value
scriptFeature = models.Feature.objects.get(pk=featureID)
qs = models.Script.objects.filter(feature=scriptFeature)
else:
qs = models.Script.objects.all()
caseTotal = qs.aggregate(Sum('caseCount'))
scriptTotal = qs.aggregate(Count('subScriptName'))
featureTotal = qs.aggregate(Count('feature'))
return render_to_response('scripts.html', locals())
I'm new to Python & Django so please be gentle :) Any help would be really appreciated.
These may be valid URLs (not entirely certain) but they're certainly not recommended.
If you want to allow parameters sent into your application using key-value pairs (as you are doing here), I'd suggest just using query parameters. Here's a way to implement that in your view:
def scriptFilter(request, session=None, product=None, feature=None):
session = request.REQUEST.get('session',session)
product = request.REQUEST.get('product',session)
feature = request.REQUEST.get('feature',session)
if session
qs = models.Script.objects.filter(session=session)
elif product:
qs = models.Script.objects.filter(product=product)
elif feature:
qs = models.Script.objects.filter(feature=feature)
else:
qs = models.Script.objects.all()
caseTotal = qs.aggregate(Sum('caseCount'))
scriptTotal = qs.aggregate(Count('subScriptName'))
featureTotal = qs.aggregate(Count('feature'))
return render_to_response('scripts.html', locals())
You'd then call the URLs as
http://example.com/myapp?session=X
http://example.com/myapp?product=X
http://example.com/myapp?session=X&feature=Y
etc.
Note that I assume when you say that pythonregex captured all the groups, that's probably because you were looking at the .findall() response. I'm not sure of the exact mechanics of Django's url dispatcher, but even if you just think about it logically, how could it assign more than one value to key and value? Your scriptFilter function does not even handle multiple values being sent in. You really want it to read:
def scriptFilter(request, session=None, product=None, feature=None):
session = request.REQUEST.get('session',session)
product = request.REQUEST.get('product',session)
feature = request.REQUEST.get('feature',session)
qs = models.Script.objects.all()
if session
qs = qs.filter(session=session)
if product:
qs = qs.filter(product=product)
if feature:
qs = qs.filter(feature=feature)
caseTotal = qs.aggregate(Sum('caseCount'))
scriptTotal = qs.aggregate(Count('subScriptName'))
featureTotal = qs.aggregate(Count('feature'))
return render_to_response('scripts.html', locals())
Finally, I'm guessing you should rewrite these lines as well:
caseTotal = qs.aggregate(Sum('caseCount'))
scriptTotal = qs.aggregate(Count('subScriptName'))
featureTotal = qs.aggregate(Count('feature'))
The aggregate function creates a QuerySet with the aggregated values. You may as well group these into one queryset:
script_totals = qs.aggregate(Sum('casecount'), Count('subscriptname'), Count('feature'))
In the template, you'd access these values as:
{{ script_totals.casecount__sum }}
{{ script_totals.subscriptname__count }}
{{ script_totals.feature__count }}
Which is a bit cleaner than
{{ caseTotal.casecount__sum }}
{{ scriptTotal.subscriptname__count }}
{{ featureTotal.feature__count }}