How the form_Valid function works in django? - python

What is this form object on the return line,is it the form object recieved by the submission of a form ? . and since we are returning it with return super().form_valid(form).
can it be accessed like context variables ? from the template represented by the success_url .also form_valid points to success_url , since were doing super() , shouldnt it point to the success_url of the parent class. but why does it go to the success_url of ContactView.
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)

What is this form object on the return line?
form is the ContactForm instance that Django constructed to validate the POST request. You can thus for example obtain cleaned data from the form with:
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
print(form.cleaned_data)
return super().form_valid(form)
The FormView will thus construct a ContactForm with request.POST and request.FILES, and check form.is_valid(). If it is, it will call form_valid with this form instance.
since were doing super(), shouldnt it point to the success_url of the parent class.
No. super() is a proxy-object that will move up the MRO and thus call the parent method, but that parent method is implemented as [GitHub]:
def form_valid(self, form):
"""If the form is valid, redirect to the supplied URL."""
return HttpResponseRedirect(self.get_success_url())
The self object is however still a ContactView object, so self.get_success_url() will return the success_url.
Often however, reverse_lazy [Django-doc] is used. That way you can provide the name of the view, and Django can automatically calculate the URL:
from django.urls import reverse_lazy
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = reverse_lazy('name-of-thanks-view')

Related

How to test django (3.0) CreateView's 'form_valid' method and parameters with a test client?

