mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-10-31 23:22:49 +00:00
Templatag pour générer des form avec typeahead
Utilise les form django et la génération de bootstrap
This commit is contained in:
parent
f2a4f83710
commit
24a39e80bb
8 changed files with 7591 additions and 42 deletions
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
{% load bootstrap_form_typeahead %}
|
||||||
|
|
||||||
{% block title %}Création et modification de machines{% endblock %}
|
{% block title %}Création et modification de machines{% endblock %}
|
||||||
|
|
||||||
|
@ -38,39 +39,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% bootstrap_form_errors domainform %}
|
{% bootstrap_form_errors domainform %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if type_to_ipv4 %}
|
|
||||||
<script>
|
|
||||||
var type_to_ipv4 = {
|
|
||||||
type: [{% for type, iplist in type_to_ipv4.items %}"{{ type }}",{% endfor %}],
|
|
||||||
iplist: [{% for type, iplist in type_to_ipv4.items %}
|
|
||||||
[ {{ iplist | safe }} ], {% endfor %}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
$(document).ready(function() {
|
|
||||||
$("#id_ipv4").html("<option value='' selected='selected'>Choisisez un type de machine d'abord</options>");
|
|
||||||
$("#id_type").change(function() {
|
|
||||||
var val = $(this).val();
|
|
||||||
var options = "<option value='' selected='selected'>Assignation automatique de l'ipv4</option>";
|
|
||||||
for (var i = 0; i < type_to_ipv4.type.length; i++) {
|
|
||||||
if (type_to_ipv4.type[i] == val) {
|
|
||||||
for (var j = 0; j < type_to_ipv4.iplist[i].length; j++) {
|
|
||||||
options += "<option value='"+type_to_ipv4.iplist[i][j].id+"'>"+type_to_ipv4.iplist[i][j].ip+"</option>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$("#id_ipv4").html(options)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if machineform %}
|
{% if machineform %}
|
||||||
{% bootstrap_form machineform %}
|
{% bootstrap_form machineform %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interfaceform %}
|
{% if interfaceform %}
|
||||||
{% bootstrap_form interfaceform %}
|
{% bootstrap_form_typeahead interfaceform 'ipv4,type' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if domainform %}
|
{% if domainform %}
|
||||||
{% bootstrap_form domainform %}
|
{% bootstrap_form domainform %}
|
||||||
|
|
20
machines/templatetags/__init__.py
Normal file
20
machines/templatetags/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# 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.
|
||||||
|
|
179
machines/templatetags/bootstrap_form_typeahead.py
Normal file
179
machines/templatetags/bootstrap_form_typeahead.py
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
# 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 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.
|
||||||
|
|
||||||
|
See boostrap_form_ for other arguments
|
||||||
|
|
||||||
|
**Usage**::
|
||||||
|
|
||||||
|
{% bootstrap_form_typeahead form ['field1[,field2[,...]]] %}
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
{% bootstrap_form_typeahead form 'ipv4' %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
t_fields = typeahead_fields.split(',')
|
||||||
|
exclude = kwargs.get('exclude', None)
|
||||||
|
exclude = exclude.split(',') if exclude else []
|
||||||
|
|
||||||
|
form = ''
|
||||||
|
for f_name, f_value in django_form.fields.items() :
|
||||||
|
if not f_name in exclude :
|
||||||
|
if f_name in t_fields :
|
||||||
|
form += render_tag(
|
||||||
|
'div',
|
||||||
|
attrs = {'class': 'form-group'},
|
||||||
|
content = label_tag( f_name, f_value ) +
|
||||||
|
input_tag( f_name, f_value ) +
|
||||||
|
hidden_tag( f_name ) +
|
||||||
|
typeahead_full_script( f_name, f_value )
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form += render_field(
|
||||||
|
f_value.get_bound_field(django_form, f_name),
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return mark_safe( form )
|
||||||
|
|
||||||
|
def input_id( f_name ):
|
||||||
|
return 'typeahead_input_'+f_name
|
||||||
|
|
||||||
|
def select_id( f_name ):
|
||||||
|
return 'typeahead_select_'+f_name
|
||||||
|
|
||||||
|
def hidden_tag( f_name ):
|
||||||
|
return render_tag(
|
||||||
|
'input',
|
||||||
|
attrs={
|
||||||
|
'id': select_id(f_name),
|
||||||
|
'name': f_name,
|
||||||
|
'type': 'hidden',
|
||||||
|
'value': ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def label_tag( f_name, f_value ):
|
||||||
|
return render_tag(
|
||||||
|
'label',
|
||||||
|
attrs={
|
||||||
|
'class': 'control-label',
|
||||||
|
'for': input_id(f_name)
|
||||||
|
},
|
||||||
|
content=f_value.label
|
||||||
|
)
|
||||||
|
|
||||||
|
def input_tag( f_name, f_value ):
|
||||||
|
return render_tag(
|
||||||
|
'input',
|
||||||
|
attrs={
|
||||||
|
'class': 'form-control',
|
||||||
|
'id': input_id(f_name),
|
||||||
|
'type': 'text',
|
||||||
|
'placeholder': f_value.empty_label
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def typeahead_full_script( f_name, f_value ) :
|
||||||
|
js_content = \
|
||||||
|
'$("#'+input_id(f_name)+'").ready( function() {\n' + \
|
||||||
|
typeahead_choices( f_value ) + '\n' + \
|
||||||
|
typeahead_engine () + '\n' + \
|
||||||
|
'$("#'+input_id(f_name) + '").typeahead(\n' + \
|
||||||
|
typeahead_datasets( f_name ) + \
|
||||||
|
').bind(\n' + \
|
||||||
|
'"typeahead:select", ' + \
|
||||||
|
typeahead_updater( f_name ) + '\n' + \
|
||||||
|
')\n' + \
|
||||||
|
'});\n'
|
||||||
|
|
||||||
|
return render_tag( 'script', content=mark_safe( js_content ) )
|
||||||
|
|
||||||
|
def typeahead_choices( f_value ) :
|
||||||
|
return 'var choices = [' + \
|
||||||
|
', '.join([ \
|
||||||
|
'{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \
|
||||||
|
', value: "' + str(choice[1]) + '"}' \
|
||||||
|
for choice in f_value.choices \
|
||||||
|
]) + \
|
||||||
|
'];'
|
||||||
|
|
||||||
|
def typeahead_engine () :
|
||||||
|
return 'var choices = new Bloodhound({ ' \
|
||||||
|
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \
|
||||||
|
'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \
|
||||||
|
'local: choices, ' \
|
||||||
|
'identify: function(obj) { return obj.value; } ' \
|
||||||
|
'});'
|
||||||
|
|
||||||
|
def typeahead_datasets( f_name ) :
|
||||||
|
return '{ ' \
|
||||||
|
'hint: true, ' \
|
||||||
|
'highlight: true, ' \
|
||||||
|
'minLength: 1 ' \
|
||||||
|
'}, ' \
|
||||||
|
'{ ' \
|
||||||
|
'templates: { ' \
|
||||||
|
'suggestion: Handlebars.compile("<div>{{value}}</div>") ' \
|
||||||
|
'}, ' \
|
||||||
|
'display: "value", ' \
|
||||||
|
'name: "'+f_name+'", ' \
|
||||||
|
'source: choices ' \
|
||||||
|
'}'
|
||||||
|
|
||||||
|
def typeahead_updater( f_name ):
|
||||||
|
return 'function(evt, item) { ' \
|
||||||
|
'$("#'+select_id(f_name)+'").val( item.key ); ' \
|
||||||
|
'return item; ' \
|
||||||
|
'}'
|
||||||
|
|
|
@ -117,11 +117,7 @@ def new_machine(request, userid):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "La machine a été créée")
|
messages.success(request, "La machine a été créée")
|
||||||
return redirect("/users/profil/" + str(user.id))
|
return redirect("/users/profil/" + str(user.id))
|
||||||
type_to_ipv4 = {}
|
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request)
|
||||||
for t in interface.fields['type'].queryset :
|
|
||||||
iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type)
|
|
||||||
type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist])
|
|
||||||
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_interface(request, interfaceid):
|
def edit_interface(request, interfaceid):
|
||||||
|
@ -158,11 +154,7 @@ def edit_interface(request, interfaceid):
|
||||||
reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data))
|
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")
|
messages.success(request, "La machine a été modifiée")
|
||||||
return redirect("/users/profil/" + str(interface.machine.user.id))
|
return redirect("/users/profil/" + str(interface.machine.user.id))
|
||||||
type_to_ipv4 = {}
|
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request)
|
||||||
for t in interface_form.fields['type'].queryset :
|
|
||||||
iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type)
|
|
||||||
type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist])
|
|
||||||
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def del_machine(request, machineid):
|
def del_machine(request, machineid):
|
||||||
|
@ -218,11 +210,7 @@ def new_interface(request, machineid):
|
||||||
reversion.set_comment("Création")
|
reversion.set_comment("Création")
|
||||||
messages.success(request, "L'interface a été ajoutée")
|
messages.success(request, "L'interface a été ajoutée")
|
||||||
return redirect("/users/profil/" + str(machine.user.id))
|
return redirect("/users/profil/" + str(machine.user.id))
|
||||||
type_to_ipv4 = {}
|
return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request)
|
||||||
for t in interface_form.fields['type'].queryset :
|
|
||||||
iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type)
|
|
||||||
type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist])
|
|
||||||
return form({'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def del_interface(request, interfaceid):
|
def del_interface(request, interfaceid):
|
||||||
|
|
93
static/css/typeaheadjs.css
Normal file
93
static/css/typeaheadjs.css
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
span.twitter-typeahead .tt-menu,
|
||||||
|
span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
float: left;
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 2px 0 0;
|
||||||
|
list-style: none;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
span.twitter-typeahead .tt-suggestion {
|
||||||
|
display: block;
|
||||||
|
padding: 3px 20px;
|
||||||
|
clear: both;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
color: #333333;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
span.twitter-typeahead .tt-suggestion.tt-cursor,
|
||||||
|
span.twitter-typeahead .tt-suggestion:hover,
|
||||||
|
span.twitter-typeahead .tt-suggestion:focus {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: 0;
|
||||||
|
background-color: #337ab7;
|
||||||
|
}
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead .form-control {
|
||||||
|
height: 46px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.3333333;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead .form-control {
|
||||||
|
height: 30px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
span.twitter-typeahead {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead {
|
||||||
|
display: block !important;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 32px !important;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead:first-child .form-control {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
.input-group span.twitter-typeahead:last-child .form-control {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 30px !important;
|
||||||
|
}
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead {
|
||||||
|
height: 46px;
|
||||||
|
}
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
|
||||||
|
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
|
||||||
|
top: 46px !important;
|
||||||
|
}
|
4840
static/js/handlebars.js
Normal file
4840
static/js/handlebars.js
Normal file
File diff suppressed because one or more lines are too long
2451
static/js/typeahead.js
Normal file
2451
static/js/typeahead.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -32,8 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<head>
|
<head>
|
||||||
{# Load CSS and JavaScript #}
|
{# Load CSS and JavaScript #}
|
||||||
{% bootstrap_css %}
|
{% bootstrap_css %}
|
||||||
|
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
|
||||||
|
|
||||||
{% bootstrap_javascript %}
|
{% bootstrap_javascript %}
|
||||||
|
<script src="/static/js/typeahead.js"></script>
|
||||||
|
<script src="/static/js/handlebars.js"></script>
|
||||||
<link rel="stylesheet" href="{% static "/css/base.css" %}">
|
<link rel="stylesheet" href="{% static "/css/base.css" %}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
|
<title>{{ site_name }} : {% block title %}Accueil{% endblock %}</title>
|
||||||
|
|
Loading…
Reference in a new issue