Tornado: pattern for Flask's flashed messages - python

In Flask, flashing messages on redirect is done using the flash function in the view + {% for message in get_flashed_messages() %} in the template. Tornado doesn't seem to have anything like that built in (which is fine with me, fwiw).
Only replacement I've seen so far looks like this (part of this gist):
class AuthLoginHandler(BaseHandler):
def get(self):
errormessage = self.get_argument("error", default="")
self.render("login.html", errormessage = errormessage)
def post(self):
...(code)...
if not auth:
error_msg = u"?error=" + tornado.escape.url_escape("Login incorrect")
self.redirect(u"/auth/login/" + error_msg)
And then in the template:
<span class="errormessage">{{errormessage}}</span>
Is there a cleaner pattern?
(I can see how one could do multiple messages with this pattern, and a couple ways of cleaning it up, but that's not what I'm asking.)

Flask's flash by default uses cookies, so a direct translation would be to use self.set_secure_cookie("flash", message) to set a message and self.get_secure_cookie("flash"); self.clear_cookie("flash") to read it back.

Related

Generating / Hiding messages from middleware in django

I've got a django app that has some middleware (written in the new style) that checks to see if something a user can register for has become 'full' before the user has finished the process to register for it.
If it has become full - the middleware kicks off an error message letting the user know that it's become full and links them to their registration so they can change it.
The middleware looks like this:
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# ....extra logic (working without issue)....
full_problem_registrations = Registration.objects.filter(
id__in=full_problem_registration_ids
)
request.full_problem_registrations = full_problem_registrations
request.session['registration_now_full'] = False
if full_problem_registrations:
request.session['registration_now_full'] = True
for problem_reg in full_problem_registrations:
reg_url = reverse(
"camp_registrations:edit_registration", kwargs={
'person_id': problem_reg.person.id,
'registration_id': problem_reg.id,
}
)
url_string = '<a href="%s">' % reg_url
error_message = format_html(
"The %s %s registration for %s %s at %s</a> has become\
full and is no longer available. Please either remove\
or change this registration." % (
url_string,
problem_reg.course_detail.course.camp.name,
problem_reg.person.first_name,
problem_reg.person.last_name,
problem_reg.course_detail.location.name,
)
)
existing_messages = get_messages(request)
if existing_messages:
for message in get_messages(request):
# check for duplicates
if message.message == error_message:
pass
else:
messages.error(
request,
error_message,
)
else:
messages.error(
request,
error_message,
)
else:
pass
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
This works great - the message is displayed letting everyone know the user has a problem with their registration.
However - when the user goes in to edit their registration and changes to a non-full course and saves it - when they save it redirects them to the next page.
On the next page it shows the success message - but also continues to show the error message. If the user refreshes or goes to any other page on the site, the error message goes away.
This is because the middleware is processing before the view is processed - and at that time the error is still true.
What is the best way to fix that and keep it from showing?
I figured in the middleware portion after the view is processed (which I believe would include the processing of the POST data) then we could run the check again and remove the error if it exists; but I can't figure out how to nicely remove an error from the messages.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
# should I be checking here to see if the problem still exists and
# removing the message here?
I've tried that - but struggle with figuring out how to remove just that specific message from the FallbackStorage object.
Forms are processed using Class Based Views (in particular this one is an UpdateView)
As you seem to keep running that check on every request and as far I understand the "problem" messages keep interferring with others (like the "success" message) and it's difficult to tell them apart I would recommend to probably not use the messages framework for that, you could eg. just create a simple context processor that provides the data for the full registrations to every template on every request. So these messages won't get persisted into the session but freshly generated on every request.
Something like:
# context processor
def registrations(request):
# .....
full_problem_registrations = Registration.objects.filter(
id__in=full_problem_registration_ids
)
return full_problem_registrations
# add a snippet to eg. your base template
{% if full_problem_registrations.exists %}
Generate problem messages here
{% endif %}

ReactJS server side rendering fo single page application

