Thanks in advance for your help!
I have a flask website, on which the users can post some programming code snippets, like them or write comments under each post. For every action, f.e. deleting a post or liking a post, I've defined a route, which checks if the user has enought rights to perform the action and so on. This works fine.
What I would like to achieve:
I want to be able to use those functionalities, without having to reload the page. I've tried this with an ajax request, but could not figure it out.
Here are some of these routes:
def delete_comment(id):
comment = Comment.query.get_or_404(id)
post = Post.query.get_or_404(comment.post_id)
if comment.user != current_user and post.author != current_user:
abort(403)
db.session.delete(comment)
db.session.commit()
flash('The comment has been deleted!', 'success')
return redirect(url_for('main.home'))
If I delete this comment, I get redirected to my homepage. Like this, you can't unlike a post if you've accidentally liked it and so on. Additionally, if the user has scrolled to the 20th post, it is anoying to scroll down again.
How could I solve this problem?
For these types of requests, I've found javascript combined with a REST API endpoint work well - i.e. create a REST endpoint that will do the like/dislike and return a response, and have the button/javascript make the REST call with a simple post or get, and modify the thing you want it to.
#app.route('/api/v1/status', methods=['GET'])
def get_status():
json_data = json.dumps({'status': get_status()})
return json_data
Similarly you can do this for posts with buttons
#app.route('/api/v1/button_update', methods=['PUT'])
def update_button():
json_data = json.dumps({'status': update_db_state()})
return json_data
For the json, you'll have to trudge through that on your own because it will depend entirely on your page structure how you pull and update those values, what libraries you're using, etc.
Related
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.
I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.
I'm new to both web development and django so maybe that's a noob question.
I want to do the following:
Ask user to fill some form and submit it.
Then, parse and format the content and display it back to the user to let him verify it.
User can accept the result or go back to the previous view, update data and resend.
This is as far as I can think:
views.py
def add_content(request):
if request.method == 'POST':
form = AddContentForm(request.POST)
if form.is_valid():
content = form.save(commit=False)
return verify_content(request, content)
else:
form = AddContentForm()
return render(request, 'myapp/add_content.html', {'form' : form})
def verify_content(request, content):
return render(request, 'myapp/verify_content.html', {'content' : content})
The verify_content template will obviously contain two buttons ('back', 'ok'), but I don't know how to pass the content object to a view for saving it in the db, or send it back to the previous view from there. Should I use js? Can i do it with just server side code?
Maybe my whole logic is wrong. Should I save the object in the db before verification and then delete it if needed (sounds ugly)? What is a good way to implement this?
Thanks in advance for your time.
You could use the users session for this:
request.session['content'] = content
and in the view where the user should verify his input do:
content = request.session['content']
and voilá you got the content between 2 views.
Django also secures that users can't tinker with its data by either saving it server side, or in a signed cookie.
I would save the form with commit=True in the add_content view, and would add a verified field or something to the model. Then you can append the pk as GET parameter to the link which will get you back to add_content view from verify. You can extract the parameter from request.GET dict.
Question Clarification:
I'm trying to test if the user is authenticated or not for each page request.
I'm trying to use Authentication for the first time in Django and I am not grasping how the login view is supposed to handle authentications.
When I use #login_required, I'm redirecting to "/login" to check if the user is logged in and if not, display the login page. However, trying to redirect back to the original page is causing an infinite loop because it's sending me back to the login page over and over again.
I'm clearly not grasping how #login_required is supposed to work but I'm not sure what I'm missing. I've been searching around for awhile for an example, but everyone uses the default #login_required without the 'login_url' parameter.
So for example.. the page I'm trying to access would be...
#login_required(login_url='/login')
def index(request):
And then my login would be.. (obviously incomplete)..
Edit: just to note.. the session variables are set in another view
def login(request):
if '_auth_user_id' in request.session:
# just for testing purposes.. to make sure the id is being set
print "id:",request.session['_auth_user_id']
try:
user = Users.objects.get(id=request.session['_auth_user_id'])
except:
raise Exception("Invalid UserID")
# TODO: Check the backend session key
# this is what I'm having trouble with.. I'm not sure how
# to redirect back to the proper view
return redirect('/')
else:
form = LoginForm()
return render_to_response('login.html',
{'form':form},
context_instance=RequestContext(request)
)
Well, as you say, obviously that's not going to work, because it's incomplete. So, until you complete it, you're going to get an infinite loop - because you haven't written the code that puts _auth_user_id into request.session.
But I don't really know why you're making that test in the first place. The auth documentation has a perfectly good example of how to write a login view: get the username and password from the form, send them to authenticate to get the user object, then pass that to login... done.
Edit I think I might see where your confusion is. The login_required decorator itself does the check for whether the user is logged in - that's exactly what it's for. There's no need for you to write any code to do that. Your job is to write the code that actually logs the user in, by calling authenticate and login.
Try to call login(), see the next please:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.login
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 #}