153

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.

Serjik
  • 10,543
  • 8
  • 61
  • 70
whatWhat
  • 3,987
  • 7
  • 37
  • 44

8 Answers8

203

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
Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
Ashok
  • 10,373
  • 3
  • 36
  • 23
  • 21
    Use ModelChoiceField whether or not it's a ModelForm - it works on normal forms too. – Daniel Roseman Aug 06 '10 at 08:02
  • 9
    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. – Breedly Dec 20 '13 at 18:48
  • 1
    @Ashok How could the CheckboxSelectMultiple widget be used in this instance? For the modelform especially. – wasabigeek Aug 14 '15 at 18:22
  • 3
    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. – coredumperror May 18 '16 at 00:42
  • 1
    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` – sofly Aug 04 '16 at 19:25
  • 1
    `user` should be passed as kwargs so that `request` is preserved as first positional arg. – MohitC Sep 14 '16 at 05:20
  • try using values_list("id","__str__") instead of array compression – ineedme Feb 20 '19 at 04:05
  • 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. – jpmorris Apr 11 '20 at 10:30
  • @jpmorris If this answer is broken, could you write a better one? – Alan Evangelista Apr 06 '21 at 23:31
  • @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. – jpmorris Apr 07 '21 at 16:07
13

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.

Sardathrion - against SE abuse
  • 17,269
  • 27
  • 101
  • 156
Alexander Lebedev
  • 5,968
  • 1
  • 20
  • 30
8

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!

Pi Delport
  • 10,356
  • 3
  • 36
  • 50
Liang
  • 81
  • 1
  • 1
4

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) 
Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
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.

Zags
  • 37,389
  • 14
  • 105
  • 140
3

If you need a dynamic choice field in django admin; This works for django >=2.1.

class CarAdminForm(forms.ModelForm):
    class Meta:
        model = Car

    def __init__(self, *args, **kwargs):
        super(CarForm, self).__init__(*args, **kwargs)

        # Now you can make it dynamic.
        choices = (
            ('audi', 'Audi'),
            ('tesla', 'Tesla')
        )

        self.fields.get('car_field').choices = choices

    car_field = forms.ChoiceField(choices=[])

@admin.register(Car)
class CarAdmin(admin.ModelAdmin):
    form = CarAdminForm

Hope this helps.

Tobias Ernst
  • 4,214
  • 1
  • 32
  • 30
2

Underneath working solution with normal choice field. my problem was that each user have their own CUSTOM choicefield options based on few conditions.

class SupportForm(BaseForm):

    affiliated = ChoiceField(required=False, label='Fieldname', choices=[], widget=Select(attrs={'onchange': 'sysAdminCheck();'}))

    def __init__(self, *args, **kwargs):

        self.request = kwargs.pop('request', None)
        grid_id = get_user_from_request(self.request)
        for l in get_all_choices().filter(user=user_id):
            admin = 'y' if l in self.core else 'n'
            choice = (('%s_%s' % (l.name, admin)), ('%s' % l.name))
            self.affiliated_choices.append(choice)
        super(SupportForm, self).__init__(*args, **kwargs)
        self.fields['affiliated'].choices = self.affiliated_choice
Deil
  • 106
  • 5
1

As pointed by Breedly and Liang, Ashok's solution will prevent you from getting the select value when posting the form.

One slightly different, but still imperfect, way to solve that would be:

class waypointForm(forms.Form):
    def __init__(self, user, *args, **kwargs):
        self.base_fields['waypoints'].choices = self._do_the_choicy_thing()
        super(waypointForm, self).__init__(*args, **kwargs)

This could cause some concurrence problems, though.

Reinout van Rees
  • 13,486
  • 2
  • 36
  • 68
Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80