control flask-cache'ing from a view - python

I am wondering if there is a way to allow the user to control the caching properties of a given view using Flask-Cache.
For example, I would like for a view to be cached indefinitely unless the user clicks a reload link, in which case the view would be re-generated. I noticed there is an unless kwarg available to #cached decorator, but I am not sure how one would use this.
It seems that I should be able to add a url_for('this_view', dont_cache=True) somewhere on this_view's Jinja template.

You can clear the cache; given a view function and the full path to the route, use:
from flask import current_app
with current_app.test_request_context(path=path):
# the cache key can use `request` so we need to provide the correct
# context here
cache_key = view.make_cache_key()
cache.delete(cache_key)
Here path is the path to the view; you could use path = url_for('this_view') to generate it, and view is the (decorated) function object you used #cache.cached() on. cache is the Flask-Cache object.
Once the cache is cleared, a new request to that view will re-generate it.
If you never set a custom key_prefix (callable or string) then the default cache key for a given view is based on the request.path value; you could use this too:
cache_key = 'view/{}'.format(url_for('this_view'))
cache.delete(cache_key)
but the current_app.test_request_context / view.make_cache_key() dance above will make your cache key re-generation more robust.

Related

Can I change Django's sessionid token length?

I would like to change the length of Django's sessioid tokens, so instead of 32 characters it would be something insane, like 64 characters. I know that's a lot, but can it be done? Are there reasons to never do something like this?
I know the key length is defined in django.contrib.sessions.backends in class SessionBase, but I can't seem to find instructions how to override backend functions or replace them.
Ps. I'm new to web development, so I find it difficult to understand some of the documentation. With C++ it was so much easier...
Subclassing in Django is just the same as in C++; you just need to define a new class that inherits from the base one and overrides the relevant method.
In your case the session key is defined in the _get_new_session_key method. You need to inherit from whichever session backend you are already using - eg the db, file or cache backends - and define that method. For example:
from django.contrib.sessions.backends.db import SessionStore as OriginalSessionStore
class SessionStore(OriginalSessionStore):
def _get_new_session_key(self):
"Returns session key that isn't being used."
while True:
session_key = get_random_string(64, VALID_KEY_CHARS)
if not self.exists(session_key):
break
return session_key
Now you can use "path.to.module.with.overridden.class" in your SESSION_ENGINE setting.

How to process URLs in Flask before Rules are mapped to endpoints?

I'm trying to implement a feature in my API where a part of a URL is optional. If supplied, I'd like to process it and stick some data in g. If not, I'd put some default info in g. Either way, I'd then remove it from the URL before Rules are mapped to endpoints. So I'd like the following two URLs to end up calling the same endpoint:
/bar/1 (I would fill in a default value for foo here)
/foo/32/bar/1
I want this same optional piece of URL in every endpoint I have. I think I could do this by brute force by decorating every endpoint but I have over 250 of them so I'd like something more elegant.
I'm using multiple Blueprints and I'd like to leave each endpoint as simple as I already have them if possible (the blueprints already have their own prefixes):
#blueprint1.route('/bar/<int:id>', methods=['GET'])
#blueprint2.route('/bar/<int:id>', methods=['GET'])
def get_foo():
I've tried the #url_defaults, #url_value_preprocessor, and #before_request decorators but it seems the Rule has already been mapped to the endpoint by then. Is there a hook to access the URL before mapping is done?
I made this work by subclassing the Flask class and overriding the create_url_adapter() function like so:
class MyFlask(Flask):
"""
MyFlask subclasses the standard Flask app class so that I can hook the URL parsing before the URL
is mapped to a Route. We do this so we can extract an optional "/foo/<int:foo_id>" prefix. If we
see this prefix, we store the int value for later processing and delete the prefix from the URL before
Flask maps it to a route.
"""
def _extract_optional_foo_id(self, path):
....
return path, foo_id # path has the optional part removed
def create_url_adapter(self, request):
"""
Augment the base class's implementation of create_url_adapter() by extracting an optional foo_id and modifying
the URL. The Flask function name is misleading: we aren't creating anything like an object. The "adapter" is
just this function call. We add our own behavior then call the super class's version of this function.
:param request: Flask's Request object
:return: the results of the Flask super class's create_url_adapter()
"""
# Must test for request. It is None when calling this for the app-wide scenario (instead of request-wide).
if request and request.environ:
(new_path, foo_id) = self._extract_optional_foo_id(request.environ['PATH_INFO'])
if foo_id is not None:
request.environ['PATH_INFO'] = new_path
request.foo_id_in_url = foo_id
return super(MyFlask, self).create_url_adapter(request)
Then in my app init code, instead of instantiating an instance of Flask, I do:
app = MyFlask( ... )
Usually URLs are mapped to the endpoints only once during the application initialization. More precisely - each time the code #app.route('/some_route') ... is being encountered by the interpreter first time. It's important to understand that mapping URLs to endpoints doesn't happen per each request (like it is in PHP, for example).
One way to add some default values to the endpoint is to override one of
Flask.route()
Flask.add_url_rule()
Blueprint.route()
in your successor app/blueprint class. Just put it into **options dict.

