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()
Related
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)
Context: how I handled foreign keys restrictions on GET
I have some trouble validating this form:
class RecordConsultationForm(forms.ModelForm):
class Meta:
model = Consultation
fields = ["fk_type", "fk_client", "duration", "date"]
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(RecordConsultationForm, self).__init__(*args, **kwargs)
self.fields['fk_client'].queryset = Client.objects.filter(fk_owner=self.user) # <=====HERE
The queryset restricts the available clients to users. Pretty effective, I just had to add the following to get_context_data():
#method_decorator(login_required, name='dispatch')
class BrowseConsultations(BrowseAndCreate):
template_name = "console/browse_consultations.html"
model = Consultation
form_class = RecordConsultationForm
success_url = 'browse_consultations'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.form_class(user = self.request.user) #<=====HERE
return context
def post(self, request):
form = self.form_class(user = self.request.user) #<=====HERE
return super().post(request)
Form validation on BrowseConsultations
Despite what I added in get_context_data() and post(), form data on POST does not seem valid, as if user were None (so it seems):
Invalid fk_client: Select a valid choice. 2 is not one of the available choices.
Maybe get_context_data() was not the proper place to set the user? Is there a way I could tweak the form in post()? Is it the proper way to do it?
Additional details
BrowseAndCreate BrowseConsultations inherits from is a Mixing class I created to handle common ordering tasks and messages. Here is a portion of it:
#method_decorator(login_required, name='dispatch')
class BrowseAndCreate(CreateView, TemplateView):
"""display a bunch of items from a model and the form to create new instances"""
def post(self, request):
super().post(request)
return redirect(self.success_url)
def form_valid(self, form):
super().form_valid(form)
messages.add_message(self.request, messages.SUCCESS,
"Recorded in {}".format(self.object.status))
def form_invalid(self, form):
for e in form.errors.items():
messages.add_message(self.request, messages.WARNING,
"Invalid {}: {}".format(e[0], e[1][0]))
Environment
django 3.0.4
python 3.7
First of all, the CreateView (that your BrowseAndCreate inherits from) handles form validation in the post method, where it calls form_valid on success and form_invalid on failure. Both these methods should return an HTTP response.
Furthermore, the get_context_data from FormMixin that you are overriding already takes care of getting the form data.
If you need the user in your form, you could have this in your form:
class RecordConsultationForm(forms.Form):
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
And this in your view:
class BrowseConsultations(BrowseAndCreate):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
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')
Newbie Question: I have a FormView which displays a sign-up form, using Django's validations, etc. It all works fine, however, I can't seem to work out how to provide any data (context) to the template. Currently, I have:
from django.shortcuts import render
from django.shortcuts import redirect
from django.http import HttpResponse
from accounts.forms import SignUpForm
class SignUpView(FormView):
template_name = 'accounts/signup.html'
form_class = SignUpForm
def form_valid(self, form):
# executes when form validates..
(...)
return redirect('/account/')
I tried adding some context data via get() per below, which I need when the page first displays, which sort of works except the input fields of the form are gone (the labels are all there):
def get(self, request):
return render(self.request, self.template_name, {'data': data })
Could someone please clarify why that is, and how to get it to work? To put it another way: When using FormView with form_valid(), where do I place code intended for the initial GET request?
You have to methods of doing it, if the data is related only to that get view then you can move on:
def get_context_data(self, **kwargs):
context = super(SignUpView, self).get_context_data(**kwargs)
something = something
context['something'] = something
return context
Or use a Mixin:
class SomeMixin(object):
def get_context_data(self, **kwargs):
context = super(SomeMixin, self).get_context_data(**kwargs)
something = something
context['something'] = something
return context
And then:
class SignUpView(SomeMixin, FormView):
def form_valid(self, form):
...
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