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>
Related
I am trying to display a ModelForm with prepopulated instance data.
It works fine except for the ChoiceField which always displays the first choice given in forms.py ('LP') rather than the choice provided by the instance.
View:
def review(request):
order = Order.objects.get(user__pk=request.user.id)
form = ProjectReviewForm(instance=order)
context = {
'form': form,
}
return render(request, 'users/projectreview.html', context)
Forms:
class ReviewForm(forms.ModelForm):
LAND = 'LP' // #INSTANCE ALWAYS SHOWS THIS RATHER THAN INSTANCE DATA
DATA = 'DC'
STATIC = 'SW'
CHOICES = (
(LAND, 'LP'),
(DATA, 'DC'),
(STATIC, 'SW')
)
product = forms.ChoiceField(choices=CHOICES, widget=forms.Select(attrs={'class': 'form-field w-input'}),)
class Meta:
model = Order
fields = '__all__'
template:
<form method="POST" class="contact-form">
{% csrf_token %}
<h2 class="form-heading-small">Please make sure everything you've submitted is correct.</h2>
{{ form }}
<button type="submit" data-wait="Please wait..." class="button w-button">Looks good!</button>
</form>
The product field on the form is overriding the field on the model. Look into using a ModelChoiceField
I have an Item object that has a manytomany relation to another object Option. I create a modelform from the Item object like so;
class Item(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
options = models.ManyToManyField(Option)
class OptionForm(ModelForm):
options = forms.ChoiceField(widget=forms.RadioSelect())
class Meta:
model = Item
fields = ( 'options', )
When i render the form in the template it renders all available options for the Item object(the expected behavior) even those not created by a specific item. I want to be able to load options defined by the specific Item that will be chosen by the user. How do i override the form to achieve such behavior.for example without a form i can render an Items own Options through its id. item = Item.objects.get(pk=id)
Its tough to make ModelForm's defined on the fly because they are intimately tied to the structure in your model. Nevertheless you could use some clever template control flow and view rendering to get your desired effect. This is untested so you millage with this might vary.
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
{% if user_option.category %}
<li>{{ form.caregory }}</li>
{% endif %}
{% if user_option.name %}
<li>{{ form.name }}</li>
{% endif %}
{% if user_option.p_opt %}
<li>{{ form.price }}</li>
<li>{{ form.options }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
From the Djano docs here.
Try overriding the form's init method and passing in the Item pk as an additional argument. The trick here is to pop the argument before calling the parent init.
class ItemOptionsForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, *args, **kwargs):
# pop 'item_id' as parent's init is not expecting it
item_id = kwargs.pop('item_id', None)
# now it's safe to call the parent init
super(ItemOptionsForm, self).__init__(*args, **kwargs)
# Limit options to only the item's options
if item_id:
try:
item = Item.objects.get(id=item_id)
except:
raise ValidationError('No item found!')
self.fields['options'] = forms.ChoiceField(item.options)
Then, in your view, create the form like:
form = ItemOptionsForm(item_id=item_id)
The nice thing about this is that you can raise ValidationErrors that will show up in the form.
Be aware that this doesn't prevent someone from POSTing option IDs to your form which do not belong to the Item, so you'll likely want to override the ModelForm.clean() to validate the options as well.
learning from link django: How to limit field choices in formset? provided by #jingo, i solved the problem by first of all creating dynamic form like so;
def partial_order_item_form(item):
"""dynamic form limiting optional_items to their items"""
class PartialOrderItemform(forms.Form):
quantity = forms.IntegerField(widget=forms.TextInput(attrs={'size':'2', 'class':'quantity','maxlength':'5'}))
option = forms.ModelChoiceField(queryset=OptionalItems.objects.filter(item=item),widget= forms.RadioSelect())
return PartialOrderItemform
then validating form like so;
def show_item(request,id):
option = get_object_or_404(Item,pk=id)
if request.method == 'POST':
form = partial_order_item_form(option)
#bound form to POST data,
final_form = form(request.POST)
# check validation of posted data
if final_form.is_valid():
order.add_to_order(request)
url =urlresolvers.reverse('order_index',kwargs={'id':a.id})
# redirect to order page
return HttpResponseRedirect(url)
else:
form = partial_order_item_form(item=id)
context={
'form':form,
}
return render_to_response('item.html',context,context_instance=RequestContext(request))
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
I have pretty complicated form items ;)
Here's my view for serving my form:
def view(request):
if request.method == "POST":
form = forms.ProfileEditForm(request.POST)
else:
form = forms.ProfileEditForm(initial={'country': request.user.profile.country})
return direct_to_template(request, "name/of/template.html",
{"form": form, "countries": Country.objects.all()})
Here's what the country model looks like:
class Country(models.Model):
iso2 = models.CharField()
iso3 = models.CharField()
name = models.CharField()
Here's what my form's clean() method looks like (highly simplified):
def clean(self):
self.cleaned_data['country'] = Country.objects.get(iso2=self.cleaned_data['country'])
return self.cleaned_data
Here's how I'm rendering it in a template:
<select name="country"{% if form.country.value %} data-initialvalue="{{form.country.value.iso2}}"{% endif %}>
{% for country in countries %}
<option value="{{country.iso2}}"{% if country.iso2==form.country.value.iso2 %} selected{% endif %}>{{country.name}}</option>
{% endfor %}
</select>
However, I'm noticing that what actually ends up being delivered to my template isn't a Country object, but a string of my input data, such as "US". When I initially render my template with the initial data, things look fine, but when validation fails, things get messed up. What should I do, and what am I doing wrong?
Just use ModelChoiceField for your country field.
And don't forget to define __unicode__ method of the model.
I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?