mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 22:52:26 +00:00
Merge branch 'faster_ipform' into 'master'
Faster ipform See merge request rezo/re2o!13
This commit is contained in:
commit
7fd3750a33
12 changed files with 7857 additions and 21 deletions
|
@ -1,3 +1,4 @@
|
|||
# -*- 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.
|
||||
|
@ -5,6 +6,7 @@
|
|||
# Copyright © 2017 Gabriel Détraz
|
||||
# Copyright © 2017 Goulven Kermarec
|
||||
# Copyright © 2017 Augustin Lemesle
|
||||
# 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
|
||||
|
@ -27,7 +29,7 @@ import re
|
|||
from django.forms import ModelForm, Form, ValidationError
|
||||
from django import forms
|
||||
from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Text, Ns, Service, Vlan, Nas, IpType, OuverturePortList, OuverturePort
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, F
|
||||
from django.core.validators import validate_email
|
||||
|
||||
from users.models import User
|
||||
|
@ -52,8 +54,7 @@ class BaseEditMachineForm(EditMachineForm):
|
|||
class EditInterfaceForm(ModelForm):
|
||||
class Meta:
|
||||
model = Interface
|
||||
# fields = '__all__'
|
||||
exclude = ['port_lists']
|
||||
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditInterfaceForm, self).__init__(*args, **kwargs)
|
||||
|
@ -62,13 +63,15 @@ class EditInterfaceForm(ModelForm):
|
|||
self.fields['type'].empty_label = "Séléctionner un type de machine"
|
||||
if "ipv4" in self.fields:
|
||||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
# Add it's own address
|
||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
if "machine" in self.fields:
|
||||
self.fields['machine'].queryset = Machine.objects.all().select_related('user')
|
||||
|
||||
class AddInterfaceForm(EditInterfaceForm):
|
||||
class Meta(EditInterfaceForm.Meta):
|
||||
fields = ['ipv4','mac_address','type','details']
|
||||
fields = ['type','ipv4','mac_address','details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
infra = kwargs.pop('infra')
|
||||
|
@ -76,17 +79,17 @@ class AddInterfaceForm(EditInterfaceForm):
|
|||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
||||
if not infra:
|
||||
self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
else:
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
|
||||
class NewInterfaceForm(EditInterfaceForm):
|
||||
class Meta(EditInterfaceForm.Meta):
|
||||
fields = ['mac_address','type','details']
|
||||
fields = ['type','mac_address','details']
|
||||
|
||||
class BaseEditInterfaceForm(EditInterfaceForm):
|
||||
class Meta(EditInterfaceForm.Meta):
|
||||
fields = ['ipv4','mac_address','type','details']
|
||||
fields = ['type','ipv4','mac_address','details']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
infra = kwargs.pop('infra')
|
||||
|
@ -94,9 +97,12 @@ class BaseEditInterfaceForm(EditInterfaceForm):
|
|||
self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4"
|
||||
if not infra:
|
||||
self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False))
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
# Add it's own address
|
||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
else:
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True)
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id'))
|
||||
|
||||
class AliasForm(ModelForm):
|
||||
class Meta:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- 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.
|
||||
|
|
|
@ -7,6 +7,7 @@ quelques clics.
|
|||
Copyright © 2017 Gabriel Détraz
|
||||
Copyright © 2017 Goulven Kermarec
|
||||
Copyright © 2017 Augustin Lemesle
|
||||
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
|
||||
|
@ -24,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load bootstrap_form_typeahead %}
|
||||
|
||||
{% block title %}Création et modification de machines{% endblock %}
|
||||
|
||||
|
@ -38,16 +40,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% bootstrap_form_errors domainform %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% if machineform %}
|
||||
<h3>Machine</h3>
|
||||
{% bootstrap_form machineform %}
|
||||
{% endif %}
|
||||
{% if interfaceform %}
|
||||
{% bootstrap_form interfaceform %}
|
||||
<h3>Interface</h3>
|
||||
{% if i_bft_param %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %}
|
||||
{% else %}
|
||||
{% bootstrap_form_typeahead interfaceform 'ipv4' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if domainform %}
|
||||
<h3>Domaine</h3>
|
||||
{% bootstrap_form domainform %}
|
||||
{% endif %}
|
||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||
|
|
21
machines/templatetags/__init__.py
Normal file
21
machines/templatetags/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- 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.
|
||||
|
330
machines/templatetags/bootstrap_form_typeahead.py
Normal file
330
machines/templatetags/bootstrap_form_typeahead.py
Normal file
|
@ -0,0 +1,330 @@
|
|||
# -*- 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_name ) :
|
||||
""" The id of the HTML input element """
|
||||
return 'id_'+f_name
|
||||
|
||||
def hidden_id( f_name ):
|
||||
""" The id of the HTML hidden input element """
|
||||
return 'typeahead_hidden_'+f_name
|
||||
|
||||
def hidden_tag( f_bound, f_name ):
|
||||
""" The HTML hidden input element """
|
||||
return render_tag(
|
||||
'input',
|
||||
attrs={
|
||||
'id': hidden_id(f_name),
|
||||
'name': f_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 + ';\n' + \
|
||||
'var setup_'+f_name+' = function() {\n' + \
|
||||
'var engine_'+f_name+' = ' + engine + ';\n' + \
|
||||
'$("#'+input_id(f_name) + '").typeahead("destroy");\n' + \
|
||||
'$("#'+input_id(f_name) + '").typeahead(\n' + \
|
||||
default_datasets( f_name, match_func ) + '\n' + \
|
||||
');\n' + \
|
||||
reset_input( f_name, f_bound ) + '\n' + \
|
||||
'};\n' + \
|
||||
'$("#'+input_id(f_name) + '").bind(\n' + \
|
||||
'"typeahead:select", ' + \
|
||||
typeahead_updater( f_name ) + '\n' + \
|
||||
').bind(\n' + \
|
||||
'"typeahead:change", ' + \
|
||||
typeahead_change( f_name ) + '\n' + \
|
||||
');\n'
|
||||
for u_id in update_on :
|
||||
js_content += '$("#'+u_id+'").change( setup_'+f_name+' );\n'
|
||||
js_content += '$("#'+input_id(f_name)+'").ready( setup_'+f_name+' );\n'
|
||||
|
||||
return render_tag( 'script', content=mark_safe( js_content ) )
|
||||
|
||||
def reset_input( f_name, f_bound ) :
|
||||
""" The JS script to reset the fields values """
|
||||
return '$("#'+input_id(f_name)+'").typeahead(' \
|
||||
'"val", ' \
|
||||
'engine_'+f_name+'.get('+str(f_bound.value())+')[0].value' \
|
||||
');\n' \
|
||||
'$("#'+hidden_id(f_name)+'").val('+str(f_bound.value())+');'
|
||||
|
||||
def default_choices( f_value ) :
|
||||
""" The JS script creating the variable choices_<fieldname> """
|
||||
return '[' + \
|
||||
', '.join([ \
|
||||
'{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \
|
||||
', value: "' + str(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; } ' \
|
||||
'})'
|
||||
|
||||
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 + \
|
||||
'}'
|
||||
|
||||
def default_match_func ( f_name ) :
|
||||
""" The JS script creating the matching function to use with typeahed """
|
||||
return 'function(q, sync) {' \
|
||||
'if (q === "") {' \
|
||||
'var nb = 10;' \
|
||||
'var first = [] ;' \
|
||||
'for ( var i=0 ; i<nb && i<choices_'+f_name+'.length; i++ ) {' \
|
||||
'first.push(choices_'+f_name+'[i].key);' \
|
||||
'}' \
|
||||
'sync(engine_'+f_name+'.get(first));' \
|
||||
'} else {' \
|
||||
'engine_'+f_name+'.search(q, sync);' \
|
||||
'}' \
|
||||
'}'
|
||||
|
||||
def typeahead_updater( f_name ):
|
||||
""" The JS script creating the function triggered when an item is
|
||||
selected through typeahead """
|
||||
return 'function(evt, item) { ' \
|
||||
'$("#'+hidden_id(f_name)+'").val( item.key ); ' \
|
||||
'$("#'+hidden_id(f_name)+'").change();' \
|
||||
'return item; ' \
|
||||
'}'
|
||||
|
||||
def typeahead_change( f_name ):
|
||||
""" 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(f_name)+'").typeahead("val") === "") {' \
|
||||
'$("#'+hidden_id(f_name)+'").val(""); ' \
|
||||
'$("#'+hidden_id(f_name)+'").change();' \
|
||||
'}' \
|
||||
'}'
|
|
@ -1,3 +1,4 @@
|
|||
# -*- 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- 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.
|
||||
|
@ -5,6 +6,7 @@
|
|||
# Copyright © 2017 Gabriel Détraz
|
||||
# Copyright © 2017 Goulven Kermarec
|
||||
# Copyright © 2017 Augustin Lemesle
|
||||
# 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
|
||||
|
@ -53,6 +55,7 @@ from .models import IpType, Machine, Interface, IpList, MachineType, Extension,
|
|||
from users.models import User
|
||||
from users.models import all_has_access
|
||||
from preferences.models import GeneralOption, OptionalMachine
|
||||
from .templatetags.bootstrap_form_typeahead import hidden_id, input_id
|
||||
|
||||
def all_active_interfaces():
|
||||
"""Renvoie l'ensemble des machines autorisées à sortir sur internet """
|
||||
|
@ -75,6 +78,81 @@ def form(ctx, template, request):
|
|||
c.update(csrf(request))
|
||||
return render(request, template, c)
|
||||
|
||||
def f_type_id( is_type_tt ):
|
||||
""" The id that will be used in HTML to store the value of the field
|
||||
type. Depends on the fact that type is generate using typeahead or not
|
||||
"""
|
||||
return hidden_id('type') if is_type_tt else input_id('type')
|
||||
|
||||
def generate_ipv4_choices( form ) :
|
||||
""" Generate the parameter choices for the bootstrap_form_typeahead tag
|
||||
"""
|
||||
f_ipv4 = form.fields['ipv4']
|
||||
used_mtype_id = []
|
||||
choices = '{ "": [{key: "", value: "Choisissez d\'abord un type de machine"},'
|
||||
mtype_id = -1
|
||||
|
||||
for ip in f_ipv4.queryset.order_by('mtype_id', 'id') :
|
||||
if mtype_id != ip.mtype_id :
|
||||
mtype_id = ip.mtype_id
|
||||
used_mtype_id.append(mtype_id)
|
||||
choices += '], "'+str(mtype_id)+'": ['
|
||||
choices += '{key: "", value: "' + str(f_ipv4.empty_label) + '"},'
|
||||
choices += '{key: ' + str(ip.id) + ', value: "' + str(ip.ipv4) + '"},'
|
||||
|
||||
for t in form.fields['type'].queryset.exclude(id__in=used_mtype_id) :
|
||||
choices += '], "'+str(t.id)+'": ['
|
||||
choices += '{key: "", value: "' + str(f_ipv4.empty_label) + '"},'
|
||||
choices += ']}'
|
||||
return choices
|
||||
|
||||
def generate_ipv4_engine( is_type_tt ) :
|
||||
""" Generate the parameter engine for the bootstrap_form_typeahead tag
|
||||
"""
|
||||
return 'new Bloodhound({ ' \
|
||||
'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \
|
||||
'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \
|
||||
'local: choices_ipv4[$("#'+f_type_id(is_type_tt)+'").val()], ' \
|
||||
'identify: function(obj) { return obj.key; } ' \
|
||||
'})'
|
||||
|
||||
def generate_ipv4_match_func( is_type_tt ) :
|
||||
""" Generate the parameter match_func for the bootstrap_form_typeahead tag
|
||||
"""
|
||||
return 'function(q, sync) {' \
|
||||
'if (q === "") {' \
|
||||
'var nb = 10;' \
|
||||
'var first = [] ;' \
|
||||
'for(' \
|
||||
'var i=0 ;' \
|
||||
'i<nb && i<choices_ipv4[' \
|
||||
'$("#'+f_type_id(is_type_tt)+'").val()' \
|
||||
'].length ;' \
|
||||
'i++' \
|
||||
') { first.push(' \
|
||||
'choices_ipv4[$("#'+f_type_id(is_type_tt)+'").val()][i].key' \
|
||||
'); }' \
|
||||
'sync(engine_ipv4.get(first));' \
|
||||
'} else {' \
|
||||
'engine_ipv4.search(q, sync);' \
|
||||
'}' \
|
||||
'}'
|
||||
|
||||
def generate_ipv4_bft_param( form, is_type_tt ):
|
||||
""" Generate all the parameters to use with the bootstrap_form_typeahead
|
||||
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 = {
|
||||
'choices': i_choices,
|
||||
'engine': i_engine,
|
||||
'match_func': i_match_func,
|
||||
'update_on': i_update_on
|
||||
}
|
||||
return i_bft_param
|
||||
|
||||
@login_required
|
||||
def new_machine(request, userid):
|
||||
try:
|
||||
|
@ -92,7 +170,7 @@ def new_machine(request, userid):
|
|||
messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces)
|
||||
return redirect("/users/profil/" + str(request.user.id))
|
||||
machine = NewMachineForm(request.POST or None)
|
||||
interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
||||
interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',)))
|
||||
nb_machine = Interface.objects.filter(machine__user=userid).count()
|
||||
domain = DomainForm(request.POST or None, user=user, nb_machine=nb_machine)
|
||||
if machine.is_valid() and interface.is_valid():
|
||||
|
@ -118,7 +196,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))
|
||||
return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request)
|
||||
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)
|
||||
|
||||
@login_required
|
||||
def edit_interface(request, interfaceid):
|
||||
|
@ -155,7 +234,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))
|
||||
return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request)
|
||||
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)
|
||||
|
||||
@login_required
|
||||
def del_machine(request, machineid):
|
||||
|
@ -211,7 +291,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))
|
||||
return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request)
|
||||
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)
|
||||
|
||||
@login_required
|
||||
def del_interface(request, interfaceid):
|
||||
|
@ -869,7 +950,7 @@ def history(request, object, id):
|
|||
object_instance = Text.objects.get(pk=id)
|
||||
except Text.DoesNotExist:
|
||||
messages.error(request, "Text inexistant")
|
||||
return redirect("/machines/")
|
||||
return redirect("/machines/")
|
||||
elif object == 'ns' and request.user.has_perms(('cableur',)):
|
||||
try:
|
||||
object_instance = Ns.objects.get(pk=id)
|
||||
|
@ -916,7 +997,7 @@ def history(request, object, id):
|
|||
@login_required
|
||||
@permission_required('cableur')
|
||||
def index_portlist(request):
|
||||
port_list = OuverturePortList.objects.all().order_by('name')
|
||||
port_list = OuverturePortList.objects.all().order_by('name')
|
||||
return render(request, "machines/index_portlist.html", {'port_list':port_list})
|
||||
|
||||
@login_required
|
||||
|
@ -929,7 +1010,7 @@ def edit_portlist(request, pk):
|
|||
return redirect("/machines/index_portlist/")
|
||||
port_list = EditOuverturePortListForm(request.POST or None, instance=port_list_instance)
|
||||
port_formset = modelformset_factory(
|
||||
OuverturePort,
|
||||
OuverturePort,
|
||||
fields=('begin','end','protocole','io'),
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
|
@ -968,7 +1049,7 @@ def del_portlist(request, pk):
|
|||
def add_portlist(request):
|
||||
port_list = EditOuverturePortListForm(request.POST or None)
|
||||
port_formset = modelformset_factory(
|
||||
OuverturePort,
|
||||
OuverturePort,
|
||||
fields=('begin','end','protocole','io'),
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
|
|
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>
|
||||
{# Load CSS and JavaScript #}
|
||||
{% bootstrap_css %}
|
||||
<link href="/static/css/typeaheadjs.css" rel="stylesheet">
|
||||
|
||||
{% bootstrap_javascript %}
|
||||
<script src="/static/js/typeahead.js"></script>
|
||||
<script src="/static/js/handlebars.js"></script>
|
||||
<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