mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-25 08:23:46 +00:00
Merge branch 'massive_use_bft_tag' into 'master'
Massive use bft tag See merge request rezo/re2o!22
This commit is contained in:
commit
e404f59b63
3 changed files with 647 additions and 410 deletions
|
@ -111,8 +111,6 @@ from .models import (
|
||||||
)
|
)
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from preferences.models import GeneralOption, OptionalMachine
|
from preferences.models import GeneralOption, OptionalMachine
|
||||||
|
|
||||||
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
|
|
||||||
from re2o.utils import (
|
from re2o.utils import (
|
||||||
all_active_assigned_interfaces,
|
all_active_assigned_interfaces,
|
||||||
all_has_access,
|
all_has_access,
|
||||||
|
@ -192,11 +190,13 @@ def generate_ipv4_mbf_param( form, is_type_tt ):
|
||||||
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
|
i_engine = { 'ipv4': generate_ipv4_engine( is_type_tt ) }
|
||||||
i_match_func = { 'ipv4': generate_ipv4_match_func( is_type_tt ) }
|
i_match_func = { 'ipv4': generate_ipv4_match_func( is_type_tt ) }
|
||||||
i_update_on = { 'ipv4': [f_type_id( is_type_tt )] }
|
i_update_on = { 'ipv4': [f_type_id( is_type_tt )] }
|
||||||
|
i_gen_select = { 'ipv4': False }
|
||||||
i_mbf_param = {
|
i_mbf_param = {
|
||||||
'choices': i_choices,
|
'choices': i_choices,
|
||||||
'engine': i_engine,
|
'engine': i_engine,
|
||||||
'match_func': i_match_func,
|
'match_func': i_match_func,
|
||||||
'update_on': i_update_on
|
'update_on': i_update_on,
|
||||||
|
'gen_select': i_gen_select
|
||||||
}
|
}
|
||||||
return i_mbf_param
|
return i_mbf_param
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,18 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
""" Templatetag used to render massive django form selects into bootstrap
|
||||||
|
forms that can still be manipulating even if there is multiple tens of
|
||||||
|
thousands of elements in the select. It's made possible using JS libaries
|
||||||
|
Twitter Typeahead and Splitree's Tokenfield.
|
||||||
|
See docstring of massive_bootstrap_form for a detailed explaantion on how
|
||||||
|
to use this templatetag.
|
||||||
|
"""
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.forms import TextInput
|
from django.forms import TextInput
|
||||||
from django.forms.widgets import Select
|
from django.forms.widgets import Select
|
||||||
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
|
|
||||||
from bootstrap3.utils import render_tag
|
from bootstrap3.utils import render_tag
|
||||||
from bootstrap3.forms import render_field
|
from bootstrap3.forms import render_field
|
||||||
|
|
||||||
|
@ -113,12 +120,29 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
||||||
A dict of list of ids that the values depends on. The engine
|
A dict of list of ids that the values depends on. The engine
|
||||||
and the typeahead properties are recalculated and reapplied.
|
and the typeahead properties are recalculated and reapplied.
|
||||||
Example :
|
Example :
|
||||||
'addition' : {
|
'update_on' : {
|
||||||
'field_A' : [ 'id0', 'id1', ... ] ,
|
'field_A' : [ 'id0', 'id1', ... ] ,
|
||||||
'field_B' : ... ,
|
'field_B' : ... ,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gen_select (optional)
|
||||||
|
A dict of boolean telling if the form should either generate
|
||||||
|
the normal select (set to true) and then use it to generate
|
||||||
|
the possible choices and then remove it or either (set to
|
||||||
|
false) generate the choices variable in this tag and do not
|
||||||
|
send any select.
|
||||||
|
Sending the select before can be usefull to permit the use
|
||||||
|
without any JS enabled but it will execute more code locally
|
||||||
|
for the client so the loading might be slower.
|
||||||
|
If not specified, this variable is set to true for each field
|
||||||
|
Example :
|
||||||
|
'gen_select' : {
|
||||||
|
'field_A': True ,
|
||||||
|
'field_B': ... ,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
See boostrap_form_ for other arguments
|
See boostrap_form_ for other arguments
|
||||||
|
|
||||||
**Usage**::
|
**Usage**::
|
||||||
|
@ -146,6 +170,11 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
||||||
[ '<field1>': '<update_on1>'
|
[ '<field1>': '<update_on1>'
|
||||||
[, '<field2>': '<update_on2>'
|
[, '<field2>': '<update_on2>'
|
||||||
[, ... ] ] ]
|
[, ... ] ] ]
|
||||||
|
} ],
|
||||||
|
[, 'gen_select': {
|
||||||
|
[ '<field1>': '<gen_select1>'
|
||||||
|
[, '<field2>': '<gen_select2>'
|
||||||
|
[, ... ] ] ]
|
||||||
} ]
|
} ]
|
||||||
} ]
|
} ]
|
||||||
[ <standard boostrap_form parameters> ]
|
[ <standard boostrap_form parameters> ]
|
||||||
|
@ -156,279 +185,189 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
||||||
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
|
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fields = mbf_fields.split(',')
|
mbf_form = MBFForm(form, mbf_fields.split(','), *args, **kwargs)
|
||||||
|
return mbf_form.render()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MBFForm():
|
||||||
|
""" An object to hold all the information and useful methods needed to
|
||||||
|
create and render a massive django form into an actual HTML and JS
|
||||||
|
code able to handle it correctly.
|
||||||
|
Every field that is not listed is rendered as a normal bootstrap_field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, form, mbf_fields, *args, **kwargs):
|
||||||
|
# The django form object
|
||||||
|
self.form = form
|
||||||
|
# The fields on which to use JS
|
||||||
|
self.fields = mbf_fields
|
||||||
|
|
||||||
|
# Other bootstrap_form arguments to render the fields
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
# Fields to exclude form the form rendering
|
||||||
|
self.exclude = self.kwargs.get('exclude', '').split(',')
|
||||||
|
|
||||||
|
# All the mbf parameters specified byt the user
|
||||||
param = kwargs.pop('mbf_param', {})
|
param = kwargs.pop('mbf_param', {})
|
||||||
exclude = param.get('exclude', '').split(',')
|
self.choices = param.get('choices', {})
|
||||||
choices = param.get('choices', {})
|
self.engine = param.get('engine', {})
|
||||||
engine = param.get('engine', {})
|
self.match_func = param.get('match_func', {})
|
||||||
match_func = param.get('match_func', {})
|
self.update_on = param.get('update_on', {})
|
||||||
update_on = param.get('update_on', {})
|
self.gen_select = param.get('gen_select', {})
|
||||||
hidden_fields = [h.name for h in form.hidden_fields()]
|
self.hidden_fields = [h.name for h in self.form.hidden_fields()]
|
||||||
|
|
||||||
html = ''
|
# HTML code to insert inside a template
|
||||||
|
self.html = ""
|
||||||
|
|
||||||
for f_name, f_value in form.fields.items() :
|
|
||||||
if not f_name in exclude :
|
|
||||||
if f_name in fields and not f_name in hidden_fields :
|
|
||||||
|
|
||||||
if not isinstance(f_value.widget, Select) :
|
def render(self):
|
||||||
|
""" HTML code for the fully rendered form with all the necessary form
|
||||||
|
"""
|
||||||
|
for name, field in self.form.fields.items():
|
||||||
|
if not name in self.exclude:
|
||||||
|
|
||||||
|
if name in self.fields and not name in self.hidden_fields:
|
||||||
|
mbf_field = MBFField(
|
||||||
|
name,
|
||||||
|
field,
|
||||||
|
field.get_bound_field(self.form, name),
|
||||||
|
self.choices.get(name, None),
|
||||||
|
self.engine.get(name, None),
|
||||||
|
self.match_func.get(name, None),
|
||||||
|
self.update_on.get(name, None),
|
||||||
|
self.gen_select.get(name, True),
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
self.html += mbf_field.render()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.html += render_field(
|
||||||
|
field.get_bound_field(self.form, name),
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return mark_safe(self.html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MBFField():
|
||||||
|
""" An object to hold all the information and useful methods needed to
|
||||||
|
create and render a massive django form field into an actual HTML and JS
|
||||||
|
code able to handle it correctly.
|
||||||
|
Twitter Typeahead is used for the display and the matching of queries and
|
||||||
|
in case of a MultipleSelect, Sliptree's Tokenfield is also used to manage
|
||||||
|
multiple values.
|
||||||
|
A div with only non visible elements is created after the div containing
|
||||||
|
the displayed input. It's used to store the actual data that will be sent
|
||||||
|
to the server """
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, name_, field_, bound_, choices_, engine_, match_func_,
|
||||||
|
update_on_, gen_select_, *args_, **kwargs_):
|
||||||
|
|
||||||
|
# Verify this field is a Select (or MultipleSelect) (only supported)
|
||||||
|
if not isinstance(field_.widget, Select):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
('Field named {f_name} from {form} is not a Select and'
|
('Field named {f_name} is not a Select and'
|
||||||
'can\'t be rendered with massive_bootstrap_form.'
|
'can\'t be rendered with massive_bootstrap_form.'
|
||||||
).format(
|
).format(
|
||||||
f_name=f_name,
|
f_name=name_
|
||||||
form=form
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
multiple = f_value.widget.allow_multiple_selected
|
# Name of the field
|
||||||
f_bound = f_value.get_bound_field( form, f_name )
|
self.name = name_
|
||||||
|
# Django field object
|
||||||
|
self.field = field_
|
||||||
|
# Bound Django field associated with field
|
||||||
|
self.bound = bound_
|
||||||
|
|
||||||
f_value.widget = TextInput(
|
# Id for the main visible input
|
||||||
attrs = {
|
self.input_id = self.bound.auto_id
|
||||||
'name': 'mbf_'+f_name,
|
# Id for a hidden input used to store the value
|
||||||
'placeholder': f_value.empty_label
|
self.hidden_id = self.input_id + '_hidden'
|
||||||
}
|
# Id for another div containing hidden inputs and script
|
||||||
)
|
self.div2_id = self.input_id + '_div'
|
||||||
html += render_field(
|
|
||||||
f_value.get_bound_field( form, f_name ),
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if multiple :
|
# Should the standard select should be generated
|
||||||
content = mbf_js(
|
self.gen_select = gen_select_
|
||||||
f_name,
|
# Is it select with multiple values possible (use of tokenfield)
|
||||||
f_value,
|
self.multiple = self.field.widget.allow_multiple_selected
|
||||||
f_bound,
|
# JS for the choices variable (user specified or default)
|
||||||
multiple,
|
self.choices = choices_ or self.default_choices()
|
||||||
choices,
|
# JS for the engine variable (typeahead) (user specified or default)
|
||||||
engine,
|
self.engine = engine_ or self.default_engine()
|
||||||
match_func,
|
# JS for the matching function (typeahead) (user specified or default)
|
||||||
update_on
|
self.match_func = match_func_ or self.default_match_func()
|
||||||
)
|
# JS for the datasets variable (typeahead) (user specified or default)
|
||||||
else :
|
self.datasets = self.default_datasets()
|
||||||
content = hidden_tag( f_bound, f_name ) + mbf_js(
|
# Ids of other fields to bind a reset/reload with when changed
|
||||||
f_name,
|
self.update_on = update_on_ or []
|
||||||
f_value,
|
|
||||||
f_bound,
|
# Whole HTML code to insert in the template
|
||||||
multiple,
|
self.html = ""
|
||||||
choices,
|
# JS code in the script tag
|
||||||
engine,
|
self.js_script = ""
|
||||||
match_func,
|
# Input tag to display instead of select
|
||||||
update_on
|
self.replace_input = None
|
||||||
)
|
|
||||||
html += render_tag(
|
# Other bootstrap_form arguments to render the fields
|
||||||
'div',
|
self.args = args_
|
||||||
content = content,
|
self.kwargs = kwargs_
|
||||||
attrs = { 'id': custom_div_id( f_bound ) }
|
|
||||||
|
|
||||||
|
def default_choices(self):
|
||||||
|
""" JS code of the variable choices_<fieldname> """
|
||||||
|
|
||||||
|
if self.gen_select:
|
||||||
|
return (
|
||||||
|
'function plop(o) {{'
|
||||||
|
'var c = [];'
|
||||||
|
'for( let i=0 ; i<o.length ; i++) {{'
|
||||||
|
' c.push( {{ key: o[i].value, value: o[i].text }} );'
|
||||||
|
'}}'
|
||||||
|
'return c;'
|
||||||
|
'}} ($("#{select_id}")[0].options)'
|
||||||
|
).format(
|
||||||
|
select_id=self.input_id
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
html += render_field(
|
|
||||||
f_value.get_bound_field( form, f_name ),
|
|
||||||
*args,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
return mark_safe( html )
|
|
||||||
|
|
||||||
def input_id( f_bound ) :
|
|
||||||
""" The id of the HTML input element """
|
|
||||||
return f_bound.auto_id
|
|
||||||
|
|
||||||
def hidden_id( f_bound ):
|
|
||||||
""" The id of the HTML hidden input element """
|
|
||||||
return input_id( f_bound ) + '_hidden'
|
|
||||||
|
|
||||||
def custom_div_id( f_bound ):
|
|
||||||
""" The id of the HTML div element containing values and script """
|
|
||||||
return input_id( f_bound ) + '_div'
|
|
||||||
|
|
||||||
def hidden_tag( f_bound, f_name ):
|
|
||||||
""" The HTML hidden input element """
|
|
||||||
return render_tag(
|
|
||||||
'input',
|
|
||||||
attrs={
|
|
||||||
'id': hidden_id( f_bound ),
|
|
||||||
'name': f_bound.html_name,
|
|
||||||
'type': 'hidden',
|
|
||||||
'value': f_bound.value() or ""
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def mbf_js( f_name, f_value, f_bound, multiple,
|
|
||||||
choices_, engine_, match_func_, update_on_ ) :
|
|
||||||
""" The whole script to use """
|
|
||||||
|
|
||||||
choices = ( mark_safe( choices_[f_name] ) if f_name in choices_.keys()
|
|
||||||
else default_choices( f_value ) )
|
|
||||||
|
|
||||||
engine = ( mark_safe( engine_[f_name] ) if f_name in engine_.keys()
|
|
||||||
else default_engine ( f_name ) )
|
|
||||||
|
|
||||||
match_func = ( mark_safe( match_func_[f_name] )
|
|
||||||
if f_name in match_func_.keys() else default_match_func( f_name ) )
|
|
||||||
|
|
||||||
update_on = update_on_[f_name] if f_name in update_on_.keys() else []
|
|
||||||
|
|
||||||
if multiple :
|
|
||||||
js_content = (
|
|
||||||
'var choices_{f_name} = {choices};'
|
|
||||||
'var engine_{f_name};'
|
|
||||||
'var setup_{f_name} = function() {{'
|
|
||||||
'engine_{f_name} = {engine};'
|
|
||||||
'$( "#{input_id}" ).tokenfield( "destroy" );'
|
|
||||||
'$( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});'
|
|
||||||
'}};'
|
|
||||||
'$( "#{input_id}" ).bind( "tokenfield:createtoken", {create} );'
|
|
||||||
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {edit} );'
|
|
||||||
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {remove} );'
|
|
||||||
'{updates}'
|
|
||||||
'$( "#{input_id}" ).ready( function() {{'
|
|
||||||
'setup_{f_name}();'
|
|
||||||
'{init_input}'
|
|
||||||
'}} );'
|
|
||||||
).format(
|
|
||||||
f_name = f_name,
|
|
||||||
choices = choices,
|
|
||||||
engine = engine,
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
datasets = default_datasets( f_name, match_func ),
|
|
||||||
create = tokenfield_create( f_name, f_bound ),
|
|
||||||
edit = tokenfield_edit( f_name, f_bound ),
|
|
||||||
remove = tokenfield_remove( f_name, f_bound ),
|
|
||||||
updates = ''.join( [ (
|
|
||||||
'$( "#{u_id}" ).change( function() {{'
|
|
||||||
'setup_{f_name}();'
|
|
||||||
'{reset_input}'
|
|
||||||
'}} );'
|
|
||||||
).format(
|
|
||||||
u_id = u_id,
|
|
||||||
reset_input = tokenfield_reset_input( f_bound ),
|
|
||||||
f_name = f_name
|
|
||||||
) for u_id in update_on ]
|
|
||||||
),
|
|
||||||
init_input = tokenfield_init_input( f_name, f_bound ),
|
|
||||||
)
|
|
||||||
else :
|
|
||||||
js_content = (
|
|
||||||
'var choices_{f_name} = {choices};'
|
|
||||||
'var engine_{f_name};'
|
|
||||||
'var setup_{f_name} = function() {{'
|
|
||||||
'engine_{f_name} = {engine};'
|
|
||||||
'$( "#{input_id}" ).typeahead( "destroy" );'
|
|
||||||
'$( "#{input_id}" ).typeahead( {datasets} );'
|
|
||||||
'}};'
|
|
||||||
'$( "#{input_id}" ).bind( "typeahead:select", {select} );'
|
|
||||||
'$( "#{input_id}" ).bind( "typeahead:change", {change} );'
|
|
||||||
'{updates}'
|
|
||||||
'$( "#{input_id}" ).ready( function() {{'
|
|
||||||
'setup_{f_name}();'
|
|
||||||
'{init_input}'
|
|
||||||
'}} );'
|
|
||||||
).format(
|
|
||||||
f_name = f_name,
|
|
||||||
choices = choices,
|
|
||||||
engine = engine,
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
datasets = default_datasets( f_name, match_func ),
|
|
||||||
select = typeahead_select( f_bound ),
|
|
||||||
change = typeahead_change( f_bound ),
|
|
||||||
updates = ''.join( [ (
|
|
||||||
'$( "#{u_id}" ).change( function() {{'
|
|
||||||
'setup_{f_name}();'
|
|
||||||
'{reset_input}'
|
|
||||||
'}} );'
|
|
||||||
).format(
|
|
||||||
u_id = u_id,
|
|
||||||
reset_input = typeahead_reset_input( f_bound ),
|
|
||||||
f_name = f_name
|
|
||||||
) for u_id in update_on ]
|
|
||||||
),
|
|
||||||
init_input = typeahead_init_input( f_name, f_bound ),
|
|
||||||
)
|
|
||||||
|
|
||||||
return render_tag( 'script', content=mark_safe( js_content ) )
|
|
||||||
|
|
||||||
def typeahead_init_input( f_name, f_bound ) :
|
|
||||||
""" The JS script to init the fields values """
|
|
||||||
init_key = f_bound.value() or '""'
|
|
||||||
return (
|
|
||||||
'$( "#{input_id}" ).typeahead("val", {init_val});'
|
|
||||||
'$( "#{hidden_id}" ).val( {init_key} );'
|
|
||||||
).format(
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
init_val = '""' if init_key == '""' else
|
|
||||||
'engine_{f_name}.get( {init_key} )[0].value'.format(
|
|
||||||
f_name = f_name,
|
|
||||||
init_key = init_key
|
|
||||||
),
|
|
||||||
init_key = init_key,
|
|
||||||
hidden_id = hidden_id( f_bound )
|
|
||||||
)
|
|
||||||
|
|
||||||
def typeahead_reset_input( f_bound ) :
|
|
||||||
""" The JS script to reset the fields values """
|
|
||||||
return (
|
|
||||||
'$( "#{input_id}" ).typeahead("val", "");'
|
|
||||||
'$( "#{hidden_id}" ).val( "" );'
|
|
||||||
).format(
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
hidden_id = hidden_id( f_bound )
|
|
||||||
)
|
|
||||||
|
|
||||||
def tokenfield_init_input( f_name, f_bound ) :
|
|
||||||
""" The JS script to init the fields values """
|
|
||||||
init_key = f_bound.value() or '""'
|
|
||||||
return (
|
|
||||||
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});'
|
|
||||||
).format(
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
init_val = '""' if init_key == '""' else (
|
|
||||||
'engine_{f_name}.get( {init_key} ).map('
|
|
||||||
'function(o) {{ return o.value; }}'
|
|
||||||
')').format(
|
|
||||||
f_name = f_name,
|
|
||||||
init_key = init_key
|
|
||||||
),
|
|
||||||
init_key = init_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
def tokenfield_reset_input( f_bound ) :
|
|
||||||
""" The JS script to reset the fields values """
|
|
||||||
return (
|
|
||||||
'$( "#{input_id}" ).tokenfield("setTokens", "");'
|
|
||||||
).format(
|
|
||||||
input_id = input_id( f_bound ),
|
|
||||||
)
|
|
||||||
|
|
||||||
def default_choices( f_value ) :
|
|
||||||
""" The JS script creating the variable choices_<fieldname> """
|
|
||||||
return '[{objects}]'.format(
|
return '[{objects}]'.format(
|
||||||
objects=','.join(
|
objects=','.join(
|
||||||
['{{key:{k},value:"{v}"}}'.format(
|
['{{key:{k},value:"{v}"}}'.format(
|
||||||
k=choice[0] if choice[0] != '' else '""',
|
k=choice[0] if choice[0] != '' else '""',
|
||||||
v=choice[1]
|
v=choice[1]
|
||||||
) for choice in f_value.choices ]
|
) for choice in self.field.choices]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def default_engine ( f_name ) :
|
|
||||||
""" The JS script creating the variable engine_<field_name> """
|
def default_engine(self):
|
||||||
|
""" Default JS code of the variable engine_<field_name> """
|
||||||
return (
|
return (
|
||||||
'new Bloodhound({{'
|
'new Bloodhound({{'
|
||||||
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
|
' datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
|
||||||
' queryTokenizer: Bloodhound.tokenizers.whitespace,'
|
' queryTokenizer: Bloodhound.tokenizers.whitespace,'
|
||||||
'local: choices_{f_name},'
|
' local: choices_{name},'
|
||||||
' identify: function(obj) {{ return obj.key; }}'
|
' identify: function(obj) {{ return obj.key; }}'
|
||||||
'}})'
|
'}})'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name
|
name=self.name
|
||||||
)
|
)
|
||||||
|
|
||||||
def default_datasets( f_name, match_func ) :
|
|
||||||
""" The JS script creating the datasets to use with typeahead """
|
def default_datasets(self):
|
||||||
|
""" Default JS script of the datasets to use with typeahead """
|
||||||
return (
|
return (
|
||||||
'{{'
|
'{{'
|
||||||
' hint: true,'
|
' hint: true,'
|
||||||
|
@ -437,34 +376,269 @@ def default_datasets( f_name, match_func ) :
|
||||||
'}},'
|
'}},'
|
||||||
'{{'
|
'{{'
|
||||||
' display: "value",'
|
' display: "value",'
|
||||||
'name: "{f_name}",'
|
' name: "{name}",'
|
||||||
' source: {match_func}'
|
' source: {match_func}'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name,
|
name=self.name,
|
||||||
match_func = match_func
|
match_func=self.match_func
|
||||||
)
|
)
|
||||||
|
|
||||||
def default_match_func ( f_name ) :
|
|
||||||
""" The JS script creating the matching function to use with typeahed """
|
def default_match_func(self):
|
||||||
|
""" Default JS code of the matching function to use with typeahed """
|
||||||
return (
|
return (
|
||||||
'function ( q, sync ) {{'
|
'function ( q, sync ) {{'
|
||||||
' if ( q === "" ) {{'
|
' if ( q === "" ) {{'
|
||||||
'var first = choices_{f_name}.slice( 0, 5 ).map('
|
' var first = choices_{name}.slice( 0, 5 ).map('
|
||||||
' function ( obj ) {{ return obj.key; }}'
|
' function ( obj ) {{ return obj.key; }}'
|
||||||
' );'
|
' );'
|
||||||
'sync( engine_{f_name}.get( first ) );'
|
' sync( engine_{name}.get( first ) );'
|
||||||
' }} else {{'
|
' }} else {{'
|
||||||
'engine_{f_name}.search( q, sync );'
|
' engine_{name}.search( q, sync );'
|
||||||
' }}'
|
' }}'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name
|
name=self.name
|
||||||
)
|
)
|
||||||
|
|
||||||
def typeahead_select( f_bound ):
|
|
||||||
""" The JS script creating the function triggered when an item is
|
def render(self):
|
||||||
selected through typeahead """
|
""" HTML code for the fully rendered field """
|
||||||
|
self.gen_displayed_div()
|
||||||
|
self.gen_hidden_div()
|
||||||
|
return mark_safe(self.html)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_displayed_div(self):
|
||||||
|
""" Generate HTML code for the div that contains displayed tags """
|
||||||
|
if self.gen_select:
|
||||||
|
self.html += render_field(
|
||||||
|
self.bound,
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
self.field.widget = TextInput(
|
||||||
|
attrs={
|
||||||
|
'name': 'mbf_'+self.name,
|
||||||
|
'placeholder': self.field.empty_label
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.replace_input = render_field(
|
||||||
|
self.bound,
|
||||||
|
*self.args,
|
||||||
|
**self.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.gen_select:
|
||||||
|
self.html += self.replace_input
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hidden_div(self):
|
||||||
|
""" Generate HTML code for the div that contains hidden tags """
|
||||||
|
self.gen_full_js()
|
||||||
|
|
||||||
|
content = self.js_script
|
||||||
|
if not self.multiple and not self.gen_select:
|
||||||
|
content += self.hidden_input()
|
||||||
|
|
||||||
|
self.html += render_tag(
|
||||||
|
'div',
|
||||||
|
content=content,
|
||||||
|
attrs={'id': self.div2_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def hidden_input(self):
|
||||||
|
""" HTML for the hidden input element """
|
||||||
|
return render_tag(
|
||||||
|
'input',
|
||||||
|
attrs={
|
||||||
|
'id': self.hidden_id,
|
||||||
|
'name': self.bound.html_name,
|
||||||
|
'type': 'hidden',
|
||||||
|
'value': self.bound.value() or ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_full_js(self):
|
||||||
|
""" Generate the full script tag containing the JS code """
|
||||||
|
self.create_js()
|
||||||
|
self.fill_js()
|
||||||
|
self.get_script()
|
||||||
|
|
||||||
|
|
||||||
|
def create_js(self):
|
||||||
|
""" Generate a template for the whole script to use depending on
|
||||||
|
gen_select and multiple """
|
||||||
|
if self.gen_select:
|
||||||
|
if self.multiple:
|
||||||
|
self.js_script = (
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' var choices_{f_name} = {choices};'
|
||||||
|
' {del_select}'
|
||||||
|
' var engine_{f_name};'
|
||||||
|
' var setup_{f_name} = function() {{'
|
||||||
|
' engine_{f_name} = {engine};'
|
||||||
|
' $( "#{input_id}" ).tokenfield( "destroy" );'
|
||||||
|
' $( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});'
|
||||||
|
' }};'
|
||||||
|
' $( "#{input_id}" ).bind( "tokenfield:createtoken", {tok_create} );'
|
||||||
|
' $( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );'
|
||||||
|
' $( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );'
|
||||||
|
' {tok_updates}'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {tok_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.js_script = (
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' var choices_{f_name} = {choices};'
|
||||||
|
' {del_select}'
|
||||||
|
' {gen_hidden}'
|
||||||
|
' var engine_{f_name};'
|
||||||
|
' var setup_{f_name} = function() {{'
|
||||||
|
' engine_{f_name} = {engine};'
|
||||||
|
' $( "#{input_id}" ).typeahead( "destroy" );'
|
||||||
|
' $( "#{input_id}" ).typeahead( {datasets} );'
|
||||||
|
' }};'
|
||||||
|
' $( "#{input_id}" ).bind( "typeahead:select", {typ_select} );'
|
||||||
|
' $( "#{input_id}" ).bind( "typeahead:change", {typ_change} );'
|
||||||
|
' {typ_updates}'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {typ_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if self.multiple:
|
||||||
|
self.js_script = (
|
||||||
|
'var choices_{f_name} = {choices};'
|
||||||
|
'var engine_{f_name};'
|
||||||
|
'var setup_{f_name} = function() {{'
|
||||||
|
' engine_{f_name} = {engine};'
|
||||||
|
' $( "#{input_id}" ).tokenfield( "destroy" );'
|
||||||
|
' $( "#{input_id}" ).tokenfield({{typeahead: [ {datasets} ] }});'
|
||||||
|
'}};'
|
||||||
|
'$( "#{input_id}" ).bind( "tokenfield:createtoken", {tok_create} );'
|
||||||
|
'$( "#{input_id}" ).bind( "tokenfield:edittoken", {tok_edit} );'
|
||||||
|
'$( "#{input_id}" ).bind( "tokenfield:removetoken", {tok_remove} );'
|
||||||
|
'{tok_updates}'
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {tok_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.js_script = (
|
||||||
|
'var choices_{f_name} ={choices};'
|
||||||
|
'var engine_{f_name};'
|
||||||
|
'var setup_{f_name} = function() {{'
|
||||||
|
' engine_{f_name} = {engine};'
|
||||||
|
' $( "#{input_id}" ).typeahead( "destroy" );'
|
||||||
|
' $( "#{input_id}" ).typeahead( {datasets} );'
|
||||||
|
'}};'
|
||||||
|
'$( "#{input_id}" ).bind( "typeahead:select", {typ_select} );'
|
||||||
|
'$( "#{input_id}" ).bind( "typeahead:change", {typ_change} );'
|
||||||
|
'{typ_updates}'
|
||||||
|
'$( "#{input_id}" ).ready( function() {{'
|
||||||
|
' setup_{f_name}();'
|
||||||
|
' {typ_init_input}'
|
||||||
|
'}} );'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fill_js(self):
|
||||||
|
""" Fill the template with the correct values """
|
||||||
|
self.js_script = self.js_script.format(
|
||||||
|
f_name=self.name,
|
||||||
|
choices=self.choices,
|
||||||
|
del_select=self.del_select(),
|
||||||
|
gen_hidden=self.gen_hidden(),
|
||||||
|
engine=self.engine,
|
||||||
|
input_id=self.input_id,
|
||||||
|
datasets=self.datasets,
|
||||||
|
typ_select=self.typeahead_select(),
|
||||||
|
typ_change=self.typeahead_change(),
|
||||||
|
tok_create=self.tokenfield_create(),
|
||||||
|
tok_edit=self.tokenfield_edit(),
|
||||||
|
tok_remove=self.tokenfield_remove(),
|
||||||
|
typ_updates=self.typeahead_updates(),
|
||||||
|
tok_updates=self.tokenfield_updates(),
|
||||||
|
tok_init_input=self.tokenfield_init_input(),
|
||||||
|
typ_init_input=self.typeahead_init_input()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_script(self):
|
||||||
|
""" Insert the JS code inside a script tag """
|
||||||
|
self.js_script = render_tag('script', content=mark_safe(self.js_script))
|
||||||
|
|
||||||
|
|
||||||
|
def del_select(self):
|
||||||
|
""" JS code to delete the select if it has been generated and replace
|
||||||
|
it with an input. """
|
||||||
|
return (
|
||||||
|
'var p = $("#{select_id}").parent()[0];'
|
||||||
|
'var new_input = `{replace_input}`;'
|
||||||
|
'p.innerHTML = new_input;'
|
||||||
|
).format(
|
||||||
|
select_id=self.input_id,
|
||||||
|
replace_input=self.replace_input
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hidden(self):
|
||||||
|
""" JS code to add a hidden tag to store the value. """
|
||||||
|
return (
|
||||||
|
'var d = $("#{div2_id}")[0];'
|
||||||
|
'var i = document.createElement("input");'
|
||||||
|
'i.id = "{hidden_id}";'
|
||||||
|
'i.name = "{html_name}";'
|
||||||
|
'i.value = "";'
|
||||||
|
'i.type = "hidden";'
|
||||||
|
'd.appendChild(i);'
|
||||||
|
).format(
|
||||||
|
div2_id=self.div2_id,
|
||||||
|
hidden_id=self.hidden_id,
|
||||||
|
html_name=self.bound.html_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_init_input(self):
|
||||||
|
""" JS code to init the fields values """
|
||||||
|
init_key = self.bound.value() or '""'
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).typeahead("val", {init_val});'
|
||||||
|
'$( "#{hidden_id}" ).val( {init_key} );'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
init_val='""' if init_key == '""' else
|
||||||
|
'engine_{name}.get( {init_key} )[0].value'.format(
|
||||||
|
name=self.name,
|
||||||
|
init_key=init_key
|
||||||
|
),
|
||||||
|
init_key=init_key,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_reset_input(self):
|
||||||
|
""" JS code to reset the fields values """
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).typeahead("val", "");'
|
||||||
|
'$( "#{hidden_id}" ).val( "" );'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
hidden_id=self.hidden_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def typeahead_select(self):
|
||||||
|
""" JS code to create the function triggered when an item is selected
|
||||||
|
through typeahead """
|
||||||
return (
|
return (
|
||||||
'function(evt, item) {{'
|
'function(evt, item) {{'
|
||||||
' $( "#{hidden_id}" ).val( item.key );'
|
' $( "#{hidden_id}" ).val( item.key );'
|
||||||
|
@ -472,12 +646,13 @@ def typeahead_select( f_bound ):
|
||||||
' return item;'
|
' return item;'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
hidden_id = hidden_id( f_bound )
|
hidden_id=self.hidden_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def typeahead_change( f_bound ):
|
|
||||||
""" The JS script creating the function triggered when an item is changed
|
def typeahead_change(self):
|
||||||
(i.e. looses focus and value has changed since the moment it gained focus
|
""" JS code of the function triggered when an item is changed (i.e.
|
||||||
|
looses focus and value has changed since the moment it gained focus )
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
'function(evt) {{'
|
'function(evt) {{'
|
||||||
|
@ -487,53 +662,98 @@ def typeahead_change( f_bound ):
|
||||||
' }}'
|
' }}'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
input_id = input_id( f_bound ),
|
input_id=self.input_id,
|
||||||
hidden_id = hidden_id( f_bound )
|
hidden_id=self.hidden_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def tokenfield_create( f_name, f_bound ):
|
|
||||||
""" The JS script triggered when a new token is created in tokenfield. """
|
def typeahead_updates(self):
|
||||||
|
""" JS code for binding external fields changes with a reset """
|
||||||
|
reset_input = self.typeahead_reset_input()
|
||||||
|
updates = [
|
||||||
|
(
|
||||||
|
'$( "#{u_id}" ).change( function() {{'
|
||||||
|
' setup_{name}();'
|
||||||
|
' {reset_input}'
|
||||||
|
'}} );'
|
||||||
|
).format(
|
||||||
|
u_id=u_id,
|
||||||
|
name=self.name,
|
||||||
|
reset_input=reset_input
|
||||||
|
) for u_id in self.update_on]
|
||||||
|
return ''.join(updates)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_init_input(self):
|
||||||
|
""" JS code to init the fields values """
|
||||||
|
init_key = self.bound.value() or '""'
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).tokenfield("setTokens", {init_val});'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id,
|
||||||
|
init_val='""' if init_key == '""' else (
|
||||||
|
'engine_{name}.get( {init_key} ).map('
|
||||||
|
' function(o) {{ return o.value; }}'
|
||||||
|
')').format(
|
||||||
|
name=self.name,
|
||||||
|
init_key=init_key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_reset_input(self):
|
||||||
|
""" JS code to reset the fields values """
|
||||||
|
return (
|
||||||
|
'$( "#{input_id}" ).tokenfield("setTokens", "");'
|
||||||
|
).format(
|
||||||
|
input_id=self.input_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_create(self):
|
||||||
|
""" JS code triggered when a new token is created in tokenfield. """
|
||||||
return (
|
return (
|
||||||
'function(evt) {{'
|
'function(evt) {{'
|
||||||
' var k = evt.attrs.key;'
|
' var k = evt.attrs.key;'
|
||||||
' if (!k) {{'
|
' if (!k) {{'
|
||||||
' var data = evt.attrs.value;'
|
' var data = evt.attrs.value;'
|
||||||
' var i = 0;'
|
' var i = 0;'
|
||||||
'while ( i<choices_{f_name}.length &&'
|
' while ( i<choices_{name}.length &&'
|
||||||
'choices_{f_name}[i].value !== data ) {{'
|
' choices_{name}[i].value !== data ) {{'
|
||||||
' i++;'
|
' i++;'
|
||||||
' }}'
|
' }}'
|
||||||
'if ( i === choices_{f_name}.length ) {{ return false; }}'
|
' if ( i === choices_{name}.length ) {{ return false; }}'
|
||||||
'k = choices_{f_name}[i].key;'
|
' k = choices_{name}[i].key;'
|
||||||
' }}'
|
' }}'
|
||||||
' var new_input = document.createElement("input");'
|
' var new_input = document.createElement("input");'
|
||||||
' new_input.type = "hidden";'
|
' new_input.type = "hidden";'
|
||||||
' new_input.id = "{hidden_id}_"+k.toString();'
|
' new_input.id = "{hidden_id}_"+k.toString();'
|
||||||
' new_input.value = k.toString();'
|
' new_input.value = k.toString();'
|
||||||
'new_input.name = "{name}";'
|
' new_input.name = "{html_name}";'
|
||||||
'$( "#{div_id}" ).append(new_input);'
|
' $( "#{div2_id}" ).append(new_input);'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name,
|
name=self.name,
|
||||||
hidden_id = hidden_id( f_bound ),
|
hidden_id=self.hidden_id,
|
||||||
name = f_bound.html_name,
|
html_name=self.bound.html_name,
|
||||||
div_id = custom_div_id( f_bound )
|
div2_id=self.div2_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def tokenfield_edit( f_name, f_bound ):
|
|
||||||
""" The JS script triggered when a token is edited in tokenfield. """
|
def tokenfield_edit(self):
|
||||||
|
""" JS code triggered when a token is edited in tokenfield. """
|
||||||
return (
|
return (
|
||||||
'function(evt) {{'
|
'function(evt) {{'
|
||||||
' var k = evt.attrs.key;'
|
' var k = evt.attrs.key;'
|
||||||
' if (!k) {{'
|
' if (!k) {{'
|
||||||
' var data = evt.attrs.value;'
|
' var data = evt.attrs.value;'
|
||||||
' var i = 0;'
|
' var i = 0;'
|
||||||
'while ( i<choices_{f_name}.length &&'
|
' while ( i<choices_{name}.length &&'
|
||||||
'choices_{f_name}[i].value !== data ) {{'
|
' choices_{name}[i].value !== data ) {{'
|
||||||
' i++;'
|
' i++;'
|
||||||
' }}'
|
' }}'
|
||||||
'if ( i === choices_{f_name}.length ) {{ return true; }}'
|
' if ( i === choices_{name}.length ) {{ return true; }}'
|
||||||
'k = choices_{f_name}[i].key;'
|
' k = choices_{name}[i].key;'
|
||||||
' }}'
|
' }}'
|
||||||
' var old_input = document.getElementById('
|
' var old_input = document.getElementById('
|
||||||
' "{hidden_id}_"+k.toString()'
|
' "{hidden_id}_"+k.toString()'
|
||||||
|
@ -541,24 +761,25 @@ def tokenfield_edit( f_name, f_bound ):
|
||||||
' old_input.parentNode.removeChild(old_input);'
|
' old_input.parentNode.removeChild(old_input);'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name,
|
name=self.name,
|
||||||
hidden_id = hidden_id( f_bound )
|
hidden_id=self.hidden_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def tokenfield_remove( f_name, f_bound ):
|
|
||||||
""" The JS script trigggered when a token is removed from tokenfield. """
|
def tokenfield_remove(self):
|
||||||
|
""" JS code trigggered when a token is removed from tokenfield. """
|
||||||
return (
|
return (
|
||||||
'function(evt) {{'
|
'function(evt) {{'
|
||||||
' var k = evt.attrs.key;'
|
' var k = evt.attrs.key;'
|
||||||
' if (!k) {{'
|
' if (!k) {{'
|
||||||
' var data = evt.attrs.value;'
|
' var data = evt.attrs.value;'
|
||||||
' var i = 0;'
|
' var i = 0;'
|
||||||
'while ( i<choices_{f_name}.length &&'
|
' while ( i<choices_{name}.length &&'
|
||||||
'choices_{f_name}[i].value !== data ) {{'
|
' choices_{name}[i].value !== data ) {{'
|
||||||
' i++;'
|
' i++;'
|
||||||
' }}'
|
' }}'
|
||||||
'if ( i === choices_{f_name}.length ) {{ return true; }}'
|
' if ( i === choices_{name}.length ) {{ return true; }}'
|
||||||
'k = choices_{f_name}[i].key;'
|
' k = choices_{name}[i].key;'
|
||||||
' }}'
|
' }}'
|
||||||
' var old_input = document.getElementById('
|
' var old_input = document.getElementById('
|
||||||
' "{hidden_id}_"+k.toString()'
|
' "{hidden_id}_"+k.toString()'
|
||||||
|
@ -566,7 +787,23 @@ def tokenfield_remove( f_name, f_bound ):
|
||||||
' old_input.parentNode.removeChild(old_input);'
|
' old_input.parentNode.removeChild(old_input);'
|
||||||
'}}'
|
'}}'
|
||||||
).format(
|
).format(
|
||||||
f_name = f_name,
|
name=self.name,
|
||||||
hidden_id = hidden_id( f_bound )
|
hidden_id=self.hidden_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokenfield_updates(self):
|
||||||
|
""" JS code for binding external fields changes with a reset """
|
||||||
|
reset_input = self.tokenfield_reset_input()
|
||||||
|
updates = [
|
||||||
|
(
|
||||||
|
'$( "#{u_id}" ).change( function() {{'
|
||||||
|
' setup_{name}();'
|
||||||
|
' {reset_input}'
|
||||||
|
'}} );'
|
||||||
|
).format(
|
||||||
|
u_id=u_id,
|
||||||
|
name=self.name,
|
||||||
|
reset_input=reset_input
|
||||||
|
) for u_id in self.update_on]
|
||||||
|
return ''.join(updates)
|
||||||
|
|
|
@ -53,7 +53,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% massive_bootstrap_form machineform 'user' %}
|
{% massive_bootstrap_form machineform 'user' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interfaceform %}
|
{% if interfaceform %}
|
||||||
{% if i_bft_param %}
|
{% if i_mbf_param %}
|
||||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
|
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
|
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
|
||||||
|
|
Loading…
Reference in a new issue