This subject is continuation of thinking in this topic. I fell back in problem of reverse in generic views. Last time I thought it's no reverse match because I used many to many, now I haven't many to *** relations in reverse but problem still in there. Since I have generic view in urls in both cases, I suggested the problem is in generic views and no view function.
At first I used #permalink decorator in the models
...
#permalink
def get_absolute_url(self):
return ('categories', str(self.id))
...
#permalink
def get_absolute_url(self):
return ('pages', (), {'page_name': self.human_readable_url})
urls
url(r'^(?P<page_name>&\w*)?/?$', direct_to_template,
{'template': 'basic.djhtml'},
name = "pages"),
url(r'cat/\d+/$',
direct_to_template,
{'template': 'basic.djhtml'},
name = "categories")
And got an error:
NoReverseMatch: Reverse for 'pages' with arguments '()' and keyword arguments '{'page_name': u'page1'}' not found.
Then I tried reverse method
def get_absolute_url(self):
return reverse('categories', args = [self.id, ])
And have the same error
NoReverseMatch: Reverse for 'categories' with arguments '(2,)' and keyword arguments '{}' not found.
Based on the fact that permalink not explicitly use reverse method, I think that the problem is in interaction reverse and generic view in url. Why is it happens? How to use reverse in url generic views?
The problem is, you have given the name categories to a generic view, direct_to_template, and you are passing an argument to that view - but direct_to_template doesn't take that argument, only a dictionary containing extra context.
If you want to pass additional arguments to a generic view, you can - but they will only be passed on to the Template. You can extend the view by writing your own function, which adds the parameter into a dictionary, then calls the generic view. Something like this:
# views.py
from django.views.generic.simple import direct_to_template
def my_view(id):
more_data = {'id': id}
return direct_to_template(template = 'basic.djhtml', more_data)
And then in your urls.py, replace the direct_to_template with my_view. Since my_view takes an id argument, reverse will properly match it, and the argument will be passed in to the generic view, and through to the template.
Presumably somewhere in your template is a line such as, {{ id }}.
Related
I can't use reverse() method in a class to generate url
for example, reverse() doesn't work in generic views or Feed classes (reverse_lazy() should be used instead)
but I can use reverse() in functions. what is the differences ?
take a look at following:
class LatestPostFeed(Feed):
title = 'My Django blog'
# link = reverse_lazy('blog:index')
description = 'New posts of my blog'
def items(self):
return models.Post.published.all()[:5]
def item_title(self, item):
return item.title
def item_description(self, item):
return truncatewords(item.body, 30)
def link(self):
return reverse('blog:index')
the link attribute above only works with reverse_lazy() method.
but the link function works with both reverse_lazy() and reverse() methods
reverse returns string and It's similar to the url template tag which use to convert namespaced url to real url pattern.
reverse_lazy returns object and It's a reverse() function’s lazy version. It’s prevent to occur error when URLConf is not loaded. Generally we use this function in case below:
providing a reversed URL as the url attribute of a generic class-based view.
providing a reversed URL to a decorator (such as the login_url argument for the django.contrib.auth.decorators.permission_required() decorator)
providing a reversed URL as a default value for a parameter in a function’s signature.
I see the following error when trying to render my item_detail template, which uses a url tag in a link to the item_update view:
NoReverseMatch at /mcadb/27/items/17
Reverse for 'item_update' with arguments '()' and keyword arguments '{u'course_id': '', u'pk': 17}' not found. 1 pattern(s) tried: [u'mcadb/(?P<course_id>[0-9]+)/items/(?P<pk>[0-9]+)/update/$']
What is the problem trying to match the pattern tried? Is it because of the u? I'm not sure why that is happening.
In views.py, I try to add 'course_id' to the context for the DetailView. I try to get 'course_id' from the kwargs for the view (I don't know why it's blank)
views.py
class ItemDetailView(DetailView):
DetailView.model=Item
template_name='mcadb/item_detail.html'
def get_context_data(self, **kwargs):
context = super(ItemDetailView, self).get_context_data(**kwargs)
context['course_id'] = self.kwargs['course_id']
return context
urls.py
url(r'^(?P<course_id>[0-9]+)/items/(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='item_detail'),
url(r'^(?P<course_id>[0-9]+)/items/(?P<pk>[0-9]+)/update/$', views.ItemUpdate.as_view(), name='item_update'),
item_detail.html
Edit Item
The problem is with the 'course_id=course_id' line. If I change it to 'course_id=26', item_detail.html renders fine.
I have two questions.
1. what does the error mean, when it looks like I'm passing two kwargs as expected?
2. why does it work if I hardcode a course_id?
Thank you very much, Carrie
I think this is the error:
url(r'^(?P<course_id>[0-9]+)/items/(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='item_detail'),
Note that you are use a generic DetailView (from django.views.generic.DetailView) with this url.
You need to use your own View myapp.views.ItemDetailView.
So in your urls.py file:
from myapp.views import ItemDetailView
url(r'^(?P<course_id>[0-9]+)/items/(?P<pk>[0-9]+)/$', ItemDetailView.as_view(), name='item_detail'),
Explanation for your exact error is here:
Reverse for 'item_update' with arguments '()' and keyword arguments '{u'course_id': '', u'pk': 17}' not found. 1 pattern(s) tried: [u'mcadb/(?P[0-9]+)/items/(?P[0-9]+)/update/$']
As you can see, reverse is getting empty course_id and URL can't be built with empty, because there must be at least 1 number (+ sign in regex pattern). so there is definetly something wrong with passing course_id into context or into url tag. Try to print that variable next to url tag and check it's value. Check if you can access this variable somewhere else in your template (maybe outside of all for loops, includes and with tags) and if you can access it directly from your view (try to create URL here using reverse or just print variable to logs).
I am missing something really basic here.
I am trying to reuse django's change password views. I have following in urls.py:
(r'^change-password/$', 'profile.views.change_password', {},'change_password'),
url(r'^change-password-done/$', 'profile.views.password_change_done', name='django.contrib.auth.views.password_change_done'),
and in corresponding views.py:
from django.contrib.auth.views import password_change, password_change_done
def change_password(request,template_name="password_change_form.html"):
"""Change Password"""
return password_change(request,template_name=template_name)
def password_change_done(request, template_name="password_change_done.html"):
return render_to_response(template_name,(),context_instance= RequestContext(request))
but I am getting following error:
Reverse for
'django.contrib.auth.views.password_change_done'
with arguments '()' and keyword
arguments '{}' not found.
looked at the source and saw this line:
post_change_redirect = reverse('django.contrib.auth.views.password_change_done')
If I change my urls.py entry to following , I do not get the above error:
url(r'^change-password-done/$', 'django.contrib.auth.views.password_change_done', name='anything'),
but I am confused as reverse() should look-up using the "name" parameter? What am I missing here?
I am using django 1.2.3
Josh has the explanation, but you are doing this wrong. If you want to overrride the post_save_redirect, then pass that in as a parameter when you call the view:
def change_password(request,template_name="password_change_form.html"):
return password_change(request, template_name=template_name,
post_save_redirect=reverse('my_done_page'))
See the documentation.
The reverse function doesn't just do lookups on name.
Reverse Documentation.
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
viewname is either the function name (either a function reference, or the string version of the name, if you used that form in urlpatterns) or the URL pattern name.
So, by doing reverse('django.contrib.auth.views.password_change_done'), it will look up that view name within the urls.py regexes, and fallback to looking for the name keyword argument if the viewname doesn't resolve.
I've found very odd thing about using reverse in Django 1.2.1.
I have:
myapp/
views.py
urls.py
in urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('myapp.views',
url(r'^$', 'browse'),
)
in views.py
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
print reverse('myapp.views.browse') # <----- this will print correct value
def browse (request):
print reverse('myapp.views.browse') # <----- this fails with exception
return render_to_response('myapp/browse.html')
When I put reverse method anywhere outside the view method (browse - in this case) I get an exception in every further use of reverse or {% url %} tag.
NoReverseMatch at /
Reverse for 'myapp.views.browse' with arguments '()'
and keyword arguments '{}' not found.
WTF? When I comment/delete the print line outside browse() , second print line inside browse() magically start working!
The most basic case is:
class MyForm(forms.Form):
field = forms.CharField(default=reverse(....))
def some_view(request):
print reverse(...)
....
1) I define a class in main-scope that is initialized when django initialize (and runs reverse)
2) When a request comes the some_view function has been triggered and it evaluates the reverse function again (and fails with exception).
I don't see anything bad at all in this approach. Why not to initialise some values in the django main-scope with results of the reverse() function ?
You will probably need to pass 'request' as the second parameter when calling reverse() from within the view function after it's already been called.
def browse(request):
print reverse('myapp.views.browse', args=[request])
This is odd behavior indeed, but this might possibly be a solution for now.
First, you should be naming your URLs in order to use reverse. That is the correct approach AFAIK.
Second, why are you calling reverse from within a FormField? I really don't get it.
Maybe you could enlighten us by posting the full code rather than a curated set of snippets.
# urls.py
url(r'^/$', 'home_view', name='home'),
url(r'^login/$', 'login_view', name='login'),
# views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
def login_view(request):
# do login stuff and redirect to home
return HttpResponseRedirect(reverse('home'))
def home(request):
# do home stuff
return render_to_response("home.html", locals(), context_instance=RequestContext(request))
The problem is caused by import hell in python. reverse depends on other things and cannot be used while initialization.
The solution is to use lazy reverse. E.g. using this:
http://djangosnippets.org/snippets/499/
Can the object value be looked up and passed to the generic view? Would the generic views need to be moved to views.py in order to support the object lookup?
urls.py
urlpatterns = patterns('myapp.views',
url(r'^mymodel/create/$', view='mymodel_create', name='mymodel_create'),
)
urlpatterns += patterns('django.views.generic',
url(r'^(?P<model>\w+)/create/$','create_update.create_object', name='object_create'),
url(r'^(?P<model>\w+)/(?P<id>\d+)/update/$', 'create_update.update_object', name='object_update' ),
url(r'^(?P<model>\w+)/(?P<id>\d+)/delete/$', 'create_update.delete_object', name='object_delete'), url(r'^(?P<model>\w+)/(?P<object_id>\d+)/$', 'list_detail.object_detail', name='object_detail'),
url(r'^(?P<model>\w+)/$','list_detail.object_list', name='object_list'),'
)
Example URL's:
http://localhost/myapp/thing/create
http://localhost/myapp/thing/1
http://localhost/myapp/thing/1/update
http://localhost/myapp/thing/1/delete
I'd like to use (?P\w+) to find the corresponding Model, 'Thing', but when this code is executed, the following error occurs: object_list() got an unexpected keyword argument 'model'
Yes, you can do this, and I've done it. The Django generic views don't support it directly. You need to wrap the generic view with your own view that looks up the model and constructs the queryset. Here's an example (showing only the detail view, you can do the others similarly). I've changed your named patterns in the URLconf to use "model" instead of "object", which is clearer naming:
in urls.py:
url(r'^(?P<model>\w+)/(?P<object_id>\d+)/$', 'my_app.views.my_object_detail', name='object_detail'),
in my_app/views.py
from django.views.generic.list_detail import object_detail
from django.db.models.loading import get_model
from django.http import Http404
def get_model_or_404(app, model):
model = get_model(app, model)
if model is None:
raise Http404
return model
def my_object_detail(request, model, object_id):
model_class = get_model_or_404('my_app', model)
queryset = model_class.objects.all()
return object_detail(request, queryset, object_id=object_id)
If you're clever and want to keep things as DRY as possible, you could create a single wrapper that accepts the generic view function to call as one of its arguments, rather than having a wrapper for create, a wrapper for update, etc.
I think you might be a bit confused about what the generic views are designed to do. If you go and read django.views.generic.list_detail, for instance, you will see that neither object_list nor object_detail accept an "object" argument, but both require a "queryset" argument. You don't need to "find the corresponding Model" - both generic views are simply common ways to render the provided queryset (in the case of object_detail, after being filtered by either object_id or slug and slug_field). I'm not entirely sure what you're trying to do but I don't think these generic views are what you're looking for.