Django: Passing data to view from url dispatcher without including the data in the url? - django

I've got my mind set on dynamically creating URLs in Django, based on names stored in database objects. All of these pages should be handled by the same view, but I would like the database object to be passed to the view as a parameter when it is called. Is that possible?
Here is the code I currently have:
places = models.Place.objects.all()
for place in places:
name = place.name.lower()
urlpatterns += patterns('',
url(r'^'+name +'/$', 'misc.views.home', name='places.'+name)
)
Is it possible to pass extra information to the view, without adding more parameters to the URL? Since the URLs are for the root directory, and I still need 404 pages to show on other values, I can't just use a string parameter. Is the solution to give up on trying to add the URLs to root, or is there another solution?
I suppose I could do a lookup on the name itself, since all URLs have to be unique anyway. Is that the only other option?

I think you can pass a dictionary to the view with additional attributes, like this:
url(r'^'+name +'/$', 'misc.views.home', {'place' : place}, name='places.'+name)
And you can change the view to expect this parameter.

That's generally a bad idea since it will query the database for every request, not only requests relevant to that model. A better idea is to come up with the general url composition and use the same view for all of them. You can then retrieve the relevant place inside the view, which will only hit the database when you reach that specific view.
For example:
urlpatterns += patterns('',
url(r'^places/(?P<name>\w+)/$', 'misc.views.home', name='places.view_place')
)
# views.py
def home(request, name):
place = models.Place.objects.get(name__iexact=name)
# Do more stuff here
I realize this is not what you truly asked for, but should provide you with much less headaches.

Related

Django context processors and URL arguments

I have some code that is repeated at the start of my Django views. It basically just adds some variables to the context, but based on the URL argument, e.g.
def someView(request, id):
target = Target.objects.get(id=id)
# name will be added to ctx
name = target.name
(there are more attributes added and other attributes from related models, but this gives the general idea --- There are quite a few lines of repeat code at the start of each view)
I thought I could make my code more DRY by taking advantage of Django's context processors, but it would seem these don't access to the URL arguments?
Is there another way to avoid these repeat lines? Maybe middleware or something else?
You can access the URL parameters via request through the resolver_match attribute. So for instance you can do request.resolver_match.kwargs['id'] to get the ID kwarg.

Can I pass non-URL definition view keyword to a view function when redirecting?

NoReverseMatch at /natrium/script/4c55be7f74312bfd435e4f672e83f44374a046a6aa08729aad6b0b1ab84a8274/
Reverse for 'run_details' with arguments '()' and keyword arguments '{'script_text': u'print "happy"', 'run_id': '6b2f9127071968c099673254fb3efbaf'}' not found.
This is an excerpt of my views.py
run_id = new_run.run_id
if not run_id:
raise AssertionError("bad run id")
# I tried with args=[run_id, clean['script_text']] too
return HttpResponseRedirect(reverse('run_details', kwargs={'run_id':run_id, 'script_text':clean['script_text']}))
which in turns calling this view function
def run_details(request, run_id, script_text):
"""
Displays the details of a given run.
"""
run = Run(run_id)
run.update(request.user)
codebundle = CodeBundle(run.cbid)
codebundle.update(request.user)
return render_response(request, "graphyte/runs/run_script.html",
{'run':run, 'codebundle':codebundle, 'files':run.artifacts, 'bundle':codebundle,
'source_code': script_text
})
Now this is my urls.py. The actual redirect views is in another app (kinda insane, but whatever...).
urlpatterns = patterns("webclient.apps.codebundles.views",
# many.....
url(r"^cb/newfolder/$", 'codebundle_newfolder', name="codebundle_newfolder"),
)
urlpatterns += patterns('webclient.apps.runs.views',
url(r"^run_details/(?P<run_id>\w+)/$", 'run_details', name="run_details"),)
This is getting really nasty for the last three hours. I am not sure what's going on. Can someone help me debug this?
Thanks.
The original plan did not have script_text, and I used args=['run_id'] only. It works. In other words, remove script_text from the two views everything will work.
EDIT
Sorry for the confusion. Script text is just a context variable that I need to pass to the reverse destination, and from there I render my template. The URLs should only display the run_id.
No, you can't really pass an 'extra keyword' to the view function when redirecting. I'll try to explain why.
When you return HttpResponseRedirect, Django returns a response with a 302 status code, and the new location.
HTTP/1.1 302 Found
Location: http://www.example.com/new-url/
Your browser will then usually fetch the new url, but that's a separate request. If your view needs a keyword, it needs to be included in that response somehow, unless you store state in the session. Your two options are
Include the extra keyword in the url:
http://www.example.com/new-url/keyword-value/
Include the extra keyword as a GET parameter
http://www.example.com/new-url/?keyword=keyword-value.
Then in your view, grab the keyword with keyword=request.GET['keyword']. Note that the keyword is no longer a kwarg in the view signature.
A third approach is to stick the keyword into the session before you redirect, then grab it out the session in the redirected view. I would advise against doing this because it's stateful and can cause odd results when users refresh pages etc.
Your run_details url doesn't accept a kwarg named script_text at all -- remove that from your reverse kwargs.

