Django - dynamic embedded FormSet Javascript with autocomplete

I am trying to create a kind of event planner editor with the ability to pin participants.

Models

class Session(models.Model): start_time = models.DateTimeField() end_time = models.DateTimeField() class Participation(models.Model): session = models.ForeignKey(Session) participant = models.ForeignKey(User) status = models.CharField(max_length=1, choices=STATUSES) 

In the editor, I would like to have an autocomplete entry from which I can find user to add to the session

Preview

Session editor

Here I typed "laurent" and I'm going to add a person by clicking on one of the names
The colors of the participants depend on their status

I have a form for a Session object defined with start and end time
Now I think I should have a built-in set of forms for Participation s

Questions

  • Do you suggest using a built-in set of forms for participants?
  • How can I dynamically add / remove members lines?
+2
source share
2 answers

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

 /* Delete button */ #participation_set .delete { display: inline-block; vertical-align: middle; cursor: pointer; } /* Hidden delete checkbox */ #participation_set .delete input { display: none; } /* Deleted form */ #participation_set li.deleted { display: none; } /* Last hidden form to clone */ #participation_set li:last-child { display: none; } 

JQuery

 /*! This adds a form line * Call it on autocomplete select */ 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); } /*! This removes a form line * Call it on click from delete buttons (placed inside each li) */ 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

+5
source

This plugin will help you get the automatic full functionality that you are looking for:

https://github.com/millioner/django-ajax-select

+1
source

All Articles