Créer un champ de choix dynamique

136

J'ai du mal à comprendre comment créer un champ de choix dynamique dans django. J'ai un modèle mis en place quelque chose comme:

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

class Waypoint(models.Model):
     lat = models.FloatField()
     lng = models.FloatField()

Ce que j'essaie de faire est de créer un champ de choix dont les valeurs sont les points de cheminement associés à ce cavalier (qui serait la personne connectée).

Actuellement, je remplace init dans mes formulaires comme ceci:

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()])

Mais tout ce que cela fait est de lister tous les waypoints, ils ne sont associés à aucun pilote en particulier. Des idées? Merci.

quoi quoi
la source

Réponses:

191

vous pouvez filtrer les waypoints en passant l'utilisateur au formulaire 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)]
        )

de votre vue lors du lancement du formulaire passer l'utilisateur

form = waypointForm(user)

en cas de formulaire modèle

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
Ashok
la source
20
Utilisez ModelChoiceField, qu'il s'agisse ou non d'un ModelForm - il fonctionne également sur les formulaires normaux.
Daniel Roseman
9
Que faites-vous lorsque vous souhaitez récupérer les données de la demande? waypointForm (request.POST) ne validera pas dans le premier, car les données à valider ne sont plus là.
Breedly
1
@Ashok Comment le widget CheckboxSelectMultiple pourrait-il être utilisé dans cette instance? Pour le modelform en particulier.
wasabigeek
2
Si vous souhaitez utiliser le CheckboxSelectMultiplewidget avec cela, vous voudrez utiliser MultipleChoiceFieldou ModelMultipleChoiceField. Au début, cela semble fonctionner avec ChoiceField, mais les composants internes se cassent lorsque vous essayez d'enregistrer le formulaire.
coredumperror
1
Merci @CoreDumpError - je viens de me battre pendant près de 2 heures avant de lire votre commentaire (doh!) Et cela a finalement tout fonctionné. D'autres, méfiez-vous de casser votre soumission de formulaire (problèmes Unicode) si vous prévoyez d'utiliser le CheckboxSelectMultiplewidget avec ce code. Assurez-vous de passer ChoiceFormauMultipleChoiceForm
doucement
11

Il existe une solution intégrée à votre problème: ModelChoiceField .

En général, il vaut toujours la peine d'essayer d'utiliser ModelFormlorsque vous devez créer / modifier des objets de base de données. Fonctionne dans 95% des cas et c'est beaucoup plus propre que de créer votre propre implémentation.

Alexandre Lebedev
la source
8

le problème c'est quand tu fais

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)])

dans une demande de mise à jour, la valeur précédente sera perdue!

Liang
la source
3

Que diriez-vous de passer l'instance de rider au formulaire lors de son initialisation?

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
la source
2

Sous la solution de travail avec champ de choix normal. mon problème était que chaque utilisateur avait ses propres options de champ de choix PERSONNALISÉES basées sur quelques 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
la source
Exactement ce que je cherchais! Merci!
Raptor
cela a rendu ma vie plus facile .. Merci beaucoup .. :)
Aravind R Pillai
1

Comme l'ont souligné Breedly et Liang, la solution d'Ashok vous empêchera d'obtenir la valeur de sélection lors de la publication du formulaire.

Une façon légèrement différente, mais toujours imparfaite, de résoudre ce problème serait:

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)

Cela pourrait cependant causer des problèmes de compatibilité.

Haroldo_OK
la source
1

Vous pouvez déclarer le champ comme un attribut de première classe de votre formulaire et simplement définir des choix de manière dynamique:

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

Vous pouvez également utiliser un ModelChoiceField et définir le jeu de requêtes sur init de la même manière.

Zags
la source
0

Si vous avez besoin d'un champ de choix dynamique dans django admin; Cela fonctionne pour 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

J'espère que cela t'aides.

Tobias Ernst
la source