diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 45680fa1..f4cd357d 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -50,34 +50,97 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): A list of field names (comma separated) that should be rendered with typeahead instead of the default bootstrap renderer. - choices - A string representing the choices in JS. The choices must be an - array of objects. Each of those objects must at least have the - fields 'key' (value to send) and 'value' (value to display). - Other fields can be added as desired. - If not specified, the key is the id of the object and the value - is its string representation as in a normal bootstrap form. - Example : - choices='[{key:0,value:"choice0",extrafield:"data0"}, {...},...];' + bft_param + A dict of parameters for the bootstrap_form_typeahead tag. The + possible parameters are the following. - match_func - A string representing a valid JS function used in the dataset to - overload the matching engine. This function is used the source of - the dataset. This function receives 2 parameters, the query and - the synchronize function as specified in typeahead.js documentation. - If needed, the local variables 'choices' and 'engine' contains - respectively the array of possible values and the engine to match - queries with possible values. - If not specified, the function used display up to the 10 first - elements if the query is empty and else the matching results. - Example : - match_func='function(q, sync) { engine.search(q, sync); }' + choices + A dict of strings representing the choices in JS. The keys of + the dict are the names of the concerned fields. The choices + must be an array of objects. Each of those objects must at + least have the fields 'key' (value to send) and 'value' (value + to display). Other fields can be added as desired. + For a more complex structure you should also consider + reimplementing the engine and the match_func. + If not specified, the key is the id of the object and the value + is its string representation as in a normal bootstrap form. + Example : + 'choices' : { + 'field_A':'[{key:0,value:"choice0",extra:"data0"},{...},...]', + 'field_B':..., + ... + } + + engine + A dict of strings representating the engine used for matching + queries and possible values with typeahead. The keys of the + dict are the names of the concerned fields. The string is valid + JS code. + If not specified, BloodHound with relevant basic properties is + used. + Example : + 'engine' : {'field_A': 'new Bloodhound()', 'field_B': ..., ...} + + match_func + A dict of strings representing a valid JS function used in the + dataset to overload the matching engine. The keys of the dict + are the names of the concerned fields. This function is used + the source of the dataset. This function receives 2 parameters, + the query and the synchronize function as specified in + typeahead.js documentation. If needed, the local variables + 'choices_' and 'engine_' contains + respectively the array of all possible values and the engine + to match queries with possible values. + If not specified, the function used display up to the 10 first + elements if the query is empty and else the matching results. + Example : + 'match_func' : { + 'field_A': 'function(q, sync) { engine.search(q, sync); }', + 'field_B': ..., + ... + } + + update_on + A dict of list of ids that the values depends on. The engine + and the typeahead properties are recalculated and reapplied. + Example : + 'addition' : { + 'field_A' : [ 'id0', 'id1', ... ] , + 'field_B' : ... , + ... + } See boostrap_form_ for other arguments **Usage**:: - {% bootstrap_form_typeahead form ['field1[,field2[,...]]] %} + {% bootstrap_form_typeahead + form + [ '[,[,...]]' ] + [ { + [ 'choices': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'engine': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'match_func': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'update_on': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + } ] + [ ] + %} **Example**: @@ -133,12 +196,15 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): return mark_safe( form ) def input_id( f_name ) : + """ The id of the HTML input element """ return 'id_'+f_name def hidden_id( f_name ): + """ The id of the HTML hidden input element """ return 'typeahead_hidden_'+f_name def hidden_tag( f_bound, f_name ): + """ The HTML hidden input element """ return render_tag( 'input', attrs={ @@ -151,6 +217,7 @@ def hidden_tag( f_bound, f_name ): def typeahead_js( f_name, f_value, t_choices, t_engine, t_match_func, t_update_on ) : + """ The whole script to use """ choices = mark_safe(t_choices[f_name]) if f_name in t_choices.keys() \ else default_choices( f_value ) @@ -188,10 +255,12 @@ def typeahead_js( f_name, f_value, return render_tag( 'script', content=mark_safe( js_content ) ) def reset_input( f_name, f_value ) : + """ The JS script to reset the fields values """ return '$("#'+input_id(f_name)+'").typeahead("val","");\n' \ '$("#'+hidden_id(f_name)+'").val("");' def default_choices( f_value ) : + """ The JS script creating the variable choices_ """ return '[' + \ ', '.join([ \ '{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \ @@ -201,6 +270,7 @@ def default_choices( f_value ) : ']' def default_engine ( f_name ) : + """ The JS script creating the variable engine_ """ return 'new Bloodhound({ ' \ 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ @@ -209,6 +279,7 @@ def default_engine ( f_name ) : '})' def default_datasets( f_name, match_func ) : + """ The JS script creating the datasets to use with typeahead """ return '{ ' \ 'hint: true, ' \ 'highlight: true, ' \ @@ -221,6 +292,7 @@ def default_datasets( f_name, match_func ) : '}' def default_match_func ( f_name ) : + """ The JS script creating the matching function to use with typeahed """ return 'function(q, sync) {' \ 'if (q === "") {' \ 'var nb = 10;' \ @@ -235,6 +307,8 @@ def default_match_func ( f_name ) : '}' def typeahead_updater( f_name ): + """ The JS script creating the function triggered when an item is + selected through typeahead """ return 'function(evt, item) { ' \ '$("#'+hidden_id(f_name)+'").val( item.key ); ' \ '$("#'+hidden_id(f_name)+'").change();' \ @@ -242,6 +316,9 @@ def typeahead_updater( f_name ): '}' def typeahead_change( f_name ): + """ The JS script creating the function triggered when an item is changed + (i.e. looses focus and value has changed since the moment it gained focus + """ return 'function(evt) { ' \ 'if ($("#'+input_id(f_name)+'").typeahead("val") === "") {' \ '$("#'+hidden_id(f_name)+'").val(""); ' \ diff --git a/machines/views.py b/machines/views.py index d9c59b15..84b13d89 100644 --- a/machines/views.py +++ b/machines/views.py @@ -170,7 +170,7 @@ def new_machine(request, userid): messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces) return redirect("/users/profil/" + str(request.user.id)) machine = NewMachineForm(request.POST or None) - interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) + interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) nb_machine = Interface.objects.filter(machine__user=userid).count() domain = DomainForm(request.POST or None, user=user, nb_machine=nb_machine) if machine.is_valid() and interface.is_valid(): @@ -950,7 +950,7 @@ def history(request, object, id): object_instance = Text.objects.get(pk=id) except Text.DoesNotExist: messages.error(request, "Text inexistant") - return redirect("/machines/") + return redirect("/machines/") elif object == 'ns' and request.user.has_perms(('cableur',)): try: object_instance = Ns.objects.get(pk=id) @@ -997,7 +997,7 @@ def history(request, object, id): @login_required @permission_required('cableur') def index_portlist(request): - port_list = OuverturePortList.objects.all().order_by('name') + port_list = OuverturePortList.objects.all().order_by('name') return render(request, "machines/index_portlist.html", {'port_list':port_list}) @login_required @@ -1010,7 +1010,7 @@ def edit_portlist(request, pk): return redirect("/machines/index_portlist/") port_list = EditOuverturePortListForm(request.POST or None, instance=port_list_instance) port_formset = modelformset_factory( - OuverturePort, + OuverturePort, fields=('begin','end','protocole','io'), extra=0, can_delete=True, @@ -1049,7 +1049,7 @@ def del_portlist(request, pk): def add_portlist(request): port_list = EditOuverturePortListForm(request.POST or None) port_formset = modelformset_factory( - OuverturePort, + OuverturePort, fields=('begin','end','protocole','io'), extra=0, can_delete=True,