Django ModelChoiceField: filtering query set and setting default value as an object

DjangoDjango Forms

Django Problem Overview


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.

Django Solutions


Solution 1 - Django

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'})

Solution 2 - Django

You can override the field in the view

yourForm = AccountDetailsForm()

yourForm.fields['accomodation'] = forms.ModelChoiceField(User.objects.filter(account=accountid).filter(primary_user=1))

Solution 3 - Django

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.

Solution 4 - Django

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioneliView Question on Stackoverflow
Solution 1 - DjangoYuji 'Tomita' TomitaView Answer on Stackoverflow
Solution 2 - DjangoWillians VivancoView Answer on Stackoverflow
Solution 3 - DjangozackcpetersenView Answer on Stackoverflow
Solution 4 - DjangotechkuzView Answer on Stackoverflow