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

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.

Related

How to call GenericModelViews with arguments from another view?

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

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.

Adding extra_context in Django logout built-in view

In django/contrib/auth/views.py there is the definition of the logout view :
def logout(request, next_page=None,
template_name='registration/logged_out.html',
redirect_field_name=REDIRECT_FIELD_NAME,
current_app=None, extra_context=None):
I would like to add extra_context to get rid of the 'Logged out' title that appear when I log off
so I'm trying this in my url confs :
(r'^accounts/logout/$', logout(extra_context={'title':'something else'}) ),
but then I get this error : logout() takes at least 1 non-keyword argument (0 given)
what I'm doing wrong?
ps:
when I do
(r'^accounts/logout/$', logout ),
it works, but then I get the 'Logged out' text...
Thanks,
Fred
When you write logout(extra_context={'title':'something else'}), you're actually calling logout right there in the URLconf, which won't work. Any URLconf tuple can have an optional third element, which should be a dictionary of extra keyword arguments to pass to the view function.
(r'^accounts/logout/$', logout, {'extra_context':{'title':'something else'}}),
Alternatively, you could write your own view which calls logout passing in whatever arguments you want -- that's typically how you would "extend" function-based generic views in more complicated cases.
Adding my findings for django 2.0 as the previous answer on this thread no longer works for the most recent django version.
With 2.0, the proper way of adding a URL to your urls.py file is by using path():
from django.urls import path
from django.contrib.auth import views as auth_views
path('accounts/logout/', auth_views.LogoutView.as_view(
extra_context={'foo':'bar'}
)),
The code snippet to highlight here is the .as_view() function. Django 2.0 implements auth views as classes. You can read more about this in the Authentication Views documentation
You then "convert" the class to a view using `.as_view() and you are able to pass in any class attributes defined in the source code as named parameters.
Passing in extra_context (which defaults to None) automatically exposes these context variables to your templates.
You can access the source code for LogoutView by following this python path: django.contrib.auth.views
Here you can see the other class attributes you can pass into LogoutView and the other auth view classes.
I had a similar problem with titles and generic views in django 1.11 (though the problem was mostly that I didn't switch docs version from 2.0). I wanted to pass title via extra_context to the view inherited from CreateView, and discovered that django's generic view had no such attribute. So, here are my crutches:
Create custom mixin (hope that's more or less what ContextMixin in 2.0 does):
class ExtraContextMixin():
extra_context = {}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.extra_context)
return context
Add mixin to view's ancestors (that's all code I had to change):
class CustomView(ExtraContextMixin, CreateView):
Pass extra_context from url:
url(r'^custom-view/$', views.CustomView.as_view(extra_context={'title': 'just any'}), name='custom-view')
Unfortunately, I have no idea whether such solution is acceptable (no need since 2.0, obviously), but at least it's working.

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