8
0
Fork 0
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!18
This commit is contained in:
Maël Kervella 2017-10-15 17:09:42 +02:00
commit 6ef055a7bc
14 changed files with 1898 additions and 424 deletions

View file

@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load staticfiles%}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification de factures{% endblock %}
@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
<h3>Editer la facture</h3>
{% bootstrap_form_typeahead factureform 'user' %}
{% massive_bootstrap_form factureform 'user' %}
{{ venteform.management_form }}
<h3>Articles de la facture</h3>
<table class="table table-striped">

View file

@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification de machines{% endblock %}
@ -78,10 +78,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% if interfaceform %}
<h3>Interface</h3>
{% if i_bft_param %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %}
{% if i_mbf_param %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_mbf_param %}
{% else %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
{% endif %}
{% endif %}
{% if domainform %}
@ -98,15 +98,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% if extensionform %}
<h3>Extension</h3>
{% bootstrap_form_typeahead extensionform 'origin' %}
{% massive_bootstrap_form extensionform 'origin' %}
{% endif %}
{% if mxform %}
<h3>Enregistrement MX</h3>
{% bootstrap_form_typeahead mxform 'name' %}
{% massive_bootstrap_form mxform 'name' %}
{% endif %}
{% if nsform %}
<h3>Enregistrement NS</h3>
{% bootstrap_form_typeahead nsform 'ns' %}
{% massive_bootstrap_form nsform 'ns' %}
{% endif %}
{% if txtform %}
<h3>Enregistrement TXT</h3>
@ -118,7 +118,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endif %}
{% if serviceform %}
<h3>Service</h3>
{% bootstrap_form serviceform %}
{% massive_bootstrap_form serviceform 'servers' %}
{% endif %}
{% if vlanform %}
<h3>Vlan</h3>

View file

@ -54,7 +54,7 @@ from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link, Vlan, Nas, Text, OuverturePortList, OuverturePort
from users.models import User
from preferences.models import GeneralOption, OptionalMachine
from re2o.templatetags.bootstrap_form_typeahead import hidden_id, input_id
from re2o.templatetags.massive_bootstrap_form import hidden_id, input_id
from re2o.utils import all_active_assigned_interfaces, all_has_access
from re2o.views import form
@ -65,7 +65,7 @@ def f_type_id( is_type_tt ):
return 'id_Interface-type_hidden' if is_type_tt else 'id_Interface-type'
def generate_ipv4_choices( form ) :
""" Generate the parameter choices for the bootstrap_form_typeahead tag
""" Generate the parameter choices for the massive_bootstrap_form tag
"""
f_ipv4 = form.fields['ipv4']
used_mtype_id = []
@ -92,7 +92,7 @@ def generate_ipv4_choices( form ) :
return choices
def generate_ipv4_engine( is_type_tt ) :
""" Generate the parameter engine for the bootstrap_form_typeahead tag
""" Generate the parameter engine for the massive_bootstrap_form tag
"""
return (
'new Bloodhound( {{'
@ -106,7 +106,7 @@ def generate_ipv4_engine( is_type_tt ) :
)
def generate_ipv4_match_func( is_type_tt ) :
""" Generate the parameter match_func for the bootstrap_form_typeahead tag
""" Generate the parameter match_func for the massive_bootstrap_form tag
"""
return (
'function(q, sync) {{'
@ -122,20 +122,20 @@ def generate_ipv4_match_func( is_type_tt ) :
type_id = f_type_id( is_type_tt )
)
def generate_ipv4_bft_param( form, is_type_tt ):
""" Generate all the parameters to use with the bootstrap_form_typeahead
def generate_ipv4_mbf_param( form, is_type_tt ):
""" Generate all the parameters to use with the massive_bootstrap_form
tag """
i_choices = { 'ipv4': generate_ipv4_choices( form ) }
i_engine = { 'ipv4': generate_ipv4_engine( 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_bft_param = {
i_mbf_param = {
'choices': i_choices,
'engine': i_engine,
'match_func': i_match_func,
'update_on': i_update_on
}
return i_bft_param
return i_mbf_param
@login_required
def new_machine(request, userid):
@ -183,8 +183,8 @@ def new_machine(request, userid):
reversion.set_comment("Création")
messages.success(request, "La machine a été créée")
return redirect("/users/profil/" + str(user.id))
i_bft_param = generate_ipv4_bft_param( interface, False )
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
i_mbf_param = generate_ipv4_mbf_param( interface, False )
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
@login_required
def edit_interface(request, interfaceid):
@ -223,8 +223,8 @@ def edit_interface(request, interfaceid):
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data))
messages.success(request, "La machine a été modifiée")
return redirect("/users/profil/" + str(interface.machine.user.id))
i_bft_param = generate_ipv4_bft_param( interface_form, False )
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
@login_required
def del_machine(request, machineid):
@ -282,8 +282,8 @@ def new_interface(request, machineid):
reversion.set_comment("Création")
messages.success(request, "L'interface a été ajoutée")
return redirect("/users/profil/" + str(machine.user.id))
i_bft_param = generate_ipv4_bft_param( interface_form, False )
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'machines/machine.html', request)
i_mbf_param = generate_ipv4_mbf_param( interface_form, False )
return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_mbf_param': i_mbf_param}, 'machines/machine.html', request)
@login_required
def del_interface(request, interfaceid):

