How to access form data in `FormView.get_success_url()` - python

Currently, I have a form that looks like this:
class SelectBoard(generic.FormView):
form_class = forms.SelectBoard
def form_valid(self, form):
board_name = form.cleaned_data['name']
return redirect('leaderboard', board_name=board_name)
However, it would be my preference to do the more idiomatic thing, and use get_success_url. Something like this:
class SelectBoard(generic.FormView):
form_class = forms.SelectBoard
def get_success_url(self):
form = ???
board_name = form.cleaned_data['name']
return reverse('leaderboard', board_name=board_name)
However, the form is not passed to get_success_url, and unlike many other pieces of request context (like self.request, self.kwargs, or self.object (in DetailView)), the form is not attached as an attribute at any point in the standard FormView dispatch sequence. Is there any good way to access cleaned & validated form data in get_success_url (that is, without having to access self.request.POST or reconstruct the form from scratch)?

You can override form_valid method to write form as instance attribute and use self.form inside get_success_url:
class SelectBoard(generic.FormView):
form_class = forms.SelectBoard
def form_valid(self, form):
"""If the form is valid, redirect to the supplied URL."""
self.form = form
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
board_name = self.form.cleaned_data['name']
return reverse('leaderboard', board_name=board_name)

You might set the success_url attribute in form_valid method getting the form field value if it is needed
class SelectBoard(generic.FormView):
form_class = forms.SelectBoard
def form_valid(self, form):
board_name = form.cleaned_data['name']
self.success_url = reverse('leaderboard', board_name=board_name)
return super().form_valid(form)

Related

How can I display form instance?

How can I pass the form with an actual filled profile informations (like ProfileUpdateForm(instance=x) in function views).
I did it this way and have no idea how to pass an actual profile instance to the form. Something like UpdateView, but in my DetailView class
class ProfileDetailView(FormMixin, DetailView):
model = Profile
context_object_name = 'profile'
template_name = 'profiles/myprofile.html'
form_class = ProfileUpdateForm
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('profiles:profile', kwargs={'pk': self.get_object().pk})
If you want to update an instance of a model you should use UpdateView [Django docs]:
from django.urls import reverse
from django.views.generic.edit import UpdateView
class ProfileDetailView(UpdateView):
model = Profile
context_object_name = 'profile'
template_name = 'profiles/myprofile.html'
form_class = ProfileUpdateForm
def get_success_url(self):
# Use `reverse` instead of `reverse_lazy`. You will get an error otherwise!
return reverse('profiles:profile', kwargs={'pk': self.get_object().pk})
Also if there is any need to pass a keyword argument to the form (here instance, although I would suggest to stick with UpdateView) you should override get_form_kwargs:
class MyView(SomeGenericView):
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['some_kwarg'] = 'Some value'
# Your use case implies below line
# kwargs['instance'] = self.get_object()
return kwargs

Error when trying to assign a group to a user

I am trying to assign a group to a user but without the need to use the django manager, but I run into a problem and it tells me that "<User: island>" must have a value for the field "id" before this many-to-many relationship can be used.
This is my view:
class UserCreationView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
permission_required = 'users.add_user'
login_url = 'users:login'
template_name = 'users/register.html'
model = User
form_class = UserForm
success_message = 'El usuario fue creado exitosamente'
success_url = reverse_lazy('users:user')
def form_valid(self, form):
self.object = form.save(commit=False)
group = Group.objects.get(pk=self.request.POST.get('groups'))
self.object.groups.add(group)
self.object.save()
Simply rearrange order of statements
self.object = self.object.save()
self.object.groups.add(group)
Also there is no need to add instance to object you can just use normal variable instead, and your parameter would be better called group/group_id instead of groups
def form_valid(self, form):
user = form.save()
group = Group.objects.get(pk=self.request.POST.get('group_id'))
user.groups.add(group)

Django Dynamic Formset UpdateView Not Updating

