I would like to be able to extract different information in my django form:
That's my form:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
class InstanceForm(ModelForm):
class Meta:
model = models.BaseAsset
widgets = {
'labels': LabelIconCheckboxSelectMultiple()
}
The model:
class AssetClass(models.Model):
default_labels = models.ManyToManyField(Label, null=True, blank=True)
pass
the M2M reference field
class Label(models.Model):
explanation = models.CharField(null=True, max_length=63)
svgpreview = models.CharField(null=True, max_length=31)
def __unicode__(self):
return unicode(self.explanation)
pass
Now, the HTML code generated by the {{ form.as_p }} is as follows:
<li><label for="id_labels_0"><input type="checkbox" name="labels" value="1" id="id_labels_0" /> Consult owner before using</label></li>
<li><label for="id_labels_1"><input type="checkbox" name="labels" value="2" id="id_labels_1" /> This item is broken</label></li>
Which means it's clearly using the __unicode__ rendering of the model 'Label'. How can I change that behavior in the Select widget, so that it would use a different function to populate it's choices? I'm trying to get it, in the reasonably portable way, to print '<img src="{{label.svgpreview}}" alt="{{label.explanation}}"...>' next to the checkbox?
You will override forms.widgets.CheckboxSelectMultiple class:
This is CheckboxSelectMultiple class and its render function:
class CheckboxSelectMultiple(SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
# Normalize to strings
str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
label_for = u' for="%s"' % final_attrs['id']
else:
label_for = ''
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
option_label = conditional_escape(force_unicode(option_label))
output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
output.append(u'</ul>')
return mark_safe(u'\n'.join(output))
So what you will do :
class MyCheckboxSelectMultiple(CheckboxSelectMultiple):
def render(self, name, value, attrs=None, choices=()):
#put your code to have custom checkbox control with icon
#...
output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label)) # especially you will be working on this line
#...
Then where you are using widgets=CheckboxSelectMultiple() it will become widgets=MyCheckboxSelectMultiple()
Reading django.forms.models.ModelChoiceField gives a hint:
# this method will be used to create object labels by the QuerySetIterator.
# Override it to customize the label.
def label_from_instance(self, obj):
"""
This method is used to convert objects into strings; it's used to
generate the labels for the choices presented by this object. Subclasses
can override this method to customize the display of the choices.
"""
return smart_unicode(obj)
ok, but how do I override it per-instance of ModelForm - this gets overridden in few places throughout django.forms
Considering the following code:
class InstanceForm(ModelForm):
class Meta:
model = models.BaseAsset
widgets = {
'labels': forms.CheckboxSelectMultiple()
}
def __init__(self, *args, **kwargs):
def new_label_from_instance(self, obj):
return obj.svgpreview
super(InstanceForm, self).__init__(*args, **kwargs)
funcType = type(self.fields['labels'].label_from_instance)
self.fields['labels'].label_from_instance = funcType(new_label_from_instance, self.fields['labels'], forms.models.ModelMultipleChoiceField)
This is somewhat creepy - basically, it's a more bizzare implementation of this:
Override a method at instance level
Please read the comments in the referenced thread to understand why this might be a bad idea in general..
You don't have to do the "creepy" instance-level override to take proper advantage of the documented django.forms.models.ModelChoiceField.label_from_instance() method.
Building on the AssetClass and Label objects in the original post:
class AssetSvgMultiField(forms.ModelMultipleChoiceField):
"""
Custom ModelMultipleChoiceField that labels instances with their svgpreview.
"""
def label_from_instance(self, obj):
return obj.svgpreview
class InstanceForm(forms.ModelForm):
default_labels = AssetSvgMultiField(queryset=Label.objects.all())
class Meta:
model = models.AssetClass
widgets = {
'default_labels': forms.CheckboxSelectMultiple()
}
This is explained in the Django documentation here:
https://docs.djangoproject.com/en/1.9/ref/forms/fields/#django.forms.ModelChoiceField.to_field_name
You can see the ModelChoiceField class calling the method on the field here:
https://github.com/django/django/blob/1155843a41af589a856efe8e671a796866430049/django/forms/models.py#L1174
If you're not overriding choices explicitly, then your code might look like this:
class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return '[{0.pk}] {0.label} ({0.location})'.format(obj)
class RectificationForm(forms.ModelForm):
items = RectificationAssetMultiField(
required=False,
queryset=InspectionItem.objects.all(),
widget=forms.CheckboxSelectMultiple,
label="Non-compliant Assets"
)
class Meta:
model = Rectification
fields = ('ref', 'items', 'status')
Be careful that this will only work if you're not setting choices directly (see _get_choices in the above URL).
If instead you wanted to override choices (for a more efficient result than a queryset, or something better expressed as a ValuesList) then you would have something like this:
class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return '[{0.pk}] {0.label} ({0.location})'.format(obj)
class RectificationForm(forms.ModelForm):
items = RectificationAssetMultiField(
required=False,
queryset=InspectionItem.objects.none(),
widget=forms.CheckboxSelectMultiple,
label="Non-compliant Assets"
)
def __init__(self, *args, **kwargs):
super(RectificationForm, self).__init__(*args, **kwargs)
self.fields['items'].choices = (InspectionItem.objects
.active()
.noncompliant()
.filter(property_id=self.instance.property_id)
.values_list('pk', 'label') # pass a key value pair
)
class Meta:
model = Rectification
fields = ('ref', 'items', 'status')
Don't use {{ form.as_p }} if you don't like that rendering.
Loop over the form instead:
<form action="/contact/" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
You are then free to use whatever HTML you want.
From: https://docs.djangoproject.com/en/dev/topics/forms/#looping-over-the-form-s-fields
Related
I have following model form where I have to add a hidden field.
class AddEditGroupForm(forms.ModelForm):
id_sel_comp = forms.CharField(
label='selected company',
initial=0,
required=True,
widget=forms.HiddenInput(attrs={'id': 'id_sel_comp'})
)
class Meta:
model = Group
fields = ('name', 'id_sel_comp')
def __init__(self, *args, **kwargs):
super(AddEditGroupForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class': 'form-control m-input form-control-sm'})
def as_two_col_layout(self):
return self._html_output(
normal_row='<div class="form-group m-form__group row"><label class="col-sm-3 col-form-label">%(label)s</label><div class="col-sm-9">%(field)s%(help_text)s</div></div>',
error_row='%s',
row_ender='',
help_text_html=' <span class="m-form__help">%s</span>',
errors_on_separate_row=True)
The form displays only the hidden form field and the 'name' charfield is not displayed. When I mark the field 'id_sel_comp' as NOT hidden, all fields are displayed. What is wrong with this?
The form is rendered in the template with:
{{ form.as_two_col_layout }}
You did not specify your row_ender properly. You are currently setting it to '' which isn't correct to what your specified as normal_row. Your row_ender in your case is </div></div>. So your as_two_col_layout becomes,
def as_two_col_layout(self):
return self._html_output(
normal_row='<div class="form-group m-form__group row">'
'<label class="col-sm-3 col-form-label">%(label)s</label>'
'<div class="col-sm-9">%(field)s%(help_text)s</div></div>',
error_row='%s',
row_ender='</div></div>',
help_text_html=' <span class="m-form__help">%s</span>',
errors_on_separate_row=True)
Hope this helps!
I succesfully implemented the django-selectable AutoCompleteSelectField in a simple form that lets you enter an note description and a corresponding domain category ( domain and foreign key picked from other Many-to-One relationship
See: most relevant code:
# MODEL
class Note(models.Model):
notetext = models.TextField(default='nota')
domain = models.ForeignKey(Domain)
def __str__(self):
return self.notetext
def get_absolute_url(self):
return reverse('note:note_detail', args= [self.id])
# FORM
class NoteForm(forms.ModelForm):
domainselect = AutoCompleteSelectField(lookup_class= DomainLookup, label='Pick a domain category', required=True,)
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
domaintext = self.instance.domain.title
self.fields['domainselect'].widget = AutoCompleteSelectWidget(DomainLookup , { 'value': self.instance.domain.title } )
def save(self, commit=True):
self.instance.domain = self.cleaned_data['domainselect']
return super(NoteForm, self).save(commit=commit)
class Meta:
model = Note
fields = ('notetext',)
widgets = {
'domain' : AutoCompleteSelectWidget(DomainLookup), }
# VIEW
class EditNoteView(generic.edit.UpdateView):
model = Note
form_class = NoteForm
success_url = "/note/"
def get_queryset(self):
base_qs = super(EditNoteView, self).get_queryset()
return base_qs.filter()
def get_object(self):
object = get_object_or_404(Note,id=self.kwargs['id'])
return object
# TEMPLATE
{% extends "base_sidebar.html" %}
{%block content%}
<form action="" method="post">
{{form.as_p}}
<button type="submit">Save</button>
{% csrf_token %}
{% load selectable_tags %}
{{ form.media.css }}
{{ form.media.js }}
</form>
{%endblock%}
Now, when an existing record is selected for editing via generic.edit.UpdateView in a Modelform, I want to populate the AutocompleteSelectField with the corresponding values ( domain description and id ) formerly saved into the database upon loading the form.
By overwriting the init(self, *args, **kwargs) method of the NoteForm, I was able to get almost this far in the sense that the first HTML input field gets populated.
However, the hidden input value gets set to the same value and pushing the save button results in posting a non valid form as if no domain category was selected.
Here's the page source that is sent back to the Browser:
<p><label for="id_domainselect_0">Pick a domain:</label>
<input data-selectable-allow-new="false" data-selectable-type="text" data-selectable-url="/selectable/domain-domainlookup/" id="id_domainselect_0" name="domainselect_0" type="text" value="politics" />
<input data-selectable-type="hidden" id="id_domainselect_1" name="domainselect_1" type="hidden" value="politics" /></p>
I don't know how to change the context (by setting self.fields['domainselect'].widget) in order to get the title into the domainselect_0 input value and the corresponding pk into the hidden domainselect_1 input value. ?
Thanks for helping me out.
After digging down into the django-selectable and Django code it appears the AutocompleteSelectWidget is based on the Django forms.MultiWidget class.
The Django MultiWidget accepts 1 single value (list) that is decomposed into the values corresponding to the respective 'subWidgets' through a mechanism implemented in a decompress method. ( see https://github.com/mlavin/django-selectable/blob/master/selectable/forms/widgets.py class SelectableMultiWidget )
So, all you have to do is assign a list containing title and id to the widget:
def __init__(self, *args, **kwargs):
super(NoteForm, self).__init__(*args, **kwargs)
self.initial['domainselect'] = [self.instance.domain.title , self.instance.domain.id ]
Using Django I aim to create a form with a "select" field, and populate that with values from the db table "TestTable".
TestTable's fields are: id, desc1, desck2, desc3, desc4, etc...
Here is my code in the form.py:
class TestForm(forms.ModelForm):
field1 = ModelChoiceField(queryset=TestTable.objects.all().order_by('desc1'))
class Meta(object):
model = BlockValue
fields = ()
Here is the template:
<html>
<head><title>TEST PAGE</title></head>
<body>
Test:
{{ form }}
</body>
</html>
Here is the view.py:
def test(request):
form = TestForm()
return render(request, 'test.html', {'form': form})
When I render the form the result is:
<tr><th><label for="id_field1">Field1:</label></th><td><select id="id_field1" name="field1">
<option value="" selected="selected">---------</option>
<option value="1">aaaaaaa</option>
<option value="3">bbbbbbb</option>
<option value="2">ccccccc</option>
</select></td></tr>
How to chose which field print in the option tag?
There are two ways. The quick way to is to change the __unicode__ return for your TestTable to return the field you like. However you might only want to show that field in current form but not other places, so it's not ideal.
Second option, you could define you own form field. It inherits ModelChoiceField, but override label_from_instance method:
class TestTableModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
# return the field you want to display
return obj.display_field
class TestForm(forms.ModelForm):
type = TestTableModelChoiceField(queryset=Property.objects.all().order_by('desc1'))
class TestForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs) # initialize form, which will create self.fields dict
self.fields['field1'].choices = [(o.id, str(o).upper()) for o in TestTable.objects.all()] # provide a list of tuples [(pk,display_string),(another_pk,display_str),...]
# display string can be whatever str/unicode you want to show.
How to add:
onclick="this.form.submit();"
in my radiobutton form? I would like to post my form when user click to the radiobutton.
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.news = kwargs.pop('news')
super(MyForm, self).__init__(*args, **kwargs)
choices = ([ ("%s" % a.id, "%s" % a.text) for a in self.news])
self.fields['new'] = forms.ChoiceField(choices = choices, widget=forms.RadioSelect())
I would like to have this result in template:
<input type="radio" id="new" name="new" value="new" onclick="this.form.submit();">
self.fields['new'] = forms.ChoiceField(choices = choices, widget=forms.RadioSelect(attrs={'onclick': 'this.form.submit();'}))
while it's not the best idea to place template logic in your .py files.
How do you generate your form in a template?
If you use {{ form.as_p }} than consider rendering your custom form like described in: Django's Custom Forms
Hey,
I'm using a model formset to let my users edit their photo album. I want to put a Radio select box on every photo saying "Set as cover image" so that I can process all the photos and find the one who should be album cover. The problem is how can I a field with radio select on to the formset and still keep it mutal with the rest of the photos? This is my current code:
class ProjectGalleryForm(forms.ModelForm):
remove_photo = forms.BooleanField()
# set_as_cover_image = .... ?? <-- what to put?
class Meta:
model = Photo
exclude = (
'effect',
'caption',
'title_slug',
'crop_from',
'is_public',
'slug',
'tags'
)
I think the key here is that the radio button is not actually part of the formset: it's part of the parent form. It's the actual Album model that needs to know which of the Photo objects is the cover image. So what you want to do is to display each option from the radio button alongside its corresponding line in the Photo formset - and that's the tricky bit, because Django can't render form fields in that way. You'll need to produce the HTML for each option manually.
So, given these forms, and assuming the Album model has a cover_image which is a OneToOneField to Photo:
class AlbumForm(forms.modelForm):
class Meta:
model = Album
photo_formset = forms.inlineformset_factory(Album, Photo, form=ProjectGalleryForm)
in the template you would do something like:
{% for photo_form in photo_formset %}
<tr><td>
{% if photo_form.instance.pk %}
<input type="radio" id="id_cover_image_{{ forloop.counter }}" name="cover_image" value="{{ photo_form.instance.pk }}">
<label for="id_cover_image_{{ forloop.counter }}">Use as cover image</label>
{% endif %>
</td><td>{{ photo_form.as_p }}</td>
</tr>
{% endfor %}
I like to have the a neat template file and therefore, I made a custom widget for this purpose.
class SingleRadioInput(Input):
input_type = 'radio'
def render(self, value, checked, attrs=None):
output = []
if value:
is_cover = ''
if checked : is_cover = 'checked'
output.append(
('<input type="radio" name="inline" value="%s" %s/>')
% (value, is_cover)
)
return mark_safe(u''.join(output))
Hope it can help someone
Based on #Mikou answer, here is my more comprehensive solution.
In order to keep my template clean and pretty, I used a custom widget
class SingleRadioInput(forms.widgets.Input):
input_type = 'radio'
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type=self.input_type)
output = []
if name:
is_checked = ''
if value:
is_checked = 'checked'
output.append(
('<input id="%s" type="radio" name="%s" value="%s" %s/>')
% (final_attrs['id'], final_attrs['name'], final_attrs['instance_id'], is_checked )
)
return mark_safe(u''.join(output))
My object form looks like that, it will auto select the object if the field default == True
class ObjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ObjectForm, self).__init__(*args, **kwargs)
self.fields['default'].widget.attrs.update({'instance_id': self.instance.id, 'name': 'default'})
if self.instance.default:
self.fields['default'].widget.attrs.update({'value': True})
class Meta:
model = MyModel
fields = ['default']
widgets = {
'default': SingleRadioInput(),
}
Here is my formset
ProductReferenceFormset = inlineformset_factory(ParentModel, MyModel,
form=ObjectForm,
extra=0, can_delete=False, can_order=False)
I gave up handling the save part in the form, it is really not worth the complexity I think... So the save part is in the form_valid() in the View
def form_valid(self, form, price_form):
form.save()
# save the default radio
MyModel.objects.filter(parent=self.object).update(default=False)
MyModel.objects.filter(id=self.request.POST.get('default')).update(default=True)
return HttpResponseRedirect(self.get_success_url())
Qualification:
<option value='10th' {% if '10th' in i.qf %} selected='select' {% endif %}>10th</option>
<option value='12th' {% if '12th' in i.qf %} selected='select' {% endif %}>12th</option>
<option value='graduted' {% if 'Graduated' in i.qf %} selected='select' {% endif %}>Graduated</option>
</select>
<br><br>