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:
commit
6ef055a7bc
14 changed files with 1898 additions and 424 deletions
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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 )
|
||||
)
|
||||
|
572
re2o/templatetags/massive_bootstrap_form.py
Normal file
572
re2o/templatetags/massive_bootstrap_form.py
Normal 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
210
static/css/bootstrap-tokenfield.css
vendored
Normal 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;
|
||||
}
|
23
static/js/bootstrap-tokenfield/LICENSE.md
Normal file
23
static/js/bootstrap-tokenfield/LICENSE.md
Normal 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.
|
1042
static/js/bootstrap-tokenfield/bootstrap-tokenfield.js
vendored
Normal file
1042
static/js/bootstrap-tokenfield/bootstrap-tokenfield.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 />
|
||||
|
|
Loading…
Reference in a new issue