Using the Django Grappelli admin tools, I can configure a ForeignKey (many-to-one) field to display as an autocomplete widget, rather than a drop down field, as follows:
class MyModel(models.Model):
related = models.ForeignKey(RelatedModel, related_name='my_models')
class MyModelAdmin(admin.ModelAdmin):
raw_id_fields = ('related',)
autocomplete_lookup_fields = {
'fk': ['related'],
}
However, what I'd like to do is define autocomplete widget lookups in the other (one-to-many) direction (i.e. in the admin for the RelatedModel so I can lookup one or more MyModel objects). Right now, I'm just using a ModelMultipleChoiceField:
class RelatedModelForm(forms.ModelForm):
class Meta:
model = RelatedModel
fields = ('my_models',)
my_models = forms.ModelMultipleChoiceField(queryset=MyModel.objects.all())
def __init__(self, *args, **kwargs):
super(SaleAdminForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['foods'].initial = self.instance.foods.all()
def save(self, *args, **kwargs):
instance = super(RelatedModelForm, self).save(commit=False)
self.fields['my_models'].initial.update(related=None)
self.cleaned_data['my_models'].update(related=instance)
return instance
class RelatedModelAdmin(admin.ModelAdmin):
model = RelatedModel
form = RelatedModelForm
However, there are just too many MyModel instances to work well with that type of widget. It would be ideal to just have one or more autocomplete lookup widgets for MyModel objects, in place of the ModelMultipleChoiceField.
Grappelli has an easy way to make autocomplete lookups for FK relations and for m2m relations, but is there a way for one-to-many relations? It seems like autocompletes for those would be just as useful as for the other two types of relations, so I would have guessed that Grappelli would provide an easy way there also, but I'm not finding it...
What I ended up doing was I added a ForeignKey field directly to the RelatedModel class so that I could use that to make Grappelli place the autocomplete on the page. That field isn't used to store anything, it's just for the autocomplete lookups. So, my classes look something like the following:
class MyModel(models.Model):
related = models.ForeignKey(RelatedModel, related_name='my_models')
class RelatedModel(models.Model):
# Used only for autocomplete lookups in the admin (Grappelli requires that a FK or m2m relationship exists in the model).
mockMyModel = models.ForeignKey(MyModel, verbose_name='Add a MyModel', related_name='for_mymodel_lookups')
# my_models - implied one-to-many field
class RelatedModelAdmin(admin.ModelAdmin):
model = RelatedModel
form = RelatedModelAdminForm
raw_id_fields = ('mockMyModel',)
autocomplete_lookup_fields = {
'fk': ['mockMyModel'],
}
class RelatedModelAdminForm(forms.ModelForm):
class Meta:
model = RelatedModel
fields = ('mockMyModel', 'my_models',)
# Note that you need to override __init__() and save() to get this field to work.
my_models = forms.ModelMultipleChoiceField(queryset=MyModel.objects.all(), required=False)
def __init__(self, *args, **kwargs):
super(RelatedModelAdminForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['my_models'].initial = self.instance.my_models.all()
def save(self, *args, **kwargs):
# TODO: Wrap reassignments into transaction
instance = super(RelatedModelAdminForm, self).save(commit=False)
self.fields['my_models'].initial.update(sale=None)
self.cleaned_data['my_models'].update(sale=instance)
return instance
The above renders an autocomplete widget on the page (for the mock FK relationship) and a multi-select widget (for the one-to-many relationship). Then I made a jquery plugin that hides the multi-select field from view and hijacks the autocomplete widget to copy each value the user selects there into the multi-select field, blanking out the autocomplete after each selection. It's a little bit of a hack... but it gets the job done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With