CreateView form_invalid for multiple forms? - django

I am working with a CBV that uses 2 ModelForm instances. I would like to display the individual form errors. It seems like this is a little challenging when using multiple forms in a class based view.
Heres a smaller snippet to show what I am working with...
class EmployeeCreate(CreateView):
form_class = EmployeeCreateForm
form_class_2 = AddressCreateForm
def post(self, request, *args, **kwargs):
employee_form = self.form_class(request.POST)
address_form = self.form_class_2(request.POST)
# Make sure both forms are validated
if employee_form.is_valid() and address_form.is_valid():
employee = employee_form.save(commit=False)
address = address_form.save(commit=False)
employee.parent = self.request.user
employee.save()
address.user = employee
address.save()
return JsonResponse({'message': 'Employee created successfully.'}, status=200)
else:
return self.form_invalid(**kwargs)
def get_context_data(self, **kwargs):
# render both forms to create an Account, and Address
context = super(EmployeeCreateView, self).get_context_data()
context['employee_form'] = self.form_class
context['address_form'] = self.form_class_2
return context
def form_invalid(self, **kwargs):
return JsonResponse({'success': False})
Now when the form is invalid, the form_invalid method is getting called and returning the JsonResponse message, but I would much rather return the specific form error.
I am trying to find a way to display each individual form error for the employee_form and the address_form. Is there a possible way to do this override in the form_invalid method?
Thank you in advance!

you are returning both forms error in single JsonResponse. Instead you should return different forms error in single JsonResponse like
return JsonResponse({'employee_form_errors': self.form_invalid(employee_form),
'address_form_errors': self.form_invalid(address_form) }, status=400)
you should use individually use form_invalid with both forms.

Related

Django Class Based View With ModelChoiceField

I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())

CreateView + redefinition of form_valid = didn't return an HttpResponse object

Django 1.10strong text
Could you help me understand why this results in:
The view wiki.views.WikiCreate didn't return an HttpResponse object. It returned None instead.
post: I didn't interfere with the chain of inheritance when redefining post method. I just intercepted the parameters from url.
form_valid: I returned the HttpResponse. But seemingly somewhere later it disappeared.
urlpatterns = [
url(r'^(?P<model>[-\w]+)/(?P<pk>\d+)/wiki/create/$', WikiCreate.as_view(), name='wiki_create'),
]
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def post(self, request, *args, **kwargs):
#For wiki history.
self.author = request.user
# Parameters from url reflect fields not shown to user.
self.related_model = kwargs.get('model')
self.related_object_id = kwargs.get('pk')
super(WikiCreate, self).post(request, *args, **kwargs)
def form_valid(self, form):
"""
Only "article" field was shown to tue user.
Now we save fields that were not explicitly shown (but were in the url).
"""
self.object = form.save(commit=False)
self.object.related_model = self.related_model
self.object.related_id = self.related_object_id
self.object.save()
return HttpResponseRedirect(self.get_success_url())
You didn't return the response from the super call in post.
Note it's rarely a good idea to override that method, and you certainly didn't need to here. You can access those kwargs directly in form_valid.

Call post function of a class based view using formview

I am new to django class based views and may be the way I am approaching this is a little naive, so I would appreciate if you could suggest a better way.
So my problem is here:
There are three types of users in my project. 1. Student, 2. Teacher, 3. Parent. I need to be able to show different user settings pertaining to each type of user when the user requests the settings page in their respective forms. Also, I need to be able to save the data into the respective tables as the user submits the form.
I have a class based view (UserSettingsView):
class UserSettingsView(LoginRequiredMixin, FormView):
success_url = '.'
template_name = 'accts/usersettings.html'
def get_initial(self):
if self.request.user.is_authenticated():
user_obj = get_user_model().objects.get(email=self.request.user.email)
if user_obj.profile.is_student:
return {
'first_name': user_obj.profile.first_name,
'last_name': user_obj.profile.last_name,
""" and other student field variables """
}
if user_obj.profile.is_teacher:
return {
""" Teacher field variables """
}
else:
return render_to_response('allauth/account/login.html')
def form_valid(self, form):
messages.add_message(self.request, messages.SUCCESS, 'Settings Saved!')
return super(UserSettingsView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UserSettingsView, self).get_context_data(**kwargs)
context['user'] = get_user_model().objects.get(email=self.request.user.email)
context['userprofile'] = UserProfile.objects.get(user_id=context['user'])
return context
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
form.full_clean()
if form.is_valid():
user = request.user
user.profile.first_name = form.cleaned_data['first_name']
user.profile.last_name = form.cleaned_data['last_name']
user.profile.save()
if user.profile.is_student:
""" update student database """
user.save()
user.student.save()
if user.profile.is_teacher:
""" update teacher database """
user.save()
user.teacher.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
Different instances of Usersettings view are called using the pick_settings generic view.
url(regex=r'^profilesettings/',view=pick_settings,name='profilesettings'),
And here is the pick_settings view:
def pick_settings(request):
if request.user.is_authenticated():
if request.method == 'GET':
if request.user.profile.is_student:
return UserSettingsView.as_view(form_class=StudentSettingsForm)(request)
if request.user.profile.is_teacher:
return UserSettingsView.as_view(form_class=TeacherSettingsForm)(request)
if request.user.profile.is_parent:
return UserSettingsView.as_view(form_class=ParentSettingsForm)(request)
else:
if request.method == 'POST':
"""
return ***<--- I need to know what to pass here to be able to call the appropriate post function of the UserSettingsView?---->"""***
else:
return HttpResponseRedirect('/accounts/login/')
I need to be able to call the post function of the UserSettingsView. May be using the get_context_data? But I am not sure how.
Again it will be great, if someone could suggest a better way because I am pretty sure this might be violating the DRY principle. Although, I am not too concerned with that as long as the job gets done as I am running a deadline. :) Thanks!
FormView has a method get_form_class(). It is called from get() and post(), so self.request will already be set (as will be self.request.user). Consequently,
class UserSettingsView(LoginRequiredMixin, FormView):
[...]
def get_form_class(self):
# no need to check is_authenticated() as we have LoginRequiredMixin
if request.user.profile.is_student:
return StudentSettingsForm
elif user.profile.is_teacher:
return TeacherSettingsForm
elif user.profile.is_parent:
return ParentSettingsForm
This should already to the trick as you get the correct form for each user type.
If you also need to render different templates, override get_template_names():
def get_template_names(self):
if request.user.profile.is_student:
return ['myapp/settings/student.html']
elif user.profile.is_teacher:
return ['myapp/settings/teacher.html']
elif user.profile.is_parent:
return ['myapp/settings/parent.html']
DRY can be achieved using proper inheritance in the templates combining common template fragments.
And lest I forget (I already forgot): To get rid of the if in the post() method of your view, simple override the save() method of you forms which I assume are ModelForms, anyway.

