Flask: URL zusammenbauen

Django, Flask, Bottle, WSGI, CGI…
Antworten
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

Hallo,

ich fummel mich gerade durch meine erste Flask-App. Aktuell hänge ich am Code um Bilder anzuzeigen. Über weiter und zurück Verknüpfungen soll man entsprechend zu den Bildern springen können.

Die URL im Browser sieht zum Beispiel wie folgt aus:

Code: Alles auswählen

http://127.0.0.1:5000/training?id=2&page=16&user=2ee95d0a83be9125faff61ae8a0811482060439f
Hier ist der Python Code, den ich bisher habe. Aktuell finde ich es etwas gruselig, wie ich die Variablen pic_suffix und next_url zusammenbauen. (Bei next_url muss noch der Zähler für page um 1 erhöht werden.)

Code: Alles auswählen

@app.route('/training', methods=['GET', 'POST'])
def show_training():
	if request.method == 'GET':
		db = get_db()
		cur = db.execute("select * from trainings where id=:t_id", {'t_id': request.args['id']})
		training = cur.fetchone()
		
		pic_suffix = 'trainings/' + training[2] + '/Folie' + request.args['page'] + '.PNG'
		next_url = '&id=' +  request.args['id'] +'&page=' + request.args['page'] +'&user=' + request.args['user']
		
		return render_template('show_training.html',
			training = training,
			pic_suffix = pic_suffix,
			next_url = next_url
		)
	elif request.method == 'POST':
		pass
So sieht mein Template aus:

Code: Alles auswählen

{% extends "layout.html" %}
{% block body %}
	<center>
		<p>
			<img src="{{ url_for('static', filename = pic_suffix) }}">
		</p>
		<table border=0>
			<tr>
				<td>
					<a href='javascript:history.go(-1)'>Zur&uuml;ck</a>
				</td>
				<td>
					<a href="{{ url_for('training', next = next_url) }}">Weiter</a>
				</td>
			</tr>
		</table>
	</center>
{% endblock %}
An dieser Zeile kommt es aktuell zu Fehlern.

Code: Alles auswählen

<a href="{{ url_for('training', next = next_url) }}">Weiter</a>
werkzeug.routing.BuildError
BuildError: ('training', {'next': u'&id=2&page=16&user=2ee95d0a83be9125faff61ae8a0811482060439f'}, None)
Irgend wie habe ich url_for glaube ich noch nicht verstanden, da ich den Fehler nicht gefixt bekomme. Außerdem such ich einen eleganten Weg, um den Pfad zu den Bildern und die URL für den Weiter- und Zurück-Button zusammen zu bauen. Habt ihr da einen Tipp für mich?
Benutzeravatar
Balmung
User
Beiträge: 44
Registriert: Sonntag 17. März 2013, 18:36

Existiert 'training'?
Müsste es nicht 'show_training' sein (der Name der Funktion)?

http://flask.pocoo.org/docs/api/#flask.url_for
Generates a URL to the given endpoint with the method provided.
Wäre es nicht einfacher in der Methode die URL ebenfalls mit url_for zusammen zu stecken?

Code: Alles auswählen

next_url = url_for('show_training', id=2, page=16, user='2ee95d0a83be9125faff61ae8a0811482060439f')
Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments.
»Honk Honk«
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

Balmung hat geschrieben:Existiert 'training'?
Müsste es nicht 'show_training' sein (der Name der Funktion)?

http://flask.pocoo.org/docs/api/#flask.url_for
Generates a URL to the given endpoint with the method provided.
Wäre es nicht einfacher in der Methode die URL ebenfalls mit url_for zusammen zu stecken?

Code: Alles auswählen

next_url = url_for('show_training', id=2, page=16, user='2ee95d0a83be9125faff61ae8a0811482060439f')
Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments.
Autsch, natürlich show_training. Jetzt ist schon mal ein Stein beiseite geräumt. auch das zusammenbauen der Variable next_url ist jetzt viel besser Lesbar.

Jetzt habe ich aber noch das Problem, dass der Zähler page um eins erhöht werden muss. In diesem Beispiel also von page=16 auf page=17. Ich dachte, ich könnte das so machen:

Code: Alles auswählen

q_page = int(request.args['page']) +1
Leider kann ich den Typ nicht nach Integer umwandeln, damit ich damit rechnen kann.
TypeError: 'int' object is not callable
BlackJack

@peddy: An was hast Du `int` denn hier gebunden? Ist keine so gute Idee Namen von eingebauten Funktionen an irgendwelche Werte zu binden. ;-)
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