View file

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification des préférences{% endblock %}
@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form_typeahead options 'utilisateur_asso' %}
{% massive_bootstrap_form options 'utilisateur_asso' %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
</form>
<br />

View file

@ -1,386 +0,0 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from django import template
from django.utils.safestring import mark_safe
from django.forms import TextInput
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field
register = template.Library()
@register.simple_tag
def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs):
"""
Render a form where some specific fields are rendered using Typeahead.
Using Typeahead really improves the performance, the speed and UX when
dealing with very large datasets (select with 50k+ elts for instance).
For convenience, it accepts the same parameters as a standard bootstrap
can accept.
**Tag name**::
bootstrap_form_typeahead
**Parameters**:
form
The form that is to be rendered
typeahead_fields
A list of field names (comma separated) that should be rendered
with typeahead instead of the default bootstrap renderer.
bft_param
A dict of parameters for the bootstrap_form_typeahead tag. The
possible parameters are the following.
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_<fieldname>' and 'engine_<fieldname>' 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>[,...]]' ]
[ {
[ 'choices': {
[ '<field1>': '<choices1>'
[, '<field2>': '<choices2>'
[, ... ] ] ]
} ]
[, 'engine': {
[ '<field1>': '<engine1>'
[, '<field2>': '<engine2>'
[, ... ] ] ]
} ]
[, 'match_func': {
[ '<field1>': '<match_func1>'
[, '<field2>': '<match_func2>'
[, ... ] ] ]
} ]
[, 'update_on': {
[ '<field1>': '<update_on1>'
[, '<field2>': '<update_on2>'
[, ... ] ] ]
} ]
} ]
[ <standard boostrap_form parameters> ]
%}
**Example**:
{% bootstrap_form_typeahead form 'ipv4' choices='[...]' %}
"""
t_fields = typeahead_fields.split(',')
params = kwargs.get('bft_param', {})
exclude = params.get('exclude', None)
exclude = exclude.split(',') if exclude else []
t_choices = params.get('choices', {})
t_engine = params.get('engine', {})
t_match_func = params.get('match_func', {})
t_update_on = params.get('update_on', {})
hidden = [h.name for h in django_form.hidden_fields()]
form = ''
for f_name, f_value in django_form.fields.items() :
if not f_name in exclude :
if f_name in t_fields and not f_name in hidden :
f_bound = f_value.get_bound_field( django_form, f_name )
f_value.widget = TextInput(
attrs={
'name': 'typeahead_'+f_name,
'placeholder': f_value.empty_label
}
)
form += render_field(
f_value.get_bound_field( django_form, f_name ),
*args,
**kwargs
)
form += render_tag(
'div',
content = hidden_tag( f_bound, f_name ) +
typeahead_js(
f_name,
f_value,
f_bound,
t_choices,
t_engine,
t_match_func,
t_update_on
)
)
else:
form += render_field(
f_value.get_bound_field(django_form, f_name),
*args,
**kwargs
)
return mark_safe( form )
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 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 typeahead_js( f_name, f_value, f_bound,
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 )
engine = mark_safe( t_engine[f_name] ) if f_name in t_engine.keys() \
else default_engine ( f_name )
match_func = mark_safe(t_match_func[f_name]) \
if f_name in t_match_func.keys() else default_match_func( f_name )
update_on = t_update_on[f_name] if f_name in t_update_on.keys() 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", {updater} );'
'$( "#{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 ),
updater = typeahead_updater( 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 = reset_input( f_bound ),
f_name = f_name
) for u_id in update_on ]
),
init_input = init_input( f_name, f_bound ),
)
return render_tag( 'script', content=mark_safe( js_content ) )
def 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 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 default_choices( f_value ) :
""" The JS script creating the variable choices_<fieldname> """
return '[{objects}]'.format(
objects = ','.join(
[ '{{key:{k},value:"{v}"}}'.format(
k = choice[0] if choice[0] != '' else '""',
v = choice[1]
) for choice in f_value.choices ]
)
)
def default_engine ( f_name ) :
""" The JS script creating the variable engine_<field_name> """
return (
'new Bloodhound({{'
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
'queryTokenizer: Bloodhound.tokenizers.whitespace,'
'local: choices_{f_name},'
'identify: function(obj) {{ return obj.key; }}'
'}})'
).format(
f_name = f_name
)
def default_datasets( f_name, match_func ) :
""" The JS script creating the datasets to use with typeahead """
return (
'{{'
'hint: true,'
'highlight: true,'
'minLength: 0'
'}},'
'{{'
'display: "value",'
'name: "{f_name}",'
'source: {match_func}'
'}}'
).format(
f_name = f_name,
match_func = 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 first = choices_{f_name}.slice( 0, 5 ).map('
'function ( obj ) {{ return obj.key; }}'
');'
'sync( engine_{f_name}.get( first ) );'
'}} else {{'
'engine_{f_name}.search( q, sync );'
'}}'
'}}'
).format(
f_name = f_name
)
def typeahead_updater( f_bound ):
""" The JS script creating the function triggered when an item is
selected through typeahead """
return (
'function(evt, item) {{'
'$( "#{hidden_id}" ).val( item.key );'
'$( "#{hidden_id}" ).change();'
'return item;'
'}}'
).format(
hidden_id = hidden_id( f_bound )
)
def typeahead_change( f_bound ):
""" 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}" ).typeahead( "val" ) === "" ) {{'
'$( "#{hidden_id}" ).val( "" );'
'$( "#{hidden_id}" ).change();'
'}}'
'}}'
).format(
input_id = input_id( f_bound ),
hidden_id = hidden_id( f_bound )
)

View file

@ -0,0 +1,572 @@
# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from django import template
from django.utils.safestring import mark_safe
from django.forms import TextInput
from django.forms.widgets import Select
from bootstrap3.templatetags.bootstrap3 import bootstrap_form
from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field
register = template.Library()
@register.simple_tag
def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
"""
Render a form where some specific fields are rendered using Twitter
Typeahead and/or splitree's Bootstrap Tokenfield to improve the performance, the
speed and UX when dealing with very large datasets (select with 50k+ elts
for instance).
When the fields specified should normally be rendered as a select with
single selectable option, Twitter Typeahead is used for a better display
and the matching query engine. When dealing with multiple selectable
options, sliptree's Bootstrap Tokenfield in addition with Typeahead.
For convenience, it accepts the same parameters as a standard bootstrap
can accept.
**Tag name**::
massive_bootstrap_form
**Parameters**:
form (required)
The form that is to be rendered
mbf_fields (optional)
A list of field names (comma separated) that should be rendered
with Typeahead/Tokenfield instead of the default bootstrap
renderer.
If not specified, all fields will be rendered as a normal bootstrap
field.
mbf_param (optional)
A dict of parameters for the massive_bootstrap_form tag. The
possible parameters are the following.
choices (optional)
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 (optional)
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 (optional)
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_<fieldname>' and 'engine_<fieldname>' 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 (optional)
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**::
{% massive_bootstrap_form
form
[ '<field1>[,<field2>[,...]]' ]
[ mbf_param = {
[ 'choices': {
[ '<field1>': '<choices1>'
[, '<field2>': '<choices2>'
[, ... ] ] ]
} ]
[, 'engine': {
[ '<field1>': '<engine1>'
[, '<field2>': '<engine2>'
[, ... ] ] ]
} ]
[, 'match_func': {
[ '<field1>': '<match_func1>'
[, '<field2>': '<match_func2>'
[, ... ] ] ]
} ]
[, 'update_on': {
[ '<field1>': '<update_on1>'
[, '<field2>': '<update_on2>'
[, ... ] ] ]
} ]
} ]
[ <standard boostrap_form parameters> ]
%}
**Example**:
{% massive_bootstrap_form form 'ipv4' choices='[...]' %}
"""
fields = mbf_fields.split(',')
param = kwargs.pop('mbf_param', {})
exclude = param.get('exclude', '').split(',')
choices = param.get('choices', {})
engine = param.get('engine', {})
match_func = param.get('match_func', {})
update_on = param.get('update_on', {})
hidden_fields = [h.name for h in form.hidden_fields()]
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) :
raise ValueError(
('Field named {f_name} from {form} is not a Select and'
'can\'t be rendered with massive_bootstrap_form.'
).format(
f_name=f_name,
form=form
)
)
multiple = f_value.widget.allow_multiple_selected
f_bound = f_value.get_bound_field( form, f_name )
f_value.widget = TextInput(
attrs = {
'name': 'mbf_'+f_name,
'placeholder': f_value.empty_label
}
)
html += render_field(
f_value.get_bound_field( form, f_name ),
*args,
**kwargs
)
if multiple :
content = mbf_js(
f_name,
f_value,
f_bound,
multiple,
choices,
engine,
match_func,
update_on
)
else :
content = hidden_tag( f_bound, f_name ) + mbf_js(
f_name,
f_value,
f_bound,
multiple,
choices,
engine,
match_func,
update_on
)
html += render_tag(
'div',
content = content,
attrs = { 'id': custom_div_id( f_bound ) }
)
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(
objects = ','.join(
[ '{{key:{k},value:"{v}"}}'.format(
k = choice[0] if choice[0] != '' else '""',
v = choice[1]
) for choice in f_value.choices ]
)
)
def default_engine ( f_name ) :
""" The JS script creating the variable engine_<field_name> """
return (
'new Bloodhound({{'
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),'
'queryTokenizer: Bloodhound.tokenizers.whitespace,'
'local: choices_{f_name},'
'identify: function(obj) {{ return obj.key; }}'
'}})'
).format(
f_name = f_name
)
def default_datasets( f_name, match_func ) :
""" The JS script creating the datasets to use with typeahead """
return (
'{{'
'hint: true,'
'highlight: true,'
'minLength: 0'
'}},'
'{{'
'display: "value",'
'name: "{f_name}",'
'source: {match_func}'
'}}'
).format(
f_name = f_name,
match_func = 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 first = choices_{f_name}.slice( 0, 5 ).map('
'function ( obj ) {{ return obj.key; }}'
');'
'sync( engine_{f_name}.get( first ) );'
'}} else {{'
'engine_{f_name}.search( q, sync );'
'}}'
'}}'
).format(
f_name = f_name
)
def typeahead_select( f_bound ):
""" The JS script creating the function triggered when an item is
selected through typeahead """
return (
'function(evt, item) {{'
'$( "#{hidden_id}" ).val( item.key );'
'$( "#{hidden_id}" ).change();'
'return item;'
'}}'
).format(
hidden_id = hidden_id( f_bound )
)
def typeahead_change( f_bound ):
""" 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}" ).typeahead( "val" ) === "" ) {{'
'$( "#{hidden_id}" ).val( "" );'
'$( "#{hidden_id}" ).change();'
'}}'
'}}'
).format(
input_id = input_id( f_bound ),
hidden_id = hidden_id( f_bound )
)
def tokenfield_create( f_name, f_bound ):
""" The JS script triggered when a new token is created in tokenfield. """
return (
'function(evt) {{'
'var k = evt.attrs.key;'
'if (!k) {{'
'var data = evt.attrs.value;'
'var i = 0;'
'while ( i<choices_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
'i++;'
'}}'
'if ( i === choices_{f_name}.length ) {{ return false; }}'
'k = choices_{f_name}[i].key;'
'}}'
'var new_input = document.createElement("input");'
'new_input.type = "hidden";'
'new_input.id = "{hidden_id}_"+k.toString();'
'new_input.value = k.toString();'
'new_input.name = "{name}";'
'$( "#{div_id}" ).append(new_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound ),
name = f_bound.html_name,
div_id = custom_div_id( f_bound )
)
def tokenfield_edit( f_name, f_bound ):
""" The JS script triggered when a token is edited in tokenfield. """
return (
'function(evt) {{'
'var k = evt.attrs.key;'
'if (!k) {{'
'var data = evt.attrs.value;'
'var i = 0;'
'while ( i<choices_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
'i++;'
'}}'
'if ( i === choices_{f_name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;'
'}}'
'var old_input = document.getElementById('
'"{hidden_id}_"+k.toString()'
');'
'old_input.parentNode.removeChild(old_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound )
)
def tokenfield_remove( f_name, f_bound ):
""" The JS script trigggered when a token is removed from tokenfield. """
return (
'function(evt) {{'
'var k = evt.attrs.key;'
'if (!k) {{'
'var data = evt.attrs.value;'
'var i = 0;'
'while ( i<choices_{f_name}.length &&'
'choices_{f_name}[i].value !== data ) {{'
'i++;'
'}}'
'if ( i === choices_{f_name}.length ) {{ return true; }}'
'k = choices_{f_name}[i].key;'
'}}'
'var old_input = document.getElementById('
'"{hidden_id}_"+k.toString()'
');'
'old_input.parentNode.removeChild(old_input);'
'}}'
).format(
f_name = f_name,
hidden_id = hidden_id( f_bound )
)

