I have to create a small web app in Flask which contains an API and also an interface and I'm facing the following problem:
This would be how i handle a GET request:
#app.route('/member/<id>', methods=['GET'])
def member_get(id):
member = cursor.execute(f"select * from members where id={id}").fetchone()
if member is not None:
return to_json(member), 200
else:
return 'Not found', 404
And I would like to create some small forms with which I could do GET,POST,PUT,DELETE operations.
This would be how I get the data from the form:
#app.route('/dashboard', methods=['POST'])
def dashboard_post():
id = request.form['get_id']
return redirect(url_for("member_get",id=id))
My question is how can I get the data from the API method without actually redirecting to that page?
More precise, can I call somehow redirect(url_for("member_get",id=id)) and get the response data directly? (if I print the return of the redirect method it only shows the request status)
I assume one solution would be using the requests module, but is there a way to do it directly in Flask?
First of all, an API should always return a response in a format that is consistent and predictable. The code for member_get returns JSON in case of success but plain text in case of failure. This is not okay. You should return JSON always, with the appropriate HTTP status code. Here you are using 404 to express Not found, this is good.
You can use the jsonify function in Flask for that. And maybe normalize the response, so that is always has the same shape, whether the member ID is found or not.
Otherwise, parsing the response from your API will be harder because it is not consistent depending on the scenario.
Second point, if I understand it right: if you want to invoke the route '/member/' from within your API, you could simply do:
return member_get(id)
You call the function that is attached to the route, not the route itself. Of course you could actually fetch the page with the requests module but this is unnecessary, since the function is available internally.
Your question is not clear, but as I understand, (1) you think you have to get your form data from one view and send it to another view to do operations. (2) you are not familiar with flask request and flask-wtf. and maybe (3) looking for a way to do this without refreshing or redirecting the page.
You don't need to separate your GET and POST methods. instead you can integrate both in one view.
#app.route('/member/<id>', methods=['GET', 'POST])
To handling data, you can use flask request.
from flask import request
and access to data in your view like this:
id = request.form.get("idField")
but you can also use Flask-WTF to simply make and handle forms.
with Flask-WTF your view would be like this:
from app.forms import SearchForm
#app.route('/your-endpoint', methods=['GET', 'POST'])
def yourView():
form = your_form()
if form.validate_on_submit():
id=form.idField.data
return render_template('test.html', form=form)
the condition form.validate_on_submit() checks if you are submitting a from or you just opened it. if you submit a form and it's data are valid based on validators defined in your form, the code runs. else just renders the template and returns the page.
To learn how to make forms with Flask-WTF I recommend reading this article:
If you don't want to refresh the page or redirect it after submitting the form, you can use AJAx on your page.
Related
#app.route('/path', methods= ['POST', 'GET'])
def path():
results = getResults(request)
return render_template('path_result.html', results=results)
I need a way to update the URL.
Currently, when the user submits their form, the results are returned and rendered, but the URL does not contain the submitted page parameters. So the url remains as /path, instead of /path?formDataA=foo
Mainly I need this to allow users to share the links to specific results with one another easily.
When you POST your data to a route you can't see the parameter. Thats the normal way. You can send your form as GET then all form parameters are appended to your url but i would prefer the POST way.
After posting the values you can redirect to another route where you display your values. When redirecting you can append some parameters your url that you need.
So make a method that process your form data. After processing redirect to another page where you display that values.
https://laravel.com/docs/8.x/routing#redirect-routes
I am unable to send the user details along with requests module i had to hard code the user details in the data payload to identify the user.
full_url = ''.join(['http://', get_current_site(request).domain, '/am/reply'])
data = {
'agent_type':'trigger',
'input':platform,
'userid':request.user.id ####==>> had to send userid like this
}
a = requests.get(full_url,params=data)
Is there way to send all general request data using requests.?
And moreover the requests url the destination view i have implemented
def index(request):
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('login'))
And request.user.id is none when url is reached through requests module
In general how should i validate a request when using requests module
Django uses request and response objects to pass state through the system.
When a page is requested, Django creates an HttpRequest object that contains metadata about the request. Then Django loads the appropriate view, passing the HttpRequest as the first argument to the view function. Each view is responsible for returning an HttpResponse object.
Some of the middleware included in Django’s contrib apps set attributes on the request. If you don’t see the attribute on a request, be sure the appropriate middleware class like authenticationmiddleware,sessionmiddleware.
Following piece of code will give the user.id if and only if the user is authenticated.
def myview(request):
if request.user.is_authenticated:
print request.user.id
else:
... # Do something else.
https://docs.djangoproject.com/en/1.10/ref/request-response/
If I understood your question correctly, You are getting request in one view, and then making a call to other view using requests module. It that case the request object in index view will be totally different because that request was sent from your server where application works, not from user. You can only get data in index view using request.GET.get("userid") and so on. And then if you will need user info, just fetch it again from database using userid. Passing request object to other view using requests library is not possible.
I am currently using code found here:
http://flask.pocoo.org/snippets/8/
And I decorate my function accordingly to have the admin authenticate when requesting a specific admin page. However, instead of requiring the admin to keep authenticating each time they admin page, I noticed that it somehow keeps track of the session and no longer requires authentication after successfully authenticating once. Is there some way to force flask to re-authenticate every time an admin requests the given decorated admin page?
Using the included snippet, there is no good way to force a user to log in every time they request the given page.
This is because that snippet is using HTTP Basic Auth and there is no good way to ask the browser to stop sending that header.
What you are looking for can be done with a custom decorator. You can use the sample below. Note that your case will be different, but you can use this as a guide.
from web import app, has_role
#app.route("/admin/my_page")
#login_required
#has_role(role="admin")
def admin_my_page():
//do stuff
Then, in your project init, or an include file you can add the following:
def has_role(role=None):
def _initial_decorator(view_func):
def _decorator(*args, **kwargs):
response = view_func(*args, **kwargs)
if g.user.user_level != role:
from flask import redirect, url_for
return redirect(url_for("no_access"))
return response
return wraps(view_func)(_decorator)
return _initial_decorator
This should at lease give you an idea of how to create a custom decorator, and then check for role permissions. You can expand this to however you need. You can put engine logic, or other checks to fit your project.
I will try to describe problem:
In HTML i have some AJAX call to, lets say, URL getMetaData (this is function in views.py). I that function I check POST dictionary to check is all values there. If not i want to redirect to main page ("main.html"). This main.html is rendered in function main(request) in same views.py file.
When i do this:
def main(request):
return render(request,'main.html')
def getMetaData(request):
if dictionary not valid:
return main(request)
This not working... main function is called but page stays the same.
A redirect in an ajax call will not necessary change the page, and in any case it's not a good idea to mix POST and GET like that. I suggest that you handle it all in the ajax call and redirect there instead, so your django view would be something like:
def getMetaData(request):
if is_invalid(request.POST):
return redirect_url
else:
return None
And the jquery:
$.post(post_url, data, function(new_url) {
if (new_url== null)
do_stuff();
else
window.location.replace(new_url);
});
The problem is that you do this with an AJAX call. Your Python code should work (at least, with this little example I don't see why it wouldn't), but the HTML of your home page will be the data returned to your AJAX call. There is no reason why your browser would then show it as the current page.
If you want to do something different depending on the result of the AJAX call, you should do it in Javascript. Can you show us that?
When processing a POST request in the Django views.py file, I sometimes need to redirect it to another url. This url I'm redirecting to is handled by another function in the same Django views.py file. Is there a way of doing this and maintaining the original POST data?
UPDATE: More explanation of why I want to do this.
I have two web apps (let's call them AppA and AppB) which accept data entered into a text field by the user. When the the user clicks submit, the data is processed and detailed results are displayed. AppA and AppB expect different types of data. Sometimes a user mistakenly posts AppB type data to AppA. When this happens I want to redirect them to AppB and show the AppB results or at least have it populated with the data they entered into AppA.
Also:
The client wants two separate apps rather than combining them into just one.
I can't show the code as it belongs to a client.
UPDATE 2:
I've decided that KISS is the best principle here. I have combined the two apps into one which makes things simpler and more robust; I should be able to convince the client it's the best way to go too. Thanks for all the great feedback. If I was going to maintain two apps as described then I think sessions would be the way to do this - thanks to Matthew J Morrison for suggesting that. Thanks to Dzida as his comments got me thinking about the design and simplification.
If you faced such problem there's a slight chance that you might need to revise your designs.
This is a restriction of HTTP that POST data cannot go with redirects.
Can you describe what are you trying to accomplish and maybe then we can think about some neat solution.
If you do not want use sessions as Matthew suggested you can pass POST params in GET to the new page (consider some limitations such as security and max length of GET params in query string).
UPDATE to your update:)
It sounds strange to me that you have 2 web apps and those apps use one views.py (am I right?). Anyway consider passing your data from POST in GET to the proper view (in case data is not sensitive of course).
I think how I would probably handle this situation would be to save the post data in session, then remove it when I no longer need it. That way I can access the original post data after a redirect even though that post is gone.
Will that work for what you're trying to do?
Here is a code sample of what I'm suggesting: (keep in mind this is untested code)
def some_view(request):
#do some stuff
request.session['_old_post'] = request.POST
return HttpResponseRedirect('next_view')
def next_view(request):
old_post = request.session.get('_old_post')
#do some stuff using old_post
One other thing to keep in mind... if you're doing this and also uploading files, i would not do it this way.
You need to use a HTTP 1.1 Temporary Redirect (307).
Unfortunately, Django redirect() and HTTPResponseRedirect
(permanent) return only a 301 or 302. You have to implement it yourself:
from django.http import HttpResponse, iri_to_uri
class HttpResponseTemporaryRedirect(HttpResponse):
status_code = 307
def __init__(self, redirect_to):
HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to)
See also the django.http module.
Edit:
on recent Django versions, change iri_to_uri import to:
from django.utils.encoding import iri_to_uri
use requests package.Its very easy to implement
pip install requests
then you can call any urls with any method and transfer data
in your views import requests
import requests
to post data, follow the format
r = requests.post('http://yourdomain/path/', data = {'key':'value'})
to get the absolute url in django view, use
request.build_absolute_uri(reverse('view_name'))
Thus the django view code looks like
r = requests.post(
request.build_absolute_uri(reverse('view_name')),
data = {'key':'value'}
)
where r is the response object with status_code and content attribute.
r.status_code gives the status code(on success it will be 200) and r.content gives the body of response. There is a json method(r.json()) that will convert response to json format
requests
requests.post
Just call your new view from your old view using the same request object.
Of course it won't result in a redirect as such, but if all you care about is 'transferring' data from one view to the other, then it should work.
I tested the following snippet and it works.
from django.views.generic import View
class MyOldView(View):
def post(self, request):
return MyNewView().post(request)
class MyNewView(View):
def post(self, request):
my_data = request.body
print "look Ma; my data made it over here:", my_data
You can use render and context with with it:
Render(request,"your template path", {'vad name' : var value}
You can recive vars in template :
{% If var name %}
{{ var name }}
{% endif %}
I faced a similar issue recently.
Basically I had a form A, upon submitting it another form B would show up, which contains some results + a form. Upon submitting B, i wanted to display some alert to user and keep user on B only.
The way I solved this, is by displaying the results in a <output> field, in B.
<output name="xyz" value="xyz">{{xyz}}</output>
And I used the same view for A->B and B->B. Now I just had to distinguish if the request is coming from A or B and render accordingly.
def view1(request):
if "xyz" in request.POST:
# request from B
# do some processing
return render(request, 'page.html', {"xyz":request.POST["xyz"]})
else:
# request from A
res = foo() # some random function
return render(request, 'page.html', {"xyz":res})
But this only works if form B is small and not that dynamic.
If you are using a redirect after processing the POST to AppB, you can actually get away with calling the AppB method from the AppA method.
An Example:
def is_appa_request(request):
## do some magic.
return False or True
is_appb_request = is_appa_request
def AppA(request):
if is_appb_request(request):
return AppB(request)
## Process AppA.
return HttpResponseRedirect('/appa/thank_you/')
def AppB(request):
if is_appa_request(request):
return AppA(request)
## Process AppB.
return HttpResponseRedirect('/appb/thank_you/')
This should yield a transparent experience for the end-user, and the client who hired you will likely never know the difference.
If you're not redirecting after the POST, aren't you worried about duplicate data due to the user refreshing the page?
You can redirect with session using request.session["key"] as shown below:
# "views.py"
from django.shortcuts import redirect
def my_view(request):
# Here
request.session["message"] = "success"
return redirect("https://example.com")
# "index.html"
{{ request.session.message }} {# success #}