BlackJack hat geschrieben:@peddy: An was hast Du `int` denn hier gebunden? Ist keine so gute Idee Namen von eingebauten Funktionen an irgendwelche Werte zu binden. ;-)
Ich dachte mit request.args['page'] hole ich mir den Dictionary-Eintrag von page, z.B. den Wert 15. int() sollte das Ergebnis dann in ein Integer umwandeln, damit ich damit rechnen kann. Leider erkenne ich noch nicht wo mein Denkfehler liegt. Wie würde das denn ein Profi machen?
BlackJack

@peddy: Der Name `int` scheint nicht mehr an die besagte Funktion gebunden zu sein. Entweder hast Du irgendwo ein ``int = …`` gemacht, oder vieleicht ein *-Import?
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

BlackJack hat geschrieben:@peddy: Der Name `int` scheint nicht mehr an die besagte Funktion gebunden zu sein. Entweder hast Du irgendwo ein ``int = …`` gemacht, oder vieleicht ein *-Import?
Int habe ich nicht an anderer Stelle benutzt. Imports sind nur folgende drin:

Code: Alles auswählen

import os
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, \
     render_template, flash
Muss ich jetzt schauen was im Modul Flask gemacht wird, oder wie fixe ich das Problem?
Benutzeravatar
Balmung
User
Beiträge: 44
Registriert: Sonntag 17. März 2013, 18:36

Wenn du kein *-import hast, dann ist das Problem wahrscheinlich, dass du an den Namen `int` einen anderen Wert gebunden hast, und `int` somit nicht mehr die built-in `int()` Funktion ist.

Nutze andere Namen für deine Variablen.
»Honk Honk«
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@peddy: zeig Deinen ganzen Code hier, sonst raten wir ewig.
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

Sirius3 hat geschrieben:@peddy: zeig Deinen ganzen Code hier, sonst raten wir ewig.
Nichts lieber als das.

Ich habe noch mal die Doku von Flask gewälzt und habe das gefunden.

Code: Alles auswählen

a = request.args.get('a', 0, type=int)
b = request.args.get('b', 0, type=int)
Note that we are using the get() method here which will never fail. If the key is missing a default value (here 0) is returned. Furthermore it can convert values to a specific type (like in our case int). This is especially handy for code that is triggered by a script (APIs, JavaScript etc.) because you don’t need special error reporting in that case.
Hier ist die App:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

#  _____       _     _         
# |     |___ _| |_ _| |___ ___ 
# | | | | . | . | | | | -_|_ -|
# |_|_|_|___|___|___|_|___|___|
#
import os
import sqlite3
from flask import Flask, request, g, render_template

#  _____     _   _   _             
# |   __|___| |_| |_|_|___ ___ ___ 
# |__   | -_|  _|  _| |   | . |_ -|
# |_____|___|_| |_| |_|_|_|_  |___|
#                         |___|
app = Flask(__name__)
app.config.from_object(__name__)

app.config.update(dict(
	DATABASE=os.path.join(app.root_path, 'wbt.db'),
	DEBUG=True,
	SECRET_KEY='development key',
	USERNAME='admin',
	PASSWORD='default'
))
app.config.from_envvar('FLASKR_SETTINGS', silent=True)

#  ____      _       _               
# |    \ ___| |_ ___| |_ ___ ___ ___ 
# |  |  | .'|  _| .'| . | .'|_ -| -_|
# |____/|__,|_| |__,|___|__,|___|___|
#
def connect_db():
	rv = sqlite3.connect(app.config['DATABASE'])
	rv.row_factory = sqlite3.Row
	return rv

def get_db():
	if not hasattr(g, 'sqlite_db'):
		g.sqlite_db = connect_db()
	return g.sqlite_db

@app.teardown_appcontext
def close_db(error):
	if hasattr(g, 'sqlite_db'):
		g.sqlite_db.close()

#  _____         _           
# | __  |___ _ _| |_ ___ ___ 
# |    -| . | | |  _| -_|_ -|
# |__|__|___|___|_| |___|___|
#

# Index
# -----
@app.route('/')
def index():
	return render_template('layout.html')

# Training
# --------
@app.route('/training', methods=['GET', 'POST'])
def show_training():
	if request.method == 'GET':
		db = get_db()
		cur = db.execute("select * from trainings where id=:t_id", {'t_id': request.args['id']})
		training = cur.fetchone()
		
		#pic_suffix = 'trainings/' + training[2] + '/Folie' + request.args['page'] + '.PNG'
		#next_url = '&id=' +  request.args['id'] +'&page=' + request.args['page'] +'&user=' + request.args['user']
		#q_user = request.args['user']
		#q_id = request.args['id']
		q_page = request.args.get('page', 0, type=int)
		return q_page
		#return render_template('show_training.html',
		#	training = training,
		#	pic_suffix = pic_suffix,
		#	next_url = next_url
		#)
	elif request.method == 'POST':
		pass

