Question

I've seen a lot of answers about how this is solved when not using Class Based Views. Is there some really obvious thing I'm missing about doing it with CBVs?

Basically I want to have a MultipleChoiceField in my form which has choices determined by what is happening in the view. e.g. I use the PK from the URL to do some backend requests and then those should be used to populate the choices.

# forms.py
from django.forms import Form, MultipleChoiceField, CharField

class EmailForm(Form):

    users = MultipleChoiceField(required=False)
    subject = CharField(max_length=100)
    message = CharField()

    def __init__(self, users=None, *args, **kwargs):
        super(EmailForm, self).__init__(*args, **kwargs)
        if users:
            self.fields['users'].choices = users

#urls.py
from django.conf.urls import url, patterns
from .views import EmailView

# url patterns
urlpatterns = patterns('',
    url( r'^(?P<pk>\d+)$', EmailView.as_view(), name="maindex" ),
)

#views.py
from django.views.generic import FormView, TemplateView
from .forms import EmailForm

class EmailView(FormView):
    template_name = 'myapp/email.html'
    form_class = EmailForm
    success_ulr = '/thanks/'

    def form_valid(self, form):
        # Do stuff here
        return super(EmailView, self).form_valid(form)

Basically it boils down to how/where to call the init function from the view. How do I do that? Or is there another way I've missed? I thought of overriding get_form_kwargs in the view, but couldn't make that do anything.

Thanks

Était-ce utile?

La solution

The view:

from django.views.generic import FormView

class EmailView(FormView):
    # ...

    def get_form_kwargs(self):
        kwargs = super(EmailView, self).get_form_kwargs()

        # get users, note: you can access request using: self.request

        kwargs['users'] = users
        return kwargs

The form:

from django import forms import Form

class EmailForm(Form):

    users = MultipleChoiceField(required=False)
    # ...

    def __init__(self, *args, **kwargs):
        self.users = kwargs.pop('users', None)
        super(EmailForm, self).__init__(*args, **kwargs) 
        self.fields['users'].choices = self.users

Autres conseils

Basically, what I've done in a similar case is the following (Python 3.5, Django 1.8):

def get_form(self, *args, **kwargs):
    form= super().get_form(*args, **kwargs)
    form.fields['rank'].choices= <sequence of 2-tuples>
    return form

where obviously rank is the field name. This way I use the default form.

Alright, the FormMixin calls get_form to get the form-class which looks like

def get_form(self, form_class):
    """
    Returns an instance of the form to be used in this view.
    """
    return form_class(**self.get_form_kwargs())

So you can either override get_form to instance your Form yourself

def get_form(self, form_class):
    return EmailForm(files=self.request.FILES or None,
                     data=self.request.POST or None,
                     users=some_user_queryset)

or stay a bit more generic and override get_form_kwargs to something like

def get_form_kwargs(self):
    form_kws = super(EmailView, self).get_form_kwargs()
    form_kws["users"] = some_user_queryset
    return form_kws

One way to do it is:

class EmailView(FormView):
    # ...

    def get(self, request, *args, **kwargs):
        self.users = ...
        return super(EmailView, self).get(request, *args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super(EmailView, self).get_form_kwargs()
        kwargs['users'] = self.users
        return kwargs

This allows you to set the user choices in the view and to pass them to the form.

You can override get_form.

I needed to update the choices for ChoiceField based on logged in user.

Form:

class InteractionCreateForm(forms.Form):
    device = forms.ChoiceField(choices=[(None, '----------')])
    ...

View:

class InteractionCreateView(FormView):
    form_class = InteractionCreateForm
    ...

    def get_form(self, form_class=None):
        form_class = super().get_form(form_class=None)

        form_class.fields['device'].choices = \
            form_class.fields['device'].choices \
            + [(device.pk, device) for device in Device.objects.filter(owner=self.request.user.id)]

        return form_class
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top