How to do form handling in Django Generic CBV? - django

I'm trying to do the same thing below in Django Generic CBV.
Function Based View
def form_handling(request):
if request.method == "POST":
form = SimilarStore(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('...')
else:
form = SimilarStore()
...
Class Based View
class SimilarStoreCreateView(CreateView):
model = SimilarStore
form_class = SimilarStoreForm
template_name='cms/similarstore_new.html'
success_url=reverse_lazy('cms:similarstore')
def form_valid(self, form):
form = SimilarStoreForm(self.request.POST, self.request.FILES)
form.save()
return redirect(self.get_success_url())
def form_invalid(self, form):
form = SimilarStoreForm()
...
I'm confused about how I can check if request.method is POST or GET in Django Generic CBV. I know Django regular CBV supports get() and post(). However, in Generic views, there's no post(). What should I do to handle POST requests in Django Generic?
Also, my CBV codes throw an error 'SimilarStoreForm' object has no attribute 'cleaned_data'. Why is that?

CreateView has post() method it's inherited from ProcessFormView. You can find method source here. As for your question from the source code you may see that form_valid and form_invalid method will be triggered only for POST requests.
You don't need to override form_invalid method since by default it will render invalid form with form's error. And you don't need to override form_valid also, CreateView do redirect automatically after. So you can simple do:
class SimilarStoreCreateView(CreateView):
model = SimilarStore
form_class = SimilarStoreForm
template_name='cms/similarstore_new.html'
success_url=reverse_lazy('cms:similarstore')
And this will give you same logic as in your function based view.

CreateViews have a post method as shown below that you can use
def post(self, request, *args, **kwargs):
self.object = None
return super().post(request, *args, **kwargs)
Here is a good reference for all the methods and attributes of CBVs

Basically, your current setup is all you need. You see, CreateView's post method is effectively its base class'es (BaseCreateView) post method which calls its own base class'es (ProcessFormView) post method, that being
class ProcessFormView(View):
# ...
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Therefore, whenever a POST HTTP request gets routed to SimilarStoreCreateView it will finally be dispatched to the aforementioned post method already implementing the desired workflow of yours.

Related

Does form_valid() in django saves the ModelForm instance by default?

The official documentation says that form_valid() is called when valid form data is POSTed. And doesn't say anything about saving data. But I'm reading a book where it says that
the default behavior of this method is saving the instance (for
modelforms ) and redirecting user to success_url
.
So, I'm a bit confused.
For that you need to see the implementation of post method of ProcessFormView(which is sub-classed by CreateView):
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You see that form.is_valid() method is called and it returns true if the form has been validated. Then you should check how form_valid() method works in ModelFormMixin.
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
Its kind of straight forward, data saved to DB using form.save() method and then it calls super class's form_valid() method, in which it redirects to the success url.
Well I found another documentation on Editing mixins where it does say
Saves the form instance, sets the current object for the view, and redirects to get_success_url().
So, it does.

Django overriding detail view get method

This is my post detail view and it works perfectly.
class PostDetailView(DetailView):
model = Post
context_object_name = 'post'
template_name = 'posts/detail.html'
def get_queryset(self, *args, **kwargs):
request = self.request
pk = self.kwargs.get('pk')
queryset = Post.objects.filter(pk=pk)
return queryset
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
content['comments'] = Comment.objects.all()
return context
However, when I add get method to the view it does not work anymore.
def get(self, request, *args, **kwargs):
# how to return here so that it works exactly like before
After adding get method get_queryset and get_context_data do not gets called automatically and the context is empty in the template. So what would be the get method so that it works exactly like before?
EDIT
My target is to do something like this
if request.is_ajax():
html = render_to_string('comments/detail.html') # ajax reply with html data
return HttpResponse(html)
return render 'posts/detail.html'
So where do I put this code and still want to keep call all methods such as get_queryset and get_context_data to be called automatically?
The idea of views like a DetailView, ListView, etc. is that it implements the boilerplate logic for you. So it has defined a function def get(self, request, *args, **kwargs) that is used to render the logic. You can usually tweak a few things by specifying the model, queryset, etc. without reimplementing the entire view.
For a DetailView [Django-doc], the logic is implemented in the BaseDetailView you can inspect the source code [GitHub]:
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
One general piece of advice I want to share:
Before overriding any attribute, one must have deep knowledge of what is the significance of that attribute (callable or not callable). This advice applies to any language or framework. Suppose when someone overrides the get in Django, all the methods that are being called from get will not be invoked unless one invokes that from overridden get. So you should see the source of get and observe that methods are called from that.

Unable to figure out how ModelForm is rendered using generic UpdateView

I'm trying to figure out how the ModelForm is instantiated when I'm using generic UpdateView.
I've gone through the django source code and looked into UpdateView and relevant Form classes but I can't see any line of code where we are explicitly passing instance to the object of ModelForm class.
For example, say we have PostForm as a ModelForm then we would have written :
form = PostForm(instance=Post.object.get(pk=pk))
to render the form from the models object.
I can't see similar code in the django source code and can't figure out how the generic ModelForm is getting populated in case of UpdateView
i.e. how the self.instance attribute of my form is getting instantiated when I submit data after POSTing the form.
The instance attribute of ModelForm is instantiated in get_form_kwargs() defined in ModelFormMixin
For detailed explanation see below :
The UpdateView view inherits SingleObjectTemplateResponseMixin, BaseUpdateView
BaseUpdateView further inherits ModelFormMixin and ProcessFormView
It also defines the get and post methods that are called via dispatch
These get and post methods sets the object attribute as the current model object. Below is the code snippet from django docs :
class BaseUpdateView(ModelFormMixin, ProcessFormView):
"""
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
The get and post methods also call the parent's get and post method i.e. get and post defined in ProcessFormView
During GET request
The get method defined in ProcessFormView calls the get_context_data() overridden under FormMixin which further invokes get_form() to return an instance of the form to be used in the view.
def get_context_data(self, **kwargs):
"""
Insert the form into the context dict.
"""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super(FormMixin, self).get_context_data(**kwargs)
get_form() calls get_form_kwargs() which lies in ModelFormMixin as well as FormMixin but since the ModelFormMixin inherits from FormMixin, the method defined in ModelFormMixin overrides the one defined in FormMixin. This get_form_kwargs() method first calls the super/parent's method and then sets the instance attribute of the form to the current model object i.e self.object (or simply object).
Code snippet from the docs below :
def get_form_kwargs(self): #defined in ModelFormMixin class
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = super(ModelFormMixin, self).get_form_kwargs()
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
The form is then rendered using the model object's attributes
During POST request :
As mentioned earlier (see first code snippet), just like get(), post() method also sets the object attribute to the current model object i.e. self.object=self.get_object(). ( get_object() is inherited from SingleObjectMixin class )
It then calls post method of ProcessFormViewi.e. parent class which creates the instance of form using get_form() method. (Just like get_context_method was doing in case of get request)
get_form() calls the get_form_kwargs which further sets the instance attribute of the form to the self.object instantiated in first post method call.
Code snippet below :
class ProcessFormView(View):
"""
A mixin that renders a form on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
Next, the form is validated against basic constraints and this is done by calling form.is_valid() method which is inherited from BaseForm class.
This is a very important step because at this point, the instance object's attributes are updated to the data POSTed in the form.
This all is achieved via following stack of calls :
form.is_valid() calls -> errors property -> which calls full_clean() -> _clean_fields() -> _clean_form() -> _post_clean()
_post_clean() constructs the instance from POST data by calling construct_instance_method
To understand these functions better read the BaseForm class for is_valid() here and BaseModelForm class for _post_clean() here
I think you might be looking for the method
FormMixin.get_form_kwargs().
Here is the source from the Github repo:
def get_form_kwargs(self):
...
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
As you can see, if the request is POST, the data from POST and FILES
are returned from this method, which in turn is used to instanciate the form, as you can see in this second snippet below from the same source:
def get_form(self, form_class=None):
...
return form_class(**self.get_form_kwargs())

How do I test Django views when they don't return values

I'm new to Django. I've worked through some tutorials, written some models, views, forms etc. but I don't understand how they can be tested since either nothing is returned, or whatever is returned is so tightly bound it doesn't make sense in terms of the test.
For example here are two view classes:
class ListBlogPostView(ListView):
model = BlogPost
template_name = 'app/blogpost_list.html'
class CreateBlogPostView(CreateView):
model = BlogPost
template_name = 'app/blogpost_edit.html'
form_class = BlogPostForm
def get_success_url(self):
return reverse('blogpost-list')
def get_context_data(self, **kwargs):
context = super(CreateBlogPostView, self).get_context_data(**kwargs)
context['action'] = reverse('blogpost-new')
return context
The first, ListBlogPostView, doesn't return anything. How can I ever check that this is working correctly?
The second has a couple of functions but their return values are not things I can test with an assert
How could I ever use a TDD approach to Django?
I'm used to using nunit and MS 'unit' tests in Visual Studio, mocking etc
Actually, Django's generic class-based views are not called generic for no reason.
You can view the source of the both ListView and CreateView.
The ListView has a GET method handler:
class BaseListView(MultipleObjectMixin, View):
"""A base view for displaying a list of objects."""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# ...
context = self.get_context_data()
return self.render_to_response(context)
Which returns a valid Django response and can be tested in tests.py.
If you look at the CreateView, it inherits from BaseCreateView (just like ListView inherits from BaseListView):
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
# ...
def post(self, request, *args, **kwargs):
# ...
Which also inherits from ProcessFormView:
class ProcessFormView(View):
"""Render a form on GET and processes it on POST."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: instantiate a blank version of the form."""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
The GET request will result in a valid response. As you see, the POST method handler here returns either self.form_valid(form) or self.form_invalid(form) depending on the form status.
You can see the source of these two methods in ViewMixin:
def form_valid(self, form):
"""If the form is valid, redirect to the supplied URL."""
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form):
"""If the form is invalid, render the invalid form."""
return self.render_to_response(self.get_context_data(form=form))
Both of these methods return a valid testable Django responses.
In conclusion, both of your ListBlogPostView and CreateBlogPostView can be directly tested in tests.py. You just need to have a more detailed look at the implementation of Django's generic views. The power of open-source!
Testing view unlike function most of the time will not have a return value. The way that I go about doing it is to assert the html response.
So for the ListBlogPostView it depends on what is in the blogpost_list.html template.
A general view test should look like this:
class ListBlogPostViewTest(TestCase):
def test_blogpost_list_view(self):
response = self.client.get(reverse('blogpost-list'))
html = response.content.decode('utf8')
self.assertTrue(html.startswith('<html>'))
self.assertIn('<title>BlogPost lists</title>', html)
self.assertTrue(html.endswith('</html>'))
For view that have context you can actually check if it is being retrieved and passed correctly to view.
blogPost = BlogPost.object.get(id=1)
self.assertEqual(response.context['blogPost'].name, blogPost.name)
How could I ever use a TDD approach to Django?
As for TDD, you just have to test the html view first before creating it. It really depend on how detail you like to test and find the balance for it. I prefer to test mainly on the context being set and important html element is in the view.
You can still surely test lot of parameters -
status code on get and post request
variables in context data (such as form)
assert template used
creation of object on post request in case of create view
check for permissions using status codes
The thing is tests on django views are technically integration tests. As long as your tests are granular enough, means you don't test code for forms or models in views, I don't see any problem provided you follow Classical TDD.