# Users
# -----
@app.route('/users')
def show_users():
	db = get_db()
	cur = db.execute("select * from users order by name, first_name asc")
	entries = cur.fetchall()
	return render_template('show_users.html', entries=entries)

# User Startpage
# --------------
@app.route('/user/<hash_value>')
def user_startpage(hash_value):
	db = get_db()
	cur = db.execute("""select t.name, ut.completed
		from users as u
		join users_trainings as ut on u.ID = ut.U_ID
		join trainings as t on ut.T_ID = t.ID
		where u.hash='{}'""".format(hash_value))
	trainings = cur.fetchall()
	cur = db.execute("select first_name, name from users where hash='{}'".format(hash_value))
	user_name = cur.fetchone()
	return render_template('show_user_trainings.html', trainings=trainings, user_name=user_name)
	
#  _____     _     
# |     |___|_|___ 
# | | | | .'| |   |
# |_|_|_|__,|_|_|_|
#
if __name__ == '__main__':
	app.debug = True
	app.run()
TypeError
TypeError: 'int' object is not callable

Traceback (most recent call last)
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1478, in full_dispatch_request
response = self.make_response(rv)
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/flask/app.py", line 1577, in make_response
rv = self.response_class.force_type(rv, request.environ)
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/werkzeug/wrappers.py", line 827, in force_type
response = BaseResponse(*_run_wsgi_app(response, environ))
File "/Users/patrick/Coding/wbt/venv/lib/python2.7/site-packages/werkzeug/test.py", line 855, in run_wsgi_app
app_iter = app(environ, start_response)
TypeError: 'int' object is not callable
The debugger caught an exception in your WSGI application. You can now look at the traceback which led to the error.
To switch between the interactive traceback and the plaintext one, you can click on the "Traceback" headline. From the text traceback you can also create a paste of it. For code execution mouse-over the frame you want to debug and click on the console icon on the right side.

You can execute arbitrary Python code in the stack frames and there are some extra helpers available for introspection:

dump() shows all variables in the frame
dump(obj) dumps all that's known about the object
Brought to you by DON'T PANIC, your friendly Werkzeug powered traceback interpreter.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich würde mal mutmaßen, dass der Return-Type einer mit ``@app.route`` deokrierten Funktion kein Integer sein darf... wandel das doch mal ein einen String!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

Um Seiteneffekte zu vermeiden, habe ich den Code mal eingedampft:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from flask import Flask, request

app = Flask(__name__)
app.config.from_object(__name__)

@app.route('/training', methods=['GET'])
def show_training():
	if request.method == 'GET':
		q_page = request.args.get('page')
		return int(q_page)
                
if __name__ == '__main__':
	app.debug = True
	app.run()
Diese URL verwende ich ---> http://127.0.0.1:5000/training?id=2&page=14

Leider immer noch der gleiche Fehler. Ein return str(q_page) wird ohne Fehler geschluckt, aber leider kann ich mit einem String nicht rechnen. Es muss doch möglich sein die Request-Variablen auszuwerten. *grrrrrr*
BlackJack

@peddy: Ähm, wo willst *Du* denn mit dem Wert Rechnen den Du *zurückgibst*. Der Rückgabewert wird hier ja an das Rahmenwerk übergeben und von da an den Aufrufer, also den Webbrowser. Schau mal in der Dokumentation was so eine Funktion zurückgeben darf. Zahlen sind nicht dabei, und machen auch überhaupt keinen Sinn.
peddy
User
Beiträge: 121
Registriert: Montag 30. Juni 2008, 13:51

BlackJack hat geschrieben:@peddy: Ähm, wo willst *Du* denn mit dem Wert Rechnen den Du *zurückgibst*. Der Rückgabewert wird hier ja an das Rahmenwerk übergeben und von da an den Aufrufer, also den Webbrowser. Schau mal in der Dokumentation was so eine Funktion zurückgeben darf. Zahlen sind nicht dabei, und machen auch überhaupt keinen Sinn.
return int(q_page) ist aktuell natürlich sinn frei und dienst nur zum testen. Beim Endprodukt soll ein Template aufgerufen werden, was ein Bild mit einem Weiter-Knopf anzeigt. Für den Weiter-Kopf möchte ich die neue URL an das Template übergeben, was eine Addition eines Integers voraussetzt.

Lautet also die URL im Browser ---> http://127.0.0.1:5000/training?id=2&page=14
so soll der Link des Weiter-Buttons http://127.0.0.1:5000/training?id=2&page=15 enthalten.
BlackJack

@peddy: Bei einem Template kannst Du ja eine Zahl übergeben, weil das Template gerendert dann ja wieder eine Zeichenkette ist.
Antworten