Criando um campo de escolha dinâmica

136

Estou com problemas para tentar entender como criar um campo de escolha dinâmica no django. Eu tenho um modelo configurado algo como:

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

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

O que estou tentando fazer é criar um campo de escolha cujos valores sejam os waypoints associados a esse piloto (que seria a pessoa logada).

Atualmente estou substituindo init em meus formulários da seguinte forma:

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

Mas tudo o que faz é listar todos os waypoints, eles não estão associados a nenhum piloto em particular. Alguma ideia? Obrigado.

O que, o que
fonte

Respostas:

191

você pode filtrar os waypoints passando o usuário para o formulário 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)]
        )

da sua visualização ao iniciar o formulário, passe o usuário

form = waypointForm(user)

no caso do modelo

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
fonte
20
Use ModelChoiceField, seja um ModelForm ou não - ele também funciona em formulários normais.
9309 Daniel Roseman
9
O que você faz quando deseja obter os dados da solicitação? O waypointForm (request.POST) não será validado no primeiro, porque os dados a serem validados não estão mais lá.
Breedly
1
@Ashok Como o widget CheckboxSelectMultiple pode ser usado nesta instância? Especialmente para o modelform.
wasabigeek
2
Se você quiser usar o CheckboxSelectMultiplewidget com isso, use MultipleChoiceFieldou ModelMultipleChoiceField. No começo, parece funcionar ChoiceField, mas os internos serão interrompidos quando você tentar salvar o formulário.
Coredumperror
1
Obrigado @CoreDumpError - apenas lutei por quase duas horas antes de ler seu comentário (doh!) E isso finalmente fez tudo funcionar. Outros, lembre-se de interromper o envio do formulário (problemas unicode) se você planeja usar o CheckboxSelectMultiplewidget com este código. Certifique-se de alterar ChoiceFormparaMultipleChoiceForm
sofly
11

Existe uma solução interna para o seu problema: ModelChoiceField .

Geralmente, sempre vale a pena tentar usar ModelFormquando você precisa criar / alterar objetos de banco de dados. Funciona em 95% dos casos e é muito mais limpo do que criar sua própria implementação.

Alexander Lebedev
fonte
8

o problema é quando você faz

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

em uma solicitação de atualização, o valor anterior será perdido!

Liang
fonte
3

Que tal passar a instância rider para o formulário enquanto a inicializa?

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
fonte
2

Debaixo da solução de trabalho com campo de escolha normal. meu problema era que cada usuário tinha suas próprias opções de campo de escolha PERSONALIZADAS com base em poucas condições.

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
fonte
Exatamente o que eu estava procurando! Obrigado!
Raptor
isso fez minha vida mais fácil .. Thanks a lot .. :)
Aravind R Pillai
1

Conforme apontado por Breedly e Liang, a solução da Ashok impedirá que você obtenha o valor selecionado ao postar o formulário.

Uma maneira um pouco diferente, mas ainda imperfeita, de resolver isso seria:

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)

Isso pode causar alguns problemas de simultaneidade.

Haroldo_OK
fonte
1

Você pode declarar o campo como um atributo de primeira classe do seu formulário e apenas definir opções dinamicamente:

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

Você também pode usar um ModelChoiceField e definir o conjunto de consultas no init de maneira semelhante.

Zags
fonte
0

Se você precisar de um campo de escolha dinâmica no django admin; Isso funciona para 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

Espero que isto ajude.

Tobias Ernst
fonte