In previous versions of autocomplete light, there was a very quick way to add new forms.
form = modelform_factory(ModelName, fields='__all__')
Provided the model had an autocomplete view registered, this would automatically build a new form based on the model given. Very quick and easy. In version 3.1.6, the latest version that's out today, this feature appears to have been removed. I'm having to go back and rework everything to get us upgraded, and I'm wondering if there is anything like the old modelform_factory
available in the new version that I have just missed. Or is there a quick way of setting up a generic autocomplete view / form that can be easily reused? Any thoughts are appreciated.
So after lots of hunting, I was unable to locate anything resembling the previous modelform_factory of autocomplete light, so I decided to make my own. The following is an example taken directly from what we have in our production CMS.
from dal import autocomplete
from mymodels import ThisModel, ThatModel, AnotherModel
def autocomplete_form_factory(ac_model, *args, **kwargs):
field_url_dict = {}
m2m = ('mypeeps', 'yourpeeps',)
if ac_model in (ThisModel, ThatModel):
# Connects the "stuff_field" of these
# models to the url named "stuff-autocomplete"
field_url_dict = {
'stuff_field': 'stuff'
}
elif ac_model == AnotherModel:
# Connects AnotherModel's ForeignKey field "headhoncho"
# and its ManyToManyFields "mypeeps" and "yourpeeps"
# to the "peeps-autocomplete" url
field_url_dict = {
'headhoncho': 'peeps',
'mypeeps': 'peeps',
'yourpeeps': 'peeps',
}
# Assign the appropriate widgets based on this model's autocomplete dictionary
ac_widgets = {}
ac_fields = kwargs.get('fields', ('__all__'))
for field, url in field_url_dict.iteritems():
is_m2m = field in m2m
text = 'Type to return a list of %s...' if is_m2m else 'Type to return a %s list...'
kwargs = {
'url': '%s-autocomplete' % url,
'attrs': {
'data-placeholder': text % ac_model._meta.get_field(field).verbose_name,
'data-minimum-input-length': 3,
}
}
ac_widgets[field] = autocomplete.ModelSelect2Multiple(**kwargs) if is_m2m else autocomplete.ModelSelect2(**kwargs)
# Create the form
class DynamicAutocompleteForm(forms.ModelForm):
class Meta:
model = ac_model
fields = ac_fields
widgets = ac_widgets
return DynamicAutocompleteForm
For the corresponding views, you might do something along the lines of the following:
class BaseAutocomplete(autocomplete.Select2QuerySetView):
model = None
fields = ['title']
filters = {}
def get_queryset(self):
if not self.request.user.is_authenticated() or not self.q or len(self.q) < 3:
return self.model.objects.none()
# OR all of our field searches together
obj = Q()
for field in self.fields:
kwargs = {}
kwargs['%s__icontains' % field] = self.q
obj = obj | Q(**kwargs)
return self.model.objects.filter(obj).filter(**self.filters)
class StuffAutocomplete(BaseAutocomplete):
model = Stuff
filters = {'is_awesome': True}
class PeepsAutocomplete(BaseAutocomplete):
model = Peeps
fields = ['name', 'notes']
And for urls, you might use the following:
url(r'^stuff-autocomplete/$', StuffAutocomplete.as_view(), name='stuff-autocomplete'),
url(r'^peeps-autocomplete/$', PeepsAutocomplete.as_view(), name='peeps-autocomplete',),
To make use of the form factory in your code, you would use something like the following, assigning the results to a model admin's form or using it as a base for a more complex form:
autocomplete_form_factory(ThisModel)
autocomplete_form_factory(AnotherModel, fields=["headhoncho", "mypeeps"])
This mimics the funcitonality we had with autocomplete's earlier modelform_factory, making it very simple to create autocomplete forms and add them to the admin as needed after getting everything set up. Hoping this will help save someone some time.