mirror of https://gitlab.federez.net/re2o/re2o
committed by
Pierre Cadart
8 changed files with 7591 additions and 42 deletions
@ -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. |
||||
|
|
||||
@ -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; ' \ |
||||
|
'}' |
||||
|
|
||||
@ -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; |
||||
|
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Loading…
Reference in new issue