Unable to reverse view in middleware - django

I'm trying to do catch a missing variable in a Django rotue with middleware - however I am unable to reverse the URL as Django cannot find the view (even though it exists in urlconf). For example:
With this route:
# matches /test and /game/test
url(r'^((?P<game>[A-Za-z0-9]+)/)?test', 'hyp.views.test'),
I am trying to detect if the game part is not given, and redirect in that case with middleware:
class GameMiddleware:
def process_view(self, request, view_func, view_args, view_kwargs):
if 'game' in view_kwargs:
game = view_kwargs['game']
if game is None:
# As a test, attempt to resolve the url
# Correctly finds ResolverMatch for hyp.views.test, game=TestGame
print resolve('/TestGame/test', urlconf=request.urlconf)
# Fails with "Reverse for 'hyp.views.test' with arguments '()'
# and keyword arguments '{'game': 'TestGame'}' not found."
return HttpResponseRedirect(reverse(
request.resolver_match.url_name, # 'hyp.views.test'
urlconf=request.urlconf,
kwargs={'game': 'TestGame'}
))
return None
request.urlconf does contain the test url:
{ '__name__': 'urlconf', '__doc__': None, 'urlpatterns': [
<RegexURLPattern None ^$>,
<RegexURLPattern None ^((?P<game>[A-Za-z0-9]+)/)?test>
], '__package__': None }
The only thing I can think of is that the URL-rewriter might not be able to deal with the regex containing optional parts - would a better solution be to create separate views for these cases (I'm going to have a lot of views with optional game params) or can I fix it?
Update
I managed to get it to work by removing the wrapping brackets in the route (so it reads r'^(?P<game>[A-Za-z0-9]+/)?test' and by passing 'TestGame/' as the game - however this isn't ideal as I have to call game.rstrip('/') each time (although only in the middleware). It's also difficult to use {% url %} tags as a name ending with / is expected.
Leaving this open in case someone has a better solution.

Thanks to Todd's answer on another question, I found a clean method of doing this: define two routes (one with the game and one without), specifying game as None in the route without the pattern:
url(r'^test$', 'hyp.views.test', {'game': None}),
url(r'^(?P<game>[A-Za-z0-9]+)/test', 'hyp.views.test'),
This triggers the middleware's if game is None part correctly and also allows games to be specified without trailing slashes.

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: creating/modifying the request object

I'm trying to build an URL-alias app which allows the user create aliases for existing url in his website.
I'm trying to do this via middleware, where the request.META['PATH_INFO'] is checked against database records of aliases:
try:
src: request.META['PATH_INFO']
alias = Alias.objects.get(src=src)
view = get_view_for_this_path(request)
return view(request)
except Alias.DoesNotExist:
pass
return None
However, for this to work correctly it is of life-importance that (at least) the PATH_INFO is changed to the destination path.
Now there are some snippets which allow the developer to create testing request objects (http://djangosnippets.org/snippets/963/, http://djangosnippets.org/snippets/2231/), but these state that they are intended for testing purposes.
Of course, it could be that these snippets are fit for usage in a live enviroment, but my knowledge about Django request processing is too undeveloped to assess this.
Instead of the approach you're taking, have you considered the Redirects app?
It won't invisibly alias the path /foo/ to return the view bar(), but it will redirect /foo/ to /bar/
(posted as answer because comments do not seem to support linebreaks or other markup)
Thank for the advice, I have the same feeling regarding modifying request attributes. There must be a reason that the Django manual states that they should be considered read only.
I came up with this middleware:
def process_request(self, request):
try:
obj = A.objects.get(src=request.path_info.rstrip('/')) #The alias record.
view, args, kwargs = resolve_to_func(obj.dst + '/') #Modified http://djangosnippets.org/snippets/2262/
request.path = request.path.replace(request.path_info, obj.dst)
request.path_info = obj.dst
request.META['PATH_INFO'] = obj.dst
request.META['ROUTED_FROM'] = obj.src
request.is_routed = True
return view(request, *args, **kwargs)
except A.DoesNotExist: #No alias for this path
request.is_routed = False
except TypeError: #View does not exist.
pass
return None
But, considering the objections against modifying the requests' attributes, wouldn't it be a better solution to just skip that part, and only add the is_routed and ROUTED_TO (instead of routed from) parts?
Code that relies on the original path could then use that key from META.
Doing this using URLConfs is not possible, because this aliasing is aimed at enabling the end-user to configure his own URLs, with the assumption that the end-user has no access to the codebase or does not know how to write his own URLConf.
Though it would be possible to write a function that converts a user-readable-editable file (XML for example) to valid Django urls, it feels that using database records allows a more dynamic generation of aliases (other objects defining their own aliases).
Sorry to necro-post, but I just found this thread while searching for answers. My solution seems simpler. Maybe a) I'm depending on newer django features or b) I'm missing a pitfall.
I encountered this because there is a bot named "Mediapartners-Google" which is asking for pages with url parameters still encoded as from a naive scrape (or double-encoded depending on how you look at it.) i.e. I have 404s in my log from it that look like:
1.2.3.4 - - [12/Nov/2012:21:23:11 -0800] "GET /article/my-slug-name%3Fpage%3D2 HTTP/1.1" 1209 404 "-" "Mediapartners-Google
Normally I'd just ignore a broken bot, but this one I want to appease because it ought to better target our ads (It's google adsense's bot) resulting in better revenue - if it can see our content. Rumor is it doesn't follow redirects so I wanted to find a solution similar to the original Q. I do not want regular clients accessing pages by these broken urls, so I detect the user-agent. Other applications probably won't do that.
I agree a redirect would normally be the right answer.
My (complete?) solution:
from django.http import QueryDict
from django.core.urlresolvers import NoReverseMatch, resolve
class MediapartnersPatch(object):
def process_request(self, request):
# short-circuit asap
if request.META['HTTP_USER_AGENT'] != 'Mediapartners-Google':
return None
idx = request.path.find('?')
if idx == -1:
return None
oldpath = request.path
newpath = oldpath[0:idx]
try:
url = resolve(newpath)
except NoReverseMatch:
return None
request.path = newpath
request.GET = QueryDict(oldpath[idx+1:])
response = url.func(request, *url.args, **url.kwargs)
response['Link'] = '<%s>; rel="canonical"' % (oldpath,)
return response

Reverse Not Found: Sending Request Context in from templates

N.B This question has been significantly edited before the first answer was given.
Hi,
I'm fairly new to django, so apologies if I'm missing something obvious.
I've got a urls.py file that looks like this:
urlpatterns = patterns(
'',
(r'^$', 'faros.lantern.views.home_page'),
(r'^login/$', 'django.contrib.auth.views.login'),
(r'^logout/$', 'django.contrib.auth.views.logout'),
(r'^about/$', 'faros.lantern.views.about_page_index', {}, 'about_page_index'),
(r'^about/(?P<page_id>([a-z0-9]+/)?)$', 'faros.lantern.views.about_page', {}, 'about_page'),
)
Views that looks like this:
def about_page_index(request):
try:
return render_to_response('lantern/about/index.html', context_instance=RequestContext(request))
except TemplateDoesNotExist:
raise Http404
def about_page(request, page_id):
page_id = page_id.strip('/ ')
try:
return render_to_response('lantern/about/' + page_id + '.html', context_instance=RequestContext(request))
except TemplateDoesNotExist:
raise Http404
And a template that includes this:
Contact
Contact
I'm getting this error message:
Caught an exception while rendering: Reverse for '<function about_page at 0x015EE730>' with arguments '()' and keyword arguments '{'page_id': u'contact'}' not found. The first reverse works fine (about_page_index), generating the correct URL without error messages.
I think this is because the request argument to the about_page view (request) is used, so I need to pass it in when I generate the URL in my template. Problem is, I don't know how to get to it, and searching around isn't getting me anywhere. Any ideas?
Thanks,
Dom
p.s. As an aside, does that method of handling static "about" type pages in an app look horrific or reasonable? I'm essentially taking URLs and assuming the path to the template is whatever comes after the about/ bit. This means I can make the static pages look like part of the app, so the user can jump into the about section and then right back to where they came from. Comments/Feedback on whether this is djangoic or stupid appreciated!
If I guess correctly from the signature of your view function (def about_page(request, page_id = None):), you likely have another URL configuration that points to the same view but that does not take a page_id parameter. If so, the django reverse function will see only one of these, and it's probably seeing the one without the named page_id regex pattern. This is a pretty common gotcha with reverse! :-)
To get around this, assign a name to each of the url patterns (see Syntax of the urlpatterns variable). In the case of your example, you'd do:
(r'^about/(?P<page_id>([a-z]+/)?)$', 'faros.lantern.views.about_page',
{}, 'about_with_page_id')
and then in the template:
Contact
Edit
Thanks for posting the updated urls.py. In the url template tag, using the unqualified pattern name should do the trick (note that I'm deleting the lantern.views part:
Contact
Contact
Edit2
I'm sorry I didn't twig to this earlier. Your pattern is expressed in a way that django can't reverse, and this is what causes the mismatch. Instead of:
r'^about/(?P<page_id>([a-z]+/)?)$'
use:
r'^about/(?P<page_id>[a-z0-9]+)/$'
I created a dummy project on my system that matched yours, reproduced the error, and inserted this correction to success. If this doesn't solve your problem, I'm going to eat my hat! :-)

Capturing URL parameters in request.GET

I am currently defining regular expressions in order to capture parameters in a URL, as described in the tutorial. How do I access parameters from the URL as part the HttpRequest object?
My HttpRequest.GET currently returns an empty QueryDict object.
I'd like to learn how to do this without a library, so I can get to know Django better.
When a URL is like domain/search/?q=haha, you would use request.GET.get('q', '').
q is the parameter you want, and '' is the default value if q isn't found.
However, if you are instead just configuring your URLconf**, then your captures from the regex are passed to the function as arguments (or named arguments).
Such as:
(r'^user/(?P<username>\w{0,50})/$', views.profile_page,),
Then in your views.py you would have
def profile_page(request, username):
# Rest of the method
To clarify camflan's explanation, let's suppose you have
the rule url(regex=r'^user/(?P<username>\w{1,50})/$', view='views.profile_page')
an incoming request for http://domain/user/thaiyoshi/?message=Hi
The URL dispatcher rule will catch parts of the URL path (here "user/thaiyoshi/") and pass them to the view function along with the request object.
The query string (here message=Hi) is parsed and parameters are stored as a QueryDict in request.GET. No further matching or processing for HTTP GET parameters is done.
This view function would use both parts extracted from the URL path and a query parameter:
def profile_page(request, username=None):
user = User.objects.get(username=username)
message = request.GET.get('message')
As a side note, you'll find the request method (in this case "GET", and for submitted forms usually "POST") in request.method. In some cases, it's useful to check that it matches what you're expecting.
Update: When deciding whether to use the URL path or the query parameters for passing information, the following may help:
use the URL path for uniquely identifying resources, e.g. /blog/post/15/ (not /blog/posts/?id=15)
use query parameters for changing the way the resource is displayed, e.g. /blog/post/15/?show_comments=1 or /blog/posts/2008/?sort_by=date&direction=desc
to make human-friendly URLs, avoid using ID numbers and use e.g. dates, categories, and/or slugs: /blog/post/2008/09/30/django-urls/
Using GET
request.GET["id"]
Using POST
request.POST["id"]
Someone would wonder how to set path in file urls.py, such as
domain/search/?q=CA
so that we could invoke query.
The fact is that it is not necessary to set such a route in file urls.py. You need to set just the route in urls.py:
urlpatterns = [
path('domain/search/', views.CityListView.as_view()),
]
And when you input http://servername:port/domain/search/?q=CA. The query part '?q=CA' will be automatically reserved in the hash table which you can reference though
request.GET.get('q', None).
Here is an example (file views.py)
class CityListView(generics.ListAPIView):
serializer_class = CityNameSerializer
def get_queryset(self):
if self.request.method == 'GET':
queryset = City.objects.all()
state_name = self.request.GET.get('q', None)
if state_name is not None:
queryset = queryset.filter(state__name=state_name)
return queryset
In addition, when you write query string in the URL:
http://servername:port/domain/search/?q=CA
Do not wrap query string in quotes. For example,
http://servername:port/domain/search/?q="CA"
def some_view(request, *args, **kwargs):
if kwargs.get('q', None):
# Do something here ..
For situations where you only have the request object you can use request.parser_context['kwargs']['your_param']
You have two common ways to do that in case your URL looks like that:
https://domain/method/?a=x&b=y
Version 1:
If a specific key is mandatory you can use:
key_a = request.GET['a']
This will return a value of a if the key exists and an exception if not.
Version 2:
If your keys are optional:
request.GET.get('a')
You can try that without any argument and this will not crash.
So you can wrap it with try: except: and return HttpResponseBadRequest() in example.
This is a simple way to make your code less complex, without using special exceptions handling.
I would like to share a tip that may save you some time.
If you plan to use something like this in your urls.py file:
url(r'^(?P<username>\w+)/$', views.profile_page,),
Which basically means www.example.com/<username>. Be sure to place it at the end of your URL entries, because otherwise, it is prone to cause conflicts with the URL entries that follow below, i.e. accessing one of them will give you the nice error: User matching query does not exist.
I've just experienced it myself; hope it helps!
These queries are currently done in two ways. If you want to access the query parameters (GET) you can query the following:
http://myserver:port/resource/?status=1
request.query_params.get('status', None) => 1
If you want to access the parameters passed by POST, you need to access this way:
request.data.get('role', None)
Accessing the dictionary (QueryDict) with 'get()', you can set a default value. In the cases above, if 'status' or 'role' are not informed, the values ​​are None.
If you don't know the name of params and want to work with them all, you can use request.GET.keys() or dict(request.GET) functions
This is not exactly what you asked for, but this snippet is helpful for managing query_strings in templates.
If you only have access to the view object, then you can get the parameters defined in the URL path this way:
view.kwargs.get('url_param')
If you only have access to the request object, use the following:
request.resolver_match.kwargs.get('url_param')
Tested on Django 3.
views.py
from rest_framework.response import Response
def update_product(request, pk):
return Response({"pk":pk})
pk means primary_key.
urls.py
from products.views import update_product
from django.urls import path
urlpatterns = [
...,
path('update/products/<int:pk>', update_product)
]
You might as well check request.META dictionary to access many useful things like
PATH_INFO, QUERY_STRING
# for example
request.META['QUERY_STRING']
# or to avoid any exceptions provide a fallback
request.META.get('QUERY_STRING', False)
you said that it returns empty query dict
I think you need to tune your url to accept required or optional args or kwargs
Django got you all the power you need with regrex like:
url(r'^project_config/(?P<product>\w+)/$', views.foo),
more about this at django-optional-url-parameters
This is another alternate solution that can be implemented:
In the URL configuration:
urlpatterns = [path('runreport/<str:queryparams>', views.get)]
In the views:
list2 = queryparams.split("&")
url parameters may be captured by request.query_params
It seems more recommended to use request.query_params. For example,
When a URL is like domain/search/?q=haha, you would use request.query_params.get('q', None)
https://www.django-rest-framework.org/api-guide/requests/
"request.query_params is a more correctly named synonym for request.GET.
For clarity inside your code, we recommend using request.query_params instead of the Django's standard request.GET. Doing so will help keep your codebase more correct and obvious - any HTTP method type may include query parameters, not just GET requests."