Writing a Django detail view with a generic class based view - django

I know this is really simple, but I'm missing something. And because I can't ever remember this, I'm hoping this can document a solution here.
All I want to do is pass a PK for the object in the URL and get the detail view back.
Url:
url(regex=r'^(?P<pk>\d+)/$',
view=AdventureDetail.as_view(),
name='adventure_detail',
),
View:
class AdventureDetail(DetailView):
""" Get a time entry detail view """
template_name = "adventure/AdventureDetail.html"
def get_object(self):
return get_object_or_404(Page)
But I'm getting a "multiple objects returned error"
MultipleObjectsReturned at /1/
get() returned more than one Page -- it returned 5! Lookup parameters were {}
This feels really silly. It should "just work" but I'm missing something obvious.
Thanks for the help.

In DetailView it is more simpler: you can just specify the model:
class AdventureDetail(DetailView):
""" Get a time entry detail view """
model = Page
template_name = "adventure/AdventureDetail.html"
And that's all. DetailView will do the rest of work.
Another way is to specify queryset:
class AdventureDetail(DetailView):
""" Get a time entry detail view """
queryset = Page.objects.all()
template_name = "adventure/AdventureDetail.html"
This will have the same result.
And the last way is to override the get_object method.
Look here for details

You're not passing any other parameters to get_object_or_404, other than the Page class. So now you're basically querying for all pages. So you'd need to do:
return get_object_or_404(Page, pk=self.kwargs.get('pk', None))
Also, why are you overriding get_object? The DetailView already contains this functionality so all you need to do is have a URL with pk in it.

In views.py
from django.views.generic import DetailView
# Import your model that you want to use in details view for example
from .models import Post
class PostDetailView(DetailView):
model = Post
Then create a template with the following name conversion
<appname>/<model_viewtype>.html
In urls.py
First import the class view that you created. In our case it is
from .views import PostDetailView
Then
path("post/<int:pk>/", views.PostDetailView.as_view(), name="PostDetailView")

Related

Which URL does CreateView in Django redirects to when the success_url is not provided?

I am learning Class-based views in Django(inheriting from generic views) and stumbled upon a code for CreateView which did not provide any success_url. But after creation, I am getting redirected to DetailView (i.e, the page describing a particular object, in this case the object just created). I am not sure how this redirection is happening. Can anyone help me with this?
# ...other imports...
# ...
from django.views.generic.edit import CreateView
#... other views...
class TweetCreateView(FormUserNeededMixin, CreateView):
form_class = TweetModelForm
template_name = "tweets/create_view.html"
class TweetDetailView(DetailView):
model = Tweet
#...other views...
Thanks.
Thanks Iain Shelvington : "If the model used in the CreateView has a get_absolute_url method, it will be used to determine the success url"
https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-editing/#model-forms

django generic DetailView

New to django and really like the simplicity in getting things done. However having problem rendering a generic DetailView as I get a 405 error stating method not supported. Below is my code.
from django.shortcuts import render, get_object_or_404, get_list_or_404
from django.views.generic import View, ListView, DetailView
from store.managers import StoreManager
from .models import Store
# Create your views here.
class StoreDetails(DetailView):
model = Store
template_name = 'store/details.html'
class StoreIndex(ListView):
model = Store
template_name = 'store/index.html'
context_object_name = 'stores'
# url
urlpatterns = [
url(r'^view/([0-9]+)/$', StoreDetails.as_view(), name='details'),
url(r'^index/$', StoreIndex.as_view(), name='index'),
]
While my StoreIndex view works perfectly, I get an error for my StoreDetails view. Tried overriding get_context_data function but same result.
The problem is in the url pattern. The DetailView needs the primary key to find the right object to display, but the pattern r'^view/([0-9]+)/$' does not specify that the matching number should be used as the primary key. Try r'^view/(?P<pk>[0-9]+)/$' (pk stands for primary key).
Also see the example at DetailView doocs (which provides slug instead of pk). Custom get_context_data should not be neede for pk and slug.

Django class-based "method_splitter" - passing 2 slugs as model name and field value, respectively

I want to create a "method_splitter" equivalent using class-based views in order to remove some hard-coding in my URL confs.
I would like to have the following URL's:
ListView: http://mysite.com/<model_name>/
DetailView: http://mysite.com/<model_name>/<field_value>/
where the query for the ListView would be:
<model_name>.objects.all()
and the queryset for the DetailView would be:
<model_name>.objects.get(<field>=<field_Value>)
Currently, my views work as a result of some hardcoding in the url conf, but I would like to find an elegant solution that can scale.
My solution does not give a 404, but displays nothing:
views.py
class ListOrDetailView(View):
def __init__(self, **kwargs):
for key, value in kwargs.iteritems():
setattr(self, key, value)
try: #If/Else instead?
def goToDetailView(self, **kwargs):
m = get_list_or_404(self.kwargs['model']) #Is this even needed?
return DetailView(model=self.kwargs['model'], slug=self.kwargs['slug'], template_name='detail.html', context_object_name='object')
except: #If/Else instead?
def goToListView(self, **kwargs):
q = get_object_or_404(self.kwargs['model'], slug=self.kwargs['slug']) #Is this even needed?
return ListView(model=self.kwargs['model'], template_name='list.html', context_object_name='object_list',)
urls.py of MyApp
url(r'^(?P<model>[\w]+)/?(?P<slug>[-_\w]+)/$', ListOrDetailView.as_view()),
As limelights said, this is a horrible design pattern; the whole point of Django is separation of models and views. If you fear that you might have to write a lot of repetitive code to cover all your different models, trust me that it's not much of an issue with class-based views. Essentially, you need to write this for each of your models you wish to expose like this:
Urls.py:
urlpatterns = patterns('',
url(r'^my_model/$', MyModelListView.as_view(), name="mymodel_list"),
url(r'^my_model/(?P<field_value>\w+)/$', MyModelDetailView.as_view(), name="mymodel_detail"),
)
views.py:
from django.views.generic import ListView, DetailView
class MyModelListView(ListView):
model = MyModel
class MyModelDetailView(DetailView):
model = MyModel
def get_queryset(self):
field_value = self.kwargs.get("field_value")
return self.model.objects.filter(field=field_value)