Some info
I'm overriding the form_valid method (for one reason or another).
What I'm trying to do
I want to test the form_valid instance, and specifically its arguments. To do so, I'm using Django's test client.
Some code
models.py:
class MyModel(models.Model):
my_model_text = models.CharField(max_length=100)
my_model_date = models.DateTimeField(
'my model date',
auto_now_add=True)
views.py:
class CreateMyModelView(LoginRequiredMixin, generic.edit.CreateView):
model = MyModel
template_name = 'myapp/create-my-model.html'
form_class = CreateMyModelForm
def post(self, request=None, *args, **kwargs):
# do something here
form = self.get_form()
if form.is_valid():
return self.form_valid(form, request)
else:
return self.form_invalid(form)
def form_valid(self, form, request):
# do something else here
my_model_text = form.cleaned_data['my_model_text']
MyModel.objects.create(my_model_text=my_model_text)
return redirect(reverse('myapp:mypage'))
forms.py:
class CreateMyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['my_model_text']
tests.py:
class CreateMyModelViewTests(TestCase):
#classmethod
def setUpTestData(cls):
cls.my_auth_user = User.objects.create(
username='my_auth_user')
cls.my_auth_user.set_password('my_auth_password')
cls.my_auth_user.save()
def test_form_valid(self):
client = self.client
client.login(
username='my_auth_user',
password='my_auth_password')
# post response ? #
Question
How can I test the arguments that form_valid gets with the test-client (not an instance of the form object, but the form_valid method itself)?
Since CBVs store the current request as self.request, you don't need to change the signature of form_valid.
I think your view can/should be simplified to something like this (and as you can see, there's just commented-out stuff in form_valid() too, so you can elide it entirely unless you need to modify the instance using e.g. the request user).
class CreateMyModelView(LoginRequiredMixin, CreateView):
model = MyModel
template_name = 'myapp/create-my-model.html'
form_class = CreateMyModelForm
success_url = reverse_lazy('myapp:mypage')
def form_valid(self, form):
# You can modify the object-to-be-saved before saving, e.g.
# form.instance.creator = self.request.user
# The CreateView implementation just calls `form.save()` and redirects away,
# so let's reuse that.
return super().form_valid(form)

Pass currently logged in username as an argument to reverse_lazy in class based view

#method_decorator([login_required, audience_required], name='dispatch')
class AudienceInterestsView(UpdateView):
model = Audience
form_class = AudienceInterestsForm
template_name = 'interests_form.html'
success_url = reverse_lazy('profiledisp')
def get_object(self):
return self.request.user.audience
def form_valid(self, form):
messages.success(self.request, 'Interests updated with success!')
return super().form_valid(form)
This is my code. I want to pass the currently logged in user's username as a parameter to the profiledisp view.
You can't access the user when you set success_url - that code runs when the module is loaded, so you don't have access to the request yet.
You can override get_success_url instead. Inside the method, you have access to the request and the logged in user.
class AudienceInterestsView(UpdateView):
def get_success_url(self):
return reverse('profiledisp', args=[self.request.user.username])

Using current user to initialize ForeignKey user in Django CreateView

I'm getting an error:
AttributeError at /courses/create/
'CourseStudentForm' object has no attribute 'user'
When I try to create a new object by setting it's user field to the current user:
class CourseStudentCreate(CreateView):
model = CourseStudent
fields = ['semester', 'block', 'course', 'grade']
success_url = reverse_lazy('quests:quests')
#method_decorator(login_required)
def form_valid(self, form):
data = form.save(commit=False)
data.user = self.request.user
data.save()
return super(CourseStudentCreate, self).form_valid(form)
This is the model:
class CourseStudent(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
semester = models.ForeignKey(Semester)
block = models.ForeignKey(Block)
course = models.ForeignKey(Course)
grade = models.PositiveIntegerField()
active = models.BooleanField(default=True)
The form displays correctly, but when I submit I get the error.
ANSWER:
From here:
Pass current user to initial for CreateView in Django
If I want to keep user as a required field, it works if I change form_valid to:
def form_valid(self, form):
form.instance.user = self.request.user
return super(CourseStudentCreate, self).form_valid(form)
The cause of the error is described by Burhan Khalid below.
The reason it doesn't work is because you are missing a required field from your form class; recall that model form validation will also validate the model instance:
Validation on a ModelForm
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
In your class, the inherited post method is calling is_valid():
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)
You can see that it only calls form_valid() if is_valid() returns true; in your case it can't return true because you have a required attribute missing.
You can solve this problem easily by making the user foreign key optional in your model.

Django Class-based Form is not displayed

New in Django. I'm trying to show form, but have instead it:
messag.views.CommentAdd object at 0x037860D0
forms.py:
from django.http import JsonResponse
class AjaxableResponseMixin(object):
"""
Mixin to add AJAX support to a form.
Must be used with an object-based FormView (e.g. CreateView)
"""
def form_invalid(self, form):
response = super(AjaxableResponseMixin, self).form_invalid(form)
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
else:
return response
def form_valid(self, form):
# We make sure to call the parent's form_valid() method because
# it might do some processing (in the case of CreateView, it will
# call form.save() for example).
response = super(AjaxableResponseMixin, self).form_valid(form)
if self.request.is_ajax():
data = {
'pk': self.object.pk,
}
return JsonResponse(data)
else:
return response
views.py:
class CommentAdd(AjaxableResponseMixin, CreateView):
model = Comment
fields = ['author_name', 'text', 'root']
class ShowTree(ListView):
model = Comment
template_name = 'comment_tree.html'
def get_context_data(self, **kwargs):
context = super(ShowTree, self).get_context_data(**kwargs)
context['comment_form'] = CommentAdd()
return context
It doesn't work because you need to pass an form instance and you are passing a class based view. CreateView is a class based view, not a ModelForm.
It could be easier to create a CreateView like in the example and get the data to build the list in get_context_data()

Send form data from views to template

Edit:
I want the 'success_url' (ie, result.html) to display the 'data' from 'form.process()'. The following code obviously doesn't work.
Can anyone please tell me what's wrong with it or suggest another way to basically view the context 'data' in a template (either in the form of list or dict), ie a better way to display data to the user after a form has been submitted.
Many thanks in advance.
-- urls.py --
url(r'^$', view='main_view'),
url(r'^result/$', view='result_view'),
-- views.py --
class ResultView(TemplateView):
template_name = "result.html"
class MainView(FormView):
template_name = 'index.html'
form_class = UserInputForm
success_url = 'result/'
def form_valid(self, form):
data = form.process()
return super(MainView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
context['data'] = data
return context
main_view = MainView.as_view()
result_view = ResultView.as_view()
As far as I understood your question, you want to show the contents of the user submitted form in the result view. Is that correct?
In this case the method get_context_data won't help you at all, because it will only store data in the current context which is in MainView.
The form_valid method of FormView will make a HttpResponseRedirect to the success_url. So the question now is, how can we give the data to this view.
As explained in Django return redirect() with parameters the easiest way would be to put the data into the session. In the result.html-template you could then access this data as explained in Django: accessing session variables from within a template?
Here is the code:
class ResultView(TemplateView):
template_name = "result.html"
class MainView(FormView):
template_name = 'index.html'
form_class = UserInputForm
success_url = 'result/'
def form_valid(self, form):
self.request.session['temp_data'] = form.cleaned_data
return super(MainView, self).form_valid(form)
in the result.html template you could then access this temp_data so:
{{ request.session.temp_data }}
As suggested above, you can override get_context_data.
For example, you can do something like the below:
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
#set some more context below.
context['foo'] = bar
...
return context
Look for get_context_data in context the Django class-based view docs. The dict returned by the overridden method will be passed into the templates.
There are a couple of things that could be your problem. First, in form_valid() method, you process the form before you call that class' parent form_valid(). Also, you're no storing the result in a common place for both methods to grab it. Try something like:
def form_valid(self, form):
self.data = form.cleaned_data
return super(MainView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
context['data'] = self.data
return context

Categories

Resources