How to make custom Django CheckboxSelectMultiple without nested input - python

I'm using Django 1.9.1.
I have a form that has Categories like this:
class MyModelEditForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['name',
'email',
'categories',
]
widgets = {
'categories': forms.CheckboxSelectMultiple(),
}
Here's the Models:
class MyModel(models.Model):
name = models.CharField(max_length=256, verbose_name='navn')
email = models.EmailField(null=True, blank=True)
categories = models.ManyToManyField(Category)
class Category(models.Model):
name = models.CharField(max_length=128)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
When I use my form in a template I can say {{ form.categories }} and I get a bunch of checkboxes with all my categories! Excellent, functionally this is exactly what I want. And like this, everything works. My problem is that I don't want the input fields nested in labels, as Django defaults to.
So I tried to loop over the categories like this:
{% for category in form.categories %}
<input type="checkbox" name="{{ category.name }}" class="styled-checkbox" value="{{ category.id }}" id="{{ category.id_for_label }}" {% if category.is_checked %}checked="checked"{% endif %}>
<label for="{{ category.id_for_label }}">{{ category.choice_label }}</label>
{% endfor %}
But apparently I can't set the value to {{ category.id }}, it renders nothing. I also tried {{ category.related__id }} but that's not a thing either. Looked in the documentation, but it doesn't really seem to say allot about this issue. If there's a way to only output the input tag and that would be acceptable as well.
So is it even possible to access the related objects id from here? Or is there another way to customise the output? I looked at overriding the render() method, but it seemed like a huge effort just to move the input outside of a label tag.

Related

How to create a form with fields for all instances of another model in Django?

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>

Django templates using buttons for Boolean fields

I have a preferences page that has many Boolean fields. I created an UpdateView and when I use {{ form.as_p }} in my template it works, but I am trying to create individual buttons for each option instead of checkboxes. I couldn't find a way to make it work in my template.
models.py:
class Preference(models.Model):
user = models.OneToOneField("User", on_delete=models.SET_NULL, null=True)
option1= models.BooleanField(default=False)
option2= models.BooleanField(default=False)
option3= models.BooleanField(default=False)
option4= models.BooleanField(default=False)
views.py:
class preferencesview(UpdateView):
model = Preference
form_class = PreferenceForm
success_url = reverse_lazy("profiles:preferences")
forms.py:
class PreferenceForm (forms.ModelForm):
class Meta:
model = Preference
exclude = ['user']
I want to have individual buttons for each option and a submit button to save the changes. Please let me know if you have any documentation or tutorials.
There is so many ways you can do it. But there is no out of the box solution.
My example is with bootstrap css and a little bit of jquery/js:
In form class definition change fields widgets to HiddenInput like:
class PreferenceForm (forms.ModelForm):
class Meta:
model = Preference
exclude = ['user']
widgets = {'option1': forms.HiddenInput,
'option2': forms.HiddenInput,
}
end so on
then in your template loop over fields of form and based on value render the class of button (btn-success/btn-danger):
<form id="myform">
{% for field in form %}
<button type="button" class="btn {% if field.value %} btn-success{% else %}btn-danger{% endif %}"
name="{{ field.name }}">
{{ field.name }}</button>
{{ field }}
{% endfor %}
</form>
don't forget to add {{ field }} itself,
And now with js watch for click on buttons inside #myform and based on hasClass change class of button and value of input:
<script>
$('#myform button').on('click', function () {
let nameof = $(this).attr('name');
if ($(this).hasClass('btn-success')){
$(this).removeClass('btn-success');
$(this).addClass('btn-danger');
$('#myform input[name='+nameof+']').val('False');
} else {
$(this).removeClass('btn-danger');
$(this).addClass('btn-success');
$('#myform input[name='+nameof+']').val('True');
}
});
</script>
That's all. Don't forget to add save button to form.
Its just one of many examples how can you do it.

Django how to set custom properties for a ModelForm

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 %}

Django pass variables to form_class

