Can’t see admin add-another + button when overriding a ModelChoiceField

It’s common for users to report that the +/add-another button disappears when using a ModelForm with an overriden ModelChoiceField. This is actually a Django issue.

As a workaround, 2.0.6 allows using Autocomplete.add_another_url_name in the admin, ie.:

autocomplete_light.register(YourModel, add_another_url_name='admin:yourapp_yourmodel_add')

While using Add another popup outside the admin has been supported for years, support for using it as a workaround in Django admin is currently experimental. You can obviously imagine the problem if such an Autocomplete is used for a user which has no access to the admin: the plus button will fail.

Your input is very welcome on this matter.

RemovedInDjango18Warning: Creating a ModelForm without either the ‘fields’ attribute or the ‘exclude’ attribute is deprecated - form YourForm needs updating

It’s very common for users who are not actively following Django 1.7 development and Django security matters (even though they should!) to report the following warning as a problem with django-autocomplete-light:

autocomplete_light/forms.py:266: RemovedInDjango18Warning: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is deprecated - form YourForm needs updating

This is a new security feature from Django, and has nothing to do with django-autocomplete-light. As the message clearly states, it is deprecated to create a ModelForm without either the ‘fields’ attribute or the ‘exclude’ attribute.

Solution: pass fields = '__all__'.

See Django documentation on “Selecting the fields to use” for details.

How to run tests

You should not try to test autocomplete_light from your own project because tests depend on example apps to be present in INSTALLED_APPS. You may use the provided test_project which is prepared to run all testst.

Install a version from git, ie:

pip install -e git+https://github.com/yourlabs/django-autocomplete-light.git#egg=autocomplete_light

From there you have two choices:

  • either go in env/src/autocomplete_light/test_project and run ./manage.py test autocomplete_light,
  • either go in env/src/autocomplete_light/ and run tox after installing it from pip.

If you’re trying to run a buildbot then you can use test.sh and use that buildbot configuration to enable CI on the 28 supported configurations:

def make_build(python, django, genericm2m, taggit):
    name = 'py%s-dj%s' % (python, django)

    if genericm2m != '0':
        name += '-genericm2m'
    if taggit != '0':
        name += '-taggit'

    slavenames = ['example-slave']
    if python == '2.7':
        slavenames.append('gina')

    factory = BuildFactory()
    # check out the source
    factory.addStep(Git(repourl='https://github.com/yourlabs/django-autocomplete-light.git', mode='incremental'))
    # run the tests (note that this will require that 'trial' is installed)
    factory.addStep(ShellCommand(command=["./test.sh"], timeout=3600))

    c['builders'].append(
        BuilderConfig(name=name,
            slavenames=slavenames,
            factory=factory,
            env={
                'DJANGO_VERSION': django,
                'PYTHON_VERSION': python,
                'DJANGO_GENERIC_M2M': genericm2m,
                'DJANGO_TAGGIT': taggit,
            }
        )
    )

    c['schedulers'].append(SingleBranchScheduler(
                        name="all-%s" % name,
                        change_filter=filter.ChangeFilter(branch='v2'),
                        treeStableTimer=None,
                        builderNames=[name]))
    c['schedulers'].append(ForceScheduler(
                        name="force-%s" % name,
                        builderNames=[name]))


c['builders'] = []
djangos = ['1.4', '1.5', '1.6']
pythons = ['2.7', '3.3']

for python in pythons:
    for django in djangos:
        if python == '3.3' and django == '1.4':
            continue

        for genericm2m in ['0','1']:
            for taggit in ['0','1']:
                make_build(python, django, genericm2m, taggit)

Why not use Widget.Media ?

In the early versions (0.1) of django-autocomplete-light, we had widgets defining the Media class like this:

class AutocompleteWidget(forms.SelectMultiple):
    class Media:
        js = ('autocomplete_light/autocomplete.js',)

This caused a problem if you wanted to load jQuery and autocomplete.js globally anyway and anywhere in the admin to have a global navigation autocomplete: it would load the scripts twice.

Also, this didn’t work well with django-compressor and other cool ways of deploying the JS.

So, in the next version, I added a dependency management system. Which sucked and was removed right away to finally keep it simple and stupid as we have it today.

Model field’s help_text and verbose_name are lost when overriding the widget

This has nothing to do with django-autocomplete-light, but still it’s a FAQ so here goes.

When Django’s ModelForm creates a form field for a model field, it copies models.Field.verbose_name to forms.Field.label and models.Field.help_text to forms.Field.help_text, as uses models.Field.blank to create forms.Field.required.

For example:

class Person(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        verbose_name='Person name',
        help_text='Please fill in the complete person name'
    )

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person

Thanks to Django’s DRY system, this is equivalent to:

class PersonForm(forms.ModelForm):
    name = forms.CharField(
        max_length=100,
        required=False,
        label='Person name',
        help_text='Please fill in the complete person name'
    )

    class Meta:
        model = Person

But you will loose that logic as soon as you decide to override Django’s generated form field with your own. So if you do this:

class PersonForm(forms.ModelForm):
    name = forms.CharField(widget=YourWidget)

    class Meta:
        model = Person

Then you loose Django’s DRY system, because you instanciate the name form field, so Django leaves it as is.

If you want to override the widget of a form field and you don’t want to override the form field, then you should refer to Django’s documentation on overriding the default fields which means you should use Meta.widgets, ie.:

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        widgets = {'name': YourWidget}

Again, this has nothing to do with django-autocomplete-light.

Fields bound on values which are not in the queryset anymore raise a ValidationError

This is not specific to django-autocomplete-light, but still it’s a FAQ so here goes.

Django specifies in its unit tests that a ModelChoiceField and ModelMultipleChoiceField should raise a ValidationError if a value is not part of the queryset passed to the field constructor.

This is the relevant part of Django’s specification:

# Delete a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
Category.objects.get(url='5th').delete()
with self.assertRaises(ValidationError):
    f.clean(c5.id)

# [...]

# Delete a Category object *after* the ModelMultipleChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
Category.objects.get(url='6th').delete()
with self.assertRaises(ValidationError):
    f.clean([c6.id])

django-autocomplete-light behaves exactly the same way. If an item is removed from the queryset, then its value will be dropped from the field values on display of the form. Trying to save that value again will raise a ValidationError will be raised, just like if the item wasn’t there at all.

But don’t take my word for it, try the security_test app of the test_project, it provides:

  • an admin to control which items are in and out of the queryset,
  • an update view with a django select
  • another update view with an autocomplete instead

How to override a JS method ?

Refer to Override autocomplete JS methods.

How to work around Django bug #9321: Hold down “Control” … ?

Just use the autocomplete_light.ModelForm or inherit from both SelectMultipleHelpTextRemovalMixin and django.forms.ModelForm.

How to report a bug effectively ?

Read How to Report Bugs Effectively and open an issue on django-autocomplete-light’s issue tracker on GitHub.

How to ask for help ?

The best way to ask for help is:

  • fork the repo,
  • add a simple way to reproduce your problem in a new app of test_project, try to keep it minimal,
  • open an issue on github and mention your fork.

Really, it takes quite some time for me to clean pasted code and put up an example app it would be really cool if you could help me with that !

If you don’t want to do the fork and the reproduce case, then you should better ask on StackOverflow and you might be lucky (just tag your question with django-autocomplete-light to ensure that I find it).