django forms and where to add dynamic choices
In django, you can use the so-called ModelForms to generate a form directly from a model definition. This is pretty useful, as you can have a models.py file like, for example:
class Project(models.Model):
name = models.CharField('Name of the project', max_length=100)
code = models.CharField('Code number of the project', max_length=10)
creator = models.ForeignKey(User, related_name='created_projects')
maintainer = models.ManyToManyField(User, related_name='maintained_projects')
In this case we are creating a Project model, that is, a model to define information we will handle for those projects (this is an example, of course it would have more fields). The first two fields are CharFields (text fields where we will save the name and a numeric code for the project). The next two fields are ForeignKey and ManyToManyField fields (links to another model, this time User, which is django.contrib.auth.models.User, that is, the default django user model).
I've added two different relations to the User model, one will be the creator of the project, the other one the maintainer (or maintainers) of the project.
Now, back to ModelForms, I can create an HTML form just adding something like that to the app forms.py:
from myproject.myapp.models import Project
class ProjectForm(forms.ModelForm):
class Meta:
model = Proyecto
Once you've defined the form, you can get an html form just doing:
from myproject.myapp.forms import ProjectForm
myform = ProjectForm()
and you can use the form as described in the forms documentation (I'll not step there in detail).
But there is one problem with that approach. Imagine I've created two different groups (using django.contrib.auth.models.Group), let's call them project creators and project maintainers. Only users in the project creators group should be available in the creator field and users in the project maintainers group should be available to select for the the maintainer field.
Well, that's easy, we only have to modify a bit our forms.py:
from myproject.myapp.models import Project
from django.contrib.auth.models import Group
class ProjectForm(forms.ModelForm):
creator_choices = [(c.id, c.username) for c in Group.objects.get(name__icontains='creator').user_set.all()]
maintainer_choices = [(m.id, m.username) for m in Group.objects.get(name__icontains='maintainer').user_set.all()]
creator = forms.ChoiceField(required=True, label='Project creator', choices=creator_choices)
maintainer = forms.MultipleChoiceField(required=True, label='Project maintainer(s)', choices=maintainer_choices)
class Meta:
model = Proyecto
Ok, what we've got here is that I've overwrote or replaced the default creator and maintainer fields automatically generated by django, setting two new fields. This allow me to set certain default options for the fields, like the list of choices available (which I've generated searching for users in the needed groups).
Now a user will be able to select only users in the project creators group in the creator field and users in the project maintainers group in the maintainer field.
BUT, there is a problem with that. Doing it that way, the list of choices for each field will be generated on startup time (when you launch the django development server or apache or lighttpd or nginx or whatever you are using). That means that if you add a new user to the maintainers group, it will not appear in the maintainer field until you restart your server!
To avoid that, we will need to add the current available choices before using the form, overwriting the default list of choices:
from myproject.myapp.forms import ProjectForm
from django.contrib.auth.models import Group
creator_choices = [(c.id, c.username) for c in Group.objects.get(name__icontains='creator').user_set.all()]
maintainer_choices = [(m.id, m.username) for m in Group.objects.get(name__icontains='maintainer').user_set.all()]
creator = forms.ChoiceField(required=True, label='Project creator', choices=creator_choices)
maintainer = forms.MultipleChoiceField(required=True, label='Project maintainer(s)', choices=maintainer_choices)
myform = ProjectForm()
myform.fields['creator'].choices = creator_choices
myform.fields['maintainer'].choices = maintainer_choices
And that's it! This way the auto-generated html form will show up-to-date information in the select combos.