What I've already done
I have a frontend entirely built with React.JS.
All the business logic is handled by Django and exposed by Django REST Framework via an API.
I'm able to build for different mobile environnements (Android and iOS via Cordova)
The web application is accessible via my Django project (the same that exposes the api), the frontend is sill the same ReactJS code bundled via webpack.
The App has a single entry point, main.js which is a bundled version of my react.js components and dependencies, so my index.html typically looks like this :
<body>
<script type="text/javascript" src="/static/bundles/main-3997ad3476694c3c91cf.js"></script>
</body>
What I want to do
I want to provide a server-side rendering of my web application to let web crawlers correctly index my app on web (I'm not looking for server-side rendering for mobile builds)
How can I handle this considering the fact that my app is a Single Page Application ? I do not want to reinvent the wheel nor to duplicate my code. What kind of node.js server do I have to write in order to achieve this automatic server-side rendering ? Is there any way to provide the server side rendering directly in Django (via some tools reading and interpreting the final results of the page as displayed on the client-side and returning this raw html ?)
You have probably solved your problem by now, but I wanted to share my solution for this.
I have a very similar setup, and have something that seems to work pretty well so far. I basically have a django w/ DRF backend api and isomorphic React/Flux javascript app. I also run a node server next to the python backend server, which acts only as a 'template rendering' service. In essence, replacing the django render function.
So I simply replace the django View with a special IsoView which calls off via http to the node server and returns the rendered html.
from rest_framework.renderers import JSONRenderer
import requests
class IsoView(View):
def user_json(self):
if self.request.user.is_anonymous():
return {'anonymous': True}
else:
return UserSerializer(self.request.user, context={'request': self.request}).data
#classmethod
def render(cls, request, template, data):
req_data = JSONRenderer().render(data)
try:
headers = {'content-type': 'application/json'}
query_params = request.GET
resp = requests.post(_build_url(request.path), params=query_params, data=req_data, headers=headers, timeout=0.1)
reply = resp.json()
if resp.status_code == 302:
return redirect(reply.get('redirect'))
if 'error' in reply:
raise Exception("\n\nRemote traceback: {}".format(reply.get('traceback')))
except requests.exceptions.RequestException as err:
logger.warn('IsoView request exception: {}'.format(err))
reply = {}
return render(request, template, {
'react': reply.get('result'),
'data': data
})
And use it like so:
class HomePage(IsoView):
def get(self, request, *args, **kwargs):
return self.render(request, 'app.html', {
'user': json_data...
})
This also assumes a django template which uses something like this
<html>
<head>
<script>
window.data = {{ data|json }};
</script>
</head>
<body>{{ react|safe }}</body>
</html>
What this does is it renders the html returned from node in the body tag and also dumps the json data required for bootstrapping the app on the client in the window.data object.
This is a really simplified version of the system, but it should work. You should be careful with XSS attacks on the window.data bit, so make sure to escape all your json data but other than that, you should be all good.
Then the node template server looks really similar to any of the tutorials online that you can find for server-side react. Just a simple express app.
Alternatively, you don't need to mess around with django templates at all if you render the full ... in node and return that as a string.
Hope that helps.

CSRF protection on AJAX authentication in Flask

I'd like to AJAXify both a login and a signup form on a site. Up to now I've been using WTForms mainly for its built-in CSRF protetion, but for this project I didn't feel like it was worth it -- an extra layer of abstraction, and therefore frustration, for something that should be pretty simple.
So I came across this snippet on Flask's security section:
#app.before_request
def csrf_protect():
if request.method == "POST":
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = some_random_string()
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
I understand the thought process behind this code. In fact, it all makes perfect sense to me (I think). I can't see anything wrong with it.
But it doesn't work. The only thing I've changed about the code is replacing the pseudofunction some_random_string() with a call to os.urandom(24). Every request has 403'd so far because token and request.form.get('_csrf_token') are never the same. When I print them this becomes obvious -- usually they're different strings, but occasionally, and seemingly with no underlying reason, one or the other will be None or a truncated version of the output of os.urandom(24). Obviously something out of sync, but I'm not understanding what it is.
You can get the convenience of flask-wtf without all the heaviness, and without rolling your own:
from flask_wtf.csrf import CsrfProtect
then on init, either:
CsrfProtect(app)
or:
csrf = CsrfProtect()
def create_app():
app = Flask(__name__)
csrf.init_app(app)
The token will then be available app-wide at any point, including via jinja2:
<form method="post" action="/">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>
(via the docs)
I think your problem is os.urandom function. The result of this function can contains symbols which not will parse properly in html. So when you insert csrf_token in html and don't do any escaping, you have the described problem.
How to fix.
Try to escape csrf_token in html (see docs) or use another approach for generating csrf token. For example using uuid:
import uuid
...
def generate_random_string():
return str(uuid.uuid4())
...

How to pass information using an HTTP redirect (in Django)

I have a view that accepts a form submission and updates a model.
After updating the model, I want to redirect to another page, and I want a message such as "Field X successfully updated" to appear on this page.
How can I "pass" this message to the other page? HttpResponseRedirect only accepts a URL. I've seen this done before on other sites. How is this accomplished?
This is a built-in feature of Django, called "messages"
See http://docs.djangoproject.com/en/dev/topics/auth/#messages
From the documentation:
A message is associated with a User.
There's no concept of expiration or
timestamps.
Messages are used by the Django admin
after successful actions. For example,
"The poll Foo was created
successfully." is a message.
You can use django-flashcookie app
http://bitbucket.org/offline/django-flashcookie/wiki/Home
it can send multiple messages and have unlimited types of messages. Lets say you want one message type for warning and one for error messages, you can write
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
return HttpResponseRedirect("/")
or
def simple_action(request):
...
request.flash['error'] = 'something wrong'
return HttpResponseRedirect("/")
or
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
request.flash['error'] = 'something wrong'
return HttpResponseRedirect("/")
or even
def simple_action(request):
...
request.flash['notice'] = 'Hello World'
request.flash['notice'] = 'Hello World 2'
request.flash['error'] = 'something wrong'
request.flash['error'] = 'something wrong 2'
return HttpResponseRedirect("/")
and then in you template show it with
{% for message in flash.notice %}
{{ message }}
{% endfor }}
or
{% for message in flash.notice %}
{{ message }}
{% endfor }}
{% for message in flash.error %}
{{ message }}
{% endfor }}
I liked the idea of using the message framework, but the example in the django documentation doesn't work for me in the context of the question above.
What really annoys me, is the line in the django docs:
If you're using the context processor, your template should be rendered with a RequestContext. Otherwise, ensure messages is available to the template context.
which is incomprehensible to a newbie (like me) and needs to expanded upon, preferably with what those 2 options look like.
I was only able to find solutions that required rendering with RequestContext... which doesn't answer the question above.
I believe I've created a solution for the 2nd option below:
Hopefully this will help someone else.
== urls.py ==
from django.conf.urls.defaults import *
from views import *
urlpatterns = patterns('',
(r'^$', main_page, { 'template_name': 'main_page.html', }, 'main_page'),
(r'^test/$', test ),
== viewtest.py ==
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def test(request):
messages.success( request, 'Test successful' )
return HttpResponseRedirect( reverse('main_page') )
== viewmain.py ==
from django.contrib.messages import get_messages
from django.shortcuts import render_to_response
def main_page(request, template_name ):
# create dictionary of items to be passed to the template
c = { messages': get_messages( request ) }
# render page
return render_to_response( template_name, c, )
== main_page.html ==
{% block content %}
{% if messages %}
<div>
{% for message in messages %}
<h2 class="{{message.tag}}">{{ message.message }}</h2>
{% endfor %}
</div>
{% endif %}
{% endblock %}
I have read and checked all answers, and it seems to me that the way to go now is using the messaging framework. Some of the replies are fairly old and have probably been the right way at the time of the posting.
There is a lot of solutions
1 Use Django-trunk version - it support sending messages to Anonymous Users
2 Sessions
def view1(request):
request.session['message'] = 'Hello view2!'
return HttpResponseRedirect('/view2/')
def view2(request):
return HttpResponse(request.session['message'])
3 redirect with param
return HttpResponseRedirect('/view2/?message=Hello+view2')
4 Cookies
Can you just pass the message as a query param oon the URL to which you're redirecting? It's not terribly RESTy, but it ought to work:
return HttpResponseRedirect('/polls/%s/results/?message=Updated" % p.id)
and have that view check for a message param, scrub it for nasties, and display it at the top.
I think this code should work for you
request.user.message_set.create(message="This is some message")
return http.HttpResponseRedirect('/url')
Take a look at Django's messages framework. http://docs.djangoproject.com/en/dev/ref/contrib/messages/#ref-contrib-messages
You could also have the redirect url be the path to an already parameterized view.
urls.py:
(r'^some/path/(?P<field_name>\w+)/$', direct_to_template,
{'template': 'field_updated_message.html',
},
'url-name'
),
views.py:
HttpResponseRedirect( reverse('url-name', args=(myfieldname,)) )
Note that args= needs to take a tuple.
The solution used by Pydev UA is the less intrusive and can be used without modifying almost nothing in your code. When you pass the message, you can update your context in the view that handles the message and in your template you can show it.
I used the same approach, but instead passing a simple text, passed a dict with the information in useful fields for me. Then in the view, updated context as well and then returned the rendered template with the updated context.
Simple, effective and very unobstrusive.
While all suggestions so far work, I would suggest going with Ry4an's (pass it in the request URL) - just change the actual text to a coded text within a predefined set of text messages.
Two advantages here:
Less chance of something hacking through your scrubbing of bad content
You can localize your messages later if needed.
The other cookie related methods.. well, they don't work if the browser doesn't support cookies, and are slightly more expensive.. But only slightly. They're indeed cleaner to the eye.

How to send a session message to an anonymous user in a Django site?

I often show messages about user actions to logged in users in my Django app views using:
request.user.message_set.create("message to user")
How could I do the same for anonymous (not logged in) users? There is no request.user for anonymous users, but the Django documentation says that using the "session" middleware you can do the same thing as the above code. The Django documentation that links to the session middleware claims it is possible, but I couldn't find how to do it from the session documentation.
This is what I do, using context processors:
project/application/context.py (check for messages and add them to the context):
def messages(request):
messages = {}
if 'message' in request.session:
message_type = request.session.get('message_type', 'error')
messages = {'message': request.session['message'],
'message_type': message_type}
del request.session['message']
if 'message_type' in request.session:
del request.session['message_type']
return messages
project/settings.py (add the context to the TEMPLATE_CONTEXT_PROCESSORS):
TEMPLATE_CONTEXT_PROCESSORS = (
"django.core.context_processors.request",
"django.core.context_processors.debug",
"django.core.context_processors.media",
"django.core.context_processors.auth",
"project.application.context.messages",
)
With the above the function messages will be called on every request and whatever it returns will be added to the template's context. With this in place, if I want to give a user a message, I can do this:
def my_view(request):
if someCondition:
request.session['message'] = 'Some Error Message'
Finally, in a template you can just check if there are errors to display:
{% if message %}
<div id="system_message" class="{{ message_type }}">
{{ message }}
</div>
{% endif %}
The message type is just used to style depending on what it is ("error","notice","success") and the way that this is setup you can only add 1 message at a time for a user, but that is all I really ever need so it works for me. It could be easily changed to allow for multiple messages and such.
See http://code.google.com/p/django-session-messages/ until the patch that enables session based messages lands in Django tree (as I saw recently, it's marked for 1.2, so no hope for quick addition...).
Another project with similar functionality is Django Flash (http://djangoflash.destaquenet.com/).
Store the data directly in the session, which is a dict-like object. Then in the view/template, check for the value.
More information here:
http://docs.djangoproject.com/en/dev/topics/http/sessions/#using-sessions-in-views
You could also create a middleware class to check for the session object on each request, and do your build up/tear down there.

Categories

Resources