Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto-generate form fields for a Form in django

I have some models and I want to generate a multi-selection form from this data. So the form would contain an entry for each category and the choices would be the skills in that category.

models.py

class SkillCategory(models.Model):
    name = models.CharField(max_length=50)

class Skill(models.Model):
    name = models.CharField(max_length=50)
    category = models.ForeignKey(SkillCategory)

Is there a way to auto-generate the form fields? I know I can manually add a 'SkillCategory' entry in the form for each SkillCategory, but the reason to have it as a model is so skills and skillcategories can be edited freely.

I want to do something like this: (I tried this, but didn't get it to work, don't remember the exact error...)

forms.py

class SkillSelectionForm(forms.Form):
    def __init__(*args, **kwargs):
        super(SkillSelectionForm, self).__init__(*args, **kwargs)
        for c in SkillCategory.objects.all():
            category_skills = [(pk, s.name) for s in c.skill_set.all()]
            setattr(self, c.name, forms.MultipleChoiceField(choices=category_skills, widget=forms.CheckboxSelectMultiple))

SOLUTION

This creates a form field entry using the SkillCategory.name and assigns choices as those in Skill. field_name/display_name are used to avoid issues with non-ascii category names.

forms.py

def get_categorized_skills():
    skills = {}
    for s in Skill.objects.values('pk', 'name', 'category__name').order_by('category__name'):
        if s['category__name'] not in skills.keys():
            skills[s['category__name']] = []
        skills[s['category__name']].append((s['pk'], s['name']))
    return skills

class SkillSelectionForm(forms.Form): 
    def __init__(self, *args, **kwargs):
        super(SkillSelectionForm, self).__init__(*args, **kwargs)
        skills = get_categorized_skills()
        for idx, cat in enumerate(skills.keys()):
            field_name = u'category-{0}'.format(idx)
            display_name = cat
            self.fields[field_name] = forms.MultipleChoiceField(choices=skills[cat], widget=forms.CheckboxSelectMultiple, label=display_name)
like image 392
monkut Avatar asked Sep 11 '09 05:09

monkut


1 Answers

Okay so you can't set fields like that on forms.Form, for reasons which will become apparent when you see DeclarativeFieldsMetaclass, the metaclass of forms.Form (but not of forms.BaseForm). A solution which may be overkill in your case but an example of how dynamic form construction can be done, is something like this:

base_fields = [
    forms.MultipleChoiceField(choices=[
        (pk, s.name) for s in c.skill_set.all()
    ]) for c in SkillCategory.objects.all()
]
SkillSelectionForm = type('SkillSelectionForm', (forms.BaseForm,), {'base_fields': base_fields})
like image 98
ozan Avatar answered Oct 13 '22 16:10

ozan