I used this tutorial to successfully set up a dynamic inline formset using Django. The CreateView works great, but I cannot get the UpdateView to actually update the related fields. There are no errors thrown, but the items will not update. I believe I have isolated the error to the form_valid function. The code is as follows. Thank you for any assistance.
class ApplicantCreate(CreateView):
model = Applicant
success_message = 'Your application was submitted successfully.'
form_class = forms.ApplicantForm
template_name = 'careers/add_applicant.html'
success_url = reverse_lazy('careers:thanks')
def get_context_data(self, **kwargs):
data = super(ApplicantCreate, self).get_context_data(**kwargs)
positions = super(ApplicantCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['employer'] = forms.ApplicantEmployerFormSet(
self.request.POST,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
self.request.POST,
prefix='education')
else:
data['employer'] = forms.ApplicantEmployerFormSet(prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(prefix='education')
return data
context['unfilled_positions'] = Position.objects.filter(filled=False)
return positions
def form_valid(self, form):
context = self.get_context_data()
employer = context['employer']
education = context['education']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if employer.is_valid():
employer.instance = self.object
employer.save()
if education.is_valid():
education.instance = self.object
education.save()
return super(ApplicantCreate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('careers:thanks')
class ApplicantUpdate(SuccessMessageMixin,LoginRequiredMixin,GroupRequiredMixin,UpdateView):
group_required = [u'careers-admin',u'careers']
model = Applicant
success_message = '%(first_name)s %(last_name)s was updated successfully.'
form_class = forms.ApplicantUpdateForm
template_name = 'careers/edit_applicant.html'
def get_context_data(self, **kwargs):
data = super(ApplicantUpdate, self).get_context_data(**kwargs)
positions = super(ApplicantUpdate, self).get_context_data(**kwargs)
if self.request.POST:
data['employer'] = forms.ApplicantEmployerFormSet(
self.request.POST,
instance=self.object,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
self.request.POST,
instance=self.object,
prefix='education')
else:
data['employer'] = forms.ApplicantEmployerFormSet(
instance=self.object,
prefix='employer')
data['education'] = forms.ApplicantEducationFormSet(
instance=self.object,
prefix='education')
return data
context['unfilled_positions'] = Position.objects.filter(filled=False)
return positions
def form_valid(self, form):
context = self.get_context_data()
employer = context['employer']
education = context['education']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if employer.is_valid():
employer.instance = self.object
employer.save()
if education.is_valid():
education.instance = self.object
education.save()
return super(ApplicantUpdate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('careers:applicant_detail',kwargs={'pk': self.object.pk})
This is not a good blog post, because no errors are shown if any of the inline forms aren't valid. As a user, I wouldn't expect the view to just silently ignore errors in the inline forms, saving my main instance successfully and not reporting back those errors.
Note that I don't know what the errors are, maybe it's just an issue with the management form. But in any case, formset errors should be handled before the main object is actually saved.
In general, if you need to write a view with multiple forms (including a formset), it's better to use a function-based view or a View where you write the get and post than trying to force this into a generic class-based view, which is not meant for this.
def multiple_forms_view(request, object_id):
# if this is a POST request we need to process the form data
obj = get_object_or_404(MyModel, pk=object_id)
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = MyForm(request.POST, instance=obj)
formset = MyFormSet(request.POST, instance=obj, ...)
# check whether it's valid:
if form.is_valid() and formset.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = MyForm(instance=obj)
formset = MyFormSet(instance=obj, ...)
return render(request, 'name.html', {'form': form, 'formset': formset})
That way your template can render the errors of each form, including those in the formset. As mentioned before, you could also do this in a View (so your mixins will work), just write the get and post methods and return the same as in the function-based view above.

Django signal is sending two times

I'm trying to use Django signals in my application but the post_save signal is sending two times, and the post_delete is sending once like it's supposed to.
My signals.py:
#receiver([post_save,post_delete], sender=Award)
def print_request(sender, **kwargs):
print('Request finished!')
The result in my terminal when I save an object:
Request finished!
Request finished!
My CreateView:
class ResearcherPrizeCreateView(LoginRequiredMixin, CreateView):
model = Award
form_class = ResearcherPrizeCreateForm
success_url = reverse_lazy('prize_list')
template_name = 'researcher/award/award_create_form.html'
def form_valid(self, form):
researcher = Researcher.objects.get(user_id=self.request.user.id)
self.object = form.save(commit=False)
self.object.researcher = researcher
self.object.save()
return super(ResearcherPrizeCreateView, self).form_valid(form)
My form.py
class ResearcherPrizeCreateForm(forms.ModelForm):
class Meta:
model = Award
fields = ('title', 'category')
widgets = {
'title':forms.TextInput(attrs={
'class':'form-control',
'placeholder':'Titulo da Premiação'
}),
'category':forms.Select(attrs={
'class':'form-control',
})
}
The object is being saved once when you call self.object.save(), and again when you call super().form_valid(form).
One option is to redirect in your form_valid method.
def form_valid(self, form):
researcher = Researcher.objects.get(user_id=self.request.user.id)
self.object = form.save(commit=False)
self.object.researcher = researcher
self.object.save()
return redirect(self.success_url)
Another option is to assign the researcher to the form instance then call super(...).form_valid() to save the form and redirect.
def form_valid(self, form):
researcher = Researcher.objects.get(user_id=self.request.user.id)
form.instance.researcher = researcher
return super(ResearcherPrizeCreateView, self).form_valid(form)

django-'NoneType' object is not callable

I got this error, when redirecting to the result page. Is that because in the redirected page, the "POST.get" function cannot get what the user has entered in the form before redirecting?
views.py
class InputFormView(FormView):
template_name = 'inputform.html'
form_class = InputForm
def get_success_url(self):
return ''.join(
[
reverse('result'),
'?company=',self.request.POST.get('company'),
'&region=',self.request.POST.get('region') <is there anything wrong here---?
]
)
class ReulstView(FormView):
context_object_name = 'result_list'
template_name = 'result_list.html'
model = Result
if form.is_valid():
company = form.cleaned_data['company']
region = form.cleaned_data['region']
self.queryset=Result.objects.filter(region=region)
return render(request, 'result_list.html', {'form': form})
def get_context_data(self, **kwargs):
context = super(ReulstView, self).get_context_data(**kwargs)
context["sales"] = self.get_queryset().aggregate(Sum('sales'))
context["company"] = self.request.POST.get("company")
context["region"] = self.request.POST.get("region")
return context
def get_queryset(self):
return Result.objects.all()
Traceback:
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\core\handlers\base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\base.py" in view
71. return self.dispatch(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\edit.py" in get
205. form = self.get_form()
File "C:\Python27\lib\site-packages\django-1.8.3-py2.7.egg\django\views\generic\edit.py" in get_form
74. return form_class(**self.get_form_kwargs())
Exception Type: TypeError at /result_list/
Exception Value: 'NoneType' object is not callable
Inputform
class InputForm(forms.ModelForm):
company=forms.CharField(widget=forms.TextInput, label="Company",error_messages={'required': 'Please enter the company name'},required=True)
#region This form can display correctly with drop down list
iquery = Dupont.objects.values_list('region', flat=True).distinct()
iquery_choices = [('', 'None')] + [(region,region) for region in iquery]
region = forms.ChoiceField(choices=iquery_choices)
For the same function, I tried another code, even though without error but cannot display the form data, it displays as none. Hope you can answer my question in the following link:
django- why after redirecting, the form display "None"
I think this is your issue: You are using a FormView but haven't defined a form class to use. Either set a form_class attr on the class, or override the get_form_class method:
class ReulstView(FormView):
context_object_name = 'result_list'
template_name = 'result_list.html'
model = Result
form_class = InputForm
Also, the form_valid method will receive the form instance, you don't need to instantiate it manually:
def form_valid(self, form, **kwargs):
form = InputForm(self.request.POST) # <- remove this line
if form.is_valid():
...
Lets make another thread. :)
The only way you can get "return outside function" error - you are trying to return something not from function. :) It could happen usually because of misstype or wrong indentation.
Could you please provide the code where you get this error? I believe that there is something wrong with indentation there.
class ReulstView(FormView):
context_object_name = 'result_list'
template_name = 'result_list.html'
model = Result
if form.is_valid(): # <- it is not a function or method
# it is a class declaration
company = form.cleaned_data['company']
region = form.cleaned_data['region']
self.queryset=Result.objects.filter(region=region)
return render(request, 'result_list.html', {'form': form})
def get_context_data(self, **kwargs): #<- and this is a method
#... all your following code
I'm not familiar with Django FormViews, but looks like correct code could be like this:
class ResultView(FormView):
context_object_name = 'result_list'
template_name = 'result_list.html'
model = Result
def form_valid(self, form):
company = form.cleaned_data['company']
region = form.cleaned_data['region']
self.queryset=Result.objects.filter(region=region)
return render(request, 'result_list.html', {'form': form})
Chances are that the problem is with your forms.py file. When you are importing and using the model in your forms.py file you are using parenthesis to define your model which you should not. For example if your model is lets suppose Post,
It should be defined as Post instead of Post()

Categories