I am trying to figure out how to access fields from a Model that is used as a ForeignKey within the Model that the forms are querying.
Each Room has a form where the user selects a Layout from a dynamic list of possible Layout objects.
1—The HTML forms/room/update/layouts.html
<form class="layouts__form form-horizontal" action="" method="post" enctype="multipart/form-data">
<fieldset class="form__options">
{% csrf_token %}
{% for field in form.layout %}
<div class="layouts__layout">
{{ field.tag }}
{{ field.choice_label }}
<label for="value_{{ forloop.counter0 }}">
<div class="layouts__layout__thumbnail layouts__layout__thumbnail--{{ field.choice_label }}" style="background-image: url('### (I WOULD LIKE TO LOAD 'Layout.thumbnail' HERE) ###');"></div>
</label>
</div>
{% endfor %}
<div class="form__submit">
<button type="submit">Submit</button>
</div>
</fieldset>
</form>
2—The form is being called by this in views.py:
class RoomLayoutView(UpdateView):
model = Room
form_class = RoomLayoutForm
template_name = 'forms/room/update/layouts.html'
3—Which is being created by this in forms.py:
class RoomLayoutForm(forms.ModelForm):
layout = forms.ModelChoiceField(
widget=forms.RadioSelect(attrs={'type': 'radio', 'id': 'value',}),
queryset=Layout.objects.all(),
required=False, empty_label=None)
class Meta:
model = Room
fields = ['layout']
4—Which uses the Room model from:
class Room(models.Model):
title = models.CharField(max_length=200, blank=True)
layout = models.ForeignKey(Layout, related_name='template_selected', blank=True, null=True)
def __str__(self):
return self.title
5—Which takes one of the Layout models as a ForeignKey defined here:
class Layout(models.Model):
title = models.CharField(max_length=200)
...
padding_top = models.IntegerField(blank=False, default=0)
...
thumbnail = models.FileField(upload_to='layouts')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-title',)
def __str__(self):
return self.title
I am trying to figure out how to access attributes from Layout model within the actual form. I would especially like to dynamically load the Layout.thumbnail or Layout.padding_top within the form at the top. I have tried at least 8 different methods and was unable to figure out a way to make this work. Any help would be really appreciated.
As stated in this answer, you can access the current instance associated with the form like this in your template:
{{ form.instance }}
So to access the thumbnail or padding_top attributes of the linked layout:
{{ form.instance.layout.thumbnail }}
{{ form.instance.layout.padding_top }}

Django - ModelChoiceField empty choice missing

Django docs say when using ModelChoiceField on a form and form is set to required=False and model is set to blank=True in addition to no default values, then I should receive a free empty choice within my select tag. I am not. Any ideas to what I've done to lose the empty choice?
models.py
class Location(models.Model):
location_name = models.CharField(max_length=50, blank=True)
address = models.CharField(max_length=50)
active = models.BooleanField(default=True)
def __unicode__(self):
return self.location_name
forms.py
class CalcForm(forms.Form):
startAddr = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
waypoint1 = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
waypoint2 = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
...
endAddr = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
template.html
<form action="../calcview" method="get">{% csrf_token% }
<label>
<div>Start Address</div>
<select name="startAddr">
{% for location in form.fields.startAddr.queryset %}
<option value = "{ location.location_name }">{{location.location_name}}/option>
{% end for %}
</select>
...
</form>
You will only get the 'free' empty option if you allow django forms to render the form field automatically. Because you are rendering it yourself in your template (and I don't know why you would want to do that...) you would need to add the empty select yourself:
<select name="startAddr">
<option value="">-----------</option>
{% for location in form.fields.startAddr.queryset %}
<option value = "{ location.location_name }">{{location.location_name}}</option>
{% end for %}
</select>
You can test this by allowing django form to render it for you:
<div>Start Address</div>
{{ form.startAddr }}
You can use the empty_label on your ModelChoiceField.
startAddr = forms.ModelChoiceField(empty_label='---------', queryset=Location.objects.all(), required=False)
Then render the form field into the template.
{{ form.startAddr }}
https://docs.djangoproject.com/en/2.1/ref/forms/fields/#django.forms.ModelChoiceField.empty_label
I know, you asked more than half a year ago, but I thought I post another answer anyway. Because I think there is a more elegant solution that uses the "empty_label" that you can define in your form.
You can access this empty_label attribute through form.fields.startAddr.empty_label or form.startAddr.field.empty_label in your template. So you can include it like this:
<select id="{{ form.fields.startAddr.id_for_label }}" name="{{ form.fields.startAddr.html_name }}">
<option value="">{{ form.fields.startAddr.empty_label }}</option>
{% for location in form.fields.startAddr.queryset %}
<option value="{{ location.location_name }}">{{ location.location_name }}/option>
{% endfor %}
</select>
I wonder if you did not actually want to use {{ location.id }} as value? Or is the location_name unique? As it could even be empty, the auto-generated id might be better as a reference.
As you can see, I have also replaced name="startAddr" with information the form object provides anyway for every field: id="{{ form.fields.startAddr.id_for_label }}" name="{{ form.fields.startAddr.html_name }}. Using these variables should make your template code more flexible and robust.
For more details, please check the Django documentation:
ModelChoiceField: https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield
Automatic primary key field: https://docs.djangoproject.com/en/dev/topics/db/models/#automatic-primary-key-fields

Categories

Resources