TLDR Question: How do you make one crispy form with a ¿segmented?(not sure if this is considered inline) layout with multiple models(some related, some not).
I am trying to understand several things in Django: forms, formsets, nested forms, and crispy, and I've been at it for a while, and feel I am close, just need someone to help connect the dots. I'm not sure how to accomplish it without crispy, so I started down this path thinking crispy was the solution. Please correct if I am wrong, thanks :)
I would like one form (as in HTML form, not necessarily Django Form), that has a primary model with lots of fields, but with secondary/tertiary models in the middle of the primary fields. I am rather close to the layout, but can't seem to get the secondary/tertiary models to render in the middle of the layout, let alone compile without crispy/django erroring.
Here is a color-coded visual of what I am trying to attain
I assume I am wrong with at least one of the following:
- I am not calling the correct formfactory
- I am not properly using formsets
- I am not referencing the form fields to the correct model fields correctly in the layout of the form helper
- The layout is not possible, or I am applying the wrong code structure to get the result.
- I don't think I can call two forms directly as directly below, as they would not be nested/integrated
code for above list item (cant put a code block directly below
#I don't think this will achieve the integration/nested look I am aiming for
#views.py:
parent_form = ParentForm()
child_form = ChildForm()
render(template.html, {
"pform": parent_form,
"cform": child_form
})
#template.html:
<form>
{{ pform }}
{{ cform }}
</form>
Files For reference
models.py
#Black in the picture
class Truck(models.Model):
name = models.CharField(…)
…
#Blue in the picture
class QuickInspection(models.Model):
odometer = models.IntegerField(…)
… (created_at, user_cookie#who did it, …)
truck = models.ForeignKey(Truck)
-----
#These two are unrelated to the Truck in the DB, and I would prefer to keep it that way, if for at least to understand how to accomplish this
-----
#Red
class Tires(models.Model):
front_tire = models.CharField(…)
… (created_at, …)
truck = models.ForeignKey(Truck)
full_inspection = models.ForeignKey(FullInspection, blank=True, null=True) #optional, and if it has this foreign key, then I know the Tires were looked at in a full inspection. If not, then they were looked at in the quick inspection, without having a foreign key to the QuickInspection
#Green
class Brakes(models.Model):
front_axle = models.CharField(…)
…
createdAt = models.DateTimeField(auto_now_add=True)
truck = models.ForeignKey(Truck)
pm = models.ForeignKey(PM, blank=True, null=True)
full_inspection = models.ForeignKey(FullInspection, blank=True, null=True) #optional, same as full_inspection in Tires
views.py
def weeklyView(request, truckID):
# POST
if request.method == 'POST':
# Check forms for valid data and save or provide error
#return response
# GET
else:
#make each form individually?
quickForm = OHReadingForm(…)
tireForm = TireForm()
brakeForm = BrakeForm()
#Or import a formset and helper?
formset = ExampleFormSet()
helper = ExampleFormSetHelper()
response = render(request, 'trucks/weeklyInspection.html', {
'ohrForm': ohrForm,
'formset': formset,
'helper': helper,
'tireForm': tireForm,
'truck': truck,
})
forms.py
class QuickInspectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(QuickInspectionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = 'post'
self.helper.form_action = 'quickInspectionURL'
self.helper.layout = Layout(
Div(
Div(
Fieldset(
'', # 'first arg is the legend of the fieldset',
'quickInspectionMetric1', #From QuickInspection.metric1
'quickInspectionMetric2', #From QuickInspection.metric2
'quickInspectionMetric3', #From QuickInspection.metric3
),
css_class="blue"
),
Div(
Fieldset(
'tireMetric1', #from Tire.metric1
'tireMetric2', #from Tire.metric2
css_class="red"
),
Div(
Fieldset(
'brakeMetric1', #from Brake.metric1
'brakeMetric2', #from Brake.metric2
css_class="green"
),
Div(
Fieldset(
'quickInspectionMetric4', #from QuickInspection.metric4
'quickInspectionMetric5', #from QuickInspection.metric5
css_class="blue"
),
),
Div(
FormActions(
Reset('reset', 'Reset'),
Submit('submit', 'Submit') #submit for all
)
),
)
class Meta:
model = QuickInspection
fields = [
'metric1','metric2','metric3','metric4','metric5',
'truck',
…,
]
ExampleFormSet = formset_factory(QuickInspectionForm, extra=1)
# Other failed attempts
# ExampleFormSet = inlineformset_factory(QuickInspectionForm, extra=1)
# ExampleFormSet = inlineformset_factory(QuickInspectionForm, TireForm, extra=1)
# ExampleFormSet = inlineformset_factory(QuickInspectionForm, TireForm, BrakeForm, extra=1)
class ExampleFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(ExampleFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.form_tag = False
self.layout = Layout(…)
#Same as Brake Form
class TireForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TCForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = 'tireURL'
self.helper.layout = Layout(…)
class Meta:
model = TireCondition
fields = [
'metric1', 'metric2', …
'truck',
]
JS fiddle for code repo. I don't know of a DJango-like Fiddle environment...
Crispy is independent of this problem. Forms can be included int the template with either:
or
For the assumptions:
Code to make an integrated form with related objects/foreign keys:
views.py:
template.html:
forms.py
THE IMPORTANT PARTS
When Architecting the forms in the forms.py
commit=false
to prevent it from saving just yetWhen creating the form in the view:
instance=truck
. You can also pass other objects if you need access to them as well, such asrelatedObject=queriredObject
prefix=formIdentifierN
, as this helps Django keep track of what unique information, fields, entries are associated with which forms. No special naming is needed, and form1, form2, etc... works well, so long as you know which are each.When saving the form in the view:
all( [a,b,c,d] )