Django routes - check route from DB

Morning,
With my django website, I want to make the urls as short as possible.
So instead of,
/user/john
/user/ronald
I just want it like /john and /ronald
So in my routes I have it configured that all the requests go to one
urlpatterns = patterns('',
....
(r'^about/$', 'frontend.views.about'),
(r'^(.*?)/$', 'users.views.index')
)
which means basically that all requests will be handled by the users controller if not handled else where, which isn't to bad.
But I want to do the same cakes.
so instead of /cakes/chocolate-coated-cake just have /chocolate-coated-cake
So really, it'd be nice if in my users method, instead of raising a 404 I could just some how call try next route, so it's conditional on a DB field.
Make sense?
In this case I'll prefer to have a separate dispatcher view (not related to users because it is not about users). There you could set up order of models in a list and iterate through it until the first sucess and call the appropriate view (user view, cake view) with this result as an argument).
P.S. Hope that you will not have a user and a cake with the same name :)

Decoupling django apps 2 - how to get object information from a slug in the URL

I am trying to de-couple two apps:
Locations - app containing details about some location (town, country, place etc)
Directory - app containing details of places of interest (shop, railway station, pub, etc) - all categorised.
Both locations.Location and directory.Item contain lat/lng coords and I can find items within a certain distance of a specific lat/lng coord.
I'd like to use the following URL structure:
/locations/<location_slug>/directory/<category_slug>/
But I don't want to make my directory app reliant on my location app.
How can I translate this url to use a view like this in my directory app?
items_near(lat, lng, distance, category):
A work around would be to create a new view somewhere that translates this - but where should I put that? if it goes in the Directory app, then I've coupled that with my Location app, and vice versa.
Would a good idea be to place this workaround code inside my project URLs file? Thus keeping clear of both apps? Any issues with doing it like this?
For your urlpattern to work, the view function invoked has to be aware of both Locations and Directories. The short answer is you can put this view function anywhere you want - it's just a python function. But there might be a few logical places for it, outside of your Directory or Location app, that make sense.
First off, I would not put that view code in in your top-level urls.py, as that file is intended for URLconf related code.
A few options of where to put your view:
Create a new view function in a file that lives outside of any particular app. <project_root>/views.py is one possibility. There is nothing wrong with this view calling the item_near(..) view from the Directory app.
# in myproject/urls.py
urlpatterns = (
...
(r'/locations/(?P<location_slug>[\w\-]+)/directory/(?P<category_slug>[\w\-]+)/',
'myproject.views.items_near_from_slug')
)
# in myproject/views.py
from directory.views import items_near
def items_near_from_slug(request, location_slug, category_slug):
location = get_object_or_404(Location, slug=location_slug)
distance = 2 # not sure where this comes from
# And then just invoke the view from your Directory app
return items_near(request, location.lat, location.lng, distance, category_slug)
Create a new app and put the code there, in <my_new_app>/views.py. There is no requirement that a Django app need to have a models.py, urls.py, etc. Just make sure to include the __init__.py if you want Django to load the app properly (if, for instance, you want Django to automatically find templatetags or app specific templates).
Personally, I would go with option 1 only if the project is relatively simple, and <project_root>/views.py is not in danger of becoming cluttered with views for everything. I would go with option 2 otherwise, especially if you anticipate having other code that needs to be aware of both Locations and Directories. With option 2, you can collect the relevant urlpatterns in their own app-specific urls.py as well.
From the django docs here if you're using django >=1.1 then you can pass any captured info into the included application. So splitting across a few files:
# in locations.urls.py
urlpatterns = ('',
(r'location/(?P<location_slug>.*?)/', include('directory.urls')),
#other stuff
)
# in directory.urls.py
urlpatterns = ('',
(r'directory/(?P<directory_slug>.*?)/', 'directory.views.someview'),
#other stuff
)
# in directory.views.py
def someview(request, location_slug = None, directory_slug = None):
#do stuff
Hope that helps. If you're in django < 1.1 I have no idea.
Irrespective of how much ever "re-usable" you make your app, inevitably there is a need for site-specific code.
I think it is logical to create a "site-specific" application that uses the views of the reusable and decoupled apps.

