How to call GenericModelViews with arguments from another view? - django

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})

Related

How to Django reverse to pages created in Wagtail?

I have a Wagtail model for an object.
class ObjectPage(Page):
# fields, not really important
I want to make the object editable by front-end users, so I have also created a generic update view to accomplish this. My question is how I can use Django's reverse() function to point to the edited object in my get_success_url() method:
class EditObjectPage(LoginRequiredMixin, UpdateView):
# model, template_name, and fields defined; not really important
def get_success_url(self):
return("ObjectPage", kwargs={"slug" : self.object.slug}) # doesn't work
return("object_page_slug", kwargs={"slug" : self.object.slug}) # also doesn't work
I know how to do this when I'm explicitly defining the URL name in my urls.py file, but in this case, the URL is created as a Wagtail page and is handled by this line of my urls.py:
url(r"", include(wagtail_urls)),
I've been searching the documentation for whether Wagtail pages are assigned names that can be reversed to, but I'm finding nothing in the official documentation or here on StackOverflow.
The page object provides a get_url method - get_success_url should be able to accept this directly, so there's no need to use reverse.
def get_success_url(self):
return self.object.get_url()
Internally the URL route used by get_url is wagtail_serve, so if you absolutely need to go through reverse, you could replicate the logic there.

django form wizard - define form_list and condition_dict in views.py (WizardView subclass), NOT in urls.py

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?

How do I override `as_view` in class-based views in Django?

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.

How to read variables added to RequestContext inside class-based generic views?

With regular views, RequestContext variables can be accessed just like request.VARNAME:
def example(request, template_name='stuff_list'):
return render_to_response(template_name,
{'stuff_list': get_list_or_404(Stuff, foo=request.DEBUG)},
context_instance=RequestContext(request))
... instead of setting context_instance I could call function-based generic view direct_to_template1
How do I read variables added to RequestContext inside class-based generic views 2?
For example:
class ArticleListView(ListView):
template_name = 'stuff_list'
bar = request.DEBUG # This won't work. What should I use instead?
queryset = get_list_or_404(Stuff, foo=bar)
1 Will be replaced by class-based TemplateView anyway.
2 They are new in Django 1.3 and I want to use them just because.
You need to use a callback — get_queryset() in this case — instead of the class attributes. Class attributes are really just shortcuts when you're controlling options statically, and they're limited to some pretty simple things. When you need to do something more complex, you'll want to switch to a callback instead.
In your case, code like the following should work:
class ArticleListView(ListView):
template_name = 'stuff_list'
def get_queryset(self):
return get_list_or_404(Stuff, foo=self.request.DEBUG)
For more details, see the documentation.
RequestContext parameters are also regular context variables. You should be able to do just {{VARNAME}}

CRUD pattern for urls.py, passing object from URI to view

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.