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

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.

Related

web.py rendering wrong page, path not correct

I'm working with web.py on a project with a user authentication component. This project has a /demo directory and a /register directory.
The problem is that for some reason, clicking the button meant to travel to the /register directory goes to /demo instead. Interestingly, if I type /register at the end of the URL, /register appears properly. The URL is different in this case, lacking the ?register=Register part that comes from clicking the button.
The specific error that appears when I click on the register button is this:
type 'exceptions.TypeError'> at /demo
__template__() takes exactly 1 argument (0 given)
The code that it points to as a problem area is this:
class demo:
def GET(self):
render = create_render(session.get('privilege', 0))
return '%s' % render.demo() # Specifically this line generates the error.
I don't think there is any weird fall through, as in the code there are two classes implemented between the register class and the demo class. They're actually implemented in the order they're displayed in the urls list, with reset and profile in between the two.
I don't think there is anything wrong with my urls list:
urls = (
'/', 'index',
'/register', 'register',
'/reset', 'reset',
'/profile', 'profile',
'/demo', 'demo',
'/entropy', 'entropy',
)
Here also is the HTML code for the buttons:
<form action="demo" method="GET">
<input type="submit" name="demo" value="Demo"/>
<form/>
<form action="register" method="GET">
<input type="submit" name="register" value="Register"/>
</form>
Other potentially relevant bits of code:
register.GET():
class register:
def GET(self):
reg_form = forms.registration_form()
render = create_render(session.get('privilege'))
return render.register()
create_privilege(privilege): (Explained here, at #4)
def create_render(privilege):
if logged():
render = web.template.render('templates/logged', base='base')
else:
render = web.template.render('templates/', base="base")
return render
So, I suppose, the final question is this: Why is the button rendering the wrong page? I can post more code if need be!
Thank you!
I fixed it! There was an issue in one of the HTML buttons, a form tag was not closed properly. What a silly error.

Accessing object properties using Flask

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.

Returning user to referrer in flask in smartest pythonic way

I want to delete something, so I use a post, but then I want to refer the user back to the view - the url is actually different depending user, so I'd like to be able to send back to referrer, but I don't see a smart way of doing it.
Here is my code:
#app.route('/delete', methods=['POST'])
def delete():
if request.method == 'POST':
_id = request.form.get('_id')
mongo.db.xxx.remove({'_id': _id})
return request.referrer
else:
return request.referrer
What is the canonical elegant way of doing this. Do I have to use session, or is there another way I can use flask to perform this.
When rendering the form for the delete view, you can add a hidden form element named next:
<form ...>
<input type="hidden" name="next" value="{{ request.path }}">
...
</form>
Then in your route:
...
return redirect(request.form.get('next', '/'))
Note: your redirect handling should take care to prevent the next parameter from being an absolute URL to an arbitrary site (see https://www.owasp.org/index.php/Open_redirect).

Sending HTML form input to python script

I'm trying to send a HTML form input from a page to a python script. This is just for testing purposes so ignore the poor code. Basically I don't know how to define the action= part to reference the search() function in the python script. I just need to get the basic functionality working. I'm using web2py as the framework which is probably relevant:
Controller: default.py
def index():
return dict()
def search():
import urllib
q=request.vars.q
result1 = urllib.urlopen('http://www.google.com/search?q=%s' % q)
return dict(result1=result1)
default/search.html:
{{extend 'layout.html'}}
<form method=get action=???>
<input name=q>
<input type=submit value=search>
</form>
It looks like the form itself is served via the search() function, so you can just set action="", and it will submit back to the search() function. Also, you should put quotes around your HTML attribute values, and you should add some logic to check whether there is anything in request.vars.q, because when the page first loads with an empty form, there is no query to process.
What you are doing there is correct for GET requests. For POST requests you need to pass the fields as the data parameter.
urllib.urlopen('http://www.google.com/search', data=urllib.urlencode({'q':'FOO'}))
does the search as a POST request for example.
See here for more info.

How to implement a "back" link on Django Templates?

I'm tooling around with Django and I'm wondering if there is a simple way to create a "back" link to the previous page using the template system.
I figure that in the worst case I can get this information from the request object in the view function, and pass it along to the template rendering method, but I'm hoping I can avoid all this boilerplate code somehow.
I've checked the Django template docs and I haven't seen anything that mentions this explicitly.
Actually it's go(-1).
<input type=button value="Previous Page" onClick="javascript:history.go(-1);">
This solution worked out for me:
Go back
But that's previously adding 'django.core.context_processors.request', to TEMPLATE_CONTEXT_PROCESSORS in your project's settings.
Back
Here |escape is used to get out of the " "string.
Well you can enable:
'django.core.context_processors.request',
in your settings.TEMPLATE_CONTEXT_PROCESSORS block and hook out the referrer but that's a bit nauseating and could break all over the place.
Most places where you'd want this (eg the edit post page on SO) you have a real object to hook on to (in that example, the post) so you can easily work out what the proper previous page should be.
You can always use the client side option which is very simple:
Back
For RESTful links where "Back" usually means going one level higher:
<input type="button" value="Back" class="btn btn-primary" />
All Javascript solutions mentioned here as well as the request.META.HTTP_REFERER solution sometimes work, but both break in the same scenario (and maybe others, too).
I usually have a Cancel button under a form that creates or changes an object. If the user submits the form once and server side validation fails, the user is presented the form again, containing the wrong data. Guess what, request.META.HTTP_REFERER now points to the URL that displays the form. You can press Cancel a thousand times and will never get back to where the initial edit/create link was.
The only solid solution I can think of is a bit involved, but works for me. If someone knows of a simpler solution, I'd be happy to hear from it. :-)
The 'trick' is to pass the initial HTTP_REFERER into the form and use it from there. So when the form gets POSTed to, it passes the correct, initial referer along.
Here is how I do it:
I created a mixin class for forms that does most of the work:
from django import forms
from django.utils.http import url_has_allowed_host_and_scheme
class FormCancelLinkMixin(forms.Form):
""" Mixin class that provides a proper Cancel button link. """
cancel_link = forms.fields.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
"""
Override to pop 'request' from kwargs.
"""
self.request = kwargs.pop("request")
initial = kwargs.pop("initial", {})
# set initial value of 'cancel_link' to the referer
initial["cancel_link"] = self.request.META.get("HTTP_REFERER", "")
kwargs["initial"] = initial
super().__init__(*args, **kwargs)
def get_cancel_link(self):
"""
Return correct URL for cancelling the form.
If the form has been submitted, the HTTP_REFERER in request.meta points to
the view that handles the form, not the view the user initially came from.
In this case, we use the value of the 'cancel_link' field.
Returns:
A safe URL to go back to, should the user cancel the form.
"""
if self.is_bound:
url = self.cleaned_data["cancel_link"]
# prevent open redirects
if url_has_allowed_host_and_scheme(url, self.request.get_host()):
return url
# fallback to current referer, then root URL
return self.request.META.get("HTTP_REFERER", "/")
The form that is used to edit/create the object (usually a ModelForm subclass) might look like this:
class SomeModelForm(FormCancelLinkMixin, forms.ModelForm):
""" Form for creating some model instance. """
class Meta:
model = ModelClass
# ...
The view must pass the current request to the form. For class based views, you can override get_form_kwargs():
class SomeModelCreateView(CreateView):
model = SomeModelClass
form_class = SomeModelForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
In the template that displays the form:
<form method="post">
{% csrf token %}
{{ form }}
<input type="submit" value="Save">
Cancel
</form>
For a 'back' button in change forms for Django admin what I end up doing is a custom template filter to parse and decode the 'preserved_filters' variable in the template. I placed the following on a customized templates/admin/submit_line.html file:
<a href="../{% if original}../{% endif %}?{{ preserved_filters | decode_filter }}">
{% trans "Back" %}
</a>
And then created a custom template filter:
from urllib.parse import unquote
from django import template
def decode_filter(variable):
if variable.startswith('_changelist_filters='):
return unquote(variable[20:])
return variable
register = template.Library()
register.filter('decode_filter', decode_filter)
Using client side solution would be the proper solution.
Cancel

Categories