Django Success_url

I built a FormView with two fields in it. I want to use the form to pass queries to a view.
class SectorForm(forms.Form):
date = forms.DateField()
days = forms.IntegerField(max_value=365, min_value=1)
Is there a way I can take my FormView and build the success_url from the valid form fields?
So overriding the get_success_url.
def get_success_url(self):
return reverse_lazy('queryable-view' date days)
I thought I could pull it out from form_valid but never had success.
get_success_url doesn't have access to the Form instance, but form_valid does. Try something like this:
from django.shortcuts import redirect
class YourFormView(FormView):
# ...
def form_valid(self, form):
date, days = form.cleaned_data['date'], form.cleaned_data['days']
return redirect('queryable-view', date, days)
I had similar problem once. When you work with forms, you may not use kwargs and get_success_url doesn't have access to the Form (as told before)
I have get around as:
class YourView(CreateView):
appointment_id =0
def form_valid(self,form):
appointment = form.save()
self.appointment_id = appointment.pk
print self.appointment_id
return super(AppointmentCreate,self).form_valid(form)
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('payment', kwargs = {'appid': self.appointment_id})

Django - Need help for TemplateView: Queryset and not updating

I am building a TemplateView with 2 forms, one to allow user to select the customer (CustomerForm) and another to add the order (OrderForm) for the customer.
Code:
class DisplayOrdersView(TemplateView):
template_name = 'orders/orders_details_form.html'
def get_context_data(self, **kwargs):
context = kwargs
context['shippingdetailsform'] = ShippingDetailsForm(prefix='shippingdetailsform')
context['ordersform'] = OrdersForm(prefix='ordersform')
return context
def dispatch(self, request, *args, **kwargs):
return super(DisplayOrdersView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
profile=request.user.get_profile()
if context['shippingdetailsform'].is_valid():
instance = context['shippingdetailsform'].save(commit=False)
instance.profile = profile
instance.save()
messages.success(request, 'orders for {0} saved'.format(profile))
elif context['ordersform'].is_valid():
instance = ordersform.save(commit=False)
shippingdetails, created = shippingdetails.objects.get_or_create(profile=profile)
shippingdetails.save()
instance.user = customer
instance.save()
messages.success(request, 'orders details for {0} saved.'.format(profile))
else:
messages.error(request, 'Error(s) saving form')
return self.render_to_response(context)
Firstly, I can't seem to load any existing data into the forms. Assuming a onetoone relationship between UserProfile->ShippingDetails (fk: UserProfile)->Orders (fk:ShippingDetails), how can I query the appropriate variables into the form on load?
Also, how can I save the data? It throws an error when saving and I have been unable to retrieve useful debug information.
Is my approach correct for having multiple forms in a templateview?
You're not passing the POST data into the forms at any point. You need to do this when you instantiate them. I would move the instantiation out of get_context_data and do it in get and post: the first as you have it now, and the second passing request.POST.
Also note that you probably want to check both forms are valid before saving either of them, rather than checking and saving each in turn. The way you have it now, if the first one is valid it won't even check the second, let alone save it, so you won't get any errors on the template if the first is valid but the second is invalid.