What is the crispiest way to add a new, empty form to a modelformset with existing forms?
forms.py:
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = '__all__'
MyFormSet = modelformset_factory(MyModel, extra=1, exclude=(), form=MyForm)
class MyFormsetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(MyFormsetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.template = 'bootstrap/table_inline_formset.html'
self.add_input(Submit("submit", "Save"))
self.add_input(Button("add", "Add New"))
template:
{% extends "base.html" %}
{% load staticfiles %}
{% load crispy_forms_tags %}
{% block stylesheets %}
{{ block.super }}
{% endblock stylesheets %}
{% block content %}
{% crispy formset helper %}
{% endblock content %}
I have an add button, but I don't see how to tie that to an action. I also don't know how crispy forms would create an empty form. Prior to discovering crispy forms, I was writing out all the template code and used something like the below to render the empty form.
<div id="empty_form" style="display:none">
<table class='no_error'>
<tr>
<td>{{ modelformset.empty_form.Field1 }}</td>
<td>{{ modelformset.empty_form.Field2 }}</td>
<td>{{ modelformset.empty_form.Field3 }}</td>
</tr>
</table>
</div>
<input type="button" value="Add" id="add_more">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$('#add_more').click(function() {
var form_idx = $('#id_form-TOTAL_FORMS').val();
$('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});
</script>
If there's no crispy way to do this, is there a way to combine the code above with the crispy formset? I tried, but nothing happens.
Thank you for any insight you all might have.
I use this code and it works fine...
button
<input type="button" value="Add" id="add_more" onclick="add_form('empty_form')">
javascript
<script>
function add_form(formset) {
let total = $("#id_" + formset + "-TOTAL_FORMS").val();
let row = $("#" + formset + "_table tr:last")[0]; // find row to copy
let table = $("#" + formset + "_table tbody")[0]; // find table to append to
let clone = row.cloneNode(true); // copy children too
clone.innerHTML = clone.innerHTML.replaceAll("-" + (total - 1) + "-", "-" + total + "-");
table.appendChild(clone); // add new row to end of table
$("#id_" + formset + "-TOTAL_FORMS").val(++total);
}
</script>
Related
I currently have an app that is meant to generate documents based on user-set settings. The settings are supposed to be read by the program based on the class's instance settings so that the program knows which setting it is generating documents for.
When creating the instance settings for the user to edit each model entry, the code works like this:
setting = get_object_or_404(SettingsClass, pk=setting_pk)
if request.method == 'GET':
form = SettingUpdateForm(instance=setting)
return render(request, 'main/viewSettings.html', {'setting': setting, 'form':form})
else:
form = SettingUpdateForm(request.POST, instance=setting)
if form.is_valid():
form.save()
return redirect('settingsHome')
So it uses the "get_object_or_404" function to return the instance that the user should be working in and tells the form to use that instance here :form = SettingUpdateForm(instance=setting)
However when I am trying to execute this while reading from the model , the same type of setup does not work. This is what I have set up in my views at the moment:
def printReports(request , reports_pk):
pkForm = get_object_or_404(SettingsClass , pk=reports_pk)
form= SettingsClass(instance=pkForm)
complexName = form.Complex
I am basically trying to tell the program to work in a certain instance of the model and read items from there, like: complexName = form.Complex
If anyone has any solution or alternate way to setup something like this , please assist. I will add the my URLs, templates and views code below for better viewing
Views.py:
def reportsHome(request):
model = SettingsClass.objects.all().order_by('Complex')
content ={'model':model }
return render(request, 'main/reportsHome.html' , content)
def printReports(request , reports_pk):
pkForm = get_object_or_404(SettingsClass , pk=reports_pk)
form= SettingsClass(instance=pkForm)
complexName = form.Complex
#CHECKING TRIAL BALANCE SETTINGS
if form.Trial_balance_Year_to_date == True:
printTrialBalanceYTD = True
### Printing Trial Balance PDF
response = HttpResponse(content_type= 'application/pdf')
response['Content-Disposition']= 'attachment; filename=TrialBalance' + \
str(datetime.now()) + '.pdf'
response['Content-Transfer-Encoding'] = 'binary'
#SQL STATEMENT
baseSelect = 'SELECT '+ op5 + op4 + ' Debit , Credit FROM [?].[dbo].[PostGL] AS genLedger '
xtrbYTD = baseSelect + trbYTD + op1 + op2 + op3 + op6
cursor = cnxn.cursor();
cursor.execute(baseTRBYear, [complexName], [complexName], [complexName], [one_yrs_ago]);
xAll = cursor.fetchall()
cursor.close()
xtrbYTD = []
for row in xtrbYTD:
rdict = {}
rdict["Description"] = row[0]
rdict["Account"] = row[1]
rdict["Debit"] = row[2]
rdict["Credit"] = row[3]
arr_trbYTD.append(rdict)
content = {"arr_trbYTD":arr_trbYTD , 'xCreditTotal':xCreditTotal , 'xDebitTotal':xDebitTotal , 'complexName':complexName , 'openingBalances': openingBalances ,'printZero':printZero}
html_string=render_to_string('main/pdf-trialbalance.html' , content)
html=HTML(string=html_string)
result=html.write_pdf()
with tempfile.NamedTemporaryFile(delete=True) as output:
output.write(result)
output.flush()
output.seek(0)
response.write(output.read())
return response
else:
printTrialBalanceYTD = False
urls.py:
#Reports
path('accConnect' , views.reportsHome, name='reportsHome'),
path('accConnect/printReports/<int:reports_pk>' , views.printReports , name='printReports')
TEMPLATES:
reportsHome.html:
{% block content%}
<h1 style=" text-align: center">Reports</h1>
<hr>
<br>
<div class="list-group">
<a href="#" class='list-group-item active'>Print Single Complex's</a>
{% for x in model %}
<a href="{% url 'printReports' %}" class="list-group-item list-group-item-action" >{{ x.Complex }} Reports</a>
{% endfor %}
</div>
{% endblock %}
pdf-trialbalance.html:
{% block content%}
<h1 class = 'center'>Kyle Database Trial Balance</h1>
<br>
</div>
<br>
<br>
<div class="table-container">
<table style="width: 100%">
<th >Account</th>
<th>Description</th>
<th>Debit</th>
<th>Credit</th>
{% for arr_trbYTD in arr_trbYTD %}
<tr>
<td>{{ arr_trbYTD.Description }}</td>
<td>{{ arr_trbYTD.Account }}</td>
<td>
{%if arr_trbYTD.Debit > 0%}
{{arr_trbYTD.Debit}}
{%endif%}
</td>
<td>
{%if arr_trbYTD.Credit > 0%}
{{arr_trbYTD.Credit}}
{%endif%}
</td>
</tr>
<tr >
{% endfor %}
<td> <b>Totals</b> </td>
<td> </td>
{% for xDebitTotal in xDebitTotal %}
<td><b>R {{ xDebitTotal }}</b></td>
{% endfor %}
{% for xCreditTotal in xCreditTotal %}
<td><b>R {{ xCreditTotal }}</b></td>
{% endfor %}
</tr>
</table>
</div>
<br>
<br>
<br>
{% endblock %}
To solve the error in the title, first you need to fix the usage of url in the template and pass the primary key of the SettingsClass instance with:
{% url 'printReports' x.pk %}
So:
{% for x in model %}
<a href="{% url 'printReports' x.pk %}" class="list-group-item list-group-item-action" >{{ x.Complex }} Reports</a>
{% endfor %}
And then in your view, you don't need to use a form to access the attributes from your SettingsClass instance, so you can just do something like:
def printReports(request , reports_pk):
settings_instance = get_object_or_404(SettingsClass , pk=reports_pk)
complexName = settings_instance.Complex
#CHECKING TRIAL BALANCE SETTINGS
if settings_instance.Trial_balance_Year_to_date == True:
...
You are not passing the "reports" variable to context. that's why the PK value is empty. check the view that's rendering reportsHome.html and apply the necessary, variable to template eg:
def view_func(request)
......
return render(request, '<template_name>.html', {"report": report })
As the title states, I have a table that I'm attempting to update that only updates the final value in the post, from what I understand if I want to update multiple records I must iterate over my request object and update a form instance with the specified ID in my db.
Before submission all records have a price_checked of 0.
and then after - you can see the final value from the post request updates all the records!
postgres table
The code in question that updates my model form instance.
def post(self,request):
if request.method=='POST':
for key in request.POST.getlist('product_id'):
product_update = ProductTable.objects.get(id=key)
form = ProductUpdateForm(request.POST,instance=product_update)
print(form)
if form.is_valid():
form.save()
messages.success(request,message='price checked...')
return redirect('product')
is anyone able to assist? I've been at this point for over 2 weeks.
models/form/view for reference.
models.py
from django.db import models
class ProductTable(models.Model):
id = models.AutoField(
primary_key=True,
editable=False
)
product_name = models.TextField(max_length=255,null=False)
price = models.DecimalField(max_digits=6,decimal_places=2,null=False)
from_date = models.DateTimeField()
to_date = models.DateTimeField(null=True)
price_checked = models.IntegerField(default=0,null=False)
def __str__(self : str) -> str:
return f"{self.product_name} - {self.id} with a price check of {self.price_checked}"
forms.py
from django.forms import ModelForm
from .models import ProductTable
class ProductUpdateForm(ModelForm):
class Meta:
model = ProductTable
fields = ('price_checked',)
views.py
from typing import Any, Dict
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.views.generic import ListView
from django.db import transaction
from .forms import ProductUpdateForm
from .models import ProductTable
class TableDataView(LoginRequiredMixin,ListView):
model = ProductTable
context_object_name = 'product'
template_name = 'tables/product.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context['product'] = ProductTable.objects.filter(pk__in= [4607,4642, 4645])
return context
def post(self,request):
if request.method=='POST':
for key in request.POST.getlist('product_id'):
product_update = ProductTable.objects.get(id=key)
form = ProductUpdateForm(request.POST,instance=product_update)
print(form)
if form.is_valid():
form.save()
messages.success(request,message='price checked...')
return redirect('product')
product.html
{% extends '_base.html' %} {% block content %} {% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Date</th>
<th>Input</th>
</tr>
</thead>
<tbody>
{% for data in product %}
<tr>
<th>{{ data.product_name }}</th>
<th>{{ data.price }}</th>
<th>{{ data.from_date }}</th>
<th>
<input type="hidden" name="product_id" value="{{data.id}}" />
<input class="form" name="price_checked" id="priced_checked" type="number" placeholder="{{ data.price_checked }}"/>
<input type="submit" value="OK">
</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
{% endblock content %}
request.POST
<QueryDict: {'csrfmiddlewaretoken': ['p0j3e0UbrY1VuFEAnJWopaCICCxOPj8v2OkLRZZiGlUa4YtxGwduD2bAIrm91VKe'], 'product_id': ['4607', '4642', '4645'], 'price_checked': ['1', '2', '3']}>
after read formset documents,I think it should looks like this.
the view python return a formset to template,and in the post function create formset by request.POST
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
ProductFormSet = modelformset_factory(ProductTable)
context['product_formset'] = ProductFormSet(queryset=ProductTable.objects.filter(pk__in= [4607,4642, 4645]))
return context
def post(self,request):
if request.method=='POST':
ProductFormSet = modelformset_factory(ProductTable)
formset = ProductFormSet(request.POST)
if formset.is_valid():
formset.save()
messages.success(request,message='price checked...')
return redirect('product')
in the template I have mark the changes,the important thing is {{ product_formset.management_form }} in the begin form.and {{ form.id }} of each loop,the each cell data should be {{ form.product_name.value() }} or {{ form.product_name.data }},you can try that
{% extends '_base.html' %} {% block content %} {% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<!-- changed -->
{{ product_formset.management_form }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Date</th>
<th>Input</th>
</tr>
</thead>
<tbody>
<!-- changed -->
{% for form in product_formset %}
{{ form.id }}
<tr>
<td>{{ form.product_name.value() }}</td>
<td>{{ form.price.value() }}</td>
<td>{{ form.from_date.value() }}</td>
<td>
{{ form.price_checked }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<input type="submit" value="submit">
</form>
{% endblock content %}
maybe this will not help you,because I never used the django form things.
I guess you are a precise man who want to follow every django's principle.
But as I know,traditional form has much limitation,some ajax or reactjs has more flexible.
A couple changes to support formsets:
Use the same formset for updating and displaying the objects, but disable the fields you don't want to be updated (make them read-only):
class ProductUpdateForm(ModelForm):
disabled_fields = ['product', 'price', 'date_added', 'date_removed']
class Meta:
model = ProductTable
fields = ('product', 'price', 'date_added', 'date_removed', 'price_checked',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.disabled_fields:
self.fields[field].disabled = True
Then update the view to use the form for displaying and for updating:
from django.forms import modelformset_factory
class TableDataView(LoginRequiredMixin,ListView):
model = ProductTable
context_object_name = 'product'
template_name = 'tables/product.html'
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
ProductTableFormset = modelformset_factory(ProductTable, form=ProductUpdateForm,)
context['formset'] = ProductTableFormset(queryset=ProductTable.objects.filter(pk__in=[4607,4642, 4645]))
return context
def post(self,request):
ProductTableFormset = modelformset_factory(ProductTable, form=ProductUpdateForm)
if request.method == 'POST':
formset = ProductTableFormset(request.POST, queryset=ProductTable.objects.filter(pk__in=[4607,4642, 4645]))
if formset.is_valid():
formset.save()
messages.success(request,message='price checked...')
return redirect('product')
And in your template, render the whole formset:
{% extends '_base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<form method="POST" action="">
{% csrf_token %}
{{ form|crispy }}
<div class="container mx-flex pt-5 mb-5">
<table class="table" id="table">
{{ formset }}
</table>
</div>
</form>
{% endblock content %}
EDIT: If you want the simplest change you can do to fix this (so you don't have to work on the styles for the formset), you can do away with the form for the post and update the products as is:
def post(self,request):
if request.method == 'POST':
products_to_update = []
for id, price_checked in zip(request.POST.getlist('product_id'), request.POST.getlist('price_checked')):
product = ProductTable.objects.get(id=id)
product.price_checked = price_checked
products_to_update.append(product)
ProductTable.objects.bulk_update(products_to_update, ['price_checked'])
messages.success(request,message='price checked...')
return redirect('product')
I'm new to django and I'm trying to write an application that given an activity allows me to save the information of the partecipants (i.e. Person) and how long the partecipation was.
I was able to setup a CreateView, with bootstrap and JavaScript to allow my formset to be dynamic and I'm able to register the activity and the partecipants though the create view.
However, when I try to modify an already existing activity, using an UpdateView, I get several errors.
I tried to reproduce a minimal example of my problem and I got the error "This field is required" that is one of the error I obtain in my main application. I'm trying also to reproduce the second error: the instruction formset.is_valid() in views.py return False since it has an Activity already linked to the Person (violating the unique_together rule). This happens everytime: even if I'm just trying to add a second person to the form without changing anything about the first one.
Please, let me know if I missed some files: here's the small example.
models.py
from django.db import models
# Create your models here.
class Person(models.Model):
surname = models.CharField(max_length=30)
class Activity(models.Model):
name = models.CharField(max_length=30)
person = models.ManyToManyField(Person, through='ActivityThrough')
def get_absolute_url(self):
return reverse('activity-update', kwargs={'pk': self.pk})
class ActivityThrough(models.Model):
activity = models.ForeignKey(to='Activity', on_delete=models.PROTECT)
person = models.ForeignKey(to='Person', on_delete=models.PROTECT)
hours = models.DecimalField(max_digits=5, decimal_places=2)
class Meta:
unique_together = [['person', 'activity']]
views.py
from django.contrib import messages
from django.shortcuts import render
from .models import Activity, ActivityThrough
from .forms import activity_through_formset, CreateActivityForm
from django.views.generic.edit import UpdateView
from django.http import HttpResponseRedirect
# Create your views here.
class UpdateActivity(UpdateView):
model = Activity
form_class = CreateActivityForm
template_name = 'minimal/activities.html'
def get_context_data(self, **kwargs):
context = super(UpdateActivity, self).get_context_data(**kwargs)
if self.request.POST:
context['formset'] = activity_through_formset(self.request.POST, instance=context['activity'])
else:
context['formset'] = activity_through_formset(instance=context['activity'])
context['empty_formset'] = activity_through_formset().empty_form
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form(self.form_class)
qs = ActivityThrough.objects.filter(activity=self.object)
formset = activity_through_formset(self.request.POST, queryset=qs)
if (form.is_valid() and formset.is_valid()):
return self.form_valid(form, formset)
else:
for f in formset:
print(f.errors)
return self.form_invalid(form, formset)
def form_valid(self, form, formset):
self.object = form.save()
formset.instance = self.object
formset.save()
messages.add_message(self.request, messages.SUCCESS, 'Update was successful')
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
messages.add_message(self.request, messages.ERROR, 'Updat cannot be performed')
return self.render_to_response(self.get_context_data(form=form, formset=formset))
In my urls.py
path('activity/<int:pk>/', views.UpdateActivity.as_view(), name='activity-update'),
forms.py
from django import forms
from .models import Person, Activity, ActivityThrough
class CreateActivityForm(forms.ModelForm):
class Meta:
model = Activity
fields = ['name']
name = forms.CharField(required=True, label='Name')
class ActivityThroughForm(forms.ModelForm):
class Meta:
model = ActivityThrough
fields = '__all__'
person = forms.ModelChoiceField(
queryset=Person.objects.all(),
required=True,
label='Person'
)
hours = forms.DecimalField(label='Hours', required=True)
activity_through_formset = forms.inlineformset_factory(
Activity,
ActivityThrough,
fields=['person', 'hours'],
form=ActivityThroughForm,
extra=1,
can_delete=False
)
And the template activities.html
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<div style="padding: 0 0 1rem">
<!-- field label -->
<b>People:</b>
*
<!-- end field label -->
<br>
<!-- field rendering -->
<div id="formset-template-{{ formset.prefix }}" style="display:none;" generic_id="NNNNN">
<div class="form-container">
<div class="row form-row spacer">
<div class="col">
<div class="input-group">
<div class="table-responsive">
<table class="table table-bordered formset-table">
{% for hidden in empty_formset.hidden_fields %}
{{ hidden }}
{% endfor %}
<tr>
{% for field in empty_formset.visible_fields %}
<th>{{ field.label_tag }}</th>
{% endfor %}
</tr>
<tr>
{% for field in empty_formset.visible_fields %}
<td>{{ field }}</td>
{% endfor %}
</tr>
</table>
</table>
</div>
</div>
<button class="btn btn-danger btn-xs remove-form-row mb-4" id="remove-{{ formset.prefix }}-NNNNN">
Remove row
</button>
</div>
</div>
</div>
</div>
{{ formset.management_form }}
{% for fs in formset %}
<div class="form-container">
<div class="row form-row spacer">
<div class="col">
<div class="input-group">
<div class="table-responsive">
<table class="table table-bordered formset-table">
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<tr>
{% for field in fs.visible_fields %}
<th>{{ field.label_tag }}</th>
{% endfor %}
</tr>
<tr>
{% for field in fs.visible_fields %}
<td>{{ field }}</td>
{% endfor %}
</tr>
</table>
</div>
</div>
{% if fs.empty %}
<button class="btn btn-danger btn-xs remove-form-row mb-4" id="remove-{{ fs.prefix }}">
Remove row
</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<div class="input-group-append">
<button id="add-form-{{ formset.prefix }}" class="btn btn-xs float-left btn-success add-form-row">
Add row
</button>
</div>
</div>
<div class="submit-row">
<input type="submit" class='btn btn-primary' value="Update">
</div>
<script>
/**
* Create a new form using a template
* #param {HTML} formset_template: the default HTML schema to fill
* #param {String} prefix: the Django formset prefix
* #param {div} position:
* #param {String} generic_id:
* #return false
*/
function clone(formset_template, prefix, position, generic_id) {
// get total forms number in formset
total = $('#id_' + prefix + '-TOTAL_FORMS').val();
// build the Regular Expression to replace generic elements
var prefix_regex = new RegExp('('+prefix+'-'+generic_id+')', 'g')
// generate new HTML block with "prefix+total indexed" form
newElement = $(formset_template).html().replace(prefix_regex,
prefix+'-'+total);
// place the new form before the "add new" button
$(position).before(newElement);
// increase total forms number in formset
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
// return false
return false;
}
/**
* Update the ID indexes of a form input fields
* #param {element} parent_div: the element that contains the form
* #param {String} prefix: the Django formset prefix
* #return false
*/
function reindex_forms(parent_div, prefix){
// build a Regular Expression to find all "form-INDEX" elements
var id_regex = new RegExp('(' + prefix + ')-(\\d+)','g');
// get the inner HTML of parent_div
var parent_html = parent_div.html()
// find the form in the HTML code
var target = parent_html.match(id_regex);
// get the INDEX value (es: form-1 -> 1)
var splitted = target[0].split('-');
var index = splitted[splitted.length-1];
// decrease the index (es: form-1 -> form-0)
var new_html = parent_html.replace(id_regex,
prefix+'-'+(parseInt(index)-1));
// update the parent_div HTML code
parent_div.html(new_html);
// return false
return false;
}
/**
* Delete the form and update the ID indexes of successive forms
* #param {element} to_remove: the element that contains the "remove" button and the form
* #param {String} prefix: the Django formset prefix
* #param {String} container_class_div: the class of div that contains the form
*/
function deleteForm(to_remove, prefix, container_class_div) {
// calculate the number of total forms in formset
total = $('#id_' + prefix + '-TOTAL_FORMS').val();
// retrieve all the forms following the one I'm deleting
var successive_forms = to_remove.nextAll(container_class_div);
// reindex each of them
successive_forms.each(function(){
reindex_forms($(this), prefix);
});
// remove the form
to_remove.remove();
// decrease total number
total--;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
}
// "add new form" event using the generic form template
$(document).on('click', '.add-form-row', function(e){
// If this method is called, the default action of the event will not be triggered.
e.preventDefault();
// get formset prefix
var formset_prefix = $(this).attr('id').split('-')[2];
// get formset template
var formset_template = $('#formset-template-'+formset_prefix);
// get generic_id attribute
var generic_id = formset_template.attr('generic_id')
// get the position of "add new" button
var position = $(this).parent();
// clone the template and create a new form in formset
clone(formset_template, formset_prefix, position, generic_id);
// return false
return false;
});
// "remove form" event
$(document).on('click', '.remove-form-row', function(e){
// If this method is called, the default action of the event will not be triggered.
e.preventDefault();
// get the CSS class of element that contains form and button
var container_class = '.form-container';
// get formset prefix
var prefix = $(this).attr('id').split('-')[1];
// get the element to remove (contains the form and the "remove" button)
var to_remove = $(this).closest(container_class);
// call deleteForm method
deleteForm(to_remove, prefix, container_class);
// return false
return false;
});
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
I've made some attempted to use formwizard but there wasn't much documentation about it so I decided to stay with the basic. I've successfully obtained and display the data from the first form to the second form and added some checkbox next to the data to allow user to choose whether to overwrite or ignore the duplicate data found in the backend process. The problem I have is the second form doesn't know how retrieve the data of the first form after hitting "Confirm" button. The form2.html template invalidated the data completely since it called itself again by the form action after submitting the data. Is there a way to solve this or a better approach to this?
forms.py
class NameForm (forms.Form):
first_name = forms.CharField (required = False)
last_name = forms.CharField (required = False)
class CheckBox (forms.Form):
overwrite = forms.BooleanField (required = False)
views.py
def form1 (request):
NameFormSet = formset_factory (NameForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)
if request.method == 'POST':
name_formset = NameFormSet (request.POST, prefix = 'nameform')
if name_formset.is_valid ():
data = name_formset.cleaned_data
request.session ['data'] = data
context = {'data': data}
return render (request, 'nameform/form2.html', context)
else:
name_formset = NameFormSet (prefix = 'nameform')
context = {......}
return render (request, 'nameform/form1.html', context)
def form2 (request):
request.session ['data'] = data
CheckBoxFormSet = formset_factory (CheckBox, extra = 2, max_num = 5)
if request.method == 'POST':
checkbox_formset = CheckBoxFormSet (request.POST, prefix = 'checkbox')
if checkbox_formset.is_valid ():
data = checkbox_formset.cleaned_data
context = {'data': data}
return render (request, 'nameform/success.html', context)
else:
checkbox_formset = CheckBoxFormSet (prefix = 'checkbox')
return HttpResponse ('No overwrite data.')
form2.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
<title>User Information</title>
</head>
<body>
<h1>User Information:</h1>
<form action="form2" method="POST">
{{ checkbox_formset.management_form }}
<div id="tablefont">
<table id="table01">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th class="center">Overwrite</th>
</tr>
{% for info in data %}
<tr>
<td>{{ info.first_name }}</td>
<td>{{ info.last_name }}</td>
<td class="center"><input id="id_checkbox-{{ forloop.counter0 }}-overwrite" name="checkbox-{{ forloop.counter0 }}-overwrite" type="checkbox" /></td>
</tr>
{% endfor %}
</table>
</div>
<br>
<p><input type="submit" value="Confirm">
<a href="{% url 'form1' %}">
<button type="button">Cancel</button></a></p>
</form>
</body>
</html>
Personally I find that class based views always make life simpler, and in this case specifically there is the Form Wizard views which use the session or a cookie. Check out the docs; https://django-formtools.readthedocs.io/en/latest/wizard.html
I've just finished writing something very similar to this;
from django.contrib.formtools.wizard.views import SessionWizardView
class SignupWizard(SessionWizardView):
template_name = 'wizard_form.html'
form_list = [Form1, Form2, Form3]
model = MyModel
def get_form_initial(self, step, **kwargs):
initial = self.initial_dict.get(step, {})
current_step = self.storage.current_step
if step or current_step in ['0', 0]:
initial.update({'field': 'data'})
elif step or current_step in ['1', 1]:
initial.update({'field': 'data'})
return initial
def get_context_data(self, form, **kwargs):
"""
Supply extra content data for each stage.
e.g. stage page title
"""
previous_data = None
if self.steps.current == self.steps.last:
previous_data = self.get_all_cleaned_data()
context = super(SignupWizard, self).get_context_data(form, **kwargs)
context.update(
{
'review_data': previous_data,
}
)
return context
def done(self, form_list, **kwargs):
"""
Final page, this is rendered after the post method to the final form.
"""
form_data_dict = self.get_all_cleaned_data()
mymodelform = form_list[0].save()
return HttpResponseRedirect()
Essentially there my first form is a ModelForm for MyModel and I use other standard forms to collect other information. So at the end, grab the first form from the list and save that to create the MyModel instance.
Equally as you can see you can grab self.get_all_cleaned_data() to get all of the data entered in to the forms through to whole process to handle the data yourself.
The template for something this would be along the lines of;
{% extends 'base_wizard.html' %}
{% load i18n %}
{% block form %}
{% for error in wizard.form.non_field_errors %}
{{ error }}
{% endfor %}
{{ wizard.form.media }}
{{ wizard.management_form }}
{% wizard.form.as_p %}
<div class="controls">
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">
{% trans "Previous step" %}
</button>
{% endif %}
<button type="submit" name="submit">
{% trans "Next step" %}
</button>
</div>
{% endblock form %}
I have 2 models, Father and Son.
I have a page to register Father. On the same page I have a formset to register Son.
On page has a button "more" to add another Father and their respective Son on the same page.
Does anyone have any examples using CreateView?
Class based views are still new, so I'll write this out. The process is simple:
First, create the forms for your objects. One of the forms will be repeated. Nothing special to be done here.
class SonInline(ModelForm):
model = Son
class FatherForm(ModelForm):
model = Father
Then, create your formset:
FatherInlineFormSet = inlineformset_factory(Father,
Son,
form=SonInline,
extra=1,
can_delete=False,
can_order=False
)
Now, to integrate it with your CreateView:
class CreateFatherView(CreateView):
template_name = 'father_create.html'
model = Father
form_class = FatherForm # the parent object's form
# On successful form submission
def get_success_url(self):
return reverse('father-created')
# Validate forms
def form_valid(self, form):
ctx = self.get_context_data()
inlines = ctx['inlines']
if inlines.is_valid() and form.is_valid():
self.object = form.save() # saves Father and Children
return redirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data(form=form))
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))
# We populate the context with the forms. Here I'm sending
# the inline forms in `inlines`
def get_context_data(self, **kwargs):
ctx = super(CreateFatherView, self).get_context_data(**kwargs)
if self.request.POST:
ctx['form'] = FatherForm(self.request.POST)
ctx['inlines'] = FatherInlineFormSet(self.request.POST)
else:
ctx['form'] = Father()
ctx['inlines'] = FatherInlineFormSet()
return ctx
Finally, here is the template:
The key part is the jquery django-dynamic-formset plugin that keeps adding new inline forms:
<form id="father-form" method="POST" enctype="multipart/form-data" action=".">
{% csrf_token %}
<div class="row">
{% for f in form %}
<div class="span3">{{ f.label }}<br />{{ f }}
{% if f.errors %}
{% for v in f.errors %}
<br /><span style="color:red;">{{ v }}</span>
{% endfor %}
{% endif %}
</div>
{% endfor %}
</div>
<hr />
<h2>Sons:</h2>
<table class="table-striped">
<table>
{% for f2 in inlines %}
<tr id="{{ f2.prefix }}-row">
{% for i in f2 %}
<td>
{{ i }}{% if i.errors %}<span style="color:red;">{{ i.errors }}</span>{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{{ inlines.management_form }}
<input type="submit" class="btn btn-primary" value="Go Go Gadget →">
</form>
<script type="text/javascript">
$(function() {
$('#father-form tr').formset({
prefix: '{{ inlines.prefix }}'
});
})
</script>