210
static/css/bootstrap-tokenfield.css vendored Normal file
View file

@ -0,0 +1,210 @@
/*!
* bootstrap-tokenfield
* https://github.com/sliptree/bootstrap-tokenfield
* Copyright 2013-2014 Sliptree and other contributors; Licensed MIT
*/
@-webkit-keyframes blink {
0% {
border-color: #ededed;
}
100% {
border-color: #b94a48;
}
}
@-moz-keyframes blink {
0% {
border-color: #ededed;
}
100% {
border-color: #b94a48;
}
}
@keyframes blink {
0% {
border-color: #ededed;
}
100% {
border-color: #b94a48;
}
}
.tokenfield {
height: auto;
min-height: 34px;
padding-bottom: 0px;
}
.tokenfield.focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.tokenfield .token {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
display: inline-block;
border: 1px solid #d9d9d9;
background-color: #ededed;
white-space: nowrap;
margin: -1px 5px 5px 0;
height: 22px;
vertical-align: top;
cursor: default;
}
.tokenfield .token:hover {
border-color: #b9b9b9;
}
.tokenfield .token.active {
border-color: #52a8ec;
border-color: rgba(82, 168, 236, 0.8);
}
.tokenfield .token.duplicate {
border-color: #ebccd1;
-webkit-animation-name: blink;
animation-name: blink;
-webkit-animation-duration: 0.1s;
animation-duration: 0.1s;
-webkit-animation-direction: normal;
animation-direction: normal;
-webkit-animation-timing-function: ease;
animation-timing-function: ease;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.tokenfield .token.invalid {
background: none;
border: 1px solid transparent;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
border-bottom: 1px dotted #d9534f;
}
.tokenfield .token.invalid.active {
background: #ededed;
border: 1px solid #ededed;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.tokenfield .token .token-label {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 4px;
vertical-align: top;
}
.tokenfield .token .close {
font-family: Arial;
display: inline-block;
line-height: 100%;
font-size: 1.1em;
line-height: 1.49em;
margin-left: 5px;
float: none;
height: 100%;
vertical-align: top;
padding-right: 4px;
}
.tokenfield .token-input {
background: none;
width: 60px;
min-width: 60px;
border: 0;
height: 20px;
padding: 0;
margin-bottom: 6px;
-webkit-box-shadow: none;
box-shadow: none;
}
.tokenfield .token-input:focus {
border-color: transparent;
outline: 0;
/* IE6-9 */
-webkit-box-shadow: none;
box-shadow: none;
}
.tokenfield.disabled {
cursor: not-allowed;
background-color: #eeeeee;
}
.tokenfield.disabled .token-input {
cursor: not-allowed;
}
.tokenfield.disabled .token:hover {
cursor: not-allowed;
border-color: #d9d9d9;
}
.tokenfield.disabled .token:hover .close {
cursor: not-allowed;
opacity: 0.2;
filter: alpha(opacity=20);
}
.has-warning .tokenfield.focus {
border-color: #66512c;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
}
.has-error .tokenfield.focus {
border-color: #843534;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
}
.has-success .tokenfield.focus {
border-color: #2b542c;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
}
.tokenfield.input-sm,
.input-group-sm .tokenfield {
min-height: 30px;
padding-bottom: 0px;
}
.input-group-sm .token,
.tokenfield.input-sm .token {
height: 20px;
margin-bottom: 4px;
}
.input-group-sm .token-input,
.tokenfield.input-sm .token-input {
height: 18px;
margin-bottom: 5px;
}
.tokenfield.input-lg,
.input-group-lg .tokenfield {
height: auto;
min-height: 45px;
padding-bottom: 4px;
}
.input-group-lg .token,
.tokenfield.input-lg .token {
height: 25px;
}
.input-group-lg .token-label,
.tokenfield.input-lg .token-label {
line-height: 23px;
}
.input-group-lg .token .close,
.tokenfield.input-lg .token .close {
line-height: 1.3em;
}
.input-group-lg .token-input,
.tokenfield.input-lg .token-input {
height: 23px;
line-height: 23px;
margin-bottom: 6px;
vertical-align: top;
}
.tokenfield.rtl {
direction: rtl;
text-align: right;
}
.tokenfield.rtl .token {
margin: -1px 0 5px 5px;
}
.tokenfield.rtl .token .token-label {
padding-left: 0px;
padding-right: 4px;
}

View file

@ -0,0 +1,23 @@
#### Sliptree
- by Illimar Tambek for [Sliptree](http://sliptree.com)
- Copyright (c) 2013 by Sliptree
Available for use under the [MIT License](http://en.wikipedia.org/wiki/MIT_License)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -33,12 +33,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{# Load CSS and JavaScript #}
{% bootstrap_css %}
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
<link href="/static/css/bootstrap-tokenfield.css" rel="stylesheet">
{% comment %}<link href="/static/css/jquery-ui.css" rel="stylesheet">{% endcomment %}
{% bootstrap_javascript %}
<script src="/static/js/typeahead/typeahead.js"></script>
<script src="/static/js/handlebars/handlebars.js"></script>
<script src="/static/js/konami/konami.js"></script>
<script src="/static/js/sapphire.js"> var s=Sapphire(); Konami(s.activate); </script>
<script src="/static/js/bootstrap-tokenfield/bootstrap-tokenfield.js"></script>
{% comment %}<script src="/static/js/jquery-ui.js"></script>{% endcomment %}
<link rel="stylesheet" href="{% static "/css/base.css" %}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>

View file

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification d'un switch{% endblock %}
@ -47,16 +47,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% if topoform %}
{% bootstrap_form_typeahead topoform 'switch_interface' %}
{% massive_bootstrap_form topoform 'switch_interface' %}
{% endif %}
{% if machineform %}
{% bootstrap_form_typeahead machineform 'user' %}
{% massive_bootstrap_form machineform 'user' %}
{% endif %}
{% if interfaceform %}
{% if i_bft_param %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' mbf_param=i_bft_param %}
{% else %}
{% bootstrap_form_typeahead interfaceform 'ipv4,machine' %}
{% massive_bootstrap_form interfaceform 'ipv4,machine' %}
{% endif %}
{% endif %}
{% if domainform %}

View file

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modificationd 'utilisateur{% endblock %}
@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form_typeahead topoform 'room,related,machine_interface' %}
{% massive_bootstrap_form topoform 'room,related,machine_interface' %}
{%bootstrap_button "Créer ou modifier" button_type="submit" icon="ok" %}
</form>
<br />

View file

@ -50,9 +50,8 @@ from topologie.forms import EditPortForm, NewSwitchForm, EditSwitchForm
from topologie.forms import AddPortForm, EditRoomForm, StackForm
from users.views import form
from machines.forms import AliasForm, NewMachineForm, EditMachineForm
from machines.forms import EditInterfaceForm, AddInterfaceForm
from machines.views import generate_ipv4_bft_param
from machines.forms import AliasForm, NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm
from machines.views import generate_ipv4_mbf_param
from preferences.models import AssoOption, GeneralOption
@ -382,6 +381,10 @@ def new_switch(request):
reversion.set_comment("Création")
messages.success(request, "Le switch a été créé")
return redirect("/topologie/")
<<<<<<< HEAD
i_bft_param = generate_ipv4_mbf_param( interface, False )
return form({'topoform':switch, 'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': i_bft_param}, 'topologie/switch.html', request)
=======
i_bft_param = generate_ipv4_bft_param(interface, False)
return form({
'topoform': switch,
@ -391,6 +394,7 @@ def new_switch(request):
'i_bft_param': i_bft_param
}, 'topologie/switch.html', request)
>>>>>>> master
@login_required
@permission_required('infra')
@ -450,6 +454,10 @@ def edit_switch(request, switch_id):
)
messages.success(request, "Le switch a bien été modifié")
return redirect("/topologie/")
<<<<<<< HEAD
i_bft_param = generate_ipv4_mbf_param( interface_form, False )
return form({'topoform':switch_form, 'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': i_bft_param}, 'topologie/switch.html', request)
=======
i_bft_param = generate_ipv4_bft_param(interface_form, False)
return form({
'topoform': switch_form,
@ -459,6 +467,7 @@ def edit_switch(request, switch_id):
'i_bft_param': i_bft_param
}, 'topologie/switch.html', request)
>>>>>>> master
@login_required
@permission_required('infra')

View file

@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% load bootstrap3 %}
{% load bootstrap_form_typeahead %}
{% load massive_bootstrap_form %}
{% block title %}Création et modification d'utilisateur{% endblock %}
@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form_typeahead userform 'room' %}
{% massive_bootstrap_form userform 'room' %}
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
</form>
<br />