Accessing object properties using Flask - python

I'm new to Flask and templating; I am trying to pass an object and display its data on my HTML pages.
I have a detachment class like
class Detachment:
def __init__(self, name, ...):
self.name = name
self.... = ... # lots of data, mostly lists that I'd like to index
I tried doing something like this:
import Detachment as dt
from flask import Flask, request, redirect, url_for
#app.route('/')
def go():
# stuff
testDetachment = dt.Detachment('name')
return redirect(url_for('cooliofunction', detachment=testDetachment)
#app.route('/templates')
def cooliofunction():
return render_template('data.html', obj=request.args.get('detachment'))
with my data.html attempting:
{{obj}} <br />
{{obj.name}} <br />
While {{obj}} will display the object address, I can't access the variables set in my detachment - it shows up blank. Am I approaching the whole problem wrong, or is there a simple way to access objects' properties?
EDIT: I was using redirect because the creation of testDetachment actually depends on a file upload, but I omitted it to focus on the object properties.
EDIT2: So I had been using the redirect because I was following a file upload tutorial that displayed what had been uploaded, but I don't need to do that. Rendering the template directly, I could access the object's attributes directly. Thanks, everyone!

If url_for gets arguments that are not part of the rule it's building, it will append them as the query string by converting the values to strings. The string representation of an object is its address, not the object itself. When you retrieve it in the other view, you're retrieving the string representation of the object, not the actual object.
As written, you should just omit the redirect and render the template directly in the go view.
If your actual code has a specific reason for that redirect, then you should send a unique identifier for the object, and re-load it in the other view. For example, if you're using a database, commit the object to the database, pass the id in the url, and load it by id in the other view.

By the time your route gets obj it is only a string because of how you're passing it. redirect(url_for('cooliofunction', detachment=testDetachment) is likely actually passing str(testDetachment) implicitly as a query string.
Even though name is not a valid attribute for a string, Jinja simply ignores this:
from flask import Flask, render_template_string
app = Flask(__name__)
#app.route("/")
def test():
obj = "Foo"
return render_template_string("{{ obj }} {{ obj.name }}", obj=obj)
#Foo
If you want to pass arbitrary Python objects, use session, or some other form of storage, like a DB, that will persist from one request to the next.

That's not what url_for is for. Or redirects, for that matter.
Redirects are either for:
When a page doesn't exist or they can't access it
You've finished doing something (e.g. logging them in) and now they can carry on their way and you want them to go to a new page.
So typically you'll do something like this:
#app.route('/login')
def login():
if request.method == 'POST' and request.form['username'] == 'the_user' \
and request.form['password'] == 'super secret!':
... # log the user in here
return redirect(url_for('main'))
Or if you really did need to pass on some information you could do it with url_for like this:
return redirect(url_for('page', extra='values', go='here', and_='will be added', to_the='query string', like='http://foo.bar?extra=values&go=here'))
But, don't do that.

Related

Flask: How to render a template AND save request.headers info?

I am trying to save objects made available by the request.headers in my Flask app.
I want to render my index.html upon page load, but I also want to grab the visiting user's email so I can use it for other functions / processes.
# routes
#app.route('/')
def index():
return render_template('index.html')
def find_aad():
aad_email = request.headers.get('X-MS-CLIENT-PRINCIPAL-NAME') # aad email
return aad_email
If I try to run find_aad() on its own,
user_email = find_aad() # cant run
I will get the typical error: Working outside of request context.
How can I on an initial load of the website secure these headers and save them to an object without having these errors?
You could get at it this way, perhaps:
On that first call to index, you can create a UUID for the "session" and use that as an identifier for the user, then you pass that code back inside the rendered UI elements for stashing on the client-side. Then, on every subsequent call to the backend, you send that UUID with the rest of the request.
On those subsequent requests, you can access the email value via that UUID as the key to the data structure you're using to store client information on the backend.
This concept is the idea of a "session" with a "session id" that is common in client/server communications. Using sockets or possibly even built in or supplemental libraries for Flask would probably be a good idea instead of "rolling your own". Sorry if I'm being unhelpful or stupid - it's late where I'm at.
EDIT:
By request here's some simple pseudocode for this:
from flask import Flask
import uuid
...
uuid_to_email = {}
...
#app.route('/')
def index():
user_id = str(uuid.uuid4())
uuid_to_email[user_id] = request.headers.get('X-MS-CLIENT-PRINCIPAL-NAME')
return render_template('index.html', uuid=user_id) # where it is implied that you would then use the uuid in the client-side code to story it and pass it back to the endpoints you want to do that with

In Flask, how can I generate a dynamic URL upon button click?

For example, now if I have two buttons in a form element, when you click on either one of them, you'll be directed to the corresponding profile.
<form action="{{ url_for('getProfile') }}" method="post">
<button type="submit" name="submit" value="profile1"> View Profile</button>
<button type="submit" name="submit" value="profile2"> View Profile</button>
</form>
In my apprunner.py, I have
#app.route('/profile', methods=['POST'])
def getProfile():
if request.form['submit'] = 'profile1':
return render_template("profile1.html")
else if request.form['submit'] = 'profile2':
return render_template("profile2.html")
However, my problem is when I click on either button, the url will always be something like "127.0.0.1:5000/profile". But, I want it to look like "http://127.0.0.1:5000/profile1" or "http://127.0.0.1:5000/profile2".
I have looked for solution on how to generate dynamic URL online, but none of them works for button click.
Thanks in advance!
#app.route('/profile<int:user>')
def profile(user):
print(user)
You can test it on a REPL:
import flask
app = flask.Flask(__name__)
#app.route('/profile<int:user>')
def profile(user):
print(user)
ctx = app.test_request_context()
ctx.push()
flask.url_for('.profile', user=1)
'/profile1'
EDIT:
how you pass the user parameter to your new route depends on what you need. If you need hardcoded routes for profile1 and profile2 you can pass user=1 and user=2 respectively. If you want to generate those links programatically, depends on how these profiles are stored.
Otherwise you could redirect instead of render_template, to the url_for with the parsed element in the request object. This means having two routes
#app.route('/profile<int:user>')
def profile_pretty(user):
print(user)
#app.route('/profile', methods=['POST'])
def getProfile():
if request.form['submit'] = 'profile1':
return redirect(url_for('.profile_pretty', user=1))
else if request.form['submit'] = 'profile2':
return redirect(url_for('.profile_pretty', user=2))
caveat: This would make your routes look like you want, but this is inefficient as it generates a new request each time, just to make your urls the way you want. At this point it's safe to ask why do you want to have dynamically generated routes for static content.
As explained in http://exploreflask.com/en/latest/views.html#url-converters
When you define a route in Flask, you can specify parts of it that will be converted into Python variables and passed to the view function.
#app.route('/user/<username>')
def profile(username):
pass
Whatever is in the part of the URL labeled will get passed to the view as the username argument. You can also specify a converter to filter the variable before it’s passed to the view.
#app.route('/user/id/<int:user_id>')
def profile(user_id):
pass
In this code block, the URL http://myapp.com/user/id/Q29kZUxlc3NvbiEh will return a 404 status code – not found. This is because the part of the URL that is supposed to be an integer is actually a string.
We could have a second view that looks for a string as well. That would be called for /user/id/Q29kZUxlc3NvbiEh/ while the first would be called for /user/id/124.

Pass variable from jinja2 template to python

Sorry if this is a noob question I am still learning. I have passed a variable from python code to a jinja2 HTML template to set up a URL, like this:
Delete
When this link is pressed it should run a query that deletes the entity with that ID. But when the link is pressed it goes to /delete/1827424298 for example, which results in a 404 error as the request handler doesn't exist.
I need to pass that ID back into my python code so it can run a method to delete the entity with that same ID. How do I go about doing this? Using webapp2 if that is important.
class DeleteRequestHandler(webapp2.RequestHandler):
def get():
template = template_env.get_template('myrequests.html')
context = {
'results': results.key.id()
}
self.response.out.write(template.render(context))
EDIT: I've added my delete handler - it is incomplete as I have yet to add the query to delete the entity. My thinking behind it so far is I can grab the results.key.id() from the jinja2 template and put it into results but I am not sure if this would work.
So I think what you're confused about is how to set up a route handler with a dynamic part to the URL. It's a shame that this is completely skipped over in the webapp2 tutorial, as it's a fundamental part of writing any web application. However, it is covered well in the guide to routing, which you should read.
At its simplest, it's just a matter of putting a regex in the route:
app = webapp2.WSGIApplication([
...
(r'/delete/(\d+)', MyDeleteHandler),
])
which will now route any URL of the form /delete/<number>/ to your deletion handler.
The ID that you pass in the URL will be the first positional argument to the handler method:
class MyDeleteHandler:
def get(self, item_id):
key = ndb.Key(MyModel, item_id) # or whatever

What is an 'endpoint' in Flask?

The Flask documentation shows:
add_url_rule(*args, **kwargs)
Connects a URL rule. Works exactly like the route() decorator.
If a view_func is provided it will be registered with the endpoint.
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
What exactly is meant by an "endpoint"?
How Flask Routing Works
The entire idea of Flask (and the underlying Werkzeug library) is to map URL paths to some logic that you will run (typically, the "view function"). Your basic view is defined like this:
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Note that the function you referred to (add_url_rule) achieves the same goal, just without using the decorator notation. Therefore, the following is the same:
# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
return 'Hello, {0}!'.format(name)
app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)
Let's say your website is located at 'www.example.org' and uses the above view. The user enters the following URL into their browser:
http://www.example.org/greeting/Mark
The job of Flask is to take this URL, figure out what the user wants to do, and pass it on to one of your many python functions for handling. It takes the path:
/greeting/Mark
...and matches it to the list of routes. In our case, we defined this path to go to the give_greeting function.
However, while this is the typical way that you might go about creating a view, it actually abstracts some extra info from you. Behind the scenes, Flask did not make the leap directly from URL to the view function that should handle this request. It does not simply say...
URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")
Actually, it there is another step, where it maps the URL to an endpoint:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"
Basically, the "endpoint" is an identifier that is used in determining what logical unit of your code should handle the request. Normally, an endpoint is just the name of a view function. However, you can actually change the endpoint, as is done in the following example.
#app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Now, when Flask routes the request, the logic looks like this:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"
How You Use the Endpoint
The endpoint is commonly used for the "reverse lookup". For example, in one view of your Flask application, you want to reference another view (perhaps when you are linking from one area of the site to another). Rather than hard-code the URL, you can use url_for(). Assume the following
#app.route('/')
def index():
print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
This is advantageous, as now we can change the URLs of our application without needing to change the line where we reference that resource.
Why not just always use the name of the view function?
One question that might come up is the following: "Why do we need this extra layer?" Why map a path to an endpoint, then an endpoint to a view function? Why not just skip that middle step?
The reason is because it is more powerful this way. For example, Flask Blueprints allow you to split your application into various parts. I might have all of my admin-side resources in a blueprint called "admin", and all of my user-level resources in an endpoint called "user".
Blueprints allow you to separate these into namespaces. For example...
main.py:
from flask import Flask, Blueprint
from admin import admin
from user import user
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')
admin.py:
admin = Blueprint('admin', __name__)
#admin.route('/greeting')
def greeting():
return 'Hello, administrative user!'
user.py:
user = Blueprint('user', __name__)
#user.route('/greeting')
def greeting():
return 'Hello, lowly normal user!'
Note that in both blueprints, the '/greeting' route is a function called "greeting". If I wanted to refer to the admin "greeting" function, I couldn't just say "greeting" because there is also a user "greeting" function. Endpoints allow for a sort of namespacing by having you specify the name of the blueprint as part of the endpoint. So, I could do the following...
print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'
Endpoint is the name used to reverse-lookup the url rules with url_for and it defaults to the name of the view function.
Small example:
from flask import Flask, url_for
app = Flask(__name__)
# We can use url_for('foo_view') for reverse-lookups in templates or view functions
#app.route('/foo')
def foo_view():
pass
# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
#app.route('/bar', endpoint='bufar')
def bar_view():
pass
with app.test_request_context('/'):
print url_for('foo_view')
print url_for('bufar')
# url_for('bar_view') will raise werkzeug.routing.BuildError
print url_for('bar_view')
If you have same class name and want to map with multiple routes, then specify the endpoint, so that framework will differentiate between two:
class ClassName(Resource):
def get(self):
if request.endpoint!='hello':
return {"data": "Hello"}
elif:
return {"data" : "World"}
api.add_resource(ClassName, '/rout1', endpoint = "world")
api.add_resource(ClassName, '/rout2', endpoint="hello")
#app.route('/') #Endpoint
def a_function(): #View function
return 'view'
Inside Flask, every endpoint with its request methods mapped to a view function. When you use app.route decorator you are actually adding a URL rule.

