Django ModelChoiceField has no plus button
Question:
I’m making a Django app with custom users. I’ve outlined the key components of my problem below, missing code is denoted by ‘…’. My custom user model has a foreign key relationship as follows:
class MyCustomUser(models.AbstractBaseUser, models.PermissionsMixin)
...
location = models.ForeignKey(Location)
class Location(models.Model)
name = models.CharField(max_length=50, blank=True, null=True)
I’ve written a custom user form that includes this field as follows:
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(Location.objects.all())
This all appears to be working correctly, however, there is no plus button to the right of the select field for location. I want to be able to add a location when I create a user, in the same way that you can add polls when creating choices in the Django tutorial. According to this question, I might not see the green plus if I don’t have permission to change the model, but I am logged in as a superuser with all permissions. Any idea what I’m doing wrong?
Answers:
You need to set a RelatedFieldWidgetWrapper
wrapper in your model form:
The RelatedFieldWidgetWrapper (found in django.contrib.admin.widgets)
is used in the Admin pages to include the capability on a Foreign Key
control to add a new related record. (In English: puts the little green plus sign to the right of the control.)
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(queryset=Location.objects.all())
def __init__(self, *args, **kwargs):
super(MyCustomUserCreationForm, self).__init__(*args, **kwargs)
rel = ManyToOneRel(self.instance.location.model, 'id')
self.fields['location'].widget = RelatedFieldWidgetWrapper(self.fields['location'].widget, rel, self.admin_site)
I could make a mistake in the example code, so see these posts and examples:
Google pointed me to this page when searching how to get a “+” icon next to fields in a custom form with ForeignKey relationship, so I thought I’d add.
For me, using django-autocomplete-light
did the trick very well, using the “add another” functionality.
I have created method based on the answers above:
def add_related_field_wrapper(form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).rel
form.fields[col_name].widget =
RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel,
admin.site, can_add_related=True, can_change_related=True)
And then calling this method from my form:
class FeatureForm(forms.ModelForm):
offer = forms.ModelChoiceField(queryset=Offer.objects.all(), required=False)
package = forms.ModelChoiceField(queryset=Package.objects.all(), required=False)
def __init__(self, *args, **kwargs):
super(FeatureForm, self).__init__(*args, **kwargs)
add_related_field_wrapper(self, 'offer')
add_related_field_wrapper(self, 'package')
That works fine on Django 1.8.2.
You don’t even need to go that far, and besides, these answers are probably outdated as NONE of them worked for me in any capacity.
What I did to solve this is, as long as you have the ForeignKey field already in your model, then you can just create your custom ModelChoiceField:
class LocationModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%" % (obj.name)
The key next is NOT to create a custom field for the ModelChoiceField in your ModelForm (ie location = forms.ModelChoiceField(Location.objects.all()))
In other words, leave that out and in your ModelForm have something like this:
class UserAdminForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
Lastly, in your ModelAdmin:
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'location':
return LocationModelChoiceField(queryset=Location.objects.order_by('name')) # if you want to alphabetize your query
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Alternative Method : Using .remote_field instead of rel
def add_related_field_wrapper(self,form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).remote_field
form.fields[col_name].widget = RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel, admin.site, can_add_related=True, can_change_related=True)
def __init__(self, *args, **kwargs):
super(CustomerAdminForm, self).__init__(*args, **kwargs)
self.add_related_field_wrapper(self, 'offer')
self.add_related_field_wrapper(self, 'package')
Thankyou,
I’m making a Django app with custom users. I’ve outlined the key components of my problem below, missing code is denoted by ‘…’. My custom user model has a foreign key relationship as follows:
class MyCustomUser(models.AbstractBaseUser, models.PermissionsMixin)
...
location = models.ForeignKey(Location)
class Location(models.Model)
name = models.CharField(max_length=50, blank=True, null=True)
I’ve written a custom user form that includes this field as follows:
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(Location.objects.all())
This all appears to be working correctly, however, there is no plus button to the right of the select field for location. I want to be able to add a location when I create a user, in the same way that you can add polls when creating choices in the Django tutorial. According to this question, I might not see the green plus if I don’t have permission to change the model, but I am logged in as a superuser with all permissions. Any idea what I’m doing wrong?
You need to set a RelatedFieldWidgetWrapper
wrapper in your model form:
The RelatedFieldWidgetWrapper (found in django.contrib.admin.widgets)
is used in the Admin pages to include the capability on a Foreign Key
control to add a new related record. (In English: puts the little green plus sign to the right of the control.)
class MyCustomUserCreationForm(models.ModelForm)
...
location = forms.ModelChoiceField(queryset=Location.objects.all())
def __init__(self, *args, **kwargs):
super(MyCustomUserCreationForm, self).__init__(*args, **kwargs)
rel = ManyToOneRel(self.instance.location.model, 'id')
self.fields['location'].widget = RelatedFieldWidgetWrapper(self.fields['location'].widget, rel, self.admin_site)
I could make a mistake in the example code, so see these posts and examples:
Google pointed me to this page when searching how to get a “+” icon next to fields in a custom form with ForeignKey relationship, so I thought I’d add.
For me, using django-autocomplete-light
did the trick very well, using the “add another” functionality.
I have created method based on the answers above:
def add_related_field_wrapper(form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).rel
form.fields[col_name].widget =
RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel,
admin.site, can_add_related=True, can_change_related=True)
And then calling this method from my form:
class FeatureForm(forms.ModelForm):
offer = forms.ModelChoiceField(queryset=Offer.objects.all(), required=False)
package = forms.ModelChoiceField(queryset=Package.objects.all(), required=False)
def __init__(self, *args, **kwargs):
super(FeatureForm, self).__init__(*args, **kwargs)
add_related_field_wrapper(self, 'offer')
add_related_field_wrapper(self, 'package')
That works fine on Django 1.8.2.
You don’t even need to go that far, and besides, these answers are probably outdated as NONE of them worked for me in any capacity.
What I did to solve this is, as long as you have the ForeignKey field already in your model, then you can just create your custom ModelChoiceField:
class LocationModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%" % (obj.name)
The key next is NOT to create a custom field for the ModelChoiceField in your ModelForm (ie location = forms.ModelChoiceField(Location.objects.all()))
In other words, leave that out and in your ModelForm have something like this:
class UserAdminForm(forms.ModelForm):
class Meta:
model = User
fields = '__all__'
Lastly, in your ModelAdmin:
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'location':
return LocationModelChoiceField(queryset=Location.objects.order_by('name')) # if you want to alphabetize your query
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Alternative Method : Using .remote_field instead of rel
def add_related_field_wrapper(self,form, col_name):
rel_model = form.Meta.model
rel = rel_model._meta.get_field(col_name).remote_field
form.fields[col_name].widget = RelatedFieldWidgetWrapper(form.fields[col_name].widget, rel, admin.site, can_add_related=True, can_change_related=True)
def __init__(self, *args, **kwargs):
super(CustomerAdminForm, self).__init__(*args, **kwargs)
self.add_related_field_wrapper(self, 'offer')
self.add_related_field_wrapper(self, 'package')
Thankyou,