Django two separate forms under single page / url - django

I have two forms which I want to display and use on index page of the website.
Index page is a HomeView which in it's context contains two forms - ContactForm and SubscribtionForm.
My goal is to use these two forms on single page. So far it is impossible because each time validation fails it redirects me to view which is responsible for form validation.
Fragment of index page html code:
<form id="contact-form" class="contact form-horizontal" action="{% url 'contact' %}" method="post" novalidate>
<form id="newsletter-form" class="newsletter_form" action="{% url 'subscribe' %}" method="post" novalidate>
How do I do it in django that when I submit it will use other views to validate and save the data but responses will be displayed on Index page? Should I use javascript/ajax calls to solve this issue? Or there is another way?
Edit:
class SubscriberFormView(FormView):
form_class = SubscriberForm
template_name = 'home/subscriber_form.html'
def get_success_url(self):
return reverse('home')
def form_valid(self, form):
form.save()
return redirect(self.get_success_url())
class ContactFormView(FormView):
form_class = ContactForm
template_name = "home/contact_form.html"
def form_valid(self, form):
instance = form.save()
instance.send_confirmation()
instance.send_notification()
return redirect(self.get_success_url())
Alternative solution with prefixes:
class HomeView(FormMixin, TemplateView):
template_name = 'index.html'
form_class = ContactForm
form_class_sub = SubscriberForm
def get_success_url(self):
return reverse('home')
def get_forms(self):
contact_kwargs = self.get_form_kwargs().copy()
contact_kwargs['prefix'] = 'contact'
sub_kwargs = self.get_form_kwargs().copy()
sub_kwargs['prefix'] = 'sub'
return {
'contact_form': self.form_class(**contact_kwargs),
'subscription_form': self.form_class_sub(**sub_kwargs),
}
def post(self, request, *args, **kwargs):
forms = self.get_forms()
if 'contact-submit' in request.POST:
form = forms['contact_form']
form_name = 'contact_form'
elif 'newsletter-submit' in request.POST:
form = forms['subscription_form']
form_name = 'subscription_form'
else:
raise Http404
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form, form_name)
def form_invalid(self, form, form_name):
return self.render_to_response(self.get_context_data())
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if 'contact_form' not in data:
data['contact_form'] = self.get_forms()['contact_form']
if 'subscription_form' not in data:
data['subscription_form'] = self.get_forms()['subscription_form']
return data
def form_valid(self, form):
instance = form.save()
return super().form_valid(form)
Function based view (index):
def index(request):
contact_form = ContactForm(request.POST or None, prefix='contact')
subscription_form = SubscriberForm(request.POST or None, prefix='sub')
if request.method == "POST":
if 'contact-submit' in request.POST:
form = contact_form
elif 'sub-submit' in request.POST:
form = subscription_form
else:
raise Http404
if form.is_valid():
form.save()
return TemplateResponse(request, template="index.html", context={
'contact_form': contact_form,
'subscription_form': subscription_form,
})
Final version:
def index(request):
contact_form = ContactForm(None, prefix='contact')
subscription_form = SubscriberForm(None, prefix='sub')
if request.method == "POST":
if 'contact-submit' in request.POST:
contact_form = ContactForm(request.POST, prefix='contact')
if contact_form.is_valid():
contact_form.save()
elif 'sub-submit' in request.POST:
subscription_form = SubscriberForm(request.POST, prefix='sub')
if contact_form.is_valid():
contact_form.save()
else:
raise Http404
return TemplateResponse(request, template="index.html", context={
'contact_form': contact_form,
'subscription_form': subscription_form,
})

You should not use different view do validate data. The forms should be validated in the same view where they've been rendered.
To be able to use multiple forms in one view, simply use form prefix:
https://docs.djangoproject.com/en/1.8/ref/forms/api/#prefixes-for-forms

Related

How Do I Convert This Function Based View To An UpdateView?

