I'm trying to introduce class-based views in my project. Looked good so far, until I found the following problem.
I'm using django-navigation to create breadcrumbs. It works like this: a view function gets decorated and this decorator introduces an attribute on that function called breadcrumb. In the template, current URL or its part get resolved and the resulting view get checked for this attribute. If it's there, it's evaluated and the result is the breadcrumb text.
Since class-based views are normally represented by the as_view() method, it seems I would need to decorate it, however, since it's a class method, I can't really access the instance there, which my breadcrumb of course depends on.
Attaching breadcrumb attribute to as_view() in the __init__() didn't work either, or I got the syntax wrong. EDIT: Of course it didn't work, since I attached it to as_view, not to its return value.
Any ideas how to properly integrate that breadcrumb decorator and class-based views?
I've solved this now like this. I've put my breadcrumb routine in a method on a child class and overridden as_view in my base view. Also used a trick from the actual as_view to get a self pointer.
#classonlymethod
def as_view(cls, **initkwargs):
self = cls(**initkwargs)
view = super(MyBaseView, cls).as_view(**initkwargs)
if hasattr(self, 'breadcrumb') and callable(getattr(self, 'breadcrumb', None)):
return breadcrumb(self.breadcrumb)(view)
return view
I guess you could do something like this in urls.py:
the_view = ListView.as_view(...)
the_view = the_decroator(the_view)
urlpatterns = patterns('',
url(r'^$', the_view, name='app_index'),
...
)
The as_view method returns a callable, and that can be decorated. The '#'-syntax is just a shortcut for what is done on line 2.
Related
I want to return GenericDeleteView with arguments from another view.
I have a view, that gets an pk of an object and I want to delete it using Django generic DeleteView subclass.
The problem is that the pk can be of different Models.
I have the respective GenericDeleteViews defined and if I add them to urls.py and trigger them from there with positional argument, everything works fine. But I would like something little different.
Example of what I would want:
views.py
def delete_object_view(request, pk):
if FirstModel.objects.filter(pk=pk).exists():
return FirstModelDeteleView.as_view()(request, !!pk!!)
else:
return SecondModelDeleteView.as_view()(request, !!pk!!)
But the case is that this does not pass the pk to the DeleteView and it returns the error:
Generic delete view must be called with either an object pk or a slug
I tried many alternatives, passing kwargs={'pk':pk} and some other, but nothing seems to be working.
I am desperate and even tough I know some workarounds, this sounds like something that should be possible, and seems elegant.
Thanks for any advice.
I will not follow your approach in calling another view from a view. (i find it is not elegant solution)
I have not test this answer, so you may need to debug some minor errors later.
Because your DeleteView may use different Model, then you may want to determine the Model dynamically.
You still could use the generic DeleteView. Since it uses the SingleObjectMixin, instead of specifying the model in the View, you should then overwrite get_queryset method or get_object method.
Sample code:
class MyDeleteView(DeleteView):
def get_queryset(self):
if FirstModel.objects.filter(pk=<pk>).exists():
return FirstModel.objects.all()
else:
return SecondModel.objects.all()
From the docs:
You should use the reverse() urlresolver.
Try this:
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
if FirstModel.objects.filter(pk=pk).exists():
return HttpResponseRedirect('first_model_delete',kwargs={'pk' : pk})
else:
return HttpResponseRedirect('second_model_delete',kwargs={'pk' : pk})
I have a class based view
class HomePage(View):
def get(self, request):
return HttpResponse('<p>This is content.</p>')
and url-pattern defined as below:
urlpatterns = patterns('',
url(r'^$', HomePage.as_view()),
)
To this pattern resolves to current view function, I wrote a test like this:
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertIsInstance(found.func, HomePage)
By running this unittest I am getting following error:
self.assertIsInstance(found.func, HomePage)
AssertionError: <function HomePage at 0x7f85dd2c7840> is not an instance of <class 'web.views.HomePage'>
Any Idea how to test this case?
Django's View.as_view() creates a function with a view_class attribute which points to the class-based view. So use:
self.assertEquals(found.func.view_class, HomePage)
Avoids the problem of two class-based views in different modules with the same name.
May be it's an old question, but in django>=1.8 assertion like
self.assertEquals(found.func.func_name, HomePage.__name__)
AttributeError: 'function' object has no attribute 'func_name' so I changed it to
self.assertEqual(found.func.__name__, HomePage.__name__)
Resolve will return the function that is returned when calling HomePage.as_view(), and not an object of that type. However, from a quick test there may be a way that you could write this test:
self.assertEquals(found.func.func_name, HomePage.__name__)
Note that here we specify HomePage.__name__ instead of 'HomePage' because this will get picked up if the name of the class is changed using refactoring tools.
The downside of this is, should you have wired up a view class with the same name but from a different module, this unit test would not fail. Of course this is more of a risk with a generic view class name such as HomePage but should be less of a risk with other view classes.
I did it another way because Valentjedi did not work for me;
I did .:
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.view_name, "home")
Hope it helps
today I've started using Form Wizard in django 1.4. The functionality seems to be nice, but according to the documentation, one need to pass the form_list (and condition_dict, when necessary) to the as_view method (which means urls.py) instead of providing it in the subclass of WizardView
This means writing some logics within urls.py and not views.py which I believe is against django pattern, as the views module is supposed to be responsible for views logics.
I've ended up with following:
# views.py
class MyWizard(SessionWizardView):
_form_list = (
('init', forms.MyWizardFormInit),
('newuser', forms.MyWizardFormNewUser),
)
_condition_dict = {
'newuser': lambda wizard: (wizard.get_cleaned_data_for_step('init') or {}).get('existing_user') == 'False'
}
and the urls.py:
url(.., MyWizard.as_view(MyWizard._form_list, condition_dict = MyWizard._condition_dict)),
which really looks silly and ridiculous.
Is there any correct way to prevent declaring logics in urls.py while keeping DRY ?
A slightly cleaner version might look like this:
views.py
my_wizard_view = MyWizard.as_view(MyWizard._form_list, condition_dict=MyWizard._condition_dict))
urls.py
url(r'^my_wizard/$', my_wizard_view, name='my_wizard')
I haven't used WizardWiev yet, but have you tried setting those attributes on the WizardView subclass itself instead of passing them through the url definition? Or, filing that, have you tried overloading the WizardView.get_form method?
I'm adding a new type of user profile to site and this new type of user(say new_type) should not be able to reach the same views like the existings users.
My question is: how can i use different types of views according to user type using the same request paths without altering the existing view codes like adding
if user.profile_type == 'blah':
do_this
else:
do_that
to each view?
In detail:
i'd like to use "http://mysite.com/path/" for both types of users, running different logics and returning different displays without making differences in existing views (since there are lots of views to modify).
I'm thinking of adding different groups of views for new type, then override urls logic to resolve the request paths to relevant views, such as :
if user is of new_type
resolve path to related_view_for_new_type
else
resolve as usual
As a straight forward example: logging in admin and normal user from the same login url, and if user is admin, run the relevant views for admin and return django admin display to her, if normal user, then run the normal view and return normal website view to her, without rewriting or changing the url they are requesting. (/index/ for example)
Is it possible to extend urls in Django in such way and if so how, or should i give up overloading the same request paths and add '/new_type/' to urls (http://mysite.com/new_type/path/)for new_type users?
Thanks
To start with, what does it mean to have different types of users? A very simple way to do this would be to store an attribute on a user. That way, given a user object, you could look at this extra attribute to determine whether the user is of a special type. Django has a standard mechanism for storing additional attributes like this, which you can read about here.
Once you have a way of determining user types, you can create a single decorator and apply it to any view that needs to behave in the way you've described. Decorators are a great way of applying extra conditions or behaviour to existing functions. The logic in the decorator gets to work before and/or after the existing function, so it can very easily accomplish something like displaying a different template based on a the user's type.
Decorator functions look very odd when you first encounter them, but read it carefully and you'll soon get it. The decorator is a function itself, and you give it the function you want to decorate. It gives you back a new function, which is your old function wrapped with the extra logic.
I've written some untested example code below.
def template_based_on_user_type(special_template, ordinary_template):
def decorator(your_view_function):
def inner_decorator(request, *args, **kwargs):
# this is the logic that checks the user type before
# every invocation of this view:
if request.user.type == 'special_type':
template = special_template
else:
template = ordinary_template
# this is the invocation of the view function, with
# a custom template selected:
return your_view_function(request, template)
return inner_decorator
return decorator
#template_based_on_user_type('my-special-template.html', 'ordinary-template.html')
def my_view_function(request, template='default.html'):
# Do what you need to do here
render_to_response(template, data, RequestContext(request)
The syntax for applying a decorator is the "#" symbole, followed by the decorator function. The decorator is customized with the template names specified.
I solved this problem using decorator in urls.py:
def dispatch_by_user(staff_view, external_user_view):
def get_view(request, **kwargs):
if (is_staff_user(request.user)):
return staff_view(request, **kwargs)
else:
return external_user_view(request, **kwargs)
return login_required(get_view)
def is_staff_user(user):
return user.groups.filter(name="privileged-group").exists()
So patterns set as following:
urlpatterns = [
url(r'^$',
dispatch_by_user(
views.StaffHomeView.as_view(),
views.ExternalUserClientView.as_view()),
name='index'),
# ...
]
RTFM as usual :)
Here's the link to a possible solution :
method_splitter # http://www.djangobook.com/en/2.0/chapter08/
new_type related views' names will be derived from the originals by adding new_type_ to beginning of the name, such as index-> new_type_index
then i'll determine the view to return by simply checking the request.user.is_new_type attribute. ugly, but better than modifying gazillions of views.
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.