The question seems very simple, but the correct answer will include several answers.
I will give point decisions using jQuery.
Autofill
This is the easy part. You can use a plugin like select2 or jqueryui autocomplete and a view that finds users like
def search_users(request): search = request.GET.get('term') users = User.objects.filter( Q(first_name__icontains=search) | Q(last_name__icontains=search) ) ulist = list({'id': u.id, 'value': u'%s %s' % (u.first_name, u.last_name)} for u in users) return JsonResponse(ulist)
This view is compatible with the default jQuery UI autocomplete plugin
Dynamic format
This is a difficult question. The key is to use management_form and form.DELETE . Here is my solution:
- Use the built-in set of forms for participants (with one additional form).
- Print
management_form - Add form lines with jQuery after selecting autocomplete, cloning a hidden blank form (optional) and increasing
id_form-TOTAL_FORMS - Delete form lines with jQuery by hiding them and checking the hidden delete checkbox.
Template
<form method="post">{% csrf_token %} {{ sessionform }} <div> {{ participant_formset.management_form }} <label for="part_search">Search: </label><input id="part_search" /> <ul id="participation_set"> {% for tform in participant_formset %} {{ tform.id }} <li> <span class="participant"> {{ tform.participant }}{{ tform.instance.participant.name }} </span> <span class="status">{{ tform.status }}</span> <span class="delete ui-icon ui-icon-circle-minus"> {{ tform.DELETE }} </span> </li> {% endfor %} </ul> </div> </form>
CSS
#participation_set .delete { display: inline-block; vertical-align: middle; cursor: pointer; } #participation_set .delete input { display: none; } #participation_set li.deleted { display: none; } #participation_set li:last-child { display: none; }
JQuery
function add_aform(inst, item) { if ($(':input[name$="participant"][value=' + item.id + ']').length) { return false; } var total = $('#id_' + inst + '-TOTAL_FORMS').val(); var sul = '#' + inst; var li = $(sul + ' li:last-child'); var new_li = li.clone().appendTo(sul); li.find('span.participant').append(item.label); li.find(':input[name$="participant"]').val(item.id); new_li.find(':input').each(function () { var new_name = $(this).attr('name') .replace('-' + (total - 1) + '-', '-' + total + '-'); $(this).attr('name', new_name); }); new_li.find('label').each(function () { var tmp = $(this).attr('for') .replace('-' + (total - 1) + '-', '-' + total + '-'); $(this).attr('for', new_for); }); new_li.find('.delete').click(del_aform); $('#id_' + inst + '-TOTAL_FORMS').val(++total); } function del_aform() { $(this).parents('li').addClass('deleted'); $(this).find(':checkbox').attr('checked', true); }
I know that I could also use an empty_form instance and use __prefix__ to replace identifiers that simplify javascript for better maintainability, but I did not find a way to decompose the code between the true form and the empty one.
View
The view is pretty standard using inlineformset_factory with extra set to 1 (to get only the hidden form for cloning). Also remember to use the HiddenInput widget for the participant field