Adding more CoC to Django

I come from a Cake background, and I'm just starting to learn Django now. I'm liking it quite a bit, but I kinda wish it used convention over configuration like cake does. So,
How can I get Cake-style URLs automatically? For example, if I went to mysite.com/posts/view/5 it would load up mysite.posts.views.view and pass an argument 5 to it? I was thinking I could add something like (r'^(.*)/(.*)', 'mysite.$1.$2'), to urls.py, but of course, that won't work.
How can I automatically load up a template? Each view function should automatically load a template like templates/posts/view.html.
Is this even possible, or do I have to hack the core of Django?
Here's my solution, based on what Carl suggested:
urlpatterns = patterns('',
# url pats here
url(r'^(?P<app>\w+)/(?P<view>\w+)/(?P<args>.*)$', 'urls.dispatch')
)
def dispatch(req, app, view, args): # FIXME: ignores decorators on view func!
func = get_callable(app+'.views.'+view)
if args:
ret = func(req, *args.split('/'))
else:
ret = func(req)
if type(ret) is dict:
return render_to_response(app+'/'+view+'.html', ret)
else:
return ret
Seems to be working pretty well with initial tests. Solves both problems with a single function. Probably won't support GET-style arguments tho.
Those points are both implementable without hacking Django core, but either one will require a non-trivial level of familiarity with advanced Python techniques.
You can do the generic URL pattern with a pattern like this:
url(r'^(?P<appname>\w+)/(?P<viewfunc>\w+)/(?P<args>.*)$', 'myresolverfunc')
Then define a 'myresolverfunc' "view" function that takes "appname", "viewfunc", and "args" parameters, and implement whatever logic you want, splitting args on "/" and dynamically importing and dispatching to whatever view function is referenced. The trickiest part is the dynamic import, you can search Django's source for "importlib" to see how dynamic imports are done internally various places.
The automatic template loader can be implemented as a view function decorator similar to the various "render_to" decorators out there, except you'll generate the template name rather than passing it in to the decorator. You'll have to introspect the function object to get its name. Getting the app name will be trickier; you'll probably just want to hardcode it as a module-level global in each views.py file, or else work in conjunction with the above URL dispatcher, and have it annotate the request object with the app name or some such.
I don't you'll need to hack the core of Django for this. It sounds like you might be in need of generic views. Also check out the Generic Views topic guide.
The first example given in the generic views documentation sounds like your first bullet point:
Example:
Given the following URL patterns:
urlpatterns = patterns('django.views.generic.simple',
(r'^foo/$', 'direct_to_template', {'template':'foo_index.html'}),
(r'^foo/(?P<id>\d+)/$', 'direct_to_template', {'template':'foo_detail.html'}),
)
... a request to /foo/ would render the template foo_index.html, and a request to /foo/15/ would render the foo_detail.html with a context variable {{ params.id }} that is set to 15.