How can I make a Generic Class Based Create View for a Model?

What I'm trying to do is Django boilerplate for functional views. Any help here is very much appreciated, as the docs show examples for the template view and list view, but I've found very little for the model-based generic views. Am I missing an example in the docs?
I have a model that represents an entry in a calendar. There's a foreign key to another object (not a user) that owns the entry. What I want to do is simply to create the entry, ensuring that the entry's foreign key is properly set and then return the user to the appropriate calendar page.
I don't know, though, how class-based generic views receive their URL arguments and I'm not clear on how to set the success_url so that it reuses the id that was originally passed to the creation URL. Again, thank you in advance for your help.
What I'm asking, essentially, is, what is the class-based generic view equivalent of the following:
def create_course_entry(request, class_id):
'''Creates a general calendar entry.'''
if request.method == 'POST':
form = CourseEntryForm(request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.course = Class.objects.get(pk=class_id)
new_entry.full_clean()
new_entry.save()
return HttpResponseRedirect('/class/%s/calendar/' % class_id)
else:
form = CourseEntryForm()
return render_to_response('classes/course_entry_create.html',
{ 'class_id': class_id, 'form': form, },
context_instance=RequestContext(request))
You could subclass the edit.CreateView generic view, set the class/course in the dispatch() method, and save this by overriding the form_valid() method:
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.views.generic.edit import CreateView
class CourseEntryCreateView(CreateView):
form_class = CourseEntryForm
model = CourseEntry
def dispatch(self, *args, **kwargs):
self.course = get_object_or_404(Class, pk=kwargs['class_id'])
return super(CourseEntryCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.course = self.course
self.object.save()
return HttpResponseRedirect(self.get_success_url())
If you're not customising the CourseEntryForm ModelForm, then you can leave out the form_class property.
Unfortunately, it is not possible to call super() in the form_valid() method - due to the way it has been written would mean the object would be saved again.
If you need the Class (course?) instance in the template context, then you can add this in the get_context_data() method:
def get_context_data(self, *args, **kwargs):
context_data = super(CourseEntryCreateView, self).get_context_data(
*args, **kwargs)
context_data.update({'course': self.course})
return context_data
An alternative to Matt Austin's answer might be to override the get_form method:
from django.shortcuts import get_object_or_404
from django.views.generic import CreateView
class CourseEntryCreateView(CreateView):
form_class = CourseEntryForm
model = CourseEntry
def get_form(self, form_class):
form = super(CustomCreateView, self).get_form(form_class)
course = get_object_or_404(Class, pk=self.kwargs['class_id'])
form.instance.course = course
return form
This way, .course is on the CourseEntry instance in the context, and on the instance created when the form is saved upon POST.