I have two models, Runs and Orders. One run will complete many orders, so I have a Many-to-one relation between my orders and runs, represented as a foreignkey on my orders.
I want to build a UI to create a run. It should be a form in which someone selects orders to run. I'd like to display a list of checkboxes alongside information about each order. I'm using django crispy forms right now.
views.py
class createRunView(LoginRequiredMixin, CreateView):
model = Run
form_class = CreateRunForm
template_name = 'runs/create_run.html'
forms.py
class CreateRunForm(forms.ModelForm):
class Meta:
model = Run
fields = ['orders',]
orders = forms.ModelMultipleChoiceField(queryset=Order.objects.filter(is_active=True, is_loaded=False))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Field('orders', template="runs/list_orders.html"),
Submit('save', 'Create Run'),
Button('cancel', 'Cancel'),
)
I'm not sure what locals are available to me in the list_orders.html
template. It seems like there's {{ field }}
and maybe form.visible_fields
but if I dig to deeply into either I get a TypeError: 'SubWidget' object is not iterable
, which is barely documented online.
The above suggests I might still be getting a widget in the template, despite the fact that Field('orders', template="runs/list_orders.html"),
should prevent that, per the crispy docs:
Field: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
I've seen this answer which suggests using label_from_instance
. However I'm not sure how to stuff a bunch of html into label_from_instance
. Instead of having a different label, I really want to have a template which generates a bunch of html which shows details about the entire order object, so I'm not sure this approach will work.
The answers in this question mostly confused me, but the accepted answer didn't work, it seems. (maybe a django version issue, or a crispy forms issue?)
How do I render templates with data from each model in ModelMultipleChoiceField?
Widgets control how fields are rendered in HTML forms. The Select widget (and its variants) have two attributes template_name
and option_template_name
. The option_template_name
supplies the name of a template to use for the select options. You can subclass a select widget to override these attributes. Using a subclass, like CheckboxSelectMultiple, is probably a good place to start because by default it will not render options in a <select>
element, so your styling will be easier.
By default the CheckboxSelectMultiple option_template_name
is 'django/forms/widgets/checkbox_option.html'
.
You can supply your own template that will render the details of the orders how you want. IE in your forms.py
class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'myapp/detail_options.html'
class CreateRunForm(forms.ModelForm):
...
orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
Suppose that myapp/detail_options.html
contained the following
{# include the default behavior #}
{% include "django/forms/widgets/input_option.html" %}
{# add your own additional div for each option #}
<div style="background-color: blue">
<h2>Additional info</h2>
</div>
You would see that blue div after each label/input. Something like this
Now, the trick will be how you get the object available to the widget namespace. By default, only certain attributes are present on a widget, as returned by the widget's get_context
method.
You can use your own subclass of MultipleModelChoiceField
and override label_from_instance
to accomplish this. The value returned by label_from_instance
is ultimately made available to the widgets as the label
attribute, which is used for the visible text in your model form, when it renders {{ widget.label }}
.
Simply override label_from_instance
to return the entire object then use this subclass for your field.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj
class CreateRunForm(forms.ModelForm):
...
orders = MyModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
So now in the myapp/detail_options
template you can use widget.label
to access the object directly and format your own content as you please. For example, the following option template could be used
{% include "django/forms/widgets/input_option.html" %}
{% with order=widget.label %}
<div style="background-color: blue">
<h2>Order info</h2>
<p style="color: red">Order Active: {{ order.is_active }}</p>
<p style="color: red">Order Loaded: {{ order.is_loaded }}</p>
</div>
{% endwith %}
And it would produce the following effect.
This also will not disrupt the default behavior of the widget label text wherever widget.label
is used. Note that in the above image the label texts (e.g. Order object (1)
) are the same as before we applied the change to label_from_instance
. This is because the default in template rendering is to use str(obj)
when presented with a model object; the same thing that would have previously been done by the default label_from_instance
.
ModelMultiplechoiceField
to have label_from_instance
return the object. option_template_name
. widget.label
will be each object.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With