Custom traversal and page templates

Using Marius Gedminas's excellent blog post, I have created a custom traverser for a folder in my site.
This allows me to show: http://foo.com/folder/random_id
Instead of: http://foo.com/folder/object.html?id=random_id
The configuration side works great, I can catch the random_ids and search through my messages for the correct one, ready to display.
My problem is that I'm unsure how to then display the data via my usual page templates - at the TODO point in his original code ;)
if name == 'mycalendar':
mycalendar = ... # TODO: do something to get the appropriate object
return mycalendar
Usually I'd use something similar to:
class Test(BrowserPage):
template = ViewPageTemplateFile('atest.pt')
def __call__(self):
return self.template()
But I can't work out how to do this correctly in the context of the custom traversal.
UPDATE: To be clear I want to avoid adding anything else to the url (No: http://foo.com/folder/random_id/read).
I don't need the view to be available via any other address (No: http://foo.com/folder/read)
The ZCML for the view I'd like to use is:
<browser:page
for="foo.interfaces.IFooFolderContainer"
name="read"
template="read.pt"
permission="zope.ManageContent"
/>
I'm guessing (on further advice), something along the lines of:
return getMultiAdapter((mycalendar, self.request), IPageTemplate, name=u'read')
Or even a default view for the object type (a dict in this case) that's being returned:
<browser:page
for="dict"
name="read"
template="read.pt"
permission="zope.ManageContent"
/>
It would be easier to answer your question if you showed what your custom traverser is doing.
Essentially, you want something like this:
def publishTraverse(self, request, name):
if name in self.context:
return MyMessageView(self.context[name], request)
# fall back to views such as index.html
view = queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view
# give up and return a 404 Not Found error page
raise NotFound(self.context, name, request)
where MyMessageView can be something as simple as
class MyMessageView(BrowserPage):
__call__ = ViewPageTemplateFile('read.pt')
Disclaimer: I'm not sure if the view you instantiate directly will be protected by a security wrapper; make sure your functional tests ensure anonymous users can't view messages if that's what you want.
If you end up at a proper object with your custom traverser, you can just tack on the template name and user "context" in that template. So http://foo.com/folder/random_id/my_template and in the template do the normal <h1 tal:content="context/title" /> stuff.
IIUC, what you want is to render the 'read' view when somebody requests /folder/random_id. If that's the case, all you need to do is make your traversal return an object (IFolderContent, maybe) representing a random_id and specify the 'view' page as the defaultView for IFolderContent.
The defaultView is needed because there's no view specified for the random_id object in your URL.

Categories