mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 23:43:47 +00:00
Support de typeahead pour les select multiples avec tokenfield
This commit is contained in:
parent
db30643c13
commit
7d8d6d85fe
6 changed files with 1499 additions and 71 deletions
|
@ -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>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
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
|
||||
|
@ -165,10 +166,24 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
|||
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,
|
||||
|
@ -180,19 +195,35 @@ def massive_bootstrap_form(form, mbf_fields, *args, **kwargs):
|
|||
*args,
|
||||
**kwargs
|
||||
)
|
||||
html += render_tag(
|
||||
'div',
|
||||
content = hidden_tag( f_bound, f_name ) +
|
||||
mbf_js(
|
||||
|
||||
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 ),
|
||||
|
@ -210,6 +241,10 @@ 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(
|
||||
|
@ -222,21 +257,61 @@ def hidden_tag( f_bound, f_name ):
|
|||
}
|
||||
)
|
||||
|
||||
def mbf_js( f_name, f_value, f_bound,
|
||||
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 )
|
||||
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 )
|
||||
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 )
|
||||
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_bound ),
|
||||
remove = tokenfield_remove( 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};'
|
||||
|
@ -245,7 +320,7 @@ def mbf_js( f_name, f_value, f_bound,
|
|||
'$( "#{input_id}" ).typeahead( "destroy" );'
|
||||
'$( "#{input_id}" ).typeahead( {datasets} );'
|
||||
'}};'
|
||||
'$( "#{input_id}" ).bind( "typeahead:select", {updater} );'
|
||||
'$( "#{input_id}" ).bind( "typeahead:select", {select} );'
|
||||
'$( "#{input_id}" ).bind( "typeahead:change", {change} );'
|
||||
'{updates}'
|
||||
'$( "#{input_id}" ).ready( function() {{'
|
||||
|
@ -258,7 +333,7 @@ def mbf_js( f_name, f_value, f_bound,
|
|||
engine = engine,
|
||||
input_id = input_id( f_bound ),
|
||||
datasets = default_datasets( f_name, match_func ),
|
||||
updater = typeahead_updater( f_bound ),
|
||||
select = typeahead_select( f_bound ),
|
||||
change = typeahead_change( f_bound ),
|
||||
updates = ''.join( [ (
|
||||
'$( "#{u_id}" ).change( function() {{'
|
||||
|
@ -267,16 +342,16 @@ def mbf_js( f_name, f_value, f_bound,
|
|||
'}} );'
|
||||
).format(
|
||||
u_id = u_id,
|
||||
reset_input = reset_input( f_bound ),
|
||||
reset_input = typeahead_reset_input( f_bound ),
|
||||
f_name = f_name
|
||||
) for u_id in update_on ]
|
||||
),
|
||||
init_input = init_input( f_name, f_bound ),
|
||||
init_input = typeahead_init_input( f_name, f_bound ),
|
||||
)
|
||||
|
||||
return render_tag( 'script', content=mark_safe( js_content ) )
|
||||
|
||||
def init_input( f_name, f_bound ) :
|
||||
def typeahead_init_input( f_name, f_bound ) :
|
||||
""" The JS script to init the fields values """
|
||||
init_key = f_bound.value() or '""'
|
||||
return (
|
||||
|
@ -293,7 +368,7 @@ def init_input( f_name, f_bound ) :
|
|||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def reset_input( f_bound ) :
|
||||
def typeahead_reset_input( f_bound ) :
|
||||
""" The JS script to reset the fields values """
|
||||
return (
|
||||
'$( "#{input_id}" ).typeahead("val", "");'
|
||||
|
@ -303,6 +378,31 @@ def reset_input( 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(
|
||||
|
@ -362,7 +462,7 @@ def default_match_func ( f_name ) :
|
|||
f_name = f_name
|
||||
)
|
||||
|
||||
def typeahead_updater( f_bound ):
|
||||
def typeahead_select( f_bound ):
|
||||
""" The JS script creating the function triggered when an item is
|
||||
selected through typeahead """
|
||||
return (
|
||||
|
@ -391,3 +491,52 @@ def typeahead_change( 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 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; }}'
|
||||
'var new_input = document.createElement("input");'
|
||||
'new_input.type = "hidden";'
|
||||
'new_input.id = "{hidden_id}_"+data;'
|
||||
'new_input.value = choices_{f_name}[i].key.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_bound ):
|
||||
""" The JS script triggered when a token is edited in tokenfield. """
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'var data = evt.attrs.value;'
|
||||
'var old_input = document.getElementById( "{hidden_id}_"+data );'
|
||||
'old_input.parentNode.removeChild(old_input);'
|
||||
'}}'
|
||||
).format(
|
||||
hidden_id = hidden_id( f_bound )
|
||||
)
|
||||
|
||||
def tokenfield_remove( f_bound ):
|
||||
""" The JS script trigggered when a token is removed from tokenfield. """
|
||||
return (
|
||||
'function(evt) {{'
|
||||
'var data = evt.attrs.value;'
|
||||
'var old_input = document.getElementById( "{hidden_id}_"+data );'
|
||||
'old_input.parentNode.removeChild(old_input);'
|
||||
'}}'
|
||||
).format(
|
||||
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>
|
||||
|
|
Loading…
Reference in a new issue