Easy way to switch between renderers within the same view method

I set up my function like this
#view_config(
route_name = 'route_name',
permissions = 'permissions',
renderer = 'r.mako'
)
def r( request ):
# stuff goes here
now, I want to add functionality such that I check certain conditions (using ajax) i would use one template, otherwise use another. is there a way to do this in pyramid? thanks
Well you can add the view multiple times with different renderers if you can determine what you want to do via predicates. For example
#view_config(route_name='route', xhr=True, renderer='json')
#view_config(route_name='route', renderer='r.mako')
#view_config(route_name='route', request_param='fmt=json', renderer='json')
def r(request):
# ...
Or you can just override the renderer manually via request.override_renderer = 'b.mako':
http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/renderers.html#overriding-a-renderer-at-runtime
Or you can just explicitly render the response via the render and render_to_response methods from within the view, as the renderer argument is ignored if you return a Response object from the view.
Note that the xhr predicate in the first example should be sufficient to check for an ajax request. Note also that you don't have to use the same view for both if you don't want to, just depends.

delegate from one view to another

I am using Pyramid with different views. I am wondering if it is possible to "delegate" parts of a views job to another view (another route).
For example:
http://localhost:6543/sample_project/testruns/testrun001/report.html?action=edit
=> delegate to:
http://localhost:6543/sample_project/testruns/testrun001/report.json
the views I am using:
# report:
#view_config(context=Root, route_name='report_route')
def report_view(context, request):
...
if 'edit' in request.GET.getall('action'):
# TODO: delegate to code_view
???
...
# render report from report.json
# editor:
#view_config(context=Root, route_name='report_edit_route')
#view_config(context=Root, route_name='code_route')
def code_view(context, request):
....
You can directly call views, they simply won't be going through the pyramid router mechanism which applies the permission and other such parameters to the view. Presumably if you are trying to call it, however, you already know these things.
In reality, you probably just want to refactor the common functionality into a separate function that each of your views can then delegate part of the work to.

Django Unittesting adding a variable to the session

In my unittest I need to add a variable to the session, because that variable is used in the view which is being tested. The django documentation says this is possible in the following way (https://docs.djangoproject.com/en/1.3/topics/testing/#django.test.client.Client.session):
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
This code example actually doesn't work, because you will get the error that a dict doesn't have a function save. I also tried various other ways to change the contents inside the session dict, but haven't found a way to change it yet.
I know what it means, what i get back is a dict object so it doesnt have the save function. But the session dict also doesn't update when adding keys.
The documentation statues when using self.client.session it should return a SessionStore object instead of a dictionary.
Seems right now there is a bug in Django, so it doesn't work for unauthenticated users to change the session. This is the corresponding ticket: https://code.djangoproject.com/ticket/11475 .
A work around is to create a dummy view in which the session variables are set and calling that view with the Client.get('url_of_dummy_view').
The ticket referenced by #Sam Stoelinga has been closed as a duplicate. The corresponding ticket can be found here.
To save session data you will have to use an authenticated user. I found a solution here.
class BlogAdminTestCase(TestCase):
def setUp(self):
# Setup Test User
User.objects.create_user(
username='foo',
password='bar'
)
# Must login to modify session variables
self.client.login(username='foo', password='bar')
s = self.client.session
s['my_session_variable'] = 'Yay!'
s.save()

Categories

Resources