I'm new to Django and I'm currently trying to create a form which should contain input fields for all existing objects of another model. Let's say that I would like to manage my supplies at home and make sure that I have enough of various products at diffect storage locations.
For example, I would like to make sure that the storage locations bathroom and basement should always have plenty of the supply toilet paper. I don't need toilet paper in the location kitchen. The storage locations are pre-defined and available through a model. I couldn't find any way to create a form for Supply which dynamically generates form fields for MinimumQuantity objects based on all available StorageLocation objects.
My form should look like this when creating the supply "toilet paper":
supply name: <input example: "toilet paper">
minimum supply quantities
bathroom - amount: <input example: 6>
kitchen - amount: <input example: 0>
basement - amount: <input example: 3>
I'm a bit lost here and any hints would be much appreciated.
This is my current code (shortened):
models.py:
class Supply(models.Model):
name = models.CharField(max_length=50)
class StorageLocation(models.Model):
name = models.CharField(max_length=50)
class MinimumQuantity(MinimumQuantity):
storage_location = models.ForeignKey(StorageLocation, on_delete=models.PROTECT)
supply = models.ForeignKey(Supply, on_delete=models.PROTECT)
amount = models.IntegerField()
views.py:
class SupplyCreateView(CreateView):
model = Supply
template_name = "supplies_form.html"
fields = [ 'name', ]
supplies_form.html:
<div class="card-body">
{% csrf_token %}
{{ form|crispy }}
</div>
I stumbled upon various related questions on stackoverflow and other sites and fiddled around with formsets, but my issues seem to be that:
I have a one-to-many from Supply to MinimumQuantity and can't think of any way to tell Supply about it, and
while all StorageLocation objects exist already, the MinimumQuantity objects don't.
Have I perhaps even made this construct too complicated and is there perhaps a way to solve the whole thing without the MinimumQuantity model at all? Thanks!
In this, we have to use a ModelForm and set the fields attribute to include the fields of the MinimumQuantity model that you want to include in the form. In your case, this would be the storage_location field and the amount field.
Now, First add this in your code and make changes to SupplyCreateView:
from django.forms import ModelForm
class MinimumQuantityForm(ModelForm):
class Meta:
model = MinimumQuantity
fields = ['storage_location', 'amount']
class SupplyCreateView(CreateView):
model = Supply
template_name = "supplies_form.html"
form_class = MinimumQuantityForm
fields = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['minimum_quantities'] = MinimumQuantity.objects.all()
return context
and then in your supplies_form.html do like this:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="card-body">
<h4>Minimum supply quantities</h4>
{% for minimum_quantity in minimum_quantities %}
<div class="form-group">
<label for="id_{{ minimum_quantity.storage_location.name }}">{{ minimum_quantity.storage_location.name }}</label>
<input type="number" name="{{ minimum_quantity.storage_location.name }}" id="id_{{ minimum_quantity.storage_location.name }}" required>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Save</button>
</div>
Image of Form Fields Dissapeering When Not Selected
Hi all,
I am having trouble understanding why my fields are disappearing when they are not selected. I am rendering this form using Crispy Forms (followed via Youtube Tutorial). This feature was working earlier - however, I am not sure why it suddenly stopped working. I have a few other forms in this application and they are facing the same issue.
Here is the relevant code that is being used to generate the form
class BookAppointmentForm(forms.ModelForm):
class Meta:
model = BooksAppt
fields = '__all__'
labels = {
'hp_username': 'Doctor',
'appt_date': 'Appointment Date',
'p_username': 'Patients'
}
widgets = {
'appt_date': forms.TextInput(attrs={'type': 'date'})
}
def __init__(self, p_username=None, *args, **kwargs):
super(BookAppointmentForm, self).__init__(*args, **kwargs)
if p_username:
self.fields['p_username'].queryset = Patient.objects.filter(
p_username=p_username).values_list('p_username', flat=True)
Relevant HTML being used to render the form
<div class="container" style="background-color:#E6E6FA">
<div class="col-md-10 offset-md-1 mt-5">
<div class="card-body">
<h1 class="display-5 "> Booking an Appointment for {{ user }}</h1>
<p class="lead">Please Fill in the Following Form to Register an Appointment</p>
<hr class="my-4">
<form class="register" action="" method="post" autocomplete="off">
{% csrf_token %}
{{ form.errors }}
{{ form.non_field_errors }}
{{ form.p_username|as_crispy_field}}
{{ form.hp_username|as_crispy_field }}
{{ form.appt_date|as_crispy_field }}
<button type="submit" class="btn btn-success">Submit</button>
</form>
<br>
</div>
</div>
I feel as though the issue is something related to the bootstrap template I am using not playing nicely with crispy forms. I don't need a complete answer - just something to nudge me in the right direction. Please let me know if there is any other relevant information you need to help debug.
Thanks,
Shwinster
The issue was seen as a result of the bootstrap template. It was setting the text to white when not in focus. I was able to figure this out using inspect element and trying to see what stylistic properties were being set the form element.
I have a forms.ModelForm 'CreateUserForm'.
I want to set a property for each form field to be later used in the template.
In this case, I want to set a icon name to specify which icon name should be used for each field.
class CreateUserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
icon_names = ['person', 'email', 'enhanced_encryption']
class Meta:
model = User
fields = ['username', 'email', 'password']
I've had trouble iterating over both the field AND the field's property 'icon_names'. I can't really zip() without losing functionality.
Currently I've hacked together iteration by using the 'forloop.parentloop.counter'
{% for field in form %}
<div class="form-group">
<div class="input-field">
<i class="icons">
{% for icon in form.icon_names %}
{% if forloop.parentloop.counter == forloop.counter %}
{{ icon }}
{% endif %}
{% endfor %}
</i>
<input type="text" id="autocomplete-input" class="autocomplete">
<label class="control-label" for="autocomplete-input">{{ field.label_tag }}</label>
</div>
</div>
{% endfor %}
Which produces the intended result, but it seems redundant, especially if I wanted to add another field property in the future.
What's the proper way to do this?
One idea would be to pass the zipped list in the context, such as:
context = {'fields_with_icons': zip(form.icon_names, [field for field in form])}
and then
{% for field, icon in fields %}
{{ field }}
{{ icon }}
{% endfor %}
There are two ways i could do this both involving adding an extra html attribute on the fields widget
see age field below, i would use the self.fields to get the field widget and add the extra icon attribute on its attrs dictionary...for this to work you should ensure it comes after the call to super().__init__(*args, **kwargs) others the self.fields will not have been populated....i'd use this when i dont have anything else i need to adjust on the widget class.
https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#styling-widget-instances
see name field below, You could do this on the Meta class https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields
The form
class PersonForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['age'].widget.attrs['icon'] = 'age'
class Meta:
model = models.Person
fields = ('name', 'age')
widgets = {
'name': forms.TextInput(attrs={'icon': 'email'})
}
And on the template when looping over the fields id get it like this
{% for field in form %}
{{ field.field.widget.attrs.icon }}
{% endfor %}
currently have a model that has a model.FileField() attribute, and when rendering in my django template I just iterate over the fields, such as
{% for field in form.visible_fields %}
<div class="form-group">
{{field.errors}}
<label for="{{field.auto_id}}">{{field.label}}</label>
{{field}}
{% endfor %}
However, when the template renders the ClearableFileInput widget, I want to add some space between the href and the checkbox for clearing the widget. Any ideas on how to access those specific "parts" of the field?
You have to override the default ClearableFileInput and set those rendering attributes to your taste
class MyClearableInput(ClearableFileInput):
template_with_initial = '%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s'
template_with_clear = '%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>'
url_markup_template = '{1}'
I've put the initial attributes, but you have to change them to reflect your desired output. It's pretty self-explanatory. Then in your form, override the widgets to use this class using the Meta/widgets attribute.
What is the "djangoy" way to approach this problem:
In my form class, I have a forms.ChoiceField whose widget is a forms.RadioSelect widget, one of whose choices needs to be presented with an inline text input (which is also a field in the form). I'm using custom validation to ignore the text field when its radio choice is not selected. When rendered, I want it to appear like below:
<ul>
<li><label for="id_rad_0"><input type="radio" id="id_rad_0" value="none" name="rad" /> No Textbox</label></li>
<li><label for="id_rad_1"><input type="radio" id="id_rad_1" value="one" name="rad" /> One Textbox: <input type="text" name="bar" id="id_bar" /></label></li>
</ul>
However, I can't simply produce this in my template, because the radio choices are not exposed. I can't see a way to do this without tightly coupling my form to my template, or alternately, putting all of the presentation logic in the form class. What is the right way to solve this problem?
edit
I realize that the above might just be an obscure problem, but I'm not sure exactly what other information I can provide in order to inspire someone to help me with this. I'm a much better backend programmer than web designer, and I'm on this project alone, so maybe it's a lack of education - is what I described simply poor design? Should I just be designing this a different way? I'm really open to any suggestion here that will help me move past this.
edit 2
Per request, the current code, shortened to save sanity, names changed to protect the innocent:
# forms.py
from myapp.models import RatherComplicatedModel
from django import forms
class RatherComplicatedForm(forms.ModelForm):
#various and sundry code...
RADIO_CHOICES = (
('none', "No Textbox"),
('one', "One Textbox: "),
)
# although I've abbreviated the model, 'rad' does not appear in the model;
# it merely provides input to the un-provided clean function
rad = forms.ChoiceField(widget=forms.RadioSelect(),choices=RADIO_CHOICES)
class Meta:
model = RatherComplicatedModel
-
# models.py
from django.db import models
class RatherComplicatedModel(models.Model):
#some other stuff...
bar = models.IntegerField(blank=True,null=True)
If I understand your problem correctly, you can access choices tuple in template:
<ul>
{# Assuming {{ field }} here is {{ form.rad }} #}
{% for choice in field.field.choices %}
<li>
<label for="id_{{ field.html_name }}_{{ forloop.counter0 }}">
<input type="radio"
id="id_{{ field.html_name }}_{{ forloop.counter0 }}"
value="{{ choice.0 }}"
name="{{ field.html_name }}" />
{{ choice.1 }}
{% if choice.0 == 'one' %}
{# Necessary field here #}
{{ form.bar }}
{% else %}
No Textbox
{% endif %}
</label>
</li>
{% endfor %}
</ul>
Anton's answer worked, and was a decent answer for a while there - but unfortunately it became unmaintainable. So, taking a cue from a diff attached to django ticket #9230, I just monkey patched django.forms.forms.BoundField
from django import forms
def MonkeyPatchDjangoFormsBoundField():
def prepare_widget_render(self, widget=None, attrs=None, only_initial=False):
"""
Prepare the data needed for the widget rendering.
"""
if not widget:
widget = self.field.widget
attrs = attrs or {}
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
if not only_initial:
attrs['id'] = auto_id
else:
attrs['id'] = self.html_initial_id
if not only_initial:
name = self.html_name
else:
name = self.html_initial_name
return widget, name, attrs
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
Renders the field by rendering the passed widget, adding any HTML
attributes passed as attrs. If no widget is specified, then the
field's default widget will be used.
"""
widget, name, attrs = self.prepare_widget_render(widget, attrs, only_initial)
return widget.render(name, self.value(), attrs=attrs)
def __iter__(self):
"""
Check if current widget has a renderer and iterate renderer.
"""
widget, name, attrs = self.prepare_widget_render()
if not hasattr(widget, 'get_renderer'):
raise Exception, "Can not iterate over widget '%s'" % widget.__class__.__name__
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
for entry in renderer:
yield entry
def __getitem__(self,idx):
"""
Tries to use current widget's renderer, and then check attribute.
"""
widget, name, attrs = self.prepare_widget_render()
try:
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
return renderer[idx]
except Exception:
return getattr(self,idx)
forms.forms.BoundField.prepare_widget_render = prepare_widget_render
forms.forms.BoundField.as_widget = as_widget
forms.forms.BoundField.__iter__ = __iter__
forms.forms.BoundField.__getitem__ = __getitem__
This allowed me to be able to access the radio inputs directly, by using {{ form.field.0.tag }}, or through iteration - {% for radio in form.field %} {{ radio.tag }} {% endfor %}. Much easier to take care of!
Choices should be in the Model:
class RatherComplicatedModel(models.Model):
BAR_CHOICES = (
(0, "No Textbox"),
(1, "One Textbox: "),
)
#some other stuff...
bar = models.IntegerField(blank=True, null=True, choices=BAR_CHOICES)
Then just:
class RatherComplicatedForm(forms.ModelForm):
#various and sundry code...
bar = forms.ChoiceField(widget=forms.RadioSelect(),
choices=RatherComplicatedModel.BAR_CHOICES)
class Meta:
model = RatherComplicatedModel
I would do this by subclassing RadioFieldRenderer and attaching it to a custom widget:
# forms.py
from django import forms
from django.forms.widgets import RadioSelect, RadioFieldRenderer
from django.template.loader import render_to_string
from myapp.models import RatherComplicatedModel
class MyRadioFieldRenderer(RadioFieldRenderer):
def render(self):
return render_to_string(
'my_radio_widget.html',
{'field': self})
class MyRadioSelect(RadioSelect):
renderer = MyRadioFieldRenderer
class RatherComplicatedForm(forms.ModelForm):
RADIO_CHOICES = (
('none', "No Textbox"),
('one', "One Textbox: "),
)
rad = forms.ChoiceField(widget=MyRadioSelect(),choices=RADIO_CHOICES)
class Meta:
model = RatherComplicatedModel
Then the template:
#my_radio_widget.html
<ul>
{% for choice in field %}
<li>
<label for="id_{{ field.name }}_{{ forloop.counter0 }}">
<input type="radio"
name="{{ field.name }}"
value="{{ choice.choice_value }}"
id="id_{{ field.name }}_{{ forloop.counter0 }}"
{% if field.value == choice.choice_value %}
checked='checked'
{% endif %}/>
{{ choice.choice_label }}
</label>
</li>
{% endfor %}
</ul>