I am trying to convert a function based view to a class based view. I've done it with the CreateView but the UpdateView is giving me grief. It won't take my update. I can get the view to take my update, but it doesn't save it.
Here's my function based view:
def update_task_update_view(request, pk):
task = Task.objects.get(id=pk)
form = TaskForm(request.POST or None, instance=task)
if request.method == "POST":
if form.is_valid():
form.save()
return redirect("MyTasks:task_detail", pk=task.id)
context = {
"form": form,
"task": task
}
return render(request, "partials/task_form.html", context)
And here was my attempt at a Class Based View.
class UpdateTaskUpdateView(LoginRequiredMixin,UpdateView):
model = Task
form_class = TaskForm
template_name = 'partials/task_form.html'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
task = form.save()
task.save()
return redirect("MyTasks:task_detail", pk=task.id)
else:
return render(request, "partials/task_form.html", {
"form":form
})
This function based view is working fine, no issues with it.
Thanks to an assist from FB...Travis Tucker....
I did this instead and it seems to be working fine...
class UpdateTaskUpdateView(LoginRequiredMixin,UpdateView):
model = Task
form_class = TaskForm
template_name = 'partials/task_form.html'
def form_valid(self, form):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
task = form.save()
task.save()
return redirect("MyTasks:task_detail", pk=task.id)
else:
return render(request, "partials/task_form.html", {
"form":form
})

context data wont display anything in detailview

I'm trying to display this models' object where the user is as same as the detail view user. here is my views:
class ScientificInfoView(FormMixin, DetailView):
model = ScientificInfo
template_name = 'reg/scientific-info.html'
form_class = ScientificInfoForm
def get_success_url(self):
messages.success(self.request, 'Profile details updated.')
return reverse('scientific-info', kwargs={'pk': self.object.pk})
def get_context_date(self, **kwargs):
context = super(ScientificInfoView, self).get_context_data(**kwargs)
#THIS IS THE LINE THAT WONT WORK#
context['result'] = Results.objects.filter(user=self.object.user)
context['sample'] = 'sample text sample text'
#################################
context['form'] = ScientificInfoForm()
return context
def post(self, request, pk):
self.object = self.get_object()
form = ScientificInfoForm(request.POST, instance=get_object_or_404(ScientificInfo, id=pk))
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
f = form.save(commit=False)
f.user = self.object.user
f.save()
return super(ScientificInfoView, self).form_valid(form)
everything works fine except the result and sample data. it shows nothing in the template. It cant even render a simple text!
this is the templates:
{% for r in result %}
{{r}}
{% endfor %}
{{ sample }}
This is just a typo, the name is get_context_data and not get_context_datE.

Submitting a form inside a detail view creates a new object instead of replacing it

I’m trying to create a dashboard for the staff users to fill in and edit some information regarding their users. The form works and saves successfully, but when I submit it for a second time, it creates a new object. It won’t replace the previous:
This is my views.py file:
class ScientificInfoView(FormMixin, DetailView):
model = ScientificInfo
template_name = 'reg/scientific-info.html'
form_class = ScientificInfoForm
def get_success_url(self):
return reverse('scientific-info', kwargs={'pk': self.object.pk})
def get_context_date(self, **kwargs):
context = super(ScientificInfoView, self).get_context_data(**kwargs)
context['form'] = ScientificInfoForm()
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
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(ScientificInfoView, self).form_valid(form)
And my template:
<form method="POST" enctype="multipart/form-data" action="{% url 'scientific-info' pk=object.id %}">
{% csrf_token %}
{{form}}
<button type="submit">submit</button>
</form>
File urls.py:
path('surveys/scientific/<pk>', login_required(views.ScientificInfoView.as_view()), name='scientific-info')
I’m pretty sure that the action part in my form is causing the issue, but how can I solve it?
Use this:
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("scientific-info", kwargs={"pk": pk})
Or
class ScientificInfoView(FormMixin, DetailView):
model = ScientificInfo
template_name = 'reg/scientific-info.html'
form_class = ScientificInfoForm
def get_success_url(self):
return reverse("scientific-info", args=[pk]) # You can replace pk

converting CBV to FBV

