using query string in Python Pyramid route configuration - python

this is very specific to what I am trying to do so I start describing what it is:
a Pyramid app serving plots like http://localhost:6543/path/to/myplot/plot001.png
if the plot is not available another image is served (work.png)
another part is the deform view which provides a HTML form to enter the configuration for a plot like: http://localhost:6543/path/to/myplot/plot001.png?action=edit. Note here the query string "action=edit".
the configuration consists of datafile, templates etc.
the form has save (to save the config) and render buttons (http://localhost:6543/path/to/myplot/plot001.png?action=render). Rendering results into a png file which then is used in a static way.
I figured out all the pieces like rendering using Matplotlib etc. but I am new to Pyramid and Deform. I also have a working view that serves the plot from file. The deform form kind of works, too. At the moment it is unclear to me how to best structure the ULRs to distinguish the serve, edit and render usecases. I guess in Pyramid talk this means how to configure the routes for serve_view and edit_view.
__init__.py:
config.add_route('serve_route',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
config.add_route('edit_route',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
# can I use query strings like "?action=edit" here to distinguish the difference?
views.py:
#view_config(context=Root, route_name='serve_route')
def plot_view(context, request):
...
#view_config(context=Root, renderer='bunseki:templates/form.pt', route_name='edit_route')
def edit_view(request):
...
I the Pyramid manual I could not find reference how to set parameters in the route. I guess a pointer to some documentation or sample would be sufficient and I can figure out the details myself. Thank you!

There are two ways to do this depending on what you prefer for separating your code.
Put all of the logic into your view, separated by 'if' statements on request.GET.get('action').
config.add_route('plot', '/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
config.scan()
#view_config(route_name='plot')
def plot_view(request):
action = request.GET('action')
if action == 'edit':
# do something
return render_to_response('bunseki:templates/form.pt', {}, request)
# return the png
Register multiple views and delegate between them using Pyramid's view lookup mechanics.
config.add_route('plot', '/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
config.scan()
#view_config(route_name='plot')
def plot_image_view(request):
# return the plot image
#view_config(route_name='plot', request_param='action=edit',
renderer='bunseki:templates/form.pt')
def edit_plot_view(request):
# edit the plot
return {}
# etc..
Hope this helps. It's an excellent example of registering a single url pattern, and using different views for different types of requests on that url.

I'm not sure you can use contex=Root in that situation, but what you are asking for is probably the matchdict.
__init__.py:
config.add_route('serve_route',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
views.py:
#view_config(route_name='serve_route')
def plot_view(request):
project_name = request.matchdict['project_name']
action = request.params.get('action', None)
http://docs.pylonsproject.org/projects/pyramid/1.1/narr/urldispatch.html#matchdict
Edit:
If your question is more a general question regarding routing, you should create one route per action to keep the code of your view functions shorter and clearer. For example, if you want to edit and render, your routes could look something like this:
__init__.py:
config.add_route('render_plot',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
config.add_route('edit_plot',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}/edit')
views.py:
#view_config('render_plot')
def render(request):
pass
#view_config('edit_plot', renderer='bunseki:templates/form.pt')
def edit(request):
pass

A more effective way will be to specify the action in the url. And you can even serve different actions on the same route name or multiple.
config.add_route('serve_route', '/{project_name}/testruns/{testrun_name}/plots/{action}/{plot_name}.png')
views.py
#view_config(context=Root, route_name='serve_route', action='view')
def plot_view(request):
pass
Or with query string
`config.add_route('serve_route',
'/{project_name}/testruns/{testrun_name}/plots/{plot_name}.png')
views.py
#view_config(context=Root, route_name='serve_route')
def plot_view(request):
try:
action = getattr(self._request.GET, 'action')
except AttributeError:
raise

Related

Pass variable from jinja2 template to python

Sorry if this is a noob question I am still learning. I have passed a variable from python code to a jinja2 HTML template to set up a URL, like this:
Delete
When this link is pressed it should run a query that deletes the entity with that ID. But when the link is pressed it goes to /delete/1827424298 for example, which results in a 404 error as the request handler doesn't exist.
I need to pass that ID back into my python code so it can run a method to delete the entity with that same ID. How do I go about doing this? Using webapp2 if that is important.
class DeleteRequestHandler(webapp2.RequestHandler):
def get():
template = template_env.get_template('myrequests.html')
context = {
'results': results.key.id()
}
self.response.out.write(template.render(context))
EDIT: I've added my delete handler - it is incomplete as I have yet to add the query to delete the entity. My thinking behind it so far is I can grab the results.key.id() from the jinja2 template and put it into results but I am not sure if this would work.
So I think what you're confused about is how to set up a route handler with a dynamic part to the URL. It's a shame that this is completely skipped over in the webapp2 tutorial, as it's a fundamental part of writing any web application. However, it is covered well in the guide to routing, which you should read.
At its simplest, it's just a matter of putting a regex in the route:
app = webapp2.WSGIApplication([
...
(r'/delete/(\d+)', MyDeleteHandler),
])
which will now route any URL of the form /delete/<number>/ to your deletion handler.
The ID that you pass in the URL will be the first positional argument to the handler method:
class MyDeleteHandler:
def get(self, item_id):
key = ndb.Key(MyModel, item_id) # or whatever

How do I make my post form handling more flexible

I have written what I hope to be a re-usable Django app, but I have a bit of a conundrum on how to make the post form handling flexible. The simplified version of my view code looks like:
def do_form(request, entity_id, template_name, success_url):
form = MyForm(request.POST or None)
if request.method =='POST':
if form.is_valid():
#do some business logic
return HttpResponseRedirect(finished_url)
return render_to_response(template_name,
{'form': form},
context_instance=RequestContext(request))
I have followed the advice in James Bennets book "Practical Django Projects" and so you can now configure the template and the success url in the url conf, so for example my url conf could look like this:
urlpatterns = patterns('myapp.views',
url(r'^do/(?P<entity_id>\d+)/$',
view = 'do_form',
name = 'do_form_view',
kwargs={'template_name':'form.html',
'success_url':'/finish/'},),
url(r'^finish/$',
view = 'finish',
name = 'finish_view')
)
This is all very well and good but when I have come to use this in my real world application I find myself in a situation that this form sits in the middle of some workflow, and I want the success url to be something like /continue/<workflow_id>/ , and the problem is that you can only have a hardcoded url in the url conf, and the workflow_id will vary every time I hit the do_form code.
Can any one suggest a way to get around this?
You can achieve that by changing the following..
in do_form() in views.py
change the return HttpResponseRedirect to
return HttpResponseRedirect('/continue/%s' %(workflowid))
And in urls.py, you can have
url(r'^continue/(?P<workflowid>\d+)/$',
view = 'continue',
name = 'continue_view')
and for the continue() view in views.py
def continue(request, workflowid=None):
...
This way.. whenever you access the url /continue/ without a number, workflowid will be equal to None. Every other time when you do have a workflowid attached for e.g. like /continue/23/ , then inside your continue() view you can access that id through the variable workflowid.
When you pass a hypothethical "flexible" success_url to a view, that view MUST supply the desired identifier. So if you mismatch the URL and the view, we can't avoid having a "breach of contract" between the two.
Therefore if we are to have flexible URLs, some kind of contract shall have to be enforced, and there will be no loss of generality if we do this through a special syntax for URLs:
'finished_url': '/finish/<workflow_id>/'
Then, of course, the view shall have to instantiate the variable through a string replacement to honor its side of the contract: instead of
return HttpResponseRedirect(finished_url)
you will have
return HttpResponseRedirect(finished_url.replace('<workflow_id>', WorkflowID))
This should keep things reasonably simple.
When reusing code, you will have to keep in mind that <workflow_id> is whatever that app uses to call workflow id, and that's why I use a complicated string such as workflow_id instead of id or maybe $1.
EDIT: I was going to add the code for the next step (intercepting workflow ID in argument of finish), but I see that keithxm23 beat me to the punch :-)
You can do it the same way people have been "overriding" Django's function-based generic views for years: simply wrap the view in another view:
def custom_do_form(request, entity_id, template_name, success_url):
template_name = some_method_to_get_template()
return do_form(request, entity_id, template_name, success_url)

How to add conditional redirect to each view in Pyramid?

I'd like to check a condition before each request and call different views.
How is that achieved?
One solution I can think of is adding something to subscriber NewRequest, but I am stuck:
#subscriber(NewRequest)
def new_request_subscriber(event):
if condition:
#what now?
#subscriber(NewRequest)
def new_request_subscriber(event):
if condition:
raise pyramid.httpexceptions.HTTPFound(location=somelocation) ## to issue a proper redirect
More info can be found here:
http://pyramid.readthedocs.org/en/latest/api/httpexceptions.html#pyramid.httpexceptions.HTTPFound
Well you've given very little information about the "condition" or what you mean by "call different views", so I'll assume you do not want to invoke a redirect but instead you want the application to think a different URL is being requested. To do that you can look at pyramid_rewrite, which is pretty handy for these things, or you can just change the request's path within the NewRequest subscriber, since it is invoked before Pyramid dispatches to a view.
if request.path == '/foo':
request.path = '/bar':
config.add_route('foo', '/foo') # never matches
config.add_route('bar', '/bar')
Yet another option to "check a condition ... and call different views" is to use custom view predicates
From Cris McDonough's blog post:
def example_dot_com_host(info, request):
if request.host == 'www.example.com:
return True
That's a custom predicate there. It returns True if the hostname is www.example.com. Here's how we use it:
#view_config(route_name='blogentry', request_method='GET')
def get_blogentry(request):
...
#view_config(route_name='blogentry', request_method='POST')
def post_blogentry(request):
...
#view_config(route_name='blogentry', request_method='GET',
custom_predicates=(example_dot_com_host,))
def get_blogentry_example_com(request):
...
#view_config(route_name='blogentry', request_method='POST',
custom_predicates=(example_dot_com_host,))
def post_blogentry_example_com(request):
...
However, for your particular problem (display a sign in page if user has no permission to view the page) a better way to achieve this would be to set up permissions for views so the framework raises an exception when user has no permission, and then register a custom view for that exception which will show a sign in form.

Defining Constants in Django

I want to have some constants in a Django Projects. For example, let's say a constant called MIN_TIME_TEST.
I would like to be able to access this constant in two places: from within my Python code, and from within any Templates.
What's the best way to go about doing this?
EDIT:
To clarify, I know about Template Context Processors and about just putting things in settings.py or some other file and just importing.
My question is, how do I combine the two approaches without violating the "Don't Repeat Yourself" rule? Based on the answers so far, here's my approach:
I'd like to create a file called global_constants.py, which will have a list of constants (things like MIN_TIME_TEST = 5). I can import this file into any module to get the constants.
But now, I want to create the context processor which returns all of these constants. How can I go about doing this automatically, without having to list them again in a dictionary, like in John Mee's answer?
Both Luper and Vladimir are correct imho but you'll need both in order to complete your requirements.
Although, the constants don't need to be in the settings.py, you could put them anywhere and import them from that place into your view/model/module code. I sometimes put them into the __init__.py if I don't care to have them to be considered globally relevant.
a context processor like this will ensure that selected variables are globally in the template scope
def settings(request):
"""
Put selected settings variables into the default template context
"""
from django.conf import settings
return {
'DOMAIN': settings.DOMAIN,
'GOOGLEMAPS_API_KEY': settings.GOOGLEMAPS_API_KEY,
}
But this might be overkill if you're new to django; perhaps you're just asking how to put variables into the template scope...?
from django.conf import settings
...
# do stuff with settings.MIN_TIME_TEST as you wish
render_to_response("the_template.html", {
"MIN_TIME_TEST": settings.MIN_TIME_TEST
}, context_instance=RequestContext(request)
To build on other people's answers, here's a simple way you'd implement this:
In your settings file:
GLOBAL_SETTINGS = {
'MIN_TIME_TEST': 'blah',
'RANDOM_GLOBAL_VAR': 'blah',
}
Then, building off of John Mee's context processor:
def settings(request):
"""
Put selected settings variables into the default template context
"""
from django.conf import settings
return settings.GLOBAL_SETTINGS
This will resolve the DRY issue.
Or, if you only plan to use the global settings occasionally and want to call them from within the view:
def view_func(request):
from django.conf import settings
# function code here
ctx = {} #context variables here
ctx.update(settings.GLOBAL_SETTINGS)
# whatever output you want here
Consider putting it into settings.py of your application. Of course, in order to use it in template you will need to make it available to template as any other usual variable.
Use context processors to have your constants available in all templates (settings.py is a nice place to define them as Vladimir said).
Context processors are better suited at handling more dynamic object data--they're defined as a mapping in the documentation and in many of the posts here they're being modified or passed around to views--it doesn't make sense that a template may lose access to global information because, for example, your forgot to use a specialized context processor in the view. The data is global by definition & that couples the view to the template.
A better way is to define a custom template tag. This way:
templates aren't relying on views to have global information passed into them
it's DRY-er: the app defining the global settings can be exported to many projects, eliminating common code across projects
templates decide whether they have access to the global information, not the view functions
In the example below I deal with your problem--loading in this MIN_TIME_TEST variable--and a problem I commonly face, loading in URLs that change when my environment changes.
I have 4 environments--2 dev and 2 production:
Dev: django-web server, url: localhost:8000
Dev: apache web server: url: sandbox.com -> resolves to 127.0.0.1
Prod sandbox server, url: sandbox.domain.com
Prod server: url: domain.com
I do this on all my projects & keep all the urls in a file, global_settings.py so it's accessible from code. I define a custom template tag {% site_url %} that can be (optionally) loaded into any template
I create an app called global_settings, and make sure it's included in my settings.INSTALLED_APPS tuple.
Django compiles templated text into nodes with a render() method that tells how the data should be displayed--I created an object that renders data by returnning values in my global_settings.py based on the name passed in.
It looks like this:
from django import template
import global_settings
class GlobalSettingNode(template.Node):
def __init__(self, settingname):
self.settingname = settingname;
def render(self, context):
if hasattr(global_settings, self.settingname):
return getattr(global_settings, self.settingname)
else:
raise template.TemplateSyntaxError('%s tag does not exist' % self.settingname)
Now, in global_settings.py I register a couple tags: site_url for my example and min_test_time for your example. This way, when {% min_time_test %} is invoked from a template, it'll call get_min_time_test which resolves to load in the value=5. In my example, {% site_url %} will do a name-based lookup so that I can keep all 4 URLs defined at once and choose which environment I'm using. This is more flexible for me than just using Django's built in settings.Debug=True/False flag.
from django import template
from templatenodes import GlobalSettingNode
register = template.Library()
MIN_TIME_TEST = 5
DEV_DJANGO_SITE_URL = 'http://localhost:8000/'
DEV_APACHE_SITE_URL = 'http://sandbox.com/'
PROD_SANDBOX_URL = 'http://sandbox.domain.com/'
PROD_URL = 'http://domain.com/'
CURRENT_ENVIRONMENT = 'DEV_DJANGO_SITE_URL'
def get_site_url(parser, token):
return GlobalSettingNode(CURRENT_ENVIRONMENT)
def get_min_time_test(parser, token):
return GlobalSettingNode('MIN_TIME_TEST')
register.tag('site_url', get_site_url)
register.tag('min_time_test', get_min_time_test)
Note that for this to work, django is expecting global_settings.py to be located in a python packaged called templatetags under your Django app. My Django app here is called global_settings, so my directory structure looks like:
/project-name/global_settings/templatetags/global_settings.py
etc.
Finally the template chooses whether to load in global settings or not, which is beneficial for performance. Add this line to your template to expose all the tags registered in global_settings.py:
{% load global_settings %}
Now, other projects that need MIN_TIME_TEST or these environments exposed can simply install this app =)
In the context processor you can use something like:
import settings
context = {}
for item in dir(settings):
#use some way to exclude __doc__, __name__, etc..
if item[0:2] != '__':
context[item] = getattr(settings, item)
Variant on John Mee's last part, with a little elaboration on the same idea Jordan Reiter discusses.
Suppose you have something in your settings akin to what Jordan suggested -- in other words, something like:
GLOBAL_SETTINGS = {
'SOME_CONST': 'thingy',
'SOME_OTHER_CONST': 'other_thingy',
}
Suppose further you already have a dictionary with some of the variables you'd like to pass your template, perhaps passed as arguments to your view. Let's call it my_dict. Suppose you want the values in my_dict to override those in the settings.GLOBAL_SETTINGS dictionary.
You might do something in your view like:
def my_view(request, *args, **kwargs)
from django.conf import settings
my_dict = some_kind_of_arg_parsing(*args,**kwargs)
tmp = settings.GLOBAL_SETTINGS.copy()
tmp.update(my_dict)
my_dict = tmp
render_to_response('the_template.html', my_dict, context_instance=RequestContext(request))
This lets you have the settings determined globally, available to your templates, and doesn't require you to manually type out each of them.
If you don't have any additional variables to pass the template, nor any need to override, you can just do:
render_to_response('the_template.html', settings.GLOBAL_SETTINGS, context_instance=RequestContext(request))
The main difference between what I'm discussing here & what Jordan has, is that for his, settings.GLOBAL_SETTINGS overrides anything it may have in common w/ your context dictionary, and with mine, my context dictionary overrides settings.GLOBAL_SETTINGS. YMMV.

Custom traversal and page templates

Using Marius Gedminas's excellent blog post, I have created a custom traverser for a folder in my site.
This allows me to show: http://foo.com/folder/random_id
Instead of: http://foo.com/folder/object.html?id=random_id
The configuration side works great, I can catch the random_ids and search through my messages for the correct one, ready to display.
My problem is that I'm unsure how to then display the data via my usual page templates - at the TODO point in his original code ;)
if name == 'mycalendar':
mycalendar = ... # TODO: do something to get the appropriate object
return mycalendar
Usually I'd use something similar to:
class Test(BrowserPage):
template = ViewPageTemplateFile('atest.pt')
def __call__(self):
return self.template()
But I can't work out how to do this correctly in the context of the custom traversal.
UPDATE: To be clear I want to avoid adding anything else to the url (No: http://foo.com/folder/random_id/read).
I don't need the view to be available via any other address (No: http://foo.com/folder/read)
The ZCML for the view I'd like to use is:
<browser:page
for="foo.interfaces.IFooFolderContainer"
name="read"
template="read.pt"
permission="zope.ManageContent"
/>
I'm guessing (on further advice), something along the lines of:
return getMultiAdapter((mycalendar, self.request), IPageTemplate, name=u'read')
Or even a default view for the object type (a dict in this case) that's being returned:
<browser:page
for="dict"
name="read"
template="read.pt"
permission="zope.ManageContent"
/>
It would be easier to answer your question if you showed what your custom traverser is doing.
Essentially, you want something like this:
def publishTraverse(self, request, name):
if name in self.context:
return MyMessageView(self.context[name], request)
# fall back to views such as index.html
view = queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view
# give up and return a 404 Not Found error page
raise NotFound(self.context, name, request)
where MyMessageView can be something as simple as
class MyMessageView(BrowserPage):
__call__ = ViewPageTemplateFile('read.pt')
Disclaimer: I'm not sure if the view you instantiate directly will be protected by a security wrapper; make sure your functional tests ensure anonymous users can't view messages if that's what you want.
If you end up at a proper object with your custom traverser, you can just tack on the template name and user "context" in that template. So http://foo.com/folder/random_id/my_template and in the template do the normal <h1 tal:content="context/title" /> stuff.
IIUC, what you want is to render the 'read' view when somebody requests /folder/random_id. If that's the case, all you need to do is make your traversal return an object (IFolderContent, maybe) representing a random_id and specify the 'view' page as the defaultView for IFolderContent.
The defaultView is needed because there's no view specified for the random_id object in your URL.

Categories