How do I use CreateView with a ModelForm

I get an error in my class AuthorCreateForm when I submit my form.
NameError
self is not defined
How do I use a CreateForm?
I have created a class in my Author.py file
from django.views.generic import TemplateView, ListView, CreateView
from books.models import Author, Publisher, Book
from books.forms import AuthorForm
class AuthorCreateView(CreateView):
objAuthorForm = AuthorForm(self.request.POST)
if(objAuthorForm.save()):
success = "Form saved!"
else:
error = "There was an error!"
and I have a html template which submits to /Author/Create
and I have the following line in my urls.py
('^authors/create/$', Author.AuthorCreateView.as_view()),
I render the form at this URL
('^authors/new/$', TemplateView.as_view(template_name="author_new.html")),
I find the class based views confusing, does anyone have a good tutorial on how to use it for CRUD operations?
Thanks
What you have is a python error -- self is not defined. self is generally what refers to the class instance itself on class methods.
Anyways, I agree, it's brand spanking new and not as documented. I think looking at the source is absolutely key at this point.
To get comfortable with class based views, I'd start by subclassing django.views.generic.base.View, which implements only a few methods, namely attempting to call a function on the class based on the request method (post, get, head, - look at source).
For example, here's the first step to replace view functions with the new view classes:
class MyClassBasedView(View):
def get(self, request):
# behave exactly like old style views
# except this is called only on get request
return http.HttpResponse("Get")
def post(self, request):
return http.HttpResponse("Post")
(r'^foobar/$', MyClassBasedView.as_view())
Back to your specific question:
All TemplateView.as_view() does is render the template - CreateView is a combination of several other classes that handle ModelForms and template rendering (TemplateView).
So, for a very basic example, look to the docs for what class mixins are used by CreateView.
We see it implements TemplateResponseMixin, ModelFormMixin, and ProcessFormView, each containing a list of methods for those classes.
The most basic CreateView
At the most basic level, provide CreateView's ModelFormMixin with the model or custom ModelForm class as documented here.
Your CreateView class would look something like the following
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'author_new.html'
success_url = 'success'
With those 3 core attributes set, call it in your URLs.
('^authors/create/$', Author.AuthorCreateView.as_view()),
Render the page and you'll see your ModelForm passed to the template as form, handling the form validation step (passing in request.POST / re-render if invalid), as well as calling form.save() and redirecting to the success_url.
Start overriding the class methods
To customize behavior, start overriding the methods documented for the mixins.
Remember that you simply need to return an HttpResponse from one of these methods just like any regular view function.
Example overriding form_invalid documented in ModelFormMixin:
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'author_new.html'
success_url = 'success'
def form_invalid(self, form):
return http.HttpResponse("form is invalid.. this is just an HttpResponse object")
This per-method overriding starts becoming extremely useful as your forms grow more advanced and ultimately lets you build huge forms with a handful of lines of code, overriding only what is necessary.
Say you want to pass your form custom parameters such as the request object (very common if you need access to the user in the form): you merely need to override get_form_kwargs.
class MyFormView(FormView):
def get_form_kwargs(self):
# pass "user" keyword argument with the current user to your form
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Class based views are a shining example of smart class usage. It gave me a great intro towards building my own mixins for views and python classes in general. It is saving countless hours.
Wow this got long. To think it started as a mere URL to the docs comment :)

How wrap a FormWizard in a View?

How do I wrap a Django Form Wizard in a view? I need to do this so I can access request.
Does anyone have some example code for this?
I probably should be just commenting on Manoj's answer, but sounds you need code
urls.py
from django.conf.urls.defaults import *
from MyApp import views
urlpatterns = patterns(
'',
(r'^wizard/$', views.MyWizardView ),
)
views.py
#login_required
def MyWizardView (request):
cw = MyWizard([WizardName, WizardQuestions, WizardProvider, WizardGoomber])
return cw(request)
The as_view function converts a class based view into a callable view:
from django import forms
from django.contrib.formtools.wizard.views import SessionWizardView
class Form1(forms.Form):
a = forms.CharField()
class Form2(forms.Form):
b = forms.CharField()
class MyWizard(SessionWizardView):
pass
wizard_view = MyWizard.as_view([Form1, Form2])
def view(request):
# do something fancy with the request object here
return wizard_view(request)
This is basicly the same answer as in How to wrap a Django Form Wizard in a View?
This Django snippet may prove useful.
From the title: "FormWizard inside view with proper context handling and site templating support, without having to use urls.py"