I am trying to change all my Function Based View to Class based view, i’ve been fairly successful except for this view, it’s a detail view that contains paystack payment gateway. Any help will be hugely appreciated.
def car_rent_detail_view(request, pk):
object = get_object_or_404(CarRent, id=pk)
paystack = PaystackAccount(
settings.PAYSTACK_EMAIL,
settings.PAYSTACK_PUBLIC_KEY,
object.total_cost
)
context = {'object': object, 'pk_public': settings.PAYSTACK_PUBLIC_KEY, 'currency': 'NGN', 'paystack': paystack,
}
if request.method == 'POST':
if paystack.verify_transaction(request.POST['reference']):
messages.success(request, "payment successfull")
…
car_rented.save()
…
rent_activation.save()
messages.success(request, "Rent successfully updated")
return render(request, 'app/CarRent_detail.html', context=context)
I will like to convert the CBV below to FBV so i can add payment functionality to it.
class ContestantDetail(DetailView, FormMixin):
model = Contestant
context_object_name = 'contestants'
template_name = 'contest/contestant_detail.html'
form_class = VoteForm
def get_success_url(self):
return reverse('contest:contestant-detail', kwargs={'pk': self.object.pk})
def get_context_data(self, *args, **kwargs):
context = super(ContestantDetail, self).get_context_data(*args, **kwargs)
context['vote_contestant'] = Contestant.objects.get(pk=self.kwargs.get('pk'))
return context
def post(self, request, *args, **kwargs):
form = self.get_form()
self.object = self.get_object()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form, *args, **kwargs):
contestant = Contestant.objects.get(pk=self.kwargs['pk'])
...
contestant.save()
messages.success(self.request, f'You have successfully casted {vote_count} vote.')
return super().form_valid(form)
The Class based View above can be converted to a Function based view as demonstrated below.
def contestant_detail_view(request, pk):
get_object_or_404(Contestant, pk=pk)
form = VoteForm()
context = {'contestants': get_object_or_404(Contestant, pk=pk),
'vote_contestant': Contestant.objects.get(pk=pk),
'form': form}
if request.method == 'POST':
form = VoteForm(request.POST)
if form.is_valid():
con = Contestant.objects.get(pk=pk)
...
con.save()
else:
form = VoteForm()
return render(request, 'contest/contestant_detail.html', context)

Django CreateView without template rendering

I have a problem with saving a form in my CreateView, I found
this solution and it worked for me:
class ScheduleDocumentView(CreateView):
def post(self, request, pk, *args, **kwargs):
form = ScheduleDocumentForm(request.POST, request.FILES)
if form.is_valid():
form.instance.relates_to = Schedule.objects.get(pk=pk)
form.save()
return redirect('planning:schedule-detail', pk=pk)
However my goal is to save a form using form_valid() and get_success_url() without a template in CreateView. I tried something like this(doesn't work):
class ScheduleDocumentView(CreateView):
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.relates_to = Schedule.objects.get(pk=pk)
form.save()
return redirect('planning:schedule-detail', pk=pk)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': pk})
It requires a template, is there any other way to handle my post request in DetailView, process it in separate CreateView and redirect it to my DetailView page?
Here's my template for DetailView:
<form enctype="multipart/form-data" action="{% url 'planning:upload-document' schedule.pk %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button button--secondary">Attach</button>
Urls:
path('schedules/<int:pk>/', ScheduleDetailView.as_view(), name='schedule-detail'),
path('schedules/<int:pk>/upload-document/', ScheduleDocumentView.as_view(), name='upload-document'),
I came across this solution:
class ScheduleDocumentView(CreateView):
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.schedule = Schedule.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': self.kwargs['pk']})
template_name is required Django Docs:
The full name of a template to use as defined by a string. Not
defining a template_name will raise a
django.core.exceptions.ImproperlyConfigured exception.
Or in your case Django would cause the default template_name to be 'yourapp/scheduledocument_create_form.html'.
Therefore you get the error TemplateDoesNotExist.
You can get the pk value from self.kwargs(Django Docs).
You can simple create the blank.html template.
class ScheduleDocumentView(CreateView):
http_method_names = ['post']
template_name = 'blank.html' # or use this 'schedule_detail.html'
model = ScheduleDocument
form_class = ScheduleDocumentForm
def form_valid(self, form):
form.instance.relates_to = Schedule.objects.get(pk=self.kwargs.get("pk"))
return super().form_valid(form)
def get_success_url(self):
return reverse('planning:schedule-detail', kwargs={'pk': self.kwargs.get("pk")})
Or use A simple view:
def create_schedule_document(request, pk):
if request.method == 'POST':
form = ScheduleDocumentForm(request.POST, request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.relates_to = Schedule.objects.get(pk=pk)
obj.save()
else:
form = ApplyAnonymousForm()
return redirect('planning:schedule-detail', pk=pk)