Creating a dynamic choice field

129,045

Solution 1

you can filter the waypoints by passing the user to the form init

class waypointForm(forms.Form):
    def __init__(self, user, *args, **kwargs):
        super(waypointForm, self).__init__(*args, **kwargs)
        self.fields['waypoints'] = forms.ChoiceField(
            choices=[(o.id, str(o)) for o in Waypoint.objects.filter(user=user)]
        )

from your view while initiating the form pass the user

form = waypointForm(user)

in case of model form

class waypointForm(forms.ModelForm):
    def __init__(self, user, *args, **kwargs):
        super(waypointForm, self).__init__(*args, **kwargs)
        self.fields['waypoints'] = forms.ModelChoiceField(
            queryset=Waypoint.objects.filter(user=user)
        )

    class Meta:
        model = Waypoint

Solution 2

There's built-in solution for your problem: ModelChoiceField.

Generally, it's always worth trying to use ModelForm when you need to create/change database objects. Works in 95% of the cases and it's much cleaner than creating your own implementation.

Solution 3

the problem is when you do

def __init__(self, user, *args, **kwargs):
    super(waypointForm, self).__init__(*args, **kwargs)
    self.fields['waypoints'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in Waypoint.objects.filter(user=user)])

in a update request, the previous value will lost!

Solution 4

You can declare the field as a first-class attribute of your form and just set choices dynamically in __init__:

class WaypointForm(forms.Form):
    waypoints = forms.ChoiceField(choices=[])

    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        waypoint_choices = [(o.id, str(o)) for o in Waypoint.objects.filter(user=user)]
        self.fields['waypoints'].choices = waypoint_choices

This approach also works with a ModelChoiceField.

This approach is superior if you are using a ModelForm, and want to override choices of an autogenerated field.

Solution 5

How about passing the rider instance to the form while initializing it?

class WaypointForm(forms.Form):
    def __init__(self, rider, *args, **kwargs):
      super(joinTripForm, self).__init__(*args, **kwargs)
      qs = rider.Waypoint_set.all()
      self.fields['waypoints'] = forms.ChoiceField(choices=[(o.id, str(o)) for o in qs])

# In view:
rider = request.user
form = WaypointForm(rider) 
Share:
129,045
whatWhat
Author by

whatWhat

Updated on April 25, 2020

Comments

  • whatWhat
    whatWhat about 4 years

    I'm having some trouble trying to understand how to create a dynamic choice field in django. I have a model set up something like:

    class rider(models.Model):
         user = models.ForeignKey(User)
         waypoint = models.ManyToManyField(Waypoint)
    
    class Waypoint(models.Model):
         lat = models.FloatField()
         lng = models.FloatField()
    

    What I'm trying to do is create a choice Field whos values are the waypoints associated with that rider (which would be the person logged in).

    Currently I'm overriding init in my forms like so:

    class waypointForm(forms.Form):
         def __init__(self, *args, **kwargs):
              super(joinTripForm, self).__init__(*args, **kwargs)
              self.fields['waypoints'] = forms.ChoiceField(choices=[ (o.id, str(o)) for o in Waypoint.objects.all()])
    

    But all that does is list all the waypoints, they're not associated with any particular rider. Any ideas? Thanks.

  • Daniel Roseman
    Daniel Roseman almost 14 years
    Use ModelChoiceField whether or not it's a ModelForm - it works on normal forms too.
  • Breedly
    Breedly over 10 years
    What do you do when you want to get the request data out though? waypointForm(request.POST) won't validate in the first one, because the data to validate against isn't there anymore.
  • wasabigeek
    wasabigeek over 8 years
    @Ashok How could the CheckboxSelectMultiple widget be used in this instance? For the modelform especially.
  • coredumperror
    coredumperror almost 8 years
    If you want to use the CheckboxSelectMultiple widget with this, you'll want to use MultipleChoiceField or ModelMultipleChoiceField. At first, it seems to work with ChoiceField, but the internals will break when you try to save the form.
  • sofly
    sofly almost 8 years
    Thank you @CoreDumpError - just fought this for nearly 2 hours before reading your comment (doh!) and this finally made everything work. Others, beware of breaking your form submission (unicode issues) if you plan on using the CheckboxSelectMultiple widget with this code. Be sure to change ChoiceForm to MultipleChoiceForm
  • MohitC
    MohitC over 7 years
    user should be passed as kwargs so that request is preserved as first positional arg.
  • Raptor
    Raptor about 6 years
    Exactly what I was looking for! Thanks!
  • Aravind R Pillai
    Aravind R Pillai over 5 years
    this made my life easier.. Thanks a lot.. :)
  • ineedme
    ineedme about 5 years
    try using values_list("id","str") instead of array compression
  • jpmorris
    jpmorris about 4 years
    this is NOT the answer. This is the BROKEN answer all over the internet. 'user' should not be in the init signature. As mentioned above when you do this if you do any binding or validation to actually use the form (as recommended in the docs) then you cant bind any data to the form because you just changed the request.POST position to 'user' now. This answer needs to stop being upvoted.
  • Alan Evangelista
    Alan Evangelista about 3 years
    @jpmorris If this answer is broken, could you write a better one?
  • jpmorris
    jpmorris about 3 years
    @AlanEvangelista sorry I barely worked with django at the time and I don't know the answer. Actually I'm not sure I even knew the answer at the time. I only knew that this wasnt the one.