How can I group checkboxes produced by CheckboxSelectMultiple by a related model?
This is best demonstrated by example.
models.py:
class FeatureCategory(models.Model):
name = models.CharField(max_length=30)
class Feature(models.Model):
name = models.CharField(max_length=30)
category = models.ForeignKey(FeatureCategory)
class Widget(models.Model):
name = models.CharField(max_length=30)
features = models.ManyToManyField(Feature, blank=True)
forms.py:
class WidgetForm(forms.ModelForm):
features = forms.ModelMultipleChoiceField(
queryset=Feature.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False
)
class Meta:
model = Widget
views.py:
def edit_widget(request):
form = WidgetForm()
return render(request, 'template.html', {'form': form})
template.html:
{{ form.as_p }}
The above produces the following output:
[] Widget 1
[] Widget 2
[] Widget 3
[] Widget 1
[] Widget 2
What I would like is for the feature checkboxes to be grouped by feature category (based on the ForeignKey):
Category 1:
[] Widget 1
[] Widget 2
[] Widget 3
Category 2:
[] Widget 1
[] Widget 2
How can I achieve this? I have tried using the {% regroup %} template tag to no avail.
Any advice much appreciated.
Thanks.
You have to write the custom CheckboxSelectMultiple widget. Using the snippet I have tried make the CheckboxSelectMultiple field iterable by adding the category_name as an attribute in field attrs. So that I can use regroup tag in template later on.
The below code is modified from snippet according to your need, obviously this code can be made more cleaner and more generic, but at this moment its not generic.
forms.py
from django import forms
from django.forms import Widget
from django.forms.widgets import SubWidget
from django.forms.util import flatatt
from django.utils.html import conditional_escape
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
from itertools import chain
import ast
from mysite.models import Widget as wid # your model name is conflicted with django.forms.Widget
from mysite.models import Feature
class CheckboxInput(SubWidget):
"""
An object used by CheckboxRenderer that represents a single
<input type='checkbox'>.
"""
def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
self.attrs = attrs
self.choice_value = force_unicode(choice[1])
self.choice_label = force_unicode(choice[2])
self.attrs.update({'cat_name': choice[0]})
self.index = index
def __unicode__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
name = name or self.name
value = value or self.value
attrs = attrs or self.attrs
if 'id' in self.attrs:
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
else:
label_for = ''
choice_label = conditional_escape(force_unicode(self.choice_label))
return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), choice_label))
def is_checked(self):
return self.choice_value in self.value
def tag(self):
if 'id' in self.attrs:
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='checkbox', name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return mark_safe(u'<input%s />' % flatatt(final_attrs))
class CheckboxRenderer(StrAndUnicode):
def __init__(self, name, value, attrs, choices):
self.name, self.value, self.attrs = name, value, attrs
self.choices = choices
def __iter__(self):
for i, choice in enumerate(self.choices):
yield CheckboxInput(self.name, self.value, self.attrs.copy(), choice, i)
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propogate
return CheckboxInput(self.name, self.value, self.attrs.copy(), choice, idx)
def __unicode__(self):
return self.render()
def render(self):
"""Outputs a <ul> for this set of checkbox fields."""
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
% force_unicode(w) for w in self]))
class CheckboxSelectMultipleIter(forms.CheckboxSelectMultiple):
"""
Checkbox multi select field that enables iteration of each checkbox
Similar to django.forms.widgets.RadioSelect
"""
renderer = CheckboxRenderer
def __init__(self, *args, **kwargs):
# Override the default renderer if we were passed one.
renderer = kwargs.pop('renderer', None)
if renderer:
self.renderer = renderer
super(CheckboxSelectMultipleIter, self).__init__(*args, **kwargs)
def subwidgets(self, name, value, attrs=None, choices=()):
for widget in self.get_renderer(name, value, attrs, choices):
yield widget
def get_renderer(self, name, value, attrs=None, choices=()):
"""Returns an instance of the renderer."""
choices_ = [ast.literal_eval(i[1]).iteritems() for i in self.choices]
choices_ = [(a[1], b[1], c[1]) for a, b, c in choices_]
if value is None: value = ''
str_values = set([force_unicode(v) for v in value]) # Normalize to string.
if attrs is None:
attrs = {}
if 'id' not in attrs:
attrs['id'] = name
final_attrs = self.build_attrs(attrs)
choices = list(chain(choices_, choices))
return self.renderer(name, str_values, final_attrs, choices)
def render(self, name, value, attrs=None, choices=()):
return self.get_renderer(name, value, attrs, choices).render()
def id_for_label(self, id_):
if id_:
id_ += '_0'
return id_
class WidgetForm(forms.ModelForm):
features = forms.ModelMultipleChoiceField(
queryset=Feature.objects.all().values('id', 'name', 'category__name'),
widget=CheckboxSelectMultipleIter,
required=False
)
class Meta:
model = wid
Then in template:
{% for field in form %}
{% if field.name == 'features' %}
{% regroup field by attrs.cat_name as list %}
<ul>
{% for el in list %}
<li>{{el.grouper}}
<ul>
{% for e in el.list %}
{{e}} <br />
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
{% else %}
{{field.label}}: {{field}}
{% endif %}
{% endfor %}
Results:
I added countries name in category table, and cities name in features table so in template I was able to regroup the cities (features) according to country (category)
Here's a solution for current versions of Django (~2.1).
## forms.py
from itertools import groupby
from django import forms
from django.forms.models import ModelChoiceIterator, ModelMultipleChoiceField
from .models import Feature, Widget
class GroupedModelMultipleChoiceField(ModelMultipleChoiceField):
def __init__(self, group_by_field, group_label=None, *args, **kwargs):
"""
``group_by_field`` is the name of a field on the model
``group_label`` is a function to return a label for each choice group
"""
super(GroupedModelMultipleChoiceField, self).__init__(*args, **kwargs)
self.group_by_field = group_by_field
if group_label is None:
self.group_label = lambda group: group
else:
self.group_label = group_label
def _get_choices(self):
if hasattr(self, '_choices'):
return self._choices
return GroupedModelChoiceIterator(self)
choices = property(_get_choices, ModelMultipleChoiceField._set_choices)
class GroupedModelChoiceIterator(ModelChoiceIterator):
def __iter__(self):
"""Now yields grouped choices."""
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
for group, choices in groupby(
self.queryset.all(),
lambda row: getattr(row, self.field.group_by_field)):
if group is None:
for ch in choices:
yield self.choice(ch)
else:
yield (
self.field.group_label(group),
[self.choice(ch) for ch in choices])
class WidgetForm(forms.ModelForm):
class Meta:
model = Widget
fields = ['features',]
def __init__(self, *args, **kwargs):
super(WidgetForm, self).__init__(*args, **kwargs)
self.fields['features'] = GroupedModelMultipleChoiceField(
group_by_field='category',
queryset=Feature.objects.all(),
widget=forms.CheckboxSelectMultiple(),
required=False)
Then you can use {{ form.as_p }} in the template for properly grouped choices.
If you would like to use the regroup template tag and iterate over the choices, you will also need to reference the following custom widget:
class GroupedCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
def optgroups(self, name, value, attrs=None):
"""
The group name is passed as an argument to the ``create_option`` method (below).
"""
groups = []
has_selected = False
for index, (option_value, option_label) in enumerate(self.choices):
if option_value is None:
option_value = ''
subgroup = []
if isinstance(option_label, (list, tuple)):
group_name = option_value
subindex = 0
choices = option_label
else:
group_name = None
subindex = None
choices = [(option_value, option_label)]
groups.append((group_name, subgroup, index))
for subvalue, sublabel in choices:
selected = (
str(subvalue) in value and
(not has_selected or self.allow_multiple_selected)
)
has_selected |= selected
subgroup.append(self.create_option(
name, subvalue, sublabel, selected, index,
subindex=subindex, attrs=attrs, group=group_name,
))
if subindex is not None:
subindex += 1
return groups
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, group=None):
"""
Added a ``group`` argument which is included in the returned dictionary.
"""
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
'wrap_label': True,
'group': group,
}
class WidgetForm(forms.ModelForm):
class Meta:
model = Widget
fields = ['features',]
def __init__(self, *args, **kwargs):
super(WidgetForm, self).__init__(*args, **kwargs)
self.fields['features'] = GroupedModelMultipleChoiceField(
group_by_field='category',
queryset=Feature.objects.all(),
widget=GroupedCheckboxSelectMultiple(),
required=False)
Then the following should work in your template:
{% regroup form.features by data.group as feature_list %}
{% for group in feature_list %}
<h6>{{ group.grouper|default:"Other Features" }}</h6>
<ul>
{% for choice in group.list %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
Credit to the following page for part of the solution:
https://mounirmesselmeni.github.io/2013/11/25/django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield/
Related
I've got two models, Question and Project. Project has a ManyToManyField referencing Question:
## models.py
class Question(models.Model):
category = models.CharField(
max_length=4000,
blank=True,
null=True
)
question = models.CharField(
max_length=4000,
blank=True,
null=True
)
class Project(models.Model):
name = models.CharField(
max_length=100,
blank=False,
)
questions = models.ManyToManyField(
Question,
blank=True,
)
From these I have a CreateView and a custom form assigning the CheckboxSelectMultiple widget to the ManyToManyField.
## views.py
class ProjectCreate(LoginRequiredMixin, CreateView):
model = Project
form_class = ProjectForm
## forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name','questions',]
widgets = {'questions':forms.CheckboxSelectMultiple()}
So, what I want to do now is group the individual labels and checkboxes for the questions CheckboxSelectMultiple form field according to each question's category. Something along the lines of this (not sure exactly how it would work):
{% for field in form.questions %}
{% regroup field by <INSERT MYSTERY CODE> as question_list %}
{% for category, field in question_list %}
{{ category }}
{{ field }}
{% endfor%}
{% endfor %}
EDIT:
A more detailed example of one approach I've tried:
{% for field in form.questions %}
{% regroup field by field.category as question_list %}
{% for category, fields in question_list %}
{{ category }}
{% for f in fields%}
{{ f }}
{% endfor %}
{% endfor%}
{% endfor %}
The above results in TypeError: 'BoundWidget' object is not iterable
I have been trying to figure this out for the best part of a week. I was looking for a simple solution but no luck. The following seems to achieve what you want.
## forms.py
from itertools import groupby
from django import forms
from django.forms.models import ModelChoiceIterator, ModelMultipleChoiceField
from .models import Project, Question
class GroupedModelMultipleChoiceField(ModelMultipleChoiceField):
def __init__(self, group_by_field, group_label=None, *args, **kwargs):
"""
``group_by_field`` is the name of a field on the model
``group_label`` is a function to return a label for each choice group
"""
super(GroupedModelMultipleChoiceField, self).__init__(*args, **kwargs)
self.group_by_field = group_by_field
if group_label is None:
self.group_label = lambda group: group
else:
self.group_label = group_label
def _get_choices(self):
if hasattr(self, '_choices'):
return self._choices
return GroupedModelChoiceIterator(self)
choices = property(_get_choices, ModelMultipleChoiceField._set_choices)
class GroupedModelChoiceIterator(ModelChoiceIterator):
def __iter__(self):
"""Now yields grouped choices."""
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
for group, choices in groupby(
self.queryset.all(),
lambda row: getattr(row, self.field.group_by_field)):
if group is None:
for ch in choices:
yield self.choice(ch)
else:
yield (
self.field.group_label(group),
[self.choice(ch) for ch in choices])
class GroupedCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
def optgroups(self, name, value, attrs=None):
"""
The group name is passed as an argument to the ``create_option`` method (below).
"""
groups = []
has_selected = False
for index, (option_value, option_label) in enumerate(self.choices):
if option_value is None:
option_value = ''
subgroup = []
if isinstance(option_label, (list, tuple)):
group_name = option_value
subindex = 0
choices = option_label
else:
group_name = None
subindex = None
choices = [(option_value, option_label)]
groups.append((group_name, subgroup, index))
for subvalue, sublabel in choices:
selected = (
str(subvalue) in value and
(not has_selected or self.allow_multiple_selected)
)
has_selected |= selected
subgroup.append(self.create_option(
name, subvalue, sublabel, selected, index,
subindex=subindex, attrs=attrs, group=group_name,
))
if subindex is not None:
subindex += 1
return groups
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, group=None):
"""
Added a ``group`` argument which is included in the returned dictionary.
"""
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
'wrap_label': True,
'group': group,
}
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name', 'questions']
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['questions'] = GroupedModelMultipleChoiceField(
group_by_field='category',
queryset=Question.objects.all(),
widget=GroupedCheckboxSelectMultiple())
Then the following should work in your template:
{% regroup form.questions by data.group as question_list %}
{% for group in question_list %}
<h6>{{ group.grouper|default:"Other Questions" }}</h6>
<ul>
{% for choice in group.list %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
Make sure you add a __str__() method to your Question class so that the question names appear correctly.
You need GroupedModelMultipleChoiceField and GroupedModelChoiceIterator to ensure your choices are properly grouped within the field instance. This means you are able to use {{ form.questions }} in the template to render a structured list.
However, if you want to use the regroup tag in a template, each choice needs to have the group name as an attribute. The GroupedCheckboxSelectMultiple widget provides this.
Credit to the following page for part of the solution:
https://mounirmesselmeni.github.io/2013/11/25/django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield/
I want to be able to vary the placeholder like so:
<input placeholder=" {{ input.placeholder }}">
Where input is a model with the "placeholder" field. The placeholder field will vary since I'll be using a formset, and each placeholder will vary.
Here's my modelForm
class Value(forms.ModelForm):
class Meta:
model = ValueModel
fields = ['value_text']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
and my modelformset
values_formset = modelformset_factory(model=ValueModel, extra=0, form=Value)
I've tried
class Value(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.fields['placeholder']
class Meta:
model = ValueModel
fields = ['value_text', 'placeholder']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
And other attempts at trying to modify the self.fields with no success.
Edit: The relevant part of my views.py:
def page_view(request, values_id):
values_form = values_formset(queryset=ValueModel.objects.filter(
values_id=values_id))
context = {'value': values_form}
return render(request, 'view.html', context)
My template view:
{{ value.management_form }}
{% for form in value %}
{{ form.id }}
{{ form.value_text }}
{% endfor %}
self.fields['placeholder'] refers to a form field object, not a value; you couldn't use it as a placeholder. But it seems like what you want is to use the value of the model instance.
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.instance.placeholder
models.py
class Car(models.Model):
Car_Name = models.CharField(max_length=40)
Car_Details = models.TextField()
Car_Price = models.PositiveIntegerField()
Car_Brand = models.ForeignKey('Brand', on_delete=models.CASCADE)
Fuel_type = models.ManyToManyField('FuelType')
def __str__(self):
return self.Car_Name
def get_absolute_url(self):
return reverse('car_detail', args=[str(self.pk)])
class FuelType(models.Model):
Fuel_Choices = (
('Petrol','petrol'),
('Diesel','diesel'),
('CNG','cng')
)
Fuel_Variant = models.CharField(max_length=10, choices=Fuel_Choices, default='1')
def __str__(self):
return self.Fuel_Variant
forms.py
class AjaxModelChoiceField(forms.ModelChoiceField):
def __init__(self, model_class, *args, **kwargs):
queryset = model_class.objects.none()
super(AjaxModelChoiceField, self).__init__(queryset, *args, **kwargs)
self.model_class = model_class
def to_python(self, value):
if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
value = self.model_class.objects.get(**{key: value})
except (ValueError, self.queryset.model.DoesNotExist):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return value
# a Custom Choicefield to populate Dropdown on Selection (let's ajax works)
class SearchCarForm(forms.ModelForm):
Car_Brand = forms.ModelChoiceField(queryset=Brand.objects.all(), widget=forms.Select(attrs={'class':'form-control'}), empty_label="Select Brand")
Car_Name = AjaxModelChoiceField(model_class=Car, widget=forms.Select(attrs={'class':'form-control'}), empty_label="Car Models", required=False)
Fuel_type = AjaxModelChoiceField(model_class=FuelType, widget=forms.Select(attrs={'class':'form-control'}), empty_label="Fuel Options", required=False)
class Meta:
model = Car
fields = ('Car_Brand', 'Car_Name', 'Fuel_type')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['Car_Name'].queryset = Car.objects.all()
if 'Brand' in self.data:
try:
brand_id = int(self.data.get('Car_Brand'))
self.fields['Car_Name'].queryset = Car.objects.filter(Car_Brand_id=brand_id).order_by('Car_Name')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty Car queryset
elif self.instance.pk:
self.fields['Car_Name'].queryset = self.instance.Brand.Car_set.order_by('Car_Name')
self.fields['Fuel_type'].queryset = FuelType.objects.all()
if 'Car' in self.data:
try:
Car_id = int(self.data.get('Car_Name'))
self.fields['Fuel_type'].queryset = FuelType.objects.filter(id=car_id).order_by('Fuel_type')
except (ValueError, TypeError):
pass # invalid input from the client; ignore and fallback to empty Car queryset
elif self.instance.pk:
self.fields['Fuel_type'].queryset = self.instance.Car.FuelType_set.order_by('Fuel_type')
def clean(self):
cleaned_data = super(SearchCarForm, self).clean()
return cleaned_data
views.py
function devised to fetch choices for fuel populated with Ajaxchoicefield from forms, similar function is used to dynamically fetch cars from thier brand name and works fine
def load_fuel(request):
car_id = request.GET.get('Car_Name')
fuel = Car.objects.filter(id=car_id)
print(fuel)
context = {
'fuel': fuel,
}
return render(request, 'fuel_dropdown_list_options.html', context=context)
fuel_dropdown_list_options.html
<option value="">Fuel options</option>
{% for fuels in fuel %}
<option value="{{ fuels.id }}">{{ fuels.Fuel_type }}</option>
{% endfor %}
template view
PS: I'm new to stackoverflow if something's missing/incomplete or i have posted something wrong, i apologise in advance
I want to show the data of a user which he has entered. This is my model
class IgaiaContent(models.Model):
CONTENT_CHANNELS = (
('YouTube','Youtube'),
('FaceBook','FaceBook'),
('Flickr','Flickr'),
('Instagram','Instagram'),
)
content_name = models.CharField(max_length=255, primary_key=True)
content_type = models.CharField(max_length=255,null=True)
content_source = models.CharField(max_length=255,null=True, choices=CONTENT_CHANNELS)
content_location = models.CharField(max_length=255,null=True)
content_latitude = models.DecimalField(max_digits=20,decimal_places=2,null=True)
content_longitude = models.DecimalField(max_digits=20,decimal_places=2,null=True)
content_embed_code = models.TextField(null=True)
content_description = models.TextField(null=True)
content_tags_user = models.CharField(max_length=255,null=True)
content_time_uploaded = models.DateTimeField(auto_now_add=True)
content_time_updated = models.DateField(null=True)
def __unicode__(self):
return self.content_name
return self.content_type
return self.content_source
return self.content_location
return self.content_latitude
return self.content_longitude
return self.embed_code
return self.description
return self.tags_user
return self.time_uploaded
return self.time_updated
tagging.register(IgaiaContent)
My view
def create_page(request):
if request.method == 'POST':
form = AuthorForm1(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/')
else:
form = AuthorForm1()
c = {}
c.update(csrf(request))
return render_to_response('portal/form1.htm',{'form':form},context_instance=RequestContext(request))
My form template:
<form method="post" style="height: 553px; width: 594px">
<div class="style12">
{% csrf_token %}
</br>{{ form.as_p }}
</div>
</form>
thats how i am showing my model values
employee_info1 = {
"queryset" : IgaiaContent.objects.all(),
"template_name" : "portal/emp1.html",
}
urlpatterns = patterns('',
(r'^view5/', list_detail.object_list, employee_info1),
)
emp1.html
{% if object_list %}
<table>
<ul>
{% for item in object_list %}
<li>{{item.content_name}}</li>
<li>{{item.content_type}}</li>
<li>{{item.content_source}}</li>
<li>{{item.content_location}}</li>
<li>{{item.content_latitude}}</li>
<li>{{item.content_longitude}}</li>
<li>{{item.content_embed_code}}</li>
<li>{{item.content_description}}</li>
<li>{{item.content_tags_user}}</li>
<li>{{item.content_time_uploaded}}</li>
<li>{{item.content_time_updated}}</li></ul>
{% empty %}
<td colspan="11">No items.</td>
{% endfor %}
</table>
{% endif %}
It is not displaying specific user value means it is displaying me everything.
can anyone tell me how to show specific user values/data?
You need to update your model so that it contains a field to store the user -
from django.contrib.auth.models import User
class IgaiaContent(models.Model):
#...
user = models.ForeignKey(User)
Then you need to create a ModelForm as described here.
class IgaiaContentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
class Meta:
model = IgaiaContent
Now update your view so that that you use your new ModelForm
def create_page(request):
if request.method == 'POST':
form = IgaiaContentForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/thanks/')
else:
form = IgaiaContentForm()
#...
Now in your object_list view you do something like -
from django.shortcuts import render_to_response
def object_list(request):
#....
object_list = IgaiaContent.objects.filter(user=request.user)
return render_to_response('object_list_template.html', {'object_list': object_list})
I have the following models,
### models.py
class Foo(models.Model):
name = models.CharField(max_length=200)
class Bar(models.Model):
foo = models.ForeignKey(Foo)
baz = models.ManyToManyField(Baz, through='Between')
class Baz(models.Model):
name = models.CharField(max_length=200)
class Between(models.Model):
foo = models.ForeignKey(Foo)
bar = models.ForeignKey(Bar)
CHOICES = (
('A', 'A'),
('B', 'B'),
('C', 'C'),
)
value = models.CharField(max_length=1, choices=CHOICES)
and I have the following forms,
### forms.py
class FooForm(forms.ModelForm):
class Meta:
model = Foo
class BarForm(forms.ModelForm):
baz = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(),
queryset=Baz.objects.all())
class Meta:
model = Bar
exclude = ('foo',)
BarFormSet = inlineformset_factory(Foo, Bar, form=BarForm, can_delete=False)
Now, this works great in that I can render a single Foo and I get a number of inline forms for Bar. This renders is that the inline BarForm renders all the options of Baz as checkboxes.
What I would like is for each record of Baz to be rendered as a set of radio buttons representing the possible choices for value---along with a "N/A" choice---so that if A,B, or C is selected then the relationship to Baz is implied. But by default there doesn't seem to be a nice way of doing this with completely re-implementing RadioSelect or implementing a completely new widget, but I would like to follow the path of least resistance.
Hopefully I am making things clear.
My solution was to create SuperForm which allows subforms. The total solution is not quite generic for everyone but I did my best here to take what I was using and make it a bit more general.
Here is models.py:
from django.db import models
class Enemy(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Hero(models.Model):
name = models.CharField(max_length=200)
enemy = models.ManyToManyField(Enemy, through='Relationship', blank=False, null=False)
def __unicode__(self):
return self.name
class Relationship(models.Model):
hero = models.ForeignKey(Hero)
enemy = models.ForeignKey(Enemy)
ENEMY_TYPE_CHOICES = (
('G', 'Good'),
('B', 'Bad'),
('U', 'Ugly'),
)
enemy_type = models.CharField(max_length=1, choices=ENEMY_TYPE_CHOICES)
def __unicode__(self):
return u"{0} {1} {2}".format(self.hero, self.strength, self.relationship)
Here is what is in my forms.py:
from models import *
from django import forms
from django.forms.formsets import formset_factory, BaseFormSet
from django.forms.models import inlineformset_factory, BaseModelFormSet
from django.utils.safestring import mark_safe
class SuperForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.formsets = []
super(SuperForm, self).__init__(*args, **kwargs)
#property
def is_bound(self):
return self._is_bound or \
any([subformset.is_bound for subformset in self.formsets])
#is_bound.setter
def is_bound(self, value):
self._is_bound = value
def has_changed(self):
return bool(self.changed_data) or \
any([subformset.has_changed() for subformset in self.formsets])
def update_empty_permitted(self, subformset):
empty_permitted = not self.has_changed()
for subformset in self.formsets:
for form in subformset:
form.empty_permitted = empty_permitted
def is_valid(self):
subforms_valid = True
for subformset in self.formsets:
self.update_empty_permitted(subformset)
sfserrors = [err for err in subformset.errors if err]
if bool(sfserrors):
subforms_valid = False
return subforms_valid and super(SuperForm, self).is_valid()
class RelationshipForm(forms.ModelForm):
enemy = forms.ModelChoiceField(queryset=Enemy.objects.all(),
widget=forms.HiddenInput(),
required=True)
enemy_type = forms.ChoiceField(label="", widget=forms.RadioSelect,
choices=Relationship.ENEMY_TYPE_CHOICES,
required=True)
def name(self):
pk = self['enemy'].value()
return self.fields['enemy'].queryset.get(pk=pk)
class Meta:
model = Relationship
exclude = ('hero', 'enemy_type',)
RelationshipFormSet = formset_factory(RelationshipForm, extra=0, can_delete=False)
class HeroForm(SuperForm):
def __init__(self, *args, **kwargs):
super(HeroForm, self).__init__(*args, **kwargs)
initial=[dict(enemy=enemy.pk) for enemy in Enemy.objects.all()]
self.relationship_formset = RelationshipFormSet(initial=initial, prefix=self.prefix, data=kwargs.get('data'))
self.formsets.append(self.relationship_formset)
class Meta:
model = Hero
exclude = ('enemy',)
HeroFormSet = formset_factory(HeroForm, extra=1, can_delete=False)
Here is the views.py:
from django.views.generic import TemplateView
from django.views.generic.edit import FormMixin
from forms import *
class HeroView(FormMixin, TemplateView):
template_name = "formfun/hero.html"
form_class = HeroFormSet
def get_context_data(self, **kwargs):
context = super(HeroView, self).get_context_data(**kwargs)
context['formset'] = HeroFormSet()
return context
def form_valid(self, form):
return render_to_response({'form':form, 'valid': True})
def form_invalid(self, form):
return render_to_response({'form':form, 'valid': False})
And here is the template as I render it:
{% extends "base.html" %}
{% block title %}form fun{% endblock %}
{% block content %}
<form>
{% for form in formset %}
{{ form.as_p }}
{% for subform in form.relationship_formset %}
<label for="id_{{ subform.html_name }}_0" class="label50">{{ subform.name }}</label>
{{ subform.as_p }}
{% endfor %}
{% endfor %}
</form>
{% endblock content %}