62

I have a Django Form class defined likes this in Models:

class AccountDetailsForm(forms.Form):
    ...
    adminuser = forms.ModelChoiceField(queryset=User.objects.all())

This works OK, but it has some limitations I can't seem to work around:

(1) I would like to use a filter on the queryset, based on a variable accountid passed to the form, like this:

User.objects.filter(account=accountid)

This can't work in the model because accountid can't be passed as a variable, of course.

It follows that the queryset must somehow be defined in the Views, but as far as I can see it's a required field in the Form class.

(2) I would like to make the default choice of AccountDetailsForm an object in the database, which I can select in the Views like this:

User.objects.filter(account=accountid).filter(primary_user=1)

I've tried specifying the adminuser as a default value in the form, (which works with other standard form fields, like CharField):

adminuser = User.objects.filter(account=accountid).filter(primary_user=1)

...

form = AccountDetailsForm({'adminuser': adminuser})
return render_to_response('accounts/edit/accountdetails.html', 
{'form': form, 'account':account})

But no luck.

Should I be using something other than ModelChoiceField given the flexibility I need here?

Thanks.

eli
  • 4,636
  • 7
  • 25
  • 29

4 Answers4

114

Override the init method and accept a new keyword argument

class AccountDetailsForm(forms.Form):
    ...
    adminuser = forms.ModelChoiceField(queryset=User.objects.all())
    def __init__(self, *args, **kwargs):
        accountid = kwargs.pop('accountid', None)
        super(AccountDetailsForm, self).__init__(*args, **kwargs)

        if accountid:
            self.fields['adminuser'].queryset = User.objects.filter(account=accountid)

form = AccountDetailsForm(accountid=3)

You can always just set the choices manually in the view as well.

form = AccountDetailsForm()
form.fields['adminuser'].queryset = User.objects.filter(account=accountid)

Be warned: you are not setting default values by passing in a dictionary to a form like in your example.

You are actually creating a Bound Form, potentially triggering validation and all that jazz.

To set defaults, use the initials argument.

form = AccountDetailsForm(initial={'adminuser':'3'})
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • Hi, thanks for your reply. Setting the queryset in the view worked well. The part I couldn't work out is setting the bound form value (rather than the default) of ModelChoiceField as _User.objects.filter(account=accountid).filter(primary_user=1)_. Thanks. – eli Mar 16 '11 at 18:52
6

You can override the field in the view

yourForm = AccountDetailsForm()

yourForm.fields['accomodation'] = forms.ModelChoiceField(User.objects.filter(account=accountid).filter(primary_user=1))
andrewsi
  • 10,807
  • 132
  • 35
  • 51
1

Something that hasn't been mentioned here yet is the Form.clean() method. This method is specifically for custom validation.

For your example, you could do something like this:

class AccountDetailsForm(forms.Form):
    adminuser = forms.ModelChoiceField(queryset=User.objects.all())
    account_id = forms.IntegerField()  # or ModelChoiceField if that applies

    def clean(self):
        account_id = self.cleaned_data['account_id']
        self.cleaned_data['adminuser'] = User.objects.filter(account_id=account_id)

    return self.cleaned_data

The clean() method gets called after the default clean methods, so you can use self.cleaned_data (same as form.cleaned_data in the view) and return it however you'd like.

Even better, you can name the method according to the field you'd like to clean (def clean_adminuser) and make easier to read.

def clean_adminuser(self):
    account_id = self.cleaned_data['account_id']
    return User.objects.filter(account_id=account_id)

Also in this method you can call Form.add_error() if there are any issues you want to handle.

0

In Django 2.0 you can pass object (User in your case) from the view to the form like this (you have to retrieve obj from the DB first):

form = AccountDetailsForm(initial={'adminuser': adminuser})

It will give you a default selected object (answers your 2) question)

techkuz
  • 3,608
  • 5
  • 34
  • 62
  • @JonProgrammer in the form? – techkuz Jul 25 '18 at 07:05
  • My current solution is that one here, but I feel it's not the most effective way: https://stackoverflow.com/questions/51512606/django-forms-queryset –  